From e5d1a8bda7e2f0c6ea40f56469b3f71056b1294f Mon Sep 17 00:00:00 2001 From: otavepto <153766569+otavepto@users.noreply.github.com> Date: Fri, 3 May 2024 01:29:57 +0300 Subject: [PATCH] separate .h/.cpp files --- dll/appticket.cpp | 266 ++ dll/auth.cpp | 600 ++- dll/base.cpp | 15 +- dll/callsystem.cpp | 406 ++ dll/dll/appticket.h | 290 +- dll/dll/auth.h | 648 +-- dll/dll/base.h | 402 +- dll/dll/callsystem.h | 132 + dll/dll/common_includes.h | 2 +- dll/dll/dll.h | 5 + dll/dll/local_storage.h | 22 +- dll/dll/network.h | 86 +- dll/dll/settings.h | 123 +- dll/dll/settings_parser.h | 8 +- dll/dll/source_query.h | 6 +- dll/dll/steam_HTMLsurface.h | 395 +- dll/dll/steam_applist.h | 8 +- dll/dll/steam_apps.h | 24 +- dll/dll/steam_client.h | 120 +- dll/dll/steam_controller.h | 1417 ++---- dll/dll/steam_friends.h | 1640 ++----- dll/dll/steam_game_coordinator.h | 155 +- dll/dll/steam_gamesearch.h | 237 +- dll/dll/steam_gameserver.h | 46 +- dll/dll/steam_gameserverstats.h | 18 +- dll/dll/steam_http.h | 23 +- dll/dll/steam_inventory.h | 1306 ++---- dll/dll/steam_masterserver_updater.h | 248 +- dll/dll/steam_matchmaking.h | 1831 ++------ dll/dll/steam_matchmaking_servers.h | 91 +- dll/dll/steam_music.h | 15 +- dll/dll/steam_musicremote.h | 8 +- dll/dll/steam_networking.h | 1188 +---- dll/dll/steam_networking_messages.h | 512 +-- dll/dll/steam_networking_sockets.h | 3241 +++++-------- dll/dll/steam_networking_socketsserialized.h | 153 +- dll/dll/steam_networking_utils.h | 955 +--- dll/dll/steam_parental.h | 8 +- dll/dll/steam_parties.h | 209 +- dll/dll/steam_remote_storage.h | 1408 ++---- dll/dll/steam_remoteplay.h | 152 +- dll/dll/steam_screenshots.h | 23 +- dll/dll/steam_tv.h | 120 +- dll/dll/steam_ugc.h | 1737 ++----- dll/dll/steam_unified_messages.h | 121 +- dll/dll/steam_user.h | 733 +-- dll/dll/steam_user_stats.h | 24 +- dll/dll/steam_utils.h | 11 +- dll/dll/steam_video.h | 8 +- dll/dll/ugc_remote_storage_bridge.h | 26 +- dll/settings_parser.cpp | 13 + dll/source_query.cpp | 124 +- dll/steam_HTMLsurface.cpp | 363 ++ dll/steam_client.cpp | 23 +- dll/steam_controller.cpp | 1167 +++++ dll/steam_friends.cpp | 1291 ++++++ dll/steam_game_coordinator.cpp | 146 + dll/steam_gamesearch.cpp | 215 + dll/steam_gameserver.cpp | 8 +- dll/steam_inventory.cpp | 963 ++++ dll/steam_masterserver_updater.cpp | 224 + dll/steam_matchmaking.cpp | 1624 +++++++ dll/steam_matchmaking_servers.cpp | 414 +- dll/steam_networking.cpp | 972 ++++ dll/steam_networking_messages.cpp | 419 ++ dll/steam_networking_sockets.cpp | 2098 +++++++++ dll/steam_networking_socketsserialized.cpp | 155 + dll/steam_networking_utils.cpp | 730 +++ dll/steam_parties.cpp | 195 + dll/steam_remote_storage.cpp | 1187 +++++ dll/steam_remoteplay.cpp | 142 + dll/steam_screenshots.cpp | 1 + dll/steam_tv.cpp | 119 + dll/steam_ugc.cpp | 1429 ++++++ dll/steam_unified_messages.cpp | 119 + dll/steam_user.cpp | 528 +++ dll/steam_user_stats.cpp | 4308 +++++++++--------- dll/steam_utils.cpp | 8 +- dll/ugc_remote_storage_bridge.cpp | 142 +- overlay_experimental/overlay/steam_overlay.h | 6 +- overlay_experimental/steam_overlay.cpp | 15 +- 81 files changed, 22736 insertions(+), 17634 deletions(-) create mode 100644 dll/appticket.cpp create mode 100644 dll/callsystem.cpp create mode 100644 dll/dll/callsystem.h create mode 100644 dll/steam_HTMLsurface.cpp create mode 100644 dll/steam_controller.cpp create mode 100644 dll/steam_friends.cpp create mode 100644 dll/steam_game_coordinator.cpp create mode 100644 dll/steam_gamesearch.cpp create mode 100644 dll/steam_inventory.cpp create mode 100644 dll/steam_masterserver_updater.cpp create mode 100644 dll/steam_matchmaking.cpp create mode 100644 dll/steam_networking.cpp create mode 100644 dll/steam_networking_messages.cpp create mode 100644 dll/steam_networking_sockets.cpp create mode 100644 dll/steam_networking_socketsserialized.cpp create mode 100644 dll/steam_networking_utils.cpp create mode 100644 dll/steam_parties.cpp create mode 100644 dll/steam_remote_storage.cpp create mode 100644 dll/steam_remoteplay.cpp create mode 100644 dll/steam_tv.cpp create mode 100644 dll/steam_ugc.cpp create mode 100644 dll/steam_unified_messages.cpp create mode 100644 dll/steam_user.cpp diff --git a/dll/appticket.cpp b/dll/appticket.cpp new file mode 100644 index 00000000..7f0a45af --- /dev/null +++ b/dll/appticket.cpp @@ -0,0 +1,266 @@ +/* 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 + . */ + +#include "dll/appticket.h" + + +void AppTicketV1::Reset() +{ + TicketSize = 0; + TicketVersion = 0; + Unk2 = 0; + UserData.clear(); +} + +std::vector AppTicketV1::Serialize() const +{ + std::vector buffer{}; + uint8_t* pBuffer{}; + + buffer.resize(16 + UserData.size()); + pBuffer = buffer.data(); + *reinterpret_cast(pBuffer) = TicketSize; pBuffer += 4; + *reinterpret_cast(pBuffer) = TicketVersion; pBuffer += 4; + *reinterpret_cast(pBuffer) = UserData.size(); pBuffer += 4; + *reinterpret_cast(pBuffer) = Unk2; pBuffer += 4; + memcpy(pBuffer, UserData.data(), UserData.size()); + + return buffer; +} + +bool AppTicketV1::Deserialize(const uint8_t* pBuffer, size_t size) +{ + if (size < 16) + return false; + + uint32_t user_data_size; + + TicketSize = *reinterpret_cast(pBuffer); pBuffer += 4; + TicketVersion = *reinterpret_cast(pBuffer); pBuffer += 4; + user_data_size = *reinterpret_cast(pBuffer); pBuffer += 4; + + if (size < (user_data_size + 16)) + return false; + + Unk2 = *reinterpret_cast(pBuffer); pBuffer += 4; + UserData.resize(user_data_size); + memcpy(UserData.data(), pBuffer, user_data_size); + + return true; +} + + + +void AppTicketV2::Reset() +{ + TicketSize = 0; + TicketVersion = 0; + SteamID = 0; + AppID = 0; + Unk1 = 0; + Unk2 = 0; + TicketFlags = 0; + TicketIssueTime = 0; + TicketValidityEnd = 0; +} + +std::vector AppTicketV2::Serialize() const +{ + std::vector buffer{}; + uint8_t* pBuffer{}; + + buffer.resize(40); + pBuffer = buffer.data(); + *reinterpret_cast(pBuffer) = TicketSize; pBuffer += 4; + *reinterpret_cast(pBuffer) = TicketVersion; pBuffer += 4; + *reinterpret_cast(pBuffer) = SteamID; pBuffer += 8; + *reinterpret_cast(pBuffer) = AppID; pBuffer += 4; + *reinterpret_cast(pBuffer) = Unk1; pBuffer += 4; + *reinterpret_cast(pBuffer) = Unk2; pBuffer += 4; + *reinterpret_cast(pBuffer) = TicketFlags; pBuffer += 4; + *reinterpret_cast(pBuffer) = TicketIssueTime; pBuffer += 4; + *reinterpret_cast(pBuffer) = TicketValidityEnd; + + return buffer; +} + +bool AppTicketV2::Deserialize(const uint8_t* pBuffer, size_t size) +{ + if (size < 40) + return false; + + TicketSize = *reinterpret_cast(pBuffer); pBuffer += 4; + TicketVersion = *reinterpret_cast(pBuffer); pBuffer += 4; + SteamID = *reinterpret_cast(pBuffer); pBuffer += 8; + AppID = *reinterpret_cast(pBuffer); pBuffer += 4; + Unk1 = *reinterpret_cast(pBuffer); pBuffer += 4; + Unk2 = *reinterpret_cast(pBuffer); pBuffer += 4; + TicketFlags = *reinterpret_cast(pBuffer); pBuffer += 4; + TicketIssueTime = *reinterpret_cast(pBuffer); pBuffer += 4; + TicketValidityEnd = *reinterpret_cast(pBuffer); + + return true; +} + + + +void AppTicketV4::Reset() +{ + AppIDs.clear(); + HasVACStatus = false; + HasAppValue = false; +} + +std::vector AppTicketV4::Serialize() +{ + std::vector appids = AppIDs; + if (appids.size() == 0) { + appids.emplace_back(0); + } + + uint16_t appid_count = static_cast(appids.size() > 140 ? 140 : appids.size()); + size_t buffer_size = static_cast(appid_count) * 4ul + 2ul; + std::vector buffer{}; + uint8_t* pBuffer{}; + + if (HasAppValue) {// VACStatus + AppValue + buffer_size += 4; + if (!HasVACStatus) { + HasVACStatus = true; + VACStatus = 0; + } + } + + if (HasVACStatus) {// VACStatus only + buffer_size += 4; + } + + buffer.resize(buffer_size); + pBuffer = buffer.data(); + *reinterpret_cast(pBuffer) = appid_count; + pBuffer += 2; + + for (int i = 0; i < appid_count && i < 140; ++i) { + *reinterpret_cast(pBuffer) = appids[i]; + pBuffer += 4; + } + + if (HasVACStatus) { + *reinterpret_cast(pBuffer) = VACStatus; + pBuffer += 4; + } + + if (HasAppValue) { + *reinterpret_cast(pBuffer) = AppValue; + } + + return buffer; +} + +bool AppTicketV4::Deserialize(const uint8_t* pBuffer, size_t size) +{ + if (size < 2) + return false; + + uint16_t appid_count = *reinterpret_cast(pBuffer); + if (size < (appid_count * 4 + 2) || appid_count >= 140) + return false; + + AppIDs.resize(appid_count); + pBuffer += 2; + size -= 2; + for (int i = 0; i < appid_count; ++i) { + AppIDs[i] = *reinterpret_cast(pBuffer); + pBuffer += 4; + size -= 4; + } + + HasVACStatus = false; + HasAppValue = false; + + if (size < 4) + return true; + + HasVACStatus = true; + VACStatus = *reinterpret_cast(pBuffer); + pBuffer += 4; + size -= 4; + + if (size < 4) + return true; + + HasAppValue = true; + AppValue = *reinterpret_cast(pBuffer); + + return true; +} + + + +bool DecryptedAppTicket::DeserializeTicket(const uint8_t* pBuffer, size_t buf_size) +{ + if (!TicketV1.Deserialize(pBuffer, buf_size)) + return false; + + pBuffer += 16 + TicketV1.UserData.size(); + buf_size -= 16 + TicketV1.UserData.size(); + if (!TicketV2.Deserialize(pBuffer, buf_size)) + return false; + + if (TicketV2.TicketVersion > 2) { + pBuffer += 40; + buf_size -= 40; + if (!TicketV4.Deserialize(pBuffer, buf_size)) + return false; + } + + return true; +} + +std::vector DecryptedAppTicket::SerializeTicket() +{ + std::vector buffer{}; + + TicketV1.TicketSize = TicketV1.UserData.size() + 40 + 2 + ((TicketV4.AppIDs.size() == 0 ? 1 : TicketV4.AppIDs.size()) * 4) + (TicketV4.HasVACStatus ? 4 : 0) + (TicketV4.HasAppValue ? 4 : 0); + TicketV2.TicketSize = TicketV1.TicketSize - TicketV1.UserData.size(); + + buffer = TicketV1.Serialize(); + + auto v = TicketV2.Serialize(); + + buffer.insert(buffer.end(), v.begin(), v.end()); + v = TicketV4.Serialize(); + buffer.insert(buffer.end(), v.begin(), v.end()); + + return buffer; +} + + + +Steam_AppTicket::Steam_AppTicket(class Settings *settings) : + settings(settings) +{ + +} + +uint32 Steam_AppTicket::GetAppOwnershipTicketData( uint32 nAppID, void *pvBuffer, uint32 cbBufferLength, uint32 *piAppId, uint32 *piSteamId, uint32 *piSignature, uint32 *pcbSignature ) +{ + PRINT_DEBUG("TODO %u, %p, %u, %p, %p, %p, %p", nAppID, pvBuffer, cbBufferLength, piAppId, piSteamId, piSignature, pcbSignature); + std::lock_guard lock(global_mutex); + + return 0; +} diff --git a/dll/auth.cpp b/dll/auth.cpp index 9495198d..03550373 100644 --- a/dll/auth.cpp +++ b/dll/auth.cpp @@ -1,5 +1,17 @@ #include "dll/auth.h" +#define STEAM_ID_OFFSET_TICKET (4 + 8) +#define STEAM_TICKET_MIN_SIZE (4 + 8 + 8) +#define STEAM_TICKET_MIN_SIZE_NEW 170 + +#define STEAM_TICKET_PROCESS_TIME 0.03 + +//Conan Exiles doesn't work with 512 or 128, 256 seems to be the good size +// Usually steam send as 1024 (or recommend sending as that) +//Steam returns 234 +#define STEAM_AUTH_TICKET_SIZE 256 //234 + + static inline int generate_random_int() { int a; randombytes((char *)&a, sizeof(a)); @@ -24,7 +36,568 @@ static uint32_t get_ticket_count() { } -static void steam_auth_manager_ticket_callback(void *object, Common_Message *msg) +// source: https://github.com/Detanup01/stmsrv/blob/main/Cert/AppTicket.key +// thanks Detanup01 +const static std::string app_ticket_key = + "-----BEGIN PRIVATE KEY-----\n" + "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMITHOY6pfsvaGTI\n" + "llmilPa1+ev4BsUV0IW3+F/3pQlZ+o57CO1HbepSh2a37cbGUSehOVQ7lREPVXP3\n" + "UdyF5tU5IMytJef5N7euM5z2IG9IszeOReO87h2AmtlwGqnRj7qd0MeVxSAuUq7P\n" + "C/Ir1VyOg58+wAKxaPL18upylnGJAgMBAAECgYEAnKQQj0KG9VYuTCoaL/6pfPcj\n" + "4PEvhaM1yrfSIKMg8YtOT/G+IsWkUZyK7L1HjUhD+FiIjRQKHNrjfdYAnJz20Xom\n" + "k6iVt7ugihIne1Q3pGYG8TY9P1DPdN7zEnAVY1Bh2PAlqJWrif3v8v1dUGE/dYr2\n" + "U3M0JhvzO7VL1B/chIECQQDqW9G5azGMA/cL4jOg0pbj9GfxjJZeT7M2rBoIaRWP\n" + "C3ROndyb+BNahlKk6tbvqillvvMQQiSFGw/PbmCwtLL3AkEA0/79W0q9d3YCXQGW\n" + "k3hQvR8HEbxLmRaRF2gU4MOa5C0JqwsmxzdK4mKoJCpVAiu1gmFonLjn2hm8i+vK\n" + "b7hffwJAEiMpCACTxRJJfFH1TOz/YIT5xmfq+0GPzRtkqGH5mSh5x9vPxwJb/RWI\n" + "L9s85y90JLuyc/+qc+K0Rol0Ujip4QJAGLXVJEn+8ajAt8SSn5fbmV+/fDK9gRef\n" + "S+Im5NgH+ubBBL3lBD2Orfqf7K8+f2VG3+6oufPXmpV7Y7fVPdZ40wJALDujJXgi\n" + "XiCBSht1YScYjfmJh2/xZWh8/w+vs5ZTtrnW2FQvfvVDG9c1hrChhpvmA0QxdgWB\n" + "zSsAno/utcuB9w==\n" + "-----END PRIVATE KEY-----\n"; + + +static std::vector sign_auth_data(const std::string &private_key_content, const std::vector &data, size_t effective_data_len) +{ + std::vector signature{}; + + // Hash the data using SHA-1 + constexpr static int SHA1_DIGEST_LENGTH = 20; + uint8_t hash[SHA1_DIGEST_LENGTH]{}; + int result = mbedtls_sha1(data.data(), effective_data_len, hash); + if (result != 0) { +#ifndef EMU_RELEASE_BUILD + // we nedd a live object until the printf does its job, hence this special handling + std::string err_msg(256, 0); + mbedtls_strerror(result, &err_msg[0], err_msg.size()); + PRINT_DEBUG("failed to hash the data via SHA1: %s", err_msg.c_str()); +#endif + + return signature; + } + + mbedtls_entropy_context entropy_ctx; // entropy context for random number generation + mbedtls_entropy_init(&entropy_ctx); + + mbedtls_ctr_drbg_context ctr_drbg_ctx; // CTR-DRBG context for deterministic random number generation + mbedtls_ctr_drbg_init(&ctr_drbg_ctx); + + // seed the CTR-DRBG context with random numbers + result = mbedtls_ctr_drbg_seed(&ctr_drbg_ctx, mbedtls_entropy_func, &entropy_ctx, nullptr, 0); + if (mbedtls_ctr_drbg_seed(&ctr_drbg_ctx, mbedtls_entropy_func, &entropy_ctx, nullptr, 0) != 0) { + mbedtls_ctr_drbg_free(&ctr_drbg_ctx); + mbedtls_entropy_free(&entropy_ctx); + +#ifndef EMU_RELEASE_BUILD + // we nedd a live object until the printf does its job, hence this special handling + std::string err_msg(256, 0); + mbedtls_strerror(result, &err_msg[0], err_msg.size()); + PRINT_DEBUG("failed to seed the CTR-DRBG context: %s", err_msg.c_str()); +#endif + + return signature; + } + + mbedtls_pk_context private_key_ctx; // holds the parsed private key + mbedtls_pk_init(&private_key_ctx); + + result = mbedtls_pk_parse_key( + &private_key_ctx, // will hold the parsed private key + (const unsigned char *)private_key_content.c_str(), + private_key_content.size() + 1, // we MUST include the null terminator, otherwise this API returns an error! + nullptr, 0, // no password stuff, private key isn't protected + mbedtls_ctr_drbg_random, &ctr_drbg_ctx // random number generation function + the CTR-DRBG context it requires as an input + ); + + if (result != 0) { + mbedtls_pk_free(&private_key_ctx); + mbedtls_ctr_drbg_free(&ctr_drbg_ctx); + mbedtls_entropy_free(&entropy_ctx); + +#ifndef EMU_RELEASE_BUILD + // we nedd a live object until the printf does its job, hence this special handling + std::string err_msg(256, 0); + mbedtls_strerror(result, &err_msg[0], err_msg.size()); + PRINT_DEBUG("failed to parse private key: %s", err_msg.c_str()); +#endif + + return signature; + } + + // private key must be valid RSA key + if (mbedtls_pk_get_type(&private_key_ctx) != MBEDTLS_PK_RSA || // invalid type + mbedtls_pk_can_do(&private_key_ctx, MBEDTLS_PK_RSA) == 0) { // or initialized but not properly setup (maybe freed?) + mbedtls_pk_free(&private_key_ctx); + mbedtls_ctr_drbg_free(&ctr_drbg_ctx); + mbedtls_entropy_free(&entropy_ctx); + + PRINT_DEBUG("parsed key is not a valid RSA private key"); + return signature; + } + + // get the underlying RSA context from the parsed private key + mbedtls_rsa_context* rsa_ctx = mbedtls_pk_rsa(private_key_ctx); + + // resize the output buffer to accomodate the size of the private key + const size_t private_key_len = mbedtls_pk_get_len(&private_key_ctx); + if (private_key_len == 0) { // TODO must be 128 siglen + mbedtls_pk_free(&private_key_ctx); + mbedtls_ctr_drbg_free(&ctr_drbg_ctx); + mbedtls_entropy_free(&entropy_ctx); + + PRINT_DEBUG("failed to get private key (final buffer) length"); + return signature; + } + + PRINT_DEBUG("computed private key (final buffer) length = %zu", private_key_len); + signature.resize(private_key_len); + + // finally sign the computed hash using RSA and PKCS#1 padding + result = mbedtls_rsa_pkcs1_sign( + rsa_ctx, + mbedtls_ctr_drbg_random, &ctr_drbg_ctx, + MBEDTLS_MD_SHA1, // we used SHA1 to hash the data + sizeof(hash), hash, + signature.data() // output + ); + + mbedtls_pk_free(&private_key_ctx); + mbedtls_ctr_drbg_free(&ctr_drbg_ctx); + mbedtls_entropy_free(&entropy_ctx); + + if (result != 0) { + signature.clear(); + +#ifndef EMU_RELEASE_BUILD + // we nedd a live object until the printf does its job, hence this special handling + std::string err_msg(256, 0); + mbedtls_strerror(result, &err_msg[0], err_msg.size()); + PRINT_DEBUG("RSA signing failed: %s", err_msg.c_str()); +#endif + } + +#ifndef EMU_RELEASE_BUILD + // we nedd a live object until the printf does its job, hence this special handling + auto str = common_helpers::uint8_vector_to_hex_string(signature); + PRINT_DEBUG("final signature [%zu bytes]:\n %s", signature.size(), str.c_str()); +#endif + + return signature; +} + + + + +std::vector DLC::Serialize() const +{ + PRINT_DEBUG("AppId = %u, Licenses count = %zu", AppId, Licenses.size()); + + // we need this variable because we depend on the sizeof, must be 2 bytes + const uint16_t dlcs_licenses_count = (uint16_t)Licenses.size(); + const size_t dlcs_licenses_total_size = + Licenses.size() * sizeof(Licenses[0]); // count * element size + + const size_t total_size = + sizeof(AppId) + + sizeof(dlcs_licenses_count) + + dlcs_licenses_total_size; + + std::vector buffer{}; + buffer.resize(total_size); + + uint8_t* pBuffer = &buffer[0]; + +#define SER_VAR(v) \ +*reinterpret_cast::type *>(pBuffer) = v; \ +pBuffer += sizeof(v) + + SER_VAR(AppId); + SER_VAR(dlcs_licenses_count); + for(uint32_t dlc_license : Licenses) { + SER_VAR(dlc_license); + } + +#undef SER_VAR + + PRINT_DEBUG("final size = %zu", buffer.size()); + return buffer; +} + +std::vector AppTicketGC::Serialize() const +{ + const uint64_t steam_id = id.ConvertToUint64(); + + // must be 52 + constexpr size_t total_size = + sizeof(STEAM_APPTICKET_GCLen) + + sizeof(GCToken) + + sizeof(steam_id) + + sizeof(ticketGenDate) + + sizeof(STEAM_APPTICKET_SESSIONLEN) + + sizeof(one) + + sizeof(two) + + sizeof(ExternalIP) + + sizeof(InternalIP) + + sizeof(TimeSinceStartup) + + sizeof(TicketGeneratedCount); + + // check the size at compile time, we must ensure the correct size +#ifndef EMU_RELEASE_BUILD + static_assert( + total_size == 52, + "AUTH::AppTicketGC::SER calculated size of serialized data != 52 bytes, your compiler has some incorrect sizes" + ); +#endif + + PRINT_DEBUG( + "\n" + " GCToken: " "%" PRIu64 "\n" + " user steam_id: " "%" PRIu64 "\n" + " ticketGenDate: %u\n" + " ExternalIP: 0x%08X, InternalIP: 0x%08X\n" + " TimeSinceStartup: %u, TicketGeneratedCount: %u\n" + " SER size = %zu", + + GCToken, + steam_id, + ticketGenDate, + ExternalIP, InternalIP, + TimeSinceStartup, TicketGeneratedCount, + total_size + ); + + std::vector buffer{}; + buffer.resize(total_size); + + uint8_t* pBuffer = &buffer[0]; + +#define SER_VAR(v) \ +*reinterpret_cast::type *>(pBuffer) = v; \ +pBuffer += sizeof(v) + + SER_VAR(STEAM_APPTICKET_GCLen); + SER_VAR(GCToken); + SER_VAR(steam_id); + SER_VAR(ticketGenDate); + SER_VAR(STEAM_APPTICKET_SESSIONLEN); + SER_VAR(one); + SER_VAR(two); + SER_VAR(ExternalIP); + SER_VAR(InternalIP); + SER_VAR(TimeSinceStartup); + SER_VAR(TicketGeneratedCount); + +#undef SER_VAR + +#ifndef EMU_RELEASE_BUILD + // we nedd a live object until the printf does its job, hence this special handling + auto str = common_helpers::uint8_vector_to_hex_string(buffer); + PRINT_DEBUG("final data [%zu bytes]:\n %s", buffer.size(), str.c_str()); +#endif + + return buffer; +} + +std::vector AppTicket::Serialize() const +{ + const uint64_t steam_id = id.ConvertToUint64(); + + PRINT_DEBUG( + "\n" + " Version: %u\n" + " user steam_id: " "%" PRIu64 "\n" + " AppId: %u\n" + " ExternalIP: 0x%08X, InternalIP: 0x%08X\n" + " TicketGeneratedDate: %u, TicketGeneratedExpireDate: %u\n" + " Licenses count: %zu, DLCs count: %zu", + + Version, + steam_id, + AppId, + ExternalIP, InternalIP, + TicketGeneratedDate, TicketGeneratedExpireDate, + Licenses.size(), DLCs.size() + ); + + // we need this variable because we depend on the sizeof, must be 2 bytes + const uint16_t licenses_count = (uint16_t)Licenses.size(); + const size_t licenses_total_size = + Licenses.size() * sizeof(Licenses[0]); // total count * element size + + // we need this variable because we depend on the sizeof, must be 2 bytes + const uint16_t dlcs_count = (uint16_t)DLCs.size(); + size_t dlcs_total_size = 0; + std::vector> serialized_dlcs{}; + for (const DLC &dlc : DLCs) { + auto dlc_ser = dlc.Serialize(); + dlcs_total_size += dlc_ser.size(); + serialized_dlcs.push_back(dlc_ser); + } + + //padding + constexpr uint16_t padding = (uint16_t)0; + + // must be 42 + constexpr size_t static_fields_size = + sizeof(Version) + + sizeof(steam_id) + + sizeof(AppId) + + sizeof(ExternalIP) + + sizeof(InternalIP) + + sizeof(AlwaysZero) + + sizeof(TicketGeneratedDate) + + sizeof(TicketGeneratedExpireDate) + + + sizeof(licenses_count) + + sizeof(dlcs_count) + + + sizeof(padding); + + // check the size at compile time, we must ensure the correct size +#ifndef EMU_RELEASE_BUILD + static_assert( + static_fields_size == 42, + "AUTH::AppTicket::SER calculated size of serialized data != 42 bytes, your compiler has some incorrect sizes" + ); +#endif + + const size_t total_size = + static_fields_size + + licenses_total_size + + dlcs_total_size; + + PRINT_DEBUG("final size = %zu", total_size); + + std::vector buffer{}; + buffer.resize(total_size); + uint8_t* pBuffer = &buffer[0]; + +#define SER_VAR(v) \ +*reinterpret_cast::type *>(pBuffer) = v; \ +pBuffer += sizeof(v) + + SER_VAR(Version); + SER_VAR(steam_id); + SER_VAR(AppId); + SER_VAR(ExternalIP); + SER_VAR(InternalIP); + SER_VAR(AlwaysZero); + SER_VAR(TicketGeneratedDate); + SER_VAR(TicketGeneratedExpireDate); + +#ifndef EMU_RELEASE_BUILD + { + // we nedd a live object until the printf does its job, hence this special handling + auto str = common_helpers::uint8_vector_to_hex_string(buffer); + PRINT_DEBUG("(before licenses + DLCs):\n %s", str.c_str()); + } +#endif + + /* + * layout of licenses: + * ------------------------ + * 2 bytes: count of licenses + * ------------------------ + * [ + * ------------------------ + * | 4 bytes: license element + * ------------------------ + * ] + */ + SER_VAR(licenses_count); + for(uint32_t license : Licenses) { + SER_VAR(license); + } + + /* + * layout of DLCs: + * ------------------------ + * | 2 bytes: count of DLCs + * ------------------------ + * [ + * ------------------------ + * | 4 bytes: app id + * ------------------------ + * | 2 bytes: DLC licenses count + * ------------------------ + * [ + * 4 bytes: DLC license element + * ] + * ] + */ + SER_VAR(dlcs_count); + for (const auto &dlc_ser : serialized_dlcs){ + memcpy(pBuffer, dlc_ser.data(), dlc_ser.size()); + pBuffer += dlc_ser.size(); + } + + //padding + SER_VAR(padding); + +#undef SER_VAR + +#ifndef EMU_RELEASE_BUILD + { + // we nedd a live object until the printf does its job, hence this special handling + auto str = common_helpers::uint8_vector_to_hex_string(buffer); + PRINT_DEBUG("final data [%zu bytes]:\n %s", buffer.size(), str.c_str()); + } +#endif + + return buffer; +} + +std::vector Auth_Data::Serialize() const +{ + /* + * layout of Auth_Data with GC: + * ------------------------ + * X bytes: GC data blob (currently 52 bytes) + * ------------------------ + * 4 bytes: remaining Auth_Data blob size (4 + Y + Z) + * ------------------------ + * 4 bytes: size of ticket data layout (not blob!, hence blob + 4) + * ------------------------ + * Y bytes: ticket data blob + * ------------------------ + * Z bytes: App Ticket signature + * ------------------------ + * + * total layout length = X + 4 + 4 + Y + Z + */ + + /* + * layout of Auth_Data without GC: + * ------------------------ + * 4 bytes: size of ticket data layout (not blob!, hence blob + 4) + * ------------------------ + * Y bytes: ticket data blob + * ------------------------ + * Z bytes: App Ticket signature + * ------------------------ + * + * total layout length = 4 + Y + Z + */ + const uint64_t steam_id = id.ConvertToUint64(); + + PRINT_DEBUG( + "\n" + " HasGC: %u\n" + " user steam_id: " "%" PRIu64 "\n" + " number: " "%" PRIu64 , + + (int)HasGC, + steam_id, + number + ); + + /* + * layout of ticket data: + * ------------------------ + * 4 bytes: size of ticket data layout (not blob!, hence blob + 4) + * ------------------------ + * Y bytes: ticket data blob + * ------------------------ + * + * total layout length = 4 + Y + */ + std::vector tickedData = Ticket.Serialize(); + // we need this variable because we depend on the sizeof, must be 4 bytes + const uint32_t ticket_data_layout_length = + sizeof(uint32_t) + // size of this uint32_t because it is included! + (uint32_t)tickedData.size(); + + size_t total_size_without_siglen = ticket_data_layout_length; + + std::vector GCData{}; + size_t gc_data_layout_length = 0; + if (HasGC) { + /* + * layout of GC data: + * ------------------------ + * X bytes: GC data blob (currently 52 bytes) + * ------------------------ + * 4 bytes: remaining Auth_Data blob size + * ------------------------ + * + * total layout length = X + 4 + */ + GCData = GC.Serialize(); + gc_data_layout_length += + GCData.size() + + sizeof(uint32_t); + + total_size_without_siglen += gc_data_layout_length; + } + + const size_t final_buffer_size = total_size_without_siglen + STEAM_APPTICKET_SIGLEN; + PRINT_DEBUG("size without sig len = %zu, size with sig len (final size) = %zu", + total_size_without_siglen, + final_buffer_size + ); + + std::vector buffer; + buffer.resize(final_buffer_size); + + uint8_t* pBuffer = &buffer[0]; + +#define SER_VAR(v) \ +*reinterpret_cast::type *>(pBuffer) = v; \ +pBuffer += sizeof(v) + + // serialize the GC data first + if (HasGC) { + memcpy(pBuffer, GCData.data(), GCData.size()); + pBuffer += GCData.size(); + + // when GC data is written (HasGC), + // the next 4 bytes after the GCData will be the length of the remaining data in the final buffer + // i.e. final buffer size - length of GCData layout + // i.e. ticket data length + STEAM_APPTICKET_SIGLEN + // + // notice that we subtract the entire layout length, not just GCData.size(), + // otherwise these next 4 bytes will include themselves! + uint32_t remaining_length = (uint32_t)(final_buffer_size - gc_data_layout_length); + SER_VAR(remaining_length); + } + + // serialize the ticket data + SER_VAR(ticket_data_layout_length); + memcpy(pBuffer, tickedData.data(), tickedData.size()); + +#ifndef EMU_RELEASE_BUILD + { + // we nedd a live object until the printf does its job, hence this special handling + auto str = common_helpers::uint8_vector_to_hex_string(buffer); + PRINT_DEBUG("final data (before signature) [%zu bytes]:\n %s", buffer.size(), str.c_str()); + } +#endif + + //Todo make a signature + std::vector signature = sign_auth_data(app_ticket_key, tickedData, total_size_without_siglen); + if (signature.size() == STEAM_APPTICKET_SIGLEN) { + memcpy(buffer.data() + total_size_without_siglen, signature.data(), signature.size()); + +#ifndef EMU_RELEASE_BUILD + { + // we nedd a live object until the printf does its job, hence this special handling + auto str = common_helpers::uint8_vector_to_hex_string(buffer); + PRINT_DEBUG("final data (after signature) [%zu bytes]:\n %s", buffer.size(), str.c_str()); + } +#endif + + } else { + PRINT_DEBUG("signature size [%zu] is invalid", signature.size()); + } + +#undef SER_VAR + + return buffer; +} + + + +void Auth_Manager::ticket_callback(void *object, Common_Message *msg) { // PRINT_DEBUG_ENTRY(); @@ -32,22 +605,22 @@ static void steam_auth_manager_ticket_callback(void *object, Common_Message *msg auth_manager->Callback(msg); } -Auth_Manager::Auth_Manager(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks) { +Auth_Manager::Auth_Manager(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks) +{ this->network = network; this->settings = settings; this->callbacks = callbacks; - this->network->setCallback(CALLBACK_ID_AUTH_TICKET, settings->get_local_steam_id(), &steam_auth_manager_ticket_callback, this); - this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &steam_auth_manager_ticket_callback, this); + this->network->setCallback(CALLBACK_ID_AUTH_TICKET, settings->get_local_steam_id(), &Auth_Manager::ticket_callback, this); + this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Auth_Manager::ticket_callback, this); } Auth_Manager::~Auth_Manager() { - this->network->rmCallback(CALLBACK_ID_AUTH_TICKET, settings->get_local_steam_id(), &steam_auth_manager_ticket_callback, this); - this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &steam_auth_manager_ticket_callback, this); + this->network->rmCallback(CALLBACK_ID_AUTH_TICKET, settings->get_local_steam_id(), &Auth_Manager::ticket_callback, this); + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Auth_Manager::ticket_callback, this); } -#define STEAM_TICKET_PROCESS_TIME 0.03 void Auth_Manager::launch_callback(CSteamID id, EAuthSessionResponse resp, double delay) { @@ -72,10 +645,6 @@ void Auth_Manager::launch_callback_gs(CSteamID id, bool approved) } } -#define STEAM_ID_OFFSET_TICKET (4 + 8) -#define STEAM_TICKET_MIN_SIZE (4 + 8 + 8) -#define STEAM_TICKET_MIN_SIZE_NEW 170 - Auth_Data Auth_Manager::getTicketData( void *pTicket, int cbMaxTicket, uint32 *pcbTicket ) { @@ -153,11 +722,6 @@ Auth_Data Auth_Manager::getTicketData( void *pTicket, int cbMaxTicket, uint32 *p return ticket_data; } -//Conan Exiles doesn't work with 512 or 128, 256 seems to be the good size -// Usually steam send as 1024 (or recommend sending as that) -//Steam returns 234 -#define STEAM_AUTH_TICKET_SIZE 256 //234 - HAuthTicket Auth_Manager::getTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket ) { if (settings->enable_new_app_ticket) @@ -301,6 +865,8 @@ bool Auth_Manager::endAuth(CSteamID id) return erased; } + + void Auth_Manager::Callback(Common_Message *msg) { if (msg->has_low_level()) { @@ -338,4 +904,4 @@ void Auth_Manager::Callback(Common_Message *msg) } } } -} \ No newline at end of file +} diff --git a/dll/base.cpp b/dll/base.cpp index 2e84c438..589842e1 100644 --- a/dll/base.cpp +++ b/dll/base.cpp @@ -17,6 +17,12 @@ #include "dll/base.h" +std::recursive_mutex global_mutex{}; +// some arbitrary counter/time for reference +const std::chrono::time_point startup_counter = std::chrono::high_resolution_clock::now(); +const std::chrono::time_point startup_time = std::chrono::system_clock::now(); + + #ifdef __WINDOWS__ void randombytes(char *buf, size_t size) @@ -93,11 +99,6 @@ bool set_env_variable(const std::string &name, const std::string &value) #endif -std::recursive_mutex global_mutex{}; - -// some arbitrary counter/time for reference -const std::chrono::time_point startup_counter = std::chrono::high_resolution_clock::now(); -const std::chrono::time_point startup_time = std::chrono::system_clock::now(); #ifndef EMU_RELEASE_BUILD const std::string dbg_log_file = get_full_program_path() + "STEAM_LOG.txt"; @@ -147,10 +148,6 @@ CSteamID generate_steam_id_lobby() return CSteamID(generate_account_id(), k_EChatInstanceFlagLobby | k_EChatInstanceFlagMMSLobby, k_EUniversePublic, k_EAccountTypeChat); } -/// @brief Check for a timeout given some initial timepoint and a timeout in sec. -/// @param old The initial timepoint which will be compared against current time -/// @param timeout The max allowed time in seconds -/// @return true if the timepoint has exceeded the max allowed timeout, false otherwise bool check_timedout(std::chrono::high_resolution_clock::time_point old, double timeout) { if (timeout == 0.0) return true; diff --git a/dll/callsystem.cpp b/dll/callsystem.cpp new file mode 100644 index 00000000..c52bf560 --- /dev/null +++ b/dll/callsystem.cpp @@ -0,0 +1,406 @@ +/* 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 + . */ + +#include "dll/callsystem.h" + + +void CCallbackMgr::SetRegister(class CCallbackBase *pCallback, int iCallback) +{ + pCallback->m_nCallbackFlags |= CCallbackBase::k_ECallbackFlagsRegistered; + pCallback->m_iCallback = iCallback; +}; + +void CCallbackMgr::SetUnregister(class CCallbackBase *pCallback) +{ + if (pCallback) + pCallback->m_nCallbackFlags &= !CCallbackBase::k_ECallbackFlagsRegistered; +}; + +bool CCallbackMgr::isServer(class CCallbackBase *pCallback) +{ + return (pCallback->m_nCallbackFlags & CCallbackBase::k_ECallbackFlagsGameServer) != 0; +}; + + + +Steam_Call_Result::Steam_Call_Result(SteamAPICall_t a, int icb, void *r, unsigned int s, double r_in, bool run_cc_cb) +{ + api_call = a; + result.resize(s); + if (s > 0 && r != NULL) { + memcpy(&(result[0]), r, s); + } + run_in = r_in; + run_call_completed_cb = run_cc_cb; + iCallback = icb; + created = std::chrono::high_resolution_clock::now(); +} + +bool Steam_Call_Result::operator==(const struct Steam_Call_Result& other) const +{ + return other.api_call == api_call && other.callbacks == callbacks; +} + +bool Steam_Call_Result::timed_out() const +{ + return check_timedout(created, STEAM_CALLRESULT_TIMEOUT); +} + +bool Steam_Call_Result::call_completed() const +{ + return (!reserved) && check_timedout(created, run_in); +} + +bool Steam_Call_Result::can_execute() const +{ + return (!to_delete) && call_completed() && (has_cb() || check_timedout(created, STEAM_CALLRESULT_WAIT_FOR_CB)); +} + +bool Steam_Call_Result::has_cb() const +{ + return callbacks.size() > 0; +} + + + +void SteamCallResults::addCallCompleted(class CCallbackBase *cb) +{ + if (std::find(completed_callbacks.begin(), completed_callbacks.end(), cb) == completed_callbacks.end()) { + completed_callbacks.push_back(cb); + } +} + +void SteamCallResults::rmCallCompleted(class CCallbackBase *cb) +{ + auto c = std::find(completed_callbacks.begin(), completed_callbacks.end(), cb); + if (c != completed_callbacks.end()) { + completed_callbacks.erase(c); + } +} + +void SteamCallResults::addCallBack(SteamAPICall_t api_call, class CCallbackBase *cb) +{ + auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; }); + if (cb_result != callresults.end()) { + cb_result->callbacks.push_back(cb); + CCallbackMgr::SetRegister(cb, cb->GetICallback()); + } +} + +bool SteamCallResults::exists(SteamAPICall_t api_call) const +{ + auto cr = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { + return item.api_call == api_call; + }); + if (callresults.end() == cr) return false; + if (!cr->call_completed()) return false; + return true; +} + +bool SteamCallResults::callback_result(SteamAPICall_t api_call, void *copy_to, unsigned int size) +{ + auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { + return item.api_call == api_call; + }); + if (cb_result != callresults.end()) { + if (!cb_result->call_completed()) return false; + if (cb_result->result.size() > size) return false; + + memcpy(copy_to, &(cb_result->result[0]), cb_result->result.size()); + cb_result->to_delete = true; + return true; + } else { + return false; + } +} + +void SteamCallResults::rmCallBack(SteamAPICall_t api_call, class CCallbackBase *cb) +{ + auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; }); + if (cb_result != callresults.end()) { + auto it = std::find(cb_result->callbacks.begin(), cb_result->callbacks.end(), cb); + if (it != cb_result->callbacks.end()) { + cb_result->callbacks.erase(it); + CCallbackMgr::SetUnregister(cb); + } + } +} + +void SteamCallResults::rmCallBack(class CCallbackBase *cb) +{ + //TODO: check if callback is callback or call result? + for (auto & cr: callresults) { + auto it = std::find(cr.callbacks.begin(), cr.callbacks.end(), cb); + if (it != cr.callbacks.end()) { + cr.callbacks.erase(it); + } + + if (cr.callbacks.size() == 0) { + cr.to_delete = true; + } + } +} + +SteamAPICall_t SteamCallResults::addCallResult(SteamAPICall_t api_call, int iCallback, void *result, unsigned int size, double timeout, bool run_call_completed_cb) +{ + PRINT_DEBUG("%i", iCallback); + auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; }); + if (cb_result != callresults.end()) { + if (cb_result->reserved) { + std::chrono::high_resolution_clock::time_point created = cb_result->created; + std::vector temp_cbs = cb_result->callbacks; + *cb_result = Steam_Call_Result(api_call, iCallback, result, size, timeout, run_call_completed_cb); + cb_result->callbacks = temp_cbs; + cb_result->created = created; + return cb_result->api_call; + } + } else { + struct Steam_Call_Result res = Steam_Call_Result(api_call, iCallback, result, size, timeout, run_call_completed_cb); + callresults.push_back(res); + return callresults.back().api_call; + } + + PRINT_DEBUG("ERROR"); + return k_uAPICallInvalid; +} + +SteamAPICall_t SteamCallResults::reserveCallResult() +{ + struct Steam_Call_Result res = Steam_Call_Result(generate_steam_api_call_id(), 0, NULL, 0, 0.0, true); + res.reserved = true; + callresults.push_back(res); + return callresults.back().api_call; +} + +SteamAPICall_t SteamCallResults::addCallResult(int iCallback, void *result, unsigned int size, double timeout, bool run_call_completed_cb) +{ + return addCallResult(generate_steam_api_call_id(), iCallback, result, size, timeout, run_call_completed_cb); +} + +void SteamCallResults::setCbAll(void (*cb_all)(std::vector result, int callback)) +{ + this->cb_all = cb_all; +} + +void SteamCallResults::runCallResults() +{ + unsigned long current_size = callresults.size(); + for (unsigned i = 0; i < current_size; ++i) { + unsigned index = i; + + if (!callresults[index].to_delete) { + if (callresults[index].can_execute()) { + std::vector result = callresults[index].result; + SteamAPICall_t api_call = callresults[index].api_call; + bool run_call_completed_cb = callresults[index].run_call_completed_cb; + int iCallback = callresults[index].iCallback; + if (run_call_completed_cb) { + callresults[index].run_call_completed_cb = false; + } + + callresults[index].to_delete = true; + if (callresults[index].has_cb()) { + std::vector temp_cbs = callresults[index].callbacks; + for (auto & cb : temp_cbs) { + PRINT_DEBUG("Calling callresult %p %i", cb, cb->GetICallback()); + global_mutex.unlock(); + //TODO: unlock relock doesn't work if mutex was locked more than once. + if (run_call_completed_cb) { //run the right function depending on if it's a callback or a call result. + cb->Run(&(result[0]), false, api_call); + } else { + cb->Run(&(result[0])); + } + //COULD BE DELETED SO DON'T TOUCH CB + global_mutex.lock(); + PRINT_DEBUG("callresult done"); + } + } + + if (run_call_completed_cb) { + //can it happen that one is removed during the callback? + std::vector callbacks = completed_callbacks; + SteamAPICallCompleted_t data{}; + data.m_hAsyncCall = api_call; + data.m_iCallback = iCallback; + data.m_cubParam = result.size(); + + for (auto & cb: callbacks) { + PRINT_DEBUG("Call complete cb %i %p %llu", iCallback, cb, api_call); + //TODO: check if this is a problem or not. + SteamAPICallCompleted_t temp = data; + global_mutex.unlock(); + cb->Run(&temp); + global_mutex.lock(); + } + + if (cb_all) { + std::vector res; + res.resize(sizeof(data)); + memcpy(&(res[0]), &data, sizeof(data)); + cb_all(res, data.k_iCallback); + } + } else { + if (cb_all) { + cb_all(result, iCallback); + } + } + } else { + if (callresults[index].timed_out()) { + callresults[index].to_delete = true; + } + } + } + } + + PRINT_DEBUG("erase to_delete"); + auto c = std::begin(callresults); + while (c != std::end(callresults)) { + if (c->to_delete) { + if (c->timed_out()) { + PRINT_DEBUG("removed callresult %i", c->iCallback); + c = callresults.erase(c); + } else { + ++c; + } + } else { + ++c; + } + } +} + + + +SteamCallBacks::SteamCallBacks(SteamCallResults *results) +{ + this->results = results; +} + +void SteamCallBacks::addCallBack(int iCallback, class CCallbackBase *cb) +{ + PRINT_DEBUG("%i", iCallback); + if (iCallback == SteamAPICallCompleted_t::k_iCallback) { + results->addCallCompleted(cb); + CCallbackMgr::SetRegister(cb, iCallback); + return; + } + + if (std::find(callbacks[iCallback].callbacks.begin(), callbacks[iCallback].callbacks.end(), cb) == callbacks[iCallback].callbacks.end()) { + callbacks[iCallback].callbacks.push_back(cb); + CCallbackMgr::SetRegister(cb, iCallback); + for (auto & res: callbacks[iCallback].results) { + //TODO: timeout? + SteamAPICall_t api_id = results->addCallResult(iCallback, &(res[0]), res.size(), 0.0, false); + results->addCallBack(api_id, cb); + } + } +} + +void SteamCallBacks::addCBResult(int iCallback, void *result, unsigned int size, double timeout, bool dont_post_if_already) +{ + if (dont_post_if_already) { + for (auto & r : callbacks[iCallback].results) { + if (r.size() == size) { + if (memcmp(&(r[0]), result, size) == 0) { + //cb already posted + return; + } + } + } + } + + std::vector temp{}; + temp.resize(size); + memcpy(&(temp[0]), result, size); + callbacks[iCallback].results.push_back(temp); + for (auto cb: callbacks[iCallback].callbacks) { + SteamAPICall_t api_id = results->addCallResult(iCallback, result, size, timeout, false); + results->addCallBack(api_id, cb); + } + + if (callbacks[iCallback].callbacks.empty()) { + results->addCallResult(iCallback, result, size, timeout, false); + } +} + +void SteamCallBacks::addCBResult(int iCallback, void *result, unsigned int size) +{ + addCBResult(iCallback, result, size, DEFAULT_CB_TIMEOUT, false); +} + +void SteamCallBacks::addCBResult(int iCallback, void *result, unsigned int size, bool dont_post_if_already) +{ + addCBResult(iCallback, result, size, DEFAULT_CB_TIMEOUT, dont_post_if_already); +} + +void SteamCallBacks::addCBResult(int iCallback, void *result, unsigned int size, double timeout) +{ + addCBResult(iCallback, result, size, timeout, false); +} + +void SteamCallBacks::rmCallBack(int iCallback, class CCallbackBase *cb) +{ + if (iCallback == SteamAPICallCompleted_t::k_iCallback) { + results->rmCallCompleted(cb); + CCallbackMgr::SetUnregister(cb); + return; + } + + auto c = std::find(callbacks[iCallback].callbacks.begin(), callbacks[iCallback].callbacks.end(), cb); + if (c != callbacks[iCallback].callbacks.end()) { + callbacks[iCallback].callbacks.erase(c); + CCallbackMgr::SetUnregister(cb); + results->rmCallBack(cb); + } +} + +void SteamCallBacks::runCallBacks() +{ + for (auto & c : callbacks) { + c.second.results.clear(); + } +} + + + +void RunEveryRunCB::add(void (*cb)(void *object), void *object) +{ + remove(cb, object); + RunCBs rcb; + rcb.function = cb; + rcb.object = object; + cbs.push_back(rcb); +} + +void RunEveryRunCB::remove(void (*cb)(void *object), void *object) +{ + auto c = std::begin(cbs); + while (c != std::end(cbs)) { + if (c->function == cb && c->object == object) { + c = cbs.erase(c); + } else { + ++c; + } + } +} + +void RunEveryRunCB::run() const +{ + std::vector temp_cbs = cbs; + for (auto c : temp_cbs) { + c.function(c.object); + } +} diff --git a/dll/dll/appticket.h b/dll/dll/appticket.h index 6694a750..479f9508 100644 --- a/dll/dll/appticket.h +++ b/dll/dll/appticket.h @@ -24,55 +24,16 @@ struct AppTicketV1 { // Total ticket size - 16 - uint32_t TicketSize; - uint32_t TicketVersion; - uint32_t Unk2; - std::vector UserData; + uint32_t TicketSize{}; + uint32_t TicketVersion{}; + uint32_t Unk2{}; + std::vector UserData{}; - void Reset() - { - TicketSize = 0; - TicketVersion = 0; - Unk2 = 0; - UserData.clear(); - } + void Reset(); - std::vector Serialize() - { - std::vector buffer; - uint8_t* pBuffer; + std::vector Serialize() const; - buffer.resize(16 + UserData.size()); - pBuffer = buffer.data(); - *reinterpret_cast(pBuffer) = TicketSize; pBuffer += 4; - *reinterpret_cast(pBuffer) = TicketVersion; pBuffer += 4; - *reinterpret_cast(pBuffer) = UserData.size(); pBuffer += 4; - *reinterpret_cast(pBuffer) = Unk2; pBuffer += 4; - memcpy(pBuffer, UserData.data(), UserData.size()); - - return buffer; - } - - bool Deserialize(const uint8_t* pBuffer, size_t size) - { - if (size < 16) - return false; - - uint32_t user_data_size; - - TicketSize = *reinterpret_cast(pBuffer); pBuffer += 4; - TicketVersion = *reinterpret_cast(pBuffer); pBuffer += 4; - user_data_size = *reinterpret_cast(pBuffer); pBuffer += 4; - - if (size < (user_data_size + 16)) - return false; - - Unk2 = *reinterpret_cast(pBuffer); pBuffer += 4; - UserData.resize(user_data_size); - memcpy(UserData.data(), pBuffer, user_data_size); - - return true; - } + bool Deserialize(const uint8_t* pBuffer, size_t size); //inline uint32_t TicketSize() { return *reinterpret_cast(_Buffer); } //inline uint32_t TicketVersion(){ return *reinterpret_cast(reinterpret_cast(_Buffer) + 4); } @@ -84,69 +45,24 @@ struct AppTicketV1 struct AppTicketV2 { // Totals ticket size - 16 - TicketV1::UserData.size() - uint32_t TicketSize; - uint32_t TicketVersion; - uint64_t SteamID; - uint32_t AppID; - uint32_t Unk1; - uint32_t Unk2; - uint32_t TicketFlags; - uint32_t TicketIssueTime; - uint32_t TicketValidityEnd; + uint32_t TicketSize{}; + uint32_t TicketVersion{}; + uint64_t SteamID{}; + uint32_t AppID{}; + uint32_t Unk1{}; + uint32_t Unk2{}; + uint32_t TicketFlags{}; + uint32_t TicketIssueTime{}; + uint32_t TicketValidityEnd{}; - static constexpr uint32_t LicenseBorrowed = 0x00000002; // Bit 1: IsLicenseBorrowed - static constexpr uint32_t LicenseTemporary = 0x00000004; // Bit 2: IsLicenseTemporary + static constexpr const uint32_t LicenseBorrowed = 0x00000002; // Bit 1: IsLicenseBorrowed + static constexpr const uint32_t LicenseTemporary = 0x00000004; // Bit 2: IsLicenseTemporary - void Reset() - { - TicketSize = 0; - TicketVersion = 0; - SteamID = 0; - AppID = 0; - Unk1 = 0; - Unk2 = 0; - TicketFlags = 0; - TicketIssueTime = 0; - TicketValidityEnd = 0; - } + void Reset(); - std::vector Serialize() - { - std::vector buffer; - uint8_t* pBuffer; + std::vector Serialize() const; - buffer.resize(40); - pBuffer = buffer.data(); - *reinterpret_cast(pBuffer) = TicketSize; pBuffer += 4; - *reinterpret_cast(pBuffer) = TicketVersion; pBuffer += 4; - *reinterpret_cast(pBuffer) = SteamID; pBuffer += 8; - *reinterpret_cast(pBuffer) = AppID; pBuffer += 4; - *reinterpret_cast(pBuffer) = Unk1; pBuffer += 4; - *reinterpret_cast(pBuffer) = Unk2; pBuffer += 4; - *reinterpret_cast(pBuffer) = TicketFlags; pBuffer += 4; - *reinterpret_cast(pBuffer) = TicketIssueTime; pBuffer += 4; - *reinterpret_cast(pBuffer) = TicketValidityEnd; - - return buffer; - } - - bool Deserialize(const uint8_t* pBuffer, size_t size) - { - if (size < 40) - return false; - - TicketSize = *reinterpret_cast(pBuffer); pBuffer += 4; - TicketVersion = *reinterpret_cast(pBuffer); pBuffer += 4; - SteamID = *reinterpret_cast(pBuffer); pBuffer += 8; - AppID = *reinterpret_cast(pBuffer); pBuffer += 4; - Unk1 = *reinterpret_cast(pBuffer); pBuffer += 4; - Unk2 = *reinterpret_cast(pBuffer); pBuffer += 4; - TicketFlags = *reinterpret_cast(pBuffer); pBuffer += 4; - TicketIssueTime = *reinterpret_cast(pBuffer); pBuffer += 4; - TicketValidityEnd = *reinterpret_cast(pBuffer); - - return true; - } + bool Deserialize(const uint8_t* pBuffer, size_t size); //inline uint32_t TicketSize() { return *reinterpret_cast(_Buffer); } //inline uint32_t TicketVersion() { return *reinterpret_cast(reinterpret_cast(_Buffer) + 4); } @@ -161,109 +77,18 @@ struct AppTicketV2 struct AppTicketV4 { - std::vector AppIDs; + std::vector AppIDs{}; bool HasVACStatus = false; - uint32_t VACStatus; + uint32_t VACStatus{}; bool HasAppValue = false; - uint32_t AppValue; + uint32_t AppValue{}; - void Reset() - { - AppIDs.clear(); - HasVACStatus = false; - HasAppValue = false; - } + void Reset(); - std::vector Serialize() - { - std::vector appids = AppIDs; - if (appids.size() == 0) - { - appids.emplace_back(0); - } + std::vector Serialize(); - uint16_t appid_count = static_cast(appids.size() > 140 ? 140 : appids.size()); - size_t buffer_size = static_cast(appid_count) * 4ul + 2ul; - std::vector buffer; - uint8_t* pBuffer; - - if (HasAppValue) - {// VACStatus + AppValue - buffer_size += 4; - if (!HasVACStatus) - { - HasVACStatus = true; - VACStatus = 0; - } - } - if (HasVACStatus) - {// VACStatus only - buffer_size += 4; - } - - buffer.resize(buffer_size); - pBuffer = buffer.data(); - *reinterpret_cast(pBuffer) = appid_count; - pBuffer += 2; - - for (int i = 0; i < appid_count && i < 140; ++i) - { - *reinterpret_cast(pBuffer) = appids[i]; - pBuffer += 4; - } - - if (HasVACStatus) - { - *reinterpret_cast(pBuffer) = VACStatus; - pBuffer += 4; - } - if (HasAppValue) - { - *reinterpret_cast(pBuffer) = AppValue; - } - - return buffer; - } - - bool Deserialize(const uint8_t* pBuffer, size_t size) - { - if (size < 2) - return false; - - uint16_t appid_count = *reinterpret_cast(pBuffer); - if (size < (appid_count * 4 + 2) || appid_count >= 140) - return false; - - AppIDs.resize(appid_count); - pBuffer += 2; - size -= 2; - for (int i = 0; i < appid_count; ++i) - { - AppIDs[i] = *reinterpret_cast(pBuffer); - pBuffer += 4; - size -= 4; - } - - HasVACStatus = false; - HasAppValue = false; - - if (size < 4) - return true; - - HasVACStatus = true; - VACStatus = *reinterpret_cast(pBuffer); - pBuffer += 4; - size -= 4; - - if (size < 4) - return true; - - HasAppValue = true; - AppValue = *reinterpret_cast(pBuffer); - - return true; - } + bool Deserialize(const uint8_t* pBuffer, size_t size); // Often 1 with empty appid //inline uint16_t AppIDCount() { return *reinterpret_cast(_Buffer); } @@ -277,48 +102,13 @@ struct AppTicketV4 class DecryptedAppTicket { public: - AppTicketV1 TicketV1; - AppTicketV2 TicketV2; - AppTicketV4 TicketV4; + AppTicketV1 TicketV1{}; + AppTicketV2 TicketV2{}; + AppTicketV4 TicketV4{}; - bool DeserializeTicket(const uint8_t* pBuffer, size_t buf_size) - { - if (!TicketV1.Deserialize(pBuffer, buf_size)) - return false; + bool DeserializeTicket(const uint8_t* pBuffer, size_t buf_size); - pBuffer += 16 + TicketV1.UserData.size(); - buf_size -= 16 + TicketV1.UserData.size(); - if (!TicketV2.Deserialize(pBuffer, buf_size)) - return false; - - if (TicketV2.TicketVersion > 2) - { - pBuffer += 40; - buf_size -= 40; - if (!TicketV4.Deserialize(pBuffer, buf_size)) - return false; - } - - return true; - } - - std::vector SerializeTicket() - { - std::vector buffer; - - TicketV1.TicketSize = TicketV1.UserData.size() + 40 + 2 + ((TicketV4.AppIDs.size() == 0 ? 1 : TicketV4.AppIDs.size()) * 4) + (TicketV4.HasVACStatus ? 4 : 0) + (TicketV4.HasAppValue ? 4 : 0); - TicketV2.TicketSize = TicketV1.TicketSize - TicketV1.UserData.size(); - - buffer = TicketV1.Serialize(); - - auto v = TicketV2.Serialize(); - - buffer.insert(buffer.end(), v.begin(), v.end()); - v = TicketV4.Serialize(); - buffer.insert(buffer.end(), v.begin(), v.end()); - - return buffer; - } + std::vector SerializeTicket(); }; @@ -326,23 +116,13 @@ class Steam_AppTicket : public ISteamAppTicket { private: - class Settings *settings; + class Settings *settings{}; public: - Steam_AppTicket(class Settings *settings) : - settings(settings) - { + Steam_AppTicket(class Settings *settings); - } + virtual uint32 GetAppOwnershipTicketData( uint32 nAppID, void *pvBuffer, uint32 cbBufferLength, uint32 *piAppId, uint32 *piSteamId, uint32 *piSignature, uint32 *pcbSignature ); - virtual uint32 GetAppOwnershipTicketData( uint32 nAppID, void *pvBuffer, uint32 cbBufferLength, uint32 *piAppId, uint32 *piSteamId, uint32 *piSignature, uint32 *pcbSignature ) - { - PRINT_DEBUG("TODO %u, %p, %u, %p, %p, %p, %p", nAppID, pvBuffer, cbBufferLength, piAppId, piSteamId, piSignature, pcbSignature); - std::lock_guard lock(global_mutex); - - return 0; - - } }; #endif // __INCLUDED_STEAM_APP_TICKET_H__ diff --git a/dll/dll/auth.h b/dll/dll/auth.h index 2c3fd036..3776b189 100644 --- a/dll/dll/auth.h +++ b/dll/dll/auth.h @@ -1,8 +1,8 @@ // source: https://github.com/Detanup01/stmsrv/blob/main/Steam3Server/Others/AppTickets.cs // thanks Detanup01 -#ifndef AUTH_INCLUDE -#define AUTH_INCLUDE +#ifndef AUTH_INCLUDE_H +#define AUTH_INCLUDE_H #include "base.h" #include "mbedtls/pk.h" @@ -14,646 +14,96 @@ // the data type is important, we depend on sizeof() for each one of them -constexpr uint32_t STEAM_APPTICKET_SIGLEN = 128; -constexpr uint32_t STEAM_APPTICKET_GCLen = 20; -constexpr uint32_t STEAM_APPTICKET_SESSIONLEN = 24; - -// source: https://github.com/Detanup01/stmsrv/blob/main/Cert/AppTicket.key -// thanks Detanup01 -const static std::string app_ticket_key = - "-----BEGIN PRIVATE KEY-----\n" - "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMITHOY6pfsvaGTI\n" - "llmilPa1+ev4BsUV0IW3+F/3pQlZ+o57CO1HbepSh2a37cbGUSehOVQ7lREPVXP3\n" - "UdyF5tU5IMytJef5N7euM5z2IG9IszeOReO87h2AmtlwGqnRj7qd0MeVxSAuUq7P\n" - "C/Ir1VyOg58+wAKxaPL18upylnGJAgMBAAECgYEAnKQQj0KG9VYuTCoaL/6pfPcj\n" - "4PEvhaM1yrfSIKMg8YtOT/G+IsWkUZyK7L1HjUhD+FiIjRQKHNrjfdYAnJz20Xom\n" - "k6iVt7ugihIne1Q3pGYG8TY9P1DPdN7zEnAVY1Bh2PAlqJWrif3v8v1dUGE/dYr2\n" - "U3M0JhvzO7VL1B/chIECQQDqW9G5azGMA/cL4jOg0pbj9GfxjJZeT7M2rBoIaRWP\n" - "C3ROndyb+BNahlKk6tbvqillvvMQQiSFGw/PbmCwtLL3AkEA0/79W0q9d3YCXQGW\n" - "k3hQvR8HEbxLmRaRF2gU4MOa5C0JqwsmxzdK4mKoJCpVAiu1gmFonLjn2hm8i+vK\n" - "b7hffwJAEiMpCACTxRJJfFH1TOz/YIT5xmfq+0GPzRtkqGH5mSh5x9vPxwJb/RWI\n" - "L9s85y90JLuyc/+qc+K0Rol0Ujip4QJAGLXVJEn+8ajAt8SSn5fbmV+/fDK9gRef\n" - "S+Im5NgH+ubBBL3lBD2Orfqf7K8+f2VG3+6oufPXmpV7Y7fVPdZ40wJALDujJXgi\n" - "XiCBSht1YScYjfmJh2/xZWh8/w+vs5ZTtrnW2FQvfvVDG9c1hrChhpvmA0QxdgWB\n" - "zSsAno/utcuB9w==\n" - "-----END PRIVATE KEY-----\n"; - - -static std::vector sign_auth_data(const std::string &private_key_content, const std::vector &data, size_t effective_data_len) { - std::vector signature{}; - - // Hash the data using SHA-1 - constexpr static int SHA1_DIGEST_LENGTH = 20; - uint8_t hash[SHA1_DIGEST_LENGTH]{}; - int result = mbedtls_sha1(data.data(), effective_data_len, hash); - if (result != 0) - { -#ifndef EMU_RELEASE_BUILD - // we nedd a live object until the printf does its job, hence this special handling - std::string err_msg(256, 0); - mbedtls_strerror(result, &err_msg[0], err_msg.size()); - PRINT_DEBUG("failed to hash the data via SHA1: %s", err_msg.c_str()); -#endif - - return signature; - } - - mbedtls_entropy_context entropy_ctx; // entropy context for random number generation - mbedtls_entropy_init(&entropy_ctx); - - mbedtls_ctr_drbg_context ctr_drbg_ctx; // CTR-DRBG context for deterministic random number generation - mbedtls_ctr_drbg_init(&ctr_drbg_ctx); - - // seed the CTR-DRBG context with random numbers - result = mbedtls_ctr_drbg_seed(&ctr_drbg_ctx, mbedtls_entropy_func, &entropy_ctx, nullptr, 0); - if (mbedtls_ctr_drbg_seed(&ctr_drbg_ctx, mbedtls_entropy_func, &entropy_ctx, nullptr, 0) != 0) - { - mbedtls_ctr_drbg_free(&ctr_drbg_ctx); - mbedtls_entropy_free(&entropy_ctx); - -#ifndef EMU_RELEASE_BUILD - // we nedd a live object until the printf does its job, hence this special handling - std::string err_msg(256, 0); - mbedtls_strerror(result, &err_msg[0], err_msg.size()); - PRINT_DEBUG("failed to seed the CTR-DRBG context: %s", err_msg.c_str()); -#endif - - return signature; - } - - mbedtls_pk_context private_key_ctx; // holds the parsed private key - mbedtls_pk_init(&private_key_ctx); - - result = mbedtls_pk_parse_key( - &private_key_ctx, // will hold the parsed private key - (const unsigned char *)private_key_content.c_str(), - private_key_content.size() + 1, // we MUST include the null terminator, otherwise this API returns an error! - nullptr, 0, // no password stuff, private key isn't protected - mbedtls_ctr_drbg_random, &ctr_drbg_ctx // random number generation function + the CTR-DRBG context it requires as an input - ); - - if (result != 0) - { - mbedtls_pk_free(&private_key_ctx); - mbedtls_ctr_drbg_free(&ctr_drbg_ctx); - mbedtls_entropy_free(&entropy_ctx); - -#ifndef EMU_RELEASE_BUILD - // we nedd a live object until the printf does its job, hence this special handling - std::string err_msg(256, 0); - mbedtls_strerror(result, &err_msg[0], err_msg.size()); - PRINT_DEBUG("failed to parse private key: %s", err_msg.c_str()); -#endif - - return signature; - } - - // private key must be valid RSA key - if (mbedtls_pk_get_type(&private_key_ctx) != MBEDTLS_PK_RSA || // invalid type - mbedtls_pk_can_do(&private_key_ctx, MBEDTLS_PK_RSA) == 0) // or initialized but not properly setup (maybe freed?) - { - mbedtls_pk_free(&private_key_ctx); - mbedtls_ctr_drbg_free(&ctr_drbg_ctx); - mbedtls_entropy_free(&entropy_ctx); - - PRINT_DEBUG("parsed key is not a valid RSA private key"); - return signature; - } - - // get the underlying RSA context from the parsed private key - mbedtls_rsa_context* rsa_ctx = mbedtls_pk_rsa(private_key_ctx); - - // resize the output buffer to accomodate the size of the private key - const size_t private_key_len = mbedtls_pk_get_len(&private_key_ctx); - if (private_key_len == 0) // TODO must be 128 siglen - { - mbedtls_pk_free(&private_key_ctx); - mbedtls_ctr_drbg_free(&ctr_drbg_ctx); - mbedtls_entropy_free(&entropy_ctx); - - PRINT_DEBUG("failed to get private key (final buffer) length"); - return signature; - } - - PRINT_DEBUG("computed private key (final buffer) length = %zu", private_key_len); - signature.resize(private_key_len); - - // finally sign the computed hash using RSA and PKCS#1 padding - result = mbedtls_rsa_pkcs1_sign( - rsa_ctx, - mbedtls_ctr_drbg_random, &ctr_drbg_ctx, - MBEDTLS_MD_SHA1, // we used SHA1 to hash the data - sizeof(hash), hash, - signature.data() // output - ); - - mbedtls_pk_free(&private_key_ctx); - mbedtls_ctr_drbg_free(&ctr_drbg_ctx); - mbedtls_entropy_free(&entropy_ctx); - - if (result != 0) - { - signature.clear(); - -#ifndef EMU_RELEASE_BUILD - // we nedd a live object until the printf does its job, hence this special handling - std::string err_msg(256, 0); - mbedtls_strerror(result, &err_msg[0], err_msg.size()); - PRINT_DEBUG("RSA signing failed: %s", err_msg.c_str()); -#endif - } - -#ifndef EMU_RELEASE_BUILD - // we nedd a live object until the printf does its job, hence this special handling - auto str = common_helpers::uint8_vector_to_hex_string(signature); - PRINT_DEBUG("final signature [%zu bytes]:\n %s", signature.size(), str.c_str()); -#endif - - return signature; -} +constexpr const static uint32_t STEAM_APPTICKET_SIGLEN = 128; +constexpr const static uint32_t STEAM_APPTICKET_GCLen = 20; +constexpr const static uint32_t STEAM_APPTICKET_SESSIONLEN = 24; struct DLC { - uint32_t AppId; - std::vector Licenses; + uint32_t AppId{}; + std::vector Licenses{}; - std::vector Serialize() - { - PRINT_DEBUG("AppId = %u, Licenses count = %zu", AppId, Licenses.size()); - - // we need this variable because we depend on the sizeof, must be 2 bytes - const uint16_t dlcs_licenses_count = (uint16_t)Licenses.size(); - const size_t dlcs_licenses_total_size = - Licenses.size() * sizeof(Licenses[0]); // count * element size - - const size_t total_size = - sizeof(AppId) + - sizeof(dlcs_licenses_count) + - dlcs_licenses_total_size; - - std::vector buffer; - buffer.resize(total_size); - - uint8_t* pBuffer = buffer.data(); - -#define SER_VAR(v) \ - *reinterpret_cast::type *>(pBuffer) = v; \ - pBuffer += sizeof(v) - - SER_VAR(AppId); - SER_VAR(dlcs_licenses_count); - for(uint32_t dlc_license : Licenses) - { - SER_VAR(dlc_license); - } - -#undef SER_VAR - - PRINT_DEBUG("final size = %zu", buffer.size()); - return buffer; - } + std::vector Serialize() const; }; struct AppTicketGC { - uint64_t GCToken; - CSteamID id; - uint32_t ticketGenDate; //epoch - uint32_t ExternalIP; - uint32_t InternalIP; - uint32_t TimeSinceStartup; - uint32_t TicketGeneratedCount; + uint64_t GCToken{}; + CSteamID id{}; + uint32_t ticketGenDate{}; //epoch + uint32_t ExternalIP{}; + uint32_t InternalIP{}; + uint32_t TimeSinceStartup{}; + uint32_t TicketGeneratedCount{}; private: uint32_t one = 1; uint32_t two = 2; public: - std::vector Serialize() - { - const uint64_t steam_id = id.ConvertToUint64(); - - // must be 52 - constexpr size_t total_size = - sizeof(STEAM_APPTICKET_GCLen) + - sizeof(GCToken) + - sizeof(steam_id) + - sizeof(ticketGenDate) + - sizeof(STEAM_APPTICKET_SESSIONLEN) + - sizeof(one) + - sizeof(two) + - sizeof(ExternalIP) + - sizeof(InternalIP) + - sizeof(TimeSinceStartup) + - sizeof(TicketGeneratedCount); - - // check the size at compile time, we must ensure the correct size -#ifndef EMU_RELEASE_BUILD - static_assert( - total_size == 52, - "AUTH::AppTicketGC::SER calculated size of serialized data != 52 bytes, your compiler has some incorrect sizes" - ); -#endif - - PRINT_DEBUG( - "\n" - " GCToken: " "%" PRIu64 "\n" - " user steam_id: " "%" PRIu64 "\n" - " ticketGenDate: %u\n" - " ExternalIP: 0x%08X, InternalIP: 0x%08X\n" - " TimeSinceStartup: %u, TicketGeneratedCount: %u\n" - " SER size = %zu", - - GCToken, - steam_id, - ticketGenDate, - ExternalIP, InternalIP, - TimeSinceStartup, TicketGeneratedCount, - total_size - ); - - std::vector buffer; - buffer.resize(total_size); - - uint8_t* pBuffer = buffer.data(); - -#define SER_VAR(v) \ - *reinterpret_cast::type *>(pBuffer) = v; \ - pBuffer += sizeof(v) - - SER_VAR(STEAM_APPTICKET_GCLen); - SER_VAR(GCToken); - SER_VAR(steam_id); - SER_VAR(ticketGenDate); - SER_VAR(STEAM_APPTICKET_SESSIONLEN); - SER_VAR(one); - SER_VAR(two); - SER_VAR(ExternalIP); - SER_VAR(InternalIP); - SER_VAR(TimeSinceStartup); - SER_VAR(TicketGeneratedCount); - -#undef SER_VAR - -#ifndef EMU_RELEASE_BUILD - // we nedd a live object until the printf does its job, hence this special handling - auto str = common_helpers::uint8_vector_to_hex_string(buffer); - PRINT_DEBUG("final data [%zu bytes]:\n %s", buffer.size(), str.c_str()); -#endif - - return buffer; - } + std::vector Serialize() const; }; struct AppTicket { - uint32_t Version; - CSteamID id; - uint32_t AppId; - uint32_t ExternalIP; - uint32_t InternalIP; + uint32_t Version{}; + CSteamID id{}; + uint32_t AppId{}; + uint32_t ExternalIP{}; + uint32_t InternalIP{}; uint32_t AlwaysZero = 0; //OwnershipFlags? - uint32_t TicketGeneratedDate; - uint32_t TicketGeneratedExpireDate; - std::vector Licenses; - std::vector DLCs; + uint32_t TicketGeneratedDate{}; + uint32_t TicketGeneratedExpireDate{}; + std::vector Licenses{}; + std::vector DLCs{}; - std::vector Serialize() - { - const uint64_t steam_id = id.ConvertToUint64(); - - PRINT_DEBUG( - "\n" - " Version: %u\n" - " user steam_id: " "%" PRIu64 "\n" - " AppId: %u\n" - " ExternalIP: 0x%08X, InternalIP: 0x%08X\n" - " TicketGeneratedDate: %u, TicketGeneratedExpireDate: %u\n" - " Licenses count: %zu, DLCs count: %zu", - - Version, - steam_id, - AppId, - ExternalIP, InternalIP, - TicketGeneratedDate, TicketGeneratedExpireDate, - Licenses.size(), DLCs.size() - ); - - // we need this variable because we depend on the sizeof, must be 2 bytes - const uint16_t licenses_count = (uint16_t)Licenses.size(); - const size_t licenses_total_size = - Licenses.size() * sizeof(Licenses[0]); // total count * element size - - // we need this variable because we depend on the sizeof, must be 2 bytes - const uint16_t dlcs_count = (uint16_t)DLCs.size(); - size_t dlcs_total_size = 0; - std::vector> serialized_dlcs; - for (DLC &dlc : DLCs) - { - auto dlc_ser = dlc.Serialize(); - dlcs_total_size += dlc_ser.size(); - serialized_dlcs.push_back(dlc_ser); - } - - //padding - constexpr uint16_t padding = (uint16_t)0; - - // must be 42 - constexpr size_t static_fields_size = - sizeof(Version) + - sizeof(steam_id) + - sizeof(AppId) + - sizeof(ExternalIP) + - sizeof(InternalIP) + - sizeof(AlwaysZero) + - sizeof(TicketGeneratedDate) + - sizeof(TicketGeneratedExpireDate) + - - sizeof(licenses_count) + - sizeof(dlcs_count) + - - sizeof(padding); - - // check the size at compile time, we must ensure the correct size -#ifndef EMU_RELEASE_BUILD - static_assert( - static_fields_size == 42, - "AUTH::AppTicket::SER calculated size of serialized data != 42 bytes, your compiler has some incorrect sizes" - ); -#endif - - const size_t total_size = - static_fields_size + - licenses_total_size + - dlcs_total_size; - - PRINT_DEBUG("final size = %zu", total_size); - - std::vector buffer; - buffer.resize(total_size); - uint8_t* pBuffer = buffer.data(); - -#define SER_VAR(v) \ - *reinterpret_cast::type *>(pBuffer) = v; \ - pBuffer += sizeof(v) - - SER_VAR(Version); - SER_VAR(steam_id); - SER_VAR(AppId); - SER_VAR(ExternalIP); - SER_VAR(InternalIP); - SER_VAR(AlwaysZero); - SER_VAR(TicketGeneratedDate); - SER_VAR(TicketGeneratedExpireDate); - -#ifndef EMU_RELEASE_BUILD - { - // we nedd a live object until the printf does its job, hence this special handling - auto str = common_helpers::uint8_vector_to_hex_string(buffer); - PRINT_DEBUG("(before licenses + DLCs):\n %s", str.c_str()); - } -#endif - - /* - * layout of licenses: - * ------------------------ - * 2 bytes: count of licenses - * ------------------------ - * [ - * ------------------------ - * | 4 bytes: license element - * ------------------------ - * ] - */ - SER_VAR(licenses_count); - for(uint32_t license : Licenses) - { - SER_VAR(license); - } - - /* - * layout of DLCs: - * ------------------------ - * | 2 bytes: count of DLCs - * ------------------------ - * [ - * ------------------------ - * | 4 bytes: app id - * ------------------------ - * | 2 bytes: DLC licenses count - * ------------------------ - * [ - * 4 bytes: DLC license element - * ] - * ] - */ - SER_VAR(dlcs_count); - for (std::vector &dlc_ser : serialized_dlcs) - { - memcpy(pBuffer, dlc_ser.data(), dlc_ser.size()); - pBuffer += dlc_ser.size(); - } - - //padding - SER_VAR(padding); - -#undef SER_VAR - -#ifndef EMU_RELEASE_BUILD - { - // we nedd a live object until the printf does its job, hence this special handling - auto str = common_helpers::uint8_vector_to_hex_string(buffer); - PRINT_DEBUG("final data [%zu bytes]:\n %s", buffer.size(), str.c_str()); - } -#endif - - return buffer; - } + std::vector Serialize() const; }; struct Auth_Data { - bool HasGC; - AppTicketGC GC; - AppTicket Ticket; + bool HasGC{}; + AppTicketGC GC{}; + AppTicket Ticket{}; //old data - CSteamID id; - uint64_t number; - std::chrono::high_resolution_clock::time_point created; + CSteamID id{}; + uint64_t number{}; + std::chrono::high_resolution_clock::time_point created{}; - std::vector Serialize() - { - /* - * layout of Auth_Data with GC: - * ------------------------ - * X bytes: GC data blob (currently 52 bytes) - * ------------------------ - * 4 bytes: remaining Auth_Data blob size (4 + Y + Z) - * ------------------------ - * 4 bytes: size of ticket data layout (not blob!, hence blob + 4) - * ------------------------ - * Y bytes: ticket data blob - * ------------------------ - * Z bytes: App Ticket signature - * ------------------------ - * - * total layout length = X + 4 + 4 + Y + Z - */ - - /* - * layout of Auth_Data without GC: - * ------------------------ - * 4 bytes: size of ticket data layout (not blob!, hence blob + 4) - * ------------------------ - * Y bytes: ticket data blob - * ------------------------ - * Z bytes: App Ticket signature - * ------------------------ - * - * total layout length = 4 + Y + Z - */ - const uint64_t steam_id = id.ConvertToUint64(); - - PRINT_DEBUG( - "\n" - " HasGC: %u\n" - " user steam_id: " "%" PRIu64 "\n" - " number: " "%" PRIu64 , - - (int)HasGC, - steam_id, - number - ); - - /* - * layout of ticket data: - * ------------------------ - * 4 bytes: size of ticket data layout (not blob!, hence blob + 4) - * ------------------------ - * Y bytes: ticket data blob - * ------------------------ - * - * total layout length = 4 + Y - */ - std::vector tickedData = Ticket.Serialize(); - // we need this variable because we depend on the sizeof, must be 4 bytes - const uint32_t ticket_data_layout_length = - sizeof(uint32_t) + // size of this uint32_t because it is included! - (uint32_t)tickedData.size(); - - size_t total_size_without_siglen = ticket_data_layout_length; - - std::vector GCData; - size_t gc_data_layout_length = 0; - if (HasGC) - { - /* - * layout of GC data: - * ------------------------ - * X bytes: GC data blob (currently 52 bytes) - * ------------------------ - * 4 bytes: remaining Auth_Data blob size - * ------------------------ - * - * total layout length = X + 4 - */ - GCData = GC.Serialize(); - gc_data_layout_length += - GCData.size() + - sizeof(uint32_t); - - total_size_without_siglen += gc_data_layout_length; - } - - const size_t final_buffer_size = total_size_without_siglen + STEAM_APPTICKET_SIGLEN; - PRINT_DEBUG("size without sig len = %zu, size with sig len (final size) = %zu", - total_size_without_siglen, - final_buffer_size - ); - - std::vector buffer; - buffer.resize(final_buffer_size); - - uint8_t* pBuffer = buffer.data(); - -#define SER_VAR(v) \ - *reinterpret_cast::type *>(pBuffer) = v; \ - pBuffer += sizeof(v) - - // serialize the GC data first - if (HasGC) - { - memcpy(pBuffer, GCData.data(), GCData.size()); - pBuffer += GCData.size(); - - // when GC data is written (HasGC), - // the next 4 bytes after the GCData will be the length of the remaining data in the final buffer - // i.e. final buffer size - length of GCData layout - // i.e. ticket data length + STEAM_APPTICKET_SIGLEN - // - // notice that we subtract the entire layout length, not just GCData.size(), - // otherwise these next 4 bytes will include themselves! - uint32_t remaining_length = (uint32_t)(final_buffer_size - gc_data_layout_length); - SER_VAR(remaining_length); - } - - // serialize the ticket data - SER_VAR(ticket_data_layout_length); - memcpy(pBuffer, tickedData.data(), tickedData.size()); - -#ifndef EMU_RELEASE_BUILD - { - // we nedd a live object until the printf does its job, hence this special handling - auto str = common_helpers::uint8_vector_to_hex_string(buffer); - PRINT_DEBUG("final data (before signature) [%zu bytes]:\n %s", buffer.size(), str.c_str()); - } -#endif - - //Todo make a signature - std::vector signature = sign_auth_data(app_ticket_key, tickedData, total_size_without_siglen); - if (signature.size() == STEAM_APPTICKET_SIGLEN) { - memcpy(buffer.data() + total_size_without_siglen, signature.data(), signature.size()); - -#ifndef EMU_RELEASE_BUILD - { - // we nedd a live object until the printf does its job, hence this special handling - auto str = common_helpers::uint8_vector_to_hex_string(buffer); - PRINT_DEBUG("final data (after signature) [%zu bytes]:\n %s", buffer.size(), str.c_str()); - } -#endif - - } else { - PRINT_DEBUG("signature size [%zu] is invalid", signature.size()); - } - -#undef SER_VAR - - return buffer; - } + std::vector Serialize() const; }; class Auth_Manager { - class Settings *settings; - class Networking *network; - class SteamCallBacks *callbacks; + class Settings *settings{}; + class Networking *network{}; + class SteamCallBacks *callbacks{}; + + std::vector inbound{}; + std::vector outbound{}; void launch_callback(CSteamID id, EAuthSessionResponse resp, double delay=0); void launch_callback_gs(CSteamID id, bool approved); - std::vector inbound; - std::vector outbound; + + static void ticket_callback(void *object, Common_Message *msg); + void Callback(Common_Message *msg); + public: Auth_Manager(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks); ~Auth_Manager(); - void Callback(Common_Message *msg); HAuthTicket getTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket ); HAuthTicket getWebApiTicket( const char *pchIdentity ); void cancelTicket(uint32 number); + EBeginAuthSessionResult beginAuth(const void *pAuthTicket, int cbAuthTicket, CSteamID steamID); bool endAuth(CSteamID id); + uint32 countInboundAuth(); + bool SendUserConnectAndAuthenticate( uint32 unIPClient, const void *pvAuthBlob, uint32 cubAuthBlobSize, CSteamID *pSteamIDUser ); + CSteamID fakeUser(); + Auth_Data getTicketData( void *pTicket, int cbMaxTicket, uint32 *pcbTicket ); }; -#endif // AUTH_INCLUDE +#endif // AUTH_INCLUDE_H diff --git a/dll/dll/base.h b/dll/dll/base.h index 4cff413b..1e02d699 100644 --- a/dll/dll/base.h +++ b/dll/dll/base.h @@ -15,17 +15,14 @@ License along with the Goldberg Emulator; if not, see . */ -#ifndef BASE_INCLUDE -#define BASE_INCLUDE +#ifndef BASE_INCLUDE_H +#define BASE_INCLUDE_H #include "common_includes.h" +#include "callsystem.h" #define PUSH_BACK_IF_NOT_IN(vector, element) { if(std::find(vector.begin(), vector.end(), element) == vector.end()) vector.push_back(element); } -#define STEAM_CALLRESULT_TIMEOUT 120.0 -#define STEAM_CALLRESULT_WAIT_FOR_CB 0.01 -#define DEFAULT_CB_TIMEOUT 0.002 - extern std::recursive_mutex global_mutex; extern const std::chrono::time_point startup_counter; @@ -34,70 +31,13 @@ extern const std::chrono::time_point startup_time; void randombytes(char *buf, size_t size); std::string get_env_variable(const std::string &name); bool set_env_variable(const std::string &name, const std::string &value); + +/// @brief Check for a timeout given some initial timepoint and a timeout in sec. +/// @param old The initial timepoint which will be compared against current time +/// @param timeout The max allowed time in seconds +/// @return true if the timepoint has exceeded the max allowed timeout, false otherwise bool check_timedout(std::chrono::high_resolution_clock::time_point old, double timeout); -class CCallbackMgr -{ -public: - static void SetRegister(class CCallbackBase *pCallback, int iCallback) { - pCallback->m_nCallbackFlags |= CCallbackBase::k_ECallbackFlagsRegistered; - pCallback->m_iCallback = iCallback; - }; - - static void SetUnregister(class CCallbackBase *pCallback) { - if (pCallback) - pCallback->m_nCallbackFlags &= !CCallbackBase::k_ECallbackFlagsRegistered; - }; - - static bool isServer(class CCallbackBase *pCallback) { - return (pCallback->m_nCallbackFlags & CCallbackBase::k_ECallbackFlagsGameServer) != 0; - }; -}; - -struct Steam_Call_Result { - Steam_Call_Result(SteamAPICall_t a, int icb, void *r, unsigned int s, double r_in, bool run_cc_cb) { - api_call = a; - result.resize(s); - if (s > 0 && r != NULL) - memcpy(&(result[0]), r, s); - created = std::chrono::high_resolution_clock::now(); - run_in = r_in; - run_call_completed_cb = run_cc_cb; - iCallback = icb; - } - - bool operator==(const struct Steam_Call_Result& a) - { - return a.api_call == api_call && a.callbacks == callbacks; - } - - bool timed_out() { - return check_timedout(created, STEAM_CALLRESULT_TIMEOUT); - } - - bool call_completed() { - return (!reserved) && check_timedout(created, run_in); - } - - bool can_execute() { - return (!to_delete) && call_completed() && (has_cb() || check_timedout(created, STEAM_CALLRESULT_WAIT_FOR_CB)); - } - - bool has_cb() { - return callbacks.size() > 0; - } - - SteamAPICall_t api_call{}; - std::vector callbacks{}; - std::vector result{}; - bool to_delete = false; - bool reserved = false; - std::chrono::high_resolution_clock::time_point created{}; - double run_in{}; - bool run_call_completed_cb{}; - int iCallback{}; -}; - unsigned generate_account_id(); CSteamID generate_steam_anon_user(); SteamAPICall_t generate_steam_api_call_id(); @@ -112,334 +52,10 @@ std::string canonical_path(const std::string &path); bool file_exists_(const std::string &full_path); unsigned int file_size_(const std::string &full_path); -class SteamCallResults { - std::vector callresults{}; - std::vector completed_callbacks{}; - void (*cb_all)(std::vector result, int callback) = nullptr; - -public: - void addCallCompleted(class CCallbackBase *cb) { - if (std::find(completed_callbacks.begin(), completed_callbacks.end(), cb) == completed_callbacks.end()) { - completed_callbacks.push_back(cb); - } - } - - void rmCallCompleted(class CCallbackBase *cb) { - auto c = std::find(completed_callbacks.begin(), completed_callbacks.end(), cb); - if (c != completed_callbacks.end()) { - completed_callbacks.erase(c); - } - } - - void addCallBack(SteamAPICall_t api_call, class CCallbackBase *cb) { - auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; }); - if (cb_result != callresults.end()) { - cb_result->callbacks.push_back(cb); - CCallbackMgr::SetRegister(cb, cb->GetICallback()); - } - } - - bool exists(SteamAPICall_t api_call) { - auto cr = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; }); - if (cr == callresults.end()) return false; - if (!cr->call_completed()) return false; - return true; - } - - bool callback_result(SteamAPICall_t api_call, void *copy_to, unsigned int size) { - auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; }); - if (cb_result != callresults.end()) { - if (!cb_result->call_completed()) return false; - if (cb_result->result.size() > size) return false; - - memcpy(copy_to, &(cb_result->result[0]), cb_result->result.size()); - cb_result->to_delete = true; - return true; - } else { - return false; - } - } - - void rmCallBack(SteamAPICall_t api_call, class CCallbackBase *cb) { - auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; }); - if (cb_result != callresults.end()) { - auto it = std::find(cb_result->callbacks.begin(), cb_result->callbacks.end(), cb); - if (it != cb_result->callbacks.end()) { - cb_result->callbacks.erase(it); - CCallbackMgr::SetUnregister(cb); - } - } - } - - void rmCallBack(class CCallbackBase *cb) { - //TODO: check if callback is callback or call result? - for (auto & cr: callresults) { - auto it = std::find(cr.callbacks.begin(), cr.callbacks.end(), cb); - if (it != cr.callbacks.end()) { - cr.callbacks.erase(it); - } - - if (cr.callbacks.size() == 0) { - cr.to_delete = true; - } - } - } - - SteamAPICall_t addCallResult(SteamAPICall_t api_call, int iCallback, void *result, unsigned int size, double timeout=DEFAULT_CB_TIMEOUT, bool run_call_completed_cb=true) { - PRINT_DEBUG("%i", iCallback); - auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; }); - if (cb_result != callresults.end()) { - if (cb_result->reserved) { - std::chrono::high_resolution_clock::time_point created = cb_result->created; - std::vector temp_cbs = cb_result->callbacks; - *cb_result = Steam_Call_Result(api_call, iCallback, result, size, timeout, run_call_completed_cb); - cb_result->callbacks = temp_cbs; - cb_result->created = created; - return cb_result->api_call; - } - } else { - struct Steam_Call_Result res = Steam_Call_Result(api_call, iCallback, result, size, timeout, run_call_completed_cb); - callresults.push_back(res); - return callresults.back().api_call; - } - - PRINT_DEBUG("ERROR"); - return k_uAPICallInvalid; - } - - SteamAPICall_t reserveCallResult() { - struct Steam_Call_Result res = Steam_Call_Result(generate_steam_api_call_id(), 0, NULL, 0, 0.0, true); - res.reserved = true; - callresults.push_back(res); - return callresults.back().api_call; - } - - SteamAPICall_t addCallResult(int iCallback, void *result, unsigned int size, double timeout=DEFAULT_CB_TIMEOUT, bool run_call_completed_cb=true) { - return addCallResult(generate_steam_api_call_id(), iCallback, result, size, timeout, run_call_completed_cb); - } - - void setCbAll(void (*cb_all)(std::vector result, int callback)) { - this->cb_all = cb_all; - } - - void runCallResults() { - unsigned long current_size = callresults.size(); - for (unsigned i = 0; i < current_size; ++i) { - unsigned index = i; - - if (!callresults[index].to_delete) { - if (callresults[index].can_execute()) { - std::vector result = callresults[index].result; - SteamAPICall_t api_call = callresults[index].api_call; - bool run_call_completed_cb = callresults[index].run_call_completed_cb; - int iCallback = callresults[index].iCallback; - if (run_call_completed_cb) { - callresults[index].run_call_completed_cb = false; - } - - callresults[index].to_delete = true; - if (callresults[index].has_cb()) { - std::vector temp_cbs = callresults[index].callbacks; - for (auto & cb : temp_cbs) { - PRINT_DEBUG("Calling callresult %p %i", cb, cb->GetICallback()); - global_mutex.unlock(); - //TODO: unlock relock doesn't work if mutex was locked more than once. - if (run_call_completed_cb) { //run the right function depending on if it's a callback or a call result. - cb->Run(&(result[0]), false, api_call); - } else { - cb->Run(&(result[0])); - } - //COULD BE DELETED SO DON'T TOUCH CB - global_mutex.lock(); - PRINT_DEBUG("callresult done"); - } - } - - if (run_call_completed_cb) { - //can it happen that one is removed during the callback? - std::vector callbacks = completed_callbacks; - SteamAPICallCompleted_t data{}; - data.m_hAsyncCall = api_call; - data.m_iCallback = iCallback; - data.m_cubParam = result.size(); - - for (auto & cb: callbacks) { - PRINT_DEBUG("Call complete cb %i %p %llu", iCallback, cb, api_call); - //TODO: check if this is a problem or not. - SteamAPICallCompleted_t temp = data; - global_mutex.unlock(); - cb->Run(&temp); - global_mutex.lock(); - } - - if (cb_all) { - std::vector res; - res.resize(sizeof(data)); - memcpy(&(res[0]), &data, sizeof(data)); - cb_all(res, data.k_iCallback); - } - } else { - if (cb_all) { - cb_all(result, iCallback); - } - } - } else { - if (callresults[index].timed_out()) { - callresults[index].to_delete = true; - } - } - } - } - - PRINT_DEBUG("erase to_delete"); - auto c = std::begin(callresults); - while (c != std::end(callresults)) { - if (c->to_delete) { - if (c->timed_out()) { - PRINT_DEBUG("removed callresult %i", c->iCallback); - c = callresults.erase(c); - } else { - ++c; - } - } else { - ++c; - } - } - } -}; - -struct Steam_Call_Back { - std::vector callbacks{}; - std::vector> results{}; -}; - -class SteamCallBacks { - std::map callbacks{}; - SteamCallResults *results{}; - -public: - SteamCallBacks(SteamCallResults *results) { - this->results = results; - } - - void addCallBack(int iCallback, class CCallbackBase *cb) { - PRINT_DEBUG("%i", iCallback); - if (iCallback == SteamAPICallCompleted_t::k_iCallback) { - results->addCallCompleted(cb); - CCallbackMgr::SetRegister(cb, iCallback); - return; - } - - if (std::find(callbacks[iCallback].callbacks.begin(), callbacks[iCallback].callbacks.end(), cb) == callbacks[iCallback].callbacks.end()) { - callbacks[iCallback].callbacks.push_back(cb); - CCallbackMgr::SetRegister(cb, iCallback); - for (auto & res: callbacks[iCallback].results) { - //TODO: timeout? - SteamAPICall_t api_id = results->addCallResult(iCallback, &(res[0]), res.size(), 0.0, false); - results->addCallBack(api_id, cb); - } - } - } - - void addCBResult(int iCallback, void *result, unsigned int size, double timeout, bool dont_post_if_already) { - if (dont_post_if_already) { - for (auto & r : callbacks[iCallback].results) { - if (r.size() == size) { - if (memcmp(&(r[0]), result, size) == 0) { - //cb already posted - return; - } - } - } - } - - std::vector temp{}; - temp.resize(size); - memcpy(&(temp[0]), result, size); - callbacks[iCallback].results.push_back(temp); - for (auto cb: callbacks[iCallback].callbacks) { - SteamAPICall_t api_id = results->addCallResult(iCallback, result, size, timeout, false); - results->addCallBack(api_id, cb); - } - - if (callbacks[iCallback].callbacks.empty()) { - results->addCallResult(iCallback, result, size, timeout, false); - } - } - - void addCBResult(int iCallback, void *result, unsigned int size) { - addCBResult(iCallback, result, size, DEFAULT_CB_TIMEOUT, false); - } - - void addCBResult(int iCallback, void *result, unsigned int size, bool dont_post_if_already) { - addCBResult(iCallback, result, size, DEFAULT_CB_TIMEOUT, dont_post_if_already); - } - - void addCBResult(int iCallback, void *result, unsigned int size, double timeout) { - addCBResult(iCallback, result, size, timeout, false); - } - - void rmCallBack(int iCallback, class CCallbackBase *cb) { - if (iCallback == SteamAPICallCompleted_t::k_iCallback) { - results->rmCallCompleted(cb); - CCallbackMgr::SetUnregister(cb); - return; - } - - auto c = std::find(callbacks[iCallback].callbacks.begin(), callbacks[iCallback].callbacks.end(), cb); - if (c != callbacks[iCallback].callbacks.end()) { - callbacks[iCallback].callbacks.erase(c); - CCallbackMgr::SetUnregister(cb); - results->rmCallBack(cb); - } - } - - void runCallBacks() { - for (auto & c : callbacks) { - c.second.results.clear(); - } - } -}; - -struct RunCBs { - void (*function)(void *object) = nullptr; - void *object{}; -}; - -class RunEveryRunCB { - std::vector cbs{}; -public: - void add(void (*cb)(void *object), void *object) { - remove(cb, object); - RunCBs rcb; - rcb.function = cb; - rcb.object = object; - cbs.push_back(rcb); - } - - void remove(void (*cb)(void *object), void *object) { - auto c = std::begin(cbs); - while (c != std::end(cbs)) { - if (c->function == cb && c->object == object) { - c = cbs.erase(c); - } else { - ++c; - } - } - } - - void run() { - std::vector temp_cbs = cbs; - for (auto c : temp_cbs) { - c.function(c.object); - } - } -}; - void set_whitelist_ips(uint32_t *from, uint32_t *to, unsigned num_ips); #ifdef EMU_EXPERIMENTAL_BUILD bool crack_SteamAPI_RestartAppIfNecessary(uint32 unOwnAppID); bool crack_SteamAPI_Init(); #endif -#endif +#endif // BASE_INCLUDE_H diff --git a/dll/dll/callsystem.h b/dll/dll/callsystem.h new file mode 100644 index 00000000..955867c1 --- /dev/null +++ b/dll/dll/callsystem.h @@ -0,0 +1,132 @@ +/* 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_CALLSYSTEM_H__ +#define __INCLUDED_CALLSYSTEM_H__ + +#include "common_includes.h" + +#define DEFAULT_CB_TIMEOUT 0.002 +#define STEAM_CALLRESULT_TIMEOUT 120.0 +#define STEAM_CALLRESULT_WAIT_FOR_CB 0.01 + + +class CCallbackMgr +{ +public: + static void SetRegister(class CCallbackBase *pCallback, int iCallback); + static void SetUnregister(class CCallbackBase *pCallback); + + static bool isServer(class CCallbackBase *pCallback); +}; + +struct Steam_Call_Result { + SteamAPICall_t api_call{}; + std::vector callbacks{}; + std::vector result{}; + bool to_delete = false; + bool reserved = false; + std::chrono::high_resolution_clock::time_point created{}; + double run_in{}; + bool run_call_completed_cb{}; + int iCallback{}; + + Steam_Call_Result(SteamAPICall_t a, int icb, void *r, unsigned int s, double r_in, bool run_cc_cb); + + bool operator==(const struct Steam_Call_Result& other) const; + + bool timed_out() const; + + bool call_completed() const; + + bool can_execute() const; + + bool has_cb() const; + +}; + +class SteamCallResults { + std::vector callresults{}; + std::vector completed_callbacks{}; + void (*cb_all)(std::vector result, int callback) = nullptr; + +public: + void addCallCompleted(class CCallbackBase *cb); + + void rmCallCompleted(class CCallbackBase *cb); + + void addCallBack(SteamAPICall_t api_call, class CCallbackBase *cb); + + bool exists(SteamAPICall_t api_call) const; + + bool callback_result(SteamAPICall_t api_call, void *copy_to, unsigned int size); + + void rmCallBack(SteamAPICall_t api_call, class CCallbackBase *cb); + + void rmCallBack(class CCallbackBase *cb); + + SteamAPICall_t addCallResult(SteamAPICall_t api_call, int iCallback, void *result, unsigned int size, double timeout=DEFAULT_CB_TIMEOUT, bool run_call_completed_cb=true); + + SteamAPICall_t reserveCallResult(); + + SteamAPICall_t addCallResult(int iCallback, void *result, unsigned int size, double timeout=DEFAULT_CB_TIMEOUT, bool run_call_completed_cb=true); + + void setCbAll(void (*cb_all)(std::vector result, int callback)); + + void runCallResults(); +}; + +struct Steam_Call_Back { + std::vector callbacks{}; + std::vector> results{}; +}; + +class SteamCallBacks { + std::map callbacks{}; + SteamCallResults *results{}; + +public: + SteamCallBacks(SteamCallResults *results); + + void addCallBack(int iCallback, class CCallbackBase *cb); + void addCBResult(int iCallback, void *result, unsigned int size, double timeout, bool dont_post_if_already); + void addCBResult(int iCallback, void *result, unsigned int size); + void addCBResult(int iCallback, void *result, unsigned int size, bool dont_post_if_already); + void addCBResult(int iCallback, void *result, unsigned int size, double timeout); + + void rmCallBack(int iCallback, class CCallbackBase *cb); + + void runCallBacks(); +}; + +struct RunCBs { + void (*function)(void *object) = nullptr; + void *object{}; +}; + +class RunEveryRunCB { + std::vector cbs{}; + +public: + void add(void (*cb)(void *object), void *object); + + void remove(void (*cb)(void *object), void *object); + + void run() const; +}; + +#endif // __INCLUDED_CALLSYSTEM_H__ diff --git a/dll/dll/common_includes.h b/dll/dll/common_includes.h index 20a02964..a6b0b0c5 100644 --- a/dll/dll/common_includes.h +++ b/dll/dll/common_includes.h @@ -244,4 +244,4 @@ static inline void reset_LastError() #define LOBBY_CONNECT_APPID ((uint32)-2) -#endif//__INCLUDED_COMMON_INCLUDES__ +#endif //__INCLUDED_COMMON_INCLUDES__ diff --git a/dll/dll/dll.h b/dll/dll/dll.h index 1b34715e..5d266046 100644 --- a/dll/dll/dll.h +++ b/dll/dll/dll.h @@ -15,6 +15,9 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_DLL_H__ +#define __INCLUDED_DLL_H__ + #include "steam_client.h" #ifdef STEAMCLIENT_DLL @@ -32,3 +35,5 @@ HSteamUser flat_hsteamuser(); HSteamPipe flat_hsteampipe(); HSteamUser flat_gs_hsteamuser(); HSteamPipe flat_gs_hsteampipe(); + +#endif // __INCLUDED_DLL_H__ diff --git a/dll/dll/local_storage.h b/dll/dll/local_storage.h index 104e0b61..0e9fd18c 100644 --- a/dll/dll/local_storage.h +++ b/dll/dll/local_storage.h @@ -15,18 +15,17 @@ License along with the Goldberg Emulator; if not, see . */ -#ifndef LOCAL_STORAGE_INCLUDE -#define LOCAL_STORAGE_INCLUDE +#ifndef LOCAL_STORAGE_INCLUDE_H +#define LOCAL_STORAGE_INCLUDE_H #include "base.h" #define MAX_FILENAME_LENGTH 300 -union image_pixel_t -{ +union image_pixel_t { uint32_t pixel; - struct pixel_channels_t - { + + struct pixel_channels_t { uint8_t r; uint8_t g; uint8_t b; @@ -34,11 +33,10 @@ union image_pixel_t } channels; }; -struct image_t -{ - size_t width; - size_t height; - std::vector pix_map; +struct image_t { + size_t width{}; + size_t height{}; + std::vector pix_map{}; }; class Local_Storage { @@ -105,4 +103,4 @@ public: static std::string desanitize_string(std::string name); }; -#endif +#endif // LOCAL_STORAGE_INCLUDE_H diff --git a/dll/dll/network.h b/dll/dll/network.h index e3ab7466..9f255a23 100644 --- a/dll/dll/network.h +++ b/dll/dll/network.h @@ -21,13 +21,6 @@ #include "base.h" #include -inline bool protobuf_message_equal(const google::protobuf::MessageLite& msg_a, - const google::protobuf::MessageLite& msg_b) { - return (msg_a.GetTypeName() == msg_b.GetTypeName()) && - (msg_a.SerializeAsString() == msg_b.SerializeAsString()); -} - - #define DEFAULT_PORT 47584 #if defined(STEAM_WIN32) @@ -36,9 +29,18 @@ typedef unsigned int sock_t; typedef int sock_t; #endif +static inline bool protobuf_message_equal( + const google::protobuf::MessageLite& msg_a, + const google::protobuf::MessageLite& msg_b) +{ + return (msg_a.GetTypeName() == msg_b.GetTypeName()) && + (msg_a.SerializeAsString() == msg_b.SerializeAsString()); +} + + struct IP_PORT { - uint32 ip; - uint16 port; + uint32 ip{}; + uint16 port{}; bool operator <(const IP_PORT& other) const { return (ip < other.ip) || (ip == other.ip && port < other.port); @@ -46,9 +48,9 @@ struct IP_PORT { }; struct Network_Callback { - void (*message_callback)(void *object, Common_Message *msg); - void *object; - CSteamID steam_id; + void (*message_callback)(void *object, Common_Message *msg) = nullptr; + void *object{}; + CSteamID steam_id{}; }; enum Callback_Ids { @@ -73,39 +75,33 @@ struct Network_Callback_Container { }; struct TCP_Socket { - sock_t sock = ~0; + sock_t sock = static_cast(~0); bool received_data = false; - std::vector recv_buffer; - std::vector send_buffer; - std::chrono::high_resolution_clock::time_point last_heartbeat_sent, last_heartbeat_received; + std::vector recv_buffer{}; + std::vector send_buffer{}; + std::chrono::high_resolution_clock::time_point last_heartbeat_sent{}, last_heartbeat_received{}; }; struct Connection { - struct TCP_Socket tcp_socket_outgoing, tcp_socket_incoming; + struct TCP_Socket tcp_socket_outgoing{}, tcp_socket_incoming{}; bool connected = false; - IP_PORT udp_ip_port; + IP_PORT udp_ip_port{}; bool udp_pinged = false; - IP_PORT tcp_ip_port; - std::vector ids; - uint32 appid; - std::chrono::high_resolution_clock::time_point last_received; + IP_PORT tcp_ip_port{}; + std::vector ids{}; + uint32 appid{}; + std::chrono::high_resolution_clock::time_point last_received{}; }; -class Networking { +class Networking +{ bool enabled = false; - bool query_alive; - std::chrono::high_resolution_clock::time_point last_run; - sock_t query_socket, udp_socket, tcp_socket; - uint16 udp_port, tcp_port; - uint32 own_ip; - std::vector connections; - struct Connection *find_connection(CSteamID id, uint32 appid = 0); - struct Connection *new_connection(CSteamID id, uint32 appid); - - bool handle_announce(Common_Message *msg, IP_PORT ip_port); - bool handle_low_level_udp(Common_Message *msg, IP_PORT ip_port); - bool handle_tcp(Common_Message *msg, struct TCP_Socket &socket); - void send_announce_broadcasts(); + bool query_alive{}; + std::chrono::high_resolution_clock::time_point last_run{}; + sock_t query_socket, udp_socket{}, tcp_socket{}; + uint16 udp_port{}, tcp_port{}; + uint32 own_ip{}; + std::vector connections{}; std::vector ids; uint32 appid; @@ -118,18 +114,30 @@ class Networking { struct Network_Callback_Container callbacks[CALLBACK_IDS_MAX]; std::vector local_send; + struct Connection *find_connection(CSteamID id, uint32 appid = 0); + struct Connection *new_connection(CSteamID id, uint32 appid); + + bool handle_announce(Common_Message *msg, IP_PORT ip_port); + bool handle_low_level_udp(Common_Message *msg, IP_PORT ip_port); + bool handle_tcp(Common_Message *msg, struct TCP_Socket &socket); + void send_announce_broadcasts(); + bool add_id_connection(struct Connection *connection, CSteamID steam_id); void run_callbacks(Callback_Ids id, Common_Message *msg); void run_callback_user(CSteamID steam_id, bool online, uint32 appid); void do_callbacks_message(Common_Message *msg); Common_Message create_announce(bool request); + + public: + Networking(CSteamID id, uint32 appid, uint16 port, std::set *custom_broadcasts, bool disable_sockets); + ~Networking(); + //NOTE: for all functions ips/ports are passed/returned in host byte order //ex: 127.0.0.1 should be passed as 0x7F000001 static std::set resolve_ip(std::string dns); - Networking(CSteamID id, uint32 appid, uint16 port, std::set *custom_broadcasts, bool disable_sockets); - ~Networking(); + void addListenId(CSteamID id); void setAppID(uint32 appid); void Run(); @@ -161,4 +169,4 @@ public: bool isQueryAlive(); }; -#endif +#endif // NETWORK_INCLUDE_H diff --git a/dll/dll/settings.h b/dll/dll/settings.h index 5f810aca..445b9573 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -15,54 +15,57 @@ License along with the Goldberg Emulator; if not, see . */ -#ifndef SETTINGS_INCLUDE -#define SETTINGS_INCLUDE +#ifndef SETTINGS_INCLUDE_H +#define SETTINGS_INCLUDE_H #include "base.h" struct IP_PORT; struct DLC_entry { - AppId_t appID; - std::string name; - bool available; + AppId_t appID{}; + std::string name{}; + bool available{}; }; struct Mod_entry { - PublishedFileId_t id; - std::string title; - std::string path; + PublishedFileId_t id{}; + std::string title{}; + std::string path{}; + + std::string previewURL{}; + EWorkshopFileType fileType{}; + std::string description{}; + uint64 steamIDOwner{}; + uint32 timeCreated{}; + uint32 timeUpdated{}; + uint32 timeAddedToUserList{}; + ERemoteStoragePublishedFileVisibility visibility{}; + bool banned = false; + bool acceptedForUse{}; + bool tagsTruncated{}; + std::string tags{}; - std::string previewURL; - EWorkshopFileType fileType; - std::string description; - uint64 steamIDOwner; - uint32 timeCreated; - uint32 timeUpdated; - uint32 timeAddedToUserList; - ERemoteStoragePublishedFileVisibility visibility; - bool banned; - bool acceptedForUse; - bool tagsTruncated; - std::string tags; // file/url information UGCHandle_t handleFile = generate_file_handle(); UGCHandle_t handlePreviewFile = generate_file_handle(); - std::string primaryFileName; - int32 primaryFileSize; - std::string previewFileName; - int32 previewFileSize; - std::string workshopItemURL; + + std::string primaryFileName{}; + int32 primaryFileSize{}; + std::string previewFileName{}; + int32 previewFileSize{}; + std::string workshopItemURL{}; + // voting information - uint32 votesUp; - uint32 votesDown; - float score; + uint32 votesUp{}; + uint32 votesDown{}; + float score{}; + // collection details - uint32 numChildren; + uint32 numChildren{}; // TODO private: - UGCHandle_t generate_file_handle() - { + UGCHandle_t generate_file_handle() { static UGCHandle_t val = 0; ++val; @@ -74,12 +77,12 @@ private: }; struct Leaderboard_config { - enum ELeaderboardSortMethod sort_method; - enum ELeaderboardDisplayType display_type; + enum ELeaderboardSortMethod sort_method{}; + enum ELeaderboardDisplayType display_type{}; }; struct Stat_config { - GameServerStats_Messages::StatInfo::Stat_Type type; + GameServerStats_Messages::StatInfo::Stat_Type type{}; union { float default_value_float; int32 default_value_int; @@ -87,21 +90,21 @@ struct Stat_config { }; struct Image_Data { - uint32 width; - uint32 height; - std::string data; + uint32 width{}; + uint32 height{}; + std::string data{}; }; struct Controller_Settings { - std::map, std::string>>> action_sets; - std::map action_set_layer_parents; - std::map, std::string>>> action_set_layers; + std::map, std::string>>> action_sets{}; + std::map action_set_layer_parents{}; + std::map, std::string>>> action_set_layers{}; }; struct Group_Clans { - CSteamID id; - std::string name; - std::string tag; + CSteamID id{}; + std::string name{}; + std::string tag{}; }; struct Overlay_Appearance { @@ -112,7 +115,7 @@ struct Overlay_Appearance { constexpr const static NotificationPosition default_pos = NotificationPosition::top_right; - std::string font_override{}; // path to a custom user-provided font + std::string font_override{}; // path to a custom user-provided TTF font float font_size = 16.0f; float icon_size = 64.0f; @@ -157,23 +160,12 @@ struct Overlay_Appearance { NotificationPosition invite_pos = default_pos; // lobby/game invitation NotificationPosition chat_msg_pos = NotificationPosition::top_center; // chat message from a friend - static NotificationPosition translate_notification_position(const std::string &str) - { - if (str == "top_left") return NotificationPosition::top_left; - else if (str == "top_center") return NotificationPosition::top_center; - else if (str == "top_right") return NotificationPosition::top_right; - else if (str == "bot_left") return NotificationPosition::bot_left; - else if (str == "bot_center") return NotificationPosition::bot_center; - else if (str == "bot_right") return NotificationPosition::bot_right; - - PRINT_DEBUG("Invalid position '%s'", str.c_str()); - return default_pos; - } + static NotificationPosition translate_notification_position(const std::string &str); }; class Settings { - CSteamID steam_id; // user id - CGameID game_id; + CSteamID steam_id{}; // user id + CGameID game_id{}; std::string name{}; std::string language{}; // default "english" CSteamID lobby_id = k_steamIDNil; @@ -294,17 +286,21 @@ public: #ifdef LOBBY_CONNECT - static const bool is_lobby_connect = true; + static constexpr const bool is_lobby_connect = true; #else - static const bool is_lobby_connect = false; + static constexpr const bool is_lobby_connect = false; #endif - static std::string sanitize(const std::string &name); Settings(CSteamID steam_id, CGameID game_id, const std::string &name, const std::string &language, bool offline); + + static std::string sanitize(const std::string &name); + CSteamID get_local_steam_id(); CGameID get_local_game_id(); + const char *get_local_name(); void set_local_name(const char *name); + const char *get_language(); void set_language(const char *language); @@ -313,9 +309,12 @@ public: const std::string& get_supported_languages() const; void set_game_id(CGameID game_id); + void set_lobby(CSteamID lobby_id); CSteamID get_lobby(); + bool is_offline(); + uint16 get_port(); void set_port(uint16 port); @@ -361,4 +360,4 @@ public: }; -#endif +#endif // SETTINGS_INCLUDE_H diff --git a/dll/dll/settings_parser.h b/dll/dll/settings_parser.h index f80ba39d..bb996d6c 100644 --- a/dll/dll/settings_parser.h +++ b/dll/dll/settings_parser.h @@ -15,10 +15,10 @@ License along with the Goldberg Emulator; if not, see . */ -#include "settings.h" +#ifndef SETTINGS_PARSER_INCLUDE_H +#define SETTINGS_PARSER_INCLUDE_H -#ifndef SETTINGS_PARSER_INCLUDE -#define SETTINGS_PARSER_INCLUDE +#include "settings.h" //returns game appid uint32 create_localstorage_settings(Settings **settings_client_out, Settings **settings_server_out, Local_Storage **local_storage_out); @@ -55,4 +55,4 @@ enum class SettingsItf { const std::map& settings_old_interfaces(); -#endif +#endif // SETTINGS_PARSER_INCLUDE_H diff --git a/dll/dll/source_query.h b/dll/dll/source_query.h index 5f9742a7..9da08429 100644 --- a/dll/dll/source_query.h +++ b/dll/dll/source_query.h @@ -15,14 +15,18 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_SOURCE_QUERY_H__ +#define __INCLUDED_SOURCE_QUERY_H__ + #include "base.h" class Source_Query { - Source_Query () = delete; + Source_Query() = delete; ~Source_Query() = delete; public: static std::vector handle_source_query(const void* buffer, size_t len, Gameserver const& gs); }; +#endif // __INCLUDED_SOURCE_QUERY_H__ diff --git a/dll/dll/steam_HTMLsurface.h b/dll/dll/steam_HTMLsurface.h index 1aea6483..201a2d78 100644 --- a/dll/dll/steam_HTMLsurface.h +++ b/dll/dll/steam_HTMLsurface.h @@ -15,6 +15,9 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_HTMLSURFACE_H__ +#define __INCLUDED_STEAM_HTMLSURFACE_H__ + #include "base.h" class Steam_HTMLsurface : @@ -24,354 +27,166 @@ public ISteamHTMLSurface003, public ISteamHTMLSurface004, public ISteamHTMLSurface { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; public: -Steam_HTMLsurface(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks) -{ - this->settings = settings; - this->network = network; - this->callback_results = callback_results; - this->callbacks = callbacks; -} + Steam_HTMLsurface(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks); -// Must call init and shutdown when starting/ending use of the interface -bool Init() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return true; -} + // Must call init and shutdown when starting/ending use of the interface + bool Init(); -bool Shutdown() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return true; -} + bool Shutdown(); -// Create a browser object for display of a html page, when creation is complete the call handle -// will return a HTML_BrowserReady_t callback for the HHTMLBrowser of your new browser. -// The user agent string is a substring to be added to the general user agent string so you can -// identify your client on web servers. -// The userCSS string lets you apply a CSS style sheet to every displayed page, leave null if -// you do not require this functionality. -// -// YOU MUST HAVE IMPLEMENTED HANDLERS FOR HTML_BrowserReady_t, HTML_StartRequest_t, -// HTML_JSAlert_t, HTML_JSConfirm_t, and HTML_FileOpenDialog_t! See the CALLBACKS -// section of this interface (AllowStartRequest, etc) for more details. If you do -// not implement these callback handlers, the browser may appear to hang instead of -// navigating to new pages or triggering javascript popups. -// -STEAM_CALL_RESULT( HTML_BrowserReady_t ) -SteamAPICall_t CreateBrowser( const char *pchUserAgent, const char *pchUserCSS ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - HTML_BrowserReady_t data; - data.unBrowserHandle = 1234869; - - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return ret; -} + // Create a browser object for display of a html page, when creation is complete the call handle + // will return a HTML_BrowserReady_t callback for the HHTMLBrowser of your new browser. + // The user agent string is a substring to be added to the general user agent string so you can + // identify your client on web servers. + // The userCSS string lets you apply a CSS style sheet to every displayed page, leave null if + // you do not require this functionality. + // + // YOU MUST HAVE IMPLEMENTED HANDLERS FOR HTML_BrowserReady_t, HTML_StartRequest_t, + // HTML_JSAlert_t, HTML_JSConfirm_t, and HTML_FileOpenDialog_t! See the CALLBACKS + // section of this interface (AllowStartRequest, etc) for more details. If you do + // not implement these callback handlers, the browser may appear to hang instead of + // navigating to new pages or triggering javascript popups. + // + STEAM_CALL_RESULT( HTML_BrowserReady_t ) + SteamAPICall_t CreateBrowser( const char *pchUserAgent, const char *pchUserCSS ); -// Call this when you are done with a html surface, this lets us free the resources being used by it -void RemoveBrowser( HHTMLBrowser unBrowserHandle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // Call this when you are done with a html surface, this lets us free the resources being used by it + void RemoveBrowser( HHTMLBrowser unBrowserHandle ); -// Navigate to this URL, results in a HTML_StartRequest_t as the request commences -void LoadURL( HHTMLBrowser unBrowserHandle, const char *pchURL, const char *pchPostData ) -{ - PRINT_DEBUG("TODO %s %s", pchURL, pchPostData); - std::lock_guard lock(global_mutex); - static char url[256]; - strncpy(url, pchURL, sizeof(url)); - static char target[] = "_self"; - static char title[] = "title"; - - { - HTML_StartRequest_t data; - data.unBrowserHandle = unBrowserHandle; - data.pchURL = url; - data.pchTarget = target; - data.pchPostData = ""; - data.bIsRedirect = false; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); - } - - { - HTML_FinishedRequest_t data; - data.unBrowserHandle = unBrowserHandle; - data.pchURL = url; - data.pchPageTitle = title; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.8); - } - -} + // Navigate to this URL, results in a HTML_StartRequest_t as the request commences + void LoadURL( HHTMLBrowser unBrowserHandle, const char *pchURL, const char *pchPostData ); -// Tells the surface the size in pixels to display the surface -void SetSize( HHTMLBrowser unBrowserHandle, uint32 unWidth, uint32 unHeight ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // Tells the surface the size in pixels to display the surface + void SetSize( HHTMLBrowser unBrowserHandle, uint32 unWidth, uint32 unHeight ); -// Stop the load of the current html page -void StopLoad( HHTMLBrowser unBrowserHandle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // Stop the load of the current html page + void StopLoad( HHTMLBrowser unBrowserHandle ); -// Reload (most likely from local cache) the current page -void Reload( HHTMLBrowser unBrowserHandle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // Reload (most likely from local cache) the current page + void Reload( HHTMLBrowser unBrowserHandle ); -// navigate back in the page history -void GoBack( HHTMLBrowser unBrowserHandle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // navigate back in the page history + void GoBack( HHTMLBrowser unBrowserHandle ); -// navigate forward in the page history -void GoForward( HHTMLBrowser unBrowserHandle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // navigate forward in the page history + void GoForward( HHTMLBrowser unBrowserHandle ); -// add this header to any url requests from this browser -void AddHeader( HHTMLBrowser unBrowserHandle, const char *pchKey, const char *pchValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // add this header to any url requests from this browser + void AddHeader( HHTMLBrowser unBrowserHandle, const char *pchKey, const char *pchValue ); -// run this javascript script in the currently loaded page -void ExecuteJavascript( HHTMLBrowser unBrowserHandle, const char *pchScript ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // run this javascript script in the currently loaded page + void ExecuteJavascript( HHTMLBrowser unBrowserHandle, const char *pchScript ); -// Mouse click and mouse movement commands -void MouseUp( HHTMLBrowser unBrowserHandle, EHTMLMouseButton eMouseButton ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // Mouse click and mouse movement commands + void MouseUp( HHTMLBrowser unBrowserHandle, EHTMLMouseButton eMouseButton ); -void MouseDown( HHTMLBrowser unBrowserHandle, EHTMLMouseButton eMouseButton ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + void MouseDown( HHTMLBrowser unBrowserHandle, EHTMLMouseButton eMouseButton ); -void MouseDoubleClick( HHTMLBrowser unBrowserHandle, EHTMLMouseButton eMouseButton ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + void MouseDoubleClick( HHTMLBrowser unBrowserHandle, EHTMLMouseButton eMouseButton ); -// x and y are relative to the HTML bounds -void MouseMove( HHTMLBrowser unBrowserHandle, int x, int y ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // x and y are relative to the HTML bounds + void MouseMove( HHTMLBrowser unBrowserHandle, int x, int y ); -// nDelta is pixels of scroll -void MouseWheel( HHTMLBrowser unBrowserHandle, int32 nDelta ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // nDelta is pixels of scroll + void MouseWheel( HHTMLBrowser unBrowserHandle, int32 nDelta ); -// keyboard interactions, native keycode is the key code value from your OS -void KeyDown( HHTMLBrowser unBrowserHandle, uint32 nNativeKeyCode, EHTMLKeyModifiers eHTMLKeyModifiers, bool bIsSystemKey = false ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // keyboard interactions, native keycode is the key code value from your OS + void KeyDown( HHTMLBrowser unBrowserHandle, uint32 nNativeKeyCode, EHTMLKeyModifiers eHTMLKeyModifiers, bool bIsSystemKey = false ); -void KeyDown( HHTMLBrowser unBrowserHandle, uint32 nNativeKeyCode, EHTMLKeyModifiers eHTMLKeyModifiers) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - KeyDown(unBrowserHandle, nNativeKeyCode, eHTMLKeyModifiers, false); -} + void KeyDown( HHTMLBrowser unBrowserHandle, uint32 nNativeKeyCode, EHTMLKeyModifiers eHTMLKeyModifiers); -void KeyUp( HHTMLBrowser unBrowserHandle, uint32 nNativeKeyCode, EHTMLKeyModifiers eHTMLKeyModifiers ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); -} + void KeyUp( HHTMLBrowser unBrowserHandle, uint32 nNativeKeyCode, EHTMLKeyModifiers eHTMLKeyModifiers ); -// cUnicodeChar is the unicode character point for this keypress (and potentially multiple chars per press) -void KeyChar( HHTMLBrowser unBrowserHandle, uint32 cUnicodeChar, EHTMLKeyModifiers eHTMLKeyModifiers ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // cUnicodeChar is the unicode character point for this keypress (and potentially multiple chars per press) + void KeyChar( HHTMLBrowser unBrowserHandle, uint32 cUnicodeChar, EHTMLKeyModifiers eHTMLKeyModifiers ); -// programmatically scroll this many pixels on the page -void SetHorizontalScroll( HHTMLBrowser unBrowserHandle, uint32 nAbsolutePixelScroll ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // programmatically scroll this many pixels on the page + void SetHorizontalScroll( HHTMLBrowser unBrowserHandle, uint32 nAbsolutePixelScroll ); -void SetVerticalScroll( HHTMLBrowser unBrowserHandle, uint32 nAbsolutePixelScroll ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + void SetVerticalScroll( HHTMLBrowser unBrowserHandle, uint32 nAbsolutePixelScroll ); -// tell the html control if it has key focus currently, controls showing the I-beam cursor in text controls amongst other things -void SetKeyFocus( HHTMLBrowser unBrowserHandle, bool bHasKeyFocus ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // tell the html control if it has key focus currently, controls showing the I-beam cursor in text controls amongst other things + void SetKeyFocus( HHTMLBrowser unBrowserHandle, bool bHasKeyFocus ); -// open the current pages html code in the local editor of choice, used for debugging -void ViewSource( HHTMLBrowser unBrowserHandle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // open the current pages html code in the local editor of choice, used for debugging + void ViewSource( HHTMLBrowser unBrowserHandle ); -// copy the currently selected text on the html page to the local clipboard -void CopyToClipboard( HHTMLBrowser unBrowserHandle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // copy the currently selected text on the html page to the local clipboard + void CopyToClipboard( HHTMLBrowser unBrowserHandle ); -// paste from the local clipboard to the current html page -void PasteFromClipboard( HHTMLBrowser unBrowserHandle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // paste from the local clipboard to the current html page + void PasteFromClipboard( HHTMLBrowser unBrowserHandle ); -// find this string in the browser, if bCurrentlyInFind is true then instead cycle to the next matching element -void Find( HHTMLBrowser unBrowserHandle, const char *pchSearchStr, bool bCurrentlyInFind, bool bReverse ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // find this string in the browser, if bCurrentlyInFind is true then instead cycle to the next matching element + void Find( HHTMLBrowser unBrowserHandle, const char *pchSearchStr, bool bCurrentlyInFind, bool bReverse ); -// cancel a currently running find -void StopFind( HHTMLBrowser unBrowserHandle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // cancel a currently running find + void StopFind( HHTMLBrowser unBrowserHandle ); -// return details about the link at position x,y on the current page -void GetLinkAtPosition( HHTMLBrowser unBrowserHandle, int x, int y ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // return details about the link at position x,y on the current page + void GetLinkAtPosition( HHTMLBrowser unBrowserHandle, int x, int y ); -// set a webcookie for the hostname in question -void SetCookie( const char *pchHostname, const char *pchKey, const char *pchValue, const char *pchPath, RTime32 nExpires, bool bSecure, bool bHTTPOnly ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // set a webcookie for the hostname in question + void SetCookie( const char *pchHostname, const char *pchKey, const char *pchValue, const char *pchPath, RTime32 nExpires, bool bSecure, bool bHTTPOnly ); -// Zoom the current page by flZoom ( from 0.0 to 2.0, so to zoom to 120% use 1.2 ), zooming around point X,Y in the page (use 0,0 if you don't care) -void SetPageScaleFactor( HHTMLBrowser unBrowserHandle, float flZoom, int nPointX, int nPointY ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // Zoom the current page by flZoom ( from 0.0 to 2.0, so to zoom to 120% use 1.2 ), zooming around point X,Y in the page (use 0,0 if you don't care) + void SetPageScaleFactor( HHTMLBrowser unBrowserHandle, float flZoom, int nPointX, int nPointY ); -// Enable/disable low-resource background mode, where javascript and repaint timers are throttled, resources are -// more aggressively purged from memory, and audio/video elements are paused. When background mode is enabled, -// all HTML5 video and audio objects will execute ".pause()" and gain the property "._steam_background_paused = 1". -// When background mode is disabled, any video or audio objects with that property will resume with ".play()". -void SetBackgroundMode( HHTMLBrowser unBrowserHandle, bool bBackgroundMode ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // Enable/disable low-resource background mode, where javascript and repaint timers are throttled, resources are + // more aggressively purged from memory, and audio/video elements are paused. When background mode is enabled, + // all HTML5 video and audio objects will execute ".pause()" and gain the property "._steam_background_paused = 1". + // When background mode is disabled, any video or audio objects with that property will resume with ".play()". + void SetBackgroundMode( HHTMLBrowser unBrowserHandle, bool bBackgroundMode ); -// Scale the output display space by this factor, this is useful when displaying content on high dpi devices. -// Specifies the ratio between physical and logical pixels. -void SetDPIScalingFactor( HHTMLBrowser unBrowserHandle, float flDPIScaling ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // Scale the output display space by this factor, this is useful when displaying content on high dpi devices. + // Specifies the ratio between physical and logical pixels. + void SetDPIScalingFactor( HHTMLBrowser unBrowserHandle, float flDPIScaling ); -void OpenDeveloperTools( HHTMLBrowser unBrowserHandle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + void OpenDeveloperTools( HHTMLBrowser unBrowserHandle ); -// CALLBACKS -// -// These set of functions are used as responses to callback requests -// + // CALLBACKS + // + // These set of functions are used as responses to callback requests + // -// You MUST call this in response to a HTML_StartRequest_t callback -// Set bAllowed to true to allow this navigation, false to cancel it and stay -// on the current page. You can use this feature to limit the valid pages -// allowed in your HTML surface. -void AllowStartRequest( HHTMLBrowser unBrowserHandle, bool bAllowed ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // You MUST call this in response to a HTML_StartRequest_t callback + // Set bAllowed to true to allow this navigation, false to cancel it and stay + // on the current page. You can use this feature to limit the valid pages + // allowed in your HTML surface. + void AllowStartRequest( HHTMLBrowser unBrowserHandle, bool bAllowed ); -// You MUST call this in response to a HTML_JSAlert_t or HTML_JSConfirm_t callback -// Set bResult to true for the OK option of a confirm, use false otherwise -void JSDialogResponse( HHTMLBrowser unBrowserHandle, bool bResult ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // You MUST call this in response to a HTML_JSAlert_t or HTML_JSConfirm_t callback + // Set bResult to true for the OK option of a confirm, use false otherwise + void JSDialogResponse( HHTMLBrowser unBrowserHandle, bool bResult ); -// You MUST call this in response to a HTML_FileOpenDialog_t callback -STEAM_IGNOREATTR() -void FileLoadDialogResponse( HHTMLBrowser unBrowserHandle, const char **pchSelectedFiles ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // You MUST call this in response to a HTML_FileOpenDialog_t callback + STEAM_IGNOREATTR() + void FileLoadDialogResponse( HHTMLBrowser unBrowserHandle, const char **pchSelectedFiles ); }; + +#endif // __INCLUDED_STEAM_HTMLSURFACE_H__ diff --git a/dll/dll/steam_applist.h b/dll/dll/steam_applist.h index 10f872e2..12455672 100644 --- a/dll/dll/steam_applist.h +++ b/dll/dll/steam_applist.h @@ -15,9 +15,13 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_APPLIST_H__ +#define __INCLUDED_STEAM_APPLIST_H__ + #include "base.h" -class Steam_Applist : public ISteamAppList +class Steam_Applist : +public ISteamAppList { public: uint32 GetNumInstalledApps(); @@ -28,3 +32,5 @@ public: int GetAppBuildId( AppId_t nAppID ); // return the buildid of this app, may change at any time based on backend updates to the game }; + +#endif // __INCLUDED_STEAM_APPLIST_H__ diff --git a/dll/dll/steam_apps.h b/dll/dll/steam_apps.h index bce604b9..c626c162 100644 --- a/dll/dll/steam_apps.h +++ b/dll/dll/steam_apps.h @@ -1,3 +1,23 @@ +/* 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_APPS_H__ +#define __INCLUDED_STEAM_APPS_H__ + #include "base.h" class Steam_Apps : @@ -10,7 +30,7 @@ public ISteamApps006, public ISteamApps007, public ISteamApps { - Settings *settings{}; + class Settings *settings{}; class SteamCallResults *callback_results{}; class SteamCallBacks *callbacks{}; @@ -115,3 +135,5 @@ public: // set current DLC AppID being played (or 0 if none). Allows Steam to track usage of major DLC extensions bool SetDlcContext( AppId_t nAppID ); }; + +#endif // __INCLUDED_STEAM_APPS_H__ diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index be5da338..8154cb2e 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -15,6 +15,9 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_CLIENT_H__ +#define __INCLUDED_STEAM_CLIENT_H__ + #include "base.h" #include "appticket.h" #include "steam_user.h" @@ -79,78 +82,81 @@ public ISteamClient020, public ISteamClient { public: - Networking *network; - SteamCallResults *callback_results_server, *callback_results_client; - SteamCallBacks *callbacks_server, *callbacks_client; - Settings *settings_client, *settings_server; - Local_Storage *local_storage; - RunEveryRunCB *run_every_runcb; + Networking *network{}; + SteamCallResults *callback_results_server{}, *callback_results_client{}; + SteamCallBacks *callbacks_server{}, *callbacks_client{}; + Settings *settings_client{}, *settings_server{}; + Local_Storage *local_storage{}; + RunEveryRunCB *run_every_runcb{}; - Ugc_Remote_Storage_Bridge *ugc_bridge; + Ugc_Remote_Storage_Bridge *ugc_bridge{}; - Steam_User *steam_user; - Steam_Friends *steam_friends; - Steam_Utils *steam_utils; - Steam_Matchmaking *steam_matchmaking; - Steam_Matchmaking_Servers *steam_matchmaking_servers; - Steam_User_Stats *steam_user_stats; - Steam_Apps *steam_apps; - Steam_Networking *steam_networking; - Steam_Remote_Storage *steam_remote_storage; - Steam_Screenshots *steam_screenshots; - Steam_HTTP *steam_http; - Steam_Controller *steam_controller; - Steam_UGC *steam_ugc; - Steam_Applist *steam_applist; - Steam_Music *steam_music; - Steam_MusicRemote *steam_musicremote; - Steam_HTMLsurface *steam_HTMLsurface; - Steam_Inventory *steam_inventory; - Steam_Video *steam_video; - Steam_Parental *steam_parental; - Steam_Networking_Sockets *steam_networking_sockets; - Steam_Networking_Sockets_Serialized *steam_networking_sockets_serialized; - Steam_Networking_Messages *steam_networking_messages; - Steam_Game_Coordinator *steam_game_coordinator; - Steam_Networking_Utils *steam_networking_utils; - Steam_Unified_Messages *steam_unified_messages; - Steam_Game_Search *steam_game_search; - Steam_Parties *steam_parties; - Steam_RemotePlay *steam_remoteplay; - Steam_TV *steam_tv; + Steam_User *steam_user{}; + Steam_Friends *steam_friends{}; + Steam_Utils *steam_utils{}; + Steam_Matchmaking *steam_matchmaking{}; + Steam_Matchmaking_Servers *steam_matchmaking_servers{}; + Steam_User_Stats *steam_user_stats{}; + Steam_Apps *steam_apps{}; + Steam_Networking *steam_networking{}; + Steam_Remote_Storage *steam_remote_storage{}; + Steam_Screenshots *steam_screenshots{}; + Steam_HTTP *steam_http{}; + Steam_Controller *steam_controller{}; + Steam_UGC *steam_ugc{}; + Steam_Applist *steam_applist{}; + Steam_Music *steam_music{}; + Steam_MusicRemote *steam_musicremote{}; + Steam_HTMLsurface *steam_HTMLsurface{}; + Steam_Inventory *steam_inventory{}; + Steam_Video *steam_video{}; + Steam_Parental *steam_parental{}; + Steam_Networking_Sockets *steam_networking_sockets{}; + Steam_Networking_Sockets_Serialized *steam_networking_sockets_serialized{}; + Steam_Networking_Messages *steam_networking_messages{}; + Steam_Game_Coordinator *steam_game_coordinator{}; + Steam_Networking_Utils *steam_networking_utils{}; + Steam_Unified_Messages *steam_unified_messages{}; + Steam_Game_Search *steam_game_search{}; + Steam_Parties *steam_parties{}; + Steam_RemotePlay *steam_remoteplay{}; + Steam_TV *steam_tv{}; - Steam_GameServer *steam_gameserver; - Steam_Utils *steam_gameserver_utils; - Steam_GameServerStats *steam_gameserverstats; - Steam_Networking *steam_gameserver_networking; - Steam_HTTP *steam_gameserver_http; - Steam_Inventory *steam_gameserver_inventory; - Steam_UGC *steam_gameserver_ugc; - Steam_Apps *steam_gameserver_apps; - Steam_Networking_Sockets *steam_gameserver_networking_sockets; - Steam_Networking_Sockets_Serialized *steam_gameserver_networking_sockets_serialized; - Steam_Networking_Messages *steam_gameserver_networking_messages; - Steam_Game_Coordinator *steam_gameserver_game_coordinator; - Steam_Masterserver_Updater *steam_masterserver_updater; - Steam_AppTicket *steam_app_ticket; + Steam_GameServer *steam_gameserver{}; + Steam_Utils *steam_gameserver_utils{}; + Steam_GameServerStats *steam_gameserverstats{}; + Steam_Networking *steam_gameserver_networking{}; + Steam_HTTP *steam_gameserver_http{}; + Steam_Inventory *steam_gameserver_inventory{}; + Steam_UGC *steam_gameserver_ugc{}; + Steam_Apps *steam_gameserver_apps{}; + Steam_Networking_Sockets *steam_gameserver_networking_sockets{}; + Steam_Networking_Sockets_Serialized *steam_gameserver_networking_sockets_serialized{}; + Steam_Networking_Messages *steam_gameserver_networking_messages{}; + Steam_Game_Coordinator *steam_gameserver_game_coordinator{}; + Steam_Masterserver_Updater *steam_masterserver_updater{}; + Steam_AppTicket *steam_app_ticket{}; - Steam_Overlay* steam_overlay; + Steam_Overlay* steam_overlay{}; bool user_logged_in = false; bool server_init = false; - std::thread background_keepalive; bool steamclient_server_inited = false; + + bool gameserver_has_ipv6_functions{}; + + std::thread background_keepalive{}; std::atomic last_cb_run{}; std::atomic_bool cb_run_active = false; unsigned steam_pipe_counter = 1; - std::map steam_pipes; + std::map steam_pipes{}; - bool gameserver_has_ipv6_functions; Steam_Client(); ~Steam_Client(); - // Creates a communication pipe to the Steam client. + + // Creates a communication pipe to the Steam client. // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling HSteamPipe CreateSteamPipe(); @@ -311,3 +317,5 @@ public: void DestroyAllInterfaces(); }; + +#endif // __INCLUDED_STEAM_CLIENT_H__ diff --git a/dll/dll/steam_controller.h b/dll/dll/steam_controller.h index aa957ef5..0d9b0ed7 100644 --- a/dll/dll/steam_controller.h +++ b/dll/dll/steam_controller.h @@ -15,64 +15,39 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_CONTROLLER_H__ +#define __INCLUDED_STEAM_CONTROLLER_H__ + #include "base.h" -#ifndef CONTROLLER_SUPPORT -inline void GamepadInit(void) {} -inline void GamepadShutdown(void) {} -inline void GamepadUpdate(void) {} -inline GAMEPAD_BOOL GamepadIsConnected(GAMEPAD_DEVICE device) { return GAMEPAD_FALSE; } -inline GAMEPAD_BOOL GamepadButtonDown(GAMEPAD_DEVICE device, GAMEPAD_BUTTON button) { return GAMEPAD_FALSE; } -inline float GamepadTriggerLength(GAMEPAD_DEVICE device, GAMEPAD_TRIGGER trigger) { return 0.0; } -inline GAMEPAD_STICKDIR GamepadStickDir(GAMEPAD_DEVICE device, GAMEPAD_STICK stick) { return STICKDIR_CENTER; } -inline void GamepadStickNormXY(GAMEPAD_DEVICE device, GAMEPAD_STICK stick, float* outX, float* outY) {} -inline float GamepadStickLength(GAMEPAD_DEVICE device, GAMEPAD_STICK stick) { return 0.0; } -inline void GamepadSetRumble(GAMEPAD_DEVICE device, float left, float right, unsigned int rumble_length_ms) {} -#endif + struct Controller_Map { - std::map> active_digital; - std::map, enum EInputSourceMode>> active_analog; + std::map> active_digital{}; + std::map, enum EInputSourceMode>> active_analog{}; }; struct Controller_Action { - ControllerHandle_t controller_handle; - struct Controller_Map active_map; - ControllerDigitalActionHandle_t active_set; + ControllerHandle_t controller_handle{}; + struct Controller_Map active_map{}; + ControllerDigitalActionHandle_t active_set{}; - Controller_Action(ControllerHandle_t controller_handle) { - this->controller_handle = controller_handle; - } + Controller_Action(ControllerHandle_t controller_handle); - void activate_action_set(ControllerDigitalActionHandle_t active_set, std::map &controller_maps) { - auto map = controller_maps.find(active_set); - if (map == controller_maps.end()) return; - this->active_set = active_set; - this->active_map = map->second; - } - - std::set button_id(ControllerDigitalActionHandle_t handle) { - auto a = active_map.active_digital.find(handle); - if (a == active_map.active_digital.end()) return {}; - return a->second; - } - - std::pair, enum EInputSourceMode> analog_id(ControllerAnalogActionHandle_t handle) { - auto a = active_map.active_analog.find(handle); - if (a == active_map.active_analog.end()) return std::pair, enum EInputSourceMode>({}, k_EInputSourceMode_None); - return a->second; - } + void activate_action_set(ControllerDigitalActionHandle_t active_set, std::map &controller_maps); + std::set button_id(ControllerDigitalActionHandle_t handle); + std::pair, enum EInputSourceMode> analog_id(ControllerAnalogActionHandle_t handle); }; struct Rumble_Thread_Data { - std::condition_variable rumble_thread_cv; - bool kill_rumble_thread; - std::mutex rumble_mutex; + std::condition_variable rumble_thread_cv{}; + bool kill_rumble_thread{}; + std::mutex rumble_mutex{}; struct Rumble_Data { - unsigned short left, right, last_left, last_right; - unsigned int rumble_length_ms; - bool new_data; + unsigned short left{}, right{}, last_left{}, last_right{}; + unsigned int rumble_length_ms{}; + bool new_data{}; } data[GAMEPAD_COUNT]; }; @@ -89,11 +64,8 @@ enum EXTRA_GAMEPAD_BUTTONS { BUTTON_STICK_RIGHT_RIGHT = BUTTON_COUNT + 10, }; -#define JOY_ID_START 10 -#define STICK_DPAD 3 -#define DEADZONE_BUTTON_STICK 0.3 - class Steam_Controller : +// --- ISteamController public ISteamController001, public ISteamController003, public ISteamController004, @@ -101,1130 +73,263 @@ public ISteamController005, public ISteamController006, public ISteamController007, public ISteamController, +// --- + +// --- ISteamInput public ISteamInput001, public ISteamInput002, public ISteamInput005, public ISteamInput +// --- { - class Settings *settings; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + static const std::map button_strings; + static const std::map analog_strings; + static const std::map analog_input_modes; - std::map button_strings = { - {"DUP", BUTTON_DPAD_UP}, - {"DDOWN", BUTTON_DPAD_DOWN}, - {"DLEFT", BUTTON_DPAD_LEFT}, - {"DRIGHT", BUTTON_DPAD_RIGHT}, - {"START", BUTTON_START}, - {"BACK", BUTTON_BACK}, - {"LSTICK", BUTTON_LEFT_THUMB}, - {"RSTICK", BUTTON_RIGHT_THUMB}, - {"LBUMPER", BUTTON_LEFT_SHOULDER}, - {"RBUMPER", BUTTON_RIGHT_SHOULDER}, - {"A", BUTTON_A}, - {"B", BUTTON_B}, - {"X", BUTTON_X}, - {"Y", BUTTON_Y}, - {"DLTRIGGER", BUTTON_LTRIGGER}, - {"DRTRIGGER", BUTTON_RTRIGGER}, - {"DLJOYUP", BUTTON_STICK_LEFT_UP}, - {"DLJOYDOWN", BUTTON_STICK_LEFT_DOWN}, - {"DLJOYLEFT", BUTTON_STICK_LEFT_LEFT}, - {"DLJOYRIGHT", BUTTON_STICK_LEFT_RIGHT}, - {"DRJOYUP", BUTTON_STICK_RIGHT_UP}, - {"DRJOYDOWN", BUTTON_STICK_RIGHT_DOWN}, - {"DRJOYLEFT", BUTTON_STICK_RIGHT_LEFT}, - {"DRJOYRIGHT", BUTTON_STICK_RIGHT_RIGHT}, - }; + class Settings *settings{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; - std::map analog_strings = { - {"LTRIGGER", TRIGGER_LEFT}, - {"RTRIGGER", TRIGGER_RIGHT}, - {"LJOY", STICK_LEFT + JOY_ID_START}, - {"RJOY", STICK_RIGHT + JOY_ID_START}, - {"DPAD", STICK_DPAD + JOY_ID_START}, - }; + std::map action_handles{}; + std::map digital_action_handles{}; + std::map analog_action_handles{}; - std::map analog_input_modes = { - {"joystick_move", k_EInputSourceMode_JoystickMove}, - {"joystick_camera", k_EInputSourceMode_JoystickCamera}, - {"trigger", k_EInputSourceMode_Trigger}, - }; + std::map controller_maps{}; + std::map controllers{}; - std::map action_handles; - std::map digital_action_handles; - std::map analog_action_handles; + std::map steaminput_glyphs{}; + std::map steamcontroller_glyphs{}; - std::map controller_maps; - std::map controllers; + std::thread background_rumble_thread{}; + Rumble_Thread_Data *rumble_thread_data{}; - std::map steaminput_glyphs; - std::map steamcontroller_glyphs; + bool disabled{}; + bool initialized{}; + bool explicitly_call_run_frame{}; - std::thread background_rumble_thread; - Rumble_Thread_Data *rumble_thread_data; + void set_handles(std::map, std::string>>> action_sets); - bool disabled; - bool initialized; - bool explicitly_call_run_frame; + void RunCallbacks(); - void set_handles(std::map, std::string>>> action_sets) { - uint64 handle_num = 1; - for (auto & set : action_sets) { - ControllerActionSetHandle_t action_handle_num = handle_num; - ++handle_num; - - action_handles[set.first] = action_handle_num; - for (auto & config_key : set.second) { - uint64 current_handle_num = handle_num; - ++handle_num; - - for (auto & button_string : config_key.second.first) { - auto digital = button_strings.find(button_string); - if (digital != button_strings.end()) { - ControllerDigitalActionHandle_t digital_handle_num = current_handle_num; - - if (digital_action_handles.find(config_key.first) == digital_action_handles.end()) { - digital_action_handles[config_key.first] = digital_handle_num; - } else { - digital_handle_num = digital_action_handles[config_key.first]; - } - - controller_maps[action_handle_num].active_digital[digital_handle_num].insert(digital->second); - } else { - auto analog = analog_strings.find(button_string); - if (analog != analog_strings.end()) { - ControllerAnalogActionHandle_t analog_handle_num = current_handle_num; - - enum EInputSourceMode source_mode; - if (analog->second == TRIGGER_LEFT || analog->second == TRIGGER_RIGHT) { - source_mode = k_EInputSourceMode_Trigger; - } else { - source_mode = k_EInputSourceMode_JoystickMove; - } - - auto input_mode = analog_input_modes.find(config_key.second.second); - if (input_mode != analog_input_modes.end()) { - source_mode = input_mode->second; - } - - if (analog_action_handles.find(config_key.first) == analog_action_handles.end()) { - analog_action_handles[config_key.first] = analog_handle_num; - } else { - analog_handle_num = analog_action_handles[config_key.first]; - } - - controller_maps[action_handle_num].active_analog[analog_handle_num].first.insert(analog->second); - controller_maps[action_handle_num].active_analog[analog_handle_num].second = source_mode; - - } else { - PRINT_DEBUG("Did not recognize controller button %s", button_string.c_str()); - continue; - } - } - } - } - } - } + static void background_rumble(Rumble_Thread_Data *data); + static void steam_run_every_runcb(void *object); public: + Steam_Controller(class Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_Controller(); -static void background_rumble(Rumble_Thread_Data *data) -{ - while (true) { - unsigned short left, right; - unsigned int rumble_length_ms; - int gamepad = -1; - while (gamepad == -1) { - std::unique_lock lck(data->rumble_mutex); - if (data->kill_rumble_thread) { - return; - } - - data->rumble_thread_cv.wait_for(lck, std::chrono::milliseconds(1000)); - if (data->kill_rumble_thread) { - return; - } - - for (int i = 0; i < GAMEPAD_COUNT; ++i) { - if (data->data[i].new_data) { - left = data->data[i].left; - right = data->data[i].right; - rumble_length_ms = data->data[i].rumble_length_ms; - data->data[i].new_data = false; - if (data->data[i].last_left != left || data->data[i].last_right != right) { - gamepad = i; - data->data[i].last_left = left; - data->data[i].last_right = right; - break; - } - } - } - } - - GamepadSetRumble((GAMEPAD_DEVICE)gamepad, ((double)left) / 65535.0, ((double)right) / 65535.0, rumble_length_ms); - } -} - -static void steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Controller *steam_controller = (Steam_Controller *)object; - steam_controller->RunCallbacks(); -} - -Steam_Controller(class Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) -{ - this->settings = settings; - this->run_every_runcb = run_every_runcb; - this->run_every_runcb->add(&Steam_Controller::steam_run_every_runcb, this); - - this->callback_results = callback_results; - this->callbacks = callbacks; - - set_handles(settings->controller_settings.action_sets); - disabled = !action_handles.size(); - initialized = false; -} - -~Steam_Controller() -{ - //TODO rm network callbacks - //TODO rumble thread - this->run_every_runcb->remove(&Steam_Controller::steam_run_every_runcb, this); -} - -// Init and Shutdown must be called when starting/ending use of this interface -bool Init(bool bExplicitlyCallRunFrame) -{ - PRINT_DEBUG("%u", bExplicitlyCallRunFrame); - std::lock_guard lock(global_mutex); - if (disabled || initialized) { - return true; - } - - GamepadInit(); - GamepadUpdate(); - - for (int i = 1; i < 5; ++i) { - struct Controller_Action cont_action(i); - //Activate the first action set. - //TODO: check exactly what decides which gets activated by default - if (action_handles.size() >= 1) { - cont_action.activate_action_set(action_handles.begin()->second, controller_maps); - } - - controllers.insert(std::pair(i, cont_action)); - } - - rumble_thread_data = new Rumble_Thread_Data(); - background_rumble_thread = std::thread(background_rumble, rumble_thread_data); - - initialized = true; - explicitly_call_run_frame = bExplicitlyCallRunFrame; - return true; -} - -bool Init( const char *pchAbsolutePathToControllerConfigVDF ) -{ - PRINT_DEBUG("old"); - return Init(); -} - -bool Init() -{ - return Init(true); -} - -bool Shutdown() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (disabled || !initialized) { - return true; - } - - controllers = std::map(); - rumble_thread_data->rumble_mutex.lock(); - rumble_thread_data->kill_rumble_thread = true; - rumble_thread_data->rumble_mutex.unlock(); - rumble_thread_data->rumble_thread_cv.notify_one(); - background_rumble_thread.join(); - delete rumble_thread_data; - GamepadShutdown(); - initialized = false; - return true; -} - -void SetOverrideMode( const char *pchMode ) -{ - PRINT_DEBUG_TODO(); -} - -// Set the absolute path to the Input Action Manifest file containing the in-game actions -// and file paths to the official configurations. Used in games that bundle Steam Input -// configurations inside of the game depot instead of using the Steam Workshop -bool SetInputActionManifestFilePath( const char *pchInputActionManifestAbsolutePath ) -{ - PRINT_DEBUG_TODO(); - //TODO SteamInput005 - return false; -} - -bool BWaitForData( bool bWaitForever, uint32 unTimeout ) -{ - PRINT_DEBUG_TODO(); - //TODO SteamInput005 - return false; -} - -// Returns true if new data has been received since the last time action data was accessed -// via GetDigitalActionData or GetAnalogActionData. The game will still need to call -// SteamInput()->RunFrame() or SteamAPI_RunCallbacks() before this to update the data stream -bool BNewDataAvailable() -{ - PRINT_DEBUG_TODO(); - //TODO SteamInput005 - return false; -} - -// Enable SteamInputDeviceConnected_t and SteamInputDeviceDisconnected_t callbacks. -// Each controller that is already connected will generate a device connected -// callback when you enable them -void EnableDeviceCallbacks() -{ - PRINT_DEBUG_TODO(); - //TODO SteamInput005 - return; -} - -// Enable SteamInputActionEvent_t callbacks. Directly calls your callback function -// for lower latency than standard Steam callbacks. Supports one callback at a time. -// Note: this is called within either SteamInput()->RunFrame or by SteamAPI_RunCallbacks -void EnableActionEventCallbacks( SteamInputActionEventCallbackPointer pCallback ) -{ - PRINT_DEBUG_TODO(); - //TODO SteamInput005 - return; -} - -// Synchronize API state with the latest Steam Controller inputs available. This -// is performed automatically by SteamAPI_RunCallbacks, but for the absolute lowest -// possible latency, you call this directly before reading controller state. -void RunFrame(bool bReservedValue) -{ - PRINT_DEBUG_ENTRY(); - if (disabled || !initialized) { - return; - } - - GamepadUpdate(); -} - -void RunFrame() -{ - RunFrame(true); -} - -bool GetControllerState( uint32 unControllerIndex, SteamControllerState001_t *pState ) -{ - PRINT_DEBUG_TODO(); - return false; -} - -// Enumerate currently connected controllers -// handlesOut should point to a STEAM_CONTROLLER_MAX_COUNT sized array of ControllerHandle_t handles -// Returns the number of handles written to handlesOut -int GetConnectedControllers( ControllerHandle_t *handlesOut ) -{ - PRINT_DEBUG_ENTRY(); - if (!handlesOut) return 0; - if (disabled) { - return 0; - } - - int count = 0; - if (GamepadIsConnected(GAMEPAD_0)) {*handlesOut = GAMEPAD_0 + 1; ++handlesOut; ++count;}; - if (GamepadIsConnected(GAMEPAD_1)) {*handlesOut = GAMEPAD_1 + 1; ++handlesOut; ++count;}; - if (GamepadIsConnected(GAMEPAD_2)) {*handlesOut = GAMEPAD_2 + 1; ++handlesOut; ++count;}; - if (GamepadIsConnected(GAMEPAD_3)) {*handlesOut = GAMEPAD_3 + 1; ++handlesOut; ++count;}; - - PRINT_DEBUG("returned %i connected controllers", count); - return count; -} - - -// Invokes the Steam overlay and brings up the binding screen -// Returns false is overlay is disabled / unavailable, or the user is not in Big Picture mode -bool ShowBindingPanel( ControllerHandle_t controllerHandle ) -{ - PRINT_DEBUG_TODO(); - return false; -} - - -// ACTION SETS -// Lookup the handle for an Action Set. Best to do this once on startup, and store the handles for all future API calls. -ControllerActionSetHandle_t GetActionSetHandle( const char *pszActionSetName ) -{ - PRINT_DEBUG("%s", pszActionSetName); - if (!pszActionSetName) return 0; - std::string upper_action_name(pszActionSetName); - std::transform(upper_action_name.begin(), upper_action_name.end(), upper_action_name.begin(),[](unsigned char c){ return std::toupper(c); }); - - auto set_handle = action_handles.find(upper_action_name); - if (set_handle == action_handles.end()) return 0; - - PRINT_DEBUG("%s ret %llu", pszActionSetName, set_handle->second); - return set_handle->second; -} - - -// Reconfigure the controller to use the specified action set (ie 'Menu', 'Walk' or 'Drive') -// This is cheap, and can be safely called repeatedly. It's often easier to repeatedly call it in -// your state loops, instead of trying to place it in all of your state transitions. -void ActivateActionSet( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle ) -{ - PRINT_DEBUG("%llu %llu", controllerHandle, actionSetHandle); - if (controllerHandle == STEAM_CONTROLLER_HANDLE_ALL_CONTROLLERS) { - for (auto & c: controllers) { - c.second.activate_action_set(actionSetHandle, controller_maps); - } - } - - auto controller = controllers.find(controllerHandle); - if (controller == controllers.end()) return; - - controller->second.activate_action_set(actionSetHandle, controller_maps); -} - -ControllerActionSetHandle_t GetCurrentActionSet( ControllerHandle_t controllerHandle ) -{ - //TODO: should return zero if no action set specifically activated with ActivateActionSet - PRINT_DEBUG("%llu", controllerHandle); - auto controller = controllers.find(controllerHandle); - if (controller == controllers.end()) return 0; - - return controller->second.active_set; -} - - -void ActivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ) -{ - PRINT_DEBUG_TODO(); -} - -void DeactivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ) -{ - PRINT_DEBUG_TODO(); -} - -void DeactivateAllActionSetLayers( ControllerHandle_t controllerHandle ) -{ - PRINT_DEBUG_TODO(); -} - -int GetActiveActionSetLayers( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t *handlesOut ) -{ - PRINT_DEBUG_TODO(); - return 0; -} - - - -// ACTIONS -// Lookup the handle for a digital action. Best to do this once on startup, and store the handles for all future API calls. -ControllerDigitalActionHandle_t GetDigitalActionHandle( const char *pszActionName ) -{ - PRINT_DEBUG("%s", pszActionName); - if (!pszActionName) return 0; - std::string upper_action_name(pszActionName); - std::transform(upper_action_name.begin(), upper_action_name.end(), upper_action_name.begin(),[](unsigned char c){ return std::toupper(c); }); - - auto handle = digital_action_handles.find(upper_action_name); - if (handle == digital_action_handles.end()) { - //apparently GetDigitalActionHandle also works with analog handles - handle = analog_action_handles.find(upper_action_name); - if (handle == analog_action_handles.end()) return 0; - } - - PRINT_DEBUG("%s ret %llu", pszActionName, handle->second); - return handle->second; -} - - -// Returns the current state of the supplied digital game action -ControllerDigitalActionData_t GetDigitalActionData( ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle ) -{ - PRINT_DEBUG("%llu %llu", controllerHandle, digitalActionHandle); - ControllerDigitalActionData_t digitalData; - digitalData.bActive = false; - digitalData.bState = false; - - auto controller = controllers.find(controllerHandle); - if (controller == controllers.end()) return digitalData; - - std::set buttons = controller->second.button_id(digitalActionHandle); - if (!buttons.size()) return digitalData; - digitalData.bActive = true; - - GAMEPAD_DEVICE device = (GAMEPAD_DEVICE)(controllerHandle - 1); - - for (auto button : buttons) { - bool pressed = false; - if (button < BUTTON_COUNT) { - pressed = GamepadButtonDown(device, (GAMEPAD_BUTTON)button); - } else { - switch (button) { - case BUTTON_LTRIGGER: - pressed = GamepadTriggerLength(device, TRIGGER_LEFT) > 0.8; - break; - case BUTTON_RTRIGGER: - pressed = GamepadTriggerLength(device, TRIGGER_RIGHT) > 0.8; - break; - case BUTTON_STICK_LEFT_UP: - case BUTTON_STICK_LEFT_DOWN: - case BUTTON_STICK_LEFT_LEFT: - case BUTTON_STICK_LEFT_RIGHT: { - float x = 0, y = 0, len = GamepadStickLength(device, STICK_LEFT); - GamepadStickNormXY(device, STICK_LEFT, &x, &y); - x *= len; - y *= len; - if (button == BUTTON_STICK_LEFT_UP) pressed = y > DEADZONE_BUTTON_STICK; - if (button == BUTTON_STICK_LEFT_DOWN) pressed = y < -DEADZONE_BUTTON_STICK; - if (button == BUTTON_STICK_LEFT_RIGHT) pressed = x > DEADZONE_BUTTON_STICK; - if (button == BUTTON_STICK_LEFT_LEFT) pressed = x < -DEADZONE_BUTTON_STICK; - break; - } - case BUTTON_STICK_RIGHT_UP: - case BUTTON_STICK_RIGHT_DOWN: - case BUTTON_STICK_RIGHT_LEFT: - case BUTTON_STICK_RIGHT_RIGHT: { - float x = 0, y = 0, len = GamepadStickLength(device, STICK_RIGHT); - GamepadStickNormXY(device, STICK_RIGHT, &x, &y); - x *= len; - y *= len; - if (button == BUTTON_STICK_RIGHT_UP) pressed = y > DEADZONE_BUTTON_STICK; - if (button == BUTTON_STICK_RIGHT_DOWN) pressed = y < -DEADZONE_BUTTON_STICK; - if (button == BUTTON_STICK_RIGHT_RIGHT) pressed = x > DEADZONE_BUTTON_STICK; - if (button == BUTTON_STICK_RIGHT_LEFT) pressed = x < -DEADZONE_BUTTON_STICK; - break; - } - default: - break; - } - } - - if (pressed) { - digitalData.bState = true; - break; - } - } - - return digitalData; -} - - -// Get the origin(s) for a digital action within an action set. Returns the number of origins supplied in originsOut. Use this to display the appropriate on-screen prompt for the action. -// originsOut should point to a STEAM_CONTROLLER_MAX_ORIGINS sized array of EControllerActionOrigin handles -int GetDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerDigitalActionHandle_t digitalActionHandle, EControllerActionOrigin *originsOut ) -{ - PRINT_DEBUG_ENTRY(); - EInputActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; - int ret = GetDigitalActionOrigins(controllerHandle, actionSetHandle, digitalActionHandle, origins ); - for (int i = 0; i < ret; ++i) { - originsOut[i] = (EControllerActionOrigin)(origins[i] - (k_EInputActionOrigin_XBox360_A - k_EControllerActionOrigin_XBox360_A)); - } - - return ret; -} - -int GetDigitalActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputDigitalActionHandle_t digitalActionHandle, EInputActionOrigin *originsOut ) -{ - PRINT_DEBUG_ENTRY(); - auto controller = controllers.find(inputHandle); - if (controller == controllers.end()) return 0; - - auto map = controller_maps.find(actionSetHandle); - if (map == controller_maps.end()) return 0; - - auto a = map->second.active_digital.find(digitalActionHandle); - if (a == map->second.active_digital.end()) return 0; - - int count = 0; - for (auto button: a->second) { - switch (button) { - case BUTTON_A: - originsOut[count] = k_EInputActionOrigin_XBox360_A; - break; - case BUTTON_B: - originsOut[count] = k_EInputActionOrigin_XBox360_B; - break; - case BUTTON_X: - originsOut[count] = k_EInputActionOrigin_XBox360_X; - break; - case BUTTON_Y: - originsOut[count] = k_EInputActionOrigin_XBox360_Y; - break; - case BUTTON_LEFT_SHOULDER: - originsOut[count] = k_EInputActionOrigin_XBox360_LeftBumper; - break; - case BUTTON_RIGHT_SHOULDER: - originsOut[count] = k_EInputActionOrigin_XBox360_RightBumper; - break; - case BUTTON_START: - originsOut[count] = k_EInputActionOrigin_XBox360_Start; - break; - case BUTTON_BACK: - originsOut[count] = k_EInputActionOrigin_XBox360_Back; - break; - case BUTTON_LTRIGGER: - originsOut[count] = k_EInputActionOrigin_XBox360_LeftTrigger_Click; - break; - case BUTTON_RTRIGGER: - originsOut[count] = k_EInputActionOrigin_XBox360_RightTrigger_Click; - break; - case BUTTON_LEFT_THUMB: - originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_Click; - break; - case BUTTON_RIGHT_THUMB: - originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_Click; - break; - - case BUTTON_STICK_LEFT_UP: - originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_DPadNorth; - break; - case BUTTON_STICK_LEFT_DOWN: - originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_DPadSouth; - break; - case BUTTON_STICK_LEFT_LEFT: - originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_DPadWest; - break; - case BUTTON_STICK_LEFT_RIGHT: - originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_DPadEast; - break; - - case BUTTON_STICK_RIGHT_UP: - originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_DPadNorth; - break; - case BUTTON_STICK_RIGHT_DOWN: - originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_DPadSouth; - break; - case BUTTON_STICK_RIGHT_LEFT: - originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_DPadWest; - break; - case BUTTON_STICK_RIGHT_RIGHT: - originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_DPadEast; - break; - - case BUTTON_DPAD_UP: - originsOut[count] = k_EInputActionOrigin_XBox360_DPad_North; - break; - case BUTTON_DPAD_DOWN: - originsOut[count] = k_EInputActionOrigin_XBox360_DPad_South; - break; - case BUTTON_DPAD_LEFT: - originsOut[count] = k_EInputActionOrigin_XBox360_DPad_West; - break; - case BUTTON_DPAD_RIGHT: - originsOut[count] = k_EInputActionOrigin_XBox360_DPad_East; - break; - - default: - originsOut[count] = k_EInputActionOrigin_None; - break; - } - - ++count; - if (count >= STEAM_INPUT_MAX_ORIGINS) { - break; - } - } - - return count; -} - -// Returns a localized string (from Steam's language setting) for the user-facing action name corresponding to the specified handle -const char *GetStringForDigitalActionName( InputDigitalActionHandle_t eActionHandle ) -{ - PRINT_DEBUG_TODO(); - //TODO SteamInput005 - return "Button String"; -} - -// Lookup the handle for an analog action. Best to do this once on startup, and store the handles for all future API calls. -ControllerAnalogActionHandle_t GetAnalogActionHandle( const char *pszActionName ) -{ - PRINT_DEBUG("%s", pszActionName); - if (!pszActionName) return 0; - std::string upper_action_name(pszActionName); - std::transform(upper_action_name.begin(), upper_action_name.end(), upper_action_name.begin(),[](unsigned char c){ return std::toupper(c); }); - - auto handle = analog_action_handles.find(upper_action_name); - if (handle == analog_action_handles.end()) return 0; - - return handle->second; -} - - -// Returns the current state of these supplied analog game action -ControllerAnalogActionData_t GetAnalogActionData( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle ) -{ - PRINT_DEBUG("%llu %llu", controllerHandle, analogActionHandle); - GAMEPAD_DEVICE device = (GAMEPAD_DEVICE)(controllerHandle - 1); - - ControllerAnalogActionData_t data; - data.eMode = k_EInputSourceMode_None; - data.x = data.y = 0; - data.bActive = false; - - auto controller = controllers.find(controllerHandle); - if (controller == controllers.end()) return data; - - auto analog = controller->second.analog_id(analogActionHandle); - if (!analog.first.size()) return data; - - data.bActive = true; - data.eMode = analog.second; - - for (auto a : analog.first) { - if (a >= JOY_ID_START) { - int joystick_id = a - JOY_ID_START; - if (joystick_id == STICK_DPAD) { - int mov_y = (int)GamepadButtonDown(device, BUTTON_DPAD_UP) - (int)GamepadButtonDown(device, BUTTON_DPAD_DOWN); - int mov_x = (int)GamepadButtonDown(device, BUTTON_DPAD_RIGHT) - (int)GamepadButtonDown(device, BUTTON_DPAD_LEFT); - if (mov_y || mov_x) { - data.x = mov_x; - data.y = mov_y; - double length = 1.0 / std::sqrt(data.x * data.x + data.y * data.y); - data.x = data.x * length; - data.y = data.y * length; - } - } else { - GamepadStickNormXY(device, (GAMEPAD_STICK) joystick_id, &data.x, &data.y); - float length = GamepadStickLength(device, (GAMEPAD_STICK) joystick_id); - data.x = data.x * length; - data.y = data.y * length; - } - } else { - data.x = GamepadTriggerLength(device, (GAMEPAD_TRIGGER) a); - } - - if (data.x || data.y) { - break; - } - } - - return data; -} - - -// Get the origin(s) for an analog action within an action set. Returns the number of origins supplied in originsOut. Use this to display the appropriate on-screen prompt for the action. -// originsOut should point to a STEAM_CONTROLLER_MAX_ORIGINS sized array of EControllerActionOrigin handles -int GetAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerAnalogActionHandle_t analogActionHandle, EControllerActionOrigin *originsOut ) -{ - PRINT_DEBUG_ENTRY(); - EInputActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; - int ret = GetAnalogActionOrigins(controllerHandle, actionSetHandle, analogActionHandle, origins ); - for (int i = 0; i < ret; ++i) { - originsOut[i] = (EControllerActionOrigin)(origins[i] - (k_EInputActionOrigin_XBox360_A - k_EControllerActionOrigin_XBox360_A)); - } - - return ret; -} - -int GetAnalogActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputAnalogActionHandle_t analogActionHandle, EInputActionOrigin *originsOut ) -{ - PRINT_DEBUG_ENTRY(); - auto controller = controllers.find(inputHandle); - if (controller == controllers.end()) return 0; - - auto map = controller_maps.find(actionSetHandle); - if (map == controller_maps.end()) return 0; - - auto a = map->second.active_analog.find(analogActionHandle); - if (a == map->second.active_analog.end()) return 0; - - int count = 0; - for (auto b: a->second.first) { - switch (b) { - case TRIGGER_LEFT: - originsOut[count] = k_EInputActionOrigin_XBox360_LeftTrigger_Pull; - break; - case TRIGGER_RIGHT: - originsOut[count] = k_EInputActionOrigin_XBox360_RightTrigger_Pull; - break; - case STICK_LEFT + JOY_ID_START: - originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_Move; - break; - case STICK_RIGHT + JOY_ID_START: - originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_Move; - break; - case STICK_DPAD + JOY_ID_START: - originsOut[count] = k_EInputActionOrigin_XBox360_DPad_Move; - break; - default: - originsOut[count] = k_EInputActionOrigin_None; - break; - } - - ++count; - if (count >= STEAM_INPUT_MAX_ORIGINS) { - break; - } - } - - return count; -} - - -void StopAnalogActionMomentum( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t eAction ) -{ - PRINT_DEBUG("%llu %llu", controllerHandle, eAction); -} - - -// Trigger a haptic pulse on a controller -void TriggerHapticPulse( ControllerHandle_t controllerHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec ) -{ - PRINT_DEBUG_TODO(); -} - -// Trigger a haptic pulse on a controller -void Legacy_TriggerHapticPulse( InputHandle_t inputHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec ) -{ - PRINT_DEBUG_TODO(); - TriggerHapticPulse(inputHandle, eTargetPad, usDurationMicroSec ); -} - -void TriggerHapticPulse( uint32 unControllerIndex, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec ) -{ - PRINT_DEBUG("old"); - TriggerHapticPulse(unControllerIndex, eTargetPad, usDurationMicroSec ); -} - -// Trigger a pulse with a duty cycle of usDurationMicroSec / usOffMicroSec, unRepeat times. -// nFlags is currently unused and reserved for future use. -void TriggerRepeatedHapticPulse( ControllerHandle_t controllerHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec, unsigned short usOffMicroSec, unsigned short unRepeat, unsigned int nFlags ) -{ - PRINT_DEBUG_TODO(); -} - -void Legacy_TriggerRepeatedHapticPulse( InputHandle_t inputHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec, unsigned short usOffMicroSec, unsigned short unRepeat, unsigned int nFlags ) -{ - PRINT_DEBUG_TODO(); - TriggerRepeatedHapticPulse(inputHandle, eTargetPad, usDurationMicroSec, usOffMicroSec, unRepeat, nFlags); -} - - -// Send a haptic pulse, works on Steam Deck and Steam Controller devices -void TriggerSimpleHapticEvent( InputHandle_t inputHandle, EControllerHapticLocation eHapticLocation, uint8 nIntensity, char nGainDB, uint8 nOtherIntensity, char nOtherGainDB ) -{ - PRINT_DEBUG_TODO(); -} - -// Tigger a vibration event on supported controllers. -void TriggerVibration( ControllerHandle_t controllerHandle, unsigned short usLeftSpeed, unsigned short usRightSpeed ) -{ - PRINT_DEBUG("%hu %hu", usLeftSpeed, usRightSpeed); - auto controller = controllers.find(controllerHandle); - if (controller == controllers.end()) return; - - unsigned int rumble_length_ms = 0; -#if defined(__linux__) - //FIXME: shadow of the tomb raider on linux doesn't seem to turn off the rumble so I made it expire after 100ms. Need to check if this is how linux steam actually behaves. - rumble_length_ms = 100; -#endif - - unsigned gamepad_device = (controllerHandle - 1); - if (gamepad_device > GAMEPAD_COUNT) return; - rumble_thread_data->rumble_mutex.lock(); - rumble_thread_data->data[gamepad_device].new_data = true; - rumble_thread_data->data[gamepad_device].left = usLeftSpeed; - rumble_thread_data->data[gamepad_device].right = usRightSpeed; - rumble_thread_data->data[gamepad_device].rumble_length_ms = rumble_length_ms; - rumble_thread_data->rumble_mutex.unlock(); - rumble_thread_data->rumble_thread_cv.notify_one(); -} - -// Trigger a vibration event on supported controllers including Xbox trigger impulse rumble - Steam will translate these commands into haptic pulses for Steam Controllers -void TriggerVibrationExtended( InputHandle_t inputHandle, unsigned short usLeftSpeed, unsigned short usRightSpeed, unsigned short usLeftTriggerSpeed, unsigned short usRightTriggerSpeed ) -{ - PRINT_DEBUG_TODO(); - TriggerVibration(inputHandle, usLeftSpeed, usRightSpeed); - //TODO trigger impulse rumbles -} - -// Set the controller LED color on supported controllers. -void SetLEDColor( ControllerHandle_t controllerHandle, uint8 nColorR, uint8 nColorG, uint8 nColorB, unsigned int nFlags ) -{ - PRINT_DEBUG_TODO(); -} - - -// Returns the associated gamepad index for the specified controller, if emulating a gamepad -int GetGamepadIndexForController( ControllerHandle_t ulControllerHandle ) -{ - PRINT_DEBUG_ENTRY(); - auto controller = controllers.find(ulControllerHandle); - if (controller == controllers.end()) return -1; - - return ulControllerHandle - 1; -} - - -// Returns the associated controller handle for the specified emulated gamepad -ControllerHandle_t GetControllerForGamepadIndex( int nIndex ) -{ - PRINT_DEBUG("%i", nIndex); - ControllerHandle_t out = nIndex + 1; - auto controller = controllers.find(out); - if (controller == controllers.end()) return 0; - return out; -} - - -// Returns raw motion data from the specified controller -ControllerMotionData_t GetMotionData( ControllerHandle_t controllerHandle ) -{ - PRINT_DEBUG_TODO(); - ControllerMotionData_t data = {}; - return data; -} - - -// Attempt to display origins of given action in the controller HUD, for the currently active action set -// Returns false is overlay is disabled / unavailable, or the user is not in Big Picture mode -bool ShowDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle, float flScale, float flXPosition, float flYPosition ) -{ - PRINT_DEBUG_TODO(); - return true; -} - -bool ShowAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle, float flScale, float flXPosition, float flYPosition ) -{ - PRINT_DEBUG_TODO(); - return true; -} - - -// Returns a localized string (from Steam's language setting) for the specified origin -const char *GetStringForActionOrigin( EControllerActionOrigin eOrigin ) -{ - PRINT_DEBUG_TODO(); - return "Button String"; -} - -const char *GetStringForActionOrigin( EInputActionOrigin eOrigin ) -{ - PRINT_DEBUG_TODO(); - return "Button String"; -} - -// Returns a localized string (from Steam's language setting) for the user-facing action name corresponding to the specified handle -const char *GetStringForAnalogActionName( InputAnalogActionHandle_t eActionHandle ) -{ - PRINT_DEBUG_TODO(); - //TODO SteamInput005 - return "Button String"; -} - -// Get a local path to art for on-screen glyph for a particular origin -const char *GetGlyphForActionOrigin( EControllerActionOrigin eOrigin ) -{ - PRINT_DEBUG("%i", eOrigin); - - if (steamcontroller_glyphs.empty()) { - std::string dir = settings->glyphs_directory; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_A] = dir + "button_a.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_B] = dir + "button_b.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_X] = dir + "button_x.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_Y] = dir + "button_y.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftBumper] = dir + "shoulder_l.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightBumper] = dir + "shoulder_r.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_Start] = dir + "xbox_button_start.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_Back] = dir + "xbox_button_select.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftTrigger_Pull] = dir + "trigger_l_pull.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftTrigger_Click] = dir + "trigger_l_click.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightTrigger_Pull] = dir + "trigger_r_pull.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightTrigger_Click] = dir + "trigger_r_click.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftStick_Move] = dir + "stick_l_move.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftStick_Click] = dir + "stick_l_click.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftStick_DPadNorth] = dir + "stick_dpad_n.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftStick_DPadSouth] = dir + "stick_dpad_s.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftStick_DPadWest] = dir + "stick_dpad_w.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftStick_DPadEast] = dir + "stick_dpad_e.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightStick_Move] = dir + "stick_r_move.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightStick_Click] = dir + "stick_r_click.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightStick_DPadNorth] = dir + "stick_dpad_n.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightStick_DPadSouth] = dir + "stick_dpad_s.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightStick_DPadWest] = dir + "stick_dpad_w.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightStick_DPadEast] = dir + "stick_dpad_e.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_DPad_North] = dir + "xbox_button_dpad_n.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_DPad_South] = dir + "xbox_button_dpad_s.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_DPad_West] = dir + "xbox_button_dpad_w.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_DPad_East] = dir + "xbox_button_dpad_e.png"; - steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_DPad_Move] = dir + "xbox_button_dpad_move.png"; - } - - auto glyph = steamcontroller_glyphs.find(eOrigin); - if (glyph == steamcontroller_glyphs.end()) return ""; - return glyph->second.c_str(); -} - -const char *GetGlyphForActionOrigin( EInputActionOrigin eOrigin ) -{ - PRINT_DEBUG("steaminput %i", eOrigin); - if (steaminput_glyphs.empty()) { - std::string dir = settings->glyphs_directory; - steaminput_glyphs[k_EInputActionOrigin_XBox360_A] = dir + "button_a.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_B] = dir + "button_b.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_X] = dir + "button_x.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_Y] = dir + "button_y.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftBumper] = dir + "shoulder_l.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_RightBumper] = dir + "shoulder_r.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_Start] = dir + "xbox_button_start.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_Back] = dir + "xbox_button_select.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftTrigger_Pull] = dir + "trigger_l_pull.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftTrigger_Click] = dir + "trigger_l_click.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_RightTrigger_Pull] = dir + "trigger_r_pull.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_RightTrigger_Click] = dir + "trigger_r_click.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftStick_Move] = dir + "stick_l_move.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftStick_Click] = dir + "stick_l_click.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftStick_DPadNorth] = dir + "stick_dpad_n.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftStick_DPadSouth] = dir + "stick_dpad_s.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftStick_DPadWest] = dir + "stick_dpad_w.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftStick_DPadEast] = dir + "stick_dpad_e.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_RightStick_Move] = dir + "stick_r_move.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_RightStick_Click] = dir + "stick_r_click.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_RightStick_DPadNorth] = dir + "stick_dpad_n.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_RightStick_DPadSouth] = dir + "stick_dpad_s.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_RightStick_DPadWest] = dir + "stick_dpad_w.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_RightStick_DPadEast] = dir + "stick_dpad_e.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_DPad_North] = dir + "xbox_button_dpad_n.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_DPad_South] = dir + "xbox_button_dpad_s.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_DPad_West] = dir + "xbox_button_dpad_w.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_DPad_East] = dir + "xbox_button_dpad_e.png"; - steaminput_glyphs[k_EInputActionOrigin_XBox360_DPad_Move] = dir + "xbox_button_dpad_move.png"; - //steaminput_glyphs[] = dir + ""; - } - - auto glyph = steaminput_glyphs.find(eOrigin); - if (glyph == steaminput_glyphs.end()) return ""; - return glyph->second.c_str(); -} - -// Get a local path to a PNG file for the provided origin's glyph. -const char *GetGlyphPNGForActionOrigin( EInputActionOrigin eOrigin, ESteamInputGlyphSize eSize, uint32 unFlags ) -{ - PRINT_DEBUG_TODO(); - //TODO SteamInput005 - return GetGlyphForActionOrigin(eOrigin); -} - -// Get a local path to a SVG file for the provided origin's glyph. -const char *GetGlyphSVGForActionOrigin( EInputActionOrigin eOrigin, uint32 unFlags ) -{ - PRINT_DEBUG_TODO(); - //TODO SteamInput005 - return ""; -} - -// Get a local path to an older, Big Picture Mode-style PNG file for a particular origin -const char *GetGlyphForActionOrigin_Legacy( EInputActionOrigin eOrigin ) -{ - PRINT_DEBUG_ENTRY(); - return GetGlyphForActionOrigin(eOrigin); -} - -// Returns the input type for a particular handle -ESteamInputType GetInputTypeForHandle( ControllerHandle_t controllerHandle ) -{ - PRINT_DEBUG("%llu", controllerHandle); - auto controller = controllers.find(controllerHandle); - if (controller == controllers.end()) return k_ESteamInputType_Unknown; - return k_ESteamInputType_XBox360Controller; -} - -const char *GetStringForXboxOrigin( EXboxOrigin eOrigin ) -{ - PRINT_DEBUG_TODO(); - return ""; -} - -const char *GetGlyphForXboxOrigin( EXboxOrigin eOrigin ) -{ - PRINT_DEBUG_TODO(); - return ""; -} - -EControllerActionOrigin GetActionOriginFromXboxOrigin_( ControllerHandle_t controllerHandle, EXboxOrigin eOrigin ) -{ - PRINT_DEBUG_TODO(); - return k_EControllerActionOrigin_None; -} - -EInputActionOrigin GetActionOriginFromXboxOrigin( InputHandle_t inputHandle, EXboxOrigin eOrigin ) -{ - PRINT_DEBUG_TODO(); - return k_EInputActionOrigin_None; -} - -EControllerActionOrigin TranslateActionOrigin( ESteamInputType eDestinationInputType, EControllerActionOrigin eSourceOrigin ) -{ - PRINT_DEBUG_TODO(); - return k_EControllerActionOrigin_None; -} - -EInputActionOrigin TranslateActionOrigin( ESteamInputType eDestinationInputType, EInputActionOrigin eSourceOrigin ) -{ - PRINT_DEBUG("steaminput destinationinputtype %d sourceorigin %d", eDestinationInputType, eSourceOrigin ); - - if (eDestinationInputType == k_ESteamInputType_XBox360Controller) - return eSourceOrigin; - - return k_EInputActionOrigin_None; -} - -bool GetControllerBindingRevision( ControllerHandle_t controllerHandle, int *pMajor, int *pMinor ) -{ - PRINT_DEBUG_TODO(); - return false; -} - -bool GetDeviceBindingRevision( InputHandle_t inputHandle, int *pMajor, int *pMinor ) -{ - PRINT_DEBUG_TODO(); - return false; -} - -uint32 GetRemotePlaySessionID( InputHandle_t inputHandle ) -{ - PRINT_DEBUG_TODO(); - return 0; -} - -// Get a bitmask of the Steam Input Configuration types opted in for the current session. Returns ESteamInputConfigurationEnableType values.? -// Note: user can override the settings from the Steamworks Partner site so the returned values may not exactly match your default configuration -uint16 GetSessionInputConfigurationSettings() -{ - PRINT_DEBUG_TODO(); - return 0; -} - -// Set the trigger effect for a DualSense controller -void SetDualSenseTriggerEffect( InputHandle_t inputHandle, const ScePadTriggerEffectParam *pParam ) -{ - PRINT_DEBUG_TODO(); -} - -void RunCallbacks() -{ - if (explicitly_call_run_frame) { - RunFrame(); - } -} + // Init and Shutdown must be called when starting/ending use of this interface + bool Init(bool bExplicitlyCallRunFrame); + + bool Init( const char *pchAbsolutePathToControllerConfigVDF ); + + bool Init(); + + bool Shutdown(); + + void SetOverrideMode( const char *pchMode ); + + // Set the absolute path to the Input Action Manifest file containing the in-game actions + // and file paths to the official configurations. Used in games that bundle Steam Input + // configurations inside of the game depot instead of using the Steam Workshop + bool SetInputActionManifestFilePath( const char *pchInputActionManifestAbsolutePath ); + + bool BWaitForData( bool bWaitForever, uint32 unTimeout ); + + // Returns true if new data has been received since the last time action data was accessed + // via GetDigitalActionData or GetAnalogActionData. The game will still need to call + // SteamInput()->RunFrame() or SteamAPI_RunCallbacks() before this to update the data stream + bool BNewDataAvailable(); + + // Enable SteamInputDeviceConnected_t and SteamInputDeviceDisconnected_t callbacks. + // Each controller that is already connected will generate a device connected + // callback when you enable them + void EnableDeviceCallbacks(); + + // Enable SteamInputActionEvent_t callbacks. Directly calls your callback function + // for lower latency than standard Steam callbacks. Supports one callback at a time. + // Note: this is called within either SteamInput()->RunFrame or by SteamAPI_RunCallbacks + void EnableActionEventCallbacks( SteamInputActionEventCallbackPointer pCallback ); + + // Synchronize API state with the latest Steam Controller inputs available. This + // is performed automatically by SteamAPI_RunCallbacks, but for the absolute lowest + // possible latency, you call this directly before reading controller state. + void RunFrame(bool bReservedValue); + + void RunFrame(); + + bool GetControllerState( uint32 unControllerIndex, SteamControllerState001_t *pState ); + + // Enumerate currently connected controllers + // handlesOut should point to a STEAM_CONTROLLER_MAX_COUNT sized array of ControllerHandle_t handles + // Returns the number of handles written to handlesOut + int GetConnectedControllers( ControllerHandle_t *handlesOut ); + + + // Invokes the Steam overlay and brings up the binding screen + // Returns false is overlay is disabled / unavailable, or the user is not in Big Picture mode + bool ShowBindingPanel( ControllerHandle_t controllerHandle ); + + + // ACTION SETS + // Lookup the handle for an Action Set. Best to do this once on startup, and store the handles for all future API calls. + ControllerActionSetHandle_t GetActionSetHandle( const char *pszActionSetName ); + + + // Reconfigure the controller to use the specified action set (ie 'Menu', 'Walk' or 'Drive') + // This is cheap, and can be safely called repeatedly. It's often easier to repeatedly call it in + // your state loops, instead of trying to place it in all of your state transitions. + void ActivateActionSet( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle ); + + ControllerActionSetHandle_t GetCurrentActionSet( ControllerHandle_t controllerHandle ); + + + void ActivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ); + + void DeactivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ); + + void DeactivateAllActionSetLayers( ControllerHandle_t controllerHandle ); + + int GetActiveActionSetLayers( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t *handlesOut ); + + + + // ACTIONS + // Lookup the handle for a digital action. Best to do this once on startup, and store the handles for all future API calls. + ControllerDigitalActionHandle_t GetDigitalActionHandle( const char *pszActionName ); + + + // Returns the current state of the supplied digital game action + ControllerDigitalActionData_t GetDigitalActionData( ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle ); + + + // Get the origin(s) for a digital action within an action set. Returns the number of origins supplied in originsOut. Use this to display the appropriate on-screen prompt for the action. + // originsOut should point to a STEAM_CONTROLLER_MAX_ORIGINS sized array of EControllerActionOrigin handles + int GetDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerDigitalActionHandle_t digitalActionHandle, EControllerActionOrigin *originsOut ); + + int GetDigitalActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputDigitalActionHandle_t digitalActionHandle, EInputActionOrigin *originsOut ); + + // Returns a localized string (from Steam's language setting) for the user-facing action name corresponding to the specified handle + const char *GetStringForDigitalActionName( InputDigitalActionHandle_t eActionHandle ); + + // Lookup the handle for an analog action. Best to do this once on startup, and store the handles for all future API calls. + ControllerAnalogActionHandle_t GetAnalogActionHandle( const char *pszActionName ); + + + // Returns the current state of these supplied analog game action + ControllerAnalogActionData_t GetAnalogActionData( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle ); + + + // Get the origin(s) for an analog action within an action set. Returns the number of origins supplied in originsOut. Use this to display the appropriate on-screen prompt for the action. + // originsOut should point to a STEAM_CONTROLLER_MAX_ORIGINS sized array of EControllerActionOrigin handles + int GetAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerAnalogActionHandle_t analogActionHandle, EControllerActionOrigin *originsOut ); + + int GetAnalogActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputAnalogActionHandle_t analogActionHandle, EInputActionOrigin *originsOut ); + + + void StopAnalogActionMomentum( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t eAction ); + + + // Trigger a haptic pulse on a controller + void TriggerHapticPulse( ControllerHandle_t controllerHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec ); + + // Trigger a haptic pulse on a controller + void Legacy_TriggerHapticPulse( InputHandle_t inputHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec ); + + void TriggerHapticPulse( uint32 unControllerIndex, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec ); + + // Trigger a pulse with a duty cycle of usDurationMicroSec / usOffMicroSec, unRepeat times. + // nFlags is currently unused and reserved for future use. + void TriggerRepeatedHapticPulse( ControllerHandle_t controllerHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec, unsigned short usOffMicroSec, unsigned short unRepeat, unsigned int nFlags ); + + void Legacy_TriggerRepeatedHapticPulse( InputHandle_t inputHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec, unsigned short usOffMicroSec, unsigned short unRepeat, unsigned int nFlags ); + + + // Send a haptic pulse, works on Steam Deck and Steam Controller devices + void TriggerSimpleHapticEvent( InputHandle_t inputHandle, EControllerHapticLocation eHapticLocation, uint8 nIntensity, char nGainDB, uint8 nOtherIntensity, char nOtherGainDB ); + + // Tigger a vibration event on supported controllers. + void TriggerVibration( ControllerHandle_t controllerHandle, unsigned short usLeftSpeed, unsigned short usRightSpeed ); + + // Trigger a vibration event on supported controllers including Xbox trigger impulse rumble - Steam will translate these commands into haptic pulses for Steam Controllers + void TriggerVibrationExtended( InputHandle_t inputHandle, unsigned short usLeftSpeed, unsigned short usRightSpeed, unsigned short usLeftTriggerSpeed, unsigned short usRightTriggerSpeed ); + + // Set the controller LED color on supported controllers. + void SetLEDColor( ControllerHandle_t controllerHandle, uint8 nColorR, uint8 nColorG, uint8 nColorB, unsigned int nFlags ); + + + // Returns the associated gamepad index for the specified controller, if emulating a gamepad + int GetGamepadIndexForController( ControllerHandle_t ulControllerHandle ); + + + // Returns the associated controller handle for the specified emulated gamepad + ControllerHandle_t GetControllerForGamepadIndex( int nIndex ); + + + // Returns raw motion data from the specified controller + ControllerMotionData_t GetMotionData( ControllerHandle_t controllerHandle ); + + + // Attempt to display origins of given action in the controller HUD, for the currently active action set + // Returns false is overlay is disabled / unavailable, or the user is not in Big Picture mode + bool ShowDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle, float flScale, float flXPosition, float flYPosition ); + + bool ShowAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle, float flScale, float flXPosition, float flYPosition ); + + + // Returns a localized string (from Steam's language setting) for the specified origin + const char *GetStringForActionOrigin( EControllerActionOrigin eOrigin ); + + const char *GetStringForActionOrigin( EInputActionOrigin eOrigin ); + + // Returns a localized string (from Steam's language setting) for the user-facing action name corresponding to the specified handle + const char *GetStringForAnalogActionName( InputAnalogActionHandle_t eActionHandle ); + + // Get a local path to art for on-screen glyph for a particular origin + const char *GetGlyphForActionOrigin( EControllerActionOrigin eOrigin ); + + const char *GetGlyphForActionOrigin( EInputActionOrigin eOrigin ); + + // Get a local path to a PNG file for the provided origin's glyph. + const char *GetGlyphPNGForActionOrigin( EInputActionOrigin eOrigin, ESteamInputGlyphSize eSize, uint32 unFlags ); + + // Get a local path to a SVG file for the provided origin's glyph. + const char *GetGlyphSVGForActionOrigin( EInputActionOrigin eOrigin, uint32 unFlags ); + + // Get a local path to an older, Big Picture Mode-style PNG file for a particular origin + const char *GetGlyphForActionOrigin_Legacy( EInputActionOrigin eOrigin ); + + // Returns the input type for a particular handle + ESteamInputType GetInputTypeForHandle( ControllerHandle_t controllerHandle ); + + const char *GetStringForXboxOrigin( EXboxOrigin eOrigin ); + + const char *GetGlyphForXboxOrigin( EXboxOrigin eOrigin ); + + EControllerActionOrigin GetActionOriginFromXboxOrigin_( ControllerHandle_t controllerHandle, EXboxOrigin eOrigin ); + + EInputActionOrigin GetActionOriginFromXboxOrigin( InputHandle_t inputHandle, EXboxOrigin eOrigin ); + + EControllerActionOrigin TranslateActionOrigin( ESteamInputType eDestinationInputType, EControllerActionOrigin eSourceOrigin ); + + EInputActionOrigin TranslateActionOrigin( ESteamInputType eDestinationInputType, EInputActionOrigin eSourceOrigin ); + + bool GetControllerBindingRevision( ControllerHandle_t controllerHandle, int *pMajor, int *pMinor ); + + bool GetDeviceBindingRevision( InputHandle_t inputHandle, int *pMajor, int *pMinor ); + + uint32 GetRemotePlaySessionID( InputHandle_t inputHandle ); + + // Get a bitmask of the Steam Input Configuration types opted in for the current session. Returns ESteamInputConfigurationEnableType values.? + // Note: user can override the settings from the Steamworks Partner site so the returned values may not exactly match your default configuration + uint16 GetSessionInputConfigurationSettings(); + + // Set the trigger effect for a DualSense controller + void SetDualSenseTriggerEffect( InputHandle_t inputHandle, const ScePadTriggerEffectParam *pParam ); }; + + + +#endif // __INCLUDED_STEAM_CONTROLLER_H__ diff --git a/dll/dll/steam_friends.h b/dll/dll/steam_friends.h index a0e21c3e..087fb984 100644 --- a/dll/dll/steam_friends.h +++ b/dll/dll/steam_friends.h @@ -21,12 +21,10 @@ #include "base.h" #include "overlay/steam_overlay.h" -#define SEND_FRIEND_RATE 4.0 - struct Avatar_Numbers { - int smallest; - int medium; - int large; + int smallest{}; + int medium{}; + int large{}; }; class Steam_Friends : @@ -46,1289 +44,381 @@ public ISteamFriends015, public ISteamFriends016, public ISteamFriends { - class Settings *settings; - class Local_Storage* local_storage; - class Networking *network; - class SteamCallBacks *callbacks; - class SteamCallResults *callback_results; - class RunEveryRunCB *run_every_runcb; - class Steam_Overlay* overlay; + class Settings *settings{}; + class Local_Storage* local_storage{}; + class Networking *network{}; + class SteamCallBacks *callbacks{}; + class SteamCallResults *callback_results{}; + class RunEveryRunCB *run_every_runcb{}; + class Steam_Overlay* overlay{}; - Friend us; - bool modified; - std::vector friends; + Friend us{}; + bool modified{}; + std::vector friends{}; - std::map avatars; - CSteamID lobby_id; + std::map avatars{}; + CSteamID lobby_id{}; - std::chrono::high_resolution_clock::time_point last_sent_friends; + std::chrono::high_resolution_clock::time_point last_sent_friends{}; -Friend *find_friend(CSteamID id) -{ - auto f = std::find_if(friends.begin(), friends.end(), [&id](Friend const& item) { return item.id() == id.ConvertToUint64(); }); - if (friends.end() == f) - return NULL; + Friend *find_friend(CSteamID id); - return &(*f); -} + void persona_change(CSteamID id, EPersonaChange flags); -void persona_change(CSteamID id, EPersonaChange flags) -{ - PersonaStateChange_t data; - data.m_ulSteamID = id.ConvertToUint64(); - data.m_nChangeFlags = flags; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); -} + void rich_presence_updated(CSteamID id, AppId_t appid); -void rich_presence_updated(CSteamID id, AppId_t appid) -{ - FriendRichPresenceUpdate_t data; - data.m_steamIDFriend = id; - data.m_nAppID = appid; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); -} + bool isAppIdCompatible(Friend *f); -bool isAppIdCompatible(Friend *f) -{ - if (settings->is_lobby_connect) return true; - if (f == &us) return true; - return settings->get_local_game_id().AppID() == f->appid(); -} + struct Avatar_Numbers add_friend_avatars(CSteamID id); -struct Avatar_Numbers add_friend_avatars(CSteamID id) -{ - uint64 steam_id = id.ConvertToUint64(); - auto avatar_ids = avatars.find(steam_id); - if (avatar_ids != avatars.end()) { - return avatar_ids->second; - } + static bool ok_friend_flags(int iFriendFlags); - struct Avatar_Numbers avatar_numbers{}; - std::string small_avatar(32 * 32 * 4, 0); - std::string medium_avatar(64 * 64 * 4, 0); - std::string large_avatar(184 * 184 * 4, 0); + static void steam_friends_callback(void *object, Common_Message *msg); + static void steam_friends_run_every_runcb(void *object); - static const std::initializer_list avatar_icons = { - "account_avatar.png", - "account_avatar.jpg", - "account_avatar.jpeg", - }; - - if (!settings->disable_account_avatar && (id == settings->get_local_steam_id())) { - std::string file_path{}; - unsigned long long file_size{}; - - // try local location first, then try global location - for (const auto &settings_path : { Local_Storage::get_game_settings_path(), local_storage->get_global_settings_path() }) { - for (const auto &file_name : avatar_icons) { - file_path = settings_path + file_name; - file_size = file_size_(file_path); - if (file_size) break; - } - if (file_size) break; - } - - // no else statement here for default otherwise this breaks default images for friends - if (file_size) { - small_avatar = Local_Storage::load_image_resized(file_path, "", 32); - medium_avatar = Local_Storage::load_image_resized(file_path, "", 64); - large_avatar = Local_Storage::load_image_resized(file_path, "", 184); - } - } else if (!settings->disable_account_avatar) { - Friend *f = find_friend(id); - if (f && (large_avatar.compare(f->avatar()) != 0)) { - large_avatar = f->avatar(); - medium_avatar = Local_Storage::load_image_resized("", f->avatar(), 64); - small_avatar = Local_Storage::load_image_resized("", f->avatar(), 32); - } else { - std::string file_path{}; - unsigned long long file_size{}; - - // try local location first, then try global location - for (const auto &settings_path : { Local_Storage::get_game_settings_path(), local_storage->get_global_settings_path() }) { - for (const auto &file_name : avatar_icons) { - file_path = settings_path + file_name; - file_size = file_size_(file_path); - if (file_size) break; - } - if (file_size) break; - } - - if (file_size) { - small_avatar = Local_Storage::load_image_resized(file_path, "", 32); - medium_avatar = Local_Storage::load_image_resized(file_path, "", 64); - large_avatar = Local_Storage::load_image_resized(file_path, "", 184); - } - } - } - - avatar_numbers.smallest = settings->add_image(small_avatar, 32, 32); - avatar_numbers.medium = settings->add_image(medium_avatar, 64, 64); - avatar_numbers.large = settings->add_image(large_avatar, 184, 184); - - avatars[steam_id] = avatar_numbers; - return avatar_numbers; -} + void RunCallbacks(); + void Callback(Common_Message *msg); public: -static void steam_friends_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Friends *steam_friends = (Steam_Friends *)object; - steam_friends->Callback(msg); -} - -static void steam_friends_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Friends *steam_friends = (Steam_Friends *)object; - steam_friends->RunCallbacks(); -} - -void resend_friend_data() -{ - modified = true; -} - -Steam_Friends(Settings* settings, class Local_Storage* local_storage, Networking* network, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Steam_Overlay* overlay): - settings(settings), - local_storage(local_storage), - network(network), - callbacks(callbacks), - callback_results(callback_results), - run_every_runcb(run_every_runcb), - overlay(overlay) -{ - this->network->setCallback(CALLBACK_ID_FRIEND, settings->get_local_steam_id(), &Steam_Friends::steam_friends_callback, this); - this->network->setCallback(CALLBACK_ID_FRIEND_MESSAGES, settings->get_local_steam_id(), &Steam_Friends::steam_friends_callback, this); - this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Friends::steam_friends_callback, this); - this->run_every_runcb->add(&Steam_Friends::steam_friends_run_every_runcb, this); - modified = false; -} - -~Steam_Friends() -{ - this->network->rmCallback(CALLBACK_ID_FRIEND, settings->get_local_steam_id(), &Steam_Friends::steam_friends_callback, this); - this->network->rmCallback(CALLBACK_ID_FRIEND_MESSAGES, settings->get_local_steam_id(), &Steam_Friends::steam_friends_callback, this); - this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Friends::steam_friends_callback, this); - this->run_every_runcb->remove(&Steam_Friends::steam_friends_run_every_runcb, this); -} - -static bool ok_friend_flags(int iFriendFlags) -{ - if (iFriendFlags & k_EFriendFlagImmediate) return true; - - return false; -} - -// returns the local players name - guaranteed to not be NULL. -// this is the same name as on the users community profile page -// this is stored in UTF-8 format -// like all the other interface functions that return a char *, it's important that this pointer is not saved -// off; it will eventually be free'd or re-allocated -const char *GetPersonaName() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - const char *local_name = settings->get_local_name(); - - return local_name; -} - -// Sets the player name, stores it on the server and publishes the changes to all friends who are online. -// Changes take place locally immediately, and a PersonaStateChange_t is posted, presuming success. -// -// The final results are available through the return value SteamAPICall_t, using SetPersonaNameResponse_t. -// -// If the name change fails to happen on the server, then an additional global PersonaStateChange_t will be posted -// to change the name back, in addition to the SetPersonaNameResponse_t callback. -STEAM_CALL_RESULT( SetPersonaNameResponse_t ) -SteamAPICall_t SetPersonaName( const char *pchPersonaName ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - SetPersonaNameResponse_t data{}; - data.m_bSuccess = true; - data.m_bLocalSuccess = false; - data.m_result = k_EResultOK; - persona_change(settings->get_local_steam_id(), k_EPersonaChangeName); - - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); - - { - PersonaStateChange_t data2{}; - data2.m_nChangeFlags = EPersonaChange::k_EPersonaChangeName; - data2.m_ulSteamID = settings->get_local_steam_id().ConvertToUint64(); - callbacks->addCBResult(data2.k_iCallback, &data2, sizeof(data2)); - } - - return ret; -} - -void SetPersonaName_old( const char *pchPersonaName ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - SetPersonaName(pchPersonaName); -} - -// gets the status of the current user -EPersonaState GetPersonaState() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - return k_EPersonaStateOnline; -} - - -// friend iteration -// takes a set of k_EFriendFlags, and returns the number of users the client knows about who meet that criteria -// then GetFriendByIndex() can then be used to return the id's of each of those users -int GetFriendCount( int iFriendFlags ) -{ - PRINT_DEBUG("%i", iFriendFlags); - std::lock_guard lock(global_mutex); - int count = 0; - if (ok_friend_flags(iFriendFlags)) count = friends.size(); - PRINT_DEBUG("count %i", count); - return count; -} - -int GetFriendCount( EFriendFlags eFriendFlags ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - return GetFriendCount((int)eFriendFlags); -} - -// returns the steamID of a user -// iFriend is a index of range [0, GetFriendCount()) -// iFriendsFlags must be the same value as used in GetFriendCount() -// the returned CSteamID can then be used by all the functions below to access details about the user -CSteamID GetFriendByIndex( int iFriend, int iFriendFlags ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - CSteamID id = k_steamIDNil; - if (ok_friend_flags(iFriendFlags)) if (iFriend < friends.size()) id = CSteamID((uint64)friends[iFriend].id()); - - return id; -} - -CSteamID GetFriendByIndex( int iFriend, EFriendFlags eFriendFlags ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - return GetFriendByIndex(iFriend, (int)eFriendFlags ); -} - -// returns a relationship to a user -EFriendRelationship GetFriendRelationship( CSteamID steamIDFriend ) -{ - PRINT_DEBUG("%llu", steamIDFriend.ConvertToUint64()); - std::lock_guard lock(global_mutex); - if (steamIDFriend == settings->get_local_steam_id()) return k_EFriendRelationshipNone; //Real steam behavior - if (find_friend(steamIDFriend)) return k_EFriendRelationshipFriend; - - return k_EFriendRelationshipNone; -} - - -// returns the current status of the specified user -// this will only be known by the local user if steamIDFriend is in their friends list; on the same game server; in a chat room or lobby; or in a small group with the local user -EPersonaState GetFriendPersonaState( CSteamID steamIDFriend ) -{ - PRINT_DEBUG("%llu", steamIDFriend.ConvertToUint64()); - std::lock_guard lock(global_mutex); - EPersonaState state = k_EPersonaStateOffline; - if (steamIDFriend == settings->get_local_steam_id() || find_friend(steamIDFriend)) { - state = k_EPersonaStateOnline; - } - - //works because all of those who could be in a lobby are our friends - return state; -} - - -// returns the name another user - guaranteed to not be NULL. -// same rules as GetFriendPersonaState() apply as to whether or not the user knowns the name of the other user -// note that on first joining a lobby, chat room or game server the local user will not known the name of the other users automatically; that information will arrive asyncronously -// -const char *GetFriendPersonaName( CSteamID steamIDFriend ) -{ - PRINT_DEBUG("%llu", steamIDFriend.ConvertToUint64()); - std::lock_guard lock(global_mutex); - const char *name = "Unknown User"; - if (steamIDFriend == settings->get_local_steam_id()) { - name = settings->get_local_name(); - } else { - Friend *f = find_friend(steamIDFriend); - if (f) name = f->name().c_str(); - } - - PRINT_DEBUG("returned '%s'", name); - return name; -} - - -// returns true if the friend is actually in a game, and fills in pFriendGameInfo with an extra details -bool GetFriendGamePlayed( CSteamID steamIDFriend, STEAM_OUT_STRUCT() FriendGameInfo_t *pFriendGameInfo ) -{ - PRINT_DEBUG("%llu %p", steamIDFriend.ConvertToUint64(), pFriendGameInfo); - std::lock_guard lock(global_mutex); - bool ret = false; - - if (steamIDFriend == settings->get_local_steam_id()) { - PRINT_DEBUG("found myself! %llu %llu", settings->get_local_game_id().ToUint64(), settings->get_lobby().ConvertToUint64()); - if (pFriendGameInfo) { - pFriendGameInfo->m_gameID = settings->get_local_game_id(); - pFriendGameInfo->m_unGameIP = 0; - pFriendGameInfo->m_usGamePort = 0; - pFriendGameInfo->m_usQueryPort = 0; - pFriendGameInfo->m_steamIDLobby = settings->get_lobby(); - } - - ret = true; - } else { - Friend *f = find_friend(steamIDFriend); - if (f) { - PRINT_DEBUG("found someone %u " "%" PRIu64 "", f->appid(), f->lobby_id()); - if (pFriendGameInfo) { - pFriendGameInfo->m_gameID = CGameID(f->appid()); - pFriendGameInfo->m_unGameIP = 0; - pFriendGameInfo->m_usGamePort = 0; - pFriendGameInfo->m_usQueryPort = 0; - pFriendGameInfo->m_steamIDLobby = CSteamID((uint64)f->lobby_id()); - } - - ret = true; - } - } - - return ret; -} - -bool GetFriendGamePlayed( CSteamID steamIDFriend, uint64 *pulGameID, uint32 *punGameIP, uint16 *pusGamePort, uint16 *pusQueryPort ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - FriendGameInfo_t info; - bool ret = GetFriendGamePlayed(steamIDFriend, &info); - if (ret) { - if (pulGameID) *pulGameID = info.m_gameID.ToUint64(); - if (punGameIP) *punGameIP = info.m_unGameIP; - if (pusGamePort) *pusGamePort = info.m_usGamePort; - if (pusQueryPort) *pusQueryPort = info.m_usQueryPort; - } - - return ret; -} - -// accesses old friends names - returns an empty string when their are no more items in the history -const char *GetFriendPersonaNameHistory( CSteamID steamIDFriend, int iPersonaName ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - const char *ret = ""; - if (iPersonaName == 0) ret = GetFriendPersonaName(steamIDFriend); - else if (iPersonaName == 1) ret = "Some Old Name"; - - return ret; -} - -// friends steam level -int GetFriendSteamLevel( CSteamID steamIDFriend ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 100; -} - - -// Returns nickname the current user has set for the specified player. Returns NULL if the no nickname has been set for that player. -const char *GetPlayerNickname( CSteamID steamIDPlayer ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return NULL; -} - - -// friend grouping (tag) apis -// returns the number of friends groups -int GetFriendsGroupCount() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -// returns the friends group ID for the given index (invalid indices return k_FriendsGroupID_Invalid) -FriendsGroupID_t GetFriendsGroupIDByIndex( int iFG ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_FriendsGroupID_Invalid; -} - -// returns the name for the given friends group (NULL in the case of invalid friends group IDs) -const char *GetFriendsGroupName( FriendsGroupID_t friendsGroupID ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return NULL; -} - -// returns the number of members in a given friends group -int GetFriendsGroupMembersCount( FriendsGroupID_t friendsGroupID ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -// gets up to nMembersCount members of the given friends group, if fewer exist than requested those positions' SteamIDs will be invalid -void GetFriendsGroupMembersList( FriendsGroupID_t friendsGroupID, STEAM_OUT_ARRAY_CALL(nMembersCount, GetFriendsGroupMembersCount, friendsGroupID ) CSteamID *pOutSteamIDMembers, int nMembersCount ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - - -// returns true if the specified user meets any of the criteria specified in iFriendFlags -// iFriendFlags can be the union (binary or, |) of one or more k_EFriendFlags values -bool HasFriend( CSteamID steamIDFriend, int iFriendFlags ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - bool ret = false; - if (ok_friend_flags(iFriendFlags)) if (find_friend(steamIDFriend)) ret = true; - - return ret; -} - -bool HasFriend( CSteamID steamIDFriend, EFriendFlags eFriendFlags ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - return HasFriend(steamIDFriend, (int)eFriendFlags ); -} - -// clan (group) iteration and access functions -int GetClanCount() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - int counter = 0; - for (auto &c : settings->subscribed_groups_clans) counter++; - return counter; -} - -CSteamID GetClanByIndex( int iClan ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - int counter = 0; - for (auto &c : settings->subscribed_groups_clans) { - if (counter == iClan) return c.id; - counter++; - } - return k_steamIDNil; -} - -const char *GetClanName( CSteamID steamIDClan ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - for (auto &c : settings->subscribed_groups_clans) { - if (c.id.ConvertToUint64() == steamIDClan.ConvertToUint64()) return c.name.c_str(); - } - return ""; -} - -const char *GetClanTag( CSteamID steamIDClan ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - for (auto &c : settings->subscribed_groups_clans) { - if (c.id.ConvertToUint64() == steamIDClan.ConvertToUint64()) return c.tag.c_str(); - } - return ""; -} - -// returns the most recent information we have about what's happening in a clan -bool GetClanActivityCounts( CSteamID steamIDClan, int *pnOnline, int *pnInGame, int *pnChatting ) -{ - PRINT_DEBUG("TODO %llu", steamIDClan.ConvertToUint64()); - std::lock_guard lock(global_mutex); - return false; -} - -// for clans a user is a member of, they will have reasonably up-to-date information, but for others you'll have to download the info to have the latest -SteamAPICall_t DownloadClanActivityCounts( STEAM_ARRAY_COUNT(cClansToRequest) CSteamID *psteamIDClans, int cClansToRequest ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - - -// iterators for getting users in a chat room, lobby, game server or clan -// note that large clans that cannot be iterated by the local user -// note that the current user must be in a lobby to retrieve CSteamIDs of other users in that lobby -// steamIDSource can be the steamID of a group, game server, lobby or chat room -int GetFriendCountFromSource( CSteamID steamIDSource ) -{ - PRINT_DEBUG("TODO %llu", steamIDSource.ConvertToUint64()); - std::lock_guard lock(global_mutex); - //TODO - return 0; -} - -CSteamID GetFriendFromSourceByIndex( CSteamID steamIDSource, int iFriend ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_steamIDNil; -} - - -// returns true if the local user can see that steamIDUser is a member or in steamIDSource -bool IsUserInSource( CSteamID steamIDUser, CSteamID steamIDSource ) -{ - PRINT_DEBUG("%llu %llu", steamIDUser.ConvertToUint64(), steamIDSource.ConvertToUint64()); - std::lock_guard lock(global_mutex); - if (steamIDUser == settings->get_local_steam_id()) { - if (settings->get_lobby() == steamIDSource) { - return true; - } - - if (settings->subscribed_groups.find(steamIDSource.ConvertToUint64()) != settings->subscribed_groups.end()) { - return true; - } - } else { - Friend *f = find_friend(steamIDUser); - if (!f) return false; - if (f->lobby_id() == steamIDSource.ConvertToUint64()) return true; - } - //TODO - return false; -} - - -// User is in a game pressing the talk button (will suppress the microphone for all voice comms from the Steam friends UI) -void SetInGameVoiceSpeaking( CSteamID steamIDUser, bool bSpeaking ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - - -// activates the game overlay, with an optional dialog to open -// valid options are "Friends", "Community", "Players", "Settings", "OfficialGameGroup", "Stats", "Achievements" -void ActivateGameOverlay( const char *pchDialog ) -{ - PRINT_DEBUG("%s", pchDialog); - std::lock_guard lock(global_mutex); - overlay->OpenOverlay(pchDialog); -} - - -// activates game overlay to a specific place -// valid options are -// "steamid" - opens the overlay web browser to the specified user or groups profile -// "chat" - opens a chat window to the specified user, or joins the group chat -// "jointrade" - opens a window to a Steam Trading session that was started with the ISteamEconomy/StartTrade Web API -// "stats" - opens the overlay web browser to the specified user's stats -// "achievements" - opens the overlay web browser to the specified user's achievements -// "friendadd" - opens the overlay in minimal mode prompting the user to add the target user as a friend -// "friendremove" - opens the overlay in minimal mode prompting the user to remove the target friend -// "friendrequestaccept" - opens the overlay in minimal mode prompting the user to accept an incoming friend invite -// "friendrequestignore" - opens the overlay in minimal mode prompting the user to ignore an incoming friend invite -void ActivateGameOverlayToUser( const char *pchDialog, CSteamID steamID ) -{ - PRINT_DEBUG("TODO %s %llu", pchDialog, steamID.ConvertToUint64()); - std::lock_guard lock(global_mutex); -} - - -// activates game overlay web browser directly to the specified URL -// full address with protocol type is required, e.g. http://www.steamgames.com/ -void ActivateGameOverlayToWebPage( const char *pchURL, EActivateGameOverlayToWebPageMode eMode = k_EActivateGameOverlayToWebPageMode_Default ) -{ - PRINT_DEBUG("TODO %s %u", pchURL, eMode); - std::lock_guard lock(global_mutex); - overlay->OpenOverlayWebpage(pchURL); -} - -void ActivateGameOverlayToWebPage( const char *pchURL ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - ActivateGameOverlayToWebPage( pchURL, k_EActivateGameOverlayToWebPageMode_Default ); -} - -// activates game overlay to store page for app -void ActivateGameOverlayToStore( AppId_t nAppID, EOverlayToStoreFlag eFlag ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - -void ActivateGameOverlayToStore( AppId_t nAppID) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); -} - -// Mark a target user as 'played with'. This is a client-side only feature that requires that the calling user is -// in game -void SetPlayedWith( CSteamID steamIDUserPlayedWith ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - - -// activates game overlay to open the invite dialog. Invitations will be sent for the provided lobby. -void ActivateGameOverlayInviteDialog( CSteamID steamIDLobby ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - overlay->OpenOverlayInvite(steamIDLobby); -} - -// gets the small (32x32) avatar of the current user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if none set -int GetSmallFriendAvatar( CSteamID steamIDFriend ) -{ - PRINT_DEBUG_ENTRY(); - //IMPORTANT NOTE: don't change friend avatar numbers for the same friend or else some games endlessly allocate stuff. - std::lock_guard lock(global_mutex); - struct Avatar_Numbers numbers = add_friend_avatars(steamIDFriend); - return numbers.smallest; -} - - -// gets the medium (64x64) avatar of the current user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if none set -int GetMediumFriendAvatar( CSteamID steamIDFriend ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct Avatar_Numbers numbers = add_friend_avatars(steamIDFriend); - return numbers.medium; -} - - -// gets the large (184x184) avatar of the current user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if none set -// returns -1 if this image has yet to be loaded, in this case wait for a AvatarImageLoaded_t callback and then call this again -int GetLargeFriendAvatar( CSteamID steamIDFriend ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct Avatar_Numbers numbers = add_friend_avatars(steamIDFriend); - return numbers.large; -} - -int GetFriendAvatar( CSteamID steamIDFriend, int eAvatarSize ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (eAvatarSize == k_EAvatarSize32x32) { - return GetSmallFriendAvatar(steamIDFriend); - } else if (eAvatarSize == k_EAvatarSize64x64) { - return GetMediumFriendAvatar(steamIDFriend); - } else if (eAvatarSize == k_EAvatarSize184x184) { - return GetLargeFriendAvatar(steamIDFriend); - } else { - return 0; - } -} - -int GetFriendAvatar(CSteamID steamIDFriend) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - return GetFriendAvatar(steamIDFriend, k_EAvatarSize32x32); -} - -// requests information about a user - persona name & avatar -// if bRequireNameOnly is set, then the avatar of a user isn't downloaded -// - it's a lot slower to download avatars and churns the local cache, so if you don't need avatars, don't request them -// if returns true, it means that data is being requested, and a PersonaStateChanged_t callback will be posted when it's retrieved -// if returns false, it means that we already have all the details about that user, and functions can be called immediately -bool RequestUserInformation( CSteamID steamIDUser, bool bRequireNameOnly ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - //persona_change(steamIDUser, k_EPersonaChangeName); - //We already know everything - return false; -} - - -// requests information about a clan officer list -// when complete, data is returned in ClanOfficerListResponse_t call result -// this makes available the calls below -// you can only ask about clans that a user is a member of -// note that this won't download avatars automatically; if you get an officer, -// and no avatar image is available, call RequestUserInformation( steamID, false ) to download the avatar -STEAM_CALL_RESULT( ClanOfficerListResponse_t ) -SteamAPICall_t RequestClanOfficerList( CSteamID steamIDClan ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - - -// iteration of clan officers - can only be done when a RequestClanOfficerList() call has completed - -// returns the steamID of the clan owner -CSteamID GetClanOwner( CSteamID steamIDClan ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_steamIDNil; -} - -// returns the number of officers in a clan (including the owner) -int GetClanOfficerCount( CSteamID steamIDClan ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -// returns the steamID of a clan officer, by index, of range [0,GetClanOfficerCount) -CSteamID GetClanOfficerByIndex( CSteamID steamIDClan, int iOfficer ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_steamIDNil; -} - -// if current user is chat restricted, he can't send or receive any text/voice chat messages. -// the user can't see custom avatars. But the user can be online and send/recv game invites. -// a chat restricted user can't add friends or join any groups. -uint32 GetUserRestrictions() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_nUserRestrictionNone; -} - -EUserRestriction GetUserRestrictions_old() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_nUserRestrictionNone; -} - -// Rich Presence data is automatically shared between friends who are in the same game -// Each user has a set of Key/Value pairs -// Note the following limits: k_cchMaxRichPresenceKeys, k_cchMaxRichPresenceKeyLength, k_cchMaxRichPresenceValueLength -// There are two magic keys: -// "status" - a UTF-8 string that will show up in the 'view game info' dialog in the Steam friends list -// "connect" - a UTF-8 string that contains the command-line for how a friend can connect to a game -// GetFriendRichPresence() returns an empty string "" if no value is set -// SetRichPresence() to a NULL or an empty string deletes the key -// You can iterate the current set of keys for a friend with GetFriendRichPresenceKeyCount() -// and GetFriendRichPresenceKeyByIndex() (typically only used for debugging) -bool SetRichPresence( const char *pchKey, const char *pchValue ) -{ - PRINT_DEBUG("%s %s", pchKey, pchValue ? pchValue : "NULL"); - std::lock_guard lock(global_mutex); - if (pchValue) { - auto prev_value = (*us.mutable_rich_presence()).find(pchKey); - if (prev_value == (*us.mutable_rich_presence()).end() || prev_value->second != pchValue) { - (*us.mutable_rich_presence())[pchKey] = pchValue; - resend_friend_data(); - } - } else { - auto to_remove = us.mutable_rich_presence()->find(pchKey); - if (to_remove != us.mutable_rich_presence()->end()) { - us.mutable_rich_presence()->erase(to_remove); - resend_friend_data(); - } - } - - return true; -} - -void ClearRichPresence() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - us.mutable_rich_presence()->clear(); - resend_friend_data(); - -} - -const char *GetFriendRichPresence( CSteamID steamIDFriend, const char *pchKey ) -{ - PRINT_DEBUG("%llu '%s'", steamIDFriend.ConvertToUint64(), pchKey); - std::lock_guard lock(global_mutex); - const char *value = ""; - - Friend *f = NULL; - if (settings->get_local_steam_id() == steamIDFriend) { - f = &us; - } else { - f = find_friend(steamIDFriend); - } - - if (f && isAppIdCompatible(f)) { - auto result = f->rich_presence().find(pchKey); - if (result != f->rich_presence().end()) value = result->second.c_str(); - } - - PRINT_DEBUG("returned '%s'", value); - return value; -} - -int GetFriendRichPresenceKeyCount( CSteamID steamIDFriend ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - int num = 0; - - Friend *f = NULL; - if (settings->get_local_steam_id() == steamIDFriend) { - f = &us; - } else { - f = find_friend(steamIDFriend); - } - - if (f && isAppIdCompatible(f)) num = f->rich_presence().size(); - - return num; -} - -const char *GetFriendRichPresenceKeyByIndex( CSteamID steamIDFriend, int iKey ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - const char *key = ""; - - Friend *f = NULL; - if (settings->get_local_steam_id() == steamIDFriend) { - f = &us; - } else { - f = find_friend(steamIDFriend); - } - - if (f && isAppIdCompatible(f) && f->rich_presence().size() > iKey && iKey >= 0) { - auto friend_data = f->rich_presence().begin(); - for (int i = 0; i < iKey; ++i) ++friend_data; - key = friend_data->first.c_str(); - } - - - return key; -} - -// Requests rich presence for a specific user. -void RequestFriendRichPresence( CSteamID steamIDFriend ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - Friend *f = find_friend(steamIDFriend); - if (f) rich_presence_updated(steamIDFriend, settings->get_local_game_id().AppID()); - -} - - -// rich invite support -// if the target accepts the invite, the pchConnectString gets added to the command-line for launching the game -// if the game is already running, a GameRichPresenceJoinRequested_t callback is posted containing the connect string -// invites can only be sent to friends -bool InviteUserToGame( CSteamID steamIDFriend, const char *pchConnectString ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - Friend *f = find_friend(steamIDFriend); - if (!f) return false; - - Common_Message msg; - Friend_Messages *friend_messages = new Friend_Messages(); - friend_messages->set_type(Friend_Messages::GAME_INVITE); - friend_messages->set_connect_str(pchConnectString); - msg.set_allocated_friend_messages(friend_messages); - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_dest_id(steamIDFriend.ConvertToUint64()); - return network->sendTo(&msg, true); -} - - -// recently-played-with friends iteration -// this iterates the entire list of users recently played with, across games -// GetFriendCoplayTime() returns as a unix time -int GetCoplayFriendCount() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -CSteamID GetCoplayFriend( int iCoplayFriend ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_steamIDNil; -} - -int GetFriendCoplayTime( CSteamID steamIDFriend ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -AppId_t GetFriendCoplayGame( CSteamID steamIDFriend ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - - -// chat interface for games -// this allows in-game access to group (clan) chats from in the game -// the behavior is somewhat sophisticated, because the user may or may not be already in the group chat from outside the game or in the overlay -// use ActivateGameOverlayToUser( "chat", steamIDClan ) to open the in-game overlay version of the chat -STEAM_CALL_RESULT( JoinClanChatRoomCompletionResult_t ) -SteamAPICall_t JoinClanChatRoom( CSteamID steamIDClan ) -{ - PRINT_DEBUG("TODO %llu", steamIDClan.ConvertToUint64()); - //TODO actually join a room - std::lock_guard lock(global_mutex); - JoinClanChatRoomCompletionResult_t data; - data.m_steamIDClanChat = steamIDClan; - data.m_eChatRoomEnterResponse = k_EChatRoomEnterResponseSuccess; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -bool LeaveClanChatRoom( CSteamID steamIDClan ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -int GetClanChatMemberCount( CSteamID steamIDClan ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -CSteamID GetChatMemberByIndex( CSteamID steamIDClan, int iUser ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_steamIDNil; -} - -bool SendClanChatMessage( CSteamID steamIDClanChat, const char *pchText ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -int GetClanChatMessage( CSteamID steamIDClanChat, int iMessage, void *prgchText, int cchTextMax, EChatEntryType *peChatEntryType, STEAM_OUT_STRUCT() CSteamID *psteamidChatter ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -bool IsClanChatAdmin( CSteamID steamIDClanChat, CSteamID steamIDUser ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - - -// interact with the Steam (game overlay / desktop) -bool IsClanChatWindowOpenInSteam( CSteamID steamIDClanChat ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -bool OpenClanChatWindowInSteam( CSteamID steamIDClanChat ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return true; -} - -bool CloseClanChatWindowInSteam( CSteamID steamIDClanChat ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return true; -} - - -// peer-to-peer chat interception -// this is so you can show P2P chats inline in the game -bool SetListenForFriendsMessages( bool bInterceptEnabled ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return true; -} - -bool ReplyToFriendMessage( CSteamID steamIDFriend, const char *pchMsgToSend ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -int GetFriendMessage( CSteamID steamIDFriend, int iMessageID, void *pvData, int cubData, EChatEntryType *peChatEntryType ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - - -// following apis -STEAM_CALL_RESULT( FriendsGetFollowerCount_t ) -SteamAPICall_t GetFollowerCount( CSteamID steamID ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -STEAM_CALL_RESULT( FriendsIsFollowing_t ) -SteamAPICall_t IsFollowing( CSteamID steamID ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -STEAM_CALL_RESULT( FriendsEnumerateFollowingList_t ) -SteamAPICall_t EnumerateFollowingList( uint32 unStartIndex ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - - -bool IsClanPublic( CSteamID steamIDClan ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -bool IsClanOfficialGameGroup( CSteamID steamIDClan ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -int GetNumChatsWithUnreadPriorityMessages() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -void ActivateGameOverlayRemotePlayTogetherInviteDialog( CSteamID steamIDLobby ) -{ - PRINT_DEBUG_ENTRY(); -} - -// Call this before calling ActivateGameOverlayToWebPage() to have the Steam Overlay Browser block navigations -// to your specified protocol (scheme) uris and instead dispatch a OverlayBrowserProtocolNavigation_t callback to your game. -// ActivateGameOverlayToWebPage() must have been called with k_EActivateGameOverlayToWebPageMode_Modal -bool RegisterProtocolInOverlayBrowser( const char *pchProtocol ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -// Activates the game overlay to open an invite dialog that will send the provided Rich Presence connect string to selected friends -void ActivateGameOverlayInviteDialogConnectString( const char *pchConnectString ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - -// Steam Community items equipped by a user on their profile -// You can register for EquippedProfileItemsChanged_t to know when a friend has changed their equipped profile items -STEAM_CALL_RESULT( EquippedProfileItems_t ) -SteamAPICall_t RequestEquippedProfileItems( CSteamID steamID ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -bool BHasEquippedProfileItem( CSteamID steamID, ECommunityProfileItemType itemType ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -const char *GetProfileItemPropertyString( CSteamID steamID, ECommunityProfileItemType itemType, ECommunityProfileItemProperty prop ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return ""; -} - -uint32 GetProfileItemPropertyUint( CSteamID steamID, ECommunityProfileItemType itemType, ECommunityProfileItemProperty prop ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -void RunCallbacks() -{ - // PRINT_DEBUG_ENTRY(); - if (settings->get_lobby() != lobby_id) { - lobby_id = settings->get_lobby(); - resend_friend_data(); - } - - if (modified) { - PRINT_DEBUG("sending modified data"); - Common_Message msg; - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - Friend *f = new Friend(us); - f->set_id(settings->get_local_steam_id().ConvertToUint64()); - f->set_name(settings->get_local_name()); - f->set_appid(settings->get_local_game_id().AppID()); - f->set_lobby_id(settings->get_lobby().ConvertToUint64()); - msg.set_allocated_friend_(f); - network->sendToAllIndividuals(&msg, true); - modified = false; - last_sent_friends = std::chrono::high_resolution_clock::now(); - } -} - -void Callback(Common_Message *msg) -{ - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::DISCONNECT) { - PRINT_DEBUG("Disconnect"); - uint64 id = msg->source_id(); - auto f = std::find_if(friends.begin(), friends.end(), [&id](Friend const& item) { return item.id() == id; }); - if (friends.end() != f) { - persona_change((uint64)f->id(), k_EPersonaChangeStatus); - overlay->FriendDisconnect(*f); - friends.erase(f); - } - } - - if (msg->low_level().type() == Low_Level::CONNECT) { - PRINT_DEBUG("Connect %llu", (uint64)msg->source_id()); - Common_Message msg_; - msg_.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg_.set_dest_id(msg->source_id()); - Friend *f = new Friend(us); - f->set_id(settings->get_local_steam_id().ConvertToUint64()); - f->set_name(settings->get_local_name()); - f->set_appid(settings->get_local_game_id().AppID()); - f->set_lobby_id(settings->get_lobby().ConvertToUint64()); - int avatar_number = GetLargeFriendAvatar(settings->get_local_steam_id()); - if (settings->images[avatar_number].data.length() > 0) f->set_avatar(settings->images[avatar_number].data); - else f->set_avatar(""); - msg_.set_allocated_friend_(f); - network->sendTo(&msg_, true); - } - } - - if (msg->has_friend_()) { - PRINT_DEBUG("Friend " "%" PRIu64 " " "%" PRIu64 "", msg->friend_().id(), msg->friend_().lobby_id()); - Friend *f = find_friend((uint64)msg->friend_().id()); - if (!f) { - if (msg->friend_().id() != settings->get_local_steam_id().ConvertToUint64()) { - friends.push_back(msg->friend_()); - overlay->FriendConnect(msg->friend_()); - persona_change((uint64)msg->friend_().id(), k_EPersonaChangeName); - GetLargeFriendAvatar((uint64)msg->friend_().id()); - } - } else { - std::map map1(f->rich_presence().begin(), f->rich_presence().end()); - std::map map2(msg->friend_().rich_presence().begin(), msg->friend_().rich_presence().end()); - - if (map1 != map2) { - //The App ID of the game. This should always be the current game. - if (isAppIdCompatible(f)) { - rich_presence_updated((uint64)msg->friend_().id(), (uint64)msg->friend_().appid()); - } - } - //TODO: callbacks? - *f = msg->friend_(); - } - } - - if (msg->has_friend_messages()) { - if (msg->friend_messages().type() == Friend_Messages::LOBBY_INVITE) { - PRINT_DEBUG("Got Lobby Invite"); - Friend *f = find_friend((uint64)msg->source_id()); - if (f) { - LobbyInvite_t data; - data.m_ulSteamIDUser = msg->source_id(); - data.m_ulSteamIDLobby = msg->friend_messages().lobby_id(); - data.m_ulGameID = f->appid(); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - - if (overlay->Ready() && !settings->hasOverlayAutoAcceptInviteFromFriend(msg->source_id())) - { - //TODO: the user should accept the invite first but we auto accept it because there's no gui yet - // Then we will handle it ! - overlay->SetLobbyInvite(*find_friend(static_cast(msg->source_id())), msg->friend_messages().lobby_id()); - } - else - { - GameLobbyJoinRequested_t data; - data.m_steamIDLobby = CSteamID((uint64)msg->friend_messages().lobby_id()); - data.m_steamIDFriend = CSteamID((uint64)msg->source_id()); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - } - } - - if (msg->friend_messages().type() == Friend_Messages::GAME_INVITE) { - PRINT_DEBUG("Got Game Invite"); - //TODO: I'm pretty sure that the user should accept the invite before this is posted but we do like above - if (overlay->Ready() && !settings->hasOverlayAutoAcceptInviteFromFriend(msg->source_id())) - { - // Then we will handle it ! - overlay->SetRichInvite(*find_friend(static_cast(msg->source_id())), msg->friend_messages().connect_str().c_str()); - } - else - { - std::string const& connect_str = msg->friend_messages().connect_str(); - GameRichPresenceJoinRequested_t data = {}; - data.m_steamIDFriend = CSteamID((uint64)msg->source_id()); - strncpy(data.m_rgchConnect, connect_str.c_str(), k_cchMaxRichPresenceValueLength - 1); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - } - } -} + Steam_Friends(Settings* settings, class Local_Storage* local_storage, Networking* network, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Steam_Overlay* overlay); + ~Steam_Friends(); + + void resend_friend_data(); + + // returns the local players name - guaranteed to not be NULL. + // this is the same name as on the users community profile page + // this is stored in UTF-8 format + // like all the other interface functions that return a char *, it's important that this pointer is not saved + // off; it will eventually be free'd or re-allocated + const char *GetPersonaName(); + + // Sets the player name, stores it on the server and publishes the changes to all friends who are online. + // Changes take place locally immediately, and a PersonaStateChange_t is posted, presuming success. + // + // The final results are available through the return value SteamAPICall_t, using SetPersonaNameResponse_t. + // + // If the name change fails to happen on the server, then an additional global PersonaStateChange_t will be posted + // to change the name back, in addition to the SetPersonaNameResponse_t callback. + STEAM_CALL_RESULT( SetPersonaNameResponse_t ) + SteamAPICall_t SetPersonaName( const char *pchPersonaName ); + + void SetPersonaName_old( const char *pchPersonaName ); + + // gets the status of the current user + EPersonaState GetPersonaState(); + + + // friend iteration + // takes a set of k_EFriendFlags, and returns the number of users the client knows about who meet that criteria + // then GetFriendByIndex() can then be used to return the id's of each of those users + int GetFriendCount( int iFriendFlags ); + + int GetFriendCount( EFriendFlags eFriendFlags ); + + // returns the steamID of a user + // iFriend is a index of range [0, GetFriendCount()) + // iFriendsFlags must be the same value as used in GetFriendCount() + // the returned CSteamID can then be used by all the functions below to access details about the user + CSteamID GetFriendByIndex( int iFriend, int iFriendFlags ); + + CSteamID GetFriendByIndex( int iFriend, EFriendFlags eFriendFlags ); + + // returns a relationship to a user + EFriendRelationship GetFriendRelationship( CSteamID steamIDFriend ); + + + // returns the current status of the specified user + // this will only be known by the local user if steamIDFriend is in their friends list; on the same game server; in a chat room or lobby; or in a small group with the local user + EPersonaState GetFriendPersonaState( CSteamID steamIDFriend ); + + + // returns the name another user - guaranteed to not be NULL. + // same rules as GetFriendPersonaState() apply as to whether or not the user knowns the name of the other user + // note that on first joining a lobby, chat room or game server the local user will not known the name of the other users automatically; that information will arrive asyncronously + // + const char *GetFriendPersonaName( CSteamID steamIDFriend ); + + + // returns true if the friend is actually in a game, and fills in pFriendGameInfo with an extra details + bool GetFriendGamePlayed( CSteamID steamIDFriend, STEAM_OUT_STRUCT() FriendGameInfo_t *pFriendGameInfo ); + + bool GetFriendGamePlayed( CSteamID steamIDFriend, uint64 *pulGameID, uint32 *punGameIP, uint16 *pusGamePort, uint16 *pusQueryPort ); + + // accesses old friends names - returns an empty string when their are no more items in the history + const char *GetFriendPersonaNameHistory( CSteamID steamIDFriend, int iPersonaName ); + + // friends steam level + int GetFriendSteamLevel( CSteamID steamIDFriend ); + + + // Returns nickname the current user has set for the specified player. Returns NULL if the no nickname has been set for that player. + const char *GetPlayerNickname( CSteamID steamIDPlayer ); + + + // friend grouping (tag) apis + // returns the number of friends groups + int GetFriendsGroupCount(); + + // returns the friends group ID for the given index (invalid indices return k_FriendsGroupID_Invalid) + FriendsGroupID_t GetFriendsGroupIDByIndex( int iFG ); + + // returns the name for the given friends group (NULL in the case of invalid friends group IDs) + const char *GetFriendsGroupName( FriendsGroupID_t friendsGroupID ); + + // returns the number of members in a given friends group + int GetFriendsGroupMembersCount( FriendsGroupID_t friendsGroupID ); + + // gets up to nMembersCount members of the given friends group, if fewer exist than requested those positions' SteamIDs will be invalid + void GetFriendsGroupMembersList( FriendsGroupID_t friendsGroupID, STEAM_OUT_ARRAY_CALL(nMembersCount, GetFriendsGroupMembersCount, friendsGroupID ) CSteamID *pOutSteamIDMembers, int nMembersCount ); + + + // returns true if the specified user meets any of the criteria specified in iFriendFlags + // iFriendFlags can be the union (binary or, |) of one or more k_EFriendFlags values + bool HasFriend( CSteamID steamIDFriend, int iFriendFlags ); + + bool HasFriend( CSteamID steamIDFriend, EFriendFlags eFriendFlags ); + + // clan (group) iteration and access functions + int GetClanCount(); + + CSteamID GetClanByIndex( int iClan ); + + const char *GetClanName( CSteamID steamIDClan ); + + const char *GetClanTag( CSteamID steamIDClan ); + + // returns the most recent information we have about what's happening in a clan + bool GetClanActivityCounts( CSteamID steamIDClan, int *pnOnline, int *pnInGame, int *pnChatting ); + + // for clans a user is a member of, they will have reasonably up-to-date information, but for others you'll have to download the info to have the latest + SteamAPICall_t DownloadClanActivityCounts( STEAM_ARRAY_COUNT(cClansToRequest) CSteamID *psteamIDClans, int cClansToRequest ); + + + // iterators for getting users in a chat room, lobby, game server or clan + // note that large clans that cannot be iterated by the local user + // note that the current user must be in a lobby to retrieve CSteamIDs of other users in that lobby + // steamIDSource can be the steamID of a group, game server, lobby or chat room + int GetFriendCountFromSource( CSteamID steamIDSource ); + + CSteamID GetFriendFromSourceByIndex( CSteamID steamIDSource, int iFriend ); + + + // returns true if the local user can see that steamIDUser is a member or in steamIDSource + bool IsUserInSource( CSteamID steamIDUser, CSteamID steamIDSource ); + + + // User is in a game pressing the talk button (will suppress the microphone for all voice comms from the Steam friends UI) + void SetInGameVoiceSpeaking( CSteamID steamIDUser, bool bSpeaking ); + + + // activates the game overlay, with an optional dialog to open + // valid options are "Friends", "Community", "Players", "Settings", "OfficialGameGroup", "Stats", "Achievements" + void ActivateGameOverlay( const char *pchDialog ); + + + // activates game overlay to a specific place + // valid options are + // "steamid" - opens the overlay web browser to the specified user or groups profile + // "chat" - opens a chat window to the specified user, or joins the group chat + // "jointrade" - opens a window to a Steam Trading session that was started with the ISteamEconomy/StartTrade Web API + // "stats" - opens the overlay web browser to the specified user's stats + // "achievements" - opens the overlay web browser to the specified user's achievements + // "friendadd" - opens the overlay in minimal mode prompting the user to add the target user as a friend + // "friendremove" - opens the overlay in minimal mode prompting the user to remove the target friend + // "friendrequestaccept" - opens the overlay in minimal mode prompting the user to accept an incoming friend invite + // "friendrequestignore" - opens the overlay in minimal mode prompting the user to ignore an incoming friend invite + void ActivateGameOverlayToUser( const char *pchDialog, CSteamID steamID ); + + + // activates game overlay web browser directly to the specified URL + // full address with protocol type is required, e.g. http://www.steamgames.com/ + void ActivateGameOverlayToWebPage( const char *pchURL, EActivateGameOverlayToWebPageMode eMode = k_EActivateGameOverlayToWebPageMode_Default ); + + void ActivateGameOverlayToWebPage( const char *pchURL ); + + // activates game overlay to store page for app + void ActivateGameOverlayToStore( AppId_t nAppID, EOverlayToStoreFlag eFlag ); + + void ActivateGameOverlayToStore( AppId_t nAppID); + + // Mark a target user as 'played with'. This is a client-side only feature that requires that the calling user is + // in game + void SetPlayedWith( CSteamID steamIDUserPlayedWith ); + + + // activates game overlay to open the invite dialog. Invitations will be sent for the provided lobby. + void ActivateGameOverlayInviteDialog( CSteamID steamIDLobby ); + + // gets the small (32x32) avatar of the current user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if none set + int GetSmallFriendAvatar( CSteamID steamIDFriend ); + + + // gets the medium (64x64) avatar of the current user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if none set + int GetMediumFriendAvatar( CSteamID steamIDFriend ); + + + // gets the large (184x184) avatar of the current user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if none set + // returns -1 if this image has yet to be loaded, in this case wait for a AvatarImageLoaded_t callback and then call this again + int GetLargeFriendAvatar( CSteamID steamIDFriend ); + + int GetFriendAvatar( CSteamID steamIDFriend, int eAvatarSize ); + + int GetFriendAvatar(CSteamID steamIDFriend); + + // requests information about a user - persona name & avatar + // if bRequireNameOnly is set, then the avatar of a user isn't downloaded + // - it's a lot slower to download avatars and churns the local cache, so if you don't need avatars, don't request them + // if returns true, it means that data is being requested, and a PersonaStateChanged_t callback will be posted when it's retrieved + // if returns false, it means that we already have all the details about that user, and functions can be called immediately + bool RequestUserInformation( CSteamID steamIDUser, bool bRequireNameOnly ); + + + // requests information about a clan officer list + // when complete, data is returned in ClanOfficerListResponse_t call result + // this makes available the calls below + // you can only ask about clans that a user is a member of + // note that this won't download avatars automatically; if you get an officer, + // and no avatar image is available, call RequestUserInformation( steamID, false ) to download the avatar + STEAM_CALL_RESULT( ClanOfficerListResponse_t ) + SteamAPICall_t RequestClanOfficerList( CSteamID steamIDClan ); + + + // iteration of clan officers - can only be done when a RequestClanOfficerList() call has completed + + // returns the steamID of the clan owner + CSteamID GetClanOwner( CSteamID steamIDClan ); + + // returns the number of officers in a clan (including the owner) + int GetClanOfficerCount( CSteamID steamIDClan ); + + // returns the steamID of a clan officer, by index, of range [0,GetClanOfficerCount) + CSteamID GetClanOfficerByIndex( CSteamID steamIDClan, int iOfficer ); + + // if current user is chat restricted, he can't send or receive any text/voice chat messages. + // the user can't see custom avatars. But the user can be online and send/recv game invites. + // a chat restricted user can't add friends or join any groups. + uint32 GetUserRestrictions(); + + EUserRestriction GetUserRestrictions_old(); + + // Rich Presence data is automatically shared between friends who are in the same game + // Each user has a set of Key/Value pairs + // Note the following limits: k_cchMaxRichPresenceKeys, k_cchMaxRichPresenceKeyLength, k_cchMaxRichPresenceValueLength + // There are two magic keys: + // "status" - a UTF-8 string that will show up in the 'view game info' dialog in the Steam friends list + // "connect" - a UTF-8 string that contains the command-line for how a friend can connect to a game + // GetFriendRichPresence() returns an empty string "" if no value is set + // SetRichPresence() to a NULL or an empty string deletes the key + // You can iterate the current set of keys for a friend with GetFriendRichPresenceKeyCount() + // and GetFriendRichPresenceKeyByIndex() (typically only used for debugging) + bool SetRichPresence( const char *pchKey, const char *pchValue ); + + void ClearRichPresence(); + + const char *GetFriendRichPresence( CSteamID steamIDFriend, const char *pchKey ); + + int GetFriendRichPresenceKeyCount( CSteamID steamIDFriend ); + + const char *GetFriendRichPresenceKeyByIndex( CSteamID steamIDFriend, int iKey ); + + // Requests rich presence for a specific user. + void RequestFriendRichPresence( CSteamID steamIDFriend ); + + + // rich invite support + // if the target accepts the invite, the pchConnectString gets added to the command-line for launching the game + // if the game is already running, a GameRichPresenceJoinRequested_t callback is posted containing the connect string + // invites can only be sent to friends + bool InviteUserToGame( CSteamID steamIDFriend, const char *pchConnectString ); + + + // recently-played-with friends iteration + // this iterates the entire list of users recently played with, across games + // GetFriendCoplayTime() returns as a unix time + int GetCoplayFriendCount(); + + CSteamID GetCoplayFriend( int iCoplayFriend ); + + int GetFriendCoplayTime( CSteamID steamIDFriend ); + + AppId_t GetFriendCoplayGame( CSteamID steamIDFriend ); + + + // chat interface for games + // this allows in-game access to group (clan) chats from in the game + // the behavior is somewhat sophisticated, because the user may or may not be already in the group chat from outside the game or in the overlay + // use ActivateGameOverlayToUser( "chat", steamIDClan ) to open the in-game overlay version of the chat + STEAM_CALL_RESULT( JoinClanChatRoomCompletionResult_t ) + SteamAPICall_t JoinClanChatRoom( CSteamID steamIDClan ); + + bool LeaveClanChatRoom( CSteamID steamIDClan ); + + int GetClanChatMemberCount( CSteamID steamIDClan ); + + CSteamID GetChatMemberByIndex( CSteamID steamIDClan, int iUser ); + + bool SendClanChatMessage( CSteamID steamIDClanChat, const char *pchText ); + + int GetClanChatMessage( CSteamID steamIDClanChat, int iMessage, void *prgchText, int cchTextMax, EChatEntryType *peChatEntryType, STEAM_OUT_STRUCT() CSteamID *psteamidChatter ); + + bool IsClanChatAdmin( CSteamID steamIDClanChat, CSteamID steamIDUser ); + + + // interact with the Steam (game overlay / desktop) + bool IsClanChatWindowOpenInSteam( CSteamID steamIDClanChat ); + + bool OpenClanChatWindowInSteam( CSteamID steamIDClanChat ); + + bool CloseClanChatWindowInSteam( CSteamID steamIDClanChat ); + + + // peer-to-peer chat interception + // this is so you can show P2P chats inline in the game + bool SetListenForFriendsMessages( bool bInterceptEnabled ); + + bool ReplyToFriendMessage( CSteamID steamIDFriend, const char *pchMsgToSend ); + + int GetFriendMessage( CSteamID steamIDFriend, int iMessageID, void *pvData, int cubData, EChatEntryType *peChatEntryType ); + + + // following apis + STEAM_CALL_RESULT( FriendsGetFollowerCount_t ) + SteamAPICall_t GetFollowerCount( CSteamID steamID ); + + STEAM_CALL_RESULT( FriendsIsFollowing_t ) + SteamAPICall_t IsFollowing( CSteamID steamID ); + + STEAM_CALL_RESULT( FriendsEnumerateFollowingList_t ) + SteamAPICall_t EnumerateFollowingList( uint32 unStartIndex ); + + + bool IsClanPublic( CSteamID steamIDClan ); + + bool IsClanOfficialGameGroup( CSteamID steamIDClan ); + + int GetNumChatsWithUnreadPriorityMessages(); + + void ActivateGameOverlayRemotePlayTogetherInviteDialog( CSteamID steamIDLobby ); + + // Call this before calling ActivateGameOverlayToWebPage() to have the Steam Overlay Browser block navigations + // to your specified protocol (scheme) uris and instead dispatch a OverlayBrowserProtocolNavigation_t callback to your game. + // ActivateGameOverlayToWebPage() must have been called with k_EActivateGameOverlayToWebPageMode_Modal + bool RegisterProtocolInOverlayBrowser( const char *pchProtocol ); + + // Activates the game overlay to open an invite dialog that will send the provided Rich Presence connect string to selected friends + void ActivateGameOverlayInviteDialogConnectString( const char *pchConnectString ); + + // Steam Community items equipped by a user on their profile + // You can register for EquippedProfileItemsChanged_t to know when a friend has changed their equipped profile items + STEAM_CALL_RESULT( EquippedProfileItems_t ) + SteamAPICall_t RequestEquippedProfileItems( CSteamID steamID ); + + bool BHasEquippedProfileItem( CSteamID steamID, ECommunityProfileItemType itemType ); + + const char *GetProfileItemPropertyString( CSteamID steamID, ECommunityProfileItemType itemType, ECommunityProfileItemProperty prop ); + + uint32 GetProfileItemPropertyUint( CSteamID steamID, ECommunityProfileItemType itemType, ECommunityProfileItemProperty prop ); }; -#endif//__INCLUDED_STEAM_FRIENDS_H__ +#endif //__INCLUDED_STEAM_FRIENDS_H__ diff --git a/dll/dll/steam_game_coordinator.h b/dll/dll/steam_game_coordinator.h index a4a87274..376343d2 100644 --- a/dll/dll/steam_game_coordinator.h +++ b/dll/dll/steam_game_coordinator.h @@ -15,143 +15,48 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_GAME_COORDINATOR_H__ +#define __INCLUDED_STEAM_GAME_COORDINATOR_H__ + #include "base.h" class Steam_Game_Coordinator : public ISteamGameCoordinator { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + constexpr const static uint32 protobuf_mask = 0x80000000; - static const uint32 protobuf_mask = 0x80000000; - std::queue outgoing_messages; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; -void push_incoming(std::string message) -{ - outgoing_messages.push(message); + std::queue outgoing_messages{}; - struct GCMessageAvailable_t data; - data.m_nMessageSize = message.size(); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); -} + void push_incoming(std::string message); + + static void steam_callback(void *object, Common_Message *msg); + static void steam_run_every_runcb(void *object); + + void RunCallbacks(); + void Callback(Common_Message *msg); public: -static void steam_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); + Steam_Game_Coordinator(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_Game_Coordinator(); - Steam_Game_Coordinator *steam_gamecoordinator = (Steam_Game_Coordinator *)object; - steam_gamecoordinator->Callback(msg); -} + // sends a message to the Game Coordinator + EGCResults SendMessage_( uint32 unMsgType, const void *pubData, uint32 cubData ); -static void steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); + // returns true if there is a message waiting from the game coordinator + bool IsMessageAvailable( uint32 *pcubMsgSize ); - Steam_Game_Coordinator *steam_gamecoordinator = (Steam_Game_Coordinator *)object; - steam_gamecoordinator->RunCallbacks(); -} - -Steam_Game_Coordinator(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->run_every_runcb = run_every_runcb; - //this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Game_Coordinator::steam_callback, this); - this->run_every_runcb->add(&Steam_Game_Coordinator::steam_run_every_runcb, this); - - this->callback_results = callback_results; - this->callbacks = callbacks; -} - -~Steam_Game_Coordinator() -{ - //this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Game_Coordinator::steam_callback, this); - this->run_every_runcb->remove(&Steam_Game_Coordinator::steam_run_every_runcb, this); -} - -// sends a message to the Game Coordinator -EGCResults SendMessage_( uint32 unMsgType, const void *pubData, uint32 cubData ) -{ - PRINT_DEBUG("%X %u len %u", unMsgType, (~protobuf_mask) & unMsgType, cubData); - std::lock_guard lock(global_mutex); - if (protobuf_mask & unMsgType) { - uint32 message_type = (~protobuf_mask) & unMsgType; - if (message_type == 4006) { //client hello - std::string message("\xA4\x0F\x00\x80\x00\x00\x00\x00\x08\x00", 10); - push_incoming(message); - } else - if (message_type == 4007) { //server hello - std::string message("\xA5\x0F\x00\x80\x00\x00\x00\x00\x08\x00", 10); - push_incoming(message); - } - } - - return k_EGCResultOK; -} - -// returns true if there is a message waiting from the game coordinator -bool IsMessageAvailable( uint32 *pcubMsgSize ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (outgoing_messages.size()) { - if (pcubMsgSize) *pcubMsgSize = outgoing_messages.front().size(); - return true; - } else { - return false; - } -} - -// fills the provided buffer with the first message in the queue and returns k_EGCResultOK or -// returns k_EGCResultNoMessage if there is no message waiting. pcubMsgSize is filled with the message size. -// If the provided buffer is not large enough to fit the entire message, k_EGCResultBufferTooSmall is returned -// and the message remains at the head of the queue. -EGCResults RetrieveMessage( uint32 *punMsgType, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (outgoing_messages.size()) { - if (outgoing_messages.front().size() > cubDest) { - return k_EGCResultBufferTooSmall; - } - - outgoing_messages.front().copy((char *)pubDest, cubDest); - if (pcubMsgSize) *pcubMsgSize = outgoing_messages.front().size(); - if (punMsgType && outgoing_messages.front().size() >= sizeof(uint32)) { - outgoing_messages.front().copy((char *)punMsgType, sizeof(uint32)); - *punMsgType = ntohl(*punMsgType); - } - - outgoing_messages.pop(); - return k_EGCResultOK; - } else { - return k_EGCResultNoMessage; - } -} - -void RunCallbacks() -{ -} - -void Callback(Common_Message *msg) -{ - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - - } - - if (msg->low_level().type() == Low_Level::DISCONNECT) { - - } - } - - if (msg->has_networking_sockets()) { - - } -} + // fills the provided buffer with the first message in the queue and returns k_EGCResultOK or + // returns k_EGCResultNoMessage if there is no message waiting. pcubMsgSize is filled with the message size. + // If the provided buffer is not large enough to fit the entire message, k_EGCResultBufferTooSmall is returned + // and the message remains at the head of the queue. + EGCResults RetrieveMessage( uint32 *punMsgType, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize ); }; + +#endif // __INCLUDED_STEAM_GAME_COORDINATOR_H__ diff --git a/dll/dll/steam_gamesearch.h b/dll/dll/steam_gamesearch.h index f688952c..18f4c691 100644 --- a/dll/dll/steam_gamesearch.h +++ b/dll/dll/steam_gamesearch.h @@ -15,215 +15,104 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_GAMESEARCH_H__ +#define __INCLUDED_STEAM_GAMESEARCH_H__ + #include "base.h" class Steam_Game_Search : public ISteamGameSearch { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; std::chrono::time_point initialized_time = std::chrono::steady_clock::now(); - FSteamNetworkingSocketsDebugOutput debug_function; + FSteamNetworkingSocketsDebugOutput debug_function{}; + + static void steam_callback(void *object, Common_Message *msg); + static void steam_run_every_runcb(void *object); + + void RunCallbacks(); + void Callback(Common_Message *msg); public: -static void steam_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); + Steam_Game_Search(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_Game_Search(); - Steam_Game_Search *steam_gamesearch = (Steam_Game_Search *)object; - steam_gamesearch->Callback(msg); -} + // ============================================================================================= + // Game Player APIs -static void steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Game_Search *steam_gamesearch = (Steam_Game_Search *)object; - steam_gamesearch->RunCallbacks(); -} - -Steam_Game_Search(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->run_every_runcb = run_every_runcb; - //this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Game_Search::steam_callback, this); - this->run_every_runcb->add(&Steam_Game_Search::steam_run_every_runcb, this); - - this->callback_results = callback_results; - this->callbacks = callbacks; -} - -~Steam_Game_Search() -{ - //this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Game_Search::steam_callback, this); - this->run_every_runcb->remove(&Steam_Game_Search::steam_run_every_runcb, this); -} - -// ============================================================================================= -// Game Player APIs - -// a keyname and a list of comma separated values: one of which is must be found in order for the match to qualify -// fails if a search is currently in progress -EGameSearchErrorCode_t AddGameSearchParams( const char *pchKeyToFind, const char *pchValuesToFind ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} + // a keyname and a list of comma separated values: one of which is must be found in order for the match to qualify + // fails if a search is currently in progress + EGameSearchErrorCode_t AddGameSearchParams( const char *pchKeyToFind, const char *pchValuesToFind ); -// all players in lobby enter the queue and await a SearchForGameNotificationCallback_t callback. fails if another search is currently in progress -// if not the owner of the lobby or search already in progress this call fails -// periodic callbacks will be sent as queue time estimates change -EGameSearchErrorCode_t SearchForGameWithLobby( CSteamID steamIDLobby, int nPlayerMin, int nPlayerMax ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} + // all players in lobby enter the queue and await a SearchForGameNotificationCallback_t callback. fails if another search is currently in progress + // if not the owner of the lobby or search already in progress this call fails + // periodic callbacks will be sent as queue time estimates change + EGameSearchErrorCode_t SearchForGameWithLobby( CSteamID steamIDLobby, int nPlayerMin, int nPlayerMax ); -// user enter the queue and await a SearchForGameNotificationCallback_t callback. fails if another search is currently in progress -// periodic callbacks will be sent as queue time estimates change -EGameSearchErrorCode_t SearchForGameSolo( int nPlayerMin, int nPlayerMax ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} + // user enter the queue and await a SearchForGameNotificationCallback_t callback. fails if another search is currently in progress + // periodic callbacks will be sent as queue time estimates change + EGameSearchErrorCode_t SearchForGameSolo( int nPlayerMin, int nPlayerMax ); -// after receiving SearchForGameResultCallback_t, accept or decline the game -// multiple SearchForGameResultCallback_t will follow as players accept game until the host starts or cancels the game -EGameSearchErrorCode_t AcceptGame() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} + // after receiving SearchForGameResultCallback_t, accept or decline the game + // multiple SearchForGameResultCallback_t will follow as players accept game until the host starts or cancels the game + EGameSearchErrorCode_t AcceptGame(); -EGameSearchErrorCode_t DeclineGame() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} + EGameSearchErrorCode_t DeclineGame(); -// after receiving GameStartedByHostCallback_t get connection details to server -EGameSearchErrorCode_t RetrieveConnectionDetails( CSteamID steamIDHost, char *pchConnectionDetails, int cubConnectionDetails ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} + // after receiving GameStartedByHostCallback_t get connection details to server + EGameSearchErrorCode_t RetrieveConnectionDetails( CSteamID steamIDHost, char *pchConnectionDetails, int cubConnectionDetails ); -// leaves queue if still waiting -EGameSearchErrorCode_t EndGameSearch() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} + // leaves queue if still waiting + EGameSearchErrorCode_t EndGameSearch(); -// ============================================================================================= -// Game Host APIs + // ============================================================================================= + // Game Host APIs -// a keyname and a list of comma separated values: all the values you allow -EGameSearchErrorCode_t SetGameHostParams( const char *pchKey, const char *pchValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} + // a keyname and a list of comma separated values: all the values you allow + EGameSearchErrorCode_t SetGameHostParams( const char *pchKey, const char *pchValue ); -// set connection details for players once game is found so they can connect to this server -EGameSearchErrorCode_t SetConnectionDetails( const char *pchConnectionDetails, int cubConnectionDetails ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} + // set connection details for players once game is found so they can connect to this server + EGameSearchErrorCode_t SetConnectionDetails( const char *pchConnectionDetails, int cubConnectionDetails ); -// mark server as available for more players with nPlayerMin,nPlayerMax desired -// accept no lobbies with playercount greater than nMaxTeamSize -// the set of lobbies returned must be partitionable into teams of no more than nMaxTeamSize -// RequestPlayersForGameNotificationCallback_t callback will be sent when the search has started -// multple RequestPlayersForGameResultCallback_t callbacks will follow when players are found -EGameSearchErrorCode_t RequestPlayersForGame( int nPlayerMin, int nPlayerMax, int nMaxTeamSize ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} + // mark server as available for more players with nPlayerMin,nPlayerMax desired + // accept no lobbies with playercount greater than nMaxTeamSize + // the set of lobbies returned must be partitionable into teams of no more than nMaxTeamSize + // RequestPlayersForGameNotificationCallback_t callback will be sent when the search has started + // multple RequestPlayersForGameResultCallback_t callbacks will follow when players are found + EGameSearchErrorCode_t RequestPlayersForGame( int nPlayerMin, int nPlayerMax, int nMaxTeamSize ); -// accept the player list and release connection details to players -// players will only be given connection details and host steamid when this is called -// ( allows host to accept after all players confirm, some confirm, or none confirm. decision is entirely up to the host ) -EGameSearchErrorCode_t HostConfirmGameStart( uint64 ullUniqueGameID ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} + // accept the player list and release connection details to players + // players will only be given connection details and host steamid when this is called + // ( allows host to accept after all players confirm, some confirm, or none confirm. decision is entirely up to the host ) + EGameSearchErrorCode_t HostConfirmGameStart( uint64 ullUniqueGameID ); -// cancel request and leave the pool of game hosts looking for players -// if a set of players has already been sent to host, all players will receive SearchForGameHostFailedToConfirm_t -EGameSearchErrorCode_t CancelRequestPlayersForGame() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} + // cancel request and leave the pool of game hosts looking for players + // if a set of players has already been sent to host, all players will receive SearchForGameHostFailedToConfirm_t + EGameSearchErrorCode_t CancelRequestPlayersForGame(); -// submit a result for one player. does not end the game. ullUniqueGameID continues to describe this game -EGameSearchErrorCode_t SubmitPlayerResult( uint64 ullUniqueGameID, CSteamID steamIDPlayer, EPlayerResult_t EPlayerResult ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} + // submit a result for one player. does not end the game. ullUniqueGameID continues to describe this game + EGameSearchErrorCode_t SubmitPlayerResult( uint64 ullUniqueGameID, CSteamID steamIDPlayer, EPlayerResult_t EPlayerResult ); -// ends the game. no further SubmitPlayerResults for ullUniqueGameID will be accepted -// any future requests will provide a new ullUniqueGameID -EGameSearchErrorCode_t EndGame( uint64 ullUniqueGameID ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EGameSearchErrorCode_Failed_Offline; -} - -void RunCallbacks() -{ -} - -void Callback(Common_Message *msg) -{ - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - - } - - if (msg->low_level().type() == Low_Level::DISCONNECT) { - - } - } - - if (msg->has_networking_sockets()) { - - } -} + // ends the game. no further SubmitPlayerResults for ullUniqueGameID will be accepted + // any future requests will provide a new ullUniqueGameID + EGameSearchErrorCode_t EndGame( uint64 ullUniqueGameID ); }; + +#endif // __INCLUDED_STEAM_GAMESEARCH_H__ diff --git a/dll/dll/steam_gameserver.h b/dll/dll/steam_gameserver.h index 04244485..f6669a31 100644 --- a/dll/dll/steam_gameserver.h +++ b/dll/dll/steam_gameserver.h @@ -15,6 +15,9 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_GAMESERVER_H__ +#define __INCLUDED_STEAM_GAMESERVER_H__ + #include "base.h" #include "auth.h" @@ -23,16 +26,16 @@ //----------------------------------------------------------------------------- struct Gameserver_Outgoing_Packet { - std::vector data; + std::vector data{}; - uint32 ip; - uint16 port; + uint32 ip{}; + uint16 port{}; }; struct Gameserver_Player_Info_t { - std::chrono::steady_clock::time_point join_time; - std::string name; - uint32 score; + std::chrono::steady_clock::time_point join_time{}; + std::string name{}; + uint32 score{}; }; class Steam_GameServer : @@ -47,27 +50,28 @@ public ISteamGameServer013, public ISteamGameServer014, public ISteamGameServer { - class Settings *settings; - class Networking *network; - class SteamCallBacks *callbacks; + class Settings *settings{}; + class Networking *network{}; + class SteamCallBacks *callbacks{}; - CSteamID steam_id; + CSteamID steam_id{}; bool call_servers_connected = false; bool logged_in = false; bool call_servers_disconnected = false; - Gameserver server_data; - std::vector> players; + Gameserver server_data{}; + std::vector> players{}; - uint32 flags; - bool policy_response_called; + uint32 flags{}; + bool policy_response_called{}; + + std::chrono::high_resolution_clock::time_point last_sent_server_info{}; + Auth_Manager *auth_manager{}; + + std::vector outgoing_packets{}; - std::chrono::high_resolution_clock::time_point last_sent_server_info; - Auth_Manager *auth_manager; - std::vector outgoing_packets; public: - Steam_GameServer(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks); ~Steam_GameServer(); @@ -361,6 +365,10 @@ public: STEAM_CALL_RESULT( ComputeNewPlayerCompatibilityResult_t ) SteamAPICall_t ComputeNewPlayerCompatibility( CSteamID steamIDNewPlayer ); - // + + // called by steam_client::runcallbacks void RunCallbacks(); + }; + +#endif // __INCLUDED_STEAM_GAMESERVER_H__ diff --git a/dll/dll/steam_gameserverstats.h b/dll/dll/steam_gameserverstats.h index 2fbab4fc..c889981d 100644 --- a/dll/dll/steam_gameserverstats.h +++ b/dll/dll/steam_gameserverstats.h @@ -15,18 +15,22 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_GAMESERVERSTATS_H__ +#define __INCLUDED_STEAM_GAMESERVERSTATS_H__ + #include "base.h" //----------------------------------------------------------------------------- // Purpose: Functions for authenticating users via Steam to play on a game server //----------------------------------------------------------------------------- -class Steam_GameServerStats : public ISteamGameServerStats +class Steam_GameServerStats : +public ISteamGameServerStats { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; struct RequestAllStats { std::chrono::high_resolution_clock::time_point created{}; @@ -109,3 +113,5 @@ public: STEAM_CALL_RESULT( GSStatsStored_t ) SteamAPICall_t StoreUserStats( CSteamID steamIDUser ); }; + +#endif // __INCLUDED_STEAM_GAMESERVERSTATS_H__ diff --git a/dll/dll/steam_http.h b/dll/dll/steam_http.h index ce939a50..d9da2fa2 100644 --- a/dll/dll/steam_http.h +++ b/dll/dll/steam_http.h @@ -15,14 +15,16 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_HTTP_H__ +#define __INCLUDED_STEAM_HTTP_H__ + #include "base.h" -#include "common_includes.h" #include struct Steam_Http_Request { - HTTPRequestHandle handle; - EHTTPMethod request_method; + HTTPRequestHandle handle{}; + EHTTPMethod request_method{}; std::string url{}; uint64 timeout_sec = 60; bool requires_valid_ssl = false; @@ -37,7 +39,7 @@ struct Steam_Http_Request { std::map get_or_post_params{}; std::string post_raw{}; - uint64 context_value; + uint64 context_value{}; // target local filepath to save std::string target_filepath{}; @@ -48,17 +50,18 @@ struct Steam_Http_Request { std::string response{}; }; + class Steam_HTTP : public ISteamHTTP001, public ISteamHTTP002, public ISteamHTTP { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; - std::vector requests; + std::vector requests{}; Steam_Http_Request *get_request(HTTPRequestHandle hRequest); void online_http_request(Steam_Http_Request *request, SteamAPICall_t *pCallHandle); @@ -177,3 +180,5 @@ public: // Check if the reason the request failed was because we timed it out (rather than some harder failure) bool GetHTTPRequestWasTimedOut( HTTPRequestHandle hRequest, bool *pbWasTimedOut ); }; + +#endif // __INCLUDED_STEAM_HTTP_H__ diff --git a/dll/dll/steam_inventory.h b/dll/dll/steam_inventory.h index ea691f3d..34b81aca 100644 --- a/dll/dll/steam_inventory.h +++ b/dll/dll/steam_inventory.h @@ -15,25 +15,25 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_INVENTORY_H__ +#define __INCLUDED_STEAM_INVENTORY_H__ + #include "base.h" // For SteamItemDef_t struct Steam_Inventory_Requests { double timeout = 0.1; bool done = false; - bool full_query; + bool full_query{}; - SteamInventoryResult_t inventory_result; - std::chrono::system_clock::time_point time_created; + SteamInventoryResult_t inventory_result{}; + std::chrono::system_clock::time_point time_created{}; - std::vector instance_ids; + std::vector instance_ids{}; - bool result_done() { - return done; - } + bool result_done() const; - uint32 timestamp() { - return std::chrono::duration_cast>(time_created.time_since_epoch()).count(); - } + // in seconds + uint32 timestamp() const; }; class Steam_Inventory : @@ -42,951 +42,367 @@ class Steam_Inventory : public ISteamInventory { public: - static constexpr auto items_user_file = "items.json"; - static constexpr auto items_default_file = "default_items.json"; + static constexpr const char items_user_file[] = "items.json"; + static constexpr const char items_default_file[] = "default_items.json"; private: - class Settings *settings; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; - class Local_Storage* local_storage; + class Settings *settings{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; + class Local_Storage* local_storage{}; - std::vector inventory_requests; + nlohmann::json defined_items{}; + nlohmann::json user_items{}; - nlohmann::json defined_items; - nlohmann::json user_items; + std::vector inventory_requests{}; - bool inventory_loaded; - bool call_definition_update; - bool item_definitions_loaded; + bool inventory_loaded{}; + bool call_definition_update{}; + bool item_definitions_loaded{}; -struct Steam_Inventory_Requests* new_inventory_result(bool full_query=true, const SteamItemInstanceID_t* pInstanceIDs = NULL, uint32 unCountInstanceIDs = 0) -{ - static SteamInventoryResult_t result; - ++result; + struct Steam_Inventory_Requests* new_inventory_result(bool full_query=true, const SteamItemInstanceID_t* pInstanceIDs = NULL, uint32 unCountInstanceIDs = 0); + struct Steam_Inventory_Requests *get_inventory_result(SteamInventoryResult_t resultHandle); - struct Steam_Inventory_Requests request; - request.inventory_result = result; - request.full_query = full_query; - if (pInstanceIDs && unCountInstanceIDs) { - request.instance_ids.reserve(unCountInstanceIDs); - std::copy(pInstanceIDs, pInstanceIDs + unCountInstanceIDs, std::back_inserter(request.instance_ids)); - } + void read_items_db(); + void read_inventory_db(); - request.time_created = std::chrono::system_clock::now(); - inventory_requests.push_back(request); - - return &(inventory_requests.back()); -} - -struct Steam_Inventory_Requests *get_inventory_result(SteamInventoryResult_t resultHandle) -{ - auto request = std::find_if(inventory_requests.begin(), inventory_requests.end(), [&resultHandle](struct Steam_Inventory_Requests const& item) { return item.inventory_result == resultHandle; }); - if (inventory_requests.end() == request) - return NULL; - - return &(*request); -} - -void read_items_db() -{ - std::string items_db_path = Local_Storage::get_game_settings_path() + items_user_file; - PRINT_DEBUG("file path: %s", 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("items file path: %s", items_db_path.c_str()); - local_storage->load_json(items_db_path, user_items); - } -} + static void run_every_runcb_cb(void *object); public: + Steam_Inventory(class Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, class Local_Storage *local_storage); + ~Steam_Inventory(); + + // INVENTORY ASYNC RESULT MANAGEMENT + // + // Asynchronous inventory queries always output a result handle which can be used with + // GetResultStatus, GetResultItems, etc. A SteamInventoryResultReady_t callback will + // be triggered when the asynchronous result becomes ready (or fails). + // + + // Find out the status of an asynchronous inventory result handle. Possible values: + // k_EResultPending - still in progress + // k_EResultOK - done, result ready + // k_EResultExpired - done, result ready, maybe out of date (see DeserializeResult) + // k_EResultInvalidParam - ERROR: invalid API call parameters + // k_EResultServiceUnavailable - ERROR: service temporarily down, you may retry later + // k_EResultLimitExceeded - ERROR: operation would exceed per-user inventory limits + // k_EResultFail - ERROR: unknown / generic error + STEAM_METHOD_DESC(Find out the status of an asynchronous inventory result handle.) + EResult GetResultStatus( SteamInventoryResult_t resultHandle ); + + + // Copies the contents of a result set into a flat array. The specific + // contents of the result set depend on which query which was used. + STEAM_METHOD_DESC(Copies the contents of a result set into a flat array. The specific contents of the result set depend on which query which was used.) + bool GetResultItems( SteamInventoryResult_t resultHandle, + STEAM_OUT_ARRAY_COUNT( punOutItemsArraySize,Output array) SteamItemDetails_t *pOutItemsArray, + uint32 *punOutItemsArraySize ); + + + // In combination with GetResultItems, you can use GetResultItemProperty to retrieve + // dynamic string properties for a given item returned in the result set. + // + // Property names are always composed of ASCII letters, numbers, and/or underscores. + // + // Pass a NULL pointer for pchPropertyName to get a comma - separated list of available + // property names. + // + // If pchValueBuffer is NULL, *punValueBufferSize will contain the + // suggested buffer size. Otherwise it will be the number of bytes actually copied + // to pchValueBuffer. If the results do not fit in the given buffer, partial + // results may be copied. + bool GetResultItemProperty( SteamInventoryResult_t resultHandle, + uint32 unItemIndex, + const char *pchPropertyName, + STEAM_OUT_STRING_COUNT( punValueBufferSizeOut ) char *pchValueBuffer, uint32 *punValueBufferSizeOut ); + + + // Returns the server time at which the result was generated. Compare against + // the value of IClientUtils::GetServerRealTime() to determine age. + STEAM_METHOD_DESC(Returns the server time at which the result was generated. Compare against the value of IClientUtils::GetServerRealTime() to determine age.) + uint32 GetResultTimestamp( SteamInventoryResult_t resultHandle ); + + + // Returns true if the result belongs to the target steam ID, false if the + // result does not. This is important when using DeserializeResult, to verify + // that a remote player is not pretending to have a different user's inventory. + STEAM_METHOD_DESC(Returns true if the result belongs to the target steam ID or false if the result does not. This is important when using DeserializeResult to verify that a remote player is not pretending to have a different users inventory.) + bool CheckResultSteamID( SteamInventoryResult_t resultHandle, CSteamID steamIDExpected ); + + + // Destroys a result handle and frees all associated memory. + STEAM_METHOD_DESC(Destroys a result handle and frees all associated memory.) + void DestroyResult( SteamInventoryResult_t resultHandle ); + + + + // INVENTORY ASYNC QUERY + // + + // Captures the entire state of the current user's Steam inventory. + // You must call DestroyResult on this handle when you are done with it. + // Returns false and sets *pResultHandle to zero if inventory is unavailable. + // Note: calls to this function are subject to rate limits and may return + // cached results if called too frequently. It is suggested that you call + // this function only when you are about to display the user's full inventory, + // or if you expect that the inventory may have changed. + STEAM_METHOD_DESC(Captures the entire state of the current users Steam inventory.) + bool GetAllItems( SteamInventoryResult_t *pResultHandle ); + + + + // Captures the state of a subset of the current user's Steam inventory, + // identified by an array of item instance IDs. The results from this call + // can be serialized and passed to other players to "prove" that the current + // user owns specific items, without exposing the user's entire inventory. + // For example, you could call GetItemsByID with the IDs of the user's + // currently equipped cosmetic items and serialize this to a buffer, and + // then transmit this buffer to other players upon joining a game. + STEAM_METHOD_DESC(Captures the state of a subset of the current users Steam inventory identified by an array of item instance IDs.) + bool GetItemsByID( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT( unCountInstanceIDs ) const SteamItemInstanceID_t *pInstanceIDs, uint32 unCountInstanceIDs ); + + + + // RESULT SERIALIZATION AND AUTHENTICATION + // + // Serialized result sets contain a short signature which can't be forged + // or replayed across different game sessions. A result set can be serialized + // on the local client, transmitted to other players via your game networking, + // and deserialized by the remote players. This is a secure way of preventing + // hackers from lying about posessing rare/high-value items. + + // Serializes a result set with signature bytes to an output buffer. Pass + // NULL as an output buffer to get the required size via punOutBufferSize. + // The size of a serialized result depends on the number items which are being + // serialized. When securely transmitting items to other players, it is + // recommended to use "GetItemsByID" first to create a minimal result set. + // Results have a built-in timestamp which will be considered "expired" after + // an hour has elapsed. See DeserializeResult for expiration handling. + bool SerializeResult( SteamInventoryResult_t resultHandle, STEAM_OUT_BUFFER_COUNT(punOutBufferSize) void *pOutBuffer, uint32 *punOutBufferSize ); + + + // Deserializes a result set and verifies the signature bytes. Returns false + // if bRequireFullOnlineVerify is set but Steam is running in Offline mode. + // Otherwise returns true and then delivers error codes via GetResultStatus. + // + // The bRESERVED_MUST_BE_FALSE flag is reserved for future use and should not + // be set to true by your game at this time. + // + // DeserializeResult has a potential soft-failure mode where the handle status + // is set to k_EResultExpired. GetResultItems() still succeeds in this mode. + // The "expired" result could indicate that the data may be out of date - not + // just due to timed expiration (one hour), but also because one of the items + // in the result set may have been traded or consumed since the result set was + // generated. You could compare the timestamp from GetResultTimestamp() to + // ISteamUtils::GetServerRealTime() to determine how old the data is. You could + // simply ignore the "expired" result code and continue as normal, or you + // could challenge the player with expired data to send an updated result set. + bool DeserializeResult( SteamInventoryResult_t *pOutResultHandle, STEAM_BUFFER_COUNT(punOutBufferSize) const void *pBuffer, uint32 unBufferSize, bool bRESERVED_MUST_BE_FALSE); + + + + // INVENTORY ASYNC MODIFICATION + // + + // GenerateItems() creates one or more items and then generates a SteamInventoryCallback_t + // notification with a matching nCallbackContext parameter. This API is only intended + // for prototyping - it is only usable by Steam accounts that belong to the publisher group + // for your game. + // If punArrayQuantity is not NULL, it should be the same length as pArrayItems and should + // describe the quantity of each item to generate. + bool GenerateItems( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, STEAM_ARRAY_COUNT(unArrayLength) const uint32 *punArrayQuantity, uint32 unArrayLength ); -static void run_every_runcb_cb(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Inventory *obj = (Steam_Inventory *)object; - obj->RunCallbacks(); -} - -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) -{ - this->run_every_runcb->add(&Steam_Inventory::run_every_runcb_cb, this); -} - -~Steam_Inventory() -{ - this->run_every_runcb->remove(&Steam_Inventory::run_every_runcb_cb, this); -} - -// INVENTORY ASYNC RESULT MANAGEMENT -// -// Asynchronous inventory queries always output a result handle which can be used with -// GetResultStatus, GetResultItems, etc. A SteamInventoryResultReady_t callback will -// be triggered when the asynchronous result becomes ready (or fails). -// - -// Find out the status of an asynchronous inventory result handle. Possible values: -// k_EResultPending - still in progress -// k_EResultOK - done, result ready -// k_EResultExpired - done, result ready, maybe out of date (see DeserializeResult) -// k_EResultInvalidParam - ERROR: invalid API call parameters -// k_EResultServiceUnavailable - ERROR: service temporarily down, you may retry later -// k_EResultLimitExceeded - ERROR: operation would exceed per-user inventory limits -// k_EResultFail - ERROR: unknown / generic error -STEAM_METHOD_DESC(Find out the status of an asynchronous inventory result handle.) -EResult GetResultStatus( SteamInventoryResult_t resultHandle ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle); - if (!request) return k_EResultInvalidParam; - if (!request->result_done()) return k_EResultPending; - return k_EResultOK; -} - - -// Copies the contents of a result set into a flat array. The specific -// contents of the result set depend on which query which was used. -STEAM_METHOD_DESC(Copies the contents of a result set into a flat array. The specific contents of the result set depend on which query which was used.) -bool GetResultItems( SteamInventoryResult_t resultHandle, - STEAM_OUT_ARRAY_COUNT( punOutItemsArraySize,Output array) SteamItemDetails_t *pOutItemsArray, - uint32 *punOutItemsArraySize ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle); - if (!request) return false; - if (!request->result_done()) return false; - if (!inventory_loaded) return false; - - if (pOutItemsArray != nullptr) - { - SteamItemDetails_t *items_array_base = pOutItemsArray; - uint32 max_items = *punOutItemsArraySize; - - if (request->full_query) { - // We end if we reached the end of items or the end of buffer - for( auto i = user_items.begin(); i != user_items.end() && max_items; ++i, --max_items ) - { - pOutItemsArray->m_iDefinition = std::stoi(i.key()); - pOutItemsArray->m_itemId = pOutItemsArray->m_iDefinition; - try - { - pOutItemsArray->m_unQuantity = i.value().get(); - } - catch (...) - { - pOutItemsArray->m_unQuantity = 0; - } - pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; - ++pOutItemsArray; - } - } else { - for (auto &itemid : request->instance_ids) { - if (!max_items) break; - auto it = user_items.find(std::to_string(itemid)); - if (it != user_items.end()) { - pOutItemsArray->m_iDefinition = itemid; - pOutItemsArray->m_itemId = itemid; - - try - { - pOutItemsArray->m_unQuantity = it->get(); - } - catch (...) - { - pOutItemsArray->m_unQuantity = 0; - } - pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; - ++pOutItemsArray; - --max_items; - } - } - } - - *punOutItemsArraySize = pOutItemsArray - items_array_base; - } - else if (punOutItemsArraySize != nullptr) - { - 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("good"); - return true; -} - - -// In combination with GetResultItems, you can use GetResultItemProperty to retrieve -// dynamic string properties for a given item returned in the result set. -// -// Property names are always composed of ASCII letters, numbers, and/or underscores. -// -// Pass a NULL pointer for pchPropertyName to get a comma - separated list of available -// property names. -// -// If pchValueBuffer is NULL, *punValueBufferSize will contain the -// suggested buffer size. Otherwise it will be the number of bytes actually copied -// to pchValueBuffer. If the results do not fit in the given buffer, partial -// results may be copied. -bool GetResultItemProperty( SteamInventoryResult_t resultHandle, - uint32 unItemIndex, - const char *pchPropertyName, - STEAM_OUT_STRING_COUNT( punValueBufferSizeOut ) char *pchValueBuffer, uint32 *punValueBufferSizeOut ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - //TODO - return false; -} - - -// Returns the server time at which the result was generated. Compare against -// the value of IClientUtils::GetServerRealTime() to determine age. -STEAM_METHOD_DESC(Returns the server time at which the result was generated. Compare against the value of IClientUtils::GetServerRealTime() to determine age.) -uint32 GetResultTimestamp( SteamInventoryResult_t resultHandle ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle); - if (!request || !request->result_done()) return 0; - return request->timestamp(); -} - - -// Returns true if the result belongs to the target steam ID, false if the -// result does not. This is important when using DeserializeResult, to verify -// that a remote player is not pretending to have a different user's inventory. -STEAM_METHOD_DESC(Returns true if the result belongs to the target steam ID or false if the result does not. This is important when using DeserializeResult to verify that a remote player is not pretending to have a different users inventory.) -bool CheckResultSteamID( SteamInventoryResult_t resultHandle, CSteamID steamIDExpected ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - //TODO - return true; -} - - -// Destroys a result handle and frees all associated memory. -STEAM_METHOD_DESC(Destroys a result handle and frees all associated memory.) -void DestroyResult( SteamInventoryResult_t resultHandle ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - auto request = std::find_if(inventory_requests.begin(), inventory_requests.end(), [&resultHandle](struct Steam_Inventory_Requests const& item) { return item.inventory_result == resultHandle; }); - if (inventory_requests.end() == request) - return; - - inventory_requests.erase(request); -} - - - -// INVENTORY ASYNC QUERY -// - -// Captures the entire state of the current user's Steam inventory. -// You must call DestroyResult on this handle when you are done with it. -// Returns false and sets *pResultHandle to zero if inventory is unavailable. -// Note: calls to this function are subject to rate limits and may return -// cached results if called too frequently. It is suggested that you call -// this function only when you are about to display the user's full inventory, -// or if you expect that the inventory may have changed. -STEAM_METHOD_DESC(Captures the entire state of the current users Steam inventory.) -bool GetAllItems( SteamInventoryResult_t *pResultHandle ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct Steam_Inventory_Requests* request = new_inventory_result(); - - if (pResultHandle != nullptr) - *pResultHandle = request->inventory_result; - - return true; -} - - - -// Captures the state of a subset of the current user's Steam inventory, -// identified by an array of item instance IDs. The results from this call -// can be serialized and passed to other players to "prove" that the current -// user owns specific items, without exposing the user's entire inventory. -// For example, you could call GetItemsByID with the IDs of the user's -// currently equipped cosmetic items and serialize this to a buffer, and -// then transmit this buffer to other players upon joining a game. -STEAM_METHOD_DESC(Captures the state of a subset of the current users Steam inventory identified by an array of item instance IDs.) -bool GetItemsByID( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT( unCountInstanceIDs ) const SteamItemInstanceID_t *pInstanceIDs, uint32 unCountInstanceIDs ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (pResultHandle) { - struct Steam_Inventory_Requests *request = new_inventory_result(false, pInstanceIDs, unCountInstanceIDs); - *pResultHandle = request->inventory_result; - return true; - } - - return false; -} - - - -// RESULT SERIALIZATION AND AUTHENTICATION -// -// Serialized result sets contain a short signature which can't be forged -// or replayed across different game sessions. A result set can be serialized -// on the local client, transmitted to other players via your game networking, -// and deserialized by the remote players. This is a secure way of preventing -// hackers from lying about posessing rare/high-value items. - -// Serializes a result set with signature bytes to an output buffer. Pass -// NULL as an output buffer to get the required size via punOutBufferSize. -// The size of a serialized result depends on the number items which are being -// serialized. When securely transmitting items to other players, it is -// recommended to use "GetItemsByID" first to create a minimal result set. -// Results have a built-in timestamp which will be considered "expired" after -// an hour has elapsed. See DeserializeResult for expiration handling. -bool SerializeResult( SteamInventoryResult_t resultHandle, STEAM_OUT_BUFFER_COUNT(punOutBufferSize) void *pOutBuffer, uint32 *punOutBufferSize ) -{ - PRINT_DEBUG("%i", resultHandle); - std::lock_guard lock(global_mutex); - //TODO - struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle); - if (!request) return false; - if (!request->result_done()) return false; - - uint8 buffer[8 + 128] = {}; - memset(buffer, 0x5F, sizeof(buffer)); - - if (!punOutBufferSize) return false; - PRINT_DEBUG(" Size %u", *punOutBufferSize); - if (!pOutBuffer) { - *punOutBufferSize = sizeof(buffer); - return true; - } - - if (*punOutBufferSize < sizeof(buffer)) { - *punOutBufferSize = sizeof(buffer); - return false; //?? - } - - memcpy(pOutBuffer, buffer, sizeof(buffer)); - *punOutBufferSize = sizeof(buffer); - return true; -} - - -// Deserializes a result set and verifies the signature bytes. Returns false -// if bRequireFullOnlineVerify is set but Steam is running in Offline mode. -// Otherwise returns true and then delivers error codes via GetResultStatus. -// -// The bRESERVED_MUST_BE_FALSE flag is reserved for future use and should not -// be set to true by your game at this time. -// -// DeserializeResult has a potential soft-failure mode where the handle status -// is set to k_EResultExpired. GetResultItems() still succeeds in this mode. -// The "expired" result could indicate that the data may be out of date - not -// just due to timed expiration (one hour), but also because one of the items -// in the result set may have been traded or consumed since the result set was -// generated. You could compare the timestamp from GetResultTimestamp() to -// ISteamUtils::GetServerRealTime() to determine how old the data is. You could -// simply ignore the "expired" result code and continue as normal, or you -// could challenge the player with expired data to send an updated result set. -bool DeserializeResult( SteamInventoryResult_t *pOutResultHandle, STEAM_BUFFER_COUNT(punOutBufferSize) const void *pBuffer, uint32 unBufferSize, bool bRESERVED_MUST_BE_FALSE) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - //TODO - if (pOutResultHandle) { - struct Steam_Inventory_Requests *request = new_inventory_result(false); - *pOutResultHandle = request->inventory_result; - return true; - } - - return false; -} - - - -// INVENTORY ASYNC MODIFICATION -// - -// GenerateItems() creates one or more items and then generates a SteamInventoryCallback_t -// notification with a matching nCallbackContext parameter. This API is only intended -// for prototyping - it is only usable by Steam accounts that belong to the publisher group -// for your game. -// If punArrayQuantity is not NULL, it should be the same length as pArrayItems and should -// describe the quantity of each item to generate. -bool GenerateItems( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, STEAM_ARRAY_COUNT(unArrayLength) const uint32 *punArrayQuantity, uint32 unArrayLength ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - return false; -} - - -// GrantPromoItems() checks the list of promotional items for which the user may be eligible -// and grants the items (one time only). On success, the result set will include items which -// were granted, if any. If no items were granted because the user isn't eligible for any -// promotions, this is still considered a success. -STEAM_METHOD_DESC(GrantPromoItems() checks the list of promotional items for which the user may be eligible and grants the items (one time only).) -bool GrantPromoItems( SteamInventoryResult_t *pResultHandle ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct Steam_Inventory_Requests* request = new_inventory_result(false); - - if (pResultHandle != nullptr) - *pResultHandle = request->inventory_result; - return true; -} - - -// AddPromoItem() / AddPromoItems() are restricted versions of GrantPromoItems(). Instead of -// scanning for all eligible promotional items, the check is restricted to a single item -// definition or set of item definitions. This can be useful if your game has custom UI for -// showing a specific promo item to the user. -bool AddPromoItem( SteamInventoryResult_t *pResultHandle, SteamItemDef_t itemDef ) -{ - PRINT_DEBUG_ENTRY(); - //TODO - std::lock_guard lock(global_mutex); - struct Steam_Inventory_Requests* request = new_inventory_result(false); - - if (pResultHandle != nullptr) - *pResultHandle = request->inventory_result; - return true; -} - -bool AddPromoItems( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, uint32 unArrayLength ) -{ - PRINT_DEBUG_ENTRY(); - //TODO - std::lock_guard lock(global_mutex); - struct Steam_Inventory_Requests* request = new_inventory_result(false); - - if (pResultHandle != nullptr) - *pResultHandle = request->inventory_result; - return true; -} - - -// ConsumeItem() removes items from the inventory, permanently. They cannot be recovered. -// Not for the faint of heart - if your game implements item removal at all, a high-friction -// UI confirmation process is highly recommended. -STEAM_METHOD_DESC(ConsumeItem() removes items from the inventory permanently.) -bool ConsumeItem( SteamInventoryResult_t *pResultHandle, SteamItemInstanceID_t itemConsume, uint32 unQuantity ) -{ - PRINT_DEBUG("%llu %u", itemConsume, unQuantity); - std::lock_guard lock(global_mutex); - - auto it = user_items.find(std::to_string(itemConsume)); - if (it != user_items.end()) { - try - { - uint32 current = it->get(); - PRINT_DEBUG("previous %u", current); - if (current < unQuantity) unQuantity = current; - uint32 result = current - unQuantity; - if (result == 0) { - user_items.erase(it); - } else { - *it = result; - } - } - catch (...) {} - - } else { - return false; - } - - struct Steam_Inventory_Requests* request = new_inventory_result(false, &itemConsume, 1); - - if (pResultHandle != nullptr) - *pResultHandle = request->inventory_result; - - return true; -} - - -// ExchangeItems() is an atomic combination of item generation and consumption. -// It can be used to implement crafting recipes or transmutations, or items which unpack -// themselves into other items (e.g., a chest). -// Exchange recipes are defined in the ItemDef, and explicitly list the required item -// types and resulting generated type. -// Exchange recipes are evaluated atomically by the Inventory Service; if the supplied -// components do not match the recipe, or do not contain sufficient quantity, the -// exchange will fail. -bool ExchangeItems( SteamInventoryResult_t *pResultHandle, - STEAM_ARRAY_COUNT(unArrayGenerateLength) const SteamItemDef_t *pArrayGenerate, STEAM_ARRAY_COUNT(unArrayGenerateLength) const uint32 *punArrayGenerateQuantity, uint32 unArrayGenerateLength, - STEAM_ARRAY_COUNT(unArrayDestroyLength) const SteamItemInstanceID_t *pArrayDestroy, STEAM_ARRAY_COUNT(unArrayDestroyLength) const uint32 *punArrayDestroyQuantity, uint32 unArrayDestroyLength ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - - - -// TransferItemQuantity() is intended for use with items which are "stackable" (can have -// quantity greater than one). It can be used to split a stack into two, or to transfer -// quantity from one stack into another stack of identical items. To split one stack into -// two, pass k_SteamItemInstanceIDInvalid for itemIdDest and a new item will be generated. -bool TransferItemQuantity( SteamInventoryResult_t *pResultHandle, SteamItemInstanceID_t itemIdSource, uint32 unQuantity, SteamItemInstanceID_t itemIdDest ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - - - -// TIMED DROPS AND PLAYTIME CREDIT -// - -// Deprecated. Calling this method is not required for proper playtime accounting. -STEAM_METHOD_DESC( Deprecated method. Playtime accounting is performed on the Steam servers. ) -void SendItemDropHeartbeat() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - - -// Playtime credit must be consumed and turned into item drops by your game. Only item -// definitions which are marked as "playtime item generators" can be spawned. The call -// will return an empty result set if there is not enough playtime credit for a drop. -// Your game should call TriggerItemDrop at an appropriate time for the user to receive -// new items, such as between rounds or while the player is dead. Note that players who -// hack their clients could modify the value of "dropListDefinition", so do not use it -// to directly control rarity. -// See your Steamworks configuration to set playtime drop rates for individual itemdefs. -// The client library will suppress too-frequent calls to this method. -STEAM_METHOD_DESC(Playtime credit must be consumed and turned into item drops by your game.) -bool TriggerItemDrop( SteamInventoryResult_t *pResultHandle, SteamItemDef_t dropListDefinition ) -{ - PRINT_DEBUG("%p %i", pResultHandle, dropListDefinition); - //TODO: if gameserver return false - std::lock_guard lock(global_mutex); - struct Steam_Inventory_Requests* request = new_inventory_result(false); - - if (pResultHandle != nullptr) - *pResultHandle = request->inventory_result; - return true; -} - - - -// Deprecated. This method is not supported. -bool TradeItems( SteamInventoryResult_t *pResultHandle, CSteamID steamIDTradePartner, - STEAM_ARRAY_COUNT(nArrayGiveLength) const SteamItemInstanceID_t *pArrayGive, STEAM_ARRAY_COUNT(nArrayGiveLength) const uint32 *pArrayGiveQuantity, uint32 nArrayGiveLength, - STEAM_ARRAY_COUNT(nArrayGetLength) const SteamItemInstanceID_t *pArrayGet, STEAM_ARRAY_COUNT(nArrayGetLength) const uint32 *pArrayGetQuantity, uint32 nArrayGetLength ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - - - -// ITEM DEFINITIONS -// -// Item definitions are a mapping of "definition IDs" (integers between 1 and 1000000) -// to a set of string properties. Some of these properties are required to display items -// on the Steam community web site. Other properties can be defined by applications. -// Use of these functions is optional; there is no reason to call LoadItemDefinitions -// if your game hardcodes the numeric definition IDs (eg, purple face mask = 20, blue -// weapon mod = 55) and does not allow for adding new item types without a client patch. -// - -// LoadItemDefinitions triggers the automatic load and refresh of item definitions. -// Every time new item definitions are available (eg, from the dynamic addition of new -// item types while players are still in-game), a SteamInventoryDefinitionUpdate_t -// callback will be fired. -STEAM_METHOD_DESC(LoadItemDefinitions triggers the automatic load and refresh of item definitions.) -bool LoadItemDefinitions() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - if (!item_definitions_loaded) { - call_definition_update = true; - } - - //real steam launches a SteamInventoryResultReady_t which is why I create a new inventory result - new_inventory_result(false); - return true; -} - - -// GetItemDefinitionIDs returns the set of all defined item definition IDs (which are -// defined via Steamworks configuration, and not necessarily contiguous integers). -// If pItemDefIDs is null, the call will return true and *punItemDefIDsArraySize will -// contain the total size necessary for a subsequent call. Otherwise, the call will -// return false if and only if there is not enough space in the output array. -bool GetItemDefinitionIDs( - STEAM_OUT_ARRAY_COUNT(punItemDefIDsArraySize,List of item definition IDs) SteamItemDef_t *pItemDefIDs, - STEAM_DESC(Size of array is passed in and actual size used is returned in this param) uint32 *punItemDefIDsArraySize ) -{ - PRINT_DEBUG("%p", pItemDefIDs); - std::lock_guard lock(global_mutex); - if (!punItemDefIDsArraySize) - return false; - - PRINT_DEBUG(" array_size %u", *punItemDefIDsArraySize); - - if (!item_definitions_loaded) - return false; - - if (pItemDefIDs == nullptr || *punItemDefIDsArraySize == 0) - { - *punItemDefIDsArraySize = defined_items.size(); - return true; - } - - if (*punItemDefIDsArraySize < defined_items.size()) - return false; - - for (auto i = defined_items.begin(); i != defined_items.end(); ++i) - *pItemDefIDs++ = std::stoi(i.key()); - - return true; -} - - -// GetItemDefinitionProperty returns a string property from a given item definition. -// Note that some properties (for example, "name") may be localized and will depend -// on the current Steam language settings (see ISteamApps::GetCurrentGameLanguage). -// Property names are always composed of ASCII letters, numbers, and/or underscores. -// Pass a NULL pointer for pchPropertyName to get a comma - separated list of available -// property names. If pchValueBuffer is NULL, *punValueBufferSize will contain the -// suggested buffer size. Otherwise it will be the number of bytes actually copied -// to pchValueBuffer. If the results do not fit in the given buffer, partial -// results may be copied. -bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPropertyName, - STEAM_OUT_STRING_COUNT(punValueBufferSizeOut) char *pchValueBuffer, uint32 *punValueBufferSizeOut ) -{ - PRINT_DEBUG("%i %s", iDefinition, pchPropertyName); - std::lock_guard lock(global_mutex); - - auto item = defined_items.find(std::to_string(iDefinition)); - if (item != defined_items.end()) - { - if (pchPropertyName != nullptr) - { - // Should I check for punValueBufferSizeOut == nullptr ? - // Try to get the property - auto attr = item.value().find(pchPropertyName); - if (attr != item.value().end()) - { - std::string val; - try - { - val = attr.value().get(); - } - catch (...) - { - pchPropertyName = ""; - *punValueBufferSizeOut = 0; - PRINT_DEBUG(" Error, item: %d, attr: %s is not a string!", iDefinition, pchPropertyName); - return true; - } - if (pchValueBuffer != nullptr) - { - // copy what we can - strncpy(pchValueBuffer, val.c_str(), *punValueBufferSizeOut); - *punValueBufferSizeOut = std::min(static_cast(val.length() + 1), *punValueBufferSizeOut); - } - else - { - // Set punValueBufferSizeOut to the property size - *punValueBufferSizeOut = val.length() + 1; - } - - if (pchValueBuffer != nullptr) - { - // Make sure we have a null terminator - pchValueBuffer[*punValueBufferSizeOut-1] = '\0'; - } - } - // Property not found - else - { - *punValueBufferSizeOut = 0; - PRINT_DEBUG(" Attr %s not found for item %d", pchPropertyName, iDefinition); - return false; - } - } - else // Pass a NULL pointer for pchPropertyName to get a comma - separated list of available property names. - { - // If pchValueBuffer is NULL, *punValueBufferSize will contain the suggested buffer size - if (pchValueBuffer == nullptr) - { - // Should I check for punValueBufferSizeOut == nullptr ? - *punValueBufferSizeOut = 0; - for (auto i = item.value().begin(); i != item.value().end(); ++i) - *punValueBufferSizeOut += i.key().length() + 1; // Size of key + comma, and the last is not a comma but null char - } - else - { - // strncat always add the null terminator, so remove 1 to the string length - uint32_t len = *punValueBufferSizeOut-1; - *punValueBufferSizeOut = 0; - memset(pchValueBuffer, 0, len); - for( auto i = item.value().begin(); i != item.value().end() && len > 0; ++i ) - { - strncat(pchValueBuffer, i.key().c_str(), len); - // Count how many chars we copied - // Either the string length or the buffer size if its too small - uint32 x = std::min(len, static_cast(i.key().length())); - *punValueBufferSizeOut += x; - len -= x; - - if (len && std::distance(i, item.value().end()) != 1) // If this is not the last item, add a comma - strncat(pchValueBuffer, ",", len--); - - // Always add 1, its a comma or the null terminator - ++*punValueBufferSizeOut; - } - } - } - - return true; - } - - return false; -} - - -// Request the list of "eligible" promo items that can be manually granted to the given -// user. These are promo items of type "manual" that won't be granted automatically. -// An example usage of this is an item that becomes available every week. -STEAM_CALL_RESULT( SteamInventoryEligiblePromoItemDefIDs_t ) -SteamAPICall_t RequestEligiblePromoItemDefinitionsIDs( CSteamID steamID ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - - -// After handling a SteamInventoryEligiblePromoItemDefIDs_t call result, use this -// function to pull out the list of item definition ids that the user can be -// manually granted via the AddPromoItems() call. -bool GetEligiblePromoItemDefinitionIDs( - CSteamID steamID, - STEAM_OUT_ARRAY_COUNT(punItemDefIDsArraySize,List of item definition IDs) SteamItemDef_t *pItemDefIDs, - STEAM_DESC(Size of array is passed in and actual size used is returned in this param) uint32 *punItemDefIDsArraySize ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - - -// Starts the purchase process for the given item definitions. The callback SteamInventoryStartPurchaseResult_t -// will be posted if Steam was able to initialize the transaction. -// -// Once the purchase has been authorized and completed by the user, the callback SteamInventoryResultReady_t -// will be posted. -STEAM_CALL_RESULT( SteamInventoryStartPurchaseResult_t ) -SteamAPICall_t StartPurchase( STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, STEAM_ARRAY_COUNT(unArrayLength) const uint32 *punArrayQuantity, uint32 unArrayLength ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - - -// Request current prices for all applicable item definitions -STEAM_CALL_RESULT( SteamInventoryRequestPricesResult_t ) -SteamAPICall_t RequestPrices() -{ - PRINT_DEBUG_ENTRY(); - SteamInventoryRequestPricesResult_t data{}; - data.m_result = k_EResultOK; - memcpy(data.m_rgchCurrency, "USD", 4); - - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.2); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.2); - return ret; -} - - -// Returns the number of items with prices. Need to call RequestPrices() first. -uint32 GetNumItemsWithPrices() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -bool GetItemsWithPrices( STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pArrayItemDefs, Items with prices) SteamItemDef_t *pArrayItemDefs, - STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pCurrentPrices, - STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pBasePrices, - uint32 unArrayLength ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -// Returns item definition ids and their prices in the user's local currency. -// Need to call RequestPrices() first. -bool GetItemsWithPrices( STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pArrayItemDefs, Items with prices) SteamItemDef_t *pArrayItemDefs, - STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pPrices, - uint32 unArrayLength ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return GetItemsWithPrices(pArrayItemDefs, pPrices, NULL, unArrayLength); -} - -bool GetItemPrice( SteamItemDef_t iDefinition, uint64 *pCurrentPrice, uint64 *pBasePrice ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -// Retrieves the price for the item definition id -// Returns false if there is no price stored for the item definition. -bool GetItemPrice( SteamItemDef_t iDefinition, uint64 *pPrice ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return GetItemPrice(iDefinition, pPrice, NULL); -} - - -// Create a request to update properties on items -SteamInventoryUpdateHandle_t StartUpdateProperties() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -// Remove the property on the item -bool RemoveProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -// Accessor methods to set properties on items -bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, const char *pchPropertyValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, bool bValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, int64 nValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, float flValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -// Submit the update request by handle -bool SubmitUpdateProperties( SteamInventoryUpdateHandle_t handle, SteamInventoryResult_t * pResultHandle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -bool InspectItem( SteamInventoryResult_t *pResultHandle, const char *pchItemToken ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -void RunCallbacks() -{ - if (call_definition_update || !inventory_requests.empty()) { - if (!item_definitions_loaded) { - read_items_db(); - item_definitions_loaded = true; - - //only gets called once - //also gets called when getting items - SteamInventoryDefinitionUpdate_t data = {}; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.05); - } - - call_definition_update = false; - } - - if (!inventory_requests.empty() && !inventory_loaded) { - read_inventory_db(); - inventory_loaded = true; - } - - if (inventory_loaded) - { - std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); - for (auto& r : inventory_requests) { - if (!r.done && std::chrono::duration_cast>(now - r.time_created).count() > r.timeout) { - if (r.full_query) { - // SteamInventoryFullUpdate_t callbacks are triggered when GetAllItems - // successfully returns a result which is newer / fresher than the last - // known result. - struct SteamInventoryFullUpdate_t data; - data.m_handle = r.inventory_result; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - - { - struct SteamInventoryResultReady_t data; - data.m_handle = r.inventory_result; - data.m_result = k_EResultOK; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - - r.done = true; - } - } - } -} + + // GrantPromoItems() checks the list of promotional items for which the user may be eligible + // and grants the items (one time only). On success, the result set will include items which + // were granted, if any. If no items were granted because the user isn't eligible for any + // promotions, this is still considered a success. + STEAM_METHOD_DESC(GrantPromoItems() checks the list of promotional items for which the user may be eligible and grants the items (one time only).) + bool GrantPromoItems( SteamInventoryResult_t *pResultHandle ); + + + // AddPromoItem() / AddPromoItems() are restricted versions of GrantPromoItems(). Instead of + // scanning for all eligible promotional items, the check is restricted to a single item + // definition or set of item definitions. This can be useful if your game has custom UI for + // showing a specific promo item to the user. + bool AddPromoItem( SteamInventoryResult_t *pResultHandle, SteamItemDef_t itemDef ); + + bool AddPromoItems( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, uint32 unArrayLength ); + + + // ConsumeItem() removes items from the inventory, permanently. They cannot be recovered. + // Not for the faint of heart - if your game implements item removal at all, a high-friction + // UI confirmation process is highly recommended. + STEAM_METHOD_DESC(ConsumeItem() removes items from the inventory permanently.) + bool ConsumeItem( SteamInventoryResult_t *pResultHandle, SteamItemInstanceID_t itemConsume, uint32 unQuantity ); + + + // ExchangeItems() is an atomic combination of item generation and consumption. + // It can be used to implement crafting recipes or transmutations, or items which unpack + // themselves into other items (e.g., a chest). + // Exchange recipes are defined in the ItemDef, and explicitly list the required item + // types and resulting generated type. + // Exchange recipes are evaluated atomically by the Inventory Service; if the supplied + // components do not match the recipe, or do not contain sufficient quantity, the + // exchange will fail. + bool ExchangeItems( SteamInventoryResult_t *pResultHandle, + STEAM_ARRAY_COUNT(unArrayGenerateLength) const SteamItemDef_t *pArrayGenerate, STEAM_ARRAY_COUNT(unArrayGenerateLength) const uint32 *punArrayGenerateQuantity, uint32 unArrayGenerateLength, + STEAM_ARRAY_COUNT(unArrayDestroyLength) const SteamItemInstanceID_t *pArrayDestroy, STEAM_ARRAY_COUNT(unArrayDestroyLength) const uint32 *punArrayDestroyQuantity, uint32 unArrayDestroyLength ); + + + + // TransferItemQuantity() is intended for use with items which are "stackable" (can have + // quantity greater than one). It can be used to split a stack into two, or to transfer + // quantity from one stack into another stack of identical items. To split one stack into + // two, pass k_SteamItemInstanceIDInvalid for itemIdDest and a new item will be generated. + bool TransferItemQuantity( SteamInventoryResult_t *pResultHandle, SteamItemInstanceID_t itemIdSource, uint32 unQuantity, SteamItemInstanceID_t itemIdDest ); + + + + // TIMED DROPS AND PLAYTIME CREDIT + // + + // Deprecated. Calling this method is not required for proper playtime accounting. + STEAM_METHOD_DESC( Deprecated method. Playtime accounting is performed on the Steam servers. ) + void SendItemDropHeartbeat(); + + + // Playtime credit must be consumed and turned into item drops by your game. Only item + // definitions which are marked as "playtime item generators" can be spawned. The call + // will return an empty result set if there is not enough playtime credit for a drop. + // Your game should call TriggerItemDrop at an appropriate time for the user to receive + // new items, such as between rounds or while the player is dead. Note that players who + // hack their clients could modify the value of "dropListDefinition", so do not use it + // to directly control rarity. + // See your Steamworks configuration to set playtime drop rates for individual itemdefs. + // The client library will suppress too-frequent calls to this method. + STEAM_METHOD_DESC(Playtime credit must be consumed and turned into item drops by your game.) + bool TriggerItemDrop( SteamInventoryResult_t *pResultHandle, SteamItemDef_t dropListDefinition ); + + + + // Deprecated. This method is not supported. + bool TradeItems( SteamInventoryResult_t *pResultHandle, CSteamID steamIDTradePartner, + STEAM_ARRAY_COUNT(nArrayGiveLength) const SteamItemInstanceID_t *pArrayGive, STEAM_ARRAY_COUNT(nArrayGiveLength) const uint32 *pArrayGiveQuantity, uint32 nArrayGiveLength, + STEAM_ARRAY_COUNT(nArrayGetLength) const SteamItemInstanceID_t *pArrayGet, STEAM_ARRAY_COUNT(nArrayGetLength) const uint32 *pArrayGetQuantity, uint32 nArrayGetLength ); + + + + // ITEM DEFINITIONS + // + // Item definitions are a mapping of "definition IDs" (integers between 1 and 1000000) + // to a set of string properties. Some of these properties are required to display items + // on the Steam community web site. Other properties can be defined by applications. + // Use of these functions is optional; there is no reason to call LoadItemDefinitions + // if your game hardcodes the numeric definition IDs (eg, purple face mask = 20, blue + // weapon mod = 55) and does not allow for adding new item types without a client patch. + // + + // LoadItemDefinitions triggers the automatic load and refresh of item definitions. + // Every time new item definitions are available (eg, from the dynamic addition of new + // item types while players are still in-game), a SteamInventoryDefinitionUpdate_t + // callback will be fired. + STEAM_METHOD_DESC(LoadItemDefinitions triggers the automatic load and refresh of item definitions.) + bool LoadItemDefinitions(); + + + // GetItemDefinitionIDs returns the set of all defined item definition IDs (which are + // defined via Steamworks configuration, and not necessarily contiguous integers). + // If pItemDefIDs is null, the call will return true and *punItemDefIDsArraySize will + // contain the total size necessary for a subsequent call. Otherwise, the call will + // return false if and only if there is not enough space in the output array. + bool GetItemDefinitionIDs( + STEAM_OUT_ARRAY_COUNT(punItemDefIDsArraySize,List of item definition IDs) SteamItemDef_t *pItemDefIDs, + STEAM_DESC(Size of array is passed in and actual size used is returned in this param) uint32 *punItemDefIDsArraySize ); + + + // GetItemDefinitionProperty returns a string property from a given item definition. + // Note that some properties (for example, "name") may be localized and will depend + // on the current Steam language settings (see ISteamApps::GetCurrentGameLanguage). + // Property names are always composed of ASCII letters, numbers, and/or underscores. + // Pass a NULL pointer for pchPropertyName to get a comma - separated list of available + // property names. If pchValueBuffer is NULL, *punValueBufferSize will contain the + // suggested buffer size. Otherwise it will be the number of bytes actually copied + // to pchValueBuffer. If the results do not fit in the given buffer, partial + // results may be copied. + bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPropertyName, + STEAM_OUT_STRING_COUNT(punValueBufferSizeOut) char *pchValueBuffer, uint32 *punValueBufferSizeOut ); + + + // Request the list of "eligible" promo items that can be manually granted to the given + // user. These are promo items of type "manual" that won't be granted automatically. + // An example usage of this is an item that becomes available every week. + STEAM_CALL_RESULT( SteamInventoryEligiblePromoItemDefIDs_t ) + SteamAPICall_t RequestEligiblePromoItemDefinitionsIDs( CSteamID steamID ); + + + // After handling a SteamInventoryEligiblePromoItemDefIDs_t call result, use this + // function to pull out the list of item definition ids that the user can be + // manually granted via the AddPromoItems() call. + bool GetEligiblePromoItemDefinitionIDs( + CSteamID steamID, + STEAM_OUT_ARRAY_COUNT(punItemDefIDsArraySize,List of item definition IDs) SteamItemDef_t *pItemDefIDs, + STEAM_DESC(Size of array is passed in and actual size used is returned in this param) uint32 *punItemDefIDsArraySize ); + + + // Starts the purchase process for the given item definitions. The callback SteamInventoryStartPurchaseResult_t + // will be posted if Steam was able to initialize the transaction. + // + // Once the purchase has been authorized and completed by the user, the callback SteamInventoryResultReady_t + // will be posted. + STEAM_CALL_RESULT( SteamInventoryStartPurchaseResult_t ) + SteamAPICall_t StartPurchase( STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, STEAM_ARRAY_COUNT(unArrayLength) const uint32 *punArrayQuantity, uint32 unArrayLength ); + + + // Request current prices for all applicable item definitions + STEAM_CALL_RESULT( SteamInventoryRequestPricesResult_t ) + SteamAPICall_t RequestPrices(); + + + // Returns the number of items with prices. Need to call RequestPrices() first. + uint32 GetNumItemsWithPrices(); + + bool GetItemsWithPrices( STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pArrayItemDefs, Items with prices) SteamItemDef_t *pArrayItemDefs, + STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pCurrentPrices, + STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pBasePrices, + uint32 unArrayLength ); + + // Returns item definition ids and their prices in the user's local currency. + // Need to call RequestPrices() first. + bool GetItemsWithPrices( STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pArrayItemDefs, Items with prices) SteamItemDef_t *pArrayItemDefs, + STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pPrices, + uint32 unArrayLength ); + + bool GetItemPrice( SteamItemDef_t iDefinition, uint64 *pCurrentPrice, uint64 *pBasePrice ); + + // Retrieves the price for the item definition id + // Returns false if there is no price stored for the item definition. + bool GetItemPrice( SteamItemDef_t iDefinition, uint64 *pPrice ); + + + // Create a request to update properties on items + SteamInventoryUpdateHandle_t StartUpdateProperties(); + + // Remove the property on the item + bool RemoveProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName ); + + // Accessor methods to set properties on items + bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, const char *pchPropertyValue ); + + bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, bool bValue ); + + bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, int64 nValue ); + + bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, float flValue ); + + // Submit the update request by handle + bool SubmitUpdateProperties( SteamInventoryUpdateHandle_t handle, SteamInventoryResult_t * pResultHandle ); + + bool InspectItem( SteamInventoryResult_t *pResultHandle, const char *pchItemToken ); + + void RunCallbacks(); }; + +#endif // __INCLUDED_STEAM_INVENTORY_H__ diff --git a/dll/dll/steam_masterserver_updater.h b/dll/dll/steam_masterserver_updater.h index 813d3373..c278ef29 100644 --- a/dll/dll/steam_masterserver_updater.h +++ b/dll/dll/steam_masterserver_updater.h @@ -15,219 +15,117 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_MASTERSERVER_UPDATER_H__ +#define __INCLUDED_STEAM_MASTERSERVER_UPDATER_H__ + #include "base.h" class Steam_Masterserver_Updater : public ISteamMasterServerUpdater { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; + + static void steam_callback(void *object, Common_Message *msg); + static void steam_run_every_runcb(void *object); + + void RunCallbacks(); + void Callback(Common_Message *msg); public: -static void steam_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); + Steam_Masterserver_Updater(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_Masterserver_Updater(); - Steam_Masterserver_Updater *steam_masterserverupdater = (Steam_Masterserver_Updater *)object; - steam_masterserverupdater->Callback(msg); -} - -static void steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Masterserver_Updater *steam_masterserverupdater = (Steam_Masterserver_Updater *)object; - steam_masterserverupdater->RunCallbacks(); -} - -Steam_Masterserver_Updater(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->run_every_runcb = run_every_runcb; - this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Masterserver_Updater::steam_callback, this); - this->run_every_runcb->add(&Steam_Masterserver_Updater::steam_run_every_runcb, this); - - this->callback_results = callback_results; - this->callbacks = callbacks; -} - -~Steam_Masterserver_Updater() -{ - this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Masterserver_Updater::steam_callback, this); - this->run_every_runcb->remove(&Steam_Masterserver_Updater::steam_run_every_runcb, this); -} - -// Call this as often as you like to tell the master server updater whether or not -// you want it to be active (default: off). -void SetActive( bool bActive ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // Call this as often as you like to tell the master server updater whether or not + // you want it to be active (default: off). + void SetActive( bool bActive ); -// You usually don't need to modify this. -// Pass -1 to use the default value for iHeartbeatInterval. -// Some mods change this. -void SetHeartbeatInterval( int iHeartbeatInterval ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // You usually don't need to modify this. + // Pass -1 to use the default value for iHeartbeatInterval. + // Some mods change this. + void SetHeartbeatInterval( int iHeartbeatInterval ); -// These are in GameSocketShare mode, where instead of ISteamMasterServerUpdater creating its own -// socket to talk to the master server on, it lets the game use its socket to forward messages -// back and forth. This prevents us from requiring server ops to open up yet another port -// in their firewalls. -// -// the IP address and port should be in host order, i.e 127.0.0.1 == 0x7f000001 + // These are in GameSocketShare mode, where instead of ISteamMasterServerUpdater creating its own + // socket to talk to the master server on, it lets the game use its socket to forward messages + // back and forth. This prevents us from requiring server ops to open up yet another port + // in their firewalls. + // + // the IP address and port should be in host order, i.e 127.0.0.1 == 0x7f000001 -// These are used when you've elected to multiplex the game server's UDP socket -// rather than having the master server updater use its own sockets. -// -// Source games use this to simplify the job of the server admins, so they -// don't have to open up more ports on their firewalls. + // These are used when you've elected to multiplex the game server's UDP socket + // rather than having the master server updater use its own sockets. + // + // Source games use this to simplify the job of the server admins, so they + // don't have to open up more ports on their firewalls. -// Call this when a packet that starts with 0xFFFFFFFF comes in. That means -// it's for us. -bool HandleIncomingPacket( const void *pData, int cbData, uint32 srcIP, uint16 srcPort ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return true; -} + // Call this when a packet that starts with 0xFFFFFFFF comes in. That means + // it's for us. + bool HandleIncomingPacket( const void *pData, int cbData, uint32 srcIP, uint16 srcPort ); -// AFTER calling HandleIncomingPacket for any packets that came in that frame, call this. -// This gets a packet that the master server updater needs to send out on UDP. -// It returns the length of the packet it wants to send, or 0 if there are no more packets to send. -// Call this each frame until it returns 0. -int GetNextOutgoingPacket( void *pOut, int cbMaxOut, uint32 *pNetAdr, uint16 *pPort ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} + // AFTER calling HandleIncomingPacket for any packets that came in that frame, call this. + // This gets a packet that the master server updater needs to send out on UDP. + // It returns the length of the packet it wants to send, or 0 if there are no more packets to send. + // Call this each frame until it returns 0. + int GetNextOutgoingPacket( void *pOut, int cbMaxOut, uint32 *pNetAdr, uint16 *pPort ); -// Functions to set various fields that are used to respond to queries. + // Functions to set various fields that are used to respond to queries. -// Call this to set basic data that is passed to the server browser. -void SetBasicServerData( - unsigned short nProtocolVersion, - bool bDedicatedServer, - const char *pRegionName, - const char *pProductName, - unsigned short nMaxReportedClients, - bool bPasswordProtected, - const char *pGameDescription ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // Call this to set basic data that is passed to the server browser. + void SetBasicServerData( + unsigned short nProtocolVersion, + bool bDedicatedServer, + const char *pRegionName, + const char *pProductName, + unsigned short nMaxReportedClients, + bool bPasswordProtected, + const char *pGameDescription ); -// Call this to clear the whole list of key/values that are sent in rules queries. -void ClearAllKeyValues() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // Call this to clear the whole list of key/values that are sent in rules queries. + void ClearAllKeyValues(); -// Call this to add/update a key/value pair. -void SetKeyValue( const char *pKey, const char *pValue ) -{ - PRINT_DEBUG("TODO '%s'='%s'", pKey, pValue); - std::lock_guard lock(global_mutex); -} + // Call this to add/update a key/value pair. + void SetKeyValue( const char *pKey, const char *pValue ); -// You can call this upon shutdown to clear out data stored for this game server and -// to tell the master servers that this server is going away. -void NotifyShutdown() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // You can call this upon shutdown to clear out data stored for this game server and + // to tell the master servers that this server is going away. + void NotifyShutdown(); -// Returns true if the master server has requested a restart. -// Only returns true once per request. -bool WasRestartRequested() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} + // Returns true if the master server has requested a restart. + // Only returns true once per request. + bool WasRestartRequested(); -// Force it to request a heartbeat from the master servers. -void ForceHeartbeat() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // Force it to request a heartbeat from the master servers. + void ForceHeartbeat(); -// Manually edit and query the master server list. -// It will provide name resolution and use the default master server port if none is provided. -bool AddMasterServer( const char *pServerAddress ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return true; -} + // Manually edit and query the master server list. + // It will provide name resolution and use the default master server port if none is provided. + bool AddMasterServer( const char *pServerAddress ); -bool RemoveMasterServer( const char *pServerAddress ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return true; -} + bool RemoveMasterServer( const char *pServerAddress ); -int GetNumMasterServers() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} + int GetNumMasterServers(); -// Returns the # of bytes written to pOut. -int GetMasterServerAddress( int iServer, char *pOut, int outBufferSize ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - - - -void RunCallbacks() -{ -} - -void Callback(Common_Message *msg) -{ - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - - } - - if (msg->low_level().type() == Low_Level::DISCONNECT) { - - } - } -} + // Returns the # of bytes written to pOut. + int GetMasterServerAddress( int iServer, char *pOut, int outBufferSize ); }; + +#endif // __INCLUDED_STEAM_MASTERSERVER_UPDATER_H__ diff --git a/dll/dll/steam_matchmaking.h b/dll/dll/steam_matchmaking.h index 629d8f2d..d206a4b3 100644 --- a/dll/dll/steam_matchmaking.h +++ b/dll/dll/steam_matchmaking.h @@ -15,16 +15,11 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_MATCHMAKING_H__ +#define __INCLUDED_STEAM_MATCHMAKING_H__ + #include "base.h" -#define SEND_LOBBY_RATE 5.0 - -#define PENDING_JOIN_TIMEOUT 10.0 -#define REQUEST_LOBBY_DATA_TIMEOUT 6.0 -#define LOBBY_DELETED_TIMEOUT 2 - -#define LOBBY_CREATE_DELAY 0.07 //artificial delay for lobby creation - struct Pending_Joins { SteamAPICall_t api_id{}; @@ -59,8 +54,6 @@ struct Chat_Entry { CSteamID lobby_id, user_id{}; }; -#define FILTER_MAX_DEFAULT 4096 - class Steam_Matchmaking : public ISteamMatchmaking002, @@ -98,1596 +91,292 @@ public ISteamMatchmaking std::map> self_lobby_member_data{}; -google::protobuf::Map::const_iterator caseinsensitive_find(const ::google::protobuf::Map< ::std::string, ::std::string >& map, std::string key) -{ - auto x = map.begin(); - while (x != map.end()) { - if (common_helpers::str_cmp_insensitive(key, x->first)) { - break; - } - ++x; - } + google::protobuf::Map::const_iterator caseinsensitive_find(const ::google::protobuf::Map< ::std::string, ::std::string >& map, std::string key); - return x; -} + static Lobby_Member *get_lobby_member(Lobby *lobby, CSteamID user_id); + static bool add_member_to_lobby(Lobby *lobby, CSteamID id); + static bool leave_lobby(Lobby *lobby, CSteamID id); -Lobby *get_lobby(CSteamID id) -{ - if (!id.IsLobby()) - return NULL; + Lobby *get_lobby(CSteamID id); + void send_lobby_data(); - auto lobby = std::find_if(lobbies.begin(), lobbies.end(), [&id](Lobby const& item) { return (item.room_id() & 0xFFFFFFFF) == (id.GetAccountID()); }); - if (lobbies.end() == lobby) - return NULL; + void trigger_lobby_dataupdate(CSteamID lobby, CSteamID member, bool success, double cb_timeout=0.005, bool send_changed_lobby=true); + void trigger_lobby_member_join_leave(CSteamID lobby, CSteamID member, bool leaving, bool success, double cb_timeout=0.0); - return &(*lobby); -} + bool send_owner_packet(CSteamID lobby_id, Lobby_Messages *message); + bool send_clients_packet(CSteamID lobby_id, Lobby_Messages *message); + bool send_lobby_members_packet(CSteamID lobby_id, Lobby_Messages *message); -void send_lobby_data() -{ - PRINT_DEBUG("lobbies %zu", lobbies.size()); + bool change_owner(Lobby *lobby, CSteamID new_owner); - for(auto & l: lobbies) { - if (get_lobby_member(&l, settings->get_local_steam_id()) && l.owner() == settings->get_local_steam_id().ConvertToUint64() && !l.deleted()) { - PRINT_DEBUG("lobby " "%" PRIu64 "", l.room_id()); - Common_Message msg = Common_Message(); - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_allocated_lobby(new Lobby(l)); - network->sendToAllIndividuals(&msg, true); - } - } -} + void send_gameservercreated_cb(uint64 room_id, uint64 server_id, uint32 ip, uint16 port); -void trigger_lobby_dataupdate(CSteamID lobby, CSteamID member, bool success, double cb_timeout=0.005, bool send_changed_lobby=true) -{ - PRINT_DEBUG("%llu %llu", lobby.ConvertToUint64(), member.ConvertToUint64()); - LobbyDataUpdate_t data{}; - data.m_ulSteamIDLobby = lobby.ConvertToUint64(); - data.m_bSuccess = success; - data.m_ulSteamIDMember = member.ConvertToUint64(); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), cb_timeout, true); + void remove_lobbies(); + void on_self_enter_leave_lobby(CSteamID id, int type, bool leaving); - // if this was a user data update, then trigger another callback for the lobby itself - if (lobby != member) { - data.m_ulSteamIDMember = lobby.ConvertToUint64(); - //Is this really necessary? - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), cb_timeout, true); - } + void create_pending_lobbies(); + void run_background(); + void RunCallbacks(); + void Callback(Common_Message *msg); - Lobby *l = get_lobby(lobby); - if (l && l->owner() == settings->get_local_steam_id().ConvertToUint64()) { - if (send_changed_lobby) { - PRINT_DEBUG("resending new data"); - Common_Message msg = Common_Message(); - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_allocated_lobby(new Lobby(*l)); - network->sendToAllIndividuals(&msg, true); - } - } -} - -void trigger_lobby_member_join_leave(CSteamID lobby, CSteamID member, bool leaving, bool success, double cb_timeout=0.0) -{ - LobbyChatUpdate_t data{}; - data.m_ulSteamIDLobby = lobby.ConvertToUint64(); - data.m_ulSteamIDUserChanged = member.ConvertToUint64(); - data.m_ulSteamIDMakingChange = member.ConvertToUint64(); - uint32 member_state_change = 0; //EChatMemberStateChange - - if (!leaving) { - member_state_change |= k_EChatMemberStateChangeEntered; - } else { - member_state_change |= k_EChatMemberStateChangeLeft; - } - - data.m_rgfChatMemberStateChange = member_state_change; - - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), cb_timeout); - // trigger_lobby_dataupdate(lobby, member, success, cb_timeout); - trigger_lobby_dataupdate(lobby, lobby, success, cb_timeout); -} - -bool send_owner_packet(CSteamID lobby_id, Lobby_Messages *message) -{ - Lobby *lobby = get_lobby(lobby_id); - - if (!lobby) { - return false; - } - - Common_Message msg; - msg.set_allocated_lobby_messages(message); - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_dest_id((uint64)lobby->owner()); - msg.mutable_lobby_messages()->set_id(lobby_id.ConvertToUint64()); - return network->sendTo(&msg, true); -} - -bool send_clients_packet(CSteamID lobby_id, Lobby_Messages *message) -{ - Lobby *lobby = get_lobby(lobby_id); - - if (!lobby) { - return false; - } - - Common_Message msg; - msg.set_allocated_lobby_messages(message); - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.mutable_lobby_messages()->set_id(lobby_id.ConvertToUint64()); - return network->sendToAllIndividuals(&msg, true); -} - -bool send_lobby_members_packet(CSteamID lobby_id, Lobby_Messages *message) -{ - Lobby *lobby = get_lobby(lobby_id); - - if (!lobby) { - return false; - } - - Common_Message msg; - msg.set_allocated_lobby_messages(message); - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.mutable_lobby_messages()->set_id(lobby_id.ConvertToUint64()); - - for (auto & m : lobby->members()) { - msg.set_dest_id((uint64)m.id()); - network->sendTo(&msg, true); - } - - return true; -} - -bool change_owner(Lobby *lobby, CSteamID new_owner) -{ - Lobby_Messages *message = new Lobby_Messages(); - message->set_type(Lobby_Messages::CHANGE_OWNER); - message->set_idata(new_owner.ConvertToUint64()); - lobby->set_owner(new_owner.ConvertToUint64()); - send_owner_packet((uint64)lobby->room_id(), message); - trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)lobby->room_id(), true); - return true; -} - -void send_gameservercreated_cb(uint64 room_id, uint64 server_id, uint32 ip, uint16 port) -{ - LobbyGameCreated_t data; - data.m_ulSteamIDLobby = room_id; - data.m_ulSteamIDGameServer = server_id; - data.m_unIP = ip; - data.m_usPort = port; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); -} - -void remove_lobbies() -{ - uint64 current_time = std::chrono::duration_cast>(std::chrono::system_clock::now().time_since_epoch()).count(); - auto g = std::begin(lobbies); - while (g != std::end(lobbies)) { - if (g->members().size() == 0 || (g->deleted() && (g->time_deleted() + LOBBY_DELETED_TIMEOUT < current_time))) { - PRINT_DEBUG("LOBBY " "%" PRIu64 "", g->room_id()); - self_lobby_member_data.erase(g->room_id()); - g = lobbies.erase(g); - } else { - ++g; - } - } -} - -void on_self_enter_leave_lobby(CSteamID id, int type, bool leaving) -{ - if (type == k_ELobbyTypeInvisible) return; - - if (!leaving) { - settings->set_lobby(id); - } else { - settings->set_lobby(k_steamIDNil); - } - - //TODO: handle cases where in two lobbies of type not invisible - //steam says a user can only be in one regular lobby but we all know how well documented steam is -} + static void steam_matchmaking_callback(void *object, Common_Message *msg); + static void steam_matchmaking_run_every_runcb(void *object); public: -static void steam_matchmaking_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); + Steam_Matchmaking(class Settings *settings, class Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_Matchmaking(); - Steam_Matchmaking *steam_matchmaking = (Steam_Matchmaking *)object; - steam_matchmaking->Callback(msg); -} + // game server favorites storage + // saves basic details about a multiplayer game server locally -static void steam_matchmaking_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Matchmaking *steam_matchmaking = (Steam_Matchmaking *)object; - steam_matchmaking->RunCallbacks(); -} - -Steam_Matchmaking(class Settings *settings, class Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) -{ - this->settings = settings; - this->local_storage = local_storage; - this->network = network; - this->run_every_runcb = run_every_runcb; - this->network->setCallback(CALLBACK_ID_LOBBY, settings->get_local_steam_id(), &Steam_Matchmaking::steam_matchmaking_callback, this); - this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Matchmaking::steam_matchmaking_callback, this); - this->run_every_runcb->add(&Steam_Matchmaking::steam_matchmaking_run_every_runcb, this); - - this->callback_results = callback_results; - this->callbacks = callbacks; - this->filter_max_results = FILTER_MAX_DEFAULT; - search_call_api_id = 0; - searching = false; -} - -~Steam_Matchmaking() -{ - this->network->rmCallback(CALLBACK_ID_LOBBY, settings->get_local_steam_id(), &Steam_Matchmaking::steam_matchmaking_callback, this); - this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Matchmaking::steam_matchmaking_callback, this); - this->run_every_runcb->remove(&Steam_Matchmaking::steam_matchmaking_run_every_runcb, this); -} - -static Lobby_Member *get_lobby_member(Lobby *lobby, CSteamID user_id) -{ - if (!lobby) return NULL; - - auto member = std::find_if(lobby->mutable_members()->begin(), lobby->mutable_members()->end(), [&user_id](Lobby_Member const& item) { return item.id() == user_id.ConvertToUint64(); }); - if (lobby->mutable_members()->end() == member) - return NULL; - - return &(*member); -} - -// game server favorites storage -// saves basic details about a multiplayer game server locally - -// returns the number of favorites servers the user has stored -int GetFavoriteGameCount() -{ - PRINT_DEBUG_ENTRY(); - std::string file_path = local_storage->get_current_save_directory() + "7" + PATH_SEPARATOR + Local_Storage::remote_storage_folder + PATH_SEPARATOR + "serverbrowser_favorites.txt"; - unsigned long long file_size = file_size_(file_path); - if (file_size) { - std::string list{}; - list.resize(file_size); - Local_Storage::get_file_data(file_path, (char *)&list[0], file_size, 0); - auto list_lines = std::count(list.begin(), list.end(), '\n'); - list_lines += (!list.empty() && list.back() != '\n'); - return list_lines; - } - return 0; -} + // returns the number of favorites servers the user has stored + int GetFavoriteGameCount(); -// returns the details of the game server -// iGame is of range [0,GetFavoriteGameCount()) -// *pnIP, *pnConnPort are filled in the with IP:port of the game server -// *punFlags specify whether the game server was stored as an explicit favorite or in the history of connections -// *pRTime32LastPlayedOnServer is filled in the with the Unix time the favorite was added -bool GetFavoriteGame( int iGame, AppId_t *pnAppID, uint32 *pnIP, uint16 *pnConnPort, uint16 *pnQueryPort, uint32 *punFlags, uint32 *pRTime32LastPlayedOnServer ) -{ - PRINT_DEBUG_ENTRY(); - return false; -} + // returns the details of the game server + // iGame is of range [0,GetFavoriteGameCount()) + // *pnIP, *pnConnPort are filled in the with IP:port of the game server + // *punFlags specify whether the game server was stored as an explicit favorite or in the history of connections + // *pRTime32LastPlayedOnServer is filled in the with the Unix time the favorite was added + bool GetFavoriteGame( int iGame, AppId_t *pnAppID, uint32 *pnIP, uint16 *pnConnPort, uint16 *pnQueryPort, uint32 *punFlags, uint32 *pRTime32LastPlayedOnServer ); -// adds the game server to the local list; updates the time played of the server if it already exists in the list -int AddFavoriteGame( AppId_t nAppID, uint32 nIP, uint16 nConnPort, uint16 nQueryPort, uint32 unFlags, uint32 rTime32LastPlayedOnServer ) -{ - PRINT_DEBUG("%u %u %hu %hu %u %u", nAppID, nIP, nConnPort, nQueryPort, unFlags, rTime32LastPlayedOnServer); - - std::string file_path; - unsigned long long file_size; - - if (unFlags == 1) { - file_path = local_storage->get_current_save_directory() + "7" + PATH_SEPARATOR + Local_Storage::remote_storage_folder + PATH_SEPARATOR + "serverbrowser_favorites.txt"; - file_size = file_size_(file_path); - } - else if (unFlags == 2) { - file_path = local_storage->get_current_save_directory() + "7" + PATH_SEPARATOR + Local_Storage::remote_storage_folder + PATH_SEPARATOR + "serverbrowser_history.txt"; - file_size = file_size_(file_path); - } - else { - return 0; - } - - unsigned char ip[4]; - ip[0] = nIP & 0xFF; - ip[1] = (nIP >> 8) & 0xFF; - ip[2] = (nIP >> 16) & 0xFF; - ip[3] = (nIP >> 24) & 0xFF; - char newip[24]; - snprintf(newip, sizeof(newip), "%d.%d.%d.%d:%d\n", ip[3], ip[2], ip[1], ip[0], nConnPort); - std::string newip_string; - newip_string.append(newip); - - if (file_size) { - std::string list{}; - list.resize(file_size); - Local_Storage::get_file_data(file_path, (char *)&list[0], file_size, 0); - auto list_lines = std::count(list.begin(), list.end(), '\n'); - list_lines += (!list.empty() && list.back() != '\n'); - - std::size_t find_ip = list.find(newip_string); - if (find_ip == std::string::npos) { - list.append(newip_string); - list.append("\n"); - - std::size_t file_directory = file_path.rfind("/"); - std::string directory_path; - std::string file_name; - if (file_directory != std::string::npos) { - directory_path = file_path.substr(0, file_directory); - file_name = file_path.substr(file_directory); - } - Local_Storage::store_file_data(directory_path, file_name, (char *)list.data(), list.size()); - - return ++list_lines; - } - - return list_lines; - } - else { - newip_string.append("\n"); - - std::size_t file_directory = file_path.rfind("/"); - std::string directory_path; - std::string file_name; - if (file_directory != std::string::npos) { - directory_path = file_path.substr(0, file_directory); - file_name = file_path.substr(file_directory); - } - Local_Storage::store_file_data(directory_path, file_name, (char *)newip_string.data(), newip_string.size()); - - return 1; - } -} + // adds the game server to the local list; updates the time played of the server if it already exists in the list + int AddFavoriteGame( AppId_t nAppID, uint32 nIP, uint16 nConnPort, uint16 nQueryPort, uint32 unFlags, uint32 rTime32LastPlayedOnServer ); -// removes the game server from the local storage; returns true if one was removed -bool RemoveFavoriteGame( AppId_t nAppID, uint32 nIP, uint16 nConnPort, uint16 nQueryPort, uint32 unFlags ) -{ - PRINT_DEBUG_ENTRY(); - - std::string file_path; - unsigned long long file_size; - - if (unFlags == 1) { - file_path = local_storage->get_current_save_directory() + "7" + PATH_SEPARATOR + Local_Storage::remote_storage_folder + "serverbrowser_favorites.txt"; - file_size = file_size_(file_path); - } - else if (unFlags == 2) { - file_path = local_storage->get_current_save_directory() + "7" + PATH_SEPARATOR + Local_Storage::remote_storage_folder + "serverbrowser_history.txt"; - file_size = file_size_(file_path); - } - else { - return false; - } - - if (file_size) { - std::string list{}; - list.resize(file_size); - Local_Storage::get_file_data(file_path, (char *)&list[0], file_size, 0); - - unsigned char ip[4]; - ip[0] = nIP & 0xFF; - ip[1] = (nIP >> 8) & 0xFF; - ip[2] = (nIP >> 16) & 0xFF; - ip[3] = (nIP >> 24) & 0xFF; - char newip[24]; - snprintf((char *)newip, sizeof(newip), "%d.%d.%d.%d:%d\n", ip[3], ip[2], ip[1], ip[0], nConnPort); - std::string newip_string; - newip_string.append(newip); - - std::size_t list_ip = list.find(newip_string); - if (list_ip != std::string::npos) { - list.erase(list_ip, newip_string.length()); - - std::size_t file_directory = file_path.rfind("/"); - std::string directory_path; - std::string file_name; - if (file_directory != std::string::npos) { - directory_path = file_path.substr(0, file_directory); - file_name = file_path.substr(file_directory); - } - Local_Storage::store_file_data(directory_path, file_name, (char *)list.data(), list.size()); - - return true; - } - } - - return false; -} + // removes the game server from the local storage; returns true if one was removed + bool RemoveFavoriteGame( AppId_t nAppID, uint32 nIP, uint16 nConnPort, uint16 nQueryPort, uint32 unFlags ); -/////// -// Game lobby functions + /////// + // Game lobby functions -// Get a list of relevant lobbies -// this is an asynchronous request -// results will be returned by LobbyMatchList_t callback & call result, with the number of lobbies found -// this will never return lobbies that are full -// to add more filter, the filter calls below need to be call before each and every RequestLobbyList() call -// use the CCallResult<> object in steam_api.h to match the SteamAPICall_t call result to a function in an object, e.g. -/* - class CMyLobbyListManager - { - CCallResult m_CallResultLobbyMatchList; - void FindLobbies() + // Get a list of relevant lobbies + // this is an asynchronous request + // results will be returned by LobbyMatchList_t callback & call result, with the number of lobbies found + // this will never return lobbies that are full + // to add more filter, the filter calls below need to be call before each and every RequestLobbyList() call + // use the CCallResult<> object in steam_api.h to match the SteamAPICall_t call result to a function in an object, e.g. + /* + class CMyLobbyListManager { - // SteamMatchmaking()->AddRequestLobbyListFilter*() functions would be called here, before RequestLobbyList(); + CCallResult m_CallResultLobbyMatchList; + void FindLobbies() + { + // SteamMatchmaking()->AddRequestLobbyListFilter*() functions would be called here, before RequestLobbyList(); - m_CallResultLobbyMatchList.Set( hSteamAPICall, this, &CMyLobbyListManager::OnLobbyMatchList ); + m_CallResultLobbyMatchList.Set( hSteamAPICall, this, &CMyLobbyListManager::OnLobbyMatchList ); - } - - void OnLobbyMatchList( LobbyMatchList_t *pLobbyMatchList, bool bIOFailure ) - { - // lobby list has be retrieved from Steam back-end, use results - } - } -*/ -// -#define LOBBY_SEARCH_TIMEOUT 0.2 //Tested on real steam -STEAM_CALL_RESULT( LobbyMatchList_t ) -SteamAPICall_t RequestLobbyList() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - filtered_lobbies.clear(); - lobby_last_search = std::chrono::high_resolution_clock::now(); - filter_values_copy = filter_values; - filter_max_results_copy = filter_max_results; - filter_values.clear(); - filter_max_results = FILTER_MAX_DEFAULT; - searching = true; - if (search_call_api_id) callback_results->rmCallBack(search_call_api_id, NULL); - search_call_api_id = callback_results->reserveCallResult(); - - return search_call_api_id; -} - -void RequestLobbyList_OLD() -{ - RequestLobbyList(); -} - -// filters for lobbies -// this needs to be called before RequestLobbyList() to take effect -// these are cleared on each call to RequestLobbyList() -void AddRequestLobbyListStringFilter( const char *pchKeyToMatch, const char *pchValueToMatch, ELobbyComparison eComparisonType ) -{ - PRINT_DEBUG("'%s'=='%s' %i", pchKeyToMatch, pchValueToMatch, eComparisonType); - if (!pchValueToMatch) return; - - std::lock_guard lock(global_mutex); - struct Filter_Values fv; - fv.key = std::string(pchKeyToMatch); - fv.value_string = std::string(pchValueToMatch); - fv.is_int = false; - fv.eComparisonType = eComparisonType; - filter_values.push_back(fv); - -} - -// numerical comparison -void AddRequestLobbyListNumericalFilter( const char *pchKeyToMatch, int nValueToMatch, ELobbyComparison eComparisonType ) -{ - PRINT_DEBUG("'%s'==%i %i", pchKeyToMatch, nValueToMatch, eComparisonType); - std::lock_guard lock(global_mutex); - struct Filter_Values fv; - fv.key = std::string(pchKeyToMatch); - fv.value_int = nValueToMatch; - fv.is_int = true; - fv.eComparisonType = eComparisonType; - filter_values.push_back(fv); - -} - -// returns results closest to the specified value. Multiple near filters can be added, with early filters taking precedence -void AddRequestLobbyListNearValueFilter( const char *pchKeyToMatch, int nValueToBeCloseTo ) -{ - PRINT_DEBUG("'%s'==%u", pchKeyToMatch, nValueToBeCloseTo); - std::lock_guard lock(global_mutex); - - -} - -// returns only lobbies with the specified number of slots available -void AddRequestLobbyListFilterSlotsAvailable( int nSlotsAvailable ) -{ - PRINT_DEBUG("%i", nSlotsAvailable); - std::lock_guard lock(global_mutex); - - -} - -// sets the distance for which we should search for lobbies (based on users IP address to location map on the Steam backed) -void AddRequestLobbyListDistanceFilter( ELobbyDistanceFilter eLobbyDistanceFilter ) -{ - PRINT_DEBUG("%i", eLobbyDistanceFilter); - std::lock_guard lock(global_mutex); - - -} - -// sets how many results to return, the lower the count the faster it is to download the lobby results & details to the client -void AddRequestLobbyListResultCountFilter( int cMaxResults ) -{ - PRINT_DEBUG("%i", cMaxResults); - std::lock_guard lock(global_mutex); - filter_max_results = cMaxResults; - -} - - -void AddRequestLobbyListCompatibleMembersFilter( CSteamID steamIDLobby ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - -} - -void AddRequestLobbyListFilter( const char *pchKeyToMatch, const char *pchValueToMatch ) -{ - AddRequestLobbyListStringFilter(pchKeyToMatch, pchValueToMatch, k_ELobbyComparisonEqual); -} - -void AddRequestLobbyListNumericalFilter( const char *pchKeyToMatch, int nValueToMatch, int nComparisonType ) -{ - AddRequestLobbyListNumericalFilter(pchKeyToMatch, nValueToMatch, (ELobbyComparison) nComparisonType ); -} - -void AddRequestLobbyListSlotsAvailableFilter() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - -} - -// returns the CSteamID of a lobby, as retrieved by a RequestLobbyList call -// should only be called after a LobbyMatchList_t callback is received -// iLobby is of the range [0, LobbyMatchList_t::m_nLobbiesMatching) -// the returned CSteamID::IsValid() will be false if iLobby is out of range -CSteamID GetLobbyByIndex( int iLobby ) -{ - PRINT_DEBUG("%i", iLobby); - std::lock_guard lock(global_mutex); - CSteamID id = k_steamIDNil; - if (0 <= iLobby && iLobby < filtered_lobbies.size()) id = filtered_lobbies[iLobby]; - PRINT_DEBUG("found lobby %llu", id.ConvertToUint64()); - return id; -} - -static bool add_member_to_lobby(Lobby *lobby, CSteamID id) -{ - if (get_lobby_member(lobby, id)) return false; // player already exists - - Lobby_Member *member = lobby->add_members(); - member->set_id(id.ConvertToUint64()); - PRINT_DEBUG("added lobby member %llu", (uint64)id.ConvertToUint64()); - return true; -} - -static bool leave_lobby(Lobby *lobby, CSteamID id) -{ - auto member = std::find_if(lobby->mutable_members()->begin(), lobby->mutable_members()->end(), [&id](Lobby_Member const& item) { return item.id() == id.ConvertToUint64(); }); - if (member != lobby->mutable_members()->end()) { - lobby->mutable_members()->erase(member); - return true; - } - - return false; -} - - -void Create_pending_lobbies() -{ - auto p_c = std::begin(pending_creates); - while (p_c != std::end(pending_creates)) { - if (check_timedout(p_c->created, LOBBY_CREATE_DELAY)) { - Lobby lobby{}; - CSteamID lobby_id = generate_steam_id_lobby(); - lobby.set_room_id(lobby_id.ConvertToUint64()); - lobby.set_joinable(true); - lobby.set_member_limit(p_c->cMaxMembers); - lobby.set_type(p_c->eLobbyType); - lobby.set_owner(settings->get_local_steam_id().ConvertToUint64()); - lobby.set_appid(settings->get_local_game_id().AppID()); - add_member_to_lobby(&lobby, settings->get_local_steam_id()); - lobbies.push_back(lobby); - - if (settings->disable_lobby_creation) { - LobbyCreated_t data; - data.m_eResult = k_EResultFail; - data.m_ulSteamIDLobby = 0; - callback_results->addCallResult(p_c->api_id, data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } else { - LobbyCreated_t data; - data.m_eResult = k_EResultOK; - data.m_ulSteamIDLobby = lobby.room_id(); - callback_results->addCallResult(p_c->api_id, data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - - { - LobbyEnter_t data2{}; - data2.m_ulSteamIDLobby = lobby.room_id(); - data2.m_rgfChatPermissions = 0; //Unused - Always 0 - if (p_c->eLobbyType == k_ELobbyTypePrivate) - data2.m_bLocked = true; - else - data2.m_bLocked = false; - data2.m_EChatRoomEnterResponse = k_EChatRoomEnterResponseSuccess; - callbacks->addCBResult(data2.k_iCallback, &data2, sizeof(data2)); - } - - on_self_enter_leave_lobby(lobby_id, p_c->eLobbyType, false); - trigger_lobby_dataupdate(lobby_id, lobby_id, true); } - p_c = pending_creates.erase(p_c); - } else { - ++p_c; - } - } -} - -// Create a lobby on the Steam servers. -// If private, then the lobby will not be returned by any RequestLobbyList() call; the CSteamID -// of the lobby will need to be communicated via game channels or via InviteUserToLobby() -// this is an asynchronous request -// results will be returned by LobbyCreated_t callback and call result; lobby is joined & ready to use at this point -// a LobbyEnter_t callback will also be received (since the local user is joining their own lobby) -STEAM_CALL_RESULT( LobbyCreated_t ) -SteamAPICall_t CreateLobby( ELobbyType eLobbyType, int cMaxMembers ) -{ - PRINT_DEBUG("type: %i max_members: %i", eLobbyType, cMaxMembers); - std::lock_guard lock(global_mutex); - struct Pending_Creates p_c{}; - p_c.api_id = callback_results->reserveCallResult(); - p_c.eLobbyType = eLobbyType; - p_c.cMaxMembers = cMaxMembers; - p_c.created = std::chrono::high_resolution_clock::now(); - pending_creates.push_back(p_c); - return p_c.api_id; -} - -SteamAPICall_t CreateLobby( ELobbyType eLobbyType ) -{ - PRINT_DEBUG("old"); - return CreateLobby(eLobbyType, 0); -} - -void CreateLobby_OLD( ELobbyType eLobbyType ) -{ - CreateLobby(eLobbyType); -} - -void CreateLobby( bool bPrivate ) -{ - CreateLobby(bPrivate ? k_ELobbyTypePrivate : k_ELobbyTypePublic); -} - -// Joins an existing lobby -// this is an asynchronous request -// results will be returned by LobbyEnter_t callback & call result, check m_EChatRoomEnterResponse to see if was successful -// lobby metadata is available to use immediately on this call completing -STEAM_CALL_RESULT( LobbyEnter_t ) -SteamAPICall_t JoinLobby( CSteamID steamIDLobby ) -{ - PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); - std::lock_guard lock(global_mutex); - - auto pj = std::find_if(pending_joins.begin(), pending_joins.end(), [&steamIDLobby](Pending_Joins const& item) {return item.lobby_id == steamIDLobby;}); - if (pj != pending_joins.end()) { - PRINT_DEBUG("already found in pending joins list"); - return pj->api_id; - } - - Pending_Joins pending_join{}; - pending_join.api_id = callback_results->reserveCallResult(); - pending_join.lobby_id = steamIDLobby; - pending_join.joined = std::chrono::high_resolution_clock::now(); - pending_joins.push_back(pending_join); - - Lobby_Messages *message = new Lobby_Messages(); - message->set_type(Lobby_Messages::JOIN); - pending_join.message_sent = send_owner_packet(steamIDLobby, message); - - PRINT_DEBUG("added new entry to pending joins"); - return pending_join.api_id; -} - -void JoinLobby_OLD( CSteamID steamIDLobby ) -{ - JoinLobby(steamIDLobby); -} - -// Leave a lobby; this will take effect immediately on the client side -// other users in the lobby will be notified by a LobbyChatUpdate_t callback -void LeaveLobby( CSteamID steamIDLobby ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - PRINT_DEBUG("pass mutex"); - Lobby *lobby = get_lobby(steamIDLobby); - if (lobby) { - if (!lobby->deleted()) { - on_self_enter_leave_lobby((uint64)lobby->room_id(), lobby->type(), true); - self_lobby_member_data.erase(lobby->room_id()); - if (lobby->owner() != settings->get_local_steam_id().ConvertToUint64()) { - PRINT_DEBUG("not owner"); - leave_lobby(&(*lobby), settings->get_local_steam_id()); - Lobby_Messages *message = new Lobby_Messages(); - message->set_type(Lobby_Messages::LEAVE); - send_owner_packet(steamIDLobby, message); - } else { - PRINT_DEBUG("owner"); - Lobby_Messages *message = new Lobby_Messages(); - message->set_type(Lobby_Messages::LEAVE); - - if (lobby->members().size() > 1) { - leave_lobby(&(*lobby), settings->get_local_steam_id()); - change_owner(&(*lobby), (uint64)lobby->members(0).id()); - send_owner_packet(steamIDLobby, message); - } else { - send_clients_packet(steamIDLobby, message); - lobby->set_deleted(true); - lobby->set_time_deleted(std::chrono::duration_cast>(std::chrono::system_clock::now().time_since_epoch()).count()); - } + void OnLobbyMatchList( LobbyMatchList_t *pLobbyMatchList, bool bIOFailure ) + { + // lobby list has be retrieved from Steam back-end, use results } } - } - - PRINT_DEBUG("Done"); - -} - - -// Invite another user to the lobby -// the target user will receive a LobbyInvite_t callback -// will return true if the invite is successfully sent, whether or not the target responds -// returns false if the local user is not connected to the Steam servers -// if the other user clicks the join link, a GameLobbyJoinRequested_t will be posted if the user is in-game, -// or if the game isn't running yet the game will be launched with the parameter +connect_lobby <64-bit lobby id> -bool InviteUserToLobby( CSteamID steamIDLobby, CSteamID steamIDInvitee ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - if (!lobby) return false; - - Common_Message msg; - Friend_Messages *friend_messages = new Friend_Messages(); - friend_messages->set_type(Friend_Messages::LOBBY_INVITE); - friend_messages->set_lobby_id(steamIDLobby.ConvertToUint64()); - msg.set_allocated_friend_messages(friend_messages); - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_dest_id(steamIDInvitee.ConvertToUint64()); - return network->sendTo(&msg, true); -} - - -// Lobby iteration, for viewing details of users in a lobby -// only accessible if the lobby user is a member of the specified lobby -// persona information for other lobby members (name, avatar, etc.) will be asynchronously received -// and accessible via ISteamFriends interface - -// returns the number of users in the specified lobby -int GetNumLobbyMembers( CSteamID steamIDLobby ) -{ - PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - int ret = 0; - if (lobby) ret = lobby->members().size(); - - PRINT_DEBUG("count=%i", ret); - return ret; -} - -// returns the CSteamID of a user in the lobby -// iMember is of range [0,GetNumLobbyMembers()) -// note that the current user must be in a lobby to retrieve CSteamIDs of other users in that lobby -CSteamID GetLobbyMemberByIndex( CSteamID steamIDLobby, int iMember ) -{ - PRINT_DEBUG("%llu %i", steamIDLobby.ConvertToUint64(), iMember); - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - CSteamID id = k_steamIDNil; - if (lobby && !lobby->deleted() && lobby->members().size() > iMember && iMember >= 0) id = (uint64)lobby->members(iMember).id(); - PRINT_DEBUG("found member: %llu", id.ConvertToUint64()); - return id; -} - - -// Get data associated with this lobby -// takes a simple key, and returns the string associated with it -// "" will be returned if no value is set, or if steamIDLobby is invalid -const char *GetLobbyData( CSteamID steamIDLobby, const char *pchKey ) -{ - PRINT_DEBUG("%llu '%s'", steamIDLobby.ConvertToUint64(), pchKey); - std::lock_guard lock(global_mutex); - if (!pchKey) return ""; - - Lobby *lobby = get_lobby(steamIDLobby); - const char *ret = ""; - if (lobby) { - auto result = caseinsensitive_find(lobby->values(), pchKey); - if (result != lobby->values().end()) ret = result->second.c_str(); - } - - PRINT_DEBUG("returned '%s'", ret); - return ret; -} - -// Sets a key/value pair in the lobby metadata -// each user in the lobby will be broadcast this new value, and any new users joining will receive any existing data -// this can be used to set lobby names, map, etc. -// to reset a key, just set it to "" -// other users in the lobby will receive notification of the lobby data change via a LobbyDataUpdate_t callback -bool SetLobbyData( CSteamID steamIDLobby, const char *pchKey, const char *pchValue ) -{ - PRINT_DEBUG("[%llu] '%s'='%s'", steamIDLobby.ConvertToUint64(), pchKey, pchValue); - std::lock_guard lock(global_mutex); - if (!pchKey) return false; - if (!pchValue) pchValue = ""; - - Lobby *lobby = get_lobby(steamIDLobby); - if (!lobby || lobby->deleted()) { - return false; - } - - bool changed = true; - //callback is always triggered when setlobbydata is called from non owner however no data is actually changed. - if (lobby->owner() == settings->get_local_steam_id().ConvertToUint64()) { - auto result = caseinsensitive_find(lobby->values(), pchKey); - if (result == lobby->values().end()) { - (*lobby->mutable_values())[pchKey] = pchValue; - } else { - if (result->second == std::string(pchValue)) changed = false; - (*lobby->mutable_values())[result->first] = pchValue; - } - } - - if (changed) - trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true, 0.005, changed); - - return true; -} - - -// returns the number of metadata keys set on the specified lobby -int GetLobbyDataCount( CSteamID steamIDLobby ) -{ - PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); - std::lock_guard lock(global_mutex); - - Lobby *lobby = get_lobby(steamIDLobby); - int size = 0; - if (lobby) size = lobby->values().size(); - - - return size; -} - - -// returns a lobby metadata key/values pair by index, of range [0, GetLobbyDataCount()) -bool GetLobbyDataByIndex( CSteamID steamIDLobby, int iLobbyData, char *pchKey, int cchKeyBufferSize, char *pchValue, int cchValueBufferSize ) -{ - PRINT_DEBUG("%llu [%i] key size=%i, value size=%i", steamIDLobby.ConvertToUint64(), iLobbyData, cchKeyBufferSize, cchValueBufferSize); - std::lock_guard lock(global_mutex); - - Lobby *lobby = get_lobby(steamIDLobby); - bool ret = false; - - if (lobby && lobby->values().size() > iLobbyData && iLobbyData >= 0) { - auto lobby_data = lobby->values().begin(); - for (int i = 0; i < iLobbyData; ++i) ++lobby_data; - if (pchKey && cchKeyBufferSize > 0) { - strncpy(pchKey, lobby_data->first.c_str(), cchKeyBufferSize - 1); - pchKey[cchKeyBufferSize - 1] = 0; - } - - if (pchValue && cchValueBufferSize > 0) { - strncpy(pchValue, lobby_data->second.c_str(), cchValueBufferSize - 1); - pchValue[cchValueBufferSize - 1] = 0; - } - - PRINT_DEBUG("ret '%s'='%s'", pchKey, pchValue); - ret = true; - } - - - return ret; -} - - -// removes a metadata key from the lobby -bool DeleteLobbyData( CSteamID steamIDLobby, const char *pchKey ) -{ - PRINT_DEBUG("'%s'", pchKey); - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - if (!lobby || lobby->owner() != settings->get_local_steam_id().ConvertToUint64() || lobby->deleted()) { - return false; - } - - lobby->mutable_values()->erase(pchKey); - trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true); - - return true; -} - - -// Gets per-user metadata for someone in this lobby -const char *GetLobbyMemberData( CSteamID steamIDLobby, CSteamID steamIDUser, const char *pchKey ) -{ - PRINT_DEBUG("'%s' %llu %llu", pchKey, steamIDLobby.ConvertToUint64(), steamIDUser.ConvertToUint64()); - std::lock_guard lock(global_mutex); - if (!pchKey) return ""; - - struct Lobby_Member *member = get_lobby_member(get_lobby(steamIDLobby), steamIDUser); - const char *ret = ""; - if (member) { - if (steamIDUser == settings->get_local_steam_id()) { - auto result = self_lobby_member_data.find(steamIDLobby.ConvertToUint64()); - if (result != self_lobby_member_data.end()) { - auto value = caseinsensitive_find(result->second, std::string(pchKey)); - if (value != result->second.end()) { - ret = value->second.c_str(); - } - } - } else { - auto result = caseinsensitive_find(member->values(), std::string(pchKey)); - if (result == member->values().end()) return ""; - ret = result->second.c_str(); - } - } - - PRINT_DEBUG("res '%s'", ret); - return ret; -} - -// Sets per-user metadata (for the local user implicitly) -void SetLobbyMemberData( CSteamID steamIDLobby, const char *pchKey, const char *pchValue ) -{ - PRINT_DEBUG("%llu '%s'='%s'", steamIDLobby.ConvertToUint64(), pchKey, pchValue); - if (!pchKey) return; - char empty_string[] = ""; - if (!pchValue) pchValue = empty_string; - - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - if (!lobby || lobby->deleted()) return; - - Lobby_Member *member = get_lobby_member(lobby, settings->get_local_steam_id()); - if (member) { - if (lobby->owner() == settings->get_local_steam_id().ConvertToUint64()) { - auto result = caseinsensitive_find(member->values(), std::string(pchKey)); - if (result == member->values().end()) { - (*member->mutable_values())[pchKey] = pchValue; - } else { - (*member->mutable_values())[result->first] = pchValue; - } - trigger_lobby_dataupdate(steamIDLobby, (uint64)member->id(), true); - } else { - Lobby_Messages *message = new Lobby_Messages(); - message->set_type(Lobby_Messages::MEMBER_DATA); - (*message->mutable_map())[pchKey] = pchValue; - send_owner_packet(steamIDLobby, message); - } - - { - auto result = self_lobby_member_data.find(steamIDLobby.ConvertToUint64()); - if (result != self_lobby_member_data.end()) { - auto value = caseinsensitive_find(result->second, std::string(pchKey)); - if (value != result->second.end()) { - self_lobby_member_data[steamIDLobby.ConvertToUint64()][value->first] = pchValue; - } else { - self_lobby_member_data[steamIDLobby.ConvertToUint64()][pchKey] = pchValue; - } - } else { - self_lobby_member_data[steamIDLobby.ConvertToUint64()][pchKey] = pchValue; - } - } - } -} - - -// Broadcasts a chat message to the all the users in the lobby -// users in the lobby (including the local user) will receive a LobbyChatMsg_t callback -// returns true if the message is successfully sent -// pvMsgBody can be binary or text data, up to 4k -// if pvMsgBody is text, cubMsgBody should be strlen( text ) + 1, to include the null terminator -bool SendLobbyChatMsg( CSteamID steamIDLobby, const void *pvMsgBody, int cubMsgBody ) -{ - PRINT_DEBUG("%llu %i", steamIDLobby.ConvertToUint64(), cubMsgBody); - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - if (!lobby || lobby->deleted()) return false; - - Lobby_Messages *message = new Lobby_Messages(); - message->set_type(Lobby_Messages::CHAT_MESSAGE); - message->set_bdata(pvMsgBody, cubMsgBody); - return send_lobby_members_packet(steamIDLobby, message); -} - -// Get a chat message as specified in a LobbyChatMsg_t callback -// iChatID is the LobbyChatMsg_t::m_iChatID value in the callback -// *pSteamIDUser is filled in with the CSteamID of the member -// *pvData is filled in with the message itself -// return value is the number of bytes written into the buffer -int GetLobbyChatEntry( CSteamID steamIDLobby, int iChatID, STEAM_OUT_STRUCT() CSteamID *pSteamIDUser, void *pvData, int cubData, EChatEntryType *peChatEntryType ) -{ - PRINT_DEBUG("%llu %i %p %p %i %p", steamIDLobby.ConvertToUint64(), iChatID, pSteamIDUser, pvData, cubData, peChatEntryType); - std::lock_guard lock(global_mutex); - if (iChatID >= chat_entries.size() || iChatID < 0 || cubData < 0) return 0; - if (chat_entries[iChatID].lobby_id != steamIDLobby) return 0; - if (pSteamIDUser) *pSteamIDUser = chat_entries[iChatID].user_id; - if (peChatEntryType) *peChatEntryType = chat_entries[iChatID].type; - if (pvData) { - if (chat_entries[iChatID].message.size() <= cubData) { - cubData = chat_entries[iChatID].message.size(); - memcpy(pvData, chat_entries[iChatID].message.data(), cubData); - PRINT_DEBUG(" Returned chat of len: %i", cubData); - return cubData; - } - } - - return 0; -} - - -// Refreshes metadata for a lobby you're not necessarily in right now -// you never do this for lobbies you're a member of, only if your -// this will send down all the metadata associated with a lobby -// this is an asynchronous call -// returns false if the local user is not connected to the Steam servers -// results will be returned by a LobbyDataUpdate_t callback -// if the specified lobby doesn't exist, LobbyDataUpdate_t::m_bSuccess will be set to false -bool RequestLobbyData( CSteamID steamIDLobby ) -{ - PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); - std::lock_guard lock(global_mutex); - - struct Data_Requested requested{}; - requested.lobby_id = steamIDLobby; - requested.requested = std::chrono::high_resolution_clock::now(); - data_requested.push_back(requested); - return true; -} - -// sets the game server associated with the lobby -// usually at this point, the users will join the specified game server -// either the IP/Port or the steamID of the game server has to be valid, depending on how you want the clients to be able to connect -void SetLobbyGameServer( CSteamID steamIDLobby, uint32 unGameServerIP, uint16 unGameServerPort, CSteamID steamIDGameServer ) -{ - PRINT_DEBUG("%llu %llu %hhu.%hhu.%hhu.%hhu:%hu", - steamIDLobby.ConvertToUint64(), steamIDGameServer.ConvertToUint64(), ((unsigned char *)&unGameServerIP)[3], ((unsigned char *)&unGameServerIP)[2], ((unsigned char *)&unGameServerIP)[1], ((unsigned char *)&unGameServerIP)[0], unGameServerPort - ); - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - if (lobby) { - if (lobby->deleted()) return; - - lobby->mutable_gameserver()->set_id(steamIDGameServer.ConvertToUint64()); - lobby->mutable_gameserver()->set_ip(unGameServerIP); - lobby->mutable_gameserver()->set_port(unGameServerPort); - lobby->mutable_gameserver()->set_num_update(lobby->gameserver().num_update() + 1); - - send_gameservercreated_cb(lobby->room_id(), lobby->gameserver().id(), lobby->gameserver().ip(), lobby->gameserver().port()); - trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true); - } - - -} - -// returns the details of a game server set in a lobby - returns false if there is no game server set, or that lobby doesn't exist -bool GetLobbyGameServer( CSteamID steamIDLobby, uint32 *punGameServerIP, uint16 *punGameServerPort, STEAM_OUT_STRUCT() CSteamID *psteamIDGameServer ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - if (!lobby) { - - return false; - } - - CSteamID server_id((uint64)lobby->gameserver().id()); - if (server_id.IsValid() || lobby->gameserver().port()) { - if (psteamIDGameServer) *psteamIDGameServer = server_id; - if (punGameServerIP) *punGameServerIP = lobby->gameserver().ip(); - if (punGameServerPort) *punGameServerPort = lobby->gameserver().port(); - - return true; - } - - - return false; -} - - -// set the limit on the # of users who can join the lobby -bool SetLobbyMemberLimit( CSteamID steamIDLobby, int cMaxMembers ) -{ - PRINT_DEBUG("%llu %i", steamIDLobby.ConvertToUint64(), cMaxMembers); - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - if (!lobby || lobby->owner() != settings->get_local_steam_id().ConvertToUint64() || lobby->deleted()) { - - return false; - } - - lobby->set_member_limit(cMaxMembers); - trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true); - - return true; -} - -// returns the current limit on the # of users who can join the lobby; returns 0 if no limit is defined -int GetLobbyMemberLimit( CSteamID steamIDLobby ) -{ - PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - int limit = 0; - if (lobby) limit = lobby->member_limit(); - - PRINT_DEBUG(" limit %i", limit); - return limit; -} - -void SetLobbyVoiceEnabled( CSteamID steamIDLobby, bool bVoiceEnabled ) -{ - PRINT_DEBUG_ENTRY(); -} - -// updates which type of lobby it is -// only lobbies that are k_ELobbyTypePublic or k_ELobbyTypeInvisible, and are set to joinable, will be returned by RequestLobbyList() calls -bool SetLobbyType( CSteamID steamIDLobby, ELobbyType eLobbyType ) -{ - PRINT_DEBUG("%i", eLobbyType); - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - if (!lobby || lobby->owner() != settings->get_local_steam_id().ConvertToUint64() || lobby->deleted()) { - return false; - } - - if (lobby->type() != eLobbyType) { - //maybe rename those functions? - if (lobby->type() == k_ELobbyTypeInvisible) on_self_enter_leave_lobby(steamIDLobby, eLobbyType, false); - if (eLobbyType == k_ELobbyTypeInvisible) on_self_enter_leave_lobby(steamIDLobby, lobby->type(), true); - - lobby->set_type(eLobbyType); - trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true); - } - - return true; -} - - -// sets whether or not a lobby is joinable - defaults to true for a new lobby -// if set to false, no user can join, even if they are a friend or have been invited -bool SetLobbyJoinable( CSteamID steamIDLobby, bool bLobbyJoinable ) -{ - PRINT_DEBUG("%u", bLobbyJoinable); - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - if (!lobby || lobby->owner() != settings->get_local_steam_id().ConvertToUint64() || lobby->deleted()) { - return false; - } - - if (lobby->joinable() != bLobbyJoinable) { - lobby->set_joinable(bLobbyJoinable); - trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true); - } - - return true; -} - - -// returns the current lobby owner -// you must be a member of the lobby to access this (Mr_Goldberg note: This is a lie) -// there always one lobby owner - if the current owner leaves, another user will become the owner -// it is possible (bur rare) to join a lobby just as the owner is leaving, thus entering a lobby with self as the owner -CSteamID GetLobbyOwner( CSteamID steamIDLobby ) -{ - PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - if (!lobby || lobby->deleted()) return k_steamIDNil; - - //TODO: might be better to require the lobby info to be at least requested first. - return (uint64)lobby->owner(); -} - -// asks the Steam servers for a list of lobbies that friends are in -// returns results by posting one RequestFriendsLobbiesResponse_t callback per friend/lobby pair -// if no friends are in lobbies, RequestFriendsLobbiesResponse_t will be posted but with 0 results -// filters don't apply to lobbies (currently) -bool RequestFriendsLobbies() -{ - PRINT_DEBUG_ENTRY(); - RequestFriendsLobbiesResponse_t data = {}; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return true; -} - -float GetLobbyDistance( CSteamID steamIDLobby ) -{ - PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); - return 0.0; -} - -// changes who the lobby owner is -// you must be the lobby owner for this to succeed, and steamIDNewOwner must be in the lobby -// after completion, the local user will no longer be the owner -bool SetLobbyOwner( CSteamID steamIDLobby, CSteamID steamIDNewOwner ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - Lobby *lobby = get_lobby(steamIDLobby); - if (!lobby || lobby->owner() != settings->get_local_steam_id().ConvertToUint64() || lobby->deleted()) return false; - Lobby_Member *member = get_lobby_member(lobby, steamIDNewOwner); - if (member) { - change_owner(&(*lobby), (uint64)member->id()); - trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true); - - return true; - } - - - return false; -} - - -// link two lobbies for the purposes of checking player compatibility -// you must be the lobby owner of both lobbies -bool SetLinkedLobby( CSteamID steamIDLobby, CSteamID steamIDLobbyDependent ) -{ - PRINT_DEBUG_ENTRY(); - return false; -} - -void RunBackground() -{ - remove_lobbies(); - Create_pending_lobbies(); - - if (check_timedout(last_sent_lobbies, SEND_LOBBY_RATE)) { - send_lobby_data(); - last_sent_lobbies = std::chrono::high_resolution_clock::now(); - } -} - -void RunCallbacks() -{ - RunBackground(); - - if (searching) { - PRINT_DEBUG("for lobbies %zu", lobbies.size()); - for(auto & l: lobbies) { - bool use = l.joinable() && (l.type() == k_ELobbyTypePublic || l.type() == k_ELobbyTypeInvisible || l.type() == k_ELobbyTypeFriendsOnly) && !l.deleted(); - PRINT_DEBUG("use lobby: %u, filters: %zu, joinable: %u, type: %u, deleted: %u", use, filter_values_copy.size(), l.joinable(), l.type(), l.deleted()); - for (auto & f : filter_values_copy) { - PRINT_DEBUG("'%s':'%s'/%i %u %i", f.key.c_str(), f.value_string.c_str(), f.value_int, f.is_int, f.eComparisonType); - auto value = caseinsensitive_find(l.values(), f.key); - if (value != l.values().end()) { - //TODO: eComparisonType - if (!f.is_int) { - PRINT_DEBUG("Compare Values %s %s", value->second.c_str(), f.value_string.c_str()); - if (f.eComparisonType == k_ELobbyComparisonEqual) { - if (value->second == f.value_string) { - PRINT_DEBUG("Equal (non-int)"); - //use = use; - } else { - PRINT_DEBUG("Not Equal (non-int)"); - use = false; - } - } else { - PRINT_DEBUG("TODO UNSUPPORTED compare type (non-int) %i", (int)f.eComparisonType); - } - } else { - try { - PRINT_DEBUG("%s", value->second.c_str()); - int compare_to = 0; - //TODO: check if this is how real steam behaves - if (value->second.size()) { - compare_to = std::stoll(value->second, 0, 0); - } - PRINT_DEBUG("Compare Values %i %i", compare_to, f.value_int); - if (f.eComparisonType == k_ELobbyComparisonEqual) { - if (compare_to == f.value_int) { - PRINT_DEBUG("Equal (int)"); - //use = use; - } else { - PRINT_DEBUG("Not Equal (int)"); - use = false; - } - } else { - PRINT_DEBUG("TODO UNSUPPORTED compare type (int) %i", (int)f.eComparisonType); - } - } catch (...) { - //Same case as if the key is not in the lobby? - use = false; - } - //TODO: add more comparisons - } - } else { - PRINT_DEBUG("Compare Key not in lobby"); - if (f.eComparisonType == k_ELobbyComparisonEqual) { - //If the key is not in the lobby do we take it into account? - use = false; - } - } - } - - PRINT_DEBUG("Lobby " "%" PRIu64 " use %u", l.room_id(), use); - if (use) PUSH_BACK_IF_NOT_IN(filtered_lobbies, (uint64)l.room_id()); - if (filtered_lobbies.size() >= filter_max_results_copy) { - PRINT_DEBUG("returning lobby search results, count=%zu", filtered_lobbies.size()); - searching = false; - LobbyMatchList_t data{}; - data.m_nLobbiesMatching = filtered_lobbies.size(); - callback_results->addCallResult(search_call_api_id, data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - search_call_api_id = 0; - } - } - } - - if (searching && check_timedout(lobby_last_search, LOBBY_SEARCH_TIMEOUT)) { - PRINT_DEBUG("LOBBY_SEARCH_TIMEOUT %zu", filtered_lobbies.size()); - LobbyMatchList_t data{}; - data.m_nLobbiesMatching = filtered_lobbies.size(); - callback_results->addCallResult(search_call_api_id, data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - searching = false; - search_call_api_id = 0; - } - - auto g = std::begin(pending_joins); - while (g != std::end(pending_joins)) { - if (!g->message_sent) { - PRINT_DEBUG("resending join lobby"); - Lobby_Messages *message = new Lobby_Messages(); - message->set_type(Lobby_Messages::JOIN); - g->message_sent = send_owner_packet(g->lobby_id, message); - } - - Lobby *lobby = get_lobby(g->lobby_id); - if (lobby && lobby->deleted()) { - PRINT_DEBUG("lobby deleted %llu", g->lobby_id.ConvertToUint64()); - LobbyEnter_t data{}; - data.m_ulSteamIDLobby = lobby->room_id(); - data.m_rgfChatPermissions = 0; //Unused - Always 0 - data.m_bLocked = false; - data.m_EChatRoomEnterResponse = k_EChatRoomEnterResponseDoesntExist; - callback_results->addCallResult(g->api_id, data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - g = pending_joins.erase(g); - } else if (get_lobby_member(lobby, settings->get_local_steam_id())) { - PRINT_DEBUG("lobby joined %llu", g->lobby_id.ConvertToUint64()); - LobbyEnter_t data{}; - data.m_ulSteamIDLobby = lobby->room_id(); - data.m_rgfChatPermissions = 0; //Unused - Always 0 - data.m_bLocked = false; - data.m_EChatRoomEnterResponse = k_EChatRoomEnterResponseSuccess; - callback_results->addCallResult(g->api_id, data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - g = pending_joins.erase(g); - trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)lobby->room_id(), true); - } else if (check_timedout(g->joined, PENDING_JOIN_TIMEOUT)) { - PRINT_DEBUG("pending join timeout %llu", g->lobby_id.ConvertToUint64()); - LobbyEnter_t data{}; - data.m_ulSteamIDLobby = g->lobby_id.ConvertToUint64(); - data.m_rgfChatPermissions = 0; //Unused - Always 0 - data.m_bLocked = false; - data.m_EChatRoomEnterResponse = k_EChatRoomEnterResponseDoesntExist; - callback_results->addCallResult(g->api_id, data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - g = pending_joins.erase(g); - } else { - ++g; - } - } - - - auto dr = std::begin(data_requested); - while (dr != std::end(data_requested)) { - if (get_lobby(dr->lobby_id)) { - trigger_lobby_dataupdate(dr->lobby_id, dr->lobby_id, true); - dr = data_requested.erase(dr); - continue; - } - - if (check_timedout(dr->requested, REQUEST_LOBBY_DATA_TIMEOUT)) { - trigger_lobby_dataupdate(dr->lobby_id, dr->lobby_id, false); - dr = data_requested.erase(dr); - continue; - } - - ++dr; - } -} - - - -void Callback(Common_Message *msg) -{ - if (msg->has_lobby()) { - PRINT_DEBUG("GOT A LOBBY appid: %u " "%" PRIu64 "", msg->lobby().appid(), msg->lobby().owner()); - if (msg->lobby().owner() != settings->get_local_steam_id().ConvertToUint64() && msg->lobby().appid() == settings->get_local_game_id().AppID()) { - Lobby *lobby = get_lobby((uint64)msg->lobby().room_id()); - if (!lobby) { - size_t old_size = lobbies.size(); - lobbies.resize(old_size + 1); - lobbies[old_size].set_room_id(msg->lobby().room_id()); - lobby = &(lobbies[old_size]); - } - - if (!lobby->deleted()) { - if (!protobuf_message_equal(*lobby, msg->lobby())) { - bool we_are_in_lobby = !!get_lobby_member(lobby, settings->get_local_steam_id()); - if (we_are_in_lobby) trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)lobby->room_id(), true); - - for (auto & m : lobby->members()) { - int count = 0; - Lobby_Member *member = get_lobby_member(msg->mutable_lobby(), (uint64)m.id()); - - if (we_are_in_lobby) { - if (!member) { - trigger_lobby_member_join_leave((uint64)lobby->room_id(), (uint64)m.id(), true, true, 0.2); - } else if (!protobuf_message_equal(*member, m)) { - trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)m.id(), true); - } - } - } - - bool joined = false; - for (auto & m : msg->lobby().members()) { - Lobby_Member *member = get_lobby_member(lobby, (uint64)m.id()); - if (!member) { - if (m.id() == settings->get_local_steam_id().ConvertToUint64()) { - CSteamID id((uint64)lobby->room_id()); - auto pd = pending_joins.begin(); - while (pd != pending_joins.end()) { - if (pd->lobby_id == id) { - bool success = true; - LobbyEnter_t data; - data.m_ulSteamIDLobby = lobby->room_id(); - data.m_rgfChatPermissions = 0; //Unused - Always 0 - data.m_bLocked = false; - data.m_EChatRoomEnterResponse = success ? k_EChatRoomEnterResponseSuccess : k_EChatRoomEnterResponseError; - callback_results->addCallResult(pd->api_id, data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - pd = pending_joins.erase(pd); - joined = true; - } else { - ++pd; - } - } - if (joined) { - on_self_enter_leave_lobby((uint64)lobby->room_id(), lobby->type(), false); - trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)lobby->room_id(), true); - } - } else { - if (we_are_in_lobby) trigger_lobby_member_join_leave((uint64)lobby->room_id(), (uint64)m.id(), false, true); - } - } - } - - if (joined) { - for (auto & m : msg->lobby().members()) { - if (m.id() != settings->get_local_steam_id().ConvertToUint64()) { - //TODO: is this good? - //trigger_lobby_member_join_leave((uint64)lobby->room_id(), (uint64)m.id(), false, true); - if (m.values().size()) { - //TODO: check if this is what steam does - //trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)m.id(), true); - } - } - } - } - - if ((joined && msg->lobby().gameserver().num_update()) || (we_are_in_lobby && (lobby->gameserver().num_update() != msg->lobby().gameserver().num_update()))) { - send_gameservercreated_cb(lobby->room_id(), msg->lobby().gameserver().id(), msg->lobby().gameserver().ip(), msg->lobby().gameserver().port()); - trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)lobby->room_id(), true); - } - - *lobby = msg->lobby(); - } - } - } - } - - - if (msg->has_lobby_messages()) { - PRINT_DEBUG("LOBBY MESSAGE %u " "%" PRIu64 "", msg->lobby_messages().type(), msg->lobby_messages().id()); - Lobby *lobby = get_lobby((uint64)msg->lobby_messages().id()); - if (lobby && !lobby->deleted()) { - bool we_are_in_lobby = !!get_lobby_member(lobby, settings->get_local_steam_id()); - if (lobby->owner() == settings->get_local_steam_id().ConvertToUint64()) { - if (msg->lobby_messages().type() == Lobby_Messages::JOIN) { - PRINT_DEBUG("LOBBY MESSAGE: JOIN, lobby=%llu from=%llu", (uint64)lobby->room_id(), (uint64)msg->source_id()); - if (add_member_to_lobby(lobby, (uint64)msg->source_id())) { - trigger_lobby_member_join_leave((uint64)lobby->room_id(), (uint64)msg->source_id(), false, true, 0.01); - } - } - - if (msg->lobby_messages().type() == Lobby_Messages::MEMBER_DATA) { - PRINT_DEBUG("LOBBY MESSAGE: MEMBER_DATA"); - Lobby_Member *member = get_lobby_member(lobby, (uint64)msg->source_id()); - if (member) { - for (auto const &p : msg->lobby_messages().map()) { - PRINT_DEBUG("member data '%s'='%s'", p.first.c_str(), p.second.c_str()); - auto result = caseinsensitive_find(member->values(), p.first); - if (result == member->values().end()) { - (*member->mutable_values())[p.first] = p.second; - } else { - (*member->mutable_values())[result->first] = p.second; - } - } - - trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)member->id(), true); - } - } - } - - if (msg->lobby_messages().type() == Lobby_Messages::LEAVE) { - PRINT_DEBUG("LOBBY MESSAGE: LEAVE " "%" PRIu64 "", msg->source_id()); - leave_lobby(lobby, (uint64)msg->source_id()); - if (we_are_in_lobby) trigger_lobby_member_join_leave((uint64)lobby->room_id(), (uint64)msg->source_id(), true, true, 0.2); - } - - if (msg->lobby_messages().type() == Lobby_Messages::CHANGE_OWNER) { - PRINT_DEBUG("LOBBY MESSAGE: CHANGE OWNER"); - lobby->set_owner(msg->lobby_messages().idata()); - if (we_are_in_lobby) trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)lobby->room_id(), true); - } - - if (msg->lobby_messages().type() == Lobby_Messages::CHAT_MESSAGE) { - PRINT_DEBUG("LOBBY MESSAGE: CHAT MESSAGE"); - if (we_are_in_lobby) { - struct Chat_Entry entry{}; - entry.type = k_EChatEntryTypeChatMsg; - entry.message = msg->lobby_messages().bdata(); - entry.lobby_id = CSteamID((uint64)msg->lobby_messages().id()); - entry.user_id = CSteamID((uint64)msg->source_id()); - LobbyChatMsg_t data{}; - data.m_ulSteamIDLobby = msg->lobby_messages().id(); - data.m_ulSteamIDUser = msg->source_id(); - data.m_eChatEntryType = entry.type; - data.m_iChatID = chat_entries.size(); - chat_entries.push_back(entry); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - } - } - } - - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - - } - - if (msg->low_level().type() == Low_Level::DISCONNECT) { - for (auto & l: lobbies) { - if (leave_lobby(&(l), (uint64)msg->source_id())) - trigger_lobby_member_join_leave((uint64)l.room_id(), (uint64)msg->source_id(), true, true, 0.0); - } - } - } - - remove_lobbies(); -} + */ + // + STEAM_CALL_RESULT( LobbyMatchList_t ) + SteamAPICall_t RequestLobbyList(); + void RequestLobbyList_OLD(); + + // filters for lobbies + // this needs to be called before RequestLobbyList() to take effect + // these are cleared on each call to RequestLobbyList() + void AddRequestLobbyListStringFilter( const char *pchKeyToMatch, const char *pchValueToMatch, ELobbyComparison eComparisonType ); + + // numerical comparison + void AddRequestLobbyListNumericalFilter( const char *pchKeyToMatch, int nValueToMatch, ELobbyComparison eComparisonType ); + + // returns results closest to the specified value. Multiple near filters can be added, with early filters taking precedence + void AddRequestLobbyListNearValueFilter( const char *pchKeyToMatch, int nValueToBeCloseTo ); + + // returns only lobbies with the specified number of slots available + void AddRequestLobbyListFilterSlotsAvailable( int nSlotsAvailable ); + + // sets the distance for which we should search for lobbies (based on users IP address to location map on the Steam backed) + void AddRequestLobbyListDistanceFilter( ELobbyDistanceFilter eLobbyDistanceFilter ); + + // sets how many results to return, the lower the count the faster it is to download the lobby results & details to the client + void AddRequestLobbyListResultCountFilter( int cMaxResults ); + + + void AddRequestLobbyListCompatibleMembersFilter( CSteamID steamIDLobby ); + + void AddRequestLobbyListFilter( const char *pchKeyToMatch, const char *pchValueToMatch ); + + void AddRequestLobbyListNumericalFilter( const char *pchKeyToMatch, int nValueToMatch, int nComparisonType ); + + void AddRequestLobbyListSlotsAvailableFilter(); + + // returns the CSteamID of a lobby, as retrieved by a RequestLobbyList call + // should only be called after a LobbyMatchList_t callback is received + // iLobby is of the range [0, LobbyMatchList_t::m_nLobbiesMatching) + // the returned CSteamID::IsValid() will be false if iLobby is out of range + CSteamID GetLobbyByIndex( int iLobby ); + + + // Create a lobby on the Steam servers. + // If private, then the lobby will not be returned by any RequestLobbyList() call; the CSteamID + // of the lobby will need to be communicated via game channels or via InviteUserToLobby() + // this is an asynchronous request + // results will be returned by LobbyCreated_t callback and call result; lobby is joined & ready to use at this point + // a LobbyEnter_t callback will also be received (since the local user is joining their own lobby) + STEAM_CALL_RESULT( LobbyCreated_t ) + SteamAPICall_t CreateLobby( ELobbyType eLobbyType, int cMaxMembers ); + + SteamAPICall_t CreateLobby( ELobbyType eLobbyType ); + + void CreateLobby_OLD( ELobbyType eLobbyType ); + + void CreateLobby( bool bPrivate ); + + // Joins an existing lobby + // this is an asynchronous request + // results will be returned by LobbyEnter_t callback & call result, check m_EChatRoomEnterResponse to see if was successful + // lobby metadata is available to use immediately on this call completing + STEAM_CALL_RESULT( LobbyEnter_t ) + SteamAPICall_t JoinLobby( CSteamID steamIDLobby ); + + void JoinLobby_OLD( CSteamID steamIDLobby ); + + // Leave a lobby; this will take effect immediately on the client side + // other users in the lobby will be notified by a LobbyChatUpdate_t callback + void LeaveLobby( CSteamID steamIDLobby ); + + + // Invite another user to the lobby + // the target user will receive a LobbyInvite_t callback + // will return true if the invite is successfully sent, whether or not the target responds + // returns false if the local user is not connected to the Steam servers + // if the other user clicks the join link, a GameLobbyJoinRequested_t will be posted if the user is in-game, + // or if the game isn't running yet the game will be launched with the parameter +connect_lobby <64-bit lobby id> + bool InviteUserToLobby( CSteamID steamIDLobby, CSteamID steamIDInvitee ); + + + // Lobby iteration, for viewing details of users in a lobby + // only accessible if the lobby user is a member of the specified lobby + // persona information for other lobby members (name, avatar, etc.) will be asynchronously received + // and accessible via ISteamFriends interface + + // returns the number of users in the specified lobby + int GetNumLobbyMembers( CSteamID steamIDLobby ); + + // returns the CSteamID of a user in the lobby + // iMember is of range [0,GetNumLobbyMembers()) + // note that the current user must be in a lobby to retrieve CSteamIDs of other users in that lobby + CSteamID GetLobbyMemberByIndex( CSteamID steamIDLobby, int iMember ); + + + // Get data associated with this lobby + // takes a simple key, and returns the string associated with it + // "" will be returned if no value is set, or if steamIDLobby is invalid + const char *GetLobbyData( CSteamID steamIDLobby, const char *pchKey ); + + // Sets a key/value pair in the lobby metadata + // each user in the lobby will be broadcast this new value, and any new users joining will receive any existing data + // this can be used to set lobby names, map, etc. + // to reset a key, just set it to "" + // other users in the lobby will receive notification of the lobby data change via a LobbyDataUpdate_t callback + bool SetLobbyData( CSteamID steamIDLobby, const char *pchKey, const char *pchValue ); + + + // returns the number of metadata keys set on the specified lobby + int GetLobbyDataCount( CSteamID steamIDLobby ); + + + // returns a lobby metadata key/values pair by index, of range [0, GetLobbyDataCount()) + bool GetLobbyDataByIndex( CSteamID steamIDLobby, int iLobbyData, char *pchKey, int cchKeyBufferSize, char *pchValue, int cchValueBufferSize ); + + + // removes a metadata key from the lobby + bool DeleteLobbyData( CSteamID steamIDLobby, const char *pchKey ); + + + // Gets per-user metadata for someone in this lobby + const char *GetLobbyMemberData( CSteamID steamIDLobby, CSteamID steamIDUser, const char *pchKey ); + + // Sets per-user metadata (for the local user implicitly) + void SetLobbyMemberData( CSteamID steamIDLobby, const char *pchKey, const char *pchValue ); + + + // Broadcasts a chat message to the all the users in the lobby + // users in the lobby (including the local user) will receive a LobbyChatMsg_t callback + // returns true if the message is successfully sent + // pvMsgBody can be binary or text data, up to 4k + // if pvMsgBody is text, cubMsgBody should be strlen( text ) + 1, to include the null terminator + bool SendLobbyChatMsg( CSteamID steamIDLobby, const void *pvMsgBody, int cubMsgBody ); + + // Get a chat message as specified in a LobbyChatMsg_t callback + // iChatID is the LobbyChatMsg_t::m_iChatID value in the callback + // *pSteamIDUser is filled in with the CSteamID of the member + // *pvData is filled in with the message itself + // return value is the number of bytes written into the buffer + int GetLobbyChatEntry( CSteamID steamIDLobby, int iChatID, STEAM_OUT_STRUCT() CSteamID *pSteamIDUser, void *pvData, int cubData, EChatEntryType *peChatEntryType ); + + + // Refreshes metadata for a lobby you're not necessarily in right now + // you never do this for lobbies you're a member of, only if your + // this will send down all the metadata associated with a lobby + // this is an asynchronous call + // returns false if the local user is not connected to the Steam servers + // results will be returned by a LobbyDataUpdate_t callback + // if the specified lobby doesn't exist, LobbyDataUpdate_t::m_bSuccess will be set to false + bool RequestLobbyData( CSteamID steamIDLobby ); + + // sets the game server associated with the lobby + // usually at this point, the users will join the specified game server + // either the IP/Port or the steamID of the game server has to be valid, depending on how you want the clients to be able to connect + void SetLobbyGameServer( CSteamID steamIDLobby, uint32 unGameServerIP, uint16 unGameServerPort, CSteamID steamIDGameServer ); + + // returns the details of a game server set in a lobby - returns false if there is no game server set, or that lobby doesn't exist + bool GetLobbyGameServer( CSteamID steamIDLobby, uint32 *punGameServerIP, uint16 *punGameServerPort, STEAM_OUT_STRUCT() CSteamID *psteamIDGameServer ); + + + // set the limit on the # of users who can join the lobby + bool SetLobbyMemberLimit( CSteamID steamIDLobby, int cMaxMembers ); + + // returns the current limit on the # of users who can join the lobby; returns 0 if no limit is defined + int GetLobbyMemberLimit( CSteamID steamIDLobby ); + + void SetLobbyVoiceEnabled( CSteamID steamIDLobby, bool bVoiceEnabled ); + + // updates which type of lobby it is + // only lobbies that are k_ELobbyTypePublic or k_ELobbyTypeInvisible, and are set to joinable, will be returned by RequestLobbyList() calls + bool SetLobbyType( CSteamID steamIDLobby, ELobbyType eLobbyType ); + + + // sets whether or not a lobby is joinable - defaults to true for a new lobby + // if set to false, no user can join, even if they are a friend or have been invited + bool SetLobbyJoinable( CSteamID steamIDLobby, bool bLobbyJoinable ); + + + // returns the current lobby owner + // you must be a member of the lobby to access this (Mr_Goldberg note: This is a lie) + // there always one lobby owner - if the current owner leaves, another user will become the owner + // it is possible (bur rare) to join a lobby just as the owner is leaving, thus entering a lobby with self as the owner + CSteamID GetLobbyOwner( CSteamID steamIDLobby ); + + // asks the Steam servers for a list of lobbies that friends are in + // returns results by posting one RequestFriendsLobbiesResponse_t callback per friend/lobby pair + // if no friends are in lobbies, RequestFriendsLobbiesResponse_t will be posted but with 0 results + // filters don't apply to lobbies (currently) + bool RequestFriendsLobbies(); + + float GetLobbyDistance( CSteamID steamIDLobby ); + + // changes who the lobby owner is + // you must be the lobby owner for this to succeed, and steamIDNewOwner must be in the lobby + // after completion, the local user will no longer be the owner + bool SetLobbyOwner( CSteamID steamIDLobby, CSteamID steamIDNewOwner ); + + + // link two lobbies for the purposes of checking player compatibility + // you must be the lobby owner of both lobbies + bool SetLinkedLobby( CSteamID steamIDLobby, CSteamID steamIDLobbyDependent ); }; + +#endif // __INCLUDED_STEAM_MATCHMAKING_H__ diff --git a/dll/dll/steam_matchmaking_servers.h b/dll/dll/steam_matchmaking_servers.h index 61cd12a3..a5bc9433 100644 --- a/dll/dll/steam_matchmaking_servers.h +++ b/dll/dll/steam_matchmaking_servers.h @@ -15,59 +15,69 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_MATCHMAKING_SERVERS_H__ +#define __INCLUDED_STEAM_MATCHMAKING_SERVERS_H__ + #include "base.h" #include -#define SERVER_TIMEOUT 10.0 -#define DIRECT_IP_DELAY 0.05 - struct Steam_Matchmaking_Servers_Direct_IP_Request { - HServerQuery id; - uint32 ip; - uint16 port; + HServerQuery id{}; + uint32 ip{}; + uint16 port{}; - std::chrono::high_resolution_clock::time_point created; - ISteamMatchmakingRulesResponse *rules_response = NULL; - ISteamMatchmakingPlayersResponse *players_response = NULL; - ISteamMatchmakingPingResponse *ping_response = NULL; + std::chrono::high_resolution_clock::time_point created{}; + ISteamMatchmakingRulesResponse *rules_response{}; + ISteamMatchmakingPlayersResponse *players_response{}; + ISteamMatchmakingPingResponse *ping_response{}; }; struct Steam_Matchmaking_Servers_Gameserver_Friends { - uint64 source_id; - uint32 ip; - uint16 port; - std::chrono::high_resolution_clock::time_point last_recv; + uint64 source_id{}; + uint32 ip{}; + uint16 port{}; + std::chrono::high_resolution_clock::time_point last_recv{}; }; struct Steam_Matchmaking_Servers_Gameserver { - Gameserver server; - std::chrono::high_resolution_clock::time_point last_recv; - EMatchMakingType type; + Gameserver server{}; + std::chrono::high_resolution_clock::time_point last_recv{}; + EMatchMakingType type{}; }; struct Steam_Matchmaking_Request { - AppId_t appid; - HServerListRequest id; - ISteamMatchmakingServerListResponse *callbacks; - ISteamMatchmakingServerListResponse001 *old_callbacks; - bool completed, cancelled, released; - std::vector gameservers_filtered; - EMatchMakingType type; + AppId_t appid{}; + HServerListRequest id{}; + ISteamMatchmakingServerListResponse *callbacks{}; + ISteamMatchmakingServerListResponse001 *old_callbacks{}; + bool completed{}, cancelled{}, released{}; + std::vector gameservers_filtered{}; + EMatchMakingType type{}; }; -class Steam_Matchmaking_Servers : public ISteamMatchmakingServers, -public ISteamMatchmakingServers001 +class Steam_Matchmaking_Servers : +public ISteamMatchmakingServers001, +public ISteamMatchmakingServers { class Settings *settings{}; class Local_Storage *local_storage{}; class Networking *network{}; - std::vector gameservers; - std::vector gameservers_friends; - std::vector requests; - std::vector direct_ip_requests; + std::vector gameservers{}; + std::vector gameservers_friends{}; + std::vector requests{}; + std::vector direct_ip_requests{}; + HServerListRequest RequestServerList(AppId_t iApp, ISteamMatchmakingServerListResponse *pRequestServersResponse, EMatchMakingType type); void RequestOldServerList(AppId_t iApp, ISteamMatchmakingServerListResponse001 *pRequestServersResponse, EMatchMakingType type); + + // + static void network_callback(void *object, Common_Message *msg); + void server_details(Gameserver *g, gameserveritem_t *server); + void server_details_players(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r); + void server_details_rules(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r); + void Callback(Common_Message *msg); + public: Steam_Matchmaking_Servers(class Settings *settings, class Local_Storage *local_storage, class Networking *network); ~Steam_Matchmaking_Servers(); @@ -195,25 +205,25 @@ public: // Get details on a given server in the list, you can get the valid range of index // values by calling GetServerCount(). You will also receive index values in // ISteamMatchmakingServerListResponse::ServerResponded() callbacks - gameserveritem_t *GetServerDetails( EMatchMakingType eType, int iServer ) { return GetServerDetails((HServerListRequest) eType , iServer ); } + gameserveritem_t *GetServerDetails( EMatchMakingType eType, int iServer ); // Cancel an request which is operation on the given list type. You should call this to cancel // any in-progress requests before destructing a callback object that may have been passed // to one of the above list request calls. Not doing so may result in a crash when a callback // occurs on the destructed object. - void CancelQuery( EMatchMakingType eType ) { return CancelQuery((HServerListRequest) eType); } + void CancelQuery( EMatchMakingType eType ); // Ping every server in your list again but don't update the list of servers - void RefreshQuery( EMatchMakingType eType ) { return RefreshQuery((HServerListRequest) eType); } + void RefreshQuery( EMatchMakingType eType ); // Returns true if the list is currently refreshing its server list - bool IsRefreshing( EMatchMakingType eType ) { return IsRefreshing((HServerListRequest) eType); } + bool IsRefreshing( EMatchMakingType eType ); // How many servers in the given list, GetServerDetails above takes 0... GetServerCount() - 1 - int GetServerCount( EMatchMakingType eType ) { return GetServerCount((HServerListRequest) eType); } + int GetServerCount( EMatchMakingType eType ); // Refresh a single server inside of a query (rather than all the servers ) - void RefreshServer( EMatchMakingType eType, int iServer ) { return RefreshServer((HServerListRequest) eType, iServer); } + void RefreshServer( EMatchMakingType eType, int iServer ); //----------------------------------------------------------------------------- // Queries to individual servers directly via IP/Port @@ -233,10 +243,9 @@ public: // to one of the above calls to avoid crashing when callbacks occur. void CancelServerQuery( HServerQuery hServerQuery ); - // + // called by steam_client::runcallbacks void RunCallbacks(); - void Callback(Common_Message *msg); - void server_details(Gameserver *g, gameserveritem_t *server); - void server_details_players(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r); - void server_details_rules(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r); + }; + +#endif // __INCLUDED_STEAM_MATCHMAKING_SERVERS_H__ diff --git a/dll/dll/steam_music.h b/dll/dll/steam_music.h index 37ab94ca..5a50cd8f 100644 --- a/dll/dll/steam_music.h +++ b/dll/dll/steam_music.h @@ -15,15 +15,20 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_MUSIC_H__ +#define __INCLUDED_STEAM_MUSIC_H__ + #include "base.h" -class Steam_Music : public ISteamMusic +class Steam_Music : +public ISteamMusic { - int playing; - float volume; + int playing{};; + float volume{};; void change_playstate(int new_playing); - class SteamCallBacks *callbacks; + class SteamCallBacks *callbacks{}; + public: Steam_Music(class SteamCallBacks *callbacks); @@ -41,3 +46,5 @@ public: void SetVolume( float flVolume ); float GetVolume(); }; + +#endif // __INCLUDED_STEAM_MUSIC_H__ diff --git a/dll/dll/steam_musicremote.h b/dll/dll/steam_musicremote.h index 4ad84c2d..dd7364ef 100644 --- a/dll/dll/steam_musicremote.h +++ b/dll/dll/steam_musicremote.h @@ -15,9 +15,13 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_MUSICREMOTE_H__ +#define __INCLUDED_STEAM_MUSICREMOTE_H__ + #include "base.h" -class Steam_MusicRemote : public ISteamMusicRemote +class Steam_MusicRemote : +public ISteamMusicRemote { public: // Service Definition @@ -65,3 +69,5 @@ public: bool SetCurrentPlaylistEntry( int nID ); bool PlaylistDidChange(); }; + +#endif // __INCLUDED_STEAM_MUSICREMOTE_H__ diff --git a/dll/dll/steam_networking.h b/dll/dll/steam_networking.h index 457448de..b838e7fe 100644 --- a/dll/dll/steam_networking.h +++ b/dll/dll/steam_networking.h @@ -15,27 +15,21 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_NETWORKING_H__ +#define __INCLUDED_STEAM_NETWORKING_H__ + #include "base.h" -//packet timeout in seconds for non connections -#define ORPHANED_PACKET_TIMEOUT (20) -#define NEW_CONNECTION_TIMEOUT (20.0) - -//kingdom 2 crowns doesn't work with a 0.3 delay or lower -#define NEW_CONNECTION_DELAY (0.4) - -#define OLD_CHANNEL_NUMBER 1 - struct Steam_Networking_Connection { - CSteamID remote; - std::set open_channels; + CSteamID remote{}; + std::set open_channels{}; }; struct steam_listen_socket { - SNetListenSocket_t id; - int nVirtualP2PPort; - uint32 nIP; - uint16 nPort; + SNetListenSocket_t id{}; + int nVirtualP2PPort{}; + uint32 nIP{}; + uint16 nPort{}; }; enum steam_socket_connection_status { @@ -46,15 +40,15 @@ enum steam_socket_connection_status { }; struct steam_connection_socket { - SNetSocket_t id; - SNetListenSocket_t listen_id; - enum steam_socket_connection_status status; - CSteamID target; - int nVirtualPort; - uint32 nIP; - uint16 nPort; - SNetSocket_t other_id; - std::vector data_packets; + SNetSocket_t id{}; + SNetListenSocket_t listen_id{}; + enum steam_socket_connection_status status{}; + CSteamID target{}; + int nVirtualPort{}; + uint32 nIP{}; + uint16 nPort{}; + SNetSocket_t other_id{}; + std::vector data_packets{}; }; class Steam_Networking : @@ -65,962 +59,208 @@ public ISteamNetworking004, public ISteamNetworking005, public ISteamNetworking { - class Settings *settings; - class Networking *network; + class Settings *settings{}; + class Networking *network{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + std::recursive_mutex messages_mutex{}; + std::list messages{}; + std::list unprocessed_messages{}; - std::recursive_mutex messages_mutex; - std::list messages; - std::list unprocessed_messages; + std::recursive_mutex connections_edit_mutex{}; + std::vector connections{}; - std::recursive_mutex connections_edit_mutex; - std::vector connections; + std::vector listen_sockets{}; + std::vector connection_sockets{}; - std::vector listen_sockets; - std::vector connection_sockets; + std::map new_connection_times{}; + std::queue new_connections_to_call_cb{}; + + SNetListenSocket_t socket_number = 0; - std::map new_connection_times; - std::queue new_connections_to_call_cb; + bool connection_exists(CSteamID id); + struct Steam_Networking_Connection *get_or_create_connection(CSteamID id); + void remove_connection(CSteamID id); + SNetSocket_t create_connection_socket(CSteamID target, int nVirtualPort, uint32 nIP, uint16 nPort, SNetListenSocket_t id=0, enum steam_socket_connection_status status=SOCKET_CONNECTING, SNetSocket_t other_id=0); + struct steam_connection_socket *get_connection_socket(SNetSocket_t id); + void remove_killed_connection_sockets(); -bool connection_exists(CSteamID id) -{ - std::lock_guard lock(connections_edit_mutex); - return std::find_if(connections.begin(), connections.end(), [&id](struct Steam_Networking_Connection const& conn) { return conn.remote == id;}) != connections.end(); -} - -struct Steam_Networking_Connection *get_or_create_connection(CSteamID id) -{ - std::lock_guard lock(connections_edit_mutex); - auto conn = std::find_if(connections.begin(), connections.end(), [&id](struct Steam_Networking_Connection const& conn) { return conn.remote == id;}); - - if (connections.end() == conn) { - struct Steam_Networking_Connection connection; - connection.remote = id; - connections.push_back(connection); - return &(connections[connections.size() - 1]); - } else { - return &(*conn); - } -} - -void remove_connection(CSteamID id) -{ - { - std::lock_guard lock(connections_edit_mutex); - auto conn = std::begin(connections); - while (conn != std::end(connections)) { - if (conn->remote == id) { - - conn = connections.erase(conn); - } else { - ++conn; - } - } - } - - //pretty sure steam also clears the entire queue of messages for that connection - { - std::lock_guard lock(messages_mutex); - auto msg = std::begin(messages); - while (msg != std::end(messages)) { - if (msg->source_id() == id.ConvertToUint64()) { - msg = messages.erase(msg); - } else { - ++msg; - } - } - } - - { - auto msg = std::begin(unprocessed_messages); - while (msg != std::end(unprocessed_messages)) { - if (msg->source_id() == id.ConvertToUint64()) { - msg = unprocessed_messages.erase(msg); - } else { - ++msg; - } - } - } -} - -SNetSocket_t create_connection_socket(CSteamID target, int nVirtualPort, uint32 nIP, uint16 nPort, SNetListenSocket_t id=0, enum steam_socket_connection_status status=SOCKET_CONNECTING, SNetSocket_t other_id=0) -{ - static SNetSocket_t socket_number = 0; - bool found; - do { - found = false; - ++socket_number; - for (auto & c: connection_sockets) { - if (c.id == socket_number || socket_number == 0) { - found = true; - break; - } - } - } while (found); - - struct steam_connection_socket socket; - socket.id = socket_number; - socket.listen_id = id; - socket.status = status; - socket.target = target; - socket.nVirtualPort = nVirtualPort; - socket.nIP = nIP; - socket.nPort = nPort; - socket.other_id = other_id; - connection_sockets.push_back(socket); - - Common_Message msg; - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_dest_id(target.ConvertToUint64()); - msg.set_allocated_network_old(new Network_Old); - if (nPort) { - msg.mutable_network_old()->set_type(Network_Old::CONNECTION_REQUEST_IP); - msg.mutable_network_old()->set_port(nPort); - } else { - msg.mutable_network_old()->set_type(Network_Old::CONNECTION_REQUEST_STEAMID); - msg.mutable_network_old()->set_port(nVirtualPort); - } - - if (socket.status == SOCKET_CONNECTED) { - msg.mutable_network_old()->set_type(Network_Old::CONNECTION_ACCEPTED); - } - - msg.mutable_network_old()->set_connection_id(socket.other_id); - msg.mutable_network_old()->set_connection_id_from(socket.id); - - if (target.IsValid()) { - network->sendTo(&msg, true); - } else if (nIP) { - network->sendToIPPort(&msg, nIP, nPort, true); - } - - return socket.id; -} - -struct steam_connection_socket *get_connection_socket(SNetSocket_t id) -{ - auto conn = std::find_if(connection_sockets.begin(), connection_sockets.end(), [&id](struct steam_connection_socket const& conn) { return conn.id == id;}); - if (conn == connection_sockets.end()) return NULL; - return &(*conn); -} - -void remove_killed_connection_sockets() -{ - auto socket = std::begin(connection_sockets); - while (socket != std::end(connection_sockets)) { - if (socket->status == SOCKET_KILLED || socket->status == SOCKET_DISCONNECTED) { - socket = connection_sockets.erase(socket); - } else { - ++socket; - } - } -} + static void steam_networking_callback(void *object, Common_Message *msg); + static void steam_networking_run_every_runcp(void *object); public: -static void steam_networking_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Networking *steam_networking = (Steam_Networking *)object; - steam_networking->Callback(msg); -} - -static void steam_networking_run_every_runcp(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Networking *steam_networking = (Steam_Networking *)object; - steam_networking->RunCallbacks(); -} - -Steam_Networking(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) -{ - this->settings = settings; - this->network = network; - this->run_every_runcb = run_every_runcb; - this->network->setCallback(CALLBACK_ID_NETWORKING, settings->get_local_steam_id(), &Steam_Networking::steam_networking_callback, this); - this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking::steam_networking_callback, this); - this->run_every_runcb->add(&Steam_Networking::steam_networking_run_every_runcp, this); - - this->callbacks = callbacks; - - PRINT_DEBUG("steam_networking_contructor %llu messages: %p", settings->get_local_steam_id().ConvertToUint64(), &messages); -} - -~Steam_Networking() -{ - this->network->rmCallback(CALLBACK_ID_NETWORKING, settings->get_local_steam_id(), &Steam_Networking::steam_networking_callback, this); - this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking::steam_networking_callback, this); - this->run_every_runcb->remove(&Steam_Networking::steam_networking_run_every_runcp, this); -} - -//////////////////////////////////////////////////////////////////////////////////////////// -// Session-less connection functions -// automatically establishes NAT-traversing or Relay server connections - -// Sends a P2P packet to the specified user -// UDP-like, unreliable and a max packet size of 1200 bytes -// the first packet send may be delayed as the NAT-traversal code runs -// if we can't get through to the user, an error will be posted via the callback P2PSessionConnectFail_t -// see EP2PSend enum above for the descriptions of the different ways of sending packets -// -// nChannel is a routing number you can use to help route message to different systems - you'll have to call ReadP2PPacket() -// with the same channel number in order to retrieve the data on the other end -// using different channels to talk to the same user will still use the same underlying p2p connection, saving on resources -bool SendP2PPacket( CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType, int nChannel) -{ - PRINT_DEBUG("len %u sendtype: %u channel: %u to: %llu", cubData, eP2PSendType, nChannel, steamIDRemote.ConvertToUint64()); - std::lock_guard lock(global_mutex); - bool reliable = false; - if (eP2PSendType == k_EP2PSendReliable || eP2PSendType == k_EP2PSendReliableWithBuffering) reliable = true; - Common_Message msg; - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_dest_id(steamIDRemote.ConvertToUint64()); - msg.set_allocated_network(new Network_pb); - - if (!connection_exists(steamIDRemote)) { - msg.mutable_network()->set_type(Network_pb::NEW_CONNECTION); - network->sendTo(&msg, true); - } - - msg.mutable_network()->set_channel(nChannel); - msg.mutable_network()->set_data(pubData, cubData); - msg.mutable_network()->set_type(Network_pb::DATA); - - struct Steam_Networking_Connection *conn = get_or_create_connection(steamIDRemote); - new_connection_times.erase(steamIDRemote); - - conn->open_channels.insert(nChannel); - bool ret = network->sendTo(&msg, reliable); - PRINT_DEBUG("Sent message with size: %zu %u", msg.network().data().size(), ret); - return ret; -} - -bool SendP2PPacket( CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType ) { - PRINT_DEBUG("old"); - return SendP2PPacket(steamIDRemote, pubData, cubData, eP2PSendType, OLD_CHANNEL_NUMBER); -} - -// returns true if any data is available for read, and the amount of data that will need to be read -bool IsP2PPacketAvailable( uint32 *pcubMsgSize, int nChannel) -{ - PRINT_DEBUG("channel: %i", nChannel); - std::lock_guard lock(messages_mutex); - //Not sure if this should be here because it slightly screws up games that don't like such low "pings" - //Commenting it out for now because it looks like it causes a bug where 20xx gets stuck in an infinite receive packet loop - //this->network->Run(); - //RunCallbacks(); - - PRINT_DEBUG("Messages %zu %p", messages.size(), &messages); - for (auto &msg : messages) { - if (connection_exists((uint64)msg.source_id()) && msg.mutable_network()->channel() == nChannel && msg.network().processed()) { - uint32 size = msg.mutable_network()->data().size(); - if (pcubMsgSize) *pcubMsgSize = size; - PRINT_DEBUG("available with size: %u", size); - return true; - } - } - - PRINT_DEBUG("(not available)"); - if (pcubMsgSize) *pcubMsgSize = 0; - return false; -} - -bool IsP2PPacketAvailable( uint32 *pcubMsgSize) -{ - PRINT_DEBUG("old"); - return IsP2PPacketAvailable(pcubMsgSize, OLD_CHANNEL_NUMBER); -} - -// reads in a packet that has been sent from another user via SendP2PPacket() -// returns the size of the message and the steamID of the user who sent it in the last two parameters -// if the buffer passed in is too small, the message will be truncated -// this call is not blocking, and will return false if no data is available -bool ReadP2PPacket( void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote, int nChannel) -{ - PRINT_DEBUG("%u %i", cubDest, nChannel); - std::lock_guard lock(messages_mutex); - //Not sure if this should be here because it slightly screws up games that don't like such low "pings" - //Commenting it out for now because it looks like it causes a bug where 20xx gets stuck in an infinite receive packet loop - //this->network->Run(); - //RunCallbacks(); - - bool read = false; - PRINT_DEBUG("Number messages %zu", messages.size()); - auto msg = std::begin(messages); - while (msg != std::end(messages)) { - if (connection_exists((uint64)msg->source_id()) && msg->network().channel() == nChannel && msg->network().processed()) { - uint32 msg_size = msg->network().data().size(); - if (msg_size > cubDest) msg_size = cubDest; - if (pcubMsgSize) *pcubMsgSize = msg_size; - memcpy(pubDest, msg->network().data().data(), msg_size); - - PRINT_DEBUG("%s", - common_helpers::uint8_vector_to_hex_string(std::vector((uint8_t*)pubDest, (uint8_t*)pubDest + msg_size)).c_str()); - - *psteamIDRemote = CSteamID((uint64)msg->source_id()); - PRINT_DEBUG("len %u channel: %u from: " "%" PRIu64 "", msg_size, nChannel, msg->source_id()); - msg = messages.erase(msg); - return true; - } - - ++msg; - } - - if (pcubMsgSize) *pcubMsgSize = 0; - if (psteamIDRemote) *psteamIDRemote = k_steamIDNil; - return false; -} - -bool ReadP2PPacket( void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote) -{ - PRINT_DEBUG("old"); - return ReadP2PPacket(pubDest, cubDest, pcubMsgSize, psteamIDRemote, OLD_CHANNEL_NUMBER); -} - -// AcceptP2PSessionWithUser() should only be called in response to a P2PSessionRequest_t callback -// P2PSessionRequest_t will be posted if another user tries to send you a packet that you haven't talked to yet -// if you don't want to talk to the user, just ignore the request -// if the user continues to send you packets, another P2PSessionRequest_t will be posted periodically -// this may be called multiple times for a single user -// (if you've called SendP2PPacket() on the other user, this implicitly accepts the session request) -bool AcceptP2PSessionWithUser( CSteamID steamIDRemote ) -{ - PRINT_DEBUG("%llu", steamIDRemote.ConvertToUint64()); - std::lock_guard lock(global_mutex); - struct Steam_Networking_Connection *conn = get_or_create_connection(steamIDRemote); - if (conn) new_connection_times.erase(steamIDRemote); - return !!conn; -} - - -// call CloseP2PSessionWithUser() when you're done talking to a user, will free up resources under-the-hood -// if the remote user tries to send data to you again, another P2PSessionRequest_t callback will be posted -bool CloseP2PSessionWithUser( CSteamID steamIDRemote ) -{ - PRINT_DEBUG("%llu", steamIDRemote.ConvertToUint64()); - std::lock_guard lock(global_mutex); - if (!connection_exists(steamIDRemote)) { - - return false; - } - - remove_connection(steamIDRemote); - - return true; -} - - -// call CloseP2PChannelWithUser() when you're done talking to a user on a specific channel. Once all channels -// open channels to a user have been closed, the open session to the user will be closed and new data from this -// user will trigger a P2PSessionRequest_t callback -bool CloseP2PChannelWithUser( CSteamID steamIDRemote, int nChannel ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!connection_exists(steamIDRemote)) { - return false; - } - - struct Steam_Networking_Connection *conn = get_or_create_connection(steamIDRemote); - - conn->open_channels.erase(nChannel); - if (conn->open_channels.size() == 0) { - remove_connection(steamIDRemote); - } - - return true; -} - - -// fills out P2PSessionState_t structure with details about the underlying connection to the user -// should only needed for debugging purposes -// returns false if no connection exists to the specified user -bool GetP2PSessionState( CSteamID steamIDRemote, P2PSessionState_t *pConnectionState ) -{ - PRINT_DEBUG("%llu", steamIDRemote.ConvertToUint64()); - std::lock_guard lock(global_mutex); - if (!connection_exists(steamIDRemote) && (steamIDRemote != settings->get_local_steam_id())) { - if (pConnectionState) { - pConnectionState->m_bConnectionActive = false; - pConnectionState->m_bConnecting = false; - pConnectionState->m_eP2PSessionError = 0; - pConnectionState->m_bUsingRelay = false; - pConnectionState->m_nBytesQueuedForSend = 0; - pConnectionState->m_nPacketsQueuedForSend = 0; - pConnectionState->m_nRemoteIP = 0; - pConnectionState->m_nRemotePort = 0; - } - - PRINT_DEBUG("No Connection"); - return false; - } - - if (pConnectionState) { - pConnectionState->m_bConnectionActive = true; - pConnectionState->m_bConnecting = false; - pConnectionState->m_eP2PSessionError = 0; - pConnectionState->m_bUsingRelay = false; - pConnectionState->m_nBytesQueuedForSend = 0; - pConnectionState->m_nPacketsQueuedForSend = 0; - pConnectionState->m_nRemoteIP = network->getIP(steamIDRemote); - pConnectionState->m_nRemotePort = 12345; - } - - PRINT_DEBUG("Connection"); - return true; -} - - -// Allow P2P connections to fall back to being relayed through the Steam servers if a direct connection -// or NAT-traversal cannot be established. Only applies to connections created after setting this value, -// or to existing connections that need to automatically reconnect after this value is set. -// -// P2P packet relay is allowed by default -bool AllowP2PPacketRelay( bool bAllow ) -{ - PRINT_DEBUG("%u", bAllow); - return true; -} - - - -//////////////////////////////////////////////////////////////////////////////////////////// -// LISTEN / CONNECT style interface functions -// -// This is an older set of functions designed around the Berkeley TCP sockets model -// it's preferential that you use the above P2P functions, they're more robust -// and these older functions will be removed eventually -// -//////////////////////////////////////////////////////////////////////////////////////////// - -SNetListenSocket_t socket_number = 0; -// creates a socket and listens others to connect -// will trigger a SocketStatusCallback_t callback on another client connecting -// nVirtualP2PPort is the unique ID that the client will connect to, in case you have multiple ports -// this can usually just be 0 unless you want multiple sets of connections -// unIP is the local IP address to bind to -// pass in 0 if you just want the default local IP -// unPort is the port to use -// pass in 0 if you don't want users to be able to connect via IP/Port, but expect to be always peer-to-peer connections only -SNetListenSocket_t CreateListenSocket( int nVirtualP2PPort, uint32 nIP, uint16 nPort, bool bAllowUseOfPacketRelay ) -{ - PRINT_DEBUG("old %i %u %hu %u", nVirtualP2PPort, nIP, nPort, bAllowUseOfPacketRelay); - std::lock_guard lock(global_mutex); - for (auto & c : listen_sockets) { - if (c.nVirtualP2PPort == nVirtualP2PPort || c.nPort == nPort) - return 0; - } - - ++socket_number; - if (!socket_number) ++socket_number; - - struct steam_listen_socket socket; - socket.id = socket_number; - socket.nVirtualP2PPort = nVirtualP2PPort; - socket.nIP = nIP; - socket.nPort = nPort; - listen_sockets.push_back(socket); - return socket.id; -} - -SNetListenSocket_t CreateListenSocket( int nVirtualP2PPort, SteamIPAddress_t nIP, uint16 nPort, bool bAllowUseOfPacketRelay ) -{ - PRINT_DEBUG("%i %i %u %hu %u", nVirtualP2PPort, nIP.m_eType, nIP.m_unIPv4, nPort, bAllowUseOfPacketRelay); - //TODO: ipv6 - return CreateListenSocket(nVirtualP2PPort, nIP.m_unIPv4, nPort, bAllowUseOfPacketRelay); -} - -SNetListenSocket_t CreateListenSocket( int nVirtualP2PPort, uint32 nIP, uint16 nPort ) -{ - PRINT_DEBUG("old"); - return CreateListenSocket(nVirtualP2PPort, nIP, nPort, true); -} - -// creates a socket and begin connection to a remote destination -// can connect via a known steamID (client or game server), or directly to an IP -// on success will trigger a SocketStatusCallback_t callback -// on failure or timeout will trigger a SocketStatusCallback_t callback with a failure code in m_eSNetSocketState -SNetSocket_t CreateP2PConnectionSocket( CSteamID steamIDTarget, int nVirtualPort, int nTimeoutSec, bool bAllowUseOfPacketRelay ) -{ - PRINT_DEBUG("%llu %i %i %u", steamIDTarget.ConvertToUint64(), nVirtualPort, nTimeoutSec, bAllowUseOfPacketRelay); - std::lock_guard lock(global_mutex); - //TODO: nTimeoutSec - return create_connection_socket(steamIDTarget, nVirtualPort, 0, 0); -} - -SNetSocket_t CreateP2PConnectionSocket( CSteamID steamIDTarget, int nVirtualPort, int nTimeoutSec ) -{ - PRINT_DEBUG("old"); - return CreateP2PConnectionSocket(steamIDTarget, nVirtualPort, nTimeoutSec, true); -} - -SNetSocket_t CreateConnectionSocket( uint32 nIP, uint16 nPort, int nTimeoutSec ) -{ - PRINT_DEBUG("%u %hu %i", nIP, nPort, nTimeoutSec); - std::lock_guard lock(global_mutex); - //TODO: nTimeoutSec - return create_connection_socket((uint64)0, 0, nIP, nPort); -} - -SNetSocket_t CreateConnectionSocket( SteamIPAddress_t nIP, uint16 nPort, int nTimeoutSec ) -{ - PRINT_DEBUG("%i %u %hu %i", nIP.m_eType, nIP.m_unIPv4, nPort, nTimeoutSec); - //TODO: ipv6 - return CreateConnectionSocket(nIP.m_unIPv4, nPort, nTimeoutSec); -} - -// disconnects the connection to the socket, if any, and invalidates the handle -// any unread data on the socket will be thrown away -// if bNotifyRemoteEnd is set, socket will not be completely destroyed until the remote end acknowledges the disconnect -bool DestroySocket( SNetSocket_t hSocket, bool bNotifyRemoteEnd ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct steam_connection_socket *socket = get_connection_socket(hSocket); - if (!socket || socket->status == SOCKET_KILLED) return false; - socket->status = SOCKET_KILLED; - return true; -} - -// destroying a listen socket will automatically kill all the regular sockets generated from it -bool DestroyListenSocket( SNetListenSocket_t hSocket, bool bNotifyRemoteEnd ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - auto c = std::begin(listen_sockets); - while (c != std::end(listen_sockets)) { - if (c->id == hSocket) { - c = listen_sockets.erase(c); - for (auto & socket : connection_sockets) { - if (socket.listen_id == hSocket) { - socket.status = SOCKET_KILLED; - } - } - return true; - } else { - ++c; - } - } - - return false; -} - - -// sending data -// must be a handle to a connected socket -// data is all sent via UDP, and thus send sizes are limited to 1200 bytes; after this, many routers will start dropping packets -// use the reliable flag with caution; although the resend rate is pretty aggressive, -// it can still cause stalls in receiving data (like TCP) -bool SendDataOnSocket( SNetSocket_t hSocket, void *pubData, uint32 cubData, bool bReliable ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct steam_connection_socket *socket = get_connection_socket(hSocket); - if (!socket || socket->status != SOCKET_CONNECTED) return false; - - Common_Message msg; - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_dest_id(socket->target.ConvertToUint64()); - msg.set_allocated_network_old(new Network_Old); - msg.mutable_network_old()->set_type(Network_Old::DATA); - msg.mutable_network_old()->set_connection_id(socket->other_id); - msg.mutable_network_old()->set_data(pubData, cubData); - return network->sendTo(&msg, bReliable); -} - - -// receiving data -// returns false if there is no data remaining -// fills out *pcubMsgSize with the size of the next message, in bytes -bool IsDataAvailableOnSocket( SNetSocket_t hSocket, uint32 *pcubMsgSize ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct steam_connection_socket *socket = get_connection_socket(hSocket); - if (!socket) { - if (pcubMsgSize) *pcubMsgSize = 0; - return false; - } - - if (socket->data_packets.size() == 0) return false; - if (pcubMsgSize) *pcubMsgSize = socket->data_packets[0].data().size(); - return true; -} - - -// fills in pubDest with the contents of the message -// messages are always complete, of the same size as was sent (i.e. packetized, not streaming) -// if *pcubMsgSize < cubDest, only partial data is written -// returns false if no data is available -bool RetrieveDataFromSocket( SNetSocket_t hSocket, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct steam_connection_socket *socket = get_connection_socket(hSocket); - if (!socket || socket->data_packets.size() == 0) return false; - - auto msg = std::begin(socket->data_packets); - if (msg != std::end(socket->data_packets)) { - uint32 msg_size = msg->data().size(); - if (msg_size > cubDest) msg_size = cubDest; - if (pcubMsgSize) *pcubMsgSize = msg_size; - memcpy(pubDest, msg->data().data(), msg_size); - msg = socket->data_packets.erase(msg); - return true; - } - - return false; -} - - -// checks for data from any socket that has been connected off this listen socket -// returns false if there is no data remaining -// fills out *pcubMsgSize with the size of the next message, in bytes -// fills out *phSocket with the socket that data is available on -bool IsDataAvailable( SNetListenSocket_t hListenSocket, uint32 *pcubMsgSize, SNetSocket_t *phSocket ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!hListenSocket) return false; - - for (auto & socket : connection_sockets) { - if (socket.listen_id == hListenSocket && socket.data_packets.size()) { - if (pcubMsgSize) *pcubMsgSize = socket.data_packets[0].data().size(); - if (phSocket) *phSocket = socket.id; - return true; - } - } - - return false; -} - - -// retrieves data from any socket that has been connected off this listen socket -// fills in pubDest with the contents of the message -// messages are always complete, of the same size as was sent (i.e. packetized, not streaming) -// if *pcubMsgSize < cubDest, only partial data is written -// returns false if no data is available -// fills out *phSocket with the socket that data is available on -bool RetrieveData( SNetListenSocket_t hListenSocket, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, SNetSocket_t *phSocket ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!hListenSocket) return false; - - for (auto & socket : connection_sockets) { - if (socket.listen_id == hListenSocket && socket.data_packets.size()) { - auto msg = std::begin(socket.data_packets); - if (msg != std::end(socket.data_packets)) { - uint32 msg_size = msg->data().size(); - if (msg_size > cubDest) msg_size = cubDest; - if (pcubMsgSize) *pcubMsgSize = msg_size; - if (phSocket) *phSocket = socket.id; - memcpy(pubDest, msg->data().data(), msg_size); - msg = socket.data_packets.erase(msg); - return true; - } - } - } - - return false; -} - - -// returns information about the specified socket, filling out the contents of the pointers -bool GetSocketInfo( SNetSocket_t hSocket, CSteamID *pSteamIDRemote, int *peSocketStatus, uint32 *punIPRemote, uint16 *punPortRemote ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct steam_connection_socket *socket = get_connection_socket(hSocket); - if (!socket) return false; - if (pSteamIDRemote) *pSteamIDRemote = socket->target; - if (peSocketStatus) { - //TODO: I'm not sure what peSocketStatus is supposed to be but I'm guessing it's ESNetSocketState - if (socket->status == SOCKET_CONNECTED) { - *peSocketStatus = k_ESNetSocketStateConnected; - } else if (socket->status == SOCKET_CONNECTING) { - *peSocketStatus = k_ESNetSocketStateInitiated; - } else if (socket->status == SOCKET_DISCONNECTED) { - *peSocketStatus = k_ESNetSocketStateDisconnecting; - } else if (socket->status == SOCKET_KILLED) { - *peSocketStatus = k_ESNetSocketStateConnectionBroken; - } else { - *peSocketStatus = k_ESNetSocketStateInvalid; - } - } - - if (punIPRemote) *punIPRemote = socket->nIP; - if (punPortRemote) *punPortRemote = socket->nPort; - return true; -} - -bool GetSocketInfo( SNetSocket_t hSocket, CSteamID *pSteamIDRemote, int *peSocketStatus, SteamIPAddress_t *punIPRemote, uint16 *punPortRemote ) -{ - PRINT_DEBUG_ENTRY(); - //TODO: ipv6 - uint32 *ip_remote = NULL; - if (punIPRemote) { - ip_remote = &(punIPRemote->m_unIPv4); - } - - bool ret = GetSocketInfo(hSocket, pSteamIDRemote, peSocketStatus, ip_remote, punPortRemote ); - if (punIPRemote && ret) { - punIPRemote->m_eType = k_ESteamIPTypeIPv4; - } - - return ret; -} - -// returns which local port the listen socket is bound to -// *pnIP and *pnPort will be 0 if the socket is set to listen for P2P connections only -bool GetListenSocketInfo( SNetListenSocket_t hListenSocket, uint32 *pnIP, uint16 *pnPort ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - auto conn = std::find_if(listen_sockets.begin(), listen_sockets.end(), [&hListenSocket](struct steam_listen_socket const& conn) { return conn.id == hListenSocket;}); - if (conn == listen_sockets.end()) return false; - if (pnIP) *pnIP = conn->nIP; - if (pnPort) *pnPort = conn->nPort; - return true; -} - -bool GetListenSocketInfo( SNetListenSocket_t hListenSocket, SteamIPAddress_t *pnIP, uint16 *pnPort ) -{ - PRINT_DEBUG_ENTRY(); - //TODO: ipv6 - uint32 *ip = NULL; - if (pnIP) { - ip = &(pnIP->m_unIPv4); - } - - bool ret = GetListenSocketInfo(hListenSocket, ip, pnPort ); - if (pnIP && ret) { - pnIP->m_eType = k_ESteamIPTypeIPv4; - } - - return ret; -} - -// returns true to describe how the socket ended up connecting -ESNetSocketConnectionType GetSocketConnectionType( SNetSocket_t hSocket ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct steam_connection_socket *socket = get_connection_socket(hSocket); - if (!socket || socket->status != SOCKET_CONNECTED) return k_ESNetSocketConnectionTypeNotConnected; - else return k_ESNetSocketConnectionTypeUDP; -} - - -// max packet size, in bytes -int GetMaxPacketSize( SNetSocket_t hSocket ) -{ - PRINT_DEBUG_ENTRY(); - return 1500; -} - -void RunCallbacks() -{ - uint64 current_time = std::chrono::duration_cast>(std::chrono::system_clock::now().time_since_epoch()).count(); - - { - std::lock_guard lock(messages_mutex); - - { - auto msg = std::begin(unprocessed_messages); - while (msg != std::end(unprocessed_messages)) { - CSteamID source_id((uint64)msg->source_id()); - if (!connection_exists(source_id)) { - if (new_connection_times.find(source_id) == new_connection_times.end()) { - new_connections_to_call_cb.push(source_id); - new_connection_times[source_id] = std::chrono::high_resolution_clock::now(); - } - } else { - struct Steam_Networking_Connection *conn = get_or_create_connection(source_id); - conn->open_channels.insert(msg->network().channel()); - } - - msg->mutable_network()->set_processed(true); - msg->mutable_network()->set_time_processed(current_time); - messages.push_back(*msg); - msg = unprocessed_messages.erase(msg); - } - } - - auto msg = std::begin(messages); - while (msg != std::end(messages)) { - bool deleted = false; - if (msg->network().processed()) { - if (!connection_exists((uint64)msg->source_id())) { - if (msg->network().time_processed() + ORPHANED_PACKET_TIMEOUT < current_time) { - deleted = true; - } - } - } - - if (deleted) { - msg = messages.erase(msg); - } else { - ++msg; - } - } - - } - - while (!new_connections_to_call_cb.empty()) { - CSteamID source_id = new_connections_to_call_cb.front(); - auto t = new_connection_times.find(source_id); - if (t == new_connection_times.end()) { - new_connections_to_call_cb.pop(); - continue; - } - - if (!check_timedout(t->second, NEW_CONNECTION_DELAY)) { - break; - } - - P2PSessionRequest_t data; - memset(&data, 0, sizeof(data)); - data.m_steamIDRemote = source_id; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - new_connections_to_call_cb.pop(); - } - - //TODO: not sure if sockets should be wiped right away - remove_killed_connection_sockets(); - - for(auto it = new_connection_times.begin(); it != new_connection_times.end(); ) { - if (std::chrono::duration_cast>(std::chrono::high_resolution_clock::now() - it->second).count() > NEW_CONNECTION_TIMEOUT) { - it = new_connection_times.erase(it); - //TODO send packet to other side to tell them connection has "failed". - } else { - ++it; - } - } -} - -void Callback(Common_Message *msg) -{ - if (msg->has_network()) { - PRINT_DEBUG("got msg from: " "%" PRIu64 " to: " "%" PRIu64 " size %zu type %u | messages %p: %zu", - msg->source_id(), msg->dest_id(), msg->network().data().size(), msg->network().type(), &messages, messages.size() - ); - PRINT_DEBUG("msg data: '%s'", - common_helpers::uint8_vector_to_hex_string(std::vector(msg->network().data().begin(), msg->network().data().end())).c_str()); - - if (msg->network().type() == Network_pb::DATA) { - unprocessed_messages.push_back(Common_Message(*msg)); - } - - if (msg->network().type() == Network_pb::NEW_CONNECTION) { - std::lock_guard lock(messages_mutex); - auto msg_temp = std::begin(messages); - while (msg_temp != std::end(messages)) { - //only delete processed to handle unreliable message arriving at the same time. - if (msg_temp->source_id() == msg->source_id() && msg_temp->network().processed()) { - msg_temp = messages.erase(msg_temp); - } else { - ++msg_temp; - } - } - } - } - - if (msg->has_network_old()) { - PRINT_DEBUG("got network socket msg %u", msg->network_old().type()); - if (msg->network_old().type() == Network_Old::CONNECTION_REQUEST_IP) { - for (auto & listen : listen_sockets) { - if (listen.nPort == msg->network_old().port()) { - SNetSocket_t new_sock = create_connection_socket((uint64)msg->source_id(), 0, 0, msg->network_old().port(), listen.id, SOCKET_CONNECTED, msg->network_old().connection_id_from()); - if (new_sock) { - struct SocketStatusCallback_t data; - data.m_hSocket = new_sock; - data.m_hListenSocket = listen.id; - data.m_steamIDRemote = (uint64)msg->source_id(); - data.m_eSNetSocketState = k_ESNetSocketStateConnected; //TODO is this the right state? - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - } - } - } else if (msg->network_old().type() == Network_Old::CONNECTION_REQUEST_STEAMID) { - for (auto & listen : listen_sockets) { - if (listen.nVirtualP2PPort == msg->network_old().port()) { - SNetSocket_t new_sock = create_connection_socket((uint64)msg->source_id(), msg->network_old().port(), 0, 0, listen.id, SOCKET_CONNECTED, msg->network_old().connection_id_from()); - if (new_sock) { - struct SocketStatusCallback_t data; - data.m_hSocket = new_sock; - data.m_hListenSocket = listen.id; - data.m_steamIDRemote = (uint64)msg->source_id(); - data.m_eSNetSocketState = k_ESNetSocketStateConnected; //TODO is this the right state? - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - } - } - - } else if (msg->network_old().type() == Network_Old::CONNECTION_ACCEPTED) { - struct steam_connection_socket *socket = get_connection_socket(msg->network_old().connection_id()); - if (socket && socket->nPort && socket->status == SOCKET_CONNECTING && !socket->target.IsValid()) { - socket->target = (uint64)msg->source_id(); - } - - if (socket && socket->status == SOCKET_CONNECTING && msg->source_id() == socket->target.ConvertToUint64()) { - socket->status = SOCKET_CONNECTED; - socket->other_id = msg->network_old().connection_id_from(); - struct SocketStatusCallback_t data; - data.m_hSocket = socket->id; - data.m_hListenSocket = socket->listen_id; - data.m_steamIDRemote = socket->target; - data.m_eSNetSocketState = k_ESNetSocketStateConnected; //TODO is this the right state? - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - } else if (msg->network_old().type() == Network_Old::CONNECTION_END) { - struct steam_connection_socket *socket = get_connection_socket(msg->network_old().connection_id()); - if (socket && socket->status == SOCKET_CONNECTED && msg->source_id() == socket->target.ConvertToUint64()) { - struct SocketStatusCallback_t data; - socket->status = SOCKET_DISCONNECTED; - data.m_hSocket = socket->id; - data.m_hListenSocket = socket->listen_id; - data.m_steamIDRemote = socket->target; - data.m_eSNetSocketState = k_ESNetSocketStateRemoteEndDisconnected; //TODO is this the right state? - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - } else if (msg->network_old().type() == Network_Old::DATA) { - struct steam_connection_socket *socket = get_connection_socket(msg->network_old().connection_id()); - if (socket && socket->status == SOCKET_CONNECTED && msg->source_id() == socket->target.ConvertToUint64()) { - socket->data_packets.push_back(msg->network_old()); - } - } - } - - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::DISCONNECT) { - CSteamID source_id((uint64)msg->source_id()); - if (connection_exists(source_id)) { - P2PSessionConnectFail_t data; - data.m_steamIDRemote = source_id; - data.m_eP2PSessionError = k_EP2PSessionErrorDestinationNotLoggedIn; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - - for (auto & socket : connection_sockets) { - if (socket.target.ConvertToUint64() == msg->source_id()) { - struct SocketStatusCallback_t data; - socket.status = SOCKET_DISCONNECTED; - data.m_hSocket = socket.id; - data.m_hListenSocket = socket.listen_id; - data.m_steamIDRemote = socket.target; - data.m_eSNetSocketState = k_ESNetSocketStateConnectionBroken; //TODO is this the right state? - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - } - } else - - if (msg->low_level().type() == Low_Level::CONNECT) { - - } - } -} + Steam_Networking(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_Networking(); + + //////////////////////////////////////////////////////////////////////////////////////////// + // Session-less connection functions + // automatically establishes NAT-traversing or Relay server connections + + // Sends a P2P packet to the specified user + // UDP-like, unreliable and a max packet size of 1200 bytes + // the first packet send may be delayed as the NAT-traversal code runs + // if we can't get through to the user, an error will be posted via the callback P2PSessionConnectFail_t + // see EP2PSend enum above for the descriptions of the different ways of sending packets + // + // nChannel is a routing number you can use to help route message to different systems - you'll have to call ReadP2PPacket() + // with the same channel number in order to retrieve the data on the other end + // using different channels to talk to the same user will still use the same underlying p2p connection, saving on resources + bool SendP2PPacket( CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType, int nChannel); + + bool SendP2PPacket( CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType ); + + // returns true if any data is available for read, and the amount of data that will need to be read + bool IsP2PPacketAvailable( uint32 *pcubMsgSize, int nChannel); + + bool IsP2PPacketAvailable( uint32 *pcubMsgSize); + + // reads in a packet that has been sent from another user via SendP2PPacket() + // returns the size of the message and the steamID of the user who sent it in the last two parameters + // if the buffer passed in is too small, the message will be truncated + // this call is not blocking, and will return false if no data is available + bool ReadP2PPacket( void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote, int nChannel); + + bool ReadP2PPacket( void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote); + + // AcceptP2PSessionWithUser() should only be called in response to a P2PSessionRequest_t callback + // P2PSessionRequest_t will be posted if another user tries to send you a packet that you haven't talked to yet + // if you don't want to talk to the user, just ignore the request + // if the user continues to send you packets, another P2PSessionRequest_t will be posted periodically + // this may be called multiple times for a single user + // (if you've called SendP2PPacket() on the other user, this implicitly accepts the session request) + bool AcceptP2PSessionWithUser( CSteamID steamIDRemote ); + + + // call CloseP2PSessionWithUser() when you're done talking to a user, will free up resources under-the-hood + // if the remote user tries to send data to you again, another P2PSessionRequest_t callback will be posted + bool CloseP2PSessionWithUser( CSteamID steamIDRemote ); + + + // call CloseP2PChannelWithUser() when you're done talking to a user on a specific channel. Once all channels + // open channels to a user have been closed, the open session to the user will be closed and new data from this + // user will trigger a P2PSessionRequest_t callback + bool CloseP2PChannelWithUser( CSteamID steamIDRemote, int nChannel ); + + + // fills out P2PSessionState_t structure with details about the underlying connection to the user + // should only needed for debugging purposes + // returns false if no connection exists to the specified user + bool GetP2PSessionState( CSteamID steamIDRemote, P2PSessionState_t *pConnectionState ); + + + // Allow P2P connections to fall back to being relayed through the Steam servers if a direct connection + // or NAT-traversal cannot be established. Only applies to connections created after setting this value, + // or to existing connections that need to automatically reconnect after this value is set. + // + // P2P packet relay is allowed by default + bool AllowP2PPacketRelay( bool bAllow ); + + + + //////////////////////////////////////////////////////////////////////////////////////////// + // LISTEN / CONNECT style interface functions + // + // This is an older set of functions designed around the Berkeley TCP sockets model + // it's preferential that you use the above P2P functions, they're more robust + // and these older functions will be removed eventually + // + //////////////////////////////////////////////////////////////////////////////////////////// + + // creates a socket and listens others to connect + // will trigger a SocketStatusCallback_t callback on another client connecting + // nVirtualP2PPort is the unique ID that the client will connect to, in case you have multiple ports + // this can usually just be 0 unless you want multiple sets of connections + // unIP is the local IP address to bind to + // pass in 0 if you just want the default local IP + // unPort is the port to use + // pass in 0 if you don't want users to be able to connect via IP/Port, but expect to be always peer-to-peer connections only + SNetListenSocket_t CreateListenSocket( int nVirtualP2PPort, uint32 nIP, uint16 nPort, bool bAllowUseOfPacketRelay ); + + SNetListenSocket_t CreateListenSocket( int nVirtualP2PPort, SteamIPAddress_t nIP, uint16 nPort, bool bAllowUseOfPacketRelay ); + + SNetListenSocket_t CreateListenSocket( int nVirtualP2PPort, uint32 nIP, uint16 nPort ); + + // creates a socket and begin connection to a remote destination + // can connect via a known steamID (client or game server), or directly to an IP + // on success will trigger a SocketStatusCallback_t callback + // on failure or timeout will trigger a SocketStatusCallback_t callback with a failure code in m_eSNetSocketState + SNetSocket_t CreateP2PConnectionSocket( CSteamID steamIDTarget, int nVirtualPort, int nTimeoutSec, bool bAllowUseOfPacketRelay ); + + SNetSocket_t CreateP2PConnectionSocket( CSteamID steamIDTarget, int nVirtualPort, int nTimeoutSec ); + + SNetSocket_t CreateConnectionSocket( uint32 nIP, uint16 nPort, int nTimeoutSec ); + + SNetSocket_t CreateConnectionSocket( SteamIPAddress_t nIP, uint16 nPort, int nTimeoutSec ); + + // disconnects the connection to the socket, if any, and invalidates the handle + // any unread data on the socket will be thrown away + // if bNotifyRemoteEnd is set, socket will not be completely destroyed until the remote end acknowledges the disconnect + bool DestroySocket( SNetSocket_t hSocket, bool bNotifyRemoteEnd ); + + // destroying a listen socket will automatically kill all the regular sockets generated from it + bool DestroyListenSocket( SNetListenSocket_t hSocket, bool bNotifyRemoteEnd ); + + + // sending data + // must be a handle to a connected socket + // data is all sent via UDP, and thus send sizes are limited to 1200 bytes; after this, many routers will start dropping packets + // use the reliable flag with caution; although the resend rate is pretty aggressive, + // it can still cause stalls in receiving data (like TCP) + bool SendDataOnSocket( SNetSocket_t hSocket, void *pubData, uint32 cubData, bool bReliable ); + + + // receiving data + // returns false if there is no data remaining + // fills out *pcubMsgSize with the size of the next message, in bytes + bool IsDataAvailableOnSocket( SNetSocket_t hSocket, uint32 *pcubMsgSize ); + + + // fills in pubDest with the contents of the message + // messages are always complete, of the same size as was sent (i.e. packetized, not streaming) + // if *pcubMsgSize < cubDest, only partial data is written + // returns false if no data is available + bool RetrieveDataFromSocket( SNetSocket_t hSocket, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize ); + + + // checks for data from any socket that has been connected off this listen socket + // returns false if there is no data remaining + // fills out *pcubMsgSize with the size of the next message, in bytes + // fills out *phSocket with the socket that data is available on + bool IsDataAvailable( SNetListenSocket_t hListenSocket, uint32 *pcubMsgSize, SNetSocket_t *phSocket ); + + + // retrieves data from any socket that has been connected off this listen socket + // fills in pubDest with the contents of the message + // messages are always complete, of the same size as was sent (i.e. packetized, not streaming) + // if *pcubMsgSize < cubDest, only partial data is written + // returns false if no data is available + // fills out *phSocket with the socket that data is available on + bool RetrieveData( SNetListenSocket_t hListenSocket, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, SNetSocket_t *phSocket ); + + + // returns information about the specified socket, filling out the contents of the pointers + bool GetSocketInfo( SNetSocket_t hSocket, CSteamID *pSteamIDRemote, int *peSocketStatus, uint32 *punIPRemote, uint16 *punPortRemote ); + + bool GetSocketInfo( SNetSocket_t hSocket, CSteamID *pSteamIDRemote, int *peSocketStatus, SteamIPAddress_t *punIPRemote, uint16 *punPortRemote ); + + // returns which local port the listen socket is bound to + // *pnIP and *pnPort will be 0 if the socket is set to listen for P2P connections only + bool GetListenSocketInfo( SNetListenSocket_t hListenSocket, uint32 *pnIP, uint16 *pnPort ); + + bool GetListenSocketInfo( SNetListenSocket_t hListenSocket, SteamIPAddress_t *pnIP, uint16 *pnPort ); + + // returns true to describe how the socket ended up connecting + ESNetSocketConnectionType GetSocketConnectionType( SNetSocket_t hSocket ); + + + // max packet size, in bytes + int GetMaxPacketSize( SNetSocket_t hSocket ); + + void RunCallbacks(); + + void Callback(Common_Message *msg); + }; + +#endif // __INCLUDED_STEAM_NETWORKING_H__ diff --git a/dll/dll/steam_networking_messages.h b/dll/dll/steam_networking_messages.h index 8e946d62..c1ba5949 100644 --- a/dll/dll/steam_networking_messages.h +++ b/dll/dll/steam_networking_messages.h @@ -15,19 +15,20 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_NETWORKING_MESSAGES_H__ +#define __INCLUDED_STEAM_NETWORKING_MESSAGES_H__ + #include "base.h" -#define NETWORKING_MESSAGES_TIMEOUT 30.0 - struct Steam_Message_Connection { - SteamNetworkingIdentity remote_identity; - std::map> data; + SteamNetworkingIdentity remote_identity{}; + std::map> data{}; - std::list channels; + std::list channels{}; bool accepted = false; bool dead = false; - unsigned id; + unsigned id{}; unsigned remote_id = 0; std::chrono::high_resolution_clock::time_point created = std::chrono::high_resolution_clock::now(); @@ -36,412 +37,119 @@ struct Steam_Message_Connection { class Steam_Networking_Messages : public ISteamNetworkingMessages { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; - std::map connections; - std::list incoming_data; + std::map connections{}; + std::list incoming_data{}; unsigned id_counter = 0; - std::chrono::steady_clock::time_point created; + std::chrono::steady_clock::time_point created{}; + + static void free_steam_message_data(SteamNetworkingMessage_t *pMsg); + static void delete_steam_message(SteamNetworkingMessage_t *pMsg); + + static void steam_callback(void *object, Common_Message *msg); + static void steam_run_every_runcb(void *object); + + std::map::iterator find_or_create_message_connection(SteamNetworkingIdentity identityRemote, bool incoming, bool restartbroken); + + void end_connection(CSteamID steam_id); + + void RunCallbacks(); + void Callback(Common_Message *msg); + public: -static void steam_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); + Steam_Networking_Messages(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_Networking_Messages(); - Steam_Networking_Messages *steam_networking_messages = (Steam_Networking_Messages *)object; - steam_networking_messages->Callback(msg); -} + /// Sends a message to the specified host. If we don't already have a session with that user, + /// a session is implicitly created. There might be some handshaking that needs to happen + /// before we can actually begin sending message data. If this handshaking fails and we can't + /// get through, an error will be posted via the callback SteamNetworkingMessagesSessionFailed_t. + /// There is no notification when the operation succeeds. (You should have the peer send a reply + /// for this purpose.) + /// + /// Sending a message to a host will also implicitly accept any incoming connection from that host. + /// + /// nSendFlags is a bitmask of k_nSteamNetworkingSend_xxx options + /// + /// nRemoteChannel is a routing number you can use to help route message to different systems. + /// You'll have to call ReceiveMessagesOnChannel() with the same channel number in order to retrieve + /// the data on the other end. + /// + /// Using different channels to talk to the same user will still use the same underlying + /// connection, saving on resources. If you don't need this feature, use 0. + /// Otherwise, small integers are the most efficient. + /// + /// It is guaranteed that reliable messages to the same host on the same channel + /// will be be received by the remote host (if they are received at all) exactly once, + /// and in the same order that they were send. + /// + /// NO other order guarantees exist! In particular, unreliable messages may be dropped, + /// received out of order with respect to each other and with respect to reliable data, + /// or may be received multiple times. Messages on different channels are *not* guaranteed + /// to be received in the order they were sent. + /// + /// A note for those familiar with TCP/IP ports, or converting an existing codebase that + /// opened multiple sockets: You might notice that there is only one channel, and with + /// TCP/IP each endpoint has a port number. You can think of the channel number as the + /// *destination* port. If you need each message to also include a "source port" (so the + /// recipient can route the reply), then just put that in your message. That is essentially + /// how UDP works! + /// + /// Returns: + /// - k_EREsultOK on success. + /// - k_EResultNoConnection will be returned if the session has failed or was closed by the peer, + /// and k_nSteamNetworkingSend_AutoRestartBrokwnSession is not used. (You can use + /// GetSessionConnectionInfo to get the details.) In order to acknowledge the broken session + /// and start a new one, you must call CloseSessionWithUser + /// - See SendMessageToConnection::SendMessageToConnection for more + EResult SendMessageToUser( const SteamNetworkingIdentity &identityRemote, const void *pubData, uint32 cubData, int nSendFlags, int nRemoteChannel ); -static void steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); + /// Reads the next message that has been sent from another user via SendMessageToUser() on the given channel. + /// Returns number of messages returned into your list. (0 if no message are available on that channel.) + /// + /// When you're done with the message object(s), make sure and call Release()! + int ReceiveMessagesOnChannel( int nLocalChannel, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ); - Steam_Networking_Messages *steam_networking_messages = (Steam_Networking_Messages *)object; - steam_networking_messages->RunCallbacks(); -} + /// AcceptSessionWithUser() should only be called in response to a SteamP2PSessionRequest_t callback + /// SteamP2PSessionRequest_t will be posted if another user tries to send you a message, and you haven't + /// tried to talk to them. If you don't want to talk to them, just ignore the request. + /// If the user continues to send you messages, SteamP2PSessionRequest_t callbacks will continue to + /// be posted periodically. This may be called multiple times for a single user. + /// + /// Calling SendMessage() on the other user, this implicitly accepts any pending session request. + bool AcceptSessionWithUser( const SteamNetworkingIdentity &identityRemote ); -Steam_Networking_Messages(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->run_every_runcb = run_every_runcb; - this->network->setCallback(CALLBACK_ID_NETWORKING_MESSAGES, settings->get_local_steam_id(), &Steam_Networking_Messages::steam_callback, this); - this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Messages::steam_callback, this); - this->run_every_runcb->add(&Steam_Networking_Messages::steam_run_every_runcb, this); + /// Call this when you're done talking to a user to immediately free up resources under-the-hood. + /// If the remote user tries to send data to you again, another P2PSessionRequest_t callback will + /// be posted. + /// + /// Note that sessions that go unused for a few minutes are automatically timed out. + bool CloseSessionWithUser( const SteamNetworkingIdentity &identityRemote ); - this->callback_results = callback_results; - this->callbacks = callbacks; + /// Call this when you're done talking to a user on a specific channel. Once all + /// open channels to a user have been closed, the open session to the user will be + /// closed, and any new data from this user will trigger a SteamP2PSessionRequest_t + /// callback + bool CloseChannelWithUser( const SteamNetworkingIdentity &identityRemote, int nLocalChannel ); - this->created = std::chrono::steady_clock::now(); -} - -~Steam_Networking_Messages() -{ - this->network->rmCallback(CALLBACK_ID_NETWORKING_MESSAGES, settings->get_local_steam_id(), &Steam_Networking_Messages::steam_callback, this); - this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Messages::steam_callback, this); - this->run_every_runcb->remove(&Steam_Networking_Messages::steam_run_every_runcb, this); -} - -std::map::iterator find_or_create_message_connection(SteamNetworkingIdentity identityRemote, bool incoming, bool restartbroken) -{ - auto conn = connections.find(identityRemote.GetSteamID()); - if (conn == connections.end() || (conn->second.dead && restartbroken)) { - ++id_counter; - struct Steam_Message_Connection con; - con.remote_identity = identityRemote; - con.id = id_counter; - connections[identityRemote.GetSteamID()] = con; - - Common_Message msg; - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_dest_id(con.remote_identity.GetSteamID64()); - msg.set_allocated_networking_messages(new Networking_Messages); - if (incoming) { - msg.mutable_networking_messages()->set_type(Networking_Messages::CONNECTION_ACCEPT); - } else { - msg.mutable_networking_messages()->set_type(Networking_Messages::CONNECTION_NEW); - } - msg.mutable_networking_messages()->set_channel(0); - msg.mutable_networking_messages()->set_id_from(con.id); - network->sendTo(&msg, true); - - conn = connections.find(identityRemote.GetSteamID()); - - if (incoming) { - SteamNetworkingMessagesSessionRequest_t data; - data.m_identityRemote = con.remote_identity; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - } - - if (!incoming) { - conn->second.accepted = true; - } - - return conn; -} - -/// Sends a message to the specified host. If we don't already have a session with that user, -/// a session is implicitly created. There might be some handshaking that needs to happen -/// before we can actually begin sending message data. If this handshaking fails and we can't -/// get through, an error will be posted via the callback SteamNetworkingMessagesSessionFailed_t. -/// There is no notification when the operation succeeds. (You should have the peer send a reply -/// for this purpose.) -/// -/// Sending a message to a host will also implicitly accept any incoming connection from that host. -/// -/// nSendFlags is a bitmask of k_nSteamNetworkingSend_xxx options -/// -/// nRemoteChannel is a routing number you can use to help route message to different systems. -/// You'll have to call ReceiveMessagesOnChannel() with the same channel number in order to retrieve -/// the data on the other end. -/// -/// Using different channels to talk to the same user will still use the same underlying -/// connection, saving on resources. If you don't need this feature, use 0. -/// Otherwise, small integers are the most efficient. -/// -/// It is guaranteed that reliable messages to the same host on the same channel -/// will be be received by the remote host (if they are received at all) exactly once, -/// and in the same order that they were send. -/// -/// NO other order guarantees exist! In particular, unreliable messages may be dropped, -/// received out of order with respect to each other and with respect to reliable data, -/// or may be received multiple times. Messages on different channels are *not* guaranteed -/// to be received in the order they were sent. -/// -/// A note for those familiar with TCP/IP ports, or converting an existing codebase that -/// opened multiple sockets: You might notice that there is only one channel, and with -/// TCP/IP each endpoint has a port number. You can think of the channel number as the -/// *destination* port. If you need each message to also include a "source port" (so the -/// recipient can route the reply), then just put that in your message. That is essentially -/// how UDP works! -/// -/// Returns: -/// - k_EREsultOK on success. -/// - k_EResultNoConnection will be returned if the session has failed or was closed by the peer, -/// and k_nSteamNetworkingSend_AutoRestartBrokwnSession is not used. (You can use -/// GetSessionConnectionInfo to get the details.) In order to acknowledge the broken session -/// and start a new one, you must call CloseSessionWithUser -/// - See SendMessageToConnection::SendMessageToConnection for more -EResult SendMessageToUser( const SteamNetworkingIdentity &identityRemote, const void *pubData, uint32 cubData, int nSendFlags, int nRemoteChannel ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - const SteamNetworkingIPAddr *ip = identityRemote.GetIPAddr(); - bool reliable = false; - if (nSendFlags & k_nSteamNetworkingSend_Reliable) { - reliable = true; - } - - bool restart_broken = false; - if (nSendFlags & k_nSteamNetworkingSend_AutoRestartBrokenSession) { - restart_broken = true; - } - - if (identityRemote.m_eType == k_ESteamNetworkingIdentityType_SteamID) { - PRINT_DEBUG("%llu", identityRemote.GetSteamID64()); - //steam id identity - } else if (ip) { - PRINT_DEBUG("%u:%u ipv4? %u", ip->GetIPv4(), ip->m_port, ip->IsIPv4()); - //ip addr - return k_EResultNoConnection; //TODO - } else { - return k_EResultNoConnection; - } - - auto conn = find_or_create_message_connection(identityRemote, false, restart_broken); - if (conn->second.dead) { - return k_EResultNoConnection; - } - - Common_Message msg; - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_dest_id(conn->second.remote_identity.GetSteamID64()); - msg.set_allocated_networking_messages(new Networking_Messages); - msg.mutable_networking_messages()->set_type(Networking_Messages::DATA); - msg.mutable_networking_messages()->set_channel(nRemoteChannel); - msg.mutable_networking_messages()->set_id_from(conn->second.id); - msg.mutable_networking_messages()->set_data(pubData, cubData); - - network->sendTo(&msg, reliable); - return k_EResultOK; -} - -static void free_steam_message_data(SteamNetworkingMessage_t *pMsg) -{ - free(pMsg->m_pData); - pMsg->m_pData = NULL; -} - -static void delete_steam_message(SteamNetworkingMessage_t *pMsg) -{ - if (pMsg->m_pfnFreeData) pMsg->m_pfnFreeData(pMsg); - delete pMsg; -} - -/// Reads the next message that has been sent from another user via SendMessageToUser() on the given channel. -/// Returns number of messages returned into your list. (0 if no message are available on that channel.) -/// -/// When you're done with the message object(s), make sure and call Release()! -int ReceiveMessagesOnChannel( int nLocalChannel, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - int message_counter = 0; - - for (auto & conn : connections) { - auto chan = conn.second.data.find(nLocalChannel); - if (chan != conn.second.data.end()) { - while (!chan->second.empty() && message_counter < nMaxMessages) { - SteamNetworkingMessage_t *pMsg = new SteamNetworkingMessage_t(); //TODO size is wrong - unsigned long size = chan->second.front().size(); - pMsg->m_pData = malloc(size); - pMsg->m_cbSize = size; - memcpy(pMsg->m_pData, chan->second.front().data(), size); - pMsg->m_conn = conn.second.id; - pMsg->m_identityPeer = conn.second.remote_identity; - pMsg->m_nConnUserData = -1; - pMsg->m_usecTimeReceived = std::chrono::duration_cast(std::chrono::steady_clock::now() - created).count(); - //TODO: messagenumber? - // pMsg->m_nMessageNumber = connect_socket->second.packet_receive_counter; - // ++connect_socket->second.packet_receive_counter; - - pMsg->m_pfnFreeData = &free_steam_message_data; - pMsg->m_pfnRelease = &delete_steam_message; - pMsg->m_nChannel = nLocalChannel; - ppOutMessages[message_counter] = pMsg; - ++message_counter; - chan->second.pop(); - } - } - - if (message_counter >= nMaxMessages) { - break; - } - } - - PRINT_DEBUG("got %u", message_counter); - return message_counter; -} - -/// AcceptSessionWithUser() should only be called in response to a SteamP2PSessionRequest_t callback -/// SteamP2PSessionRequest_t will be posted if another user tries to send you a message, and you haven't -/// tried to talk to them. If you don't want to talk to them, just ignore the request. -/// If the user continues to send you messages, SteamP2PSessionRequest_t callbacks will continue to -/// be posted periodically. This may be called multiple times for a single user. -/// -/// Calling SendMessage() on the other user, this implicitly accepts any pending session request. -bool AcceptSessionWithUser( const SteamNetworkingIdentity &identityRemote ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - auto conn = connections.find(identityRemote.GetSteamID()); - if (conn == connections.end()) { - return false; - } - - conn->second.accepted = true; - return true; -} - -/// Call this when you're done talking to a user to immediately free up resources under-the-hood. -/// If the remote user tries to send data to you again, another P2PSessionRequest_t callback will -/// be posted. -/// -/// Note that sessions that go unused for a few minutes are automatically timed out. -bool CloseSessionWithUser( const SteamNetworkingIdentity &identityRemote ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - auto conn = connections.find(identityRemote.GetSteamID()); - if (conn == connections.end()) { - return false; - } - - Common_Message msg; - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_dest_id(conn->second.remote_identity.GetSteamID64()); - msg.set_allocated_networking_messages(new Networking_Messages); - msg.mutable_networking_messages()->set_type(Networking_Messages::CONNECTION_END); - msg.mutable_networking_messages()->set_channel(0); - msg.mutable_networking_messages()->set_id_from(conn->second.id); - network->sendTo(&msg, true); - - connections.erase(conn); - return true; -} - -/// Call this when you're done talking to a user on a specific channel. Once all -/// open channels to a user have been closed, the open session to the user will be -/// closed, and any new data from this user will trigger a SteamP2PSessionRequest_t -/// callback -bool CloseChannelWithUser( const SteamNetworkingIdentity &identityRemote, int nLocalChannel ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - //TODO - return false; -} - -/// Returns information about the latest state of a connection, if any, with the given peer. -/// Primarily intended for debugging purposes, but can also be used to get more detailed -/// failure information. (See SendMessageToUser and k_nSteamNetworkingSend_AutoRestartBrokwnSession.) -/// -/// Returns the value of SteamNetConnectionInfo_t::m_eState, or k_ESteamNetworkingConnectionState_None -/// if no connection exists with specified peer. You may pass nullptr for either parameter if -/// you do not need the corresponding details. Note that sessions time out after a while, -/// so if a connection fails, or SendMessageToUser returns SendMessageToUser, you cannot wait -/// indefinitely to obtain the reason for failure. -ESteamNetworkingConnectionState GetSessionConnectionInfo( const SteamNetworkingIdentity &identityRemote, SteamNetConnectionInfo_t *pConnectionInfo, SteamNetConnectionRealTimeStatus_t *pQuickStatus ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - auto conn = connections.find(identityRemote.GetSteamID()); - if (conn == connections.end()) { - return k_ESteamNetworkingConnectionState_None; - } - - ESteamNetworkingConnectionState state = k_ESteamNetworkingConnectionState_Connected; - if (conn->second.remote_id == 0 || !conn->second.accepted) { - state = k_ESteamNetworkingConnectionState_Connecting; - } else if (conn->second.dead) { - state = k_ESteamNetworkingConnectionState_ClosedByPeer; - } - - if (pConnectionInfo) { - memset(pConnectionInfo, 0, sizeof(SteamNetConnectionInfo_t)); - pConnectionInfo->m_eState = state; - pConnectionInfo->m_identityRemote = conn->second.remote_identity; - //TODO - } - - if (pQuickStatus) { - memset(pQuickStatus, 0, sizeof(SteamNetConnectionRealTimeStatus_t)); - pQuickStatus->m_eState = state; - pQuickStatus->m_nPing = 10; //TODO: calculate real numbers? - pQuickStatus->m_flConnectionQualityLocal = 1.0; - pQuickStatus->m_flConnectionQualityRemote = 1.0; - //TODO - } - - return k_ESteamNetworkingConnectionState_Connected; -} - -void end_connection(CSteamID steam_id) -{ - auto conn = connections.find(steam_id); - if (conn != connections.end()) { - conn->second.dead = true; - } -} - -void RunCallbacks() -{ - auto msg = std::begin(incoming_data); - while (msg != std::end(incoming_data)) { - CSteamID source_id((uint64)msg->source_id()); - - auto conn = connections.find(source_id); - if (conn != connections.end()) { - if (conn->second.remote_id == msg->networking_messages().id_from()) - conn->second.data[msg->networking_messages().channel()].push(msg->networking_messages().data()); - } - - msg = incoming_data.erase(msg); - } - - auto conn = std::begin(connections); - while (conn != std::end(connections)) { - if (!conn->second.accepted && check_timedout(conn->second.created, NETWORKING_MESSAGES_TIMEOUT)) { - conn = connections.erase(conn); - } else { - ++conn; - } - } -} - -void Callback(Common_Message *msg) -{ - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - - } - - if (msg->low_level().type() == Low_Level::DISCONNECT) { - end_connection((uint64)msg->source_id()); - } - } - - if (msg->has_networking_messages()) { - PRINT_DEBUG("got network socket msg %u", msg->networking_messages().type()); - if (msg->networking_messages().type() == Networking_Messages::CONNECTION_NEW) { - SteamNetworkingIdentity identity; - identity.SetSteamID64(msg->source_id()); - auto conn = find_or_create_message_connection(identity, true, false); - conn->second.remote_id = msg->networking_messages().id_from(); - conn->second.dead = false; - } - - if (msg->networking_messages().type() == Networking_Messages::CONNECTION_ACCEPT) { - auto conn = connections.find((uint64)msg->source_id()); - if (conn != connections.end()) { - conn->second.remote_id = msg->networking_messages().id_from(); - } - } - - if (msg->networking_messages().type() == Networking_Messages::CONNECTION_END) { - end_connection((uint64)msg->source_id()); - } - - if (msg->networking_messages().type() == Networking_Messages::DATA) { - incoming_data.push_back(Common_Message(*msg)); - } - } -} + /// Returns information about the latest state of a connection, if any, with the given peer. + /// Primarily intended for debugging purposes, but can also be used to get more detailed + /// failure information. (See SendMessageToUser and k_nSteamNetworkingSend_AutoRestartBrokwnSession.) + /// + /// Returns the value of SteamNetConnectionInfo_t::m_eState, or k_ESteamNetworkingConnectionState_None + /// if no connection exists with specified peer. You may pass nullptr for either parameter if + /// you do not need the corresponding details. Note that sessions time out after a while, + /// so if a connection fails, or SendMessageToUser returns SendMessageToUser, you cannot wait + /// indefinitely to obtain the reason for failure. + ESteamNetworkingConnectionState GetSessionConnectionInfo( const SteamNetworkingIdentity &identityRemote, SteamNetConnectionInfo_t *pConnectionInfo, SteamNetConnectionRealTimeStatus_t *pQuickStatus ); }; + +#endif // __INCLUDED_STEAM_NETWORKING_MESSAGES_H__ diff --git a/dll/dll/steam_networking_sockets.h b/dll/dll/steam_networking_sockets.h index 2c1bb765..3bc626ea 100644 --- a/dll/dll/steam_networking_sockets.h +++ b/dll/dll/steam_networking_sockets.h @@ -15,15 +15,18 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_NETWORKING_SOCKETS_H__ +#define __INCLUDED_STEAM_NETWORKING_SOCKETS_H__ + #include "base.h" struct Listen_Socket { - HSteamListenSocket socket_id; + HSteamListenSocket socket_id{}; - int virtual_port; - int real_port; + int virtual_port{}; + int real_port{}; - CSteamID created_by; + CSteamID created_by{}; }; enum connect_socket_status { @@ -35,39 +38,39 @@ enum connect_socket_status { CONNECT_SOCKET_TIMEDOUT }; -struct compare_snm_for_queue { - bool operator()(Networking_Sockets &left, Networking_Sockets &right) { - return left.message_number() > right.message_number(); - } -}; - struct Connect_Socket { - int virtual_port; - int real_port; + struct compare_snm_for_queue { + bool operator()(const Networking_Sockets &left, const Networking_Sockets &right) { + return left.message_number() > right.message_number(); + } + }; - SteamNetworkingIdentity remote_identity; - HSteamNetConnection remote_id; + int virtual_port{}; + int real_port{}; - HSteamListenSocket listen_socket_id; + SteamNetworkingIdentity remote_identity{}; + HSteamNetConnection remote_id{}; - enum connect_socket_status status; - int64 user_data; + HSteamListenSocket listen_socket_id{}; - std::priority_queue, compare_snm_for_queue> data; - HSteamNetPollGroup poll_group; + enum connect_socket_status status{}; + int64 user_data{}; - unsigned long long packet_send_counter; - CSteamID created_by; + std::priority_queue, compare_snm_for_queue> data{}; + HSteamNetPollGroup poll_group{}; - std::chrono::steady_clock::time_point connect_request_last_sent; - unsigned connect_requests_sent; + unsigned long long packet_send_counter{}; + CSteamID created_by{}; + + std::chrono::steady_clock::time_point connect_request_last_sent{}; + unsigned connect_requests_sent{}; }; struct shared_between_client_server { - std::vector listen_sockets; - std::map connect_sockets; - std::map> poll_groups; - unsigned used; + std::vector listen_sockets{}; + std::map connect_sockets{}; + std::map> poll_groups{}; + unsigned used{}; }; class Steam_Networking_Sockets : @@ -80,2090 +83,1114 @@ public ISteamNetworkingSockets008, public ISteamNetworkingSockets009, public ISteamNetworkingSockets { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; - struct shared_between_client_server *s; - std::chrono::steady_clock::time_point created; + struct shared_between_client_server *sbcs{}; + std::chrono::steady_clock::time_point created{}; static const int SNS_DISABLED_PORT = -1; + static void steam_callback(void *object, Common_Message *msg); + static void steam_run_every_runcb(void *object); + + SteamNetworkingMessage_t *get_steam_message_connection(HSteamNetConnection hConn); + static void free_steam_message_data(SteamNetworkingMessage_t *pMsg); + static void delete_steam_message(SteamNetworkingMessage_t *pMsg); + + static unsigned long get_socket_id(); + + HSteamNetConnection new_connect_socket(SteamNetworkingIdentity remote_identity, int virtual_port, int real_port, enum connect_socket_status status=CONNECT_SOCKET_CONNECTING, HSteamListenSocket listen_socket_id=k_HSteamListenSocket_Invalid, HSteamNetConnection remote_id=k_HSteamNetConnection_Invalid); + struct Listen_Socket *get_connection_socket(HSteamListenSocket id); + + bool send_packet_new_connection(HSteamNetConnection m_hConn); + + HSteamListenSocket new_listen_socket(int nSteamConnectVirtualPort, int real_port); + + ESteamNetworkingConnectionState convert_status(enum connect_socket_status old_status); + + void set_steamnetconnectioninfo(std::map::iterator connect_socket, SteamNetConnectionInfo_t *pInfo); + + void launch_callback(HSteamNetConnection m_hConn, enum connect_socket_status old_status); + + void Callback(Common_Message *msg); + public: -static void steam_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Networking_Sockets *steam_networkingsockets = (Steam_Networking_Sockets *)object; - steam_networkingsockets->Callback(msg); -} - -static void steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Networking_Sockets *steam_networkingsockets = (Steam_Networking_Sockets *)object; - steam_networkingsockets->RunCallbacks(); -} - -Steam_Networking_Sockets(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, shared_between_client_server *sbcs) -{ - this->settings = settings; - this->network = network; - this->run_every_runcb = run_every_runcb; - this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Sockets::steam_callback, this); - this->network->setCallback(CALLBACK_ID_NETWORKING_SOCKETS, settings->get_local_steam_id(), &Steam_Networking_Sockets::steam_callback, this); - this->run_every_runcb->add(&Steam_Networking_Sockets::steam_run_every_runcb, this); - - this->callback_results = callback_results; - this->callbacks = callbacks; - - if (!sbcs) { - this->s = new shared_between_client_server(); - this->s->used = 0; - } else { - this->s = sbcs; - this->s->used += 1; - } - - this->created = std::chrono::steady_clock::now(); -} - -~Steam_Networking_Sockets() -{ - this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Sockets::steam_callback, this); - this->network->rmCallback(CALLBACK_ID_NETWORKING_SOCKETS, settings->get_local_steam_id(), &Steam_Networking_Sockets::steam_callback, this); - this->run_every_runcb->remove(&Steam_Networking_Sockets::steam_run_every_runcb, this); - if (this->s->used) { - this->s->used -= 1; - } else { - delete this->s; - } -} - -static unsigned long get_socket_id() -{ - static unsigned long socket_id; - socket_id++; - return socket_id; -} - -shared_between_client_server *get_shared_between_client_server() -{ - return s; -} - -HSteamListenSocket new_listen_socket(int nSteamConnectVirtualPort, int real_port) -{ - HSteamListenSocket socket_id = get_socket_id(); - if (socket_id == k_HSteamListenSocket_Invalid) ++socket_id; - CSteamID steam_id = settings->get_local_steam_id(); - - auto conn = std::find_if(s->listen_sockets.begin(), s->listen_sockets.end(), [&nSteamConnectVirtualPort,&steam_id](struct Listen_Socket const& conn) { return conn.virtual_port == nSteamConnectVirtualPort && conn.created_by == steam_id;}); - if (conn != s->listen_sockets.end()) return k_HSteamListenSocket_Invalid; - - struct Listen_Socket listen_socket; - listen_socket.socket_id = socket_id; - listen_socket.virtual_port = nSteamConnectVirtualPort; - listen_socket.real_port = real_port; - listen_socket.created_by = steam_id; - s->listen_sockets.push_back(listen_socket); - return socket_id; -} - -struct Listen_Socket *get_connection_socket(HSteamListenSocket id) -{ - auto conn = std::find_if(s->listen_sockets.begin(), s->listen_sockets.end(), [&id](struct Listen_Socket const& conn) { return conn.socket_id == id;}); - if (conn == s->listen_sockets.end()) return NULL; - return &(*conn); -} - -bool send_packet_new_connection(HSteamNetConnection m_hConn) -{ - auto connect_socket = s->connect_sockets.find(m_hConn); - if (connect_socket == s->connect_sockets.end()) return false; - - //TODO: right now this only supports connecting with steam id, might need to make ip/port connections work in the future when I find a game that uses them. - - Common_Message msg; - msg.set_source_id(connect_socket->second.created_by.ConvertToUint64()); - msg.set_allocated_networking_sockets(new Networking_Sockets); - if (connect_socket->second.status == CONNECT_SOCKET_CONNECTING) { - msg.mutable_networking_sockets()->set_type(Networking_Sockets::CONNECTION_REQUEST); - } else if (connect_socket->second.status == CONNECT_SOCKET_CONNECTED) { - msg.mutable_networking_sockets()->set_type(Networking_Sockets::CONNECTION_ACCEPTED); - } - - msg.mutable_networking_sockets()->set_virtual_port(connect_socket->second.virtual_port); - msg.mutable_networking_sockets()->set_real_port(connect_socket->second.real_port); - msg.mutable_networking_sockets()->set_connection_id_from(connect_socket->first); - msg.mutable_networking_sockets()->set_connection_id(connect_socket->second.remote_id); - - uint64_t steam_id = connect_socket->second.remote_identity.GetSteamID64(); - if (steam_id) { - msg.set_dest_id(steam_id); - return network->sendTo(&msg, true); - } - - const SteamNetworkingIPAddr *ip_addr = connect_socket->second.remote_identity.GetIPAddr(); - if (ip_addr) { - return network->sendToIPPort(&msg, ip_addr->GetIPv4(), ip_addr->m_port, true); - } - - return false; -} - -HSteamNetConnection new_connect_socket(SteamNetworkingIdentity remote_identity, int virtual_port, int real_port, enum connect_socket_status status=CONNECT_SOCKET_CONNECTING, HSteamListenSocket listen_socket_id=k_HSteamListenSocket_Invalid, HSteamNetConnection remote_id=k_HSteamNetConnection_Invalid) -{ - Connect_Socket socket = {}; - socket.remote_identity = remote_identity; - socket.virtual_port = virtual_port; - socket.real_port = real_port; - socket.listen_socket_id = listen_socket_id; - socket.remote_id = remote_id; - socket.status = status; - socket.user_data = -1; - socket.poll_group = k_HSteamNetPollGroup_Invalid; - socket.created_by = settings->get_local_steam_id(); - socket.connect_request_last_sent = std::chrono::steady_clock::now(); - socket.connect_requests_sent = 0; - socket.packet_send_counter = 1; - - HSteamNetConnection socket_id = get_socket_id(); - if (socket_id == k_HSteamNetConnection_Invalid) ++socket_id; - - if (s->connect_sockets.insert(std::make_pair(socket_id, socket)).second == false) { - return k_HSteamNetConnection_Invalid; - } - - return socket_id; -} - -ESteamNetworkingConnectionState convert_status(enum connect_socket_status old_status) -{ - if (old_status == CONNECT_SOCKET_NO_CONNECTION) return k_ESteamNetworkingConnectionState_None; - if (old_status == CONNECT_SOCKET_CONNECTING) return k_ESteamNetworkingConnectionState_Connecting; - if (old_status == CONNECT_SOCKET_NOT_ACCEPTED) return k_ESteamNetworkingConnectionState_Connecting; - if (old_status == CONNECT_SOCKET_CONNECTED) return k_ESteamNetworkingConnectionState_Connected; - if (old_status == CONNECT_SOCKET_CLOSED) return k_ESteamNetworkingConnectionState_ClosedByPeer; - if (old_status == CONNECT_SOCKET_TIMEDOUT) return k_ESteamNetworkingConnectionState_ProblemDetectedLocally; - return k_ESteamNetworkingConnectionState_None; -} - -void set_steamnetconnectioninfo(std::map::iterator connect_socket, SteamNetConnectionInfo_t *pInfo) -{ - pInfo->m_identityRemote = connect_socket->second.remote_identity; - pInfo->m_nUserData = connect_socket->second.user_data; - pInfo->m_hListenSocket = connect_socket->second.listen_socket_id; - pInfo->m_addrRemote.Clear(); //TODO - if (connect_socket->second.real_port != SNS_DISABLED_PORT) { - pInfo->m_addrRemote.SetIPv4(network->getIP(connect_socket->second.remote_identity.GetSteamID()), connect_socket->first); - } - - pInfo->m_idPOPRemote = 0; - pInfo->m_idPOPRelay = 0; - pInfo->m_eState = convert_status(connect_socket->second.status); - pInfo->m_eEndReason = 0; //TODO - pInfo->m_szEndDebug[0] = 0; - sprintf(pInfo->m_szConnectionDescription, "%u", connect_socket->first); - - //Note some games might not allocate a struct the whole size of SteamNetConnectionInfo_t when calling GetConnectionInfo - //keep this in mind in future interface updates -} - -void launch_callback(HSteamNetConnection m_hConn, enum connect_socket_status old_status) -{ - auto connect_socket = s->connect_sockets.find(m_hConn); - if (connect_socket == s->connect_sockets.end()) return; - - struct SteamNetConnectionStatusChangedCallback_t data = {}; - data.m_hConn = connect_socket->first; - data.m_eOldState = convert_status(old_status); - set_steamnetconnectioninfo(connect_socket, &data.m_info); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); -} - - -/// Creates a "server" socket that listens for clients to connect to, either by calling -/// ConnectSocketBySteamID or ConnectSocketByIPv4Address. -/// -/// nSteamConnectVirtualPort specifies how clients can connect to this socket using -/// ConnectBySteamID. A negative value indicates that this functionality is -/// disabled and clients must connect by IP address. It's very common for applications -/// to only have one listening socket; in that case, use zero. If you need to open -/// multiple listen sockets and have clients be able to connect to one or the other, then -/// nSteamConnectVirtualPort should be a small integer constant unique to each listen socket -/// you create. -/// -/// In the open-source version of this API, you must pass -1 for nSteamConnectVirtualPort -/// -/// If you want clients to connect to you by your IPv4 addresses using -/// ConnectByIPv4Address, then you must set nPort to be nonzero. Steam will -/// bind a UDP socket to the specified local port, and clients will send packets using -/// ordinary IP routing. It's up to you to take care of NAT, protecting your server -/// from DoS, etc. If you don't need clients to connect to you by IP, then set nPort=0. -/// Use nIP if you wish to bind to a particular local interface. Typically you will use 0, -/// which means to listen on all interfaces, and accept the default outbound IP address. -/// If nPort is zero, then nIP must also be zero. -/// -/// A SocketStatusCallback_t callback when another client attempts a connection. -HSteamListenSocket CreateListenSocket( int nSteamConnectVirtualPort, uint32 nIP, uint16 nPort ) -{ - PRINT_DEBUG("%i %u %u", nSteamConnectVirtualPort, nIP, nPort); - std::lock_guard lock(global_mutex); - return new_listen_socket(nSteamConnectVirtualPort, nPort); -} - -/// Creates a "server" socket that listens for clients to connect to by -/// calling ConnectByIPAddress, over ordinary UDP (IPv4 or IPv6) -/// -/// You must select a specific local port to listen on and set it -/// the port field of the local address. -/// -/// Usually you wil set the IP portion of the address to zero, (SteamNetworkingIPAddr::Clear()). -/// This means that you will not bind to any particular local interface. In addition, -/// if possible the socket will be bound in "dual stack" mode, which means that it can -/// accept both IPv4 and IPv6 clients. If you wish to bind a particular interface, then -/// set the local address to the appropriate IPv4 or IPv6 IP. -/// -/// When a client attempts to connect, a SteamNetConnectionStatusChangedCallback_t -/// will be posted. The connection will be in the connecting state. -HSteamListenSocket CreateListenSocketIP( const SteamNetworkingIPAddr &localAddress ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - return new_listen_socket(SNS_DISABLED_PORT, localAddress.m_port); -} - -HSteamListenSocket CreateListenSocketIP( const SteamNetworkingIPAddr *localAddress ) -{ - PRINT_DEBUG("old1"); - std::lock_guard lock(global_mutex); - return new_listen_socket(SNS_DISABLED_PORT, localAddress->m_port); -} - -HSteamListenSocket CreateListenSocketIP( const SteamNetworkingIPAddr &localAddress, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - return new_listen_socket(SNS_DISABLED_PORT, localAddress.m_port); -} - -/// Creates a connection and begins talking to a "server" over UDP at the -/// given IPv4 or IPv6 address. The remote host must be listening with a -/// matching call to CreateListenSocketIP on the specified port. -/// -/// A SteamNetConnectionStatusChangedCallback_t callback will be triggered when we start -/// connecting, and then another one on either timeout or successful connection. -/// -/// If the server does not have any identity configured, then their network address -/// will be the only identity in use. Or, the network host may provide a platform-specific -/// identity with or without a valid certificate to authenticate that identity. (These -/// details will be contained in the SteamNetConnectionStatusChangedCallback_t.) It's -/// up to your application to decide whether to allow the connection. -/// -/// By default, all connections will get basic encryption sufficient to prevent -/// casual eavesdropping. But note that without certificates (or a shared secret -/// distributed through some other out-of-band mechanism), you don't have any -/// way of knowing who is actually on the other end, and thus are vulnerable to -/// man-in-the-middle attacks. -HSteamNetConnection ConnectByIPAddress( const SteamNetworkingIPAddr &address ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - SteamNetworkingIdentity ip_id; - ip_id.SetIPAddr(address); - HSteamNetConnection socket = new_connect_socket(ip_id, SNS_DISABLED_PORT, address.m_port); - send_packet_new_connection(socket); - return socket; -} - -HSteamNetConnection ConnectByIPAddress( const SteamNetworkingIPAddr *address ) -{ - PRINT_DEBUG("old1"); - std::lock_guard lock(global_mutex); - SteamNetworkingIdentity ip_id; - ip_id.SetIPAddr(*address); - HSteamNetConnection socket = new_connect_socket(ip_id, SNS_DISABLED_PORT, address->m_port); - send_packet_new_connection(socket); - return socket; -} - -HSteamNetConnection ConnectByIPAddress( const SteamNetworkingIPAddr &address, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) -{ - PRINT_DEBUG("%X", address.GetIPv4()); - std::lock_guard lock(global_mutex); - SteamNetworkingIdentity ip_id; - ip_id.SetIPAddr(address); - HSteamNetConnection socket = new_connect_socket(ip_id, SNS_DISABLED_PORT, address.m_port); - send_packet_new_connection(socket); - return socket; -} - -/// Like CreateListenSocketIP, but clients will connect using ConnectP2P -/// -/// nVirtualPort specifies how clients can connect to this socket using -/// ConnectP2P. It's very common for applications to only have one listening socket; -/// in that case, use zero. If you need to open multiple listen sockets and have clients -/// be able to connect to one or the other, then nVirtualPort should be a small integer (<1000) -/// unique to each listen socket you create. -/// -/// If you use this, you probably want to call ISteamNetworkingUtils::InitializeRelayNetworkAccess() -/// when your app initializes -HSteamListenSocket CreateListenSocketP2P( int nVirtualPort ) -{ - PRINT_DEBUG("old %i", nVirtualPort); - std::lock_guard lock(global_mutex); - return new_listen_socket(nVirtualPort, SNS_DISABLED_PORT); -} - -HSteamListenSocket CreateListenSocketP2P( int nVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) -{ - PRINT_DEBUG("%i", nVirtualPort); - //TODO config options - std::lock_guard lock(global_mutex); - return new_listen_socket(nVirtualPort, SNS_DISABLED_PORT); -} - -/// Begin connecting to a server that is identified using a platform-specific identifier. -/// This requires some sort of third party rendezvous service, and will depend on the -/// platform and what other libraries and services you are integrating with. -/// -/// At the time of this writing, there is only one supported rendezvous service: Steam. -/// Set the SteamID (whether "user" or "gameserver") and Steam will determine if the -/// client is online and facilitate a relay connection. Note that all P2P connections on -/// Steam are currently relayed. -/// -/// If you use this, you probably want to call ISteamNetworkingUtils::InitializeRelayNetworkAccess() -/// when your app initializes -HSteamNetConnection ConnectP2P( const SteamNetworkingIdentity &identityRemote, int nVirtualPort ) -{ - PRINT_DEBUG("old %i", nVirtualPort); - std::lock_guard lock(global_mutex); - - const SteamNetworkingIPAddr *ip = identityRemote.GetIPAddr(); - - if (identityRemote.m_eType == k_ESteamNetworkingIdentityType_SteamID) { - PRINT_DEBUG("%llu", identityRemote.GetSteamID64()); - //steam id identity - } else if (ip) { - PRINT_DEBUG("%u:%u ipv4? %u", ip->GetIPv4(), ip->m_port, ip->IsIPv4()); - //ip addr - } else { - return k_HSteamNetConnection_Invalid; - } - - HSteamNetConnection socket = new_connect_socket(identityRemote, nVirtualPort, SNS_DISABLED_PORT); - send_packet_new_connection(socket); - return socket; -} - -HSteamNetConnection ConnectP2P( const SteamNetworkingIdentity *identityRemote, int nVirtualPort ) -{ - PRINT_DEBUG("old1"); - return ConnectP2P(*identityRemote, nVirtualPort); -} - -HSteamNetConnection ConnectP2P( const SteamNetworkingIdentity &identityRemote, int nVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) -{ - PRINT_DEBUG("%i", nVirtualPort); - //TODO config options - return ConnectP2P(identityRemote, nVirtualPort); -} - -/// Creates a connection and begins talking to a remote destination. The remote host -/// must be listening with a matching call to CreateListenSocket. -/// -/// Use ConnectBySteamID to connect using the SteamID (client or game server) as the network address. -/// Use ConnectByIPv4Address to connect by IP address. -/// -/// A SteamNetConnectionStatusChangedCallback_t callback will be triggered when we start connecting, -/// and then another one on timeout or successful connection -//#ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE -HSteamNetConnection ConnectBySteamID( CSteamID steamIDTarget, int nVirtualPort ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_HSteamNetConnection_Invalid; -} - -//#endif -HSteamNetConnection ConnectByIPv4Address( uint32 nIP, uint16 nPort ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_HSteamNetConnection_Invalid; -} - - -/// Accept an incoming connection that has been received on a listen socket. -/// -/// When a connection attempt is received (perhaps after a few basic handshake -/// packets have been exchanged to prevent trivial spoofing), a connection interface -/// object is created in the k_ESteamNetworkingConnectionState_Connecting state -/// and a SteamNetConnectionStatusChangedCallback_t is posted. At this point, your -/// application MUST either accept or close the connection. (It may not ignore it.) -/// Accepting the connection will transition it either into the connected state, -/// of the finding route state, depending on the connection type. -/// -/// You should take action within a second or two, because accepting the connection is -/// what actually sends the reply notifying the client that they are connected. If you -/// delay taking action, from the client's perspective it is the same as the network -/// being unresponsive, and the client may timeout the connection attempt. In other -/// words, the client cannot distinguish between a delay caused by network problems -/// and a delay caused by the application. -/// -/// This means that if your application goes for more than a few seconds without -/// processing callbacks (for example, while loading a map), then there is a chance -/// that a client may attempt to connect in that interval and fail due to timeout. -/// -/// If the application does not respond to the connection attempt in a timely manner, -/// and we stop receiving communication from the client, the connection attempt will -/// be timed out locally, transitioning the connection to the -/// k_ESteamNetworkingConnectionState_ProblemDetectedLocally state. The client may also -/// close the connection before it is accepted, and a transition to the -/// k_ESteamNetworkingConnectionState_ClosedByPeer is also possible depending the exact -/// sequence of events. -/// -/// Returns k_EResultInvalidParam if the handle is invalid. -/// Returns k_EResultInvalidState if the connection is not in the appropriate state. -/// (Remember that the connection state could change in between the time that the -/// notification being posted to the queue and when it is received by the application.) -EResult AcceptConnection( HSteamNetConnection hConn ) -{ - PRINT_DEBUG("%u", hConn); - std::lock_guard lock(global_mutex); - - auto connect_socket = s->connect_sockets.find(hConn); - if (connect_socket == s->connect_sockets.end()) return k_EResultInvalidParam; - if (connect_socket->second.status != CONNECT_SOCKET_NOT_ACCEPTED) return k_EResultInvalidState; - connect_socket->second.status = CONNECT_SOCKET_CONNECTED; - send_packet_new_connection(connect_socket->first); - launch_callback(connect_socket->first, CONNECT_SOCKET_NOT_ACCEPTED); - - return k_EResultOK; -} - - -/// Disconnects from the remote host and invalidates the connection handle. -/// Any unread data on the connection is discarded. -/// -/// nReason is an application defined code that will be received on the other -/// end and recorded (when possible) in backend analytics. The value should -/// come from a restricted range. (See ESteamNetConnectionEnd.) If you don't need -/// to communicate any information to the remote host, and do not want analytics to -/// be able to distinguish "normal" connection terminations from "exceptional" ones, -/// You may pass zero, in which case the generic value of -/// k_ESteamNetConnectionEnd_App_Generic will be used. -/// -/// pszDebug is an optional human-readable diagnostic string that will be received -/// by the remote host and recorded (when possible) in backend analytics. -/// -/// If you wish to put the socket into a "linger" state, where an attempt is made to -/// flush any remaining sent data, use bEnableLinger=true. Otherwise reliable data -/// is not flushed. -/// -/// If the connection has already ended and you are just freeing up the -/// connection interface, the reason code, debug string, and linger flag are -/// ignored. -bool CloseConnection( HSteamNetConnection hPeer, int nReason, const char *pszDebug, bool bEnableLinger ) -{ - PRINT_DEBUG("%u", hPeer); - std::lock_guard lock(global_mutex); - - auto connect_socket = s->connect_sockets.find(hPeer); - if (connect_socket == s->connect_sockets.end()) return false; - - if (connect_socket->second.status != CONNECT_SOCKET_CLOSED && connect_socket->second.status != CONNECT_SOCKET_TIMEDOUT) { - //TODO send/nReason and pszDebug - Common_Message msg; - msg.set_source_id(connect_socket->second.created_by.ConvertToUint64()); - msg.set_dest_id(connect_socket->second.remote_identity.GetSteamID64()); - msg.set_allocated_networking_sockets(new Networking_Sockets); - msg.mutable_networking_sockets()->set_type(Networking_Sockets::CONNECTION_END); - msg.mutable_networking_sockets()->set_virtual_port(connect_socket->second.virtual_port); - msg.mutable_networking_sockets()->set_real_port(connect_socket->second.real_port); - msg.mutable_networking_sockets()->set_connection_id_from(connect_socket->first); - msg.mutable_networking_sockets()->set_connection_id(connect_socket->second.remote_id); - network->sendTo(&msg, true); - } - - s->connect_sockets.erase(connect_socket); - return true; -} - - -/// Destroy a listen socket, and all the client sockets generated by accepting connections -/// on the listen socket. -/// -/// pszNotifyRemoteReason determines what cleanup actions are performed on the client -/// sockets being destroyed. (See DestroySocket for more details.) -/// -/// Note that if cleanup is requested and you have requested the listen socket bound to a -/// particular local port to facilitate direct UDP/IPv4 connections, then the underlying UDP -/// socket must remain open until all clients have been cleaned up. -bool CloseListenSocket( HSteamListenSocket hSocket, const char *pszNotifyRemoteReason ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - return false; -} - -/// Destroy a listen socket. All the connections that were accepting on the listen -/// socket are closed ungracefully. -bool CloseListenSocket( HSteamListenSocket hSocket ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - auto conn = std::find_if(s->listen_sockets.begin(), s->listen_sockets.end(), [&hSocket](struct Listen_Socket const& conn) { return conn.socket_id == hSocket;}); - if (conn == s->listen_sockets.end()) return false; - - std::queue to_close; - - auto socket_conn = std::begin(s->connect_sockets); - while (socket_conn != std::end(s->connect_sockets)) { - if (socket_conn->second.listen_socket_id == hSocket) { - to_close.push(socket_conn->first); - } - - ++socket_conn; - } - - while (to_close.size()) { - CloseConnection(to_close.front(), 0, "", false); - to_close.pop(); - } - - s->listen_sockets.erase(conn); - return true; -} - -/// Set connection user data. Returns false if the handle is invalid. -bool SetConnectionUserData( HSteamNetConnection hPeer, int64 nUserData ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - auto connect_socket = s->connect_sockets.find(hPeer); - if (connect_socket == s->connect_sockets.end()) return false; - connect_socket->second.user_data = nUserData; - return true; -} - - -/// Fetch connection user data. Returns -1 if handle is invalid -/// or if you haven't set any userdata on the connection. -int64 GetConnectionUserData( HSteamNetConnection hPeer ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - auto connect_socket = s->connect_sockets.find(hPeer); - if (connect_socket == s->connect_sockets.end()) return -1; - return connect_socket->second.user_data; -} - - -/// Set a name for the connection, used mostly for debugging -void SetConnectionName( HSteamNetConnection hPeer, const char *pszName ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - - -/// Fetch connection name. Returns false if handle is invalid -bool GetConnectionName( HSteamNetConnection hPeer, char *pszName, int nMaxLen ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - - -/// Send a message to the remote host on the connected socket. -/// -/// eSendType determines the delivery guarantees that will be provided, -/// when data should be buffered, etc. -/// -/// Note that the semantics we use for messages are not precisely -/// the same as the semantics of a standard "stream" socket. -/// (SOCK_STREAM) For an ordinary stream socket, the boundaries -/// between chunks are not considered relevant, and the sizes of -/// the chunks of data written will not necessarily match up to -/// the sizes of the chunks that are returned by the reads on -/// the other end. The remote host might read a partial chunk, -/// or chunks might be coalesced. For the message semantics -/// used here, however, the sizes WILL match. Each send call -/// will match a successful read call on the remote host -/// one-for-one. If you are porting existing stream-oriented -/// code to the semantics of reliable messages, your code should -/// work the same, since reliable message semantics are more -/// strict than stream semantics. The only caveat is related to -/// performance: there is per-message overhead to retain the -/// messages sizes, and so if your code sends many small chunks -/// of data, performance will suffer. Any code based on stream -/// sockets that does not write excessively small chunks will -/// work without any changes. -EResult SendMessageToConnection( HSteamNetConnection hConn, const void *pData, uint32 cbData, ESteamNetworkingSendType eSendType ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - return k_EResultFail; -} - -/// Send a message to the remote host on the specified connection. -/// -/// nSendFlags determines the delivery guarantees that will be provided, -/// when data should be buffered, etc. E.g. k_nSteamNetworkingSend_Unreliable -/// -/// Note that the semantics we use for messages are not precisely -/// the same as the semantics of a standard "stream" socket. -/// (SOCK_STREAM) For an ordinary stream socket, the boundaries -/// between chunks are not considered relevant, and the sizes of -/// the chunks of data written will not necessarily match up to -/// the sizes of the chunks that are returned by the reads on -/// the other end. The remote host might read a partial chunk, -/// or chunks might be coalesced. For the message semantics -/// used here, however, the sizes WILL match. Each send call -/// will match a successful read call on the remote host -/// one-for-one. If you are porting existing stream-oriented -/// code to the semantics of reliable messages, your code should -/// work the same, since reliable message semantics are more -/// strict than stream semantics. The only caveat is related to -/// performance: there is per-message overhead to retain the -/// message sizes, and so if your code sends many small chunks -/// of data, performance will suffer. Any code based on stream -/// sockets that does not write excessively small chunks will -/// work without any changes. -/// -/// The pOutMessageNumber is an optional pointer to receive the -/// message number assigned to the message, if sending was successful. -/// -/// Returns: -/// - k_EResultInvalidParam: invalid connection handle, or the individual message is too big. -/// (See k_cbMaxSteamNetworkingSocketsMessageSizeSend) -/// - k_EResultInvalidState: connection is in an invalid state -/// - k_EResultNoConnection: connection has ended -/// - k_EResultIgnored: You used k_nSteamNetworkingSend_NoDelay, and the message was dropped because -/// we were not ready to send it. -/// - k_EResultLimitExceeded: there was already too much data queued to be sent. -/// (See k_ESteamNetworkingConfig_SendBufferSize) -EResult SendMessageToConnection( HSteamNetConnection hConn, const void *pData, uint32 cbData, int nSendFlags, int64 *pOutMessageNumber ) -{ - PRINT_DEBUG("%u, len %u, flags %i", hConn, cbData, nSendFlags); - std::lock_guard lock(global_mutex); - - auto connect_socket = s->connect_sockets.find(hConn); - if (connect_socket == s->connect_sockets.end()) return k_EResultInvalidParam; - if (connect_socket->second.status == CONNECT_SOCKET_CLOSED) return k_EResultNoConnection; - if (connect_socket->second.status == CONNECT_SOCKET_TIMEDOUT) return k_EResultNoConnection; - if (connect_socket->second.status != CONNECT_SOCKET_CONNECTED && connect_socket->second.status != CONNECT_SOCKET_CONNECTING) return k_EResultInvalidState; - - Common_Message msg; - msg.set_source_id(connect_socket->second.created_by.ConvertToUint64()); - msg.set_dest_id(connect_socket->second.remote_identity.GetSteamID64()); - msg.set_allocated_networking_sockets(new Networking_Sockets); - msg.mutable_networking_sockets()->set_type(Networking_Sockets::DATA); - msg.mutable_networking_sockets()->set_virtual_port(connect_socket->second.virtual_port); - msg.mutable_networking_sockets()->set_real_port(connect_socket->second.real_port); - msg.mutable_networking_sockets()->set_connection_id_from(connect_socket->first); - msg.mutable_networking_sockets()->set_connection_id(connect_socket->second.remote_id); - msg.mutable_networking_sockets()->set_data(pData, cbData); - uint64 message_number = connect_socket->second.packet_send_counter; - msg.mutable_networking_sockets()->set_message_number(message_number); - connect_socket->second.packet_send_counter += 1; - - bool reliable = false; - if (nSendFlags & k_nSteamNetworkingSend_Reliable) reliable = true; - if (network->sendTo(&msg, reliable)) { - if (pOutMessageNumber) *pOutMessageNumber = message_number; - return k_EResultOK; - } - - return k_EResultFail; -} - -EResult SendMessageToConnection( HSteamNetConnection hConn, const void *pData, uint32 cbData, int nSendFlags ) -{ - PRINT_DEBUG("old %u, len %u, flags %i", hConn, cbData, nSendFlags); - return SendMessageToConnection(hConn, pData, cbData, nSendFlags, NULL); -} - -/// Send one or more messages without copying the message payload. -/// This is the most efficient way to send messages. To use this -/// function, you must first allocate a message object using -/// ISteamNetworkingUtils::AllocateMessage. (Do not declare one -/// on the stack or allocate your own.) -/// -/// You should fill in the message payload. You can either let -/// it allocate the buffer for you and then fill in the payload, -/// or if you already have a buffer allocated, you can just point -/// m_pData at your buffer and set the callback to the appropriate function -/// to free it. Note that if you use your own buffer, it MUST remain valid -/// until the callback is executed. And also note that your callback can be -/// invoked at ant time from any thread (perhaps even before SendMessages -/// returns!), so it MUST be fast and threadsafe. -/// -/// You MUST also fill in: -/// - m_conn - the handle of the connection to send the message to -/// - m_nFlags - bitmask of k_nSteamNetworkingSend_xxx flags. -/// -/// All other fields are currently reserved and should not be modified. -/// -/// The library will take ownership of the message structures. They may -/// be modified or become invalid at any time, so you must not read them -/// after passing them to this function. -/// -/// pOutMessageNumberOrResult is an optional array that will receive, -/// for each message, the message number that was assigned to the message -/// if sending was successful. If sending failed, then a negative EResult -/// valid is placed into the array. For example, the array will hold -/// -k_EResultInvalidState if the connection was in an invalid state. -/// See ISteamNetworkingSockets::SendMessageToConnection for possible -/// failure codes. -void SendMessages( int nMessages, SteamNetworkingMessage_t *const *pMessages, int64 *pOutMessageNumberOrResult ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - for (int i = 0; i < nMessages; ++i) { - int64 out_number = 0; - int result = SendMessageToConnection(pMessages[i]->m_conn, pMessages[i]->m_pData, pMessages[i]->m_cbSize, pMessages[i]->m_nFlags, &out_number); - if (pOutMessageNumberOrResult) { - if (result == k_EResultOK) { - pOutMessageNumberOrResult[i] = out_number; - } else { - pOutMessageNumberOrResult[i] = -result; - } - } - - pMessages[i]->m_pfnFreeData(pMessages[i]); - pMessages[i]->Release(); - } -} - - -/// If Nagle is enabled (its on by default) then when calling -/// SendMessageToConnection the message will be queued up the Nagle time -/// before being sent to merge small messages into the same packet. -/// -/// Call this function to flush any queued messages and send them immediately -/// on the next transmission time (often that means right now). -EResult FlushMessagesOnConnection( HSteamNetConnection hConn ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EResultOK; -} - -static void free_steam_message_data(SteamNetworkingMessage_t *pMsg) -{ - free(pMsg->m_pData); - pMsg->m_pData = NULL; -} - -static void delete_steam_message(SteamNetworkingMessage_t *pMsg) -{ - if (pMsg->m_pfnFreeData) pMsg->m_pfnFreeData(pMsg); - delete pMsg; -} - -SteamNetworkingMessage_t *get_steam_message_connection(HSteamNetConnection hConn) -{ - auto connect_socket = s->connect_sockets.find(hConn); - if (connect_socket == s->connect_sockets.end()) return NULL; - if (connect_socket->second.data.empty()) return NULL; - SteamNetworkingMessage_t *pMsg = new SteamNetworkingMessage_t(); - unsigned long size = connect_socket->second.data.top().data().size(); - pMsg->m_pData = malloc(size); - pMsg->m_cbSize = size; - memcpy(pMsg->m_pData, connect_socket->second.data.top().data().data(), size); - pMsg->m_conn = hConn; - pMsg->m_identityPeer = connect_socket->second.remote_identity; - pMsg->m_nConnUserData = connect_socket->second.user_data; - pMsg->m_usecTimeReceived = std::chrono::duration_cast(std::chrono::steady_clock::now() - created).count(); - //TODO: check where messagenumber starts - pMsg->m_nMessageNumber = connect_socket->second.data.top().message_number(); - - pMsg->m_pfnFreeData = &free_steam_message_data; - pMsg->m_pfnRelease = &delete_steam_message; - pMsg->m_nChannel = 0; - connect_socket->second.data.pop(); - PRINT_DEBUG("get_steam_message_connection %u %lu, %llu", hConn, size, pMsg->m_nMessageNumber); - return pMsg; -} - -/// Fetch the next available message(s) from the connection, if any. -/// Returns the number of messages returned into your array, up to nMaxMessages. -/// If the connection handle is invalid, -1 is returned. -/// -/// The order of the messages returned in the array is relevant. -/// Reliable messages will be received in the order they were sent (and with the -/// same sizes --- see SendMessageToConnection for on this subtle difference from a stream socket). -/// -/// Unreliable messages may be dropped, or delivered out of order withrespect to -/// each other or with respect to reliable messages. The same unreliable message -/// may be received multiple times. -/// -/// If any messages are returned, you MUST call SteamNetworkingMessage_t::Release() on each -/// of them free up resources after you are done. It is safe to keep the object alive for -/// a little while (put it into some queue, etc), and you may call Release() from any thread. -int ReceiveMessagesOnConnection( HSteamNetConnection hConn, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) -{ - PRINT_DEBUG("%u %i", hConn, nMaxMessages); - std::lock_guard lock(global_mutex); - if (!ppOutMessages || !nMaxMessages) return 0; - - SteamNetworkingMessage_t *msg = NULL; - int messages = 0; - while (messages < nMaxMessages && (msg = get_steam_message_connection(hConn))) { - ppOutMessages[messages] = msg; - ++messages; - } - - PRINT_DEBUG("messages %u", messages); - return messages; -} - -/// Same as ReceiveMessagesOnConnection, but will return the next message available -/// on any connection that was accepted through the specified listen socket. Examine -/// SteamNetworkingMessage_t::m_conn to know which client connection. -/// -/// Delivery order of messages among different clients is not defined. They may -/// be returned in an order different from what they were actually received. (Delivery -/// order of messages from the same client is well defined, and thus the order of the -/// messages is relevant!) -int ReceiveMessagesOnListenSocket( HSteamListenSocket hSocket, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) -{ - PRINT_DEBUG("%u %i", hSocket, nMaxMessages); - std::lock_guard lock(global_mutex); - if (!ppOutMessages || !nMaxMessages) return 0; - - SteamNetworkingMessage_t *msg = NULL; - int messages = 0; - - auto socket_conn = std::begin(s->connect_sockets); - while (socket_conn != std::end(s->connect_sockets) && messages < nMaxMessages) { - if (socket_conn->second.listen_socket_id == hSocket) { - while (messages < nMaxMessages && (msg = get_steam_message_connection(socket_conn->first))) { - ppOutMessages[messages] = msg; - ++messages; - } - } - - ++socket_conn; - } - - return messages; -} - -/// Returns basic information about the high-level state of the connection. -bool GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo_t *pInfo ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!pInfo) return false; - - auto connect_socket = s->connect_sockets.find(hConn); - if (connect_socket == s->connect_sockets.end()) return false; - - set_steamnetconnectioninfo(connect_socket, pInfo); - - //Note some games might not allocate a struct the whole size of SteamNetConnectionInfo_t - //keep this in mind in future interface updates - - return true; -} - -/// Returns a small set of information about the real-time state of the connection -/// and the queue status of each lane. -/// -/// - pStatus may be NULL if the information is not desired. (E.g. you are only interested -/// in the lane information.) -/// - On entry, nLanes specifies the length of the pLanes array. This may be 0 -/// if you do not wish to receive any lane data. It's OK for this to be smaller than -/// the total number of configured lanes. -/// - pLanes points to an array that will receive lane-specific info. It can be NULL -/// if this is not needed. -/// -/// Return value: -/// - k_EResultNoConnection - connection handle is invalid or connection has been closed. -/// - k_EResultInvalidParam - nLanes is bad -EResult GetConnectionRealTimeStatus( HSteamNetConnection hConn, SteamNetConnectionRealTimeStatus_t *pStatus, int nLanes, SteamNetConnectionRealTimeLaneStatus_t *pLanes ) -{ - PRINT_DEBUG("%s %u %p %i %p", __FUNCTION__, hConn, pStatus, nLanes, pLanes); - std::lock_guard lock(global_mutex); - auto connect_socket = s->connect_sockets.find(hConn); - if (connect_socket == s->connect_sockets.end()) return k_EResultNoConnection; - - if (pStatus) { - pStatus->m_eState = convert_status(connect_socket->second.status); - pStatus->m_nPing = 10; //TODO: calculate real numbers? - pStatus->m_flConnectionQualityLocal = 1.0; - pStatus->m_flConnectionQualityRemote = 1.0; - //TODO: rest - pStatus->m_flOutPacketsPerSec = 0.0; - pStatus->m_flOutBytesPerSec = 0.0; - pStatus->m_flInPacketsPerSec = 0.0; - pStatus->m_flInBytesPerSec = 0.0; - pStatus->m_cbSentUnackedReliable = 0.0; - pStatus->m_usecQueueTime = 0.0; - - //Note some games (volcanoids) might not allocate a struct the whole size of SteamNetworkingQuickConnectionStatus - //keep this in mind in future interface updates - //NOTE: need to implement GetQuickConnectionStatus seperately if this changes. - } - - //TODO: lanes - return k_EResultOK; -} - -/// Fetch the next available message(s) from the socket, if any. -/// Returns the number of messages returned into your array, up to nMaxMessages. -/// If the connection handle is invalid, -1 is returned. -/// -/// The order of the messages returned in the array is relevant. -/// Reliable messages will be received in the order they were sent (and with the -/// same sizes --- see SendMessageToConnection for on this subtle difference from a stream socket). -/// -/// FIXME - We're still debating the exact set of guarantees for unreliable, so this might change. -/// Unreliable messages may not be received. The order of delivery of unreliable messages -/// is NOT specified. They may be received out of order with respect to each other or -/// reliable messages. They may be received multiple times! -/// -/// If any messages are returned, you MUST call Release() to each of them free up resources -/// after you are done. It is safe to keep the object alive for a little while (put it -/// into some queue, etc), and you may call Release() from any thread. -int ReceiveMessagesOnConnection( HSteamNetConnection hConn, SteamNetworkingMessage001_t **ppOutMessages, int nMaxMessages ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return -1; -} - - -/// Same as ReceiveMessagesOnConnection, but will return the next message available -/// on any client socket that was accepted through the specified listen socket. Examine -/// SteamNetworkingMessage_t::m_conn to know which client connection. -/// -/// Delivery order of messages among different clients is not defined. They may -/// be returned in an order different from what they were actually received. (Delivery -/// order of messages from the same client is well defined, and thus the order of the -/// messages is relevant!) -int ReceiveMessagesOnListenSocket( HSteamListenSocket hSocket, SteamNetworkingMessage001_t **ppOutMessages, int nMaxMessages ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return -1; -} - - -/// Returns information about the specified connection. -bool GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo001_t *pInfo ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - - -/// Returns brief set of connection status that you might want to display -/// to the user in game. -bool GetQuickConnectionStatus( HSteamNetConnection hConn, SteamNetworkingQuickConnectionStatus *pStats ) -{ - PRINT_DEBUG_ENTRY(); - if (!pStats) - return false; - - return GetConnectionRealTimeStatus(hConn, pStats, 0, NULL) == k_EResultOK; -} - - -/// Returns detailed connection stats in text format. Useful -/// for dumping to a log, etc. -/// -/// Returns: -/// -1 failure (bad connection handle) -/// 0 OK, your buffer was filled in and '\0'-terminated -/// >0 Your buffer was either nullptr, or it was too small and the text got truncated. Try again with a buffer of at least N bytes. -int GetDetailedConnectionStatus( HSteamNetConnection hConn, char *pszBuf, int cbBuf ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return -1; -} - -/// Returns local IP and port that a listen socket created using CreateListenSocketIP is bound to. -/// -/// An IPv6 address of ::0 means "any IPv4 or IPv6" -/// An IPv6 address of ::ffff:0000:0000 means "any IPv4" -bool GetListenSocketAddress( HSteamListenSocket hSocket, SteamNetworkingIPAddr *address ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -/// Returns information about the listen socket. -/// -/// *pnIP and *pnPort will be 0 if the socket is set to listen for connections based -/// on SteamID only. If your listen socket accepts connections on IPv4, then both -/// fields will return nonzero, even if you originally passed a zero IP. However, -/// note that the address returned may be a private address (e.g. 10.0.0.x or 192.168.x.x), -/// and may not be reachable by a general host on the Internet. -bool GetListenSocketInfo( HSteamListenSocket hSocket, uint32 *pnIP, uint16 *pnPort ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - struct Listen_Socket *socket = get_connection_socket(hSocket); - if (!socket) return false; - if (pnIP) *pnIP = 0;//socket->ip; - if (pnPort) *pnPort = 0;//socket->port; - return true; -} - - -/// Create a pair of connections that are talking to each other, e.g. a loopback connection. -/// This is very useful for testing, or so that your client/server code can work the same -/// even when you are running a local "server". -/// -/// The two connections will immediately be placed into the connected state, and no callbacks -/// will be posted immediately. After this, if you close either connection, the other connection -/// will receive a callback, exactly as if they were communicating over the network. You must -/// close *both* sides in order to fully clean up the resources! -/// -/// By default, internal buffers are used, completely bypassing the network, the chopping up of -/// messages into packets, encryption, copying the payload, etc. This means that loopback -/// packets, by default, will not simulate lag or loss. Passing true for bUseNetworkLoopback will -/// cause the socket pair to send packets through the local network loopback device (127.0.0.1) -/// on ephemeral ports. Fake lag and loss are supported in this case, and CPU time is expended -/// to encrypt and decrypt. -/// -/// The SteamID assigned to both ends of the connection will be the SteamID of this interface. -bool CreateSocketPair( HSteamNetConnection *pOutConnection1, HSteamNetConnection *pOutConnection2, bool bUseNetworkLoopback ) -{ - PRINT_DEBUG("old"); - return CreateSocketPair(pOutConnection1, pOutConnection2, bUseNetworkLoopback, NULL, NULL); -} - -/// Create a pair of connections that are talking to each other, e.g. a loopback connection. -/// This is very useful for testing, or so that your client/server code can work the same -/// even when you are running a local "server". -/// -/// The two connections will immediately be placed into the connected state, and no callbacks -/// will be posted immediately. After this, if you close either connection, the other connection -/// will receive a callback, exactly as if they were communicating over the network. You must -/// close *both* sides in order to fully clean up the resources! -/// -/// By default, internal buffers are used, completely bypassing the network, the chopping up of -/// messages into packets, encryption, copying the payload, etc. This means that loopback -/// packets, by default, will not simulate lag or loss. Passing true for bUseNetworkLoopback will -/// cause the socket pair to send packets through the local network loopback device (127.0.0.1) -/// on ephemeral ports. Fake lag and loss are supported in this case, and CPU time is expended -/// to encrypt and decrypt. -/// -/// If you wish to assign a specific identity to either connection, you may pass a particular -/// identity. Otherwise, if you pass nullptr, the respective connection will assume a generic -/// "localhost" identity. If you use real network loopback, this might be translated to the -/// actual bound loopback port. Otherwise, the port will be zero. -bool CreateSocketPair( HSteamNetConnection *pOutConnection1, HSteamNetConnection *pOutConnection2, bool bUseNetworkLoopback, const SteamNetworkingIdentity *pIdentity1, const SteamNetworkingIdentity *pIdentity2 ) -{ - PRINT_DEBUG("%u %p %p", bUseNetworkLoopback, pIdentity1, pIdentity2); - std::lock_guard lock(global_mutex); - if (!pOutConnection1 || !pOutConnection1) return false; - - SteamNetworkingIdentity remote_identity; - remote_identity.SetSteamID(settings->get_local_steam_id()); - HSteamNetConnection con1 = new_connect_socket(remote_identity, 0, SNS_DISABLED_PORT, CONNECT_SOCKET_CONNECTED, k_HSteamListenSocket_Invalid, k_HSteamNetConnection_Invalid); - HSteamNetConnection con2 = new_connect_socket(remote_identity, 0, SNS_DISABLED_PORT, CONNECT_SOCKET_CONNECTED, k_HSteamListenSocket_Invalid, con1); - s->connect_sockets[con1].remote_id = con2; - *pOutConnection1 = con1; - *pOutConnection2 = con2; - return true; -} - -/// Configure multiple outbound messages streams ("lanes") on a connection, and -/// control head-of-line blocking between them. Messages within a given lane -/// are always sent in the order they are queued, but messages from different -/// lanes may be sent out of order. Each lane has its own message number -/// sequence. The first message sent on each lane will be assigned the number 1. -/// -/// Each lane has a "priority". Lower priority lanes will only be processed -/// when all higher-priority lanes are empty. The magnitudes of the priority -/// values are not relevant, only their sort order. Higher numeric values -/// take priority over lower numeric values. -/// -/// Each lane also is assigned a weight, which controls the approximate proportion -/// of the bandwidth that will be consumed by the lane, relative to other lanes -/// of the same priority. (This is assuming the lane stays busy. An idle lane -/// does not build up "credits" to be be spent once a message is queued.) -/// This value is only meaningful as a proportion, relative to other lanes with -/// the same priority. For lanes with different priorities, the strict priority -/// order will prevail, and their weights relative to each other are not relevant. -/// Thus, if a lane has a unique priority value, the weight value for that lane is -/// not relevant. -/// -/// Example: 3 lanes, with priorities [ 0, 10, 10 ] and weights [ (NA), 20, 5 ]. -/// Messages sent on the first will always be sent first, before messages in the -/// other two lanes. Its weight value is irrelevant, since there are no other -/// lanes with priority=0. The other two lanes will share bandwidth, with the second -/// and third lanes sharing bandwidth using a ratio of approximately 4:1. -/// (The weights [ NA, 4, 1 ] would be equivalent.) -/// -/// Notes: -/// - At the time of this writing, some code has performance cost that is linear -/// in the number of lanes, so keep the number of lanes to an absolute minimum. -/// 3 or so is fine; >8 is a lot. The max number of lanes on Steam is 255, -/// which is a very large number and not recommended! If you are compiling this -/// library from source, see STEAMNETWORKINGSOCKETS_MAX_LANES.) -/// - Lane priority values may be any int. Their absolute value is not relevant, -/// only the order matters. -/// - Weights must be positive, and due to implementation details, they are restricted -/// to 16-bit values. The absolute magnitudes don't matter, just the proportions. -/// - Messages sent on a lane index other than 0 have a small overhead on the wire, -/// so for maximum wire efficiency, lane 0 should be the "most common" lane, regardless -/// of priorities or weights. -/// - A connection has a single lane by default. Calling this function with -/// nNumLanes=1 is legal, but pointless, since the priority and weight values are -/// irrelevant in that case. -/// - You may reconfigure connection lanes at any time, however reducing the number of -/// lanes is not allowed. -/// - Reconfiguring lanes might restart any bandwidth sharing balancing. Usually you -/// will call this function once, near the start of the connection, perhaps after -/// exchanging a few messages. -/// - To assign all lanes the same priority, you may use pLanePriorities=NULL. -/// - If you wish all lanes with the same priority to share bandwidth equally (or -/// if no two lanes have the same priority value, and thus priority values are -/// irrelevant), you may use pLaneWeights=NULL -/// - Priorities and weights determine the order that messages are SENT on the wire. -/// There are NO GUARANTEES on the order that messages are RECEIVED! Due to packet -/// loss, out-of-order delivery, and subtle details of packet serialization, messages -/// might still be received slightly out-of-order! The *only* strong guarantee is that -/// *reliable* messages on the *same lane* will be delivered in the order they are sent. -/// - Each host configures the lanes for the packets they send; the lanes for the flow -/// in one direction are completely unrelated to the lanes in the opposite direction. -/// -/// Return value: -/// - k_EResultNoConnection - bad hConn -/// - k_EResultInvalidParam - Invalid number of lanes, bad weights, or you tried to reduce the number of lanes -/// - k_EResultInvalidState - Connection is already dead, etc -/// -/// See also: -/// SteamNetworkingMessage_t::m_idxLane -EResult ConfigureConnectionLanes( HSteamNetConnection hConn, int nNumLanes, const int *pLanePriorities, const uint16 *pLaneWeights ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - auto connect_socket = s->connect_sockets.find(hConn); - if (connect_socket == s->connect_sockets.end()) return k_EResultNoConnection; - //TODO - return k_EResultOK; -} - - -/// Get the identity assigned to this interface. -/// E.g. on Steam, this is the user's SteamID, or for the gameserver interface, the SteamID assigned -/// to the gameserver. Returns false and sets the result to an invalid identity if we don't know -/// our identity yet. (E.g. GameServer has not logged in. On Steam, the user will know their SteamID -/// even if they are not signed into Steam.) -bool GetIdentity( SteamNetworkingIdentity *pIdentity ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!pIdentity) return false; - pIdentity->SetSteamID(settings->get_local_steam_id()); - return true; -} - -/// Indicate our desire to be ready participate in authenticated communications. -/// If we are currently not ready, then steps will be taken to obtain the necessary -/// certificates. (This includes a certificate for us, as well as any CA certificates -/// needed to authenticate peers.) -/// -/// You can call this at program init time if you know that you are going to -/// be making authenticated connections, so that we will be ready immediately when -/// those connections are attempted. (Note that essentially all connections require -/// authentication, with the exception of ordinary UDP connections with authentication -/// disabled using k_ESteamNetworkingConfig_IP_AllowWithoutAuth.) If you don't call -/// this function, we will wait until a feature is utilized that that necessitates -/// these resources. -/// -/// You can also call this function to force a retry, if failure has occurred. -/// Once we make an attempt and fail, we will not automatically retry. -/// In this respect, the behavior of the system after trying and failing is the same -/// as before the first attempt: attempting authenticated communication or calling -/// this function will call the system to attempt to acquire the necessary resources. -/// -/// You can use GetAuthenticationStatus or listen for SteamNetAuthenticationStatus_t -/// to monitor the status. -/// -/// Returns the current value that would be returned from GetAuthenticationStatus. -ESteamNetworkingAvailability InitAuthentication() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_ESteamNetworkingAvailability_Current; -} - -/// Query our readiness to participate in authenticated communications. A -/// SteamNetAuthenticationStatus_t callback is posted any time this status changes, -/// but you can use this function to query it at any time. -/// -/// The value of SteamNetAuthenticationStatus_t::m_eAvail is returned. If you only -/// want this high level status, you can pass NULL for pDetails. If you want further -/// details, pass non-NULL to receive them. -ESteamNetworkingAvailability GetAuthenticationStatus( SteamNetAuthenticationStatus_t *pDetails ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_ESteamNetworkingAvailability_Current; -} - -/// Create a new poll group. -/// -/// You should destroy the poll group when you are done using DestroyPollGroup -HSteamNetPollGroup CreatePollGroup() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - static HSteamNetPollGroup poll_group_counter; - ++poll_group_counter; - - HSteamNetPollGroup poll_group_number = poll_group_counter; - s->poll_groups[poll_group_number] = std::list(); - return poll_group_number; -} - -/// Destroy a poll group created with CreatePollGroup(). -/// -/// If there are any connections in the poll group, they are removed from the group, -/// and left in a state where they are not part of any poll group. -/// Returns false if passed an invalid poll group handle. -bool DestroyPollGroup( HSteamNetPollGroup hPollGroup ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - auto group = s->poll_groups.find(hPollGroup); - if (group == s->poll_groups.end()) { - return false; - } - - for (auto c : group->second) { - auto connect_socket = s->connect_sockets.find(c); - if (connect_socket != s->connect_sockets.end()) { - connect_socket->second.poll_group = k_HSteamNetPollGroup_Invalid; - } - } - - s->poll_groups.erase(group); - return true; -} - -/// Assign a connection to a poll group. Note that a connection may only belong to a -/// single poll group. Adding a connection to a poll group implicitly removes it from -/// any other poll group it is in. -/// -/// You can pass k_HSteamNetPollGroup_Invalid to remove a connection from its current -/// poll group without adding it to a new poll group. -/// -/// If there are received messages currently pending on the connection, an attempt -/// is made to add them to the queue of messages for the poll group in approximately -/// the order that would have applied if the connection was already part of the poll -/// group at the time that the messages were received. -/// -/// Returns false if the connection handle is invalid, or if the poll group handle -/// is invalid (and not k_HSteamNetPollGroup_Invalid). -bool SetConnectionPollGroup( HSteamNetConnection hConn, HSteamNetPollGroup hPollGroup ) -{ - PRINT_DEBUG("%u %u", hConn, hPollGroup); - std::lock_guard lock(global_mutex); - auto connect_socket = s->connect_sockets.find(hConn); - if (connect_socket == s->connect_sockets.end()) { - return false; - } - - auto group = s->poll_groups.find(hPollGroup); - if (group == s->poll_groups.end() && hPollGroup != k_HSteamNetPollGroup_Invalid) { - return false; - } - - HSteamNetPollGroup old_poll_group = connect_socket->second.poll_group; - if (old_poll_group != k_HSteamNetPollGroup_Invalid) { - auto group = s->poll_groups.find(hPollGroup); - if (group != s->poll_groups.end()) { - group->second.remove(hConn); - } - } - - connect_socket->second.poll_group = hPollGroup; - if (hPollGroup == k_HSteamNetPollGroup_Invalid) { - return true; - } - - group->second.push_back(hConn); - return true; -} - -/// Same as ReceiveMessagesOnConnection, but will return the next messages available -/// on any connection in the poll group. Examine SteamNetworkingMessage_t::m_conn -/// to know which connection. (SteamNetworkingMessage_t::m_nConnUserData might also -/// be useful.) -/// -/// Delivery order of messages among different connections will usually match the -/// order that the last packet was received which completed the message. But this -/// is not a strong guarantee, especially for packets received right as a connection -/// is being assigned to poll group. -/// -/// Delivery order of messages on the same connection is well defined and the -/// same guarantees are present as mentioned in ReceiveMessagesOnConnection. -/// (But the messages are not grouped by connection, so they will not necessarily -/// appear consecutively in the list; they may be interleaved with messages for -/// other connections.) -int ReceiveMessagesOnPollGroup( HSteamNetPollGroup hPollGroup, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) -{ - PRINT_DEBUG("%u %i", hPollGroup, nMaxMessages); - std::lock_guard lock(global_mutex); - auto group = s->poll_groups.find(hPollGroup); - if (group == s->poll_groups.end()) { - return 0; - } - - SteamNetworkingMessage_t *msg = NULL; - int messages = 0; - - for (auto c : group->second) { - while (messages < nMaxMessages && (msg = get_steam_message_connection(c))) { - ppOutMessages[messages] = msg; - ++messages; - } - } - - PRINT_DEBUG("out %i", messages); - return messages; -} - - -//#ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE - -// -// Clients connecting to dedicated servers hosted in a data center, -// using central-authority-granted tickets. -// - -/// Called when we receive a ticket from our central matchmaking system. Puts the -/// ticket into a persistent cache, and optionally returns the parsed ticket. -/// -/// See stamdatagram_ticketgen.h for more details. -bool ReceivedRelayAuthTicket( const void *pvTicket, int cbTicket, SteamDatagramRelayAuthTicket *pOutParsedTicket ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - - -/// Search cache for a ticket to talk to the server on the specified virtual port. -/// If found, returns the number of second until the ticket expires, and optionally -/// the complete cracked ticket. Returns 0 if we don't have a ticket. -/// -/// Typically this is useful just to confirm that you have a ticket, before you -/// call ConnectToHostedDedicatedServer to connect to the server. -int FindRelayAuthTicketForServer( CSteamID steamID, int nVirtualPort, SteamDatagramRelayAuthTicket *pOutParsedTicket ) -{ - PRINT_DEBUG("old"); - return 0; -} - -/// Search cache for a ticket to talk to the server on the specified virtual port. -/// If found, returns the number of seconds until the ticket expires, and optionally -/// the complete cracked ticket. Returns 0 if we don't have a ticket. -/// -/// Typically this is useful just to confirm that you have a ticket, before you -/// call ConnectToHostedDedicatedServer to connect to the server. -int FindRelayAuthTicketForServer( const SteamNetworkingIdentity *identityGameServer, int nVirtualPort, SteamDatagramRelayAuthTicket *pOutParsedTicket ) -{ - PRINT_DEBUG("old1"); - return 0; -} - -int FindRelayAuthTicketForServer( const SteamNetworkingIdentity &identityGameServer, int nVirtualPort, SteamDatagramRelayAuthTicket *pOutParsedTicket ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -/// Client call to connect to a server hosted in a Valve data center, on the specified virtual -/// port. You must have placed a ticket for this server into the cache, or else this connect attempt will fail! -/// -/// You may wonder why tickets are stored in a cache, instead of simply being passed as an argument -/// here. The reason is to make reconnection to a gameserver robust, even if the client computer loses -/// connection to Steam or the central backend, or the app is restarted or crashes, etc. -/// -/// If you use this, you probably want to call ISteamNetworkingUtils::InitializeRelayNetworkAccess() -/// when your app initializes -HSteamNetConnection ConnectToHostedDedicatedServer( const SteamNetworkingIdentity &identityTarget, int nVirtualPort ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - return k_HSteamListenSocket_Invalid; -} - -HSteamNetConnection ConnectToHostedDedicatedServer( const SteamNetworkingIdentity *identityTarget, int nVirtualPort ) -{ - PRINT_DEBUG("old1"); - std::lock_guard lock(global_mutex); - return k_HSteamListenSocket_Invalid; -} - -/// Client call to connect to a server hosted in a Valve data center, on the specified virtual -/// port. You should have received a ticket for this server, or else this connect call will fail! -/// -/// You may wonder why tickets are stored in a cache, instead of simply being passed as an argument -/// here. The reason is to make reconnection to a gameserver robust, even if the client computer loses -/// connection to Steam or the central backend, or the app is restarted or crashes, etc. -HSteamNetConnection ConnectToHostedDedicatedServer( CSteamID steamIDTarget, int nVirtualPort ) -{ - PRINT_DEBUG("older"); - std::lock_guard lock(global_mutex); - return k_HSteamListenSocket_Invalid; -} - -HSteamNetConnection ConnectToHostedDedicatedServer( const SteamNetworkingIdentity &identityTarget, int nVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_HSteamListenSocket_Invalid; -} - -// -// Servers hosted in Valve data centers -// - -/// Returns the value of the SDR_LISTEN_PORT environment variable. -uint16 GetHostedDedicatedServerPort() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - //TODO? - return 27054; -} - - -/// If you are running in a production data center, this will return the data -/// center code. Returns 0 otherwise. -SteamNetworkingPOPID GetHostedDedicatedServerPOPID() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - - -/// Return info about the hosted server. You will need to send this information to your -/// backend, and put it in tickets, so that the relays will know how to forward traffic from -/// clients to your server. See SteamDatagramRelayAuthTicket for more info. -/// -/// NOTE ABOUT DEVELOPMENT ENVIRONMENTS: -/// In production in our data centers, these parameters are configured via environment variables. -/// In development, the only one you need to set is SDR_LISTEN_PORT, which is the local port you -/// want to listen on. Furthermore, if you are running your server behind a corporate firewall, -/// you probably will not be able to put the routing information returned by this function into -/// tickets. Instead, it should be a public internet address that the relays can use to send -/// data to your server. So you might just end up hardcoding a public address and setup port -/// forwarding on your corporate firewall. In that case, the port you put into the ticket -/// needs to be the public-facing port opened on your firewall, if it is different from the -/// actual server port. -/// -/// This function will fail if SteamDatagramServer_Init has not been called. -/// -/// Returns false if the SDR_LISTEN_PORT environment variable is not set. -bool GetHostedDedicatedServerAddress001( SteamDatagramHostedAddress *pRouting ) -{ - PRINT_DEBUG("%p", pRouting); - return GetHostedDedicatedServerAddress(pRouting) == k_EResultOK; -} - -/// Return info about the hosted server. This contains the PoPID of the server, -/// and opaque routing information that can be used by the relays to send traffic -/// to your server. -/// -/// You will need to send this information to your backend, and put it in tickets, -/// so that the relays will know how to forward traffic from -/// clients to your server. See SteamDatagramRelayAuthTicket for more info. -/// -/// Also, note that the routing information is contained in SteamDatagramGameCoordinatorServerLogin, -/// so if possible, it's preferred to use GetGameCoordinatorServerLogin to send this info -/// to your game coordinator service, and also login securely at the same time. -/// -/// On a successful exit, k_EResultOK is returned -/// -/// Unsuccessful exit: -/// - Something other than k_EResultOK is returned. -/// - k_EResultInvalidState: We are not configured to listen for SDR (SDR_LISTEN_SOCKET -/// is not set.) -/// - k_EResultPending: we do not (yet) have the authentication information needed. -/// (See GetAuthenticationStatus.) If you use environment variables to pre-fetch -/// the network config, this data should always be available immediately. -/// - A non-localized diagnostic debug message will be placed in m_data that describes -/// the cause of the failure. -/// -/// NOTE: The returned blob is not encrypted. Send it to your backend, but don't -/// directly share it with clients. -virtual EResult GetHostedDedicatedServerAddress( SteamDatagramHostedAddress *pRouting ) -{ - PRINT_DEBUG("%p", pRouting); - std::lock_guard lock(global_mutex); - pRouting->SetDevAddress(network->getOwnIP(), 27054); - return k_EResultOK; -} - -/// Create a listen socket on the specified virtual port. The physical UDP port to use -/// will be determined by the SDR_LISTEN_PORT environment variable. If a UDP port is not -/// configured, this call will fail. -/// -/// Note that this call MUST be made through the SteamNetworkingSocketsGameServer() interface -HSteamListenSocket CreateHostedDedicatedServerListenSocket( int nVirtualPort ) -{ - PRINT_DEBUG("old %i", nVirtualPort); - std::lock_guard lock(global_mutex); - return new_listen_socket(nVirtualPort, SNS_DISABLED_PORT); -} - -/// Create a listen socket on the specified virtual port. The physical UDP port to use -/// will be determined by the SDR_LISTEN_PORT environment variable. If a UDP port is not -/// configured, this call will fail. -/// -/// Note that this call MUST be made through the SteamGameServerNetworkingSockets() interface -/// -/// If you need to set any initial config options, pass them here. See -/// SteamNetworkingConfigValue_t for more about why this is preferable to -/// setting the options "immediately" after creation. -HSteamListenSocket CreateHostedDedicatedServerListenSocket( int nVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) -{ - PRINT_DEBUG("old %i", nVirtualPort); - //TODO config options - std::lock_guard lock(global_mutex); - return new_listen_socket(nVirtualPort, SNS_DISABLED_PORT); -} - - -//#endif // #ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE - -// -// Gets some debug text from the connection -// -bool GetConnectionDebugText( HSteamNetConnection hConn, char *pOut, int nOutCCH ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - - -// -// Set and get configuration values, see ESteamNetworkingConfigurationValue for individual descriptions. -// -// Returns the value or -1 is eConfigValue is invalid -int32 GetConfigurationValue( ESteamNetworkingConfigurationValue eConfigValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return -1; -} - -// Returns true if successfully set -bool SetConfigurationValue( ESteamNetworkingConfigurationValue eConfigValue, int32 nValue ) -{ - PRINT_DEBUG("%i: %i", eConfigValue, nValue); - std::lock_guard lock(global_mutex); - return true; -} - - -// Return the name of an int configuration value, or NULL if config value isn't known -const char *GetConfigurationValueName( ESteamNetworkingConfigurationValue eConfigValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return NULL; -} - - -// -// Set and get configuration strings, see ESteamNetworkingConfigurationString for individual descriptions. -// -// Get the configuration string, returns length of string needed if pDest is nullpr or destSize is 0 -// returns -1 if the eConfigValue is invalid -int32 GetConfigurationString( ESteamNetworkingConfigurationString eConfigString, char *pDest, int32 destSize ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return -1; -} - -bool SetConfigurationString( ESteamNetworkingConfigurationString eConfigString, const char *pString ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - - -// Return the name of a string configuration value, or NULL if config value isn't known -const char *GetConfigurationStringName( ESteamNetworkingConfigurationString eConfigString ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return NULL; -} - - -// -// Set and get configuration values, see ESteamNetworkingConnectionConfigurationValue for individual descriptions. -// -// Returns the value or -1 is eConfigValue is invalid -int32 GetConnectionConfigurationValue( HSteamNetConnection hConn, ESteamNetworkingConnectionConfigurationValue eConfigValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return -1; -} - -// Returns true if successfully set -bool SetConnectionConfigurationValue( HSteamNetConnection hConn, ESteamNetworkingConnectionConfigurationValue eConfigValue, int32 nValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -/// Generate an authentication blob that can be used to securely login with -/// your backend, using SteamDatagram_ParseHostedServerLogin. (See -/// steamdatagram_gamecoordinator.h) -/// -/// Before calling the function: -/// - Populate the app data in pLoginInfo (m_cbAppData and m_appData). You can leave -/// all other fields uninitialized. -/// - *pcbSignedBlob contains the size of the buffer at pBlob. (It should be -/// at least k_cbMaxSteamDatagramGameCoordinatorServerLoginSerialized.) -/// -/// On a successful exit: -/// - k_EResultOK is returned -/// - All of the remaining fields of pLoginInfo will be filled out. -/// - *pcbSignedBlob contains the size of the serialized blob that has been -/// placed into pBlob. -/// -/// Unsuccessful exit: -/// - Something other than k_EResultOK is returned. -/// - k_EResultNotLoggedOn: you are not logged in (yet) -/// - See GetHostedDedicatedServerAddress for more potential failure return values. -/// - A non-localized diagnostic debug message will be placed in pBlob that describes -/// the cause of the failure. -/// -/// This works by signing the contents of the SteamDatagramGameCoordinatorServerLogin -/// with the cert that is issued to this server. In dev environments, it's OK if you do -/// not have a cert. (You will need to enable insecure dev login in SteamDatagram_ParseHostedServerLogin.) -/// Otherwise, you will need a signed cert. -/// -/// NOTE: The routing blob returned here is not encrypted. Send it to your backend -/// and don't share it directly with clients. -EResult GetGameCoordinatorServerLogin( SteamDatagramGameCoordinatorServerLogin *pLoginInfo, int *pcbSignedBlob, void *pBlob ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EResultFail; -} - -// -// Relayed connections using custom signaling protocol -// -// This is used if you have your own method of sending out-of-band -// signaling / rendezvous messages through a mutually trusted channel. -// - -/// Create a P2P "client" connection that does signaling over a custom -/// rendezvous/signaling channel. -/// -/// pSignaling points to a new object that you create just for this connection. -/// It must stay valid until Release() is called. Once you pass the -/// object to this function, it assumes ownership. Release() will be called -/// from within the function call if the call fails. Furthermore, until Release() -/// is called, you should be prepared for methods to be invoked on your -/// object from any thread! You need to make sure your object is threadsafe! -/// Furthermore, you should make sure that dispatching the methods is done -/// as quickly as possible. -/// -/// This function will immediately construct a connection in the "connecting" -/// state. Soon after (perhaps before this function returns, perhaps in another thread), -/// the connection will begin sending signaling messages by calling -/// ISteamNetworkingConnectionCustomSignaling::SendSignal. -/// -/// When the remote peer accepts the connection (See -/// ISteamNetworkingCustomSignalingRecvContext::OnConnectRequest), -/// it will begin sending signaling messages. When these messages are received, -/// you can pass them to the connection using ReceivedP2PCustomSignal. -/// -/// If you know the identity of the peer that you expect to be on the other end, -/// you can pass their identity to improve debug output or just detect bugs. -/// If you don't know their identity yet, you can pass NULL, and their -/// identity will be established in the connection handshake. -/// -/// If you use this, you probably want to call ISteamNetworkingUtils::InitRelayNetworkAccess() -/// when your app initializes -/// -/// If you need to set any initial config options, pass them here. See -/// SteamNetworkingConfigValue_t for more about why this is preferable to -/// setting the options "immediately" after creation. -HSteamNetConnection ConnectP2PCustomSignaling( ISteamNetworkingConnectionCustomSignaling *pSignaling, const SteamNetworkingIdentity *pPeerIdentity, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - //return ConnectP2PCustomSignaling(pSignaling, pPeerIdentity, 0, nOptions, pOptions); - return k_HSteamNetConnection_Invalid; -} - -//HSteamNetConnection ConnectP2PCustomSignaling( ISteamNetworkingConnectionCustomSignaling *pSignaling, const SteamNetworkingIdentity *pPeerIdentity, int nRemoteVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) -HSteamNetConnection ConnectP2PCustomSignaling( ISteamNetworkingConnectionSignaling *pSignaling, const SteamNetworkingIdentity *pPeerIdentity, int nRemoteVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_HSteamNetConnection_Invalid; -} - -/// Called when custom signaling has received a message. When your -/// signaling channel receives a message, it should save off whatever -/// routing information was in the envelope into the context object, -/// and then pass the payload to this function. -/// -/// A few different things can happen next, depending on the message: -/// -/// - If the signal is associated with existing connection, it is dealt -/// with immediately. If any replies need to be sent, they will be -/// dispatched using the ISteamNetworkingConnectionCustomSignaling -/// associated with the connection. -/// - If the message represents a connection request (and the request -/// is not redundant for an existing connection), a new connection -/// will be created, and ReceivedConnectRequest will be called on your -/// context object to determine how to proceed. -/// - Otherwise, the message is for a connection that does not -/// exist (anymore). In this case, we *may* call SendRejectionReply -/// on your context object. -/// -/// In any case, we will not save off pContext or access it after this -/// function returns. -/// -/// Returns true if the message was parsed and dispatched without anything -/// unusual or suspicious happening. Returns false if there was some problem -/// with the message that prevented ordinary handling. (Debug output will -/// usually have more information.) -/// -/// If you expect to be using relayed connections, then you probably want -/// to call ISteamNetworkingUtils::InitRelayNetworkAccess() when your app initializes -bool ReceivedP2PCustomSignal( const void *pMsg, int cbMsg, ISteamNetworkingCustomSignalingRecvContext *pContext ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - return false; -} - -bool ReceivedP2PCustomSignal( const void *pMsg, int cbMsg, ISteamNetworkingSignalingRecvContext *pContext ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -// -// Certificate provision by the application. On Steam, we normally handle all this automatically -// and you will not need to use these advanced functions. -// - -/// Get blob that describes a certificate request. You can send this to your game coordinator. -/// Upon entry, *pcbBlob should contain the size of the buffer. On successful exit, it will -/// return the number of bytes that were populated. You can pass pBlob=NULL to query for the required -/// size. (256 bytes is a very conservative estimate.) -/// -/// Pass this blob to your game coordinator and call SteamDatagram_CreateCert. -bool GetCertificateRequest( int *pcbBlob, void *pBlob, SteamNetworkingErrMsg &errMsg ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -/// Set the certificate. The certificate blob should be the output of -/// SteamDatagram_CreateCert. -bool SetCertificate( const void *pCertificate, int cbCertificate, SteamNetworkingErrMsg &errMsg ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -/// Reset the identity associated with this instance. -/// Any open connections are closed. Any previous certificates, etc are discarded. -/// You can pass a specific identity that you want to use, or you can pass NULL, -/// in which case the identity will be invalid until you set it using SetCertificate -/// -/// NOTE: This function is not actually supported on Steam! It is included -/// for use on other platforms where the active user can sign out and -/// a new user can sign in. -void ResetIdentity( const SteamNetworkingIdentity *pIdentity ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - -// -// "FakeIP" system. -// -// A FakeIP is essentially a temporary, arbitrary identifier that -// happens to be a valid IPv4 address. The purpose of this system is to make it -// easy to integrate with existing code that identifies hosts using IPv4 addresses. -// The FakeIP address will never actually be used to send or receive any packets -// on the Internet, it is strictly an identifier. -// -// FakeIP addresses are designed to (hopefully) pass through existing code as -// transparently as possible, while conflicting with "real" addresses that might -// be in use on networks (both the Internet and LANs) in the same code as little -// as possible. At the time this comment is being written, they come from the -// 169.254.0.0/16 range, and the port number will always be >1024. HOWEVER, -// this is subject to change! Do not make assumptions about these addresses, -// or your code might break in the future. In particular, you should use -// functions such as ISteamNetworkingUtils::IsFakeIP to determine if an IP -// address is a "fake" one used by this system. -// - -/// Begin asynchronous process of allocating a fake IPv4 address that other -/// peers can use to contact us via P2P. IP addresses returned by this -/// function are globally unique for a given appid. -/// -/// nNumPorts is the numbers of ports you wish to reserve. This is useful -/// for the same reason that listening on multiple UDP ports is useful for -/// different types of traffic. Because these allocations come from a global -/// namespace, there is a relatively strict limit on the maximum number of -/// ports you may request. (At the time of this writing, the limit is 4.) -/// The Port assignments are *not* guaranteed to have any particular order -/// or relationship! Do *not* assume they are contiguous, even though that -/// may often occur in practice. -/// -/// Returns false if a request was already in progress, true if a new request -/// was started. A SteamNetworkingFakeIPResult_t will be posted when the request -/// completes. -/// -/// For gameservers, you *must* call this after initializing the SDK but before -/// beginning login. Steam needs to know in advance that FakeIP will be used. -/// Everywhere your public IP would normally appear (such as the server browser) will be -/// replaced by the FakeIP, and the fake port at index 0. The request is actually queued -/// until the logon completes, so you must not wait until the allocation completes -/// before logging in. Except for trivial failures that can be detected locally -/// (e.g. invalid parameter), a SteamNetworkingFakeIPResult_t callback (whether success or -/// failure) will not be posted until after we have logged in. Furthermore, it is assumed -/// that FakeIP allocation is essential for your application to function, and so failure -/// will not be reported until *several* retries have been attempted. This process may -/// last several minutes. It is *highly* recommended to treat failure as fatal. -/// -/// To communicate using a connection-oriented (TCP-style) API: -/// - Server creates a listen socket using CreateListenSocketP2PFakeIP -/// - Client connects using ConnectByIPAddress, passing in the FakeIP address. -/// - The connection will behave mostly like a P2P connection. The identities -/// that appear in SteamNetConnectionInfo_t will be the FakeIP identity until -/// we know the real identity. Then it will be the real identity. If the -/// SteamNetConnectionInfo_t::m_addrRemote is valid, it will be a real IPv4 -/// address of a NAT-punched connection. Otherwise, it will not be valid. -/// -/// To communicate using an ad-hoc sendto/recv from (UDP-style) API, -/// use CreateFakeUDPPort. -bool BeginAsyncRequestFakeIP( int nNumPorts ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -/// Return info about the FakeIP and port(s) that we have been assigned, -/// if any. idxFirstPort is currently reserved and must be zero. -/// Make sure and check SteamNetworkingFakeIPResult_t::m_eResult -void GetFakeIP( int idxFirstPort, SteamNetworkingFakeIPResult_t *pInfo ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - -/// Create a listen socket that will listen for P2P connections sent -/// to our FakeIP. A peer can initiate connections to this listen -/// socket by calling ConnectByIPAddress. -/// -/// idxFakePort refers to the *index* of the fake port requested, -/// not the actual port number. For example, pass 0 to refer to the -/// first port in the reservation. You must call this only after calling -/// BeginAsyncRequestFakeIP. However, you do not need to wait for the -/// request to complete before creating the listen socket. -HSteamListenSocket CreateListenSocketP2PFakeIP( int idxFakePort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_HSteamListenSocket_Invalid; -} - -/// If the connection was initiated using the "FakeIP" system, then we -/// we can get an IP address for the remote host. If the remote host had -/// a global FakeIP at the time the connection was established, this -/// function will return that global IP. Otherwise, a FakeIP that is -/// unique locally will be allocated from the local FakeIP address space, -/// and that will be returned. -/// -/// The allocation of local FakeIPs attempts to assign addresses in -/// a consistent manner. If multiple connections are made to the -/// same remote host, they *probably* will return the same FakeIP. -/// However, since the namespace is limited, this cannot be guaranteed. -/// -/// On failure, returns: -/// - k_EResultInvalidParam: invalid connection handle -/// - k_EResultIPNotFound: This connection wasn't made using FakeIP system -EResult GetRemoteFakeIPForConnection( HSteamNetConnection hConn, SteamNetworkingIPAddr *pOutAddr ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EResultNone; -} - -/// Get an interface that can be used like a UDP port to send/receive -/// datagrams to a FakeIP address. This is intended to make it easy -/// to port existing UDP-based code to take advantage of SDR. -/// -/// idxFakeServerPort refers to the *index* of the port allocated using -/// BeginAsyncRequestFakeIP and is used to create "server" ports. You may -/// call this before the allocation has completed. However, any attempts -/// to send packets will fail until the allocation has succeeded. When -/// the peer receives packets sent from this interface, the from address -/// of the packet will be the globally-unique FakeIP. If you call this -/// function multiple times and pass the same (nonnegative) fake port index, -/// the same object will be returned, and this object is not reference counted. -/// -/// To create a "client" port (e.g. the equivalent of an ephemeral UDP port) -/// pass -1. In this case, a distinct object will be returned for each call. -/// When the peer receives packets sent from this interface, the peer will -/// assign a FakeIP from its own locally-controlled namespace. -ISteamNetworkingFakeUDPPort *CreateFakeUDPPort( int idxFakeServerPort ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return NULL; -} - -// TEMP KLUDGE Call to invoke all queued callbacks. -// Eventually this function will go away, and callwacks will be ordinary Steamworks callbacks. -// You should call this at the same time you call SteamAPI_RunCallbacks and SteamGameServer_RunCallbacks -// to minimize potential changes in timing when that change happens. -void RunCallbacks( ISteamNetworkingSocketsCallbacks *pCallbacks ) -{ - // PRINT_DEBUG_ENTRY(); -} - - -void RunCallbacks() -{ - // PRINT_DEBUG_ENTRY(); - - //TODO: timeout unaccepted connections after a few seconds or so - auto current_time = std::chrono::steady_clock::now(); - auto socket_conn = std::begin(s->connect_sockets); - while (socket_conn != std::end(s->connect_sockets)) { - if (socket_conn->second.connect_requests_sent < 10 && socket_conn->second.status == CONNECT_SOCKET_CONNECTING && (std::chrono::duration_cast(current_time - socket_conn->second.connect_request_last_sent).count() > 3000)) { - send_packet_new_connection(socket_conn->first); - socket_conn->second.connect_request_last_sent = current_time; - socket_conn->second.connect_requests_sent += 1; - } - - ++socket_conn; - } -} - - -void Callback(Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); - - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - - } - - if (msg->low_level().type() == Low_Level::DISCONNECT) { - for (auto & connect_socket : s->connect_sockets) { - if (connect_socket.second.remote_identity.GetSteamID64() == msg->source_id()) { - enum connect_socket_status old_status = connect_socket.second.status; - connect_socket.second.status = CONNECT_SOCKET_TIMEDOUT; - launch_callback(connect_socket.first, old_status); - } - } - } - } - - if (msg->has_networking_sockets()) { - PRINT_DEBUG("got network socket msg %u " "%" PRIu64 "", msg->networking_sockets().type(), msg->source_id()); - if (msg->networking_sockets().type() == Networking_Sockets::CONNECTION_REQUEST) { - int virtual_port = msg->networking_sockets().virtual_port(); - int real_port = msg->networking_sockets().real_port(); - uint64 dest_id = msg->dest_id(); - std::vector::iterator conn; - if (virtual_port == SNS_DISABLED_PORT) { - conn = std::find_if(s->listen_sockets.begin(), s->listen_sockets.end(), [&real_port,&dest_id](struct Listen_Socket const& conn) { return conn.real_port == real_port && dest_id == conn.created_by.ConvertToUint64();}); - } else { - conn = std::find_if(s->listen_sockets.begin(), s->listen_sockets.end(), [&virtual_port,&dest_id](struct Listen_Socket const& conn) { return conn.virtual_port == virtual_port && dest_id == conn.created_by.ConvertToUint64();}); - } - - if (conn != s->listen_sockets.end()) { - auto connect_socket = std::find_if(s->connect_sockets.begin(), s->connect_sockets.end(), [msg](const auto &in) {return in.second.remote_identity.GetSteamID64() == msg->source_id() && (in.second.status == CONNECT_SOCKET_NOT_ACCEPTED || in.second.status == CONNECT_SOCKET_CONNECTED) && in.second.remote_id == msg->networking_sockets().connection_id_from();}); - if (connect_socket == s->connect_sockets.end()) { - SteamNetworkingIdentity identity; - identity.SetSteamID64(msg->source_id()); - HSteamNetConnection new_connection = new_connect_socket(identity, virtual_port, real_port, CONNECT_SOCKET_NOT_ACCEPTED, conn->socket_id, msg->networking_sockets().connection_id_from()); - launch_callback(new_connection, CONNECT_SOCKET_NO_CONNECTION); - } - } - - } else if (msg->networking_sockets().type() == Networking_Sockets::CONNECTION_ACCEPTED) { - auto connect_socket = s->connect_sockets.find(msg->networking_sockets().connection_id()); - if (connect_socket != s->connect_sockets.end()) { - if (connect_socket->second.remote_identity.GetSteamID64() == 0) { - connect_socket->second.remote_identity.SetSteamID64(msg->source_id()); - } - - if (connect_socket->second.remote_identity.GetSteamID64() == msg->source_id() && connect_socket->second.status == CONNECT_SOCKET_CONNECTING) { - connect_socket->second.remote_id = msg->networking_sockets().connection_id_from(); - connect_socket->second.status = CONNECT_SOCKET_CONNECTED; - launch_callback(connect_socket->first, CONNECT_SOCKET_CONNECTING); - } - } - } else if (msg->networking_sockets().type() == Networking_Sockets::DATA) { - auto connect_socket = s->connect_sockets.find(msg->networking_sockets().connection_id()); - if (connect_socket != s->connect_sockets.end()) { - if (connect_socket->second.remote_identity.GetSteamID64() == msg->source_id() && (connect_socket->second.status == CONNECT_SOCKET_CONNECTED)) { - PRINT_DEBUG("got data len %zu, num " "%" PRIu64 " on connection %u", msg->networking_sockets().data().size(), msg->networking_sockets().message_number(), connect_socket->first); - connect_socket->second.data.push(msg->networking_sockets()); - } - } else { - connect_socket = std::find_if(s->connect_sockets.begin(), s->connect_sockets.end(), [msg](const auto &in) {return in.second.remote_identity.GetSteamID64() == msg->source_id() && (in.second.status == CONNECT_SOCKET_NOT_ACCEPTED || in.second.status == CONNECT_SOCKET_CONNECTED) && in.second.remote_id == msg->networking_sockets().connection_id_from();}); - if (connect_socket != s->connect_sockets.end()) { - PRINT_DEBUG("got data len %zu, num " "%" PRIu64 " on not accepted connection %u", msg->networking_sockets().data().size(), msg->networking_sockets().message_number(), connect_socket->first); - connect_socket->second.data.push(msg->networking_sockets()); - } - } - } else if (msg->networking_sockets().type() == Networking_Sockets::CONNECTION_END) { - auto connect_socket = s->connect_sockets.find(msg->networking_sockets().connection_id()); - if (connect_socket != s->connect_sockets.end()) { - if (connect_socket->second.remote_identity.GetSteamID64() == msg->source_id() && connect_socket->second.status == CONNECT_SOCKET_CONNECTED) { - enum connect_socket_status old_status = connect_socket->second.status; - connect_socket->second.status = CONNECT_SOCKET_CLOSED; - launch_callback(connect_socket->first, old_status); - } - } - } - } -} + Steam_Networking_Sockets(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, shared_between_client_server *sbcs); + ~Steam_Networking_Sockets(); + + shared_between_client_server *get_shared_between_client_server(); + + + /// Creates a "server" socket that listens for clients to connect to, either by calling + /// ConnectSocketBySteamID or ConnectSocketByIPv4Address. + /// + /// nSteamConnectVirtualPort specifies how clients can connect to this socket using + /// ConnectBySteamID. A negative value indicates that this functionality is + /// disabled and clients must connect by IP address. It's very common for applications + /// to only have one listening socket; in that case, use zero. If you need to open + /// multiple listen sockets and have clients be able to connect to one or the other, then + /// nSteamConnectVirtualPort should be a small integer constant unique to each listen socket + /// you create. + /// + /// In the open-source version of this API, you must pass -1 for nSteamConnectVirtualPort + /// + /// If you want clients to connect to you by your IPv4 addresses using + /// ConnectByIPv4Address, then you must set nPort to be nonzero. Steam will + /// bind a UDP socket to the specified local port, and clients will send packets using + /// ordinary IP routing. It's up to you to take care of NAT, protecting your server + /// from DoS, etc. If you don't need clients to connect to you by IP, then set nPort=0. + /// Use nIP if you wish to bind to a particular local interface. Typically you will use 0, + /// which means to listen on all interfaces, and accept the default outbound IP address. + /// If nPort is zero, then nIP must also be zero. + /// + /// A SocketStatusCallback_t callback when another client attempts a connection. + HSteamListenSocket CreateListenSocket( int nSteamConnectVirtualPort, uint32 nIP, uint16 nPort ); + + /// Creates a "server" socket that listens for clients to connect to by + /// calling ConnectByIPAddress, over ordinary UDP (IPv4 or IPv6) + /// + /// You must select a specific local port to listen on and set it + /// the port field of the local address. + /// + /// Usually you wil set the IP portion of the address to zero, (SteamNetworkingIPAddr::Clear()). + /// This means that you will not bind to any particular local interface. In addition, + /// if possible the socket will be bound in "dual stack" mode, which means that it can + /// accept both IPv4 and IPv6 clients. If you wish to bind a particular interface, then + /// set the local address to the appropriate IPv4 or IPv6 IP. + /// + /// When a client attempts to connect, a SteamNetConnectionStatusChangedCallback_t + /// will be posted. The connection will be in the connecting state. + HSteamListenSocket CreateListenSocketIP( const SteamNetworkingIPAddr &localAddress ); + + HSteamListenSocket CreateListenSocketIP( const SteamNetworkingIPAddr *localAddress ); + + HSteamListenSocket CreateListenSocketIP( const SteamNetworkingIPAddr &localAddress, int nOptions, const SteamNetworkingConfigValue_t *pOptions ); + + /// Creates a connection and begins talking to a "server" over UDP at the + /// given IPv4 or IPv6 address. The remote host must be listening with a + /// matching call to CreateListenSocketIP on the specified port. + /// + /// A SteamNetConnectionStatusChangedCallback_t callback will be triggered when we start + /// connecting, and then another one on either timeout or successful connection. + /// + /// If the server does not have any identity configured, then their network address + /// will be the only identity in use. Or, the network host may provide a platform-specific + /// identity with or without a valid certificate to authenticate that identity. (These + /// details will be contained in the SteamNetConnectionStatusChangedCallback_t.) It's + /// up to your application to decide whether to allow the connection. + /// + /// By default, all connections will get basic encryption sufficient to prevent + /// casual eavesdropping. But note that without certificates (or a shared secret + /// distributed through some other out-of-band mechanism), you don't have any + /// way of knowing who is actually on the other end, and thus are vulnerable to + /// man-in-the-middle attacks. + HSteamNetConnection ConnectByIPAddress( const SteamNetworkingIPAddr &address ); + + HSteamNetConnection ConnectByIPAddress( const SteamNetworkingIPAddr *address ); + + HSteamNetConnection ConnectByIPAddress( const SteamNetworkingIPAddr &address, int nOptions, const SteamNetworkingConfigValue_t *pOptions ); + + /// Like CreateListenSocketIP, but clients will connect using ConnectP2P + /// + /// nVirtualPort specifies how clients can connect to this socket using + /// ConnectP2P. It's very common for applications to only have one listening socket; + /// in that case, use zero. If you need to open multiple listen sockets and have clients + /// be able to connect to one or the other, then nVirtualPort should be a small integer (<1000) + /// unique to each listen socket you create. + /// + /// If you use this, you probably want to call ISteamNetworkingUtils::InitializeRelayNetworkAccess() + /// when your app initializes + HSteamListenSocket CreateListenSocketP2P( int nVirtualPort ); + + HSteamListenSocket CreateListenSocketP2P( int nVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ); + + /// Begin connecting to a server that is identified using a platform-specific identifier. + /// This requires some sort of third party rendezvous service, and will depend on the + /// platform and what other libraries and services you are integrating with. + /// + /// At the time of this writing, there is only one supported rendezvous service: Steam. + /// Set the SteamID (whether "user" or "gameserver") and Steam will determine if the + /// client is online and facilitate a relay connection. Note that all P2P connections on + /// Steam are currently relayed. + /// + /// If you use this, you probably want to call ISteamNetworkingUtils::InitializeRelayNetworkAccess() + /// when your app initializes + HSteamNetConnection ConnectP2P( const SteamNetworkingIdentity &identityRemote, int nVirtualPort ); + + HSteamNetConnection ConnectP2P( const SteamNetworkingIdentity *identityRemote, int nVirtualPort ); + + HSteamNetConnection ConnectP2P( const SteamNetworkingIdentity &identityRemote, int nVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ); + + /// Creates a connection and begins talking to a remote destination. The remote host + /// must be listening with a matching call to CreateListenSocket. + /// + /// Use ConnectBySteamID to connect using the SteamID (client or game server) as the network address. + /// Use ConnectByIPv4Address to connect by IP address. + /// + /// A SteamNetConnectionStatusChangedCallback_t callback will be triggered when we start connecting, + /// and then another one on timeout or successful connection + //#ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE + HSteamNetConnection ConnectBySteamID( CSteamID steamIDTarget, int nVirtualPort ); + + //#endif + HSteamNetConnection ConnectByIPv4Address( uint32 nIP, uint16 nPort ); + + + /// Accept an incoming connection that has been received on a listen socket. + /// + /// When a connection attempt is received (perhaps after a few basic handshake + /// packets have been exchanged to prevent trivial spoofing), a connection interface + /// object is created in the k_ESteamNetworkingConnectionState_Connecting state + /// and a SteamNetConnectionStatusChangedCallback_t is posted. At this point, your + /// application MUST either accept or close the connection. (It may not ignore it.) + /// Accepting the connection will transition it either into the connected state, + /// of the finding route state, depending on the connection type. + /// + /// You should take action within a second or two, because accepting the connection is + /// what actually sends the reply notifying the client that they are connected. If you + /// delay taking action, from the client's perspective it is the same as the network + /// being unresponsive, and the client may timeout the connection attempt. In other + /// words, the client cannot distinguish between a delay caused by network problems + /// and a delay caused by the application. + /// + /// This means that if your application goes for more than a few seconds without + /// processing callbacks (for example, while loading a map), then there is a chance + /// that a client may attempt to connect in that interval and fail due to timeout. + /// + /// If the application does not respond to the connection attempt in a timely manner, + /// and we stop receiving communication from the client, the connection attempt will + /// be timed out locally, transitioning the connection to the + /// k_ESteamNetworkingConnectionState_ProblemDetectedLocally state. The client may also + /// close the connection before it is accepted, and a transition to the + /// k_ESteamNetworkingConnectionState_ClosedByPeer is also possible depending the exact + /// sequence of events. + /// + /// Returns k_EResultInvalidParam if the handle is invalid. + /// Returns k_EResultInvalidState if the connection is not in the appropriate state. + /// (Remember that the connection state could change in between the time that the + /// notification being posted to the queue and when it is received by the application.) + EResult AcceptConnection( HSteamNetConnection hConn ); + + + /// Disconnects from the remote host and invalidates the connection handle. + /// Any unread data on the connection is discarded. + /// + /// nReason is an application defined code that will be received on the other + /// end and recorded (when possible) in backend analytics. The value should + /// come from a restricted range. (See ESteamNetConnectionEnd.) If you don't need + /// to communicate any information to the remote host, and do not want analytics to + /// be able to distinguish "normal" connection terminations from "exceptional" ones, + /// You may pass zero, in which case the generic value of + /// k_ESteamNetConnectionEnd_App_Generic will be used. + /// + /// pszDebug is an optional human-readable diagnostic string that will be received + /// by the remote host and recorded (when possible) in backend analytics. + /// + /// If you wish to put the socket into a "linger" state, where an attempt is made to + /// flush any remaining sent data, use bEnableLinger=true. Otherwise reliable data + /// is not flushed. + /// + /// If the connection has already ended and you are just freeing up the + /// connection interface, the reason code, debug string, and linger flag are + /// ignored. + bool CloseConnection( HSteamNetConnection hPeer, int nReason, const char *pszDebug, bool bEnableLinger ); + + + /// Destroy a listen socket, and all the client sockets generated by accepting connections + /// on the listen socket. + /// + /// pszNotifyRemoteReason determines what cleanup actions are performed on the client + /// sockets being destroyed. (See DestroySocket for more details.) + /// + /// Note that if cleanup is requested and you have requested the listen socket bound to a + /// particular local port to facilitate direct UDP/IPv4 connections, then the underlying UDP + /// socket must remain open until all clients have been cleaned up. + bool CloseListenSocket( HSteamListenSocket hSocket, const char *pszNotifyRemoteReason ); + + /// Destroy a listen socket. All the connections that were accepting on the listen + /// socket are closed ungracefully. + bool CloseListenSocket( HSteamListenSocket hSocket ); + + /// Set connection user data. Returns false if the handle is invalid. + bool SetConnectionUserData( HSteamNetConnection hPeer, int64 nUserData ); + + + /// Fetch connection user data. Returns -1 if handle is invalid + /// or if you haven't set any userdata on the connection. + int64 GetConnectionUserData( HSteamNetConnection hPeer ); + + + /// Set a name for the connection, used mostly for debugging + void SetConnectionName( HSteamNetConnection hPeer, const char *pszName ); + + + /// Fetch connection name. Returns false if handle is invalid + bool GetConnectionName( HSteamNetConnection hPeer, char *pszName, int nMaxLen ); + + + /// Send a message to the remote host on the connected socket. + /// + /// eSendType determines the delivery guarantees that will be provided, + /// when data should be buffered, etc. + /// + /// Note that the semantics we use for messages are not precisely + /// the same as the semantics of a standard "stream" socket. + /// (SOCK_STREAM) For an ordinary stream socket, the boundaries + /// between chunks are not considered relevant, and the sizes of + /// the chunks of data written will not necessarily match up to + /// the sizes of the chunks that are returned by the reads on + /// the other end. The remote host might read a partial chunk, + /// or chunks might be coalesced. For the message semantics + /// used here, however, the sizes WILL match. Each send call + /// will match a successful read call on the remote host + /// one-for-one. If you are porting existing stream-oriented + /// code to the semantics of reliable messages, your code should + /// work the same, since reliable message semantics are more + /// strict than stream semantics. The only caveat is related to + /// performance: there is per-message overhead to retain the + /// messages sizes, and so if your code sends many small chunks + /// of data, performance will suffer. Any code based on stream + /// sockets that does not write excessively small chunks will + /// work without any changes. + EResult SendMessageToConnection( HSteamNetConnection hConn, const void *pData, uint32 cbData, ESteamNetworkingSendType eSendType ); + + /// Send a message to the remote host on the specified connection. + /// + /// nSendFlags determines the delivery guarantees that will be provided, + /// when data should be buffered, etc. E.g. k_nSteamNetworkingSend_Unreliable + /// + /// Note that the semantics we use for messages are not precisely + /// the same as the semantics of a standard "stream" socket. + /// (SOCK_STREAM) For an ordinary stream socket, the boundaries + /// between chunks are not considered relevant, and the sizes of + /// the chunks of data written will not necessarily match up to + /// the sizes of the chunks that are returned by the reads on + /// the other end. The remote host might read a partial chunk, + /// or chunks might be coalesced. For the message semantics + /// used here, however, the sizes WILL match. Each send call + /// will match a successful read call on the remote host + /// one-for-one. If you are porting existing stream-oriented + /// code to the semantics of reliable messages, your code should + /// work the same, since reliable message semantics are more + /// strict than stream semantics. The only caveat is related to + /// performance: there is per-message overhead to retain the + /// message sizes, and so if your code sends many small chunks + /// of data, performance will suffer. Any code based on stream + /// sockets that does not write excessively small chunks will + /// work without any changes. + /// + /// The pOutMessageNumber is an optional pointer to receive the + /// message number assigned to the message, if sending was successful. + /// + /// Returns: + /// - k_EResultInvalidParam: invalid connection handle, or the individual message is too big. + /// (See k_cbMaxSteamNetworkingSocketsMessageSizeSend) + /// - k_EResultInvalidState: connection is in an invalid state + /// - k_EResultNoConnection: connection has ended + /// - k_EResultIgnored: You used k_nSteamNetworkingSend_NoDelay, and the message was dropped because + /// we were not ready to send it. + /// - k_EResultLimitExceeded: there was already too much data queued to be sent. + /// (See k_ESteamNetworkingConfig_SendBufferSize) + EResult SendMessageToConnection( HSteamNetConnection hConn, const void *pData, uint32 cbData, int nSendFlags, int64 *pOutMessageNumber ); + + EResult SendMessageToConnection( HSteamNetConnection hConn, const void *pData, uint32 cbData, int nSendFlags ); + + /// Send one or more messages without copying the message payload. + /// This is the most efficient way to send messages. To use this + /// function, you must first allocate a message object using + /// ISteamNetworkingUtils::AllocateMessage. (Do not declare one + /// on the stack or allocate your own.) + /// + /// You should fill in the message payload. You can either let + /// it allocate the buffer for you and then fill in the payload, + /// or if you already have a buffer allocated, you can just point + /// m_pData at your buffer and set the callback to the appropriate function + /// to free it. Note that if you use your own buffer, it MUST remain valid + /// until the callback is executed. And also note that your callback can be + /// invoked at ant time from any thread (perhaps even before SendMessages + /// returns!), so it MUST be fast and threadsafe. + /// + /// You MUST also fill in: + /// - m_conn - the handle of the connection to send the message to + /// - m_nFlags - bitmask of k_nSteamNetworkingSend_xxx flags. + /// + /// All other fields are currently reserved and should not be modified. + /// + /// The library will take ownership of the message structures. They may + /// be modified or become invalid at any time, so you must not read them + /// after passing them to this function. + /// + /// pOutMessageNumberOrResult is an optional array that will receive, + /// for each message, the message number that was assigned to the message + /// if sending was successful. If sending failed, then a negative EResult + /// valid is placed into the array. For example, the array will hold + /// -k_EResultInvalidState if the connection was in an invalid state. + /// See ISteamNetworkingSockets::SendMessageToConnection for possible + /// failure codes. + void SendMessages( int nMessages, SteamNetworkingMessage_t *const *pMessages, int64 *pOutMessageNumberOrResult ); + + + /// If Nagle is enabled (its on by default) then when calling + /// SendMessageToConnection the message will be queued up the Nagle time + /// before being sent to merge small messages into the same packet. + /// + /// Call this function to flush any queued messages and send them immediately + /// on the next transmission time (often that means right now). + EResult FlushMessagesOnConnection( HSteamNetConnection hConn ); + + /// Fetch the next available message(s) from the connection, if any. + /// Returns the number of messages returned into your array, up to nMaxMessages. + /// If the connection handle is invalid, -1 is returned. + /// + /// The order of the messages returned in the array is relevant. + /// Reliable messages will be received in the order they were sent (and with the + /// same sizes --- see SendMessageToConnection for on this subtle difference from a stream socket). + /// + /// Unreliable messages may be dropped, or delivered out of order withrespect to + /// each other or with respect to reliable messages. The same unreliable message + /// may be received multiple times. + /// + /// If any messages are returned, you MUST call SteamNetworkingMessage_t::Release() on each + /// of them free up resources after you are done. It is safe to keep the object alive for + /// a little while (put it into some queue, etc), and you may call Release() from any thread. + int ReceiveMessagesOnConnection( HSteamNetConnection hConn, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ); + + /// Same as ReceiveMessagesOnConnection, but will return the next message available + /// on any connection that was accepted through the specified listen socket. Examine + /// SteamNetworkingMessage_t::m_conn to know which client connection. + /// + /// Delivery order of messages among different clients is not defined. They may + /// be returned in an order different from what they were actually received. (Delivery + /// order of messages from the same client is well defined, and thus the order of the + /// messages is relevant!) + int ReceiveMessagesOnListenSocket( HSteamListenSocket hSocket, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ); + + /// Returns basic information about the high-level state of the connection. + bool GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo_t *pInfo ); + + /// Returns a small set of information about the real-time state of the connection + /// and the queue status of each lane. + /// + /// - pStatus may be NULL if the information is not desired. (E.g. you are only interested + /// in the lane information.) + /// - On entry, nLanes specifies the length of the pLanes array. This may be 0 + /// if you do not wish to receive any lane data. It's OK for this to be smaller than + /// the total number of configured lanes. + /// - pLanes points to an array that will receive lane-specific info. It can be NULL + /// if this is not needed. + /// + /// Return value: + /// - k_EResultNoConnection - connection handle is invalid or connection has been closed. + /// - k_EResultInvalidParam - nLanes is bad + EResult GetConnectionRealTimeStatus( HSteamNetConnection hConn, SteamNetConnectionRealTimeStatus_t *pStatus, int nLanes, SteamNetConnectionRealTimeLaneStatus_t *pLanes ); + + /// Fetch the next available message(s) from the socket, if any. + /// Returns the number of messages returned into your array, up to nMaxMessages. + /// If the connection handle is invalid, -1 is returned. + /// + /// The order of the messages returned in the array is relevant. + /// Reliable messages will be received in the order they were sent (and with the + /// same sizes --- see SendMessageToConnection for on this subtle difference from a stream socket). + /// + /// FIXME - We're still debating the exact set of guarantees for unreliable, so this might change. + /// Unreliable messages may not be received. The order of delivery of unreliable messages + /// is NOT specified. They may be received out of order with respect to each other or + /// reliable messages. They may be received multiple times! + /// + /// If any messages are returned, you MUST call Release() to each of them free up resources + /// after you are done. It is safe to keep the object alive for a little while (put it + /// into some queue, etc), and you may call Release() from any thread. + int ReceiveMessagesOnConnection( HSteamNetConnection hConn, SteamNetworkingMessage001_t **ppOutMessages, int nMaxMessages ); + + + /// Same as ReceiveMessagesOnConnection, but will return the next message available + /// on any client socket that was accepted through the specified listen socket. Examine + /// SteamNetworkingMessage_t::m_conn to know which client connection. + /// + /// Delivery order of messages among different clients is not defined. They may + /// be returned in an order different from what they were actually received. (Delivery + /// order of messages from the same client is well defined, and thus the order of the + /// messages is relevant!) + int ReceiveMessagesOnListenSocket( HSteamListenSocket hSocket, SteamNetworkingMessage001_t **ppOutMessages, int nMaxMessages ); + + + /// Returns information about the specified connection. + bool GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo001_t *pInfo ); + + + /// Returns brief set of connection status that you might want to display + /// to the user in game. + bool GetQuickConnectionStatus( HSteamNetConnection hConn, SteamNetworkingQuickConnectionStatus *pStats ); + + + /// Returns detailed connection stats in text format. Useful + /// for dumping to a log, etc. + /// + /// Returns: + /// -1 failure (bad connection handle) + /// 0 OK, your buffer was filled in and '\0'-terminated + /// >0 Your buffer was either nullptr, or it was too small and the text got truncated. Try again with a buffer of at least N bytes. + int GetDetailedConnectionStatus( HSteamNetConnection hConn, char *pszBuf, int cbBuf ); + + /// Returns local IP and port that a listen socket created using CreateListenSocketIP is bound to. + /// + /// An IPv6 address of ::0 means "any IPv4 or IPv6" + /// An IPv6 address of ::ffff:0000:0000 means "any IPv4" + bool GetListenSocketAddress( HSteamListenSocket hSocket, SteamNetworkingIPAddr *address ); + + /// Returns information about the listen socket. + /// + /// *pnIP and *pnPort will be 0 if the socket is set to listen for connections based + /// on SteamID only. If your listen socket accepts connections on IPv4, then both + /// fields will return nonzero, even if you originally passed a zero IP. However, + /// note that the address returned may be a private address (e.g. 10.0.0.x or 192.168.x.x), + /// and may not be reachable by a general host on the Internet. + bool GetListenSocketInfo( HSteamListenSocket hSocket, uint32 *pnIP, uint16 *pnPort ); + + + /// Create a pair of connections that are talking to each other, e.g. a loopback connection. + /// This is very useful for testing, or so that your client/server code can work the same + /// even when you are running a local "server". + /// + /// The two connections will immediately be placed into the connected state, and no callbacks + /// will be posted immediately. After this, if you close either connection, the other connection + /// will receive a callback, exactly as if they were communicating over the network. You must + /// close *both* sides in order to fully clean up the resources! + /// + /// By default, internal buffers are used, completely bypassing the network, the chopping up of + /// messages into packets, encryption, copying the payload, etc. This means that loopback + /// packets, by default, will not simulate lag or loss. Passing true for bUseNetworkLoopback will + /// cause the socket pair to send packets through the local network loopback device (127.0.0.1) + /// on ephemeral ports. Fake lag and loss are supported in this case, and CPU time is expended + /// to encrypt and decrypt. + /// + /// The SteamID assigned to both ends of the connection will be the SteamID of this interface. + bool CreateSocketPair( HSteamNetConnection *pOutConnection1, HSteamNetConnection *pOutConnection2, bool bUseNetworkLoopback ); + + /// Create a pair of connections that are talking to each other, e.g. a loopback connection. + /// This is very useful for testing, or so that your client/server code can work the same + /// even when you are running a local "server". + /// + /// The two connections will immediately be placed into the connected state, and no callbacks + /// will be posted immediately. After this, if you close either connection, the other connection + /// will receive a callback, exactly as if they were communicating over the network. You must + /// close *both* sides in order to fully clean up the resources! + /// + /// By default, internal buffers are used, completely bypassing the network, the chopping up of + /// messages into packets, encryption, copying the payload, etc. This means that loopback + /// packets, by default, will not simulate lag or loss. Passing true for bUseNetworkLoopback will + /// cause the socket pair to send packets through the local network loopback device (127.0.0.1) + /// on ephemeral ports. Fake lag and loss are supported in this case, and CPU time is expended + /// to encrypt and decrypt. + /// + /// If you wish to assign a specific identity to either connection, you may pass a particular + /// identity. Otherwise, if you pass nullptr, the respective connection will assume a generic + /// "localhost" identity. If you use real network loopback, this might be translated to the + /// actual bound loopback port. Otherwise, the port will be zero. + bool CreateSocketPair( HSteamNetConnection *pOutConnection1, HSteamNetConnection *pOutConnection2, bool bUseNetworkLoopback, const SteamNetworkingIdentity *pIdentity1, const SteamNetworkingIdentity *pIdentity2 ); + + /// Configure multiple outbound messages streams ("lanes") on a connection, and + /// control head-of-line blocking between them. Messages within a given lane + /// are always sent in the order they are queued, but messages from different + /// lanes may be sent out of order. Each lane has its own message number + /// sequence. The first message sent on each lane will be assigned the number 1. + /// + /// Each lane has a "priority". Lower priority lanes will only be processed + /// when all higher-priority lanes are empty. The magnitudes of the priority + /// values are not relevant, only their sort order. Higher numeric values + /// take priority over lower numeric values. + /// + /// Each lane also is assigned a weight, which controls the approximate proportion + /// of the bandwidth that will be consumed by the lane, relative to other lanes + /// of the same priority. (This is assuming the lane stays busy. An idle lane + /// does not build up "credits" to be be spent once a message is queued.) + /// This value is only meaningful as a proportion, relative to other lanes with + /// the same priority. For lanes with different priorities, the strict priority + /// order will prevail, and their weights relative to each other are not relevant. + /// Thus, if a lane has a unique priority value, the weight value for that lane is + /// not relevant. + /// + /// Example: 3 lanes, with priorities [ 0, 10, 10 ] and weights [ (NA), 20, 5 ]. + /// Messages sent on the first will always be sent first, before messages in the + /// other two lanes. Its weight value is irrelevant, since there are no other + /// lanes with priority=0. The other two lanes will share bandwidth, with the second + /// and third lanes sharing bandwidth using a ratio of approximately 4:1. + /// (The weights [ NA, 4, 1 ] would be equivalent.) + /// + /// Notes: + /// - At the time of this writing, some code has performance cost that is linear + /// in the number of lanes, so keep the number of lanes to an absolute minimum. + /// 3 or so is fine; >8 is a lot. The max number of lanes on Steam is 255, + /// which is a very large number and not recommended! If you are compiling this + /// library from source, see STEAMNETWORKINGSOCKETS_MAX_LANES.) + /// - Lane priority values may be any int. Their absolute value is not relevant, + /// only the order matters. + /// - Weights must be positive, and due to implementation details, they are restricted + /// to 16-bit values. The absolute magnitudes don't matter, just the proportions. + /// - Messages sent on a lane index other than 0 have a small overhead on the wire, + /// so for maximum wire efficiency, lane 0 should be the "most common" lane, regardless + /// of priorities or weights. + /// - A connection has a single lane by default. Calling this function with + /// nNumLanes=1 is legal, but pointless, since the priority and weight values are + /// irrelevant in that case. + /// - You may reconfigure connection lanes at any time, however reducing the number of + /// lanes is not allowed. + /// - Reconfiguring lanes might restart any bandwidth sharing balancing. Usually you + /// will call this function once, near the start of the connection, perhaps after + /// exchanging a few messages. + /// - To assign all lanes the same priority, you may use pLanePriorities=NULL. + /// - If you wish all lanes with the same priority to share bandwidth equally (or + /// if no two lanes have the same priority value, and thus priority values are + /// irrelevant), you may use pLaneWeights=NULL + /// - Priorities and weights determine the order that messages are SENT on the wire. + /// There are NO GUARANTEES on the order that messages are RECEIVED! Due to packet + /// loss, out-of-order delivery, and subtle details of packet serialization, messages + /// might still be received slightly out-of-order! The *only* strong guarantee is that + /// *reliable* messages on the *same lane* will be delivered in the order they are sent. + /// - Each host configures the lanes for the packets they send; the lanes for the flow + /// in one direction are completely unrelated to the lanes in the opposite direction. + /// + /// Return value: + /// - k_EResultNoConnection - bad hConn + /// - k_EResultInvalidParam - Invalid number of lanes, bad weights, or you tried to reduce the number of lanes + /// - k_EResultInvalidState - Connection is already dead, etc + /// + /// See also: + /// SteamNetworkingMessage_t::m_idxLane + EResult ConfigureConnectionLanes( HSteamNetConnection hConn, int nNumLanes, const int *pLanePriorities, const uint16 *pLaneWeights ); + + + /// Get the identity assigned to this interface. + /// E.g. on Steam, this is the user's SteamID, or for the gameserver interface, the SteamID assigned + /// to the gameserver. Returns false and sets the result to an invalid identity if we don't know + /// our identity yet. (E.g. GameServer has not logged in. On Steam, the user will know their SteamID + /// even if they are not signed into Steam.) + bool GetIdentity( SteamNetworkingIdentity *pIdentity ); + + /// Indicate our desire to be ready participate in authenticated communications. + /// If we are currently not ready, then steps will be taken to obtain the necessary + /// certificates. (This includes a certificate for us, as well as any CA certificates + /// needed to authenticate peers.) + /// + /// You can call this at program init time if you know that you are going to + /// be making authenticated connections, so that we will be ready immediately when + /// those connections are attempted. (Note that essentially all connections require + /// authentication, with the exception of ordinary UDP connections with authentication + /// disabled using k_ESteamNetworkingConfig_IP_AllowWithoutAuth.) If you don't call + /// this function, we will wait until a feature is utilized that that necessitates + /// these resources. + /// + /// You can also call this function to force a retry, if failure has occurred. + /// Once we make an attempt and fail, we will not automatically retry. + /// In this respect, the behavior of the system after trying and failing is the same + /// as before the first attempt: attempting authenticated communication or calling + /// this function will call the system to attempt to acquire the necessary resources. + /// + /// You can use GetAuthenticationStatus or listen for SteamNetAuthenticationStatus_t + /// to monitor the status. + /// + /// Returns the current value that would be returned from GetAuthenticationStatus. + ESteamNetworkingAvailability InitAuthentication(); + + /// Query our readiness to participate in authenticated communications. A + /// SteamNetAuthenticationStatus_t callback is posted any time this status changes, + /// but you can use this function to query it at any time. + /// + /// The value of SteamNetAuthenticationStatus_t::m_eAvail is returned. If you only + /// want this high level status, you can pass NULL for pDetails. If you want further + /// details, pass non-NULL to receive them. + ESteamNetworkingAvailability GetAuthenticationStatus( SteamNetAuthenticationStatus_t *pDetails ); + + /// Create a new poll group. + /// + /// You should destroy the poll group when you are done using DestroyPollGroup + HSteamNetPollGroup CreatePollGroup(); + + /// Destroy a poll group created with CreatePollGroup(). + /// + /// If there are any connections in the poll group, they are removed from the group, + /// and left in a state where they are not part of any poll group. + /// Returns false if passed an invalid poll group handle. + bool DestroyPollGroup( HSteamNetPollGroup hPollGroup ); + + /// Assign a connection to a poll group. Note that a connection may only belong to a + /// single poll group. Adding a connection to a poll group implicitly removes it from + /// any other poll group it is in. + /// + /// You can pass k_HSteamNetPollGroup_Invalid to remove a connection from its current + /// poll group without adding it to a new poll group. + /// + /// If there are received messages currently pending on the connection, an attempt + /// is made to add them to the queue of messages for the poll group in approximately + /// the order that would have applied if the connection was already part of the poll + /// group at the time that the messages were received. + /// + /// Returns false if the connection handle is invalid, or if the poll group handle + /// is invalid (and not k_HSteamNetPollGroup_Invalid). + bool SetConnectionPollGroup( HSteamNetConnection hConn, HSteamNetPollGroup hPollGroup ); + + /// Same as ReceiveMessagesOnConnection, but will return the next messages available + /// on any connection in the poll group. Examine SteamNetworkingMessage_t::m_conn + /// to know which connection. (SteamNetworkingMessage_t::m_nConnUserData might also + /// be useful.) + /// + /// Delivery order of messages among different connections will usually match the + /// order that the last packet was received which completed the message. But this + /// is not a strong guarantee, especially for packets received right as a connection + /// is being assigned to poll group. + /// + /// Delivery order of messages on the same connection is well defined and the + /// same guarantees are present as mentioned in ReceiveMessagesOnConnection. + /// (But the messages are not grouped by connection, so they will not necessarily + /// appear consecutively in the list; they may be interleaved with messages for + /// other connections.) + int ReceiveMessagesOnPollGroup( HSteamNetPollGroup hPollGroup, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ); + + + //#ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE + + // + // Clients connecting to dedicated servers hosted in a data center, + // using central-authority-granted tickets. + // + + /// Called when we receive a ticket from our central matchmaking system. Puts the + /// ticket into a persistent cache, and optionally returns the parsed ticket. + /// + /// See stamdatagram_ticketgen.h for more details. + bool ReceivedRelayAuthTicket( const void *pvTicket, int cbTicket, SteamDatagramRelayAuthTicket *pOutParsedTicket ); + + + /// Search cache for a ticket to talk to the server on the specified virtual port. + /// If found, returns the number of second until the ticket expires, and optionally + /// the complete cracked ticket. Returns 0 if we don't have a ticket. + /// + /// Typically this is useful just to confirm that you have a ticket, before you + /// call ConnectToHostedDedicatedServer to connect to the server. + int FindRelayAuthTicketForServer( CSteamID steamID, int nVirtualPort, SteamDatagramRelayAuthTicket *pOutParsedTicket ); + + /// Search cache for a ticket to talk to the server on the specified virtual port. + /// If found, returns the number of seconds until the ticket expires, and optionally + /// the complete cracked ticket. Returns 0 if we don't have a ticket. + /// + /// Typically this is useful just to confirm that you have a ticket, before you + /// call ConnectToHostedDedicatedServer to connect to the server. + int FindRelayAuthTicketForServer( const SteamNetworkingIdentity *identityGameServer, int nVirtualPort, SteamDatagramRelayAuthTicket *pOutParsedTicket ); + + int FindRelayAuthTicketForServer( const SteamNetworkingIdentity &identityGameServer, int nVirtualPort, SteamDatagramRelayAuthTicket *pOutParsedTicket ); + + /// Client call to connect to a server hosted in a Valve data center, on the specified virtual + /// port. You must have placed a ticket for this server into the cache, or else this connect attempt will fail! + /// + /// You may wonder why tickets are stored in a cache, instead of simply being passed as an argument + /// here. The reason is to make reconnection to a gameserver robust, even if the client computer loses + /// connection to Steam or the central backend, or the app is restarted or crashes, etc. + /// + /// If you use this, you probably want to call ISteamNetworkingUtils::InitializeRelayNetworkAccess() + /// when your app initializes + HSteamNetConnection ConnectToHostedDedicatedServer( const SteamNetworkingIdentity &identityTarget, int nVirtualPort ); + + HSteamNetConnection ConnectToHostedDedicatedServer( const SteamNetworkingIdentity *identityTarget, int nVirtualPort ); + + /// Client call to connect to a server hosted in a Valve data center, on the specified virtual + /// port. You should have received a ticket for this server, or else this connect call will fail! + /// + /// You may wonder why tickets are stored in a cache, instead of simply being passed as an argument + /// here. The reason is to make reconnection to a gameserver robust, even if the client computer loses + /// connection to Steam or the central backend, or the app is restarted or crashes, etc. + HSteamNetConnection ConnectToHostedDedicatedServer( CSteamID steamIDTarget, int nVirtualPort ); + + HSteamNetConnection ConnectToHostedDedicatedServer( const SteamNetworkingIdentity &identityTarget, int nVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ); + + // + // Servers hosted in Valve data centers + // + + /// Returns the value of the SDR_LISTEN_PORT environment variable. + uint16 GetHostedDedicatedServerPort(); + + + /// If you are running in a production data center, this will return the data + /// center code. Returns 0 otherwise. + SteamNetworkingPOPID GetHostedDedicatedServerPOPID(); + + + /// Return info about the hosted server. You will need to send this information to your + /// backend, and put it in tickets, so that the relays will know how to forward traffic from + /// clients to your server. See SteamDatagramRelayAuthTicket for more info. + /// + /// NOTE ABOUT DEVELOPMENT ENVIRONMENTS: + /// In production in our data centers, these parameters are configured via environment variables. + /// In development, the only one you need to set is SDR_LISTEN_PORT, which is the local port you + /// want to listen on. Furthermore, if you are running your server behind a corporate firewall, + /// you probably will not be able to put the routing information returned by this function into + /// tickets. Instead, it should be a public internet address that the relays can use to send + /// data to your server. So you might just end up hardcoding a public address and setup port + /// forwarding on your corporate firewall. In that case, the port you put into the ticket + /// needs to be the public-facing port opened on your firewall, if it is different from the + /// actual server port. + /// + /// This function will fail if SteamDatagramServer_Init has not been called. + /// + /// Returns false if the SDR_LISTEN_PORT environment variable is not set. + bool GetHostedDedicatedServerAddress001( SteamDatagramHostedAddress *pRouting ); + + /// Return info about the hosted server. This contains the PoPID of the server, + /// and opaque routing information that can be used by the relays to send traffic + /// to your server. + /// + /// You will need to send this information to your backend, and put it in tickets, + /// so that the relays will know how to forward traffic from + /// clients to your server. See SteamDatagramRelayAuthTicket for more info. + /// + /// Also, note that the routing information is contained in SteamDatagramGameCoordinatorServerLogin, + /// so if possible, it's preferred to use GetGameCoordinatorServerLogin to send this info + /// to your game coordinator service, and also login securely at the same time. + /// + /// On a successful exit, k_EResultOK is returned + /// + /// Unsuccessful exit: + /// - Something other than k_EResultOK is returned. + /// - k_EResultInvalidState: We are not configured to listen for SDR (SDR_LISTEN_SOCKET + /// is not set.) + /// - k_EResultPending: we do not (yet) have the authentication information needed. + /// (See GetAuthenticationStatus.) If you use environment variables to pre-fetch + /// the network config, this data should always be available immediately. + /// - A non-localized diagnostic debug message will be placed in m_data that describes + /// the cause of the failure. + /// + /// NOTE: The returned blob is not encrypted. Send it to your backend, but don't + /// directly share it with clients. + virtual EResult GetHostedDedicatedServerAddress( SteamDatagramHostedAddress *pRouting ); + + /// Create a listen socket on the specified virtual port. The physical UDP port to use + /// will be determined by the SDR_LISTEN_PORT environment variable. If a UDP port is not + /// configured, this call will fail. + /// + /// Note that this call MUST be made through the SteamNetworkingSocketsGameServer() interface + HSteamListenSocket CreateHostedDedicatedServerListenSocket( int nVirtualPort ); + + /// Create a listen socket on the specified virtual port. The physical UDP port to use + /// will be determined by the SDR_LISTEN_PORT environment variable. If a UDP port is not + /// configured, this call will fail. + /// + /// Note that this call MUST be made through the SteamGameServerNetworkingSockets() interface + /// + /// If you need to set any initial config options, pass them here. See + /// SteamNetworkingConfigValue_t for more about why this is preferable to + /// setting the options "immediately" after creation. + HSteamListenSocket CreateHostedDedicatedServerListenSocket( int nVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ); + + + //#endif // #ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE + + // + // Gets some debug text from the connection + // + bool GetConnectionDebugText( HSteamNetConnection hConn, char *pOut, int nOutCCH ); + + + // + // Set and get configuration values, see ESteamNetworkingConfigurationValue for individual descriptions. + // + // Returns the value or -1 is eConfigValue is invalid + int32 GetConfigurationValue( ESteamNetworkingConfigurationValue eConfigValue ); + + // Returns true if successfully set + bool SetConfigurationValue( ESteamNetworkingConfigurationValue eConfigValue, int32 nValue ); + + + // Return the name of an int configuration value, or NULL if config value isn't known + const char *GetConfigurationValueName( ESteamNetworkingConfigurationValue eConfigValue ); + + + // + // Set and get configuration strings, see ESteamNetworkingConfigurationString for individual descriptions. + // + // Get the configuration string, returns length of string needed if pDest is nullpr or destSize is 0 + // returns -1 if the eConfigValue is invalid + int32 GetConfigurationString( ESteamNetworkingConfigurationString eConfigString, char *pDest, int32 destSize ); + + bool SetConfigurationString( ESteamNetworkingConfigurationString eConfigString, const char *pString ); + + + // Return the name of a string configuration value, or NULL if config value isn't known + const char *GetConfigurationStringName( ESteamNetworkingConfigurationString eConfigString ); + + + // + // Set and get configuration values, see ESteamNetworkingConnectionConfigurationValue for individual descriptions. + // + // Returns the value or -1 is eConfigValue is invalid + int32 GetConnectionConfigurationValue( HSteamNetConnection hConn, ESteamNetworkingConnectionConfigurationValue eConfigValue ); + + // Returns true if successfully set + bool SetConnectionConfigurationValue( HSteamNetConnection hConn, ESteamNetworkingConnectionConfigurationValue eConfigValue, int32 nValue ); + + /// Generate an authentication blob that can be used to securely login with + /// your backend, using SteamDatagram_ParseHostedServerLogin. (See + /// steamdatagram_gamecoordinator.h) + /// + /// Before calling the function: + /// - Populate the app data in pLoginInfo (m_cbAppData and m_appData). You can leave + /// all other fields uninitialized. + /// - *pcbSignedBlob contains the size of the buffer at pBlob. (It should be + /// at least k_cbMaxSteamDatagramGameCoordinatorServerLoginSerialized.) + /// + /// On a successful exit: + /// - k_EResultOK is returned + /// - All of the remaining fields of pLoginInfo will be filled out. + /// - *pcbSignedBlob contains the size of the serialized blob that has been + /// placed into pBlob. + /// + /// Unsuccessful exit: + /// - Something other than k_EResultOK is returned. + /// - k_EResultNotLoggedOn: you are not logged in (yet) + /// - See GetHostedDedicatedServerAddress for more potential failure return values. + /// - A non-localized diagnostic debug message will be placed in pBlob that describes + /// the cause of the failure. + /// + /// This works by signing the contents of the SteamDatagramGameCoordinatorServerLogin + /// with the cert that is issued to this server. In dev environments, it's OK if you do + /// not have a cert. (You will need to enable insecure dev login in SteamDatagram_ParseHostedServerLogin.) + /// Otherwise, you will need a signed cert. + /// + /// NOTE: The routing blob returned here is not encrypted. Send it to your backend + /// and don't share it directly with clients. + EResult GetGameCoordinatorServerLogin( SteamDatagramGameCoordinatorServerLogin *pLoginInfo, int *pcbSignedBlob, void *pBlob ); + + // + // Relayed connections using custom signaling protocol + // + // This is used if you have your own method of sending out-of-band + // signaling / rendezvous messages through a mutually trusted channel. + // + + /// Create a P2P "client" connection that does signaling over a custom + /// rendezvous/signaling channel. + /// + /// pSignaling points to a new object that you create just for this connection. + /// It must stay valid until Release() is called. Once you pass the + /// object to this function, it assumes ownership. Release() will be called + /// from within the function call if the call fails. Furthermore, until Release() + /// is called, you should be prepared for methods to be invoked on your + /// object from any thread! You need to make sure your object is threadsafe! + /// Furthermore, you should make sure that dispatching the methods is done + /// as quickly as possible. + /// + /// This function will immediately construct a connection in the "connecting" + /// state. Soon after (perhaps before this function returns, perhaps in another thread), + /// the connection will begin sending signaling messages by calling + /// ISteamNetworkingConnectionCustomSignaling::SendSignal. + /// + /// When the remote peer accepts the connection (See + /// ISteamNetworkingCustomSignalingRecvContext::OnConnectRequest), + /// it will begin sending signaling messages. When these messages are received, + /// you can pass them to the connection using ReceivedP2PCustomSignal. + /// + /// If you know the identity of the peer that you expect to be on the other end, + /// you can pass their identity to improve debug output or just detect bugs. + /// If you don't know their identity yet, you can pass NULL, and their + /// identity will be established in the connection handshake. + /// + /// If you use this, you probably want to call ISteamNetworkingUtils::InitRelayNetworkAccess() + /// when your app initializes + /// + /// If you need to set any initial config options, pass them here. See + /// SteamNetworkingConfigValue_t for more about why this is preferable to + /// setting the options "immediately" after creation. + HSteamNetConnection ConnectP2PCustomSignaling( ISteamNetworkingConnectionCustomSignaling *pSignaling, const SteamNetworkingIdentity *pPeerIdentity, int nOptions, const SteamNetworkingConfigValue_t *pOptions ); + + //HSteamNetConnection ConnectP2PCustomSignaling( ISteamNetworkingConnectionCustomSignaling *pSignaling, const SteamNetworkingIdentity *pPeerIdentity, int nRemoteVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) + HSteamNetConnection ConnectP2PCustomSignaling( ISteamNetworkingConnectionSignaling *pSignaling, const SteamNetworkingIdentity *pPeerIdentity, int nRemoteVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ); + + /// Called when custom signaling has received a message. When your + /// signaling channel receives a message, it should save off whatever + /// routing information was in the envelope into the context object, + /// and then pass the payload to this function. + /// + /// A few different things can happen next, depending on the message: + /// + /// - If the signal is associated with existing connection, it is dealt + /// with immediately. If any replies need to be sent, they will be + /// dispatched using the ISteamNetworkingConnectionCustomSignaling + /// associated with the connection. + /// - If the message represents a connection request (and the request + /// is not redundant for an existing connection), a new connection + /// will be created, and ReceivedConnectRequest will be called on your + /// context object to determine how to proceed. + /// - Otherwise, the message is for a connection that does not + /// exist (anymore). In this case, we *may* call SendRejectionReply + /// on your context object. + /// + /// In any case, we will not save off pContext or access it after this + /// function returns. + /// + /// Returns true if the message was parsed and dispatched without anything + /// unusual or suspicious happening. Returns false if there was some problem + /// with the message that prevented ordinary handling. (Debug output will + /// usually have more information.) + /// + /// If you expect to be using relayed connections, then you probably want + /// to call ISteamNetworkingUtils::InitRelayNetworkAccess() when your app initializes + bool ReceivedP2PCustomSignal( const void *pMsg, int cbMsg, ISteamNetworkingCustomSignalingRecvContext *pContext ); + + bool ReceivedP2PCustomSignal( const void *pMsg, int cbMsg, ISteamNetworkingSignalingRecvContext *pContext ); + + // + // Certificate provision by the application. On Steam, we normally handle all this automatically + // and you will not need to use these advanced functions. + // + + /// Get blob that describes a certificate request. You can send this to your game coordinator. + /// Upon entry, *pcbBlob should contain the size of the buffer. On successful exit, it will + /// return the number of bytes that were populated. You can pass pBlob=NULL to query for the required + /// size. (256 bytes is a very conservative estimate.) + /// + /// Pass this blob to your game coordinator and call SteamDatagram_CreateCert. + bool GetCertificateRequest( int *pcbBlob, void *pBlob, SteamNetworkingErrMsg &errMsg ); + + /// Set the certificate. The certificate blob should be the output of + /// SteamDatagram_CreateCert. + bool SetCertificate( const void *pCertificate, int cbCertificate, SteamNetworkingErrMsg &errMsg ); + + /// Reset the identity associated with this instance. + /// Any open connections are closed. Any previous certificates, etc are discarded. + /// You can pass a specific identity that you want to use, or you can pass NULL, + /// in which case the identity will be invalid until you set it using SetCertificate + /// + /// NOTE: This function is not actually supported on Steam! It is included + /// for use on other platforms where the active user can sign out and + /// a new user can sign in. + void ResetIdentity( const SteamNetworkingIdentity *pIdentity ); + + // + // "FakeIP" system. + // + // A FakeIP is essentially a temporary, arbitrary identifier that + // happens to be a valid IPv4 address. The purpose of this system is to make it + // easy to integrate with existing code that identifies hosts using IPv4 addresses. + // The FakeIP address will never actually be used to send or receive any packets + // on the Internet, it is strictly an identifier. + // + // FakeIP addresses are designed to (hopefully) pass through existing code as + // transparently as possible, while conflicting with "real" addresses that might + // be in use on networks (both the Internet and LANs) in the same code as little + // as possible. At the time this comment is being written, they come from the + // 169.254.0.0/16 range, and the port number will always be >1024. HOWEVER, + // this is subject to change! Do not make assumptions about these addresses, + // or your code might break in the future. In particular, you should use + // functions such as ISteamNetworkingUtils::IsFakeIP to determine if an IP + // address is a "fake" one used by this system. + // + + /// Begin asynchronous process of allocating a fake IPv4 address that other + /// peers can use to contact us via P2P. IP addresses returned by this + /// function are globally unique for a given appid. + /// + /// nNumPorts is the numbers of ports you wish to reserve. This is useful + /// for the same reason that listening on multiple UDP ports is useful for + /// different types of traffic. Because these allocations come from a global + /// namespace, there is a relatively strict limit on the maximum number of + /// ports you may request. (At the time of this writing, the limit is 4.) + /// The Port assignments are *not* guaranteed to have any particular order + /// or relationship! Do *not* assume they are contiguous, even though that + /// may often occur in practice. + /// + /// Returns false if a request was already in progress, true if a new request + /// was started. A SteamNetworkingFakeIPResult_t will be posted when the request + /// completes. + /// + /// For gameservers, you *must* call this after initializing the SDK but before + /// beginning login. Steam needs to know in advance that FakeIP will be used. + /// Everywhere your public IP would normally appear (such as the server browser) will be + /// replaced by the FakeIP, and the fake port at index 0. The request is actually queued + /// until the logon completes, so you must not wait until the allocation completes + /// before logging in. Except for trivial failures that can be detected locally + /// (e.g. invalid parameter), a SteamNetworkingFakeIPResult_t callback (whether success or + /// failure) will not be posted until after we have logged in. Furthermore, it is assumed + /// that FakeIP allocation is essential for your application to function, and so failure + /// will not be reported until *several* retries have been attempted. This process may + /// last several minutes. It is *highly* recommended to treat failure as fatal. + /// + /// To communicate using a connection-oriented (TCP-style) API: + /// - Server creates a listen socket using CreateListenSocketP2PFakeIP + /// - Client connects using ConnectByIPAddress, passing in the FakeIP address. + /// - The connection will behave mostly like a P2P connection. The identities + /// that appear in SteamNetConnectionInfo_t will be the FakeIP identity until + /// we know the real identity. Then it will be the real identity. If the + /// SteamNetConnectionInfo_t::m_addrRemote is valid, it will be a real IPv4 + /// address of a NAT-punched connection. Otherwise, it will not be valid. + /// + /// To communicate using an ad-hoc sendto/recv from (UDP-style) API, + /// use CreateFakeUDPPort. + bool BeginAsyncRequestFakeIP( int nNumPorts ); + + /// Return info about the FakeIP and port(s) that we have been assigned, + /// if any. idxFirstPort is currently reserved and must be zero. + /// Make sure and check SteamNetworkingFakeIPResult_t::m_eResult + void GetFakeIP( int idxFirstPort, SteamNetworkingFakeIPResult_t *pInfo ); + + /// Create a listen socket that will listen for P2P connections sent + /// to our FakeIP. A peer can initiate connections to this listen + /// socket by calling ConnectByIPAddress. + /// + /// idxFakePort refers to the *index* of the fake port requested, + /// not the actual port number. For example, pass 0 to refer to the + /// first port in the reservation. You must call this only after calling + /// BeginAsyncRequestFakeIP. However, you do not need to wait for the + /// request to complete before creating the listen socket. + HSteamListenSocket CreateListenSocketP2PFakeIP( int idxFakePort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ); + + /// If the connection was initiated using the "FakeIP" system, then we + /// we can get an IP address for the remote host. If the remote host had + /// a global FakeIP at the time the connection was established, this + /// function will return that global IP. Otherwise, a FakeIP that is + /// unique locally will be allocated from the local FakeIP address space, + /// and that will be returned. + /// + /// The allocation of local FakeIPs attempts to assign addresses in + /// a consistent manner. If multiple connections are made to the + /// same remote host, they *probably* will return the same FakeIP. + /// However, since the namespace is limited, this cannot be guaranteed. + /// + /// On failure, returns: + /// - k_EResultInvalidParam: invalid connection handle + /// - k_EResultIPNotFound: This connection wasn't made using FakeIP system + EResult GetRemoteFakeIPForConnection( HSteamNetConnection hConn, SteamNetworkingIPAddr *pOutAddr ); + + /// Get an interface that can be used like a UDP port to send/receive + /// datagrams to a FakeIP address. This is intended to make it easy + /// to port existing UDP-based code to take advantage of SDR. + /// + /// idxFakeServerPort refers to the *index* of the port allocated using + /// BeginAsyncRequestFakeIP and is used to create "server" ports. You may + /// call this before the allocation has completed. However, any attempts + /// to send packets will fail until the allocation has succeeded. When + /// the peer receives packets sent from this interface, the from address + /// of the packet will be the globally-unique FakeIP. If you call this + /// function multiple times and pass the same (nonnegative) fake port index, + /// the same object will be returned, and this object is not reference counted. + /// + /// To create a "client" port (e.g. the equivalent of an ephemeral UDP port) + /// pass -1. In this case, a distinct object will be returned for each call. + /// When the peer receives packets sent from this interface, the peer will + /// assign a FakeIP from its own locally-controlled namespace. + ISteamNetworkingFakeUDPPort *CreateFakeUDPPort( int idxFakeServerPort ); + + // TEMP KLUDGE Call to invoke all queued callbacks. + // Eventually this function will go away, and callwacks will be ordinary Steamworks callbacks. + // You should call this at the same time you call SteamAPI_RunCallbacks and SteamGameServer_RunCallbacks + // to minimize potential changes in timing when that change happens. + void RunCallbacks( ISteamNetworkingSocketsCallbacks *pCallbacks ); + + + void RunCallbacks(); }; + +#endif // __INCLUDED_STEAM_NETWORKING_SOCKETS_H__ diff --git a/dll/dll/steam_networking_socketsserialized.h b/dll/dll/steam_networking_socketsserialized.h index 7a8837c1..f3f1d21f 100644 --- a/dll/dll/steam_networking_socketsserialized.h +++ b/dll/dll/steam_networking_socketsserialized.h @@ -15,6 +15,9 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_NETWORKING_SOCKETSERIALIZED_H__ +#define __INCLUDED_STEAM_NETWORKING_SOCKETSERIALIZED_H__ + #include "base.h" class Steam_Networking_Sockets_Serialized : @@ -23,145 +26,47 @@ public ISteamNetworkingSocketsSerialized003, public ISteamNetworkingSocketsSerialized004, public ISteamNetworkingSocketsSerialized005 { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; + + static void steam_callback(void *object, Common_Message *msg); + static void steam_run_every_runcb(void *object); public: -static void steam_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); + Steam_Networking_Sockets_Serialized(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_Networking_Sockets_Serialized(); - Steam_Networking_Sockets_Serialized *steam_networkingsockets = (Steam_Networking_Sockets_Serialized *)object; - steam_networkingsockets->Callback(msg); -} + void SendP2PRendezvous( CSteamID steamIDRemote, uint32 unConnectionIDSrc, const void *pMsgRendezvous, uint32 cbRendezvous ); -static void steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); + void SendP2PConnectionFailure( CSteamID steamIDRemote, uint32 unConnectionIDDest, uint32 nReason, const char *pszReason ); - Steam_Networking_Sockets_Serialized *steam_networkingsockets = (Steam_Networking_Sockets_Serialized *)object; - steam_networkingsockets->RunCallbacks(); -} + SteamAPICall_t GetCertAsync(); -Steam_Networking_Sockets_Serialized(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->run_every_runcb = run_every_runcb; - this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Sockets_Serialized::steam_callback, this); - this->run_every_runcb->add(&Steam_Networking_Sockets_Serialized::steam_run_every_runcb, this); + int GetNetworkConfigJSON( void *buf, uint32 cbBuf, const char *pszLauncherPartner ); - this->callback_results = callback_results; - this->callbacks = callbacks; -} + int GetNetworkConfigJSON( void *buf, uint32 cbBuf ); -~Steam_Networking_Sockets_Serialized() -{ - this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Sockets_Serialized::steam_callback, this); - this->run_every_runcb->remove(&Steam_Networking_Sockets_Serialized::steam_run_every_runcb, this); -} + void CacheRelayTicket( const void *pTicket, uint32 cbTicket ); -void SendP2PRendezvous( CSteamID steamIDRemote, uint32 unConnectionIDSrc, const void *pMsgRendezvous, uint32 cbRendezvous ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + uint32 GetCachedRelayTicketCount(); -void SendP2PConnectionFailure( CSteamID steamIDRemote, uint32 unConnectionIDDest, uint32 nReason, const char *pszReason ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + int GetCachedRelayTicket( uint32 idxTicket, void *buf, uint32 cbBuf ); -SteamAPICall_t GetCertAsync() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - struct SteamNetworkingSocketsCert_t data = {}; - data.m_eResult = k_EResultOK; + void PostConnectionStateMsg( const void *pMsg, uint32 cbMsg ); - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} + bool GetSTUNServer(int dont_know, char *buf, unsigned int len); -int GetNetworkConfigJSON( void *buf, uint32 cbBuf, const char *pszLauncherPartner ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} + bool BAllowDirectConnectToPeer(SteamNetworkingIdentity const &identity); -int GetNetworkConfigJSON( void *buf, uint32 cbBuf ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return GetNetworkConfigJSON(buf, cbBuf, ""); -} + int BeginAsyncRequestFakeIP(int a); -void CacheRelayTicket( const void *pTicket, uint32 cbTicket ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + void RunCallbacks(); -uint32 GetCachedRelayTicketCount() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -int GetCachedRelayTicket( uint32 idxTicket, void *buf, uint32 cbBuf ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -void PostConnectionStateMsg( const void *pMsg, uint32 cbMsg ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - -bool GetSTUNServer(int dont_know, char *buf, unsigned int len) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -bool BAllowDirectConnectToPeer(SteamNetworkingIdentity const &identity) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return true; -} - -int BeginAsyncRequestFakeIP(int a) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return true; -} - -void RunCallbacks() -{ -} - -void Callback(Common_Message *msg) -{ - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - - } - - if (msg->low_level().type() == Low_Level::DISCONNECT) { - - } - } -} + void Callback(Common_Message *msg); }; + +#endif // __INCLUDED_STEAM_NETWORKING_SOCKETSERIALIZED_H__ diff --git a/dll/dll/steam_networking_utils.h b/dll/dll/steam_networking_utils.h index 1de676f1..ca4f0e35 100644 --- a/dll/dll/steam_networking_utils.h +++ b/dll/dll/steam_networking_utils.h @@ -15,6 +15,9 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_NETWORKING_UTILS_H__ +#define __INCLUDED_STEAM_NETWORKING_UTILS_H__ + #include "base.h" class Steam_Networking_Utils : @@ -23,728 +26,246 @@ public ISteamNetworkingUtils002, public ISteamNetworkingUtils003, public ISteamNetworkingUtils { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; + std::chrono::time_point initialized_time = std::chrono::steady_clock::now(); - FSteamNetworkingSocketsDebugOutput debug_function; + FSteamNetworkingSocketsDebugOutput debug_function{}; bool relay_initialized = false; bool init_relay = false; + static void free_steam_message_data(SteamNetworkingMessage_t *pMsg); + static void delete_steam_message(SteamNetworkingMessage_t *pMsg); + + static void steam_callback(void *object, Common_Message *msg); + static void steam_run_every_runcb(void *object); + public: -static void steam_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Networking_Utils *steam_networkingutils = (Steam_Networking_Utils *)object; - steam_networkingutils->Callback(msg); -} - -static void steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Networking_Utils *steam_networkingutils = (Steam_Networking_Utils *)object; - steam_networkingutils->RunCallbacks(); -} - -Steam_Networking_Utils(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->run_every_runcb = run_every_runcb; - //this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Utils::steam_callback, this); - this->run_every_runcb->add(&Steam_Networking_Utils::steam_run_every_runcb, this); - - this->callback_results = callback_results; - this->callbacks = callbacks; -} - -~Steam_Networking_Utils() -{ - //this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Utils::steam_callback, this); - this->run_every_runcb->remove(&Steam_Networking_Utils::steam_run_every_runcb, this); -} - -static void free_steam_message_data(SteamNetworkingMessage_t *pMsg) -{ - free(pMsg->m_pData); - pMsg->m_pData = NULL; -} - -static void delete_steam_message(SteamNetworkingMessage_t *pMsg) -{ - if (pMsg->m_pfnFreeData) pMsg->m_pfnFreeData(pMsg); - delete pMsg; -} - -/// Allocate and initialize a message object. Usually the reason -/// you call this is to pass it to ISteamNetworkingSockets::SendMessages. -/// The returned object will have all of the relevant fields cleared to zero. -/// -/// Optionally you can also request that this system allocate space to -/// hold the payload itself. If cbAllocateBuffer is nonzero, the system -/// will allocate memory to hold a payload of at least cbAllocateBuffer bytes. -/// m_pData will point to the allocated buffer, m_cbSize will be set to the -/// size, and m_pfnFreeData will be set to the proper function to free up -/// the buffer. -/// -/// If cbAllocateBuffer=0, then no buffer is allocated. m_pData will be NULL, -/// m_cbSize will be zero, and m_pfnFreeData will be NULL. You will need to -/// set each of these. -/// -/// You can use SteamNetworkingMessage_t::Release to free up the message -/// bookkeeping object and any associated buffer. See -/// ISteamNetworkingSockets::SendMessages for details on reference -/// counting and ownership. -SteamNetworkingMessage_t *AllocateMessage( int cbAllocateBuffer ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - SteamNetworkingMessage_t *pMsg = new SteamNetworkingMessage_t(); - pMsg->m_pfnFreeData = &free_steam_message_data; - pMsg->m_pfnRelease = &delete_steam_message; - if (cbAllocateBuffer < 0) - cbAllocateBuffer = 0; - - pMsg->m_pData = nullptr; - if (cbAllocateBuffer) - pMsg->m_pData = malloc(cbAllocateBuffer); - - pMsg->m_cbSize = cbAllocateBuffer; - return pMsg; -} - -bool InitializeRelayAccess() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - init_relay = true; - return relay_initialized; -} - -SteamRelayNetworkStatus_t get_network_status() -{ - SteamRelayNetworkStatus_t data = {}; - data.m_eAvail = k_ESteamNetworkingAvailability_Current; - data.m_bPingMeasurementInProgress = 0; - data.m_eAvailAnyRelay = k_ESteamNetworkingAvailability_Current; - data.m_eAvailNetworkConfig = k_ESteamNetworkingAvailability_Current; - strcpy(data.m_debugMsg, "OK"); - return data; -} - -/// Fetch current status of the relay network. -/// -/// SteamRelayNetworkStatus_t is also a callback. It will be triggered on -/// both the user and gameserver interfaces any time the status changes, or -/// ping measurement starts or stops. -/// -/// SteamRelayNetworkStatus_t::m_eAvail is returned. If you want -/// more details, you can pass a non-NULL value. -ESteamNetworkingAvailability GetRelayNetworkStatus( SteamRelayNetworkStatus_t *pDetails ) -{ - PRINT_DEBUG("TODO %p", pDetails); - std::lock_guard lock(global_mutex); - - //TODO: check if this is how real steam returns it - SteamRelayNetworkStatus_t data = {}; - if (relay_initialized) { - data = get_network_status(); - } - - if (pDetails) { - *pDetails = data; - } - - return k_ESteamNetworkingAvailability_Current; -} - -float GetLocalPingLocation( SteamNetworkPingLocation_t &result ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (relay_initialized) { - result.m_data[2] = 123; - result.m_data[8] = 67; - return 2.0; - } - - return -1; -} - -int EstimatePingTimeBetweenTwoLocations( const SteamNetworkPingLocation_t &location1, const SteamNetworkPingLocation_t &location2 ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - //return k_nSteamNetworkingPing_Unknown; - return 2; -} - - -int EstimatePingTimeFromLocalHost( const SteamNetworkPingLocation_t &remoteLocation ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 2; -} - - -void ConvertPingLocationToString( const SteamNetworkPingLocation_t &location, char *pszBuf, int cchBufSize ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - strncpy(pszBuf, "fra=10+2", cchBufSize); -} - - -bool ParsePingLocationString( const char *pszString, SteamNetworkPingLocation_t &result ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return true; -} - - -bool CheckPingDataUpToDate( float flMaxAgeSeconds ) -{ - PRINT_DEBUG("TODO %f", flMaxAgeSeconds); - init_relay = true; - return relay_initialized; -} - - -bool IsPingMeasurementInProgress() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - - -int GetPingToDataCenter( SteamNetworkingPOPID popID, SteamNetworkingPOPID *pViaRelayPoP ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - - -int GetDirectPingToPOP( SteamNetworkingPOPID popID ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - - -int GetPOPCount() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - - -int GetPOPList( SteamNetworkingPOPID *list, int nListSz ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - - -// -// Misc -// - -/// Fetch current timestamp. This timer has the following properties: -/// -/// - Monotonicity is guaranteed. -/// - The initial value will be at least 24*3600*30*1e6, i.e. about -/// 30 days worth of microseconds. In this way, the timestamp value of -/// 0 will always be at least "30 days ago". Also, negative numbers -/// will never be returned. -/// - Wraparound / overflow is not a practical concern. -/// -/// If you are running under the debugger and stop the process, the clock -/// might not advance the full wall clock time that has elapsed between -/// calls. If the process is not blocked from normal operation, the -/// timestamp values will track wall clock time, even if you don't call -/// the function frequently. -/// -/// The value is only meaningful for this run of the process. Don't compare -/// it to values obtained on another computer, or other runs of the same process. -SteamNetworkingMicroseconds GetLocalTimestamp() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - return std::chrono::duration_cast(std::chrono::steady_clock::now() - initialized_time).count() + (SteamNetworkingMicroseconds)24*3600*30*1e6; -} - - -/// Set a function to receive network-related information that is useful for debugging. -/// This can be very useful during development, but it can also be useful for troubleshooting -/// problems with tech savvy end users. If you have a console or other log that customers -/// can examine, these log messages can often be helpful to troubleshoot network issues. -/// (Especially any warning/error messages.) -/// -/// The detail level indicates what message to invoke your callback on. Lower numeric -/// value means more important, and the value you pass is the lowest priority (highest -/// numeric value) you wish to receive callbacks for. -/// -/// Except when debugging, you should only use k_ESteamNetworkingSocketsDebugOutputType_Msg -/// or k_ESteamNetworkingSocketsDebugOutputType_Warning. For best performance, do NOT -/// request a high detail level and then filter out messages in your callback. Instead, -/// call function function to adjust the desired level of detail. -/// -/// IMPORTANT: This may be called from a service thread, while we own a mutex, etc. -/// Your output function must be threadsafe and fast! Do not make any other -/// Steamworks calls from within the handler. -void SetDebugOutputFunction( ESteamNetworkingSocketsDebugOutputType eDetailLevel, FSteamNetworkingSocketsDebugOutput pfnFunc ) -{ - PRINT_DEBUG("%i", eDetailLevel); - std::lock_guard lock(global_mutex); - if (eDetailLevel != k_ESteamNetworkingSocketsDebugOutputType_None) { - debug_function = pfnFunc; - } -} - -// -// Fake IP -// -// Useful for interfacing with code that assumes peers are identified using an IPv4 address -// - -/// Return true if an IPv4 address is one that might be used as a "fake" one. -/// This function is fast; it just does some logical tests on the IP and does -/// not need to do any lookup operations. -// inline bool IsFakeIPv4( uint32 nIPv4 ) { return GetIPv4FakeIPType( nIPv4 ) > k_ESteamNetworkingFakeIPType_NotFake; } -ESteamNetworkingFakeIPType GetIPv4FakeIPType( uint32 nIPv4 ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_ESteamNetworkingFakeIPType_NotFake; -} - -/// Get the real identity associated with a given FakeIP. -/// -/// On failure, returns: -/// - k_EResultInvalidParam: the IP is not a FakeIP. -/// - k_EResultNoMatch: we don't recognize that FakeIP and don't know the corresponding identity. -/// -/// FakeIP's used by active connections, or the FakeIPs assigned to local identities, -/// will always work. FakeIPs for recently destroyed connections will continue to -/// return results for a little while, but not forever. At some point, we will forget -/// FakeIPs to save space. It's reasonably safe to assume that you can read back the -/// real identity of a connection very soon after it is destroyed. But do not wait -/// indefinitely. -EResult GetRealIdentityForFakeIP( const SteamNetworkingIPAddr &fakeIP, SteamNetworkingIdentity *pOutRealIdentity ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_EResultNoMatch; -} - - -// -// Set and get configuration values, see ESteamNetworkingConfigValue for individual descriptions. -// - -// Shortcuts for common cases. (Implemented as inline functions below) -/* -bool SetGlobalConfigValueInt32( ESteamNetworkingConfigValue eValue, int32 val ); -bool SetGlobalConfigValueFloat( ESteamNetworkingConfigValue eValue, float val ); -bool SetGlobalConfigValueString( ESteamNetworkingConfigValue eValue, const char *val ); -bool SetConnectionConfigValueInt32( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, int32 val ); -bool SetConnectionConfigValueFloat( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, float val ); -bool SetConnectionConfigValueString( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, const char *val ); -*/ -/// Set a configuration value. -/// - eValue: which value is being set -/// - eScope: Onto what type of object are you applying the setting? -/// - scopeArg: Which object you want to change? (Ignored for global scope). E.g. connection handle, listen socket handle, interface pointer, etc. -/// - eDataType: What type of data is in the buffer at pValue? This must match the type of the variable exactly! -/// - pArg: Value to set it to. You can pass NULL to remove a non-global sett at this scope, -/// causing the value for that object to use global defaults. Or at global scope, passing NULL -/// will reset any custom value and restore it to the system default. -/// NOTE: When setting callback functions, do not pass the function pointer directly. -/// Your argument should be a pointer to a function pointer. -bool SetConfigValue( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigScope eScopeType, intptr_t scopeObj, - ESteamNetworkingConfigDataType eDataType, const void *pArg ) -{ - PRINT_DEBUG("TODO %i %i " "%" PRIdPTR " %i %p", eValue, eScopeType, scopeObj, eDataType, pArg); - std::lock_guard lock(global_mutex); - return true; -} - - -/// Get a configuration value. -/// - eValue: which value to fetch -/// - eScopeType: query setting on what type of object -/// - eScopeArg: the object to query the setting for -/// - pOutDataType: If non-NULL, the data type of the value is returned. -/// - pResult: Where to put the result. Pass NULL to query the required buffer size. (k_ESteamNetworkingGetConfigValue_BufferTooSmall will be returned.) -/// - cbResult: IN: the size of your buffer. OUT: the number of bytes filled in or required. -ESteamNetworkingGetConfigValueResult GetConfigValue( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigScope eScopeType, intptr_t scopeObj, - ESteamNetworkingConfigDataType *pOutDataType, void *pResult, size_t *cbResult ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_ESteamNetworkingGetConfigValue_BadValue; -} - - -/// Returns info about a configuration value. Returns false if the value does not exist. -/// pOutNextValue can be used to iterate through all of the known configuration values. -/// (Use GetFirstConfigValue() to begin the iteration, will be k_ESteamNetworkingConfig_Invalid on the last value) -/// Any of the output parameters can be NULL if you do not need that information. -bool GetConfigValueInfo( ESteamNetworkingConfigValue eValue, const char **pOutName, ESteamNetworkingConfigDataType *pOutDataType, ESteamNetworkingConfigScope *pOutScope, ESteamNetworkingConfigValue *pOutNextValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - //TODO flat api - return false; -} - -/// Get info about a configuration value. Returns the name of the value, -/// or NULL if the value doesn't exist. Other output parameters can be NULL -/// if you do not need them. -const char *GetConfigValueInfo( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigDataType *pOutDataType, ESteamNetworkingConfigScope *pOutScope ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - //TODO flat api - return NULL; -} - -/// Return the lowest numbered configuration value available in the current environment. -ESteamNetworkingConfigValue GetFirstConfigValue() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_ESteamNetworkingConfig_Invalid; -} - -/// Iterate the list of all configuration values in the current environment that it might -/// be possible to display or edit using a generic UI. To get the first iterable value, -/// pass k_ESteamNetworkingConfig_Invalid. Returns k_ESteamNetworkingConfig_Invalid -/// to signal end of list. -/// -/// The bEnumerateDevVars argument can be used to include "dev" vars. These are vars that -/// are recommended to only be editable in "debug" or "dev" mode and typically should not be -/// shown in a retail environment where a malicious local user might use this to cheat. -ESteamNetworkingConfigValue IterateGenericEditableConfigValues( ESteamNetworkingConfigValue eCurrent, bool bEnumerateDevVars ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_ESteamNetworkingConfig_Invalid; -} - - -// String conversions. You'll usually access these using the respective -// inline methods. -void SteamNetworkingIPAddr_ToString( const SteamNetworkingIPAddr &addr, char *buf, size_t cbBuf, bool bWithPort ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (buf == nullptr || cbBuf == 0) return; - - char buffer[64]{}; // enough for ipv4 & ipv6 + port - std::string str_addr{}; - if (addr.IsIPv4()) { - in_addr ipv4_addr; - ipv4_addr.s_addr = htonl(addr.GetIPv4()); - - if (inet_ntop(AF_INET, &ipv4_addr, buffer, sizeof(buffer) / sizeof(*buffer)) != nullptr) { - if (bWithPort) { - str_addr = buffer; - str_addr += ':'; - str_addr += std::to_string(addr.m_port); - } else { - str_addr = buffer; - } - } - } else { - in6_addr ipv6_addr{}; - memcpy(ipv6_addr.s6_addr, addr.m_ipv6, sizeof(addr.m_ipv6)); - - if (inet_ntop(AF_INET6, &ipv6_addr, buffer, sizeof(buffer) / sizeof(*buffer)) != nullptr) { - if (bWithPort) { - str_addr = '['; - str_addr += buffer; - str_addr += "]:"; - str_addr += std::to_string(addr.m_port); - } else { - str_addr = buffer; - } - } - } - - cbBuf = std::min(cbBuf, str_addr.length() + 1); - strncpy(buf, str_addr.c_str(), cbBuf); - buf[cbBuf - 1] = '\0'; -} - -bool SteamNetworkingIPAddr_ParseString( SteamNetworkingIPAddr *pAddr, const char *pszStr ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - bool valid = false; - - if (pAddr == nullptr || pszStr == nullptr) return valid; - - std::string str(pszStr); - size_t pos = str.find(':'); - - if (pos != std::string::npos) {// Try ipv4 with port - in_addr ipv4_addr; - std::string tmp(str); - tmp[pos] = 0; - const char* ip = tmp.c_str(); - const char* port = &tmp[pos + 1]; - - if (inet_pton(AF_INET, ip, &ipv4_addr) == 1) - { - valid = true; - pAddr->SetIPv4(ntohl(ipv4_addr.s_addr), strtoul(port, nullptr, 10)); - } - } else {// Try ipv4 without port - in_addr ipv4_addr; - if (inet_pton(AF_INET, str.c_str(), &ipv4_addr) == 1) - { - valid = true; - pAddr->SetIPv4(ntohl(ipv4_addr.s_addr), 0); - } - } - - if (!valid) {// Try ipv6 - addrinfo* info = nullptr; - addrinfo hints = {}; - hints.ai_family = AF_INET6; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; - - size_t sep_pos = 0; - std::string ip; - int sep_count = 0; - for (int i = 0; i < str.length(); ++i) { - if (str[i] == ':') { - sep_pos = i; - ++sep_count; - } - } - - if (sep_count == 8) { - ip = std::move(std::string(str.begin(), str.begin() + sep_pos)); - } else { - ip = str; - } - - if (getaddrinfo(ip.c_str(), nullptr, &hints, &info) == 0) { - sockaddr_in6* maddr = (sockaddr_in6*)info->ai_addr; - - size_t pos = str.find(']'); - std::string str_port("0"); - if (pos != std::string::npos) { - str_port = std::move(std::string(str.begin() + pos + 2, str.end())); - } else if (sep_count == 8) { - str_port = std::move(std::string(str.begin() + sep_pos + 1, str.end())); - } - - try { - int port = std::stoi(str_port); - if (port >= 0 && port <= 65535) { - pAddr->SetIPv6(maddr->sin6_addr.s6_addr, port); - valid = true; - } - } - catch(...) { } - } - - if (info) { - freeaddrinfo(info); - } - } - - if (!valid) { - pAddr->Clear(); - } - - return valid; -} - -ESteamNetworkingFakeIPType SteamNetworkingIPAddr_GetFakeIPType( const SteamNetworkingIPAddr &addr ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_ESteamNetworkingFakeIPType_NotFake; -} - - -void SteamNetworkingIdentity_ToString( const SteamNetworkingIdentity &identity, char *buf, size_t cbBuf ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (buf == nullptr) - return; - - std::string str; - str.reserve(SteamNetworkingIdentity::k_cchMaxString); - switch (identity.m_eType) - { - case k_ESteamNetworkingIdentityType_SteamID: - { - str = "steamid:"; - str += std::move(std::to_string(identity.GetSteamID64())); - } - break; - - case k_ESteamNetworkingIdentityType_IPAddress: - { - str = "ip:"; - char buff[SteamNetworkingIPAddr::k_cchMaxString]; - auto& addr = *identity.GetIPAddr(); - SteamNetworkingIPAddr_ToString(addr, buff, sizeof(buff), true); - str += buff; - } - break; - - case k_ESteamNetworkingIdentityType_GenericBytes: - { - int generic_len; - const uint8* pBuf = identity.GetGenericBytes(generic_len); - - str = "gen:"; - str.resize(4 + (generic_len * 2)); - - char* pDest = &str[4]; - while(generic_len--) - { - // I don't care for the last char, I've reserved the max string size - snprintf(pDest, 3, "%02x", *pBuf); - ++pBuf; - pDest += 2; - } - } - break; - - case k_ESteamNetworkingIdentityType_GenericString: - { - str = "str:"; - str += identity.GetGenericString(); - } - break; - - case k_ESteamNetworkingIdentityType_UnknownType: - { - str = identity.m_szUnknownRawString; - } - break; - } - cbBuf = std::min(cbBuf, str.length() + 1); - strncpy(buf, str.c_str(), cbBuf); - buf[cbBuf - 1] = '\0'; -} - -bool SteamNetworkingIdentity_ParseString( SteamNetworkingIdentity *pIdentity, const char *pszStr ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - bool valid = false; - if (pIdentity == nullptr) - { - return valid; - } - - if (pszStr != nullptr) - { - const char* end = strchr(pszStr, ':'); - if (end != nullptr) - { - ++end; - if (strncmp(pszStr, "gen:", end - pszStr) == 0) - { - size_t length = strlen(end); - if (!(length % 2) && length <= (sizeof(pIdentity->m_genericBytes) * 2)) - {// Must be even - valid = true; - length /= 2; - pIdentity->m_eType = k_ESteamNetworkingIdentityType_GenericBytes; - pIdentity->m_cbSize = length; - uint8* pBytes = pIdentity->m_genericBytes; - - char hex[3] = { 0,0,0 }; - while (length) - { - hex[0] = end[0]; - hex[1] = end[1]; - // Steam doesn't check if wasn't a hex char - *pBytes = strtol(hex, nullptr, 16); - - ++pBytes; - end += 2; - --length; - } - } - } - else if (strncmp(pszStr, "steamid:", end - pszStr) == 0) - { - CSteamID steam_id(uint64(strtoull(end, nullptr, 10))); - if (steam_id.IsValid()) - { - valid = true; - pIdentity->SetSteamID(steam_id); - } - } - else if (strncmp(pszStr, "str:", end - pszStr) == 0) - { - valid = pIdentity->SetGenericString(end); - } - else if (strncmp(pszStr, "ip:", end - pszStr) == 0) - { - SteamNetworkingIPAddr steam_addr; - if (SteamNetworkingIPAddr_ParseString(&steam_addr, end)) - { - valid = true; - pIdentity->SetIPAddr(steam_addr); - } - } - } - } - - return valid; -} - - -void RunCallbacks() -{ - if (init_relay && !relay_initialized) { - relay_initialized = true; - SteamRelayNetworkStatus_t data = get_network_status(); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } -} - -void Callback(Common_Message *msg) -{ - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - - } - - if (msg->low_level().type() == Low_Level::DISCONNECT) { - - } - } - - if (msg->has_networking_sockets()) { - - } -} + Steam_Networking_Utils(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_Networking_Utils(); + /// Allocate and initialize a message object. Usually the reason + /// you call this is to pass it to ISteamNetworkingSockets::SendMessages. + /// The returned object will have all of the relevant fields cleared to zero. + /// + /// Optionally you can also request that this system allocate space to + /// hold the payload itself. If cbAllocateBuffer is nonzero, the system + /// will allocate memory to hold a payload of at least cbAllocateBuffer bytes. + /// m_pData will point to the allocated buffer, m_cbSize will be set to the + /// size, and m_pfnFreeData will be set to the proper function to free up + /// the buffer. + /// + /// If cbAllocateBuffer=0, then no buffer is allocated. m_pData will be NULL, + /// m_cbSize will be zero, and m_pfnFreeData will be NULL. You will need to + /// set each of these. + /// + /// You can use SteamNetworkingMessage_t::Release to free up the message + /// bookkeeping object and any associated buffer. See + /// ISteamNetworkingSockets::SendMessages for details on reference + /// counting and ownership. + SteamNetworkingMessage_t *AllocateMessage( int cbAllocateBuffer ); + + bool InitializeRelayAccess(); + + SteamRelayNetworkStatus_t get_network_status(); + + /// Fetch current status of the relay network. + /// + /// SteamRelayNetworkStatus_t is also a callback. It will be triggered on + /// both the user and gameserver interfaces any time the status changes, or + /// ping measurement starts or stops. + /// + /// SteamRelayNetworkStatus_t::m_eAvail is returned. If you want + /// more details, you can pass a non-NULL value. + ESteamNetworkingAvailability GetRelayNetworkStatus( SteamRelayNetworkStatus_t *pDetails ); + + float GetLocalPingLocation( SteamNetworkPingLocation_t &result ); + + int EstimatePingTimeBetweenTwoLocations( const SteamNetworkPingLocation_t &location1, const SteamNetworkPingLocation_t &location2 ); + + + int EstimatePingTimeFromLocalHost( const SteamNetworkPingLocation_t &remoteLocation ); + + + void ConvertPingLocationToString( const SteamNetworkPingLocation_t &location, char *pszBuf, int cchBufSize ); + + + bool ParsePingLocationString( const char *pszString, SteamNetworkPingLocation_t &result ); + + + bool CheckPingDataUpToDate( float flMaxAgeSeconds ); + + + bool IsPingMeasurementInProgress(); + + + int GetPingToDataCenter( SteamNetworkingPOPID popID, SteamNetworkingPOPID *pViaRelayPoP ); + + + int GetDirectPingToPOP( SteamNetworkingPOPID popID ); + + + int GetPOPCount(); + + + int GetPOPList( SteamNetworkingPOPID *list, int nListSz ); + + + // + // Misc + // + + /// Fetch current timestamp. This timer has the following properties: + /// + /// - Monotonicity is guaranteed. + /// - The initial value will be at least 24*3600*30*1e6, i.e. about + /// 30 days worth of microseconds. In this way, the timestamp value of + /// 0 will always be at least "30 days ago". Also, negative numbers + /// will never be returned. + /// - Wraparound / overflow is not a practical concern. + /// + /// If you are running under the debugger and stop the process, the clock + /// might not advance the full wall clock time that has elapsed between + /// calls. If the process is not blocked from normal operation, the + /// timestamp values will track wall clock time, even if you don't call + /// the function frequently. + /// + /// The value is only meaningful for this run of the process. Don't compare + /// it to values obtained on another computer, or other runs of the same process. + SteamNetworkingMicroseconds GetLocalTimestamp(); + + + /// Set a function to receive network-related information that is useful for debugging. + /// This can be very useful during development, but it can also be useful for troubleshooting + /// problems with tech savvy end users. If you have a console or other log that customers + /// can examine, these log messages can often be helpful to troubleshoot network issues. + /// (Especially any warning/error messages.) + /// + /// The detail level indicates what message to invoke your callback on. Lower numeric + /// value means more important, and the value you pass is the lowest priority (highest + /// numeric value) you wish to receive callbacks for. + /// + /// Except when debugging, you should only use k_ESteamNetworkingSocketsDebugOutputType_Msg + /// or k_ESteamNetworkingSocketsDebugOutputType_Warning. For best performance, do NOT + /// request a high detail level and then filter out messages in your callback. Instead, + /// call function function to adjust the desired level of detail. + /// + /// IMPORTANT: This may be called from a service thread, while we own a mutex, etc. + /// Your output function must be threadsafe and fast! Do not make any other + /// Steamworks calls from within the handler. + void SetDebugOutputFunction( ESteamNetworkingSocketsDebugOutputType eDetailLevel, FSteamNetworkingSocketsDebugOutput pfnFunc ); + + // + // Fake IP + // + // Useful for interfacing with code that assumes peers are identified using an IPv4 address + // + + /// Return true if an IPv4 address is one that might be used as a "fake" one. + /// This function is fast; it just does some logical tests on the IP and does + /// not need to do any lookup operations. + // inline bool IsFakeIPv4( uint32 nIPv4 ) { return GetIPv4FakeIPType( nIPv4 ) > k_ESteamNetworkingFakeIPType_NotFake; } + ESteamNetworkingFakeIPType GetIPv4FakeIPType( uint32 nIPv4 ); + + /// Get the real identity associated with a given FakeIP. + /// + /// On failure, returns: + /// - k_EResultInvalidParam: the IP is not a FakeIP. + /// - k_EResultNoMatch: we don't recognize that FakeIP and don't know the corresponding identity. + /// + /// FakeIP's used by active connections, or the FakeIPs assigned to local identities, + /// will always work. FakeIPs for recently destroyed connections will continue to + /// return results for a little while, but not forever. At some point, we will forget + /// FakeIPs to save space. It's reasonably safe to assume that you can read back the + /// real identity of a connection very soon after it is destroyed. But do not wait + /// indefinitely. + EResult GetRealIdentityForFakeIP( const SteamNetworkingIPAddr &fakeIP, SteamNetworkingIdentity *pOutRealIdentity ); + + + // + // Set and get configuration values, see ESteamNetworkingConfigValue for individual descriptions. + // + + // Shortcuts for common cases. (Implemented as inline functions below) + /* + bool SetGlobalConfigValueInt32( ESteamNetworkingConfigValue eValue, int32 val ); + bool SetGlobalConfigValueFloat( ESteamNetworkingConfigValue eValue, float val ); + bool SetGlobalConfigValueString( ESteamNetworkingConfigValue eValue, const char *val ); + bool SetConnectionConfigValueInt32( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, int32 val ); + bool SetConnectionConfigValueFloat( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, float val ); + bool SetConnectionConfigValueString( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, const char *val ); + */ + /// Set a configuration value. + /// - eValue: which value is being set + /// - eScope: Onto what type of object are you applying the setting? + /// - scopeArg: Which object you want to change? (Ignored for global scope). E.g. connection handle, listen socket handle, interface pointer, etc. + /// - eDataType: What type of data is in the buffer at pValue? This must match the type of the variable exactly! + /// - pArg: Value to set it to. You can pass NULL to remove a non-global sett at this scope, + /// causing the value for that object to use global defaults. Or at global scope, passing NULL + /// will reset any custom value and restore it to the system default. + /// NOTE: When setting callback functions, do not pass the function pointer directly. + /// Your argument should be a pointer to a function pointer. + bool SetConfigValue( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigScope eScopeType, intptr_t scopeObj, + ESteamNetworkingConfigDataType eDataType, const void *pArg ); + + + /// Get a configuration value. + /// - eValue: which value to fetch + /// - eScopeType: query setting on what type of object + /// - eScopeArg: the object to query the setting for + /// - pOutDataType: If non-NULL, the data type of the value is returned. + /// - pResult: Where to put the result. Pass NULL to query the required buffer size. (k_ESteamNetworkingGetConfigValue_BufferTooSmall will be returned.) + /// - cbResult: IN: the size of your buffer. OUT: the number of bytes filled in or required. + ESteamNetworkingGetConfigValueResult GetConfigValue( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigScope eScopeType, intptr_t scopeObj, + ESteamNetworkingConfigDataType *pOutDataType, void *pResult, size_t *cbResult ); + + + /// Returns info about a configuration value. Returns false if the value does not exist. + /// pOutNextValue can be used to iterate through all of the known configuration values. + /// (Use GetFirstConfigValue() to begin the iteration, will be k_ESteamNetworkingConfig_Invalid on the last value) + /// Any of the output parameters can be NULL if you do not need that information. + bool GetConfigValueInfo( ESteamNetworkingConfigValue eValue, const char **pOutName, ESteamNetworkingConfigDataType *pOutDataType, ESteamNetworkingConfigScope *pOutScope, ESteamNetworkingConfigValue *pOutNextValue ); + + /// Get info about a configuration value. Returns the name of the value, + /// or NULL if the value doesn't exist. Other output parameters can be NULL + /// if you do not need them. + const char *GetConfigValueInfo( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigDataType *pOutDataType, ESteamNetworkingConfigScope *pOutScope ); + + /// Return the lowest numbered configuration value available in the current environment. + ESteamNetworkingConfigValue GetFirstConfigValue(); + + /// Iterate the list of all configuration values in the current environment that it might + /// be possible to display or edit using a generic UI. To get the first iterable value, + /// pass k_ESteamNetworkingConfig_Invalid. Returns k_ESteamNetworkingConfig_Invalid + /// to signal end of list. + /// + /// The bEnumerateDevVars argument can be used to include "dev" vars. These are vars that + /// are recommended to only be editable in "debug" or "dev" mode and typically should not be + /// shown in a retail environment where a malicious local user might use this to cheat. + ESteamNetworkingConfigValue IterateGenericEditableConfigValues( ESteamNetworkingConfigValue eCurrent, bool bEnumerateDevVars ); + + + // String conversions. You'll usually access these using the respective + // inline methods. + void SteamNetworkingIPAddr_ToString( const SteamNetworkingIPAddr &addr, char *buf, size_t cbBuf, bool bWithPort ); + + bool SteamNetworkingIPAddr_ParseString( SteamNetworkingIPAddr *pAddr, const char *pszStr ); + + ESteamNetworkingFakeIPType SteamNetworkingIPAddr_GetFakeIPType( const SteamNetworkingIPAddr &addr ); + + + void SteamNetworkingIdentity_ToString( const SteamNetworkingIdentity &identity, char *buf, size_t cbBuf ); + + bool SteamNetworkingIdentity_ParseString( SteamNetworkingIdentity *pIdentity, const char *pszStr ); + + + void RunCallbacks(); + + void Callback(Common_Message *msg); }; + +#endif // __INCLUDED_STEAM_NETWORKING_UTILS_H__ diff --git a/dll/dll/steam_parental.h b/dll/dll/steam_parental.h index 5505c613..fa1ca9cc 100644 --- a/dll/dll/steam_parental.h +++ b/dll/dll/steam_parental.h @@ -15,9 +15,13 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_PARENTAL_H__ +#define __INCLUDED_STEAM_PARENTAL_H__ + #include "base.h" -class Steam_Parental : public ISteamParentalSettings +class Steam_Parental : +public ISteamParentalSettings { public: bool BIsParentalLockEnabled(); @@ -29,3 +33,5 @@ public: bool BIsFeatureBlocked( EParentalFeature eFeature ); bool BIsFeatureInBlockList( EParentalFeature eFeature ); }; + +#endif // __INCLUDED_STEAM_PARENTAL_H__ diff --git a/dll/dll/steam_parties.h b/dll/dll/steam_parties.h index 8a02308a..b732297a 100644 --- a/dll/dll/steam_parties.h +++ b/dll/dll/steam_parties.h @@ -15,192 +15,95 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_PARTIES_H__ +#define __INCLUDED_STEAM_PARTIES_H__ + #include "base.h" class Steam_Parties : public ISteamParties { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; + std::chrono::time_point initialized_time = std::chrono::steady_clock::now(); - FSteamNetworkingSocketsDebugOutput debug_function; + FSteamNetworkingSocketsDebugOutput debug_function{}; + + static void steam_callback(void *object, Common_Message *msg); + static void steam_run_every_runcb(void *object); public: -static void steam_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Parties *steam_parties = (Steam_Parties *)object; - steam_parties->Callback(msg); -} - -static void steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Parties *steam_parties = (Steam_Parties *)object; - steam_parties->RunCallbacks(); -} - -Steam_Parties(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->run_every_runcb = run_every_runcb; - //this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Parties::steam_callback, this); - this->run_every_runcb->add(&Steam_Parties::steam_run_every_runcb, this); - - this->callback_results = callback_results; - this->callbacks = callbacks; -} - -~Steam_Parties() -{ - //this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Parties::steam_callback, this); - this->run_every_runcb->remove(&Steam_Parties::steam_run_every_runcb, this); -} + Steam_Parties(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_Parties(); -// ============================================================================================= -// Party Client APIs + // ============================================================================================= + // Party Client APIs -// Enumerate any active beacons for parties you may wish to join -uint32 GetNumActiveBeacons() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} + // Enumerate any active beacons for parties you may wish to join + uint32 GetNumActiveBeacons(); -PartyBeaconID_t GetBeaconByIndex( uint32 unIndex ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_ulPartyBeaconIdInvalid; -} + PartyBeaconID_t GetBeaconByIndex( uint32 unIndex ); -bool GetBeaconDetails( PartyBeaconID_t ulBeaconID, CSteamID *pSteamIDBeaconOwner, STEAM_OUT_STRUCT() SteamPartyBeaconLocation_t *pLocation, STEAM_OUT_STRING_COUNT(cchMetadata) char *pchMetadata, int cchMetadata ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} + bool GetBeaconDetails( PartyBeaconID_t ulBeaconID, CSteamID *pSteamIDBeaconOwner, STEAM_OUT_STRUCT() SteamPartyBeaconLocation_t *pLocation, STEAM_OUT_STRING_COUNT(cchMetadata) char *pchMetadata, int cchMetadata ); -// Join an open party. Steam will reserve one beacon slot for your SteamID, -// and return the necessary JoinGame string for you to use to connect -STEAM_CALL_RESULT( JoinPartyCallback_t ) -SteamAPICall_t JoinParty( PartyBeaconID_t ulBeaconID ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} + // Join an open party. Steam will reserve one beacon slot for your SteamID, + // and return the necessary JoinGame string for you to use to connect + STEAM_CALL_RESULT( JoinPartyCallback_t ) + SteamAPICall_t JoinParty( PartyBeaconID_t ulBeaconID ); -// ============================================================================================= -// Party Host APIs + // ============================================================================================= + // Party Host APIs -// Get a list of possible beacon locations -bool GetNumAvailableBeaconLocations( uint32 *puNumLocations ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} + // Get a list of possible beacon locations + bool GetNumAvailableBeaconLocations( uint32 *puNumLocations ); -bool GetAvailableBeaconLocations( SteamPartyBeaconLocation_t *pLocationList, uint32 uMaxNumLocations ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} + bool GetAvailableBeaconLocations( SteamPartyBeaconLocation_t *pLocationList, uint32 uMaxNumLocations ); -// Create a new party beacon and activate it in the selected location. -// unOpenSlots is the maximum number of users that Steam will send to you. -// When people begin responding to your beacon, Steam will send you -// PartyReservationCallback_t callbacks to let you know who is on the way. -STEAM_CALL_RESULT( CreateBeaconCallback_t ) -SteamAPICall_t CreateBeacon( uint32 unOpenSlots, SteamPartyBeaconLocation_t *pBeaconLocation, const char *pchConnectString, const char *pchMetadata ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} + // Create a new party beacon and activate it in the selected location. + // unOpenSlots is the maximum number of users that Steam will send to you. + // When people begin responding to your beacon, Steam will send you + // PartyReservationCallback_t callbacks to let you know who is on the way. + STEAM_CALL_RESULT( CreateBeaconCallback_t ) + SteamAPICall_t CreateBeacon( uint32 unOpenSlots, SteamPartyBeaconLocation_t *pBeaconLocation, const char *pchConnectString, const char *pchMetadata ); -// Call this function when a user that had a reservation (see callback below) -// has successfully joined your party. -// Steam will manage the remaining open slots automatically. -void OnReservationCompleted( PartyBeaconID_t ulBeacon, CSteamID steamIDUser ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // Call this function when a user that had a reservation (see callback below) + // has successfully joined your party. + // Steam will manage the remaining open slots automatically. + void OnReservationCompleted( PartyBeaconID_t ulBeacon, CSteamID steamIDUser ); -// To cancel a reservation (due to timeout or user input), call this. -// Steam will open a new reservation slot. -// Note: The user may already be in-flight to your game, so it's possible they will still connect and try to join your party. -void CancelReservation( PartyBeaconID_t ulBeacon, CSteamID steamIDUser ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + // To cancel a reservation (due to timeout or user input), call this. + // Steam will open a new reservation slot. + // Note: The user may already be in-flight to your game, so it's possible they will still connect and try to join your party. + void CancelReservation( PartyBeaconID_t ulBeacon, CSteamID steamIDUser ); -// Change the number of open beacon reservation slots. -// Call this if, for example, someone without a reservation joins your party (eg a friend, or via your own matchmaking system). -STEAM_CALL_RESULT( ChangeNumOpenSlotsCallback_t ) -SteamAPICall_t ChangeNumOpenSlots( PartyBeaconID_t ulBeacon, uint32 unOpenSlots ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} + // Change the number of open beacon reservation slots. + // Call this if, for example, someone without a reservation joins your party (eg a friend, or via your own matchmaking system). + STEAM_CALL_RESULT( ChangeNumOpenSlotsCallback_t ) + SteamAPICall_t ChangeNumOpenSlots( PartyBeaconID_t ulBeacon, uint32 unOpenSlots ); -// Turn off the beacon. -bool DestroyBeacon( PartyBeaconID_t ulBeacon ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} + // Turn off the beacon. + bool DestroyBeacon( PartyBeaconID_t ulBeacon ); -// Utils -bool GetBeaconLocationData( SteamPartyBeaconLocation_t BeaconLocation, ESteamPartyBeaconLocationData eData, STEAM_OUT_STRING_COUNT(cchDataStringOut) char *pchDataStringOut, int cchDataStringOut ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} + // Utils + bool GetBeaconLocationData( SteamPartyBeaconLocation_t BeaconLocation, ESteamPartyBeaconLocationData eData, STEAM_OUT_STRING_COUNT(cchDataStringOut) char *pchDataStringOut, int cchDataStringOut ); -void RunCallbacks() -{ -} + void RunCallbacks(); -void Callback(Common_Message *msg) -{ - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - - } - - if (msg->low_level().type() == Low_Level::DISCONNECT) { - - } - } - - if (msg->has_networking_sockets()) { - - } -} + void Callback(Common_Message *msg); }; + +#endif // __INCLUDED_STEAM_PARTIES_H__ diff --git a/dll/dll/steam_remote_storage.h b/dll/dll/steam_remote_storage.h index 4a6785a9..283c7a57 100644 --- a/dll/dll/steam_remote_storage.h +++ b/dll/dll/steam_remote_storage.h @@ -15,58 +15,54 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_REMOTE_STORAGE_H__ +#define __INCLUDED_STEAM_REMOTE_STORAGE_H__ + #include "base.h" -#include "dll/ugc_remote_storage_bridge.h" +#include "ugc_remote_storage_bridge.h" struct Async_Read { - SteamAPICall_t api_call; - uint32 offset; - uint32 to_read; - uint32 size; - std::string file_name; + SteamAPICall_t api_call{}; + uint32 offset{}; + uint32 to_read{}; + uint32 size{}; + std::string file_name{}; }; struct Stream_Write { - std::string file_name; - UGCFileWriteStreamHandle_t write_stream_handle; - std::vector file_data; + std::string file_name{}; + UGCFileWriteStreamHandle_t write_stream_handle{}; + std::vector file_data{}; }; struct Downloaded_File { + // --- these are needed due to the usage of union + Downloaded_File(); + ~Downloaded_File(); + // --- + enum DownloadSource { AfterFileShare, // attempted download after a call to Steam_Remote_Storage::FileShare() AfterSendQueryUGCRequest, // attempted download after a call to Steam_UGC::SendQueryUGCRequest() FromUGCDownloadToLocation, // attempted download via Steam_Remote_Storage::UGCDownloadToLocation() - } source; + } source{}; // *** used in any case - std::string file; - uint64 total_size; + std::string file{}; + uint64 total_size{}; // put any additional data needed by other sources here - // *** used when source = SendQueryUGCRequest only - Ugc_Remote_Storage_Bridge::QueryInfo mod_query_info; + union { + // *** used when source = SendQueryUGCRequest only + Ugc_Remote_Storage_Bridge::QueryInfo mod_query_info; - // *** used when source = FromUGCDownloadToLocation only - std::string download_to_location_fullpath; + // *** used when source = FromUGCDownloadToLocation only + std::string download_to_location_fullpath; + }; + }; -static void copy_file(const std::string &src_filepath, const std::string &dst_filepath) -{ - try - { - PRINT_DEBUG("copying file '%s' to '%s'", src_filepath.c_str(), dst_filepath.c_str()); - const std::filesystem::path src_p(std::filesystem::u8path(src_filepath)); - - if (!common_helpers::file_exist(src_p)) return; - - const std::filesystem::path dst_p(std::filesystem::u8path(dst_filepath)); - std::filesystem::create_directories(dst_p.parent_path()); // make the folder tree if needed - std::filesystem::copy_file(src_p, dst_p, std::filesystem::copy_options::overwrite_existing); - } catch(...) {} -} - class Steam_Remote_Storage : public ISteamRemoteStorage001, public ISteamRemoteStorage002, @@ -85,1158 +81,262 @@ public ISteamRemoteStorage014, public ISteamRemoteStorage { private: - class Settings *settings; - class Ugc_Remote_Storage_Bridge *ugc_bridge; - class Local_Storage *local_storage; - class SteamCallResults *callback_results; - bool steam_cloud_enabled; - std::vector async_reads; - std::vector stream_writes; - std::map shared_files; - std::map downloaded_files; + class Settings *settings{}; + class Ugc_Remote_Storage_Bridge *ugc_bridge{}; + class Local_Storage *local_storage{}; + class SteamCallResults *callback_results{}; - public: -Steam_Remote_Storage(class Settings *settings, class Ugc_Remote_Storage_Bridge *ugc_bridge, class Local_Storage *local_storage, class SteamCallResults *callback_results) -{ - this->settings = settings; - this->ugc_bridge = ugc_bridge; - this->local_storage = local_storage; - this->callback_results = callback_results; - steam_cloud_enabled = true; -} - -// NOTE -// -// Filenames are case-insensitive, and will be converted to lowercase automatically. -// So "foo.bar" and "Foo.bar" are the same file, and if you write "Foo.bar" then -// iterate the files, the filename returned will be "foo.bar". -// - -// file operations -bool FileWrite( const char *pchFile, const void *pvData, int32 cubData ) -{ - PRINT_DEBUG("'%s' %p %u", pchFile, pvData, cubData); - std::lock_guard lock(global_mutex); - - if (!pchFile || !pchFile[0] || cubData <= 0 || cubData > k_unMaxCloudFileChunkSize || !pvData) { - return false; - } - - int data_stored = local_storage->store_data(Local_Storage::remote_storage_folder, pchFile, (char* )pvData, cubData); - PRINT_DEBUG("%i, %u", data_stored, data_stored == cubData); - return data_stored == cubData; -} - -int32 FileRead( const char *pchFile, void *pvData, int32 cubDataToRead ) -{ - PRINT_DEBUG("'%s' %p %i", pchFile, pvData, cubDataToRead); - std::lock_guard lock(global_mutex); - - if (!pchFile || !pchFile[0] || !pvData || !cubDataToRead) return 0; - int read_data = local_storage->get_data(Local_Storage::remote_storage_folder, pchFile, (char* )pvData, cubDataToRead); - if (read_data < 0) read_data = 0; - PRINT_DEBUG(" Read %i", read_data); - return read_data; -} - -STEAM_CALL_RESULT( RemoteStorageFileWriteAsyncComplete_t ) -SteamAPICall_t FileWriteAsync( const char *pchFile, const void *pvData, uint32 cubData ) -{ - PRINT_DEBUG("'%s' %p %u", pchFile, pvData, cubData); - std::lock_guard lock(global_mutex); - - if (!pchFile || !pchFile[0] || cubData > k_unMaxCloudFileChunkSize || cubData == 0 || !pvData) { - return k_uAPICallInvalid; - } - - bool success = local_storage->store_data(Local_Storage::remote_storage_folder, pchFile, (char* )pvData, cubData) == cubData; - RemoteStorageFileWriteAsyncComplete_t data; - data.m_eResult = success ? k_EResultOK : k_EResultFail; - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.01); -} - - -STEAM_CALL_RESULT( RemoteStorageFileReadAsyncComplete_t ) -SteamAPICall_t FileReadAsync( const char *pchFile, uint32 nOffset, uint32 cubToRead ) -{ - PRINT_DEBUG("'%s' %u %u", pchFile, nOffset, cubToRead); - std::lock_guard lock(global_mutex); - - if (!pchFile || !pchFile[0]) return k_uAPICallInvalid; - unsigned int size = local_storage->file_size(Local_Storage::remote_storage_folder, pchFile); - - RemoteStorageFileReadAsyncComplete_t data; - if (size <= nOffset) { - return k_uAPICallInvalid; - } - - if ((size - nOffset) < cubToRead) cubToRead = size - nOffset; - - struct Async_Read a_read{}; - data.m_eResult = k_EResultOK; - a_read.offset = data.m_nOffset = nOffset; - a_read.api_call = data.m_hFileReadAsync = callback_results->reserveCallResult(); - a_read.to_read = data.m_cubRead = cubToRead; - a_read.file_name = std::string(pchFile); - a_read.size = size; - - async_reads.push_back(a_read); - callback_results->addCallResult(data.m_hFileReadAsync, data.k_iCallback, &data, sizeof(data), 0.0); - return data.m_hFileReadAsync; -} - -bool FileReadAsyncComplete( SteamAPICall_t hReadCall, void *pvBuffer, uint32 cubToRead ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!pvBuffer) return false; - - auto a_read = std::find_if(async_reads.begin(), async_reads.end(), [&hReadCall](Async_Read const& item) { return item.api_call == hReadCall; }); - if (async_reads.end() == a_read) - return false; - - if (cubToRead < a_read->to_read) - return false; - - char *temp = new char[a_read->size]; - int read_data = local_storage->get_data(Local_Storage::remote_storage_folder, a_read->file_name, (char* )temp, a_read->size); - if (read_data < a_read->to_read + a_read->offset) { - delete[] temp; - return false; - } - - memcpy(pvBuffer, temp + a_read->offset, a_read->to_read); - delete[] temp; - async_reads.erase(a_read); - return true; -} - - -bool FileForget( const char *pchFile ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!pchFile || !pchFile[0]) return false; - - return true; -} - -bool FileDelete( const char *pchFile ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!pchFile || !pchFile[0]) return false; + std::vector async_reads{}; + std::vector stream_writes{}; + std::map shared_files{}; + std::map downloaded_files{}; - return local_storage->file_delete(Local_Storage::remote_storage_folder, pchFile); -} + bool steam_cloud_enabled = true; -STEAM_CALL_RESULT( RemoteStorageFileShareResult_t ) -SteamAPICall_t FileShare( const char *pchFile ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!pchFile || !pchFile[0]) return k_uAPICallInvalid; +public: - RemoteStorageFileShareResult_t data = {}; - if (local_storage->file_exists(Local_Storage::remote_storage_folder, pchFile)) { - data.m_eResult = k_EResultOK; - data.m_hFile = generate_steam_api_call_id(); - strncpy(data.m_rgchFilename, pchFile, sizeof(data.m_rgchFilename) - 1); - shared_files[data.m_hFile] = pchFile; - } else { - data.m_eResult = k_EResultFileNotFound; - } + Steam_Remote_Storage(class Settings *settings, class Ugc_Remote_Storage_Bridge *ugc_bridge, class Local_Storage *local_storage, class SteamCallResults *callback_results); - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} + // NOTE + // + // Filenames are case-insensitive, and will be converted to lowercase automatically. + // So "foo.bar" and "Foo.bar" are the same file, and if you write "Foo.bar" then + // iterate the files, the filename returned will be "foo.bar". + // -bool SetSyncPlatforms( const char *pchFile, ERemoteStoragePlatform eRemoteStoragePlatform ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!pchFile || !pchFile[0]) return false; - - return true; -} + // file operations + bool FileWrite( const char *pchFile, const void *pvData, int32 cubData ); + + int32 FileRead( const char *pchFile, void *pvData, int32 cubDataToRead ); + + STEAM_CALL_RESULT( RemoteStorageFileWriteAsyncComplete_t ) + SteamAPICall_t FileWriteAsync( const char *pchFile, const void *pvData, uint32 cubData ); -// file operations that cause network IO -UGCFileWriteStreamHandle_t FileWriteStreamOpen( const char *pchFile ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!pchFile || !pchFile[0]) return k_UGCFileStreamHandleInvalid; - - static UGCFileWriteStreamHandle_t handle; - ++handle; - struct Stream_Write stream_write; - stream_write.file_name = std::string(pchFile); - stream_write.write_stream_handle = handle; - stream_writes.push_back(stream_write); - return stream_write.write_stream_handle; -} + STEAM_CALL_RESULT( RemoteStorageFileReadAsyncComplete_t ) + SteamAPICall_t FileReadAsync( const char *pchFile, uint32 nOffset, uint32 cubToRead ); -bool FileWriteStreamWriteChunk( UGCFileWriteStreamHandle_t writeHandle, const void *pvData, int32 cubData ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!pvData || cubData < 0) return false; - - auto request = std::find_if(stream_writes.begin(), stream_writes.end(), [&writeHandle](struct Stream_Write const& item) { return item.write_stream_handle == writeHandle; }); - if (stream_writes.end() == request) - return false; - - std::copy((char *)pvData, (char *)pvData + cubData, std::back_inserter(request->file_data)); - return true; -} - -bool FileWriteStreamClose( UGCFileWriteStreamHandle_t writeHandle ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - auto request = std::find_if(stream_writes.begin(), stream_writes.end(), [&writeHandle](struct Stream_Write const& item) { return item.write_stream_handle == writeHandle; }); - if (stream_writes.end() == request) - return false; - - local_storage->store_data(Local_Storage::remote_storage_folder, request->file_name, request->file_data.data(), request->file_data.size()); - stream_writes.erase(request); - return true; -} - -bool FileWriteStreamCancel( UGCFileWriteStreamHandle_t writeHandle ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - auto request = std::find_if(stream_writes.begin(), stream_writes.end(), [&writeHandle](struct Stream_Write const& item) { return item.write_stream_handle == writeHandle; }); - if (stream_writes.end() == request) - return false; - - stream_writes.erase(request); - return true; -} - -// file information -bool FileExists( const char *pchFile ) -{ - PRINT_DEBUG("%s", pchFile); - std::lock_guard lock(global_mutex); - if (!pchFile || !pchFile[0]) return false; - - return local_storage->file_exists(Local_Storage::remote_storage_folder, pchFile); -} - -bool FilePersisted( const char *pchFile ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!pchFile || !pchFile[0]) return false; - - return local_storage->file_exists(Local_Storage::remote_storage_folder, pchFile); -} - -int32 GetFileSize( const char *pchFile ) -{ - PRINT_DEBUG("%s", pchFile); - std::lock_guard lock(global_mutex); - if (!pchFile || !pchFile[0]) return 0; - - return local_storage->file_size(Local_Storage::remote_storage_folder, pchFile); -} - -int64 GetFileTimestamp( const char *pchFile ) -{ - PRINT_DEBUG("'%s'", pchFile); - std::lock_guard lock(global_mutex); - if (!pchFile || !pchFile[0]) return 0; - - return local_storage->file_timestamp(Local_Storage::remote_storage_folder, pchFile); -} - -ERemoteStoragePlatform GetSyncPlatforms( const char *pchFile ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return k_ERemoteStoragePlatformAll; -} + bool FileReadAsyncComplete( SteamAPICall_t hReadCall, void *pvBuffer, uint32 cubToRead ); -// iteration -int32 GetFileCount() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - int32 num = local_storage->count_files(Local_Storage::remote_storage_folder); - PRINT_DEBUG("count: %i", num); - return num; -} + bool FileForget( const char *pchFile ); -const char *GetFileNameAndSize( int iFile, int32 *pnFileSizeInBytes ) -{ - PRINT_DEBUG("%i", iFile); - std::lock_guard lock(global_mutex); - - static char output_filename[MAX_FILENAME_LENGTH]; - if (local_storage->iterate_file(Local_Storage::remote_storage_folder, iFile, output_filename, pnFileSizeInBytes)) { - PRINT_DEBUG("|%s|, size: %i", output_filename, pnFileSizeInBytes ? *pnFileSizeInBytes : 0); - return output_filename; - } else { - return ""; - } -} + bool FileDelete( const char *pchFile ); + + STEAM_CALL_RESULT( RemoteStorageFileShareResult_t ) + SteamAPICall_t FileShare( const char *pchFile ); + + bool SetSyncPlatforms( const char *pchFile, ERemoteStoragePlatform eRemoteStoragePlatform ); -// configuration management -bool GetQuota( uint64 *pnTotalBytes, uint64 *puAvailableBytes ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - uint64 quota = 2 << 26; - if (pnTotalBytes) *pnTotalBytes = quota; - if (puAvailableBytes) *puAvailableBytes = (quota); - return true; -} + // file operations that cause network IO + UGCFileWriteStreamHandle_t FileWriteStreamOpen( const char *pchFile ); -bool GetQuota( int32 *pnTotalBytes, int32 *puAvailableBytes ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - uint64 quota = 2 << 26; - if (pnTotalBytes) *pnTotalBytes = quota; - if (puAvailableBytes) *puAvailableBytes = (quota); - return true; -} + bool FileWriteStreamWriteChunk( UGCFileWriteStreamHandle_t writeHandle, const void *pvData, int32 cubData ); -bool IsCloudEnabledForAccount() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return true; -} + bool FileWriteStreamClose( UGCFileWriteStreamHandle_t writeHandle ); -bool IsCloudEnabledForApp() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return steam_cloud_enabled; -} + bool FileWriteStreamCancel( UGCFileWriteStreamHandle_t writeHandle ); -bool IsCloudEnabledThisApp() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return steam_cloud_enabled; -} + // file information + bool FileExists( const char *pchFile ); -void SetCloudEnabledForApp( bool bEnabled ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - steam_cloud_enabled = bEnabled; -} + bool FilePersisted( const char *pchFile ); -bool SetCloudEnabledThisApp( bool bEnabled ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - steam_cloud_enabled = bEnabled; - return true; -} + int32 GetFileSize( const char *pchFile ); -// user generated content + int64 GetFileTimestamp( const char *pchFile ); -// Downloads a UGC file. A priority value of 0 will download the file immediately, -// otherwise it will wait to download the file until all downloads with a lower priority -// value are completed. Downloads with equal priority will occur simultaneously. -STEAM_CALL_RESULT( RemoteStorageDownloadUGCResult_t ) -SteamAPICall_t UGCDownload( UGCHandle_t hContent, uint32 unPriority ) -{ - PRINT_DEBUG("%llu", hContent); - std::lock_guard lock(global_mutex); - if (hContent == k_UGCHandleInvalid) return k_uAPICallInvalid; + ERemoteStoragePlatform GetSyncPlatforms( const char *pchFile ); - RemoteStorageDownloadUGCResult_t data{}; - data.m_hFile = hContent; - if (shared_files.count(hContent)) { - data.m_eResult = k_EResultOK; - data.m_nAppID = settings->get_local_game_id().AppID(); - data.m_ulSteamIDOwner = settings->get_local_steam_id().ConvertToUint64(); - data.m_nSizeInBytes = local_storage->file_size(Local_Storage::remote_storage_folder, shared_files[hContent]); + // iteration + int32 GetFileCount(); - shared_files[hContent].copy(data.m_pchFileName, sizeof(data.m_pchFileName) - 1); + const char *GetFileNameAndSize( int iFile, int32 *pnFileSizeInBytes ); - downloaded_files[hContent].source = Downloaded_File::DownloadSource::AfterFileShare; - downloaded_files[hContent].file = shared_files[hContent]; - downloaded_files[hContent].total_size = data.m_nSizeInBytes; - } else if (auto query_res = ugc_bridge->get_ugc_query_result(hContent)) { - auto mod = settings->getMod(query_res.value().mod_id); - auto &mod_name = query_res.value().is_primary_file - ? mod.primaryFileName - : mod.previewFileName; - int32 mod_size = query_res.value().is_primary_file - ? mod.primaryFileSize - : mod.previewFileSize; - data.m_eResult = k_EResultOK; - data.m_nAppID = settings->get_local_game_id().AppID(); - data.m_ulSteamIDOwner = mod.steamIDOwner; - data.m_nSizeInBytes = mod_size; - data.m_ulSteamIDOwner = mod.steamIDOwner; + // configuration management + bool GetQuota( uint64 *pnTotalBytes, uint64 *puAvailableBytes ); - mod_name.copy(data.m_pchFileName, sizeof(data.m_pchFileName) - 1); + bool GetQuota( int32 *pnTotalBytes, int32 *puAvailableBytes ); + + bool IsCloudEnabledForAccount(); + + bool IsCloudEnabledForApp(); + + bool IsCloudEnabledThisApp(); + + void SetCloudEnabledForApp( bool bEnabled ); + + bool SetCloudEnabledThisApp( bool bEnabled ); + + // user generated content + + // Downloads a UGC file. A priority value of 0 will download the file immediately, + // otherwise it will wait to download the file until all downloads with a lower priority + // value are completed. Downloads with equal priority will occur simultaneously. + STEAM_CALL_RESULT( RemoteStorageDownloadUGCResult_t ) + SteamAPICall_t UGCDownload( UGCHandle_t hContent, uint32 unPriority ); + + STEAM_CALL_RESULT( RemoteStorageDownloadUGCResult_t ) + SteamAPICall_t UGCDownload( UGCHandle_t hContent ); + + + // Gets the amount of data downloaded so far for a piece of content. pnBytesExpected can be 0 if function returns false + // or if the transfer hasn't started yet, so be careful to check for that before dividing to get a percentage + bool GetUGCDownloadProgress( UGCHandle_t hContent, int32 *pnBytesDownloaded, int32 *pnBytesExpected ); + + bool GetUGCDownloadProgress( UGCHandle_t hContent, uint32 *pnBytesDownloaded, uint32 *pnBytesExpected ); + + + // Gets metadata for a file after it has been downloaded. This is the same metadata given in the RemoteStorageDownloadUGCResult_t call result + bool GetUGCDetails( UGCHandle_t hContent, AppId_t *pnAppID, STEAM_OUT_STRING() char **ppchName, int32 *pnFileSizeInBytes, STEAM_OUT_STRUCT() CSteamID *pSteamIDOwner ); + + + // After download, gets the content of the file. + // Small files can be read all at once by calling this function with an offset of 0 and cubDataToRead equal to the size of the file. + // Larger files can be read in chunks to reduce memory usage (since both sides of the IPC client and the game itself must allocate + // enough memory for each chunk). Once the last byte is read, the file is implicitly closed and further calls to UGCRead will fail + // unless UGCDownload is called again. + // For especially large files (anything over 100MB) it is a requirement that the file is read in chunks. + int32 UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead, uint32 cOffset, EUGCReadAction eAction ); + + int32 UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead ); + + int32 UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead, uint32 cOffset); + + // Functions to iterate through UGC that has finished downloading but has not yet been read via UGCRead() + int32 GetCachedUGCCount(); + + UGCHandle_t GetCachedUGCHandle( int32 iCachedContent ); + + + // The following functions are only necessary on the Playstation 3. On PC & Mac, the Steam client will handle these operations for you + // On Playstation 3, the game controls which files are stored in the cloud, via FilePersist, FileFetch, and FileForget. - downloaded_files[hContent].source = Downloaded_File::DownloadSource::AfterSendQueryUGCRequest; - downloaded_files[hContent].file = mod_name; - downloaded_files[hContent].total_size = mod_size; - - downloaded_files[hContent].mod_query_info = query_res.value(); - - } else { - data.m_eResult = k_EResultFileNotFound; //TODO: not sure if this is the right result - } - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -STEAM_CALL_RESULT( RemoteStorageDownloadUGCResult_t ) -SteamAPICall_t UGCDownload( UGCHandle_t hContent ) -{ - PRINT_DEBUG("old"); - return UGCDownload(hContent, 1); -} - - -// Gets the amount of data downloaded so far for a piece of content. pnBytesExpected can be 0 if function returns false -// or if the transfer hasn't started yet, so be careful to check for that before dividing to get a percentage -bool GetUGCDownloadProgress( UGCHandle_t hContent, int32 *pnBytesDownloaded, int32 *pnBytesExpected ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - -bool GetUGCDownloadProgress( UGCHandle_t hContent, uint32 *pnBytesDownloaded, uint32 *pnBytesExpected ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - - return false; -} - - -// Gets metadata for a file after it has been downloaded. This is the same metadata given in the RemoteStorageDownloadUGCResult_t call result -bool GetUGCDetails( UGCHandle_t hContent, AppId_t *pnAppID, STEAM_OUT_STRING() char **ppchName, int32 *pnFileSizeInBytes, STEAM_OUT_STRUCT() CSteamID *pSteamIDOwner ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - - -// After download, gets the content of the file. -// Small files can be read all at once by calling this function with an offset of 0 and cubDataToRead equal to the size of the file. -// Larger files can be read in chunks to reduce memory usage (since both sides of the IPC client and the game itself must allocate -// enough memory for each chunk). Once the last byte is read, the file is implicitly closed and further calls to UGCRead will fail -// unless UGCDownload is called again. -// For especially large files (anything over 100MB) it is a requirement that the file is read in chunks. -int32 UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead, uint32 cOffset, EUGCReadAction eAction ) -{ - PRINT_DEBUG("%llu, %p, %i, %u, %i", hContent, pvData, cubDataToRead, cOffset, eAction); - std::lock_guard lock(global_mutex); - - if (hContent == k_UGCHandleInvalid || !downloaded_files.count(hContent) || cubDataToRead < 0) { - return -1; //TODO: is this the right return value? - } - - int read_data = -1; - uint64 total_size = 0; - Downloaded_File f = downloaded_files[hContent]; - - // depending on the download source, we have to decide where to grab the content/data - switch (f.source) - { - case Downloaded_File::DownloadSource::AfterFileShare: - { - PRINT_DEBUG(" source = AfterFileShare '%s'", f.file.c_str()); - read_data = local_storage->get_data(Local_Storage::remote_storage_folder, f.file, (char *)pvData, cubDataToRead, cOffset); - total_size = f.total_size; - } - break; - - case Downloaded_File::DownloadSource::AfterSendQueryUGCRequest: - case Downloaded_File::DownloadSource::FromUGCDownloadToLocation: - { - PRINT_DEBUG(" source = AfterSendQueryUGCRequest || FromUGCDownloadToLocation [%i]", (int)f.source); - auto mod = settings->getMod(f.mod_query_info.mod_id); - auto &mod_name = f.mod_query_info.is_primary_file - ? mod.primaryFileName - : mod.previewFileName; - - std::string mod_fullpath{}; - if (f.source == Downloaded_File::DownloadSource::AfterSendQueryUGCRequest) { - std::string mod_base_path = f.mod_query_info.is_primary_file - ? mod.path - : Local_Storage::get_game_settings_path() + "mod_images" + PATH_SEPARATOR + std::to_string(mod.id); - - mod_fullpath = common_helpers::to_absolute(mod_name, mod_base_path); - } else { // Downloaded_File::DownloadSource::FromUGCDownloadToLocation - mod_fullpath = f.download_to_location_fullpath; - } - - read_data = Local_Storage::get_file_data(mod_fullpath, (char *)pvData, cubDataToRead, cOffset); - PRINT_DEBUG(" mod file '%s' [%i]", mod_fullpath.c_str(), read_data); - total_size = f.total_size; - } - break; - - default: - PRINT_DEBUG(" unhandled download source %i", (int)f.source); - return -1; //TODO: is this the right return value? - break; - } - - PRINT_DEBUG(" read bytes = %i", read_data); - if (read_data < 0) return -1; //TODO: is this the right return value? - - if (eAction == k_EUGCRead_Close || - (eAction == k_EUGCRead_ContinueReadingUntilFinished && (read_data < cubDataToRead || (cOffset + cubDataToRead) >= total_size))) { - downloaded_files.erase(hContent); - } - - return read_data; -} - -int32 UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - - return UGCRead( hContent, pvData, cubDataToRead, 0); -} - -int32 UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead, uint32 cOffset) -{ - PRINT_DEBUG("old2"); - std::lock_guard lock(global_mutex); - - return UGCRead(hContent, pvData, cubDataToRead, cOffset, k_EUGCRead_ContinueReadingUntilFinished); -} - -// Functions to iterate through UGC that has finished downloading but has not yet been read via UGCRead() -int32 GetCachedUGCCount() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return 0; -} - -UGCHandle_t GetCachedUGCHandle( int32 iCachedContent ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return k_UGCHandleInvalid; -} - - -// The following functions are only necessary on the Playstation 3. On PC & Mac, the Steam client will handle these operations for you -// On Playstation 3, the game controls which files are stored in the cloud, via FilePersist, FileFetch, and FileForget. - -#if defined(_PS3) || defined(_SERVER) -// Connect to Steam and get a list of files in the Cloud - results in a RemoteStorageAppSyncStatusCheck_t callback -void GetFileListFromServer() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - -} - -// Indicate this file should be downloaded in the next sync -bool FileFetch( const char *pchFile ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return true; -} - -// Indicate this file should be persisted in the next sync -bool FilePersist( const char *pchFile ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return true; -} - -// Pull any requested files down from the Cloud - results in a RemoteStorageAppSyncedClient_t callback -bool SynchronizeToClient() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - -} - -// Upload any requested files to the Cloud - results in a RemoteStorageAppSyncedServer_t callback -bool SynchronizeToServer() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - -} - -// Reset any fetch/persist/etc requests -bool ResetFileRequestState() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - -} - -#endif - -// publishing UGC -STEAM_CALL_RESULT( RemoteStoragePublishFileProgress_t ) -SteamAPICall_t PublishWorkshopFile( const char *pchFile, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags, EWorkshopFileType eWorkshopFileType ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return k_uAPICallInvalid; -} - -PublishedFileUpdateHandle_t CreatePublishedFileUpdateRequest( PublishedFileId_t unPublishedFileId ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return k_PublishedFileUpdateHandleInvalid; -} - -bool UpdatePublishedFileFile( PublishedFileUpdateHandle_t updateHandle, const char *pchFile ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return false; -} - -SteamAPICall_t PublishFile( const char *pchFile, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return k_uAPICallInvalid; -} - -SteamAPICall_t PublishWorkshopFile( const char *pchFile, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, SteamParamStringArray_t *pTags ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return k_uAPICallInvalid; -} - -SteamAPICall_t UpdatePublishedFile( RemoteStorageUpdatePublishedFileRequest_t updatePublishedFileRequest ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return k_uAPICallInvalid; -} - -bool UpdatePublishedFilePreviewFile( PublishedFileUpdateHandle_t updateHandle, const char *pchPreviewFile ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return false; -} - -bool UpdatePublishedFileTitle( PublishedFileUpdateHandle_t updateHandle, const char *pchTitle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return false; -} - -bool UpdatePublishedFileDescription( PublishedFileUpdateHandle_t updateHandle, const char *pchDescription ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return false; -} - -bool UpdatePublishedFileVisibility( PublishedFileUpdateHandle_t updateHandle, ERemoteStoragePublishedFileVisibility eVisibility ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return false; -} - -bool UpdatePublishedFileTags( PublishedFileUpdateHandle_t updateHandle, SteamParamStringArray_t *pTags ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return false; -} - -STEAM_CALL_RESULT( RemoteStorageUpdatePublishedFileResult_t ) -SteamAPICall_t CommitPublishedFileUpdate( PublishedFileUpdateHandle_t updateHandle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return k_uAPICallInvalid; -} - -// Gets published file details for the given publishedfileid. If unMaxSecondsOld is greater than 0, -// cached data may be returned, depending on how long ago it was cached. A value of 0 will force a refresh. -// A value of k_WorkshopForceLoadPublishedFileDetailsFromCache will use cached data if it exists, no matter how old it is. -STEAM_CALL_RESULT( RemoteStorageGetPublishedFileDetailsResult_t ) -SteamAPICall_t GetPublishedFileDetails( PublishedFileId_t unPublishedFileId, uint32 unMaxSecondsOld ) -{ - PRINT_DEBUG("TODO %llu %u", unPublishedFileId, unMaxSecondsOld); - //TODO: check what this function really returns - // TODO is this implementation correct? - std::lock_guard lock(global_mutex); - if (unPublishedFileId == k_PublishedFileIdInvalid) return k_uAPICallInvalid; - - RemoteStorageGetPublishedFileDetailsResult_t data{}; - data.m_nPublishedFileId = unPublishedFileId; - - if (settings->isModInstalled(unPublishedFileId)) { - auto mod = settings->getMod(unPublishedFileId); - data.m_eResult = EResult::k_EResultOK; - data.m_bAcceptedForUse = mod.acceptedForUse; - data.m_bBanned = mod.banned; - data.m_bTagsTruncated = mod.tagsTruncated; - data.m_eFileType = mod.fileType; - data.m_eVisibility = mod.visibility; - data.m_hFile = mod.handleFile; - data.m_hPreviewFile = mod.handlePreviewFile; - data.m_nConsumerAppID = settings->get_local_game_id().AppID(); // TODO is this correct? - data.m_nCreatorAppID = settings->get_local_game_id().AppID(); // TODO is this correct? - data.m_nFileSize = mod.primaryFileSize; - data.m_nPreviewFileSize = mod.previewFileSize; - data.m_rtimeCreated = mod.timeCreated; - data.m_rtimeUpdated = mod.timeUpdated; - data.m_ulSteamIDOwner = mod.steamIDOwner; - - mod.primaryFileName.copy(data.m_pchFileName, sizeof(data.m_pchFileName) - 1); - mod.description.copy(data.m_rgchDescription, sizeof(data.m_rgchDescription) - 1); - mod.tags.copy(data.m_rgchTags, sizeof(data.m_rgchTags) - 1); - mod.title.copy(data.m_rgchTitle, sizeof(data.m_rgchTitle) - 1); - mod.workshopItemURL.copy(data.m_rgchURL, sizeof(data.m_rgchURL) - 1); - - } else { - data.m_eResult = EResult::k_EResultFail; // TODO is this correct? - } - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); - - // return 0; -/* - std::lock_guard lock(global_mutex); - RemoteStorageGetPublishedFileDetailsResult_t data = {}; - data.m_eResult = k_EResultFail; - data.m_nPublishedFileId = unPublishedFileId; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -*/ -} - -STEAM_CALL_RESULT( RemoteStorageGetPublishedFileDetailsResult_t ) -SteamAPICall_t GetPublishedFileDetails( PublishedFileId_t unPublishedFileId ) -{ - PRINT_DEBUG_TODO(); - return GetPublishedFileDetails(unPublishedFileId, 0); -} - -STEAM_CALL_RESULT( RemoteStorageDeletePublishedFileResult_t ) -SteamAPICall_t DeletePublishedFile( PublishedFileId_t unPublishedFileId ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return k_uAPICallInvalid; -} - -// enumerate the files that the current user published with this app -STEAM_CALL_RESULT( RemoteStorageEnumerateUserPublishedFilesResult_t ) -SteamAPICall_t EnumerateUserPublishedFiles( uint32 unStartIndex ) -{ - PRINT_DEBUG("TODO %u", unStartIndex); - // TODO is this implementation correct? - std::lock_guard lock(global_mutex); - RemoteStorageEnumerateUserPublishedFilesResult_t data{}; - - // collect all published mods by this user - auto mods = settings->modSet(); - std::vector user_pubed{}; - for (auto& id : mods) { - auto mod = settings->getMod(id); - if (mod.steamIDOwner == settings->get_local_steam_id().ConvertToUint64()) { - user_pubed.push_back(id); - } - } - uint32_t modCount = (uint32_t)user_pubed.size(); - - if (unStartIndex >= modCount) { - data.m_eResult = EResult::k_EResultInvalidParam; // TODO is this correct? - } else { - data.m_eResult = EResult::k_EResultOK; - data.m_nTotalResultCount = modCount - unStartIndex; // total count starting from this index - std::vector::iterator i = user_pubed.begin(); - std::advance(i, unStartIndex); - uint32_t iterated = 0; - for (; i != user_pubed.end() && iterated < k_unEnumeratePublishedFilesMaxResults; i++) { - PublishedFileId_t modId = *i; - auto mod = settings->getMod(modId); - data.m_rgPublishedFileId[iterated] = modId; - iterated++; - PRINT_DEBUG(" EnumerateUserPublishedFiles file %llu", modId); - } - data.m_nResultsReturned = iterated; - } - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -STEAM_CALL_RESULT( RemoteStorageSubscribePublishedFileResult_t ) -SteamAPICall_t SubscribePublishedFile( PublishedFileId_t unPublishedFileId ) -{ - PRINT_DEBUG("TODO %llu", unPublishedFileId); - std::lock_guard lock(global_mutex); - if (unPublishedFileId == k_PublishedFileIdInvalid) return k_uAPICallInvalid; - - // TODO is this implementation correct? - RemoteStorageSubscribePublishedFileResult_t data{}; - data.m_nPublishedFileId = unPublishedFileId; - - if (settings->isModInstalled(unPublishedFileId)) { - data.m_eResult = EResult::k_EResultOK; - ugc_bridge->add_subbed_mod(unPublishedFileId); - } else { - data.m_eResult = EResult::k_EResultFail; // TODO is this correct? - } - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -STEAM_CALL_RESULT( RemoteStorageEnumerateUserSubscribedFilesResult_t ) -SteamAPICall_t EnumerateUserSubscribedFiles( uint32 unStartIndex ) -{ - // https://partner.steamgames.com/doc/api/ISteamRemoteStorage - PRINT_DEBUG("%u", unStartIndex); - std::lock_guard lock(global_mutex); - // Get ready for a working but bad implementation - Detanup01 - RemoteStorageEnumerateUserSubscribedFilesResult_t data{}; - uint32_t modCount = (uint32_t)ugc_bridge->subbed_mods_count(); - if (unStartIndex >= modCount) { - data.m_eResult = EResult::k_EResultInvalidParam; // is this correct? - } else { - data.m_eResult = k_EResultOK; - data.m_nTotalResultCount = modCount - unStartIndex; // total amount starting from given index - std::set::iterator i = ugc_bridge->subbed_mods_itr_begin(); - std::advance(i, unStartIndex); - uint32_t iterated = 0; - for (; i != ugc_bridge->subbed_mods_itr_end() && iterated < k_unEnumeratePublishedFilesMaxResults; i++) { - PublishedFileId_t modId = *i; - auto mod = settings->getMod(modId); - uint32 time = mod.timeAddedToUserList; //this can be changed, default is 1554997000 - data.m_rgPublishedFileId[iterated] = modId; - data.m_rgRTimeSubscribed[iterated] = time; - iterated++; - PRINT_DEBUG(" EnumerateUserSubscribedFiles file %llu", modId); - } - data.m_nResultsReturned = iterated; - } - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -STEAM_CALL_RESULT( RemoteStorageUnsubscribePublishedFileResult_t ) -SteamAPICall_t UnsubscribePublishedFile( PublishedFileId_t unPublishedFileId ) -{ - PRINT_DEBUG("TODO %llu", unPublishedFileId); - std::lock_guard lock(global_mutex); - if (unPublishedFileId == k_PublishedFileIdInvalid) return k_uAPICallInvalid; - - // TODO is this implementation correct? - RemoteStorageUnsubscribePublishedFileResult_t data{}; - data.m_nPublishedFileId = unPublishedFileId; - // TODO is this correct? - if (ugc_bridge->has_subbed_mod(unPublishedFileId)) { - data.m_eResult = k_EResultOK; - ugc_bridge->remove_subbed_mod(unPublishedFileId); - } else { - data.m_eResult = k_EResultFail; - } - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -bool UpdatePublishedFileSetChangeDescription( PublishedFileUpdateHandle_t updateHandle, const char *pchChangeDescription ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - -STEAM_CALL_RESULT( RemoteStorageGetPublishedItemVoteDetailsResult_t ) -SteamAPICall_t GetPublishedItemVoteDetails( PublishedFileId_t unPublishedFileId ) -{ - PRINT_DEBUG_TODO(); - // TODO s this implementation correct? - std::lock_guard lock(global_mutex); - if (unPublishedFileId == k_PublishedFileIdInvalid) return k_uAPICallInvalid; - - RemoteStorageGetPublishedItemVoteDetailsResult_t data{}; - data.m_unPublishedFileId = unPublishedFileId; - if (settings->isModInstalled(unPublishedFileId)) { - data.m_eResult = EResult::k_EResultOK; - auto mod = settings->getMod(unPublishedFileId); - data.m_fScore = mod.score; - data.m_nReports = 0; // TODO is this ok? - data.m_nVotesAgainst = mod.votesDown; - data.m_nVotesFor = mod.votesUp; - } else { - data.m_eResult = EResult::k_EResultFail; // TODO is this correct? - } - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -STEAM_CALL_RESULT( RemoteStorageUpdateUserPublishedItemVoteResult_t ) -SteamAPICall_t UpdateUserPublishedItemVote( PublishedFileId_t unPublishedFileId, bool bVoteUp ) -{ - // I assume this function is supposed to increase the upvotes of the mod, - // given that the mod owner is the current user - PRINT_DEBUG_TODO(); - // TODO is this implementation correct? - std::lock_guard lock(global_mutex); - if (unPublishedFileId == k_PublishedFileIdInvalid) return k_uAPICallInvalid; - - RemoteStorageUpdateUserPublishedItemVoteResult_t data{}; - data.m_nPublishedFileId = unPublishedFileId; - if (settings->isModInstalled(unPublishedFileId)) { - auto mod = settings->getMod(unPublishedFileId); - if (mod.steamIDOwner == settings->get_local_steam_id().ConvertToUint64()) { - data.m_eResult = EResult::k_EResultOK; - } else { // not published by this user - data.m_eResult = EResult::k_EResultFail; // TODO is this correct? - } - } else { // mod not installed - data.m_eResult = EResult::k_EResultFail; // TODO is this correct? - } - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -STEAM_CALL_RESULT( RemoteStorageGetPublishedItemVoteDetailsResult_t ) -SteamAPICall_t GetUserPublishedItemVoteDetails( PublishedFileId_t unPublishedFileId ) -{ - PRINT_DEBUG_ENTRY(); - - // TODO is this implementation correct? - std::lock_guard lock(global_mutex); - if (unPublishedFileId == k_PublishedFileIdInvalid) return k_uAPICallInvalid; - - RemoteStorageGetPublishedItemVoteDetailsResult_t data{}; - data.m_unPublishedFileId = unPublishedFileId; - if (settings->isModInstalled(unPublishedFileId)) { - auto mod = settings->getMod(unPublishedFileId); - if (mod.steamIDOwner == settings->get_local_steam_id().ConvertToUint64()) { - data.m_eResult = EResult::k_EResultOK; - data.m_fScore = mod.score; - data.m_nReports = 0; // TODO is this ok? - data.m_nVotesAgainst = mod.votesDown; - data.m_nVotesFor = mod.votesUp; - } else { // not published by this user - data.m_eResult = EResult::k_EResultFail; // TODO is this correct? - } - } else { // mod not installed - data.m_eResult = EResult::k_EResultFail; // TODO is this correct? - } - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); - - return 0; -} - -STEAM_CALL_RESULT( RemoteStorageEnumerateUserPublishedFilesResult_t ) -SteamAPICall_t EnumerateUserSharedWorkshopFiles( CSteamID steamId, uint32 unStartIndex, SteamParamStringArray_t *pRequiredTags, SteamParamStringArray_t *pExcludedTags ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - RemoteStorageEnumerateUserPublishedFilesResult_t data{}; - data.m_eResult = k_EResultOK; - data.m_nResultsReturned = 0; - data.m_nTotalResultCount = 0; - //data.m_rgPublishedFileId; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -STEAM_CALL_RESULT( RemoteStorageEnumerateUserPublishedFilesResult_t ) -SteamAPICall_t EnumerateUserSharedWorkshopFiles(AppId_t nAppId, CSteamID steamId, uint32 unStartIndex, SteamParamStringArray_t *pRequiredTags, SteamParamStringArray_t *pExcludedTags ) -{ - PRINT_DEBUG("old"); - return EnumerateUserSharedWorkshopFiles(steamId, unStartIndex, pRequiredTags, pExcludedTags); -} - -STEAM_CALL_RESULT( RemoteStoragePublishFileProgress_t ) -SteamAPICall_t PublishVideo( EWorkshopVideoProvider eVideoProvider, const char *pchVideoAccount, const char *pchVideoIdentifier, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_uAPICallInvalid; -} - -STEAM_CALL_RESULT( RemoteStoragePublishFileProgress_t ) -SteamAPICall_t PublishVideo(const char *pchFileName, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_uAPICallInvalid; -} - -STEAM_CALL_RESULT( RemoteStorageSetUserPublishedFileActionResult_t ) -SteamAPICall_t SetUserPublishedFileAction( PublishedFileId_t unPublishedFileId, EWorkshopFileAction eAction ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_uAPICallInvalid; -} - -STEAM_CALL_RESULT( RemoteStorageEnumeratePublishedFilesByUserActionResult_t ) -SteamAPICall_t EnumeratePublishedFilesByUserAction( EWorkshopFileAction eAction, uint32 unStartIndex ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_uAPICallInvalid; -} - -// this method enumerates the public view of workshop files -STEAM_CALL_RESULT( RemoteStorageEnumerateWorkshopFilesResult_t ) -SteamAPICall_t EnumeratePublishedWorkshopFiles( EWorkshopEnumerationType eEnumerationType, uint32 unStartIndex, uint32 unCount, uint32 unDays, SteamParamStringArray_t *pTags, SteamParamStringArray_t *pUserTags ) -{ - PRINT_DEBUG_TODO(); - // TODO is this implementation correct? - std::lock_guard lock(global_mutex); - RemoteStorageEnumerateWorkshopFilesResult_t data{}; - data.m_eResult = EResult::k_EResultOK; - data.m_nResultsReturned = 0; - data.m_nTotalResultCount = 0; - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - - -STEAM_CALL_RESULT( RemoteStorageDownloadUGCResult_t ) -SteamAPICall_t UGCDownloadToLocation( UGCHandle_t hContent, const char *pchLocation, uint32 unPriority ) -{ - PRINT_DEBUG("TODO %llu %s", hContent, pchLocation); - // TODO is this implementation correct? - std::lock_guard lock(global_mutex); - //TODO: not sure if this is the right result - if (hContent == k_UGCHandleInvalid || !pchLocation || !pchLocation[0]) return k_uAPICallInvalid; - - RemoteStorageDownloadUGCResult_t data{}; - data.m_hFile = hContent; - data.m_nAppID = settings->get_local_game_id().AppID(); - - auto query_res = ugc_bridge->get_ugc_query_result(hContent); - if (query_res) { - auto mod = settings->getMod(query_res.value().mod_id); - auto &mod_name = query_res.value().is_primary_file - ? mod.primaryFileName - : mod.previewFileName; - std::string mod_base_path = query_res.value().is_primary_file - ? mod.path - : Local_Storage::get_game_settings_path() + "mod_images" + PATH_SEPARATOR + std::to_string(mod.id); - int32 mod_size = query_res.value().is_primary_file - ? mod.primaryFileSize - : mod.previewFileSize; - - data.m_eResult = k_EResultOK; - data.m_nAppID = settings->get_local_game_id().AppID(); - data.m_ulSteamIDOwner = mod.steamIDOwner; - data.m_nSizeInBytes = mod_size; - data.m_ulSteamIDOwner = mod.steamIDOwner; - - mod_name.copy(data.m_pchFileName, sizeof(data.m_pchFileName) - 1); - - // copy the file - const auto mod_fullpath = common_helpers::to_absolute(mod_name, mod_base_path); - copy_file(mod_fullpath, pchLocation); - - // TODO not sure about this though - downloaded_files[hContent].source = Downloaded_File::DownloadSource::FromUGCDownloadToLocation; - downloaded_files[hContent].file = mod_name; - downloaded_files[hContent].total_size = mod_size; - - downloaded_files[hContent].mod_query_info = query_res.value(); - downloaded_files[hContent].download_to_location_fullpath = pchLocation; - - } else { - data.m_eResult = k_EResultFileNotFound; //TODO: not sure if this is the right result - } - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -// Cloud dynamic state change notification -int32 GetLocalFileChangeCount() -{ - PRINT_DEBUG("GetLocalFileChangeCount"); - std::lock_guard lock(global_mutex); - - return 0; -} - -const char *GetLocalFileChange( int iFile, ERemoteStorageLocalFileChange *pEChangeType, ERemoteStorageFilePathType *pEFilePathType ) -{ - PRINT_DEBUG("GetLocalFileChange"); - std::lock_guard lock(global_mutex); - - return ""; -} - -// Indicate to Steam the beginning / end of a set of local file -// operations - for example, writing a game save that requires updating two files. -bool BeginFileWriteBatch() -{ - PRINT_DEBUG("BeginFileWriteBatch"); - std::lock_guard lock(global_mutex); - - return true; -} - -bool EndFileWriteBatch() -{ - PRINT_DEBUG("EndFileWriteBatch"); - std::lock_guard lock(global_mutex); - - return true; -} + #if defined(_PS3) || defined(_SERVER) + // Connect to Steam and get a list of files in the Cloud - results in a RemoteStorageAppSyncStatusCheck_t callback + void GetFileListFromServer(); + + // Indicate this file should be downloaded in the next sync + bool FileFetch( const char *pchFile ); + + // Indicate this file should be persisted in the next sync + bool FilePersist( const char *pchFile ); + + // Pull any requested files down from the Cloud - results in a RemoteStorageAppSyncedClient_t callback + bool SynchronizeToClient(); + + // Upload any requested files to the Cloud - results in a RemoteStorageAppSyncedServer_t callback + bool SynchronizeToServer(); + + // Reset any fetch/persist/etc requests + bool ResetFileRequestState(); + + #endif + + // publishing UGC + STEAM_CALL_RESULT( RemoteStoragePublishFileProgress_t ) + SteamAPICall_t PublishWorkshopFile( const char *pchFile, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags, EWorkshopFileType eWorkshopFileType ); + + PublishedFileUpdateHandle_t CreatePublishedFileUpdateRequest( PublishedFileId_t unPublishedFileId ); + + bool UpdatePublishedFileFile( PublishedFileUpdateHandle_t updateHandle, const char *pchFile ); + + SteamAPICall_t PublishFile( const char *pchFile, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags ); + + SteamAPICall_t PublishWorkshopFile( const char *pchFile, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, SteamParamStringArray_t *pTags ); + + SteamAPICall_t UpdatePublishedFile( RemoteStorageUpdatePublishedFileRequest_t updatePublishedFileRequest ); + + bool UpdatePublishedFilePreviewFile( PublishedFileUpdateHandle_t updateHandle, const char *pchPreviewFile ); + + bool UpdatePublishedFileTitle( PublishedFileUpdateHandle_t updateHandle, const char *pchTitle ); + + bool UpdatePublishedFileDescription( PublishedFileUpdateHandle_t updateHandle, const char *pchDescription ); + + bool UpdatePublishedFileVisibility( PublishedFileUpdateHandle_t updateHandle, ERemoteStoragePublishedFileVisibility eVisibility ); + + bool UpdatePublishedFileTags( PublishedFileUpdateHandle_t updateHandle, SteamParamStringArray_t *pTags ); + + STEAM_CALL_RESULT( RemoteStorageUpdatePublishedFileResult_t ) + SteamAPICall_t CommitPublishedFileUpdate( PublishedFileUpdateHandle_t updateHandle ); + + // Gets published file details for the given publishedfileid. If unMaxSecondsOld is greater than 0, + // cached data may be returned, depending on how long ago it was cached. A value of 0 will force a refresh. + // A value of k_WorkshopForceLoadPublishedFileDetailsFromCache will use cached data if it exists, no matter how old it is. + STEAM_CALL_RESULT( RemoteStorageGetPublishedFileDetailsResult_t ) + SteamAPICall_t GetPublishedFileDetails( PublishedFileId_t unPublishedFileId, uint32 unMaxSecondsOld ); + + STEAM_CALL_RESULT( RemoteStorageGetPublishedFileDetailsResult_t ) + SteamAPICall_t GetPublishedFileDetails( PublishedFileId_t unPublishedFileId ); + + STEAM_CALL_RESULT( RemoteStorageDeletePublishedFileResult_t ) + SteamAPICall_t DeletePublishedFile( PublishedFileId_t unPublishedFileId ); + + // enumerate the files that the current user published with this app + STEAM_CALL_RESULT( RemoteStorageEnumerateUserPublishedFilesResult_t ) + SteamAPICall_t EnumerateUserPublishedFiles( uint32 unStartIndex ); + + STEAM_CALL_RESULT( RemoteStorageSubscribePublishedFileResult_t ) + SteamAPICall_t SubscribePublishedFile( PublishedFileId_t unPublishedFileId ); + + STEAM_CALL_RESULT( RemoteStorageEnumerateUserSubscribedFilesResult_t ) + SteamAPICall_t EnumerateUserSubscribedFiles( uint32 unStartIndex ); + + STEAM_CALL_RESULT( RemoteStorageUnsubscribePublishedFileResult_t ) + SteamAPICall_t UnsubscribePublishedFile( PublishedFileId_t unPublishedFileId ); + + bool UpdatePublishedFileSetChangeDescription( PublishedFileUpdateHandle_t updateHandle, const char *pchChangeDescription ); + + STEAM_CALL_RESULT( RemoteStorageGetPublishedItemVoteDetailsResult_t ) + SteamAPICall_t GetPublishedItemVoteDetails( PublishedFileId_t unPublishedFileId ); + + STEAM_CALL_RESULT( RemoteStorageUpdateUserPublishedItemVoteResult_t ) + SteamAPICall_t UpdateUserPublishedItemVote( PublishedFileId_t unPublishedFileId, bool bVoteUp ); + + STEAM_CALL_RESULT( RemoteStorageGetPublishedItemVoteDetailsResult_t ) + SteamAPICall_t GetUserPublishedItemVoteDetails( PublishedFileId_t unPublishedFileId ); + + STEAM_CALL_RESULT( RemoteStorageEnumerateUserPublishedFilesResult_t ) + SteamAPICall_t EnumerateUserSharedWorkshopFiles( CSteamID steamId, uint32 unStartIndex, SteamParamStringArray_t *pRequiredTags, SteamParamStringArray_t *pExcludedTags ); + + STEAM_CALL_RESULT( RemoteStorageEnumerateUserPublishedFilesResult_t ) + SteamAPICall_t EnumerateUserSharedWorkshopFiles(AppId_t nAppId, CSteamID steamId, uint32 unStartIndex, SteamParamStringArray_t *pRequiredTags, SteamParamStringArray_t *pExcludedTags ); + + STEAM_CALL_RESULT( RemoteStoragePublishFileProgress_t ) + SteamAPICall_t PublishVideo( EWorkshopVideoProvider eVideoProvider, const char *pchVideoAccount, const char *pchVideoIdentifier, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags ); + + STEAM_CALL_RESULT( RemoteStoragePublishFileProgress_t ) + SteamAPICall_t PublishVideo(const char *pchFileName, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags ); + + STEAM_CALL_RESULT( RemoteStorageSetUserPublishedFileActionResult_t ) + SteamAPICall_t SetUserPublishedFileAction( PublishedFileId_t unPublishedFileId, EWorkshopFileAction eAction ); + + STEAM_CALL_RESULT( RemoteStorageEnumeratePublishedFilesByUserActionResult_t ) + SteamAPICall_t EnumeratePublishedFilesByUserAction( EWorkshopFileAction eAction, uint32 unStartIndex ); + + // this method enumerates the public view of workshop files + STEAM_CALL_RESULT( RemoteStorageEnumerateWorkshopFilesResult_t ) + SteamAPICall_t EnumeratePublishedWorkshopFiles( EWorkshopEnumerationType eEnumerationType, uint32 unStartIndex, uint32 unCount, uint32 unDays, SteamParamStringArray_t *pTags, SteamParamStringArray_t *pUserTags ); + + + STEAM_CALL_RESULT( RemoteStorageDownloadUGCResult_t ) + SteamAPICall_t UGCDownloadToLocation( UGCHandle_t hContent, const char *pchLocation, uint32 unPriority ); + + // Cloud dynamic state change notification + int32 GetLocalFileChangeCount(); + + const char *GetLocalFileChange( int iFile, ERemoteStorageLocalFileChange *pEChangeType, ERemoteStorageFilePathType *pEFilePathType ); + + // Indicate to Steam the beginning / end of a set of local file + // operations - for example, writing a game save that requires updating two files. + bool BeginFileWriteBatch(); + + bool EndFileWriteBatch(); }; + +#endif // __INCLUDED_STEAM_REMOTE_STORAGE_H__ diff --git a/dll/dll/steam_remoteplay.h b/dll/dll/steam_remoteplay.h index 74e8e725..71614169 100644 --- a/dll/dll/steam_remoteplay.h +++ b/dll/dll/steam_remoteplay.h @@ -15,140 +15,58 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_REMOTEPLAY_H__ +#define __INCLUDED_STEAM_REMOTEPLAY_H__ + #include "base.h" class Steam_RemotePlay : public ISteamRemotePlay001, public ISteamRemotePlay { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; + + static void steam_callback(void *object, Common_Message *msg); + static void steam_run_every_runcb(void *object); public: -static void steam_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); + Steam_RemotePlay(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_RemotePlay(); - Steam_RemotePlay *steam_remoteplay = (Steam_RemotePlay *)object; - steam_remoteplay->Callback(msg); -} + // Get the number of currently connected Steam Remote Play sessions + uint32 GetSessionCount(); -static void steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); + // Get the currently connected Steam Remote Play session ID at the specified index. Returns zero if index is out of bounds. + uint32 GetSessionID( int iSessionIndex ); - Steam_RemotePlay *steam_remoteplay = (Steam_RemotePlay *)object; - steam_remoteplay->RunCallbacks(); -} + // Get the SteamID of the connected user + CSteamID GetSessionSteamID( uint32 unSessionID ); -Steam_RemotePlay(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->run_every_runcb = run_every_runcb; - //this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_RemotePlay::steam_callback, this); - this->run_every_runcb->add(&Steam_RemotePlay::steam_run_every_runcb, this); + // Get the name of the session client device + // This returns NULL if the sessionID is not valid + const char *GetSessionClientName( uint32 unSessionID ); - this->callback_results = callback_results; - this->callbacks = callbacks; -} + // Get the form factor of the session client device + ESteamDeviceFormFactor GetSessionClientFormFactor( uint32 unSessionID ); -~Steam_RemotePlay() -{ - //this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_RemotePlay::steam_callback, this); - this->run_every_runcb->remove(&Steam_RemotePlay::steam_run_every_runcb, this); -} + // Get the resolution, in pixels, of the session client device + // This is set to 0x0 if the resolution is not available + bool BGetSessionClientResolution( uint32 unSessionID, int *pnResolutionX, int *pnResolutionY ); -// Get the number of currently connected Steam Remote Play sessions -uint32 GetSessionCount() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} + bool BStartRemotePlayTogether( bool bShowOverlay ); -// Get the currently connected Steam Remote Play session ID at the specified index. Returns zero if index is out of bounds. -uint32 GetSessionID( int iSessionIndex ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} + // Invite a friend to Remote Play Together + // This returns false if the invite can't be sent + bool BSendRemotePlayTogetherInvite( CSteamID steamIDFriend ); -// Get the SteamID of the connected user -CSteamID GetSessionSteamID( uint32 unSessionID ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_steamIDNil; -} + void RunCallbacks(); -// Get the name of the session client device -// This returns NULL if the sessionID is not valid -const char *GetSessionClientName( uint32 unSessionID ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return NULL; -} - -// Get the form factor of the session client device -ESteamDeviceFormFactor GetSessionClientFormFactor( uint32 unSessionID ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return k_ESteamDeviceFormFactorUnknown; -} - -// Get the resolution, in pixels, of the session client device -// This is set to 0x0 if the resolution is not available -bool BGetSessionClientResolution( uint32 unSessionID, int *pnResolutionX, int *pnResolutionY ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (pnResolutionX) *pnResolutionX = 0; - if (pnResolutionY) *pnResolutionY = 0; - return false; -} - -bool BStartRemotePlayTogether( bool bShowOverlay ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -// Invite a friend to Remote Play Together -// This returns false if the invite can't be sent -bool BSendRemotePlayTogetherInvite( CSteamID steamIDFriend ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -void RunCallbacks() -{ -} - -void Callback(Common_Message *msg) -{ - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - - } - - if (msg->low_level().type() == Low_Level::DISCONNECT) { - - } - } - - if (msg->has_networking_sockets()) { - - } -} + void Callback(Common_Message *msg); }; + +#endif // __INCLUDED_STEAM_REMOTEPLAY_H__ diff --git a/dll/dll/steam_screenshots.h b/dll/dll/steam_screenshots.h index 745be9f0..5faae8b5 100644 --- a/dll/dll/steam_screenshots.h +++ b/dll/dll/steam_screenshots.h @@ -15,21 +15,24 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_SCRNSHOTS_H__ +#define __INCLUDED_STEAM_SCRNSHOTS_H__ + #include "base.h" -struct screenshot_infos_t -{ - std::string screenshot_name; - nlohmann::json metadatas; +struct screenshot_infos_t { + std::string screenshot_name{}; + nlohmann::json metadatas{}; }; -class Steam_Screenshots : public ISteamScreenshots +class Steam_Screenshots : +public ISteamScreenshots { - bool hooked = false; - std::map _screenshots; + class Local_Storage *local_storage{}; + class SteamCallBacks *callbacks{}; - class Local_Storage* local_storage; - class SteamCallBacks* callbacks; + bool hooked = false; + std::map _screenshots{}; ScreenshotHandle create_screenshot_handle(); @@ -73,3 +76,5 @@ public: // JPEG, TGA, and PNG formats are supported. ScreenshotHandle AddVRScreenshotToLibrary( EVRScreenshotType eType, const char *pchFilename, const char *pchVRFilename ); }; + +#endif //__INCLUDED_STEAM_SCRNSHOTS_H__ diff --git a/dll/dll/steam_tv.h b/dll/dll/steam_tv.h index 485fd7bd..e2bdbda5 100644 --- a/dll/dll/steam_tv.h +++ b/dll/dll/steam_tv.h @@ -15,117 +15,47 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_TV_H__ +#define __INCLUDED_STEAM_TV_H__ + #include "base.h" class Steam_TV : public ISteamTV { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; std::chrono::time_point initialized_time = std::chrono::steady_clock::now(); - FSteamNetworkingSocketsDebugOutput debug_function; + FSteamNetworkingSocketsDebugOutput debug_function{}; + + static void steam_callback(void *object, Common_Message *msg); + static void steam_run_every_runcb(void *object); public: -static void steam_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); + Steam_TV(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_TV(); - Steam_TV *steam_parties = (Steam_TV *)object; - steam_parties->Callback(msg); -} + bool IsBroadcasting(int *pnNumViewers); -static void steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); + void AddBroadcastGameData(const char * pchKey, const char * pchValue); - Steam_TV *steam_parties = (Steam_TV *)object; - steam_parties->RunCallbacks(); -} + void RemoveBroadcastGameData(const char * pchKey); -Steam_TV(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->run_every_runcb = run_every_runcb; - //this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_TV::steam_callback, this); - // this->run_every_runcb->add(&Steam_TV::steam_run_every_runcb, this); + void AddTimelineMarker(const char * pchTemplateName, bool bPersistent, uint8 nColorR, uint8 nColorG, uint8 nColorB); - this->callback_results = callback_results; - this->callbacks = callbacks; -} + void RemoveTimelineMarker(); -~Steam_TV() -{ - //this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_TV::steam_callback, this); - //this->run_every_runcb->remove(&Steam_TV::steam_run_every_runcb, this); -} + uint32 AddRegion(const char * pchElementName, const char * pchTimelineDataSection, const SteamTVRegion_t * pSteamTVRegion, ESteamTVRegionBehavior eSteamTVRegionBehavior); -bool IsBroadcasting(int *pnNumViewers) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} + void RemoveRegion(uint32 unRegionHandle); -void AddBroadcastGameData(const char * pchKey, const char * pchValue) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} + void RunCallbacks(); -void RemoveBroadcastGameData(const char * pchKey) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - -void AddTimelineMarker(const char * pchTemplateName, bool bPersistent, uint8 nColorR, uint8 nColorG, uint8 nColorB) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - -void RemoveTimelineMarker() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - -uint32 AddRegion(const char * pchElementName, const char * pchTimelineDataSection, const SteamTVRegion_t * pSteamTVRegion, ESteamTVRegionBehavior eSteamTVRegionBehavior) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -void RemoveRegion(uint32 unRegionHandle) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); -} - -void RunCallbacks() -{ -} - -void Callback(Common_Message *msg) -{ - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - - } - - if (msg->low_level().type() == Low_Level::DISCONNECT) { - - } - } - - if (msg->has_networking_sockets()) { - - } -} + void Callback(Common_Message *msg); }; + +#endif // __INCLUDED_STEAM_TV_H__ diff --git a/dll/dll/steam_ugc.h b/dll/dll/steam_ugc.h index ec407df3..0b261c17 100644 --- a/dll/dll/steam_ugc.h +++ b/dll/dll/steam_ugc.h @@ -15,14 +15,18 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_UGC_H__ +#define __INCLUDED_STEAM_UGC_H__ + #include "base.h" +#include "ugc_remote_storage_bridge.h" struct UGC_query { - UGCQueryHandle_t handle; - std::set return_only; - bool return_all_subscribed; + UGCQueryHandle_t handle{}; + std::set return_only{}; + bool return_all_subscribed{}; - std::set results; + std::set results{}; }; class Steam_UGC : @@ -46,1424 +50,337 @@ public ISteamUGC { constexpr static const char ugc_favorits_file[] = "favorites.txt"; - class Settings *settings; - class Ugc_Remote_Storage_Bridge *ugc_bridge; - class Local_Storage *local_storage; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; + class Settings *settings{}; + class Ugc_Remote_Storage_Bridge *ugc_bridge{}; + class Local_Storage *local_storage{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; UGCQueryHandle_t handle = 50; // just makes debugging easier, any initial val is fine, even 1 std::vector ugc_queries{}; std::set favorites{}; -UGCQueryHandle_t new_ugc_query( - bool return_all_subscribed = false, - std::set return_only = std::set()) -{ - std::lock_guard lock(global_mutex); - - ++handle; - if ((handle == 0) || (handle == k_UGCQueryHandleInvalid)) handle = 50; + UGCQueryHandle_t new_ugc_query( + bool return_all_subscribed = false, + std::set return_only = std::set()); - struct UGC_query query{}; - query.handle = handle; - query.return_all_subscribed = return_all_subscribed; - query.return_only = return_only; - ugc_queries.push_back(query); - PRINT_DEBUG("handle = %llu", query.handle); - return query.handle; -} + std::optional get_query_ugc(UGCQueryHandle_t handle, uint32 index); -void set_details(PublishedFileId_t id, SteamUGCDetails_t *pDetails) -{ - if (pDetails) { - memset(pDetails, 0, sizeof(SteamUGCDetails_t)); + std::optional get_query_ugc_tag(UGCQueryHandle_t handle, uint32 index, uint32 indexTag); - pDetails->m_nPublishedFileId = id; + std::optional> get_query_ugc_tags(UGCQueryHandle_t handle, uint32 index); - if (settings->isModInstalled(id)) { - PRINT_DEBUG(" mod is installed, setting details"); - pDetails->m_eResult = k_EResultOK; + void set_details(PublishedFileId_t id, SteamUGCDetails_t *pDetails); - auto mod = settings->getMod(id); - pDetails->m_bAcceptedForUse = mod.acceptedForUse; - pDetails->m_bBanned = mod.banned; - pDetails->m_bTagsTruncated = mod.tagsTruncated; - pDetails->m_eFileType = mod.fileType; - pDetails->m_eVisibility = mod.visibility; - pDetails->m_hFile = mod.handleFile; - pDetails->m_hPreviewFile = mod.handlePreviewFile; - pDetails->m_nConsumerAppID = settings->get_local_game_id().AppID(); - pDetails->m_nCreatorAppID = settings->get_local_game_id().AppID(); - pDetails->m_nFileSize = mod.primaryFileSize; - pDetails->m_nPreviewFileSize = mod.previewFileSize; - pDetails->m_rtimeCreated = mod.timeCreated; - pDetails->m_rtimeUpdated = mod.timeUpdated; - pDetails->m_ulSteamIDOwner = settings->get_local_steam_id().ConvertToUint64(); + void read_ugc_favorites(); - pDetails->m_rtimeAddedToUserList = mod.timeAddedToUserList; - pDetails->m_unVotesUp = mod.votesUp; - pDetails->m_unVotesDown = mod.votesDown; - pDetails->m_flScore = mod.score; - - mod.primaryFileName.copy(pDetails->m_pchFileName, sizeof(pDetails->m_pchFileName) - 1); - mod.description.copy(pDetails->m_rgchDescription, sizeof(pDetails->m_rgchDescription) - 1); - mod.tags.copy(pDetails->m_rgchTags, sizeof(pDetails->m_rgchTags) - 1); - mod.title.copy(pDetails->m_rgchTitle, sizeof(pDetails->m_rgchTitle) - 1); - mod.workshopItemURL.copy(pDetails->m_rgchURL, sizeof(pDetails->m_rgchURL) - 1); - - // TODO should we enable this? - // pDetails->m_unNumChildren = mod.numChildren; - } else { - PRINT_DEBUG(" mod isn't installed, returning failure"); - pDetails->m_eResult = k_EResultFail; - } - } -} - -void read_ugc_favorites() -{ - if (!local_storage->file_exists("", ugc_favorits_file)) return; - - unsigned int size = local_storage->file_size("", ugc_favorits_file); - if (!size) return; - - std::string data(size, '\0'); - int read = local_storage->get_data("", ugc_favorits_file, &data[0], (unsigned int)data.size()); - if ((size_t)read != data.size()) return; - - std::stringstream ss(data); - std::string line{}; - while (std::getline(ss, line)) { - try - { - unsigned long long fav_id = std::stoull(line); - favorites.insert(fav_id); - PRINT_DEBUG("added item to favorites %llu", fav_id); - } catch(...) { } - } - -} - -bool write_ugc_favorites() -{ - std::stringstream ss{}; - for (auto id : favorites) { - ss << id << "\n"; - ss.flush(); - } - auto file_data = ss.str(); - int stored = local_storage->store_data("", ugc_favorits_file, &file_data[0], file_data.size()); - return (size_t)stored == file_data.size(); -} + bool write_ugc_favorites(); public: -Steam_UGC(class Settings *settings, class Ugc_Remote_Storage_Bridge *ugc_bridge, class Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks) -{ - this->settings = settings; - this->ugc_bridge = ugc_bridge; - this->local_storage = local_storage; - this->callbacks = callbacks; - this->callback_results = callback_results; - - read_ugc_favorites(); -} - - -// Query UGC associated with a user. Creator app id or consumer app id must be valid and be set to the current running app. unPage should start at 1. -UGCQueryHandle_t CreateQueryUserUGCRequest( AccountID_t unAccountID, EUserUGCList eListType, EUGCMatchingUGCType eMatchingUGCType, EUserUGCListSortOrder eSortOrder, AppId_t nCreatorAppID, AppId_t nConsumerAppID, uint32 unPage ) -{ - PRINT_DEBUG("%u %i %i %i %u %u %u", unAccountID, eListType, eMatchingUGCType, eSortOrder, nCreatorAppID, nConsumerAppID, unPage); - std::lock_guard lock(global_mutex); - - if (nCreatorAppID != settings->get_local_game_id().AppID() || nConsumerAppID != settings->get_local_game_id().AppID()) return k_UGCQueryHandleInvalid; - if (unPage < 1) return k_UGCQueryHandleInvalid; - if (eListType < 0) return k_UGCQueryHandleInvalid; - if (unAccountID != settings->get_local_steam_id().GetAccountID()) return k_UGCQueryHandleInvalid; - - // TODO - return new_ugc_query(eListType == k_EUserUGCList_Subscribed || eListType == k_EUserUGCList_Published); -} - - -// Query for all matching UGC. Creator app id or consumer app id must be valid and be set to the current running app. unPage should start at 1. -UGCQueryHandle_t CreateQueryAllUGCRequest( EUGCQuery eQueryType, EUGCMatchingUGCType eMatchingeMatchingUGCTypeFileType, AppId_t nCreatorAppID, AppId_t nConsumerAppID, uint32 unPage ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - if (nCreatorAppID != settings->get_local_game_id().AppID() || nConsumerAppID != settings->get_local_game_id().AppID()) return k_UGCQueryHandleInvalid; - if (unPage < 1) return k_UGCQueryHandleInvalid; - if (eQueryType < 0) return k_UGCQueryHandleInvalid; - - // TODO - return new_ugc_query(); -} - -// Query for all matching UGC using the new deep paging interface. Creator app id or consumer app id must be valid and be set to the current running app. pchCursor should be set to NULL or "*" to get the first result set. -UGCQueryHandle_t CreateQueryAllUGCRequest( EUGCQuery eQueryType, EUGCMatchingUGCType eMatchingeMatchingUGCTypeFileType, AppId_t nCreatorAppID, AppId_t nConsumerAppID, const char *pchCursor = NULL ) -{ - PRINT_DEBUG("other"); - std::lock_guard lock(global_mutex); - - if (nCreatorAppID != settings->get_local_game_id().AppID() || nConsumerAppID != settings->get_local_game_id().AppID()) return k_UGCQueryHandleInvalid; - if (eQueryType < 0) return k_UGCQueryHandleInvalid; - - // TODO - return new_ugc_query(); -} - -// Query for the details of the given published file ids (the RequestUGCDetails call is deprecated and replaced with this) -UGCQueryHandle_t CreateQueryUGCDetailsRequest( PublishedFileId_t *pvecPublishedFileID, uint32 unNumPublishedFileIDs ) -{ - PRINT_DEBUG("%p, max file IDs = [%u]", pvecPublishedFileID, unNumPublishedFileIDs); - std::lock_guard lock(global_mutex); - - if (!pvecPublishedFileID) return k_UGCQueryHandleInvalid; - if (unNumPublishedFileIDs < 1) return k_UGCQueryHandleInvalid; - - // TODO - std::set only(pvecPublishedFileID, pvecPublishedFileID + unNumPublishedFileIDs); - -#ifndef EMU_RELEASE_BUILD - for (const auto &id : only) { - PRINT_DEBUG(" file ID = %llu", id); - } -#endif - - return new_ugc_query(false, only); -} - - -// Send the query to Steam -STEAM_CALL_RESULT( SteamUGCQueryCompleted_t ) -SteamAPICall_t SendQueryUGCRequest( UGCQueryHandle_t handle ) -{ - PRINT_DEBUG("%llu", handle); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return k_uAPICallInvalid; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) - return k_uAPICallInvalid; - - if (request->return_all_subscribed) { - request->results = std::set(ugc_bridge->subbed_mods_itr_begin(), ugc_bridge->subbed_mods_itr_end()); - } - - if (request->return_only.size()) { - for (auto & s : request->return_only) { - if (ugc_bridge->has_subbed_mod(s)) { - request->results.insert(s); - } - } - } - - // send these handles to steam_remote_storage since the game will later - // call Steam_Remote_Storage::UGCDownload() with these files handles (primary + preview) - for (auto fileid : request->results) { - auto mod = settings->getMod(fileid); - ugc_bridge->add_ugc_query_result(mod.handleFile, fileid, true); - ugc_bridge->add_ugc_query_result(mod.handlePreviewFile, fileid, false); - } - - SteamUGCQueryCompleted_t data = {}; - data.m_handle = handle; - data.m_eResult = k_EResultOK; - data.m_unNumResultsReturned = request->results.size(); - data.m_unTotalMatchingResults = request->results.size(); - data.m_bCachedData = false; - - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return ret; -} - - -// Retrieve an individual result after receiving the callback for querying UGC -bool GetQueryUGCResult( UGCQueryHandle_t handle, uint32 index, SteamUGCDetails_t *pDetails ) -{ - PRINT_DEBUG("%llu %u %p", handle, index, pDetails); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) { - return false; - } - - if (index >= request->results.size()) { - return false; - } - - auto it = request->results.begin(); - std::advance(it, index); - PublishedFileId_t file_id = *it; - set_details(file_id, pDetails); - return true; -} - -std::optional get_query_ugc(UGCQueryHandle_t handle, uint32 index) { - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return std::nullopt; - if (index >= request->results.size()) return std::nullopt; - - auto it = request->results.begin(); - std::advance(it, index); - - PublishedFileId_t file_id = *it; - if (!settings->isModInstalled(file_id)) return std::nullopt; - - return settings->getMod(file_id); -} - -std::optional> get_query_ugc_tags(UGCQueryHandle_t handle, uint32 index) { - auto res = get_query_ugc(handle, index); - if (!res.has_value()) return std::nullopt; - - auto tags_tokens = std::vector{}; - std::stringstream ss(res.value().tags); - std::string tmp{}; - while(ss >> tmp) { - if (tmp.back() == ',') tmp = tmp.substr(0, tmp.size() - 1); - tags_tokens.push_back(tmp); - } - - return tags_tokens; - -} - -std::optional get_query_ugc_tag(UGCQueryHandle_t handle, uint32 index, uint32 indexTag) { - auto res = get_query_ugc_tags(handle, index); - if (!res.has_value()) return std::nullopt; - if (indexTag >= res.value().size()) return std::nullopt; - - std::string tmp = res.value()[indexTag]; - if (tmp.back() == ',') { - tmp = tmp.substr(0, tmp.size() - 1); - } - return tmp; -} - -uint32 GetQueryUGCNumTags( UGCQueryHandle_t handle, uint32 index ) -{ - PRINT_DEBUG_TODO(); - // TODO is this correct? - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return 0; - - auto res = get_query_ugc_tags(handle, index); - return res.has_value() ? res.value().size() : 0; -} - -bool GetQueryUGCTag( UGCQueryHandle_t handle, uint32 index, uint32 indexTag, STEAM_OUT_STRING_COUNT( cchValueSize ) char* pchValue, uint32 cchValueSize ) -{ - PRINT_DEBUG_TODO(); - // TODO is this correct? - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - if (!pchValue || !cchValueSize) return false; - - auto res = get_query_ugc_tag(handle, index, indexTag); - if (!res.has_value()) return false; - - memset(pchValue, 0, cchValueSize); - res.value().copy(pchValue, cchValueSize - 1); - return true; -} - -bool GetQueryUGCTagDisplayName( UGCQueryHandle_t handle, uint32 index, uint32 indexTag, STEAM_OUT_STRING_COUNT( cchValueSize ) char* pchValue, uint32 cchValueSize ) -{ - PRINT_DEBUG_TODO(); - // TODO is this correct? - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - if (!pchValue || !cchValueSize) return false; - - auto res = get_query_ugc_tag(handle, index, indexTag); - if (!res.has_value()) return false; - - memset(pchValue, 0, cchValueSize); - res.value().copy(pchValue, cchValueSize - 1); - return true; -} - -bool GetQueryUGCPreviewURL( UGCQueryHandle_t handle, uint32 index, STEAM_OUT_STRING_COUNT(cchURLSize) char *pchURL, uint32 cchURLSize ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - //TODO: escape simulator tries downloading this url and unsubscribes if it fails - if (handle == k_UGCQueryHandleInvalid) return false; - if (!pchURL || !cchURLSize) return false; - - auto res = get_query_ugc(handle, index); - if (!res.has_value()) return false; - - auto mod = res.value(); - PRINT_DEBUG("Steam_UGC:GetQueryUGCPreviewURL: '%s'", mod.previewURL.c_str()); - mod.previewURL.copy(pchURL, cchURLSize - 1); - return true; -} - - -bool GetQueryUGCMetadata( UGCQueryHandle_t handle, uint32 index, STEAM_OUT_STRING_COUNT(cchMetadatasize) char *pchMetadata, uint32 cchMetadatasize ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return false; -} - - -bool GetQueryUGCChildren( UGCQueryHandle_t handle, uint32 index, PublishedFileId_t* pvecPublishedFileID, uint32 cMaxEntries ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return false; -} - - -bool GetQueryUGCStatistic( UGCQueryHandle_t handle, uint32 index, EItemStatistic eStatType, uint64 *pStatValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return false; -} - -bool GetQueryUGCStatistic( UGCQueryHandle_t handle, uint32 index, EItemStatistic eStatType, uint32 *pStatValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return false; -} - -uint32 GetQueryUGCNumAdditionalPreviews( UGCQueryHandle_t handle, uint32 index ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return 0; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return 0; - - return 0; -} - - -bool GetQueryUGCAdditionalPreview( UGCQueryHandle_t handle, uint32 index, uint32 previewIndex, STEAM_OUT_STRING_COUNT(cchURLSize) char *pchURLOrVideoID, uint32 cchURLSize, STEAM_OUT_STRING_COUNT(cchURLSize) char *pchOriginalFileName, uint32 cchOriginalFileNameSize, EItemPreviewType *pPreviewType ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return false; -} - -bool GetQueryUGCAdditionalPreview( UGCQueryHandle_t handle, uint32 index, uint32 previewIndex, char *pchURLOrVideoID, uint32 cchURLSize, bool *hz ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return false; -} - -uint32 GetQueryUGCNumKeyValueTags( UGCQueryHandle_t handle, uint32 index ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return 0; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return 0; - - return 0; -} - - -bool GetQueryUGCKeyValueTag( UGCQueryHandle_t handle, uint32 index, uint32 keyValueTagIndex, STEAM_OUT_STRING_COUNT(cchKeySize) char *pchKey, uint32 cchKeySize, STEAM_OUT_STRING_COUNT(cchValueSize) char *pchValue, uint32 cchValueSize ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return false; -} - -bool GetQueryUGCKeyValueTag( UGCQueryHandle_t handle, uint32 index, const char *pchKey, STEAM_OUT_STRING_COUNT(cchValueSize) char *pchValue, uint32 cchValueSize ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return false; -} - -uint32 GetQueryUGCContentDescriptors( UGCQueryHandle_t handle, uint32 index, EUGCContentDescriptorID *pvecDescriptors, uint32 cMaxEntries ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return 0; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return 0; - - return 0; -} - -// Release the request to free up memory, after retrieving results -bool ReleaseQueryUGCRequest( UGCQueryHandle_t handle ) -{ - PRINT_DEBUG("%llu", handle); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - ugc_queries.erase(request); - return true; -} - - -// Options to set for querying UGC -bool AddRequiredTag( UGCQueryHandle_t handle, const char *pTagName ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - -bool AddRequiredTagGroup( UGCQueryHandle_t handle, const SteamParamStringArray_t *pTagGroups ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - -bool AddExcludedTag( UGCQueryHandle_t handle, const char *pTagName ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -bool SetReturnOnlyIDs( UGCQueryHandle_t handle, bool bReturnOnlyIDs ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -bool SetReturnKeyValueTags( UGCQueryHandle_t handle, bool bReturnKeyValueTags ) -{ - PRINT_DEBUG_TODO(); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -bool SetReturnLongDescription( UGCQueryHandle_t handle, bool bReturnLongDescription ) -{ - PRINT_DEBUG_TODO(); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -bool SetReturnMetadata( UGCQueryHandle_t handle, bool bReturnMetadata ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -bool SetReturnChildren( UGCQueryHandle_t handle, bool bReturnChildren ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -bool SetReturnAdditionalPreviews( UGCQueryHandle_t handle, bool bReturnAdditionalPreviews ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -bool SetReturnTotalOnly( UGCQueryHandle_t handle, bool bReturnTotalOnly ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -bool SetReturnPlaytimeStats( UGCQueryHandle_t handle, uint32 unDays ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -bool SetLanguage( UGCQueryHandle_t handle, const char *pchLanguage ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -bool SetAllowCachedResponse( UGCQueryHandle_t handle, uint32 unMaxAgeSeconds ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -// Options only for querying user UGC -bool SetCloudFileNameFilter( UGCQueryHandle_t handle, const char *pMatchCloudFileName ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -// Options only for querying all UGC -bool SetMatchAnyTag( UGCQueryHandle_t handle, bool bMatchAnyTag ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -bool SetSearchText( UGCQueryHandle_t handle, const char *pSearchText ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -bool SetRankedByTrendDays( UGCQueryHandle_t handle, uint32 unDays ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - - -bool AddRequiredKeyValueTag( UGCQueryHandle_t handle, const char *pKey, const char *pValue ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - -bool SetTimeCreatedDateRange( UGCQueryHandle_t handle, RTime32 rtStart, RTime32 rtEnd ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - -bool SetTimeUpdatedDateRange( UGCQueryHandle_t handle, RTime32 rtStart, RTime32 rtEnd ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (handle == k_UGCQueryHandleInvalid) return false; - - auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); - if (ugc_queries.end() == request) return false; - - return true; -} - -// DEPRECATED - Use CreateQueryUGCDetailsRequest call above instead! -SteamAPICall_t RequestUGCDetails( PublishedFileId_t nPublishedFileID, uint32 unMaxAgeSeconds ) -{ - PRINT_DEBUG("%llu", nPublishedFileID); - std::lock_guard lock(global_mutex); - - SteamUGCRequestUGCDetailsResult_t data{}; - data.m_bCachedData = false; - set_details(nPublishedFileID, &(data.m_details)); - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -SteamAPICall_t RequestUGCDetails( PublishedFileId_t nPublishedFileID ) -{ - PRINT_DEBUG("old"); - return RequestUGCDetails(nPublishedFileID, 0); -} - - -// Steam Workshop Creator API -STEAM_CALL_RESULT( CreateItemResult_t ) -SteamAPICall_t CreateItem( AppId_t nConsumerAppId, EWorkshopFileType eFileType ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return k_uAPICallInvalid; -} - // create new item for this app with no content attached yet - - -UGCUpdateHandle_t StartItemUpdate( AppId_t nConsumerAppId, PublishedFileId_t nPublishedFileID ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return k_UGCUpdateHandleInvalid; -} - // start an UGC item update. Set changed properties before commiting update with CommitItemUpdate() - - -bool SetItemTitle( UGCUpdateHandle_t handle, const char *pchTitle ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // change the title of an UGC item - - -bool SetItemDescription( UGCUpdateHandle_t handle, const char *pchDescription ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // change the description of an UGC item - - -bool SetItemUpdateLanguage( UGCUpdateHandle_t handle, const char *pchLanguage ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // specify the language of the title or description that will be set - - -bool SetItemMetadata( UGCUpdateHandle_t handle, const char *pchMetaData ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // change the metadata of an UGC item (max = k_cchDeveloperMetadataMax) - - -bool SetItemVisibility( UGCUpdateHandle_t handle, ERemoteStoragePublishedFileVisibility eVisibility ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // change the visibility of an UGC item - - -bool SetItemTags( UGCUpdateHandle_t updateHandle, const SteamParamStringArray_t *pTags ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - - return false; -} - -bool SetItemTags( UGCUpdateHandle_t updateHandle, const SteamParamStringArray_t *pTags, bool bAllowAdminTags ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // change the tags of an UGC item - -bool SetItemContent( UGCUpdateHandle_t handle, const char *pszContentFolder ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // update item content from this local folder - - -bool SetItemPreview( UGCUpdateHandle_t handle, const char *pszPreviewFile ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // change preview image file for this item. pszPreviewFile points to local image file, which must be under 1MB in size - -bool SetAllowLegacyUpload( UGCUpdateHandle_t handle, bool bAllowLegacyUpload ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - -bool RemoveAllItemKeyValueTags( UGCUpdateHandle_t handle ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // remove all existing key-value tags (you can add new ones via the AddItemKeyValueTag function) - -bool RemoveItemKeyValueTags( UGCUpdateHandle_t handle, const char *pchKey ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // remove any existing key-value tags with the specified key - - -bool AddItemKeyValueTag( UGCUpdateHandle_t handle, const char *pchKey, const char *pchValue ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // add new key-value tags for the item. Note that there can be multiple values for a tag. - - -bool AddItemPreviewFile( UGCUpdateHandle_t handle, const char *pszPreviewFile, EItemPreviewType type ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // add preview file for this item. pszPreviewFile points to local file, which must be under 1MB in size - - -bool AddItemPreviewVideo( UGCUpdateHandle_t handle, const char *pszVideoID ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // add preview video for this item - - -bool UpdateItemPreviewFile( UGCUpdateHandle_t handle, uint32 index, const char *pszPreviewFile ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // updates an existing preview file for this item. pszPreviewFile points to local file, which must be under 1MB in size - - -bool UpdateItemPreviewVideo( UGCUpdateHandle_t handle, uint32 index, const char *pszVideoID ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - // updates an existing preview video for this item - - -bool RemoveItemPreview( UGCUpdateHandle_t handle, uint32 index ) -{ - PRINT_DEBUG("%llu %u", handle, index); - std::lock_guard lock(global_mutex); - - return false; -} - // remove a preview by index starting at 0 (previews are sorted) - -bool AddContentDescriptor( UGCUpdateHandle_t handle, EUGCContentDescriptorID descid ) -{ - PRINT_DEBUG("%llu %u", handle, descid); - std::lock_guard lock(global_mutex); - - return false; -} - -bool RemoveContentDescriptor( UGCUpdateHandle_t handle, EUGCContentDescriptorID descid ) -{ - PRINT_DEBUG("%llu %u", handle, descid); - std::lock_guard lock(global_mutex); - - return false; -} - -STEAM_CALL_RESULT( SubmitItemUpdateResult_t ) -SteamAPICall_t SubmitItemUpdate( UGCUpdateHandle_t handle, const char *pchChangeNote ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return k_uAPICallInvalid; -} - // commit update process started with StartItemUpdate() - - -EItemUpdateStatus GetItemUpdateProgress( UGCUpdateHandle_t handle, uint64 *punBytesProcessed, uint64* punBytesTotal ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return k_EItemUpdateStatusInvalid; -} - - -// Steam Workshop Consumer API - -STEAM_CALL_RESULT( SetUserItemVoteResult_t ) -SteamAPICall_t SetUserItemVote( PublishedFileId_t nPublishedFileID, bool bVoteUp ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!settings->isModInstalled(nPublishedFileID)) return k_uAPICallInvalid; // TODO is this correct - - auto mod = settings->getMod(nPublishedFileID); - SetUserItemVoteResult_t data{}; - data.m_eResult = EResult::k_EResultOK; - data.m_nPublishedFileId = nPublishedFileID; - if (bVoteUp) { - ++mod.votesUp; - } else { - ++mod.votesDown; - } - settings->addModDetails(nPublishedFileID, mod); - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - - -STEAM_CALL_RESULT( GetUserItemVoteResult_t ) -SteamAPICall_t GetUserItemVote( PublishedFileId_t nPublishedFileID ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (nPublishedFileID == k_PublishedFileIdInvalid || !settings->isModInstalled(nPublishedFileID)) return k_uAPICallInvalid; // TODO is this correct - - auto mod = settings->getMod(nPublishedFileID); - GetUserItemVoteResult_t data{}; - data.m_eResult = EResult::k_EResultOK; - data.m_nPublishedFileId = nPublishedFileID; - data.m_bVotedDown = mod.votesDown; - data.m_bVotedUp = mod.votesUp; - data.m_bVoteSkipped = true; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - - -STEAM_CALL_RESULT( UserFavoriteItemsListChanged_t ) -SteamAPICall_t AddItemToFavorites( AppId_t nAppId, PublishedFileId_t nPublishedFileID ) -{ - PRINT_DEBUG("%u %llu", nAppId, nPublishedFileID); - std::lock_guard lock(global_mutex); - if (nAppId == k_uAppIdInvalid || nAppId != settings->get_local_game_id().AppID()) return k_uAPICallInvalid; // TODO is this correct - if (nPublishedFileID == k_PublishedFileIdInvalid || !settings->isModInstalled(nPublishedFileID)) return k_uAPICallInvalid; // TODO is this correct - - UserFavoriteItemsListChanged_t data{}; - data.m_nPublishedFileId = nPublishedFileID; - data.m_bWasAddRequest = true; - - auto add = favorites.insert(nPublishedFileID); - if (add.second) { // if new insertion - PRINT_DEBUG(" adding new item to favorites"); - bool ok = write_ugc_favorites(); - data.m_eResult = ok ? EResult::k_EResultOK : EResult::k_EResultFail; - } else { // nPublishedFileID already exists - data.m_eResult = EResult::k_EResultOK; - } - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - - -STEAM_CALL_RESULT( UserFavoriteItemsListChanged_t ) -SteamAPICall_t RemoveItemFromFavorites( AppId_t nAppId, PublishedFileId_t nPublishedFileID ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (nAppId == k_uAppIdInvalid || nAppId != settings->get_local_game_id().AppID()) return k_uAPICallInvalid; // TODO is this correct - if (nPublishedFileID == k_PublishedFileIdInvalid || !settings->isModInstalled(nPublishedFileID)) return k_uAPICallInvalid; // TODO is this correct - - UserFavoriteItemsListChanged_t data{}; - data.m_nPublishedFileId = nPublishedFileID; - data.m_bWasAddRequest = false; - - auto removed = favorites.erase(nPublishedFileID); - if (removed) { - PRINT_DEBUG(" removing item from favorites"); - bool ok = write_ugc_favorites(); - data.m_eResult = ok ? EResult::k_EResultOK : EResult::k_EResultFail; - } else { // nPublishedFileID didn't exist - data.m_eResult = EResult::k_EResultOK; - } - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - - -STEAM_CALL_RESULT( RemoteStorageSubscribePublishedFileResult_t ) -SteamAPICall_t SubscribeItem( PublishedFileId_t nPublishedFileID ) -{ - PRINT_DEBUG("%llu", nPublishedFileID); - std::lock_guard lock(global_mutex); - - RemoteStorageSubscribePublishedFileResult_t data; - data.m_nPublishedFileId = nPublishedFileID; - if (settings->isModInstalled(nPublishedFileID)) { - data.m_eResult = k_EResultOK; - ugc_bridge->add_subbed_mod(nPublishedFileID); - } else { - data.m_eResult = k_EResultFail; - } - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - // subscribe to this item, will be installed ASAP - -STEAM_CALL_RESULT( RemoteStorageUnsubscribePublishedFileResult_t ) -SteamAPICall_t UnsubscribeItem( PublishedFileId_t nPublishedFileID ) -{ - PRINT_DEBUG("%llu", nPublishedFileID); - std::lock_guard lock(global_mutex); - - RemoteStorageUnsubscribePublishedFileResult_t data; - data.m_nPublishedFileId = nPublishedFileID; - if (!ugc_bridge->has_subbed_mod(nPublishedFileID)) { - data.m_eResult = k_EResultFail; //TODO: check if this is accurate - } else { - data.m_eResult = k_EResultOK; - ugc_bridge->remove_subbed_mod(nPublishedFileID); - } - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - // unsubscribe from this item, will be uninstalled after game quits - -uint32 GetNumSubscribedItems() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - PRINT_DEBUG(" Steam_UGC::GetNumSubscribedItems = %zu", ugc_bridge->subbed_mods_count()); - return (uint32)ugc_bridge->subbed_mods_count(); -} - // number of subscribed items - -uint32 GetSubscribedItems( PublishedFileId_t* pvecPublishedFileID, uint32 cMaxEntries ) -{ - PRINT_DEBUG("%p %u", pvecPublishedFileID, cMaxEntries); - std::lock_guard lock(global_mutex); - if ((size_t)cMaxEntries > ugc_bridge->subbed_mods_count()) { - cMaxEntries = (uint32)ugc_bridge->subbed_mods_count(); - } - - std::copy_n(ugc_bridge->subbed_mods_itr_begin(), cMaxEntries, pvecPublishedFileID); - return cMaxEntries; -} - // all subscribed item PublishFileIDs - -// get EItemState flags about item on this client -uint32 GetItemState( PublishedFileId_t nPublishedFileID ) -{ - PRINT_DEBUG("%llu", nPublishedFileID); - std::lock_guard lock(global_mutex); - if (ugc_bridge->has_subbed_mod(nPublishedFileID)) { - if (settings->isModInstalled(nPublishedFileID)) { - PRINT_DEBUG(" mod is subscribed and installed"); - return k_EItemStateInstalled | k_EItemStateSubscribed; - } - - PRINT_DEBUG(" mod is subscribed"); - return k_EItemStateSubscribed; - } - - PRINT_DEBUG(" mod isn't found"); - return k_EItemStateNone; -} - - -// get info about currently installed content on disc for items that have k_EItemStateInstalled set -// if k_EItemStateLegacyItem is set, pchFolder contains the path to the legacy file itself (not a folder) -bool GetItemInstallInfo( PublishedFileId_t nPublishedFileID, uint64 *punSizeOnDisk, STEAM_OUT_STRING_COUNT( cchFolderSize ) char *pchFolder, uint32 cchFolderSize, uint32 *punTimeStamp ) -{ - PRINT_DEBUG("%llu %p %p [%u] %p", nPublishedFileID, punSizeOnDisk, pchFolder, cchFolderSize, punTimeStamp); - std::lock_guard lock(global_mutex); - if (!cchFolderSize) return false; - if (!settings->isModInstalled(nPublishedFileID)) return false; - - auto mod = settings->getMod(nPublishedFileID); - - // I don't know if this is accurate behavior, but to avoid returning true with invalid data - if ((cchFolderSize - 1) < mod.path.size()) { // -1 because the last char is reserved for null terminator - PRINT_DEBUG(" ERROR mod path: '%s' [%zu bytes] cannot fit into the given buffer", mod.path.c_str(), mod.path.size()); - return false; - } - - if (punSizeOnDisk) *punSizeOnDisk = mod.primaryFileSize; - if (punTimeStamp) *punTimeStamp = mod.timeUpdated; - if (pchFolder && cchFolderSize) { - // human fall flat doesn't send a nulled buffer, and won't recognize the proper mod path because of that - memset(pchFolder, 0, cchFolderSize); - mod.path.copy(pchFolder, cchFolderSize - 1); - PRINT_DEBUG(" copied mod path: '%s'", pchFolder); - } - - return true; -} - - -// get info about pending update for items that have k_EItemStateNeedsUpdate set. punBytesTotal will be valid after download started once -bool GetItemDownloadInfo( PublishedFileId_t nPublishedFileID, uint64 *punBytesDownloaded, uint64 *punBytesTotal ) -{ - PRINT_DEBUG("%llu", nPublishedFileID); - std::lock_guard lock(global_mutex); - if (!settings->isModInstalled(nPublishedFileID)) return false; - - auto mod = settings->getMod(nPublishedFileID); - if (punBytesDownloaded) *punBytesDownloaded = mod.primaryFileSize; - if (punBytesTotal) *punBytesTotal = mod.primaryFileSize; - return true; -} - -bool GetItemInstallInfo( PublishedFileId_t nPublishedFileID, uint64 *punSizeOnDisk, STEAM_OUT_STRING_COUNT( cchFolderSize ) char *pchFolder, uint32 cchFolderSize, bool *pbLegacyItem ) // returns true if item is installed -{ - PRINT_DEBUG("old"); - return GetItemInstallInfo(nPublishedFileID, punSizeOnDisk, pchFolder, cchFolderSize, (uint32*) nullptr); -} - -bool GetItemUpdateInfo( PublishedFileId_t nPublishedFileID, bool *pbNeedsUpdate, bool *pbIsDownloading, uint64 *punBytesDownloaded, uint64 *punBytesTotal ) -{ - PRINT_DEBUG("old"); - std::lock_guard lock(global_mutex); - bool res = GetItemDownloadInfo(nPublishedFileID, punBytesDownloaded, punBytesTotal); - if (res) { - if (pbNeedsUpdate) *pbNeedsUpdate = false; - if (pbIsDownloading) *pbIsDownloading = false; - } - return res; -} - -bool GetItemInstallInfo( PublishedFileId_t nPublishedFileID, uint64 *punSizeOnDisk, char *pchFolder, uint32 cchFolderSize ) // returns true if item is installed -{ - PRINT_DEBUG("older"); - return GetItemInstallInfo(nPublishedFileID, punSizeOnDisk, pchFolder, cchFolderSize, (uint32*) nullptr); -} - - -// download new or update already installed item. If function returns true, wait for DownloadItemResult_t. If the item is already installed, -// then files on disk should not be used until callback received. If item is not subscribed to, it will be cached for some time. -// If bHighPriority is set, any other item download will be suspended and this item downloaded ASAP. -bool DownloadItem( PublishedFileId_t nPublishedFileID, bool bHighPriority ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - - -// game servers can set a specific workshop folder before issuing any UGC commands. -// This is helpful if you want to support multiple game servers running out of the same install folder -bool BInitWorkshopForGameServer( DepotId_t unWorkshopDepotID, const char *pszFolder ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - return false; -} - - -// SuspendDownloads( true ) will suspend all workshop downloads until SuspendDownloads( false ) is called or the game ends -void SuspendDownloads( bool bSuspend ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - -} - - -// usage tracking -STEAM_CALL_RESULT( StartPlaytimeTrackingResult_t ) -SteamAPICall_t StartPlaytimeTracking( PublishedFileId_t *pvecPublishedFileID, uint32 unNumPublishedFileIDs ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - StopPlaytimeTrackingResult_t data; - data.m_eResult = k_EResultOK; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -STEAM_CALL_RESULT( StopPlaytimeTrackingResult_t ) -SteamAPICall_t StopPlaytimeTracking( PublishedFileId_t *pvecPublishedFileID, uint32 unNumPublishedFileIDs ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - StopPlaytimeTrackingResult_t data; - data.m_eResult = k_EResultOK; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -STEAM_CALL_RESULT( StopPlaytimeTrackingResult_t ) -SteamAPICall_t StopPlaytimeTrackingForAllItems() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - StopPlaytimeTrackingResult_t data; - data.m_eResult = k_EResultOK; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - - -// parent-child relationship or dependency management -STEAM_CALL_RESULT( AddUGCDependencyResult_t ) -SteamAPICall_t AddDependency( PublishedFileId_t nParentPublishedFileID, PublishedFileId_t nChildPublishedFileID ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - if (nParentPublishedFileID == k_PublishedFileIdInvalid) return k_uAPICallInvalid; - - return k_uAPICallInvalid; -} - -STEAM_CALL_RESULT( RemoveUGCDependencyResult_t ) -SteamAPICall_t RemoveDependency( PublishedFileId_t nParentPublishedFileID, PublishedFileId_t nChildPublishedFileID ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - if (nParentPublishedFileID == k_PublishedFileIdInvalid) return k_uAPICallInvalid; - - return k_uAPICallInvalid; -} - - -// add/remove app dependence/requirements (usually DLC) -STEAM_CALL_RESULT( AddAppDependencyResult_t ) -SteamAPICall_t AddAppDependency( PublishedFileId_t nPublishedFileID, AppId_t nAppID ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - if (nPublishedFileID == k_PublishedFileIdInvalid) return k_uAPICallInvalid; - - return k_uAPICallInvalid; -} - -STEAM_CALL_RESULT( RemoveAppDependencyResult_t ) -SteamAPICall_t RemoveAppDependency( PublishedFileId_t nPublishedFileID, AppId_t nAppID ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - if (nPublishedFileID == k_PublishedFileIdInvalid) return k_uAPICallInvalid; - - return k_uAPICallInvalid; -} - -// request app dependencies. note that whatever callback you register for GetAppDependenciesResult_t may be called multiple times -// until all app dependencies have been returned -STEAM_CALL_RESULT( GetAppDependenciesResult_t ) -SteamAPICall_t GetAppDependencies( PublishedFileId_t nPublishedFileID ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - if (nPublishedFileID == k_PublishedFileIdInvalid) return k_uAPICallInvalid; - - return k_uAPICallInvalid; -} - - -// delete the item without prompting the user -STEAM_CALL_RESULT( DeleteItemResult_t ) -SteamAPICall_t DeleteItem( PublishedFileId_t nPublishedFileID ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - if (nPublishedFileID == k_PublishedFileIdInvalid) return k_uAPICallInvalid; - - return k_uAPICallInvalid; -} - -// Show the app's latest Workshop EULA to the user in an overlay window, where they can accept it or not -bool ShowWorkshopEULA() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return false; -} - -// Retrieve information related to the user's acceptance or not of the app's specific Workshop EULA -STEAM_CALL_RESULT( WorkshopEULAStatus_t ) -SteamAPICall_t GetWorkshopEULAStatus() -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return k_uAPICallInvalid; -} - -// Return the user's community content descriptor preferences -uint32 GetUserContentDescriptorPreferences( EUGCContentDescriptorID *pvecDescriptors, uint32 cMaxEntries ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return 0; -} + Steam_UGC(class Settings *settings, class Ugc_Remote_Storage_Bridge *ugc_bridge, class Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks); + + + // Query UGC associated with a user. Creator app id or consumer app id must be valid and be set to the current running app. unPage should start at 1. + UGCQueryHandle_t CreateQueryUserUGCRequest( AccountID_t unAccountID, EUserUGCList eListType, EUGCMatchingUGCType eMatchingUGCType, EUserUGCListSortOrder eSortOrder, AppId_t nCreatorAppID, AppId_t nConsumerAppID, uint32 unPage ); + + + // Query for all matching UGC. Creator app id or consumer app id must be valid and be set to the current running app. unPage should start at 1. + UGCQueryHandle_t CreateQueryAllUGCRequest( EUGCQuery eQueryType, EUGCMatchingUGCType eMatchingeMatchingUGCTypeFileType, AppId_t nCreatorAppID, AppId_t nConsumerAppID, uint32 unPage ); + + // Query for all matching UGC using the new deep paging interface. Creator app id or consumer app id must be valid and be set to the current running app. pchCursor should be set to NULL or "*" to get the first result set. + UGCQueryHandle_t CreateQueryAllUGCRequest( EUGCQuery eQueryType, EUGCMatchingUGCType eMatchingeMatchingUGCTypeFileType, AppId_t nCreatorAppID, AppId_t nConsumerAppID, const char *pchCursor = NULL ); + + // Query for the details of the given published file ids (the RequestUGCDetails call is deprecated and replaced with this) + UGCQueryHandle_t CreateQueryUGCDetailsRequest( PublishedFileId_t *pvecPublishedFileID, uint32 unNumPublishedFileIDs ); + + + // Send the query to Steam + STEAM_CALL_RESULT( SteamUGCQueryCompleted_t ) + SteamAPICall_t SendQueryUGCRequest( UGCQueryHandle_t handle ); + + + // Retrieve an individual result after receiving the callback for querying UGC + bool GetQueryUGCResult( UGCQueryHandle_t handle, uint32 index, SteamUGCDetails_t *pDetails ); + + uint32 GetQueryUGCNumTags( UGCQueryHandle_t handle, uint32 index ); + + bool GetQueryUGCTag( UGCQueryHandle_t handle, uint32 index, uint32 indexTag, STEAM_OUT_STRING_COUNT( cchValueSize ) char* pchValue, uint32 cchValueSize ); + + bool GetQueryUGCTagDisplayName( UGCQueryHandle_t handle, uint32 index, uint32 indexTag, STEAM_OUT_STRING_COUNT( cchValueSize ) char* pchValue, uint32 cchValueSize ); + + bool GetQueryUGCPreviewURL( UGCQueryHandle_t handle, uint32 index, STEAM_OUT_STRING_COUNT(cchURLSize) char *pchURL, uint32 cchURLSize ); + + + bool GetQueryUGCMetadata( UGCQueryHandle_t handle, uint32 index, STEAM_OUT_STRING_COUNT(cchMetadatasize) char *pchMetadata, uint32 cchMetadatasize ); + + bool GetQueryUGCChildren( UGCQueryHandle_t handle, uint32 index, PublishedFileId_t* pvecPublishedFileID, uint32 cMaxEntries ); + + + bool GetQueryUGCStatistic( UGCQueryHandle_t handle, uint32 index, EItemStatistic eStatType, uint64 *pStatValue ); + + bool GetQueryUGCStatistic( UGCQueryHandle_t handle, uint32 index, EItemStatistic eStatType, uint32 *pStatValue ); + + uint32 GetQueryUGCNumAdditionalPreviews( UGCQueryHandle_t handle, uint32 index ); + + + bool GetQueryUGCAdditionalPreview( UGCQueryHandle_t handle, uint32 index, uint32 previewIndex, STEAM_OUT_STRING_COUNT(cchURLSize) char *pchURLOrVideoID, uint32 cchURLSize, STEAM_OUT_STRING_COUNT(cchURLSize) char *pchOriginalFileName, uint32 cchOriginalFileNameSize, EItemPreviewType *pPreviewType ); + + bool GetQueryUGCAdditionalPreview( UGCQueryHandle_t handle, uint32 index, uint32 previewIndex, char *pchURLOrVideoID, uint32 cchURLSize, bool *hz ); + + uint32 GetQueryUGCNumKeyValueTags( UGCQueryHandle_t handle, uint32 index ); + + + bool GetQueryUGCKeyValueTag( UGCQueryHandle_t handle, uint32 index, uint32 keyValueTagIndex, STEAM_OUT_STRING_COUNT(cchKeySize) char *pchKey, uint32 cchKeySize, STEAM_OUT_STRING_COUNT(cchValueSize) char *pchValue, uint32 cchValueSize ); + + bool GetQueryUGCKeyValueTag( UGCQueryHandle_t handle, uint32 index, const char *pchKey, STEAM_OUT_STRING_COUNT(cchValueSize) char *pchValue, uint32 cchValueSize ); + + uint32 GetQueryUGCContentDescriptors( UGCQueryHandle_t handle, uint32 index, EUGCContentDescriptorID *pvecDescriptors, uint32 cMaxEntries ); + + // Release the request to free up memory, after retrieving results + bool ReleaseQueryUGCRequest( UGCQueryHandle_t handle ); + + + // Options to set for querying UGC + bool AddRequiredTag( UGCQueryHandle_t handle, const char *pTagName ); + + bool AddRequiredTagGroup( UGCQueryHandle_t handle, const SteamParamStringArray_t *pTagGroups ); + + bool AddExcludedTag( UGCQueryHandle_t handle, const char *pTagName ); + + bool SetReturnOnlyIDs( UGCQueryHandle_t handle, bool bReturnOnlyIDs ); + + bool SetReturnKeyValueTags( UGCQueryHandle_t handle, bool bReturnKeyValueTags ); + + bool SetReturnLongDescription( UGCQueryHandle_t handle, bool bReturnLongDescription ); + + bool SetReturnMetadata( UGCQueryHandle_t handle, bool bReturnMetadata ); + + bool SetReturnChildren( UGCQueryHandle_t handle, bool bReturnChildren ); + + bool SetReturnAdditionalPreviews( UGCQueryHandle_t handle, bool bReturnAdditionalPreviews ); + + bool SetReturnTotalOnly( UGCQueryHandle_t handle, bool bReturnTotalOnly ); + + bool SetReturnPlaytimeStats( UGCQueryHandle_t handle, uint32 unDays ); + + bool SetLanguage( UGCQueryHandle_t handle, const char *pchLanguage ); + + bool SetAllowCachedResponse( UGCQueryHandle_t handle, uint32 unMaxAgeSeconds ); + + // Options only for querying user UGC + bool SetCloudFileNameFilter( UGCQueryHandle_t handle, const char *pMatchCloudFileName ); + + // Options only for querying all UGC + bool SetMatchAnyTag( UGCQueryHandle_t handle, bool bMatchAnyTag ); + + bool SetSearchText( UGCQueryHandle_t handle, const char *pSearchText ); + + bool SetRankedByTrendDays( UGCQueryHandle_t handle, uint32 unDays ); + + bool AddRequiredKeyValueTag( UGCQueryHandle_t handle, const char *pKey, const char *pValue ); + + bool SetTimeCreatedDateRange( UGCQueryHandle_t handle, RTime32 rtStart, RTime32 rtEnd ); + + bool SetTimeUpdatedDateRange( UGCQueryHandle_t handle, RTime32 rtStart, RTime32 rtEnd ); + + + // DEPRECATED - Use CreateQueryUGCDetailsRequest call above instead! + SteamAPICall_t RequestUGCDetails( PublishedFileId_t nPublishedFileID, uint32 unMaxAgeSeconds ); + + SteamAPICall_t RequestUGCDetails( PublishedFileId_t nPublishedFileID ); + + + // Steam Workshop Creator API + STEAM_CALL_RESULT( CreateItemResult_t ) + SteamAPICall_t CreateItem( AppId_t nConsumerAppId, EWorkshopFileType eFileType ); + // create new item for this app with no content attached yet + + UGCUpdateHandle_t StartItemUpdate( AppId_t nConsumerAppId, PublishedFileId_t nPublishedFileID ); + // start an UGC item update. Set changed properties before commiting update with CommitItemUpdate() + + bool SetItemTitle( UGCUpdateHandle_t handle, const char *pchTitle ); + // change the title of an UGC item + + bool SetItemDescription( UGCUpdateHandle_t handle, const char *pchDescription ); + // change the description of an UGC item + + bool SetItemUpdateLanguage( UGCUpdateHandle_t handle, const char *pchLanguage ); + // specify the language of the title or description that will be set + + bool SetItemMetadata( UGCUpdateHandle_t handle, const char *pchMetaData ); + // change the metadata of an UGC item (max = k_cchDeveloperMetadataMax) + + + bool SetItemVisibility( UGCUpdateHandle_t handle, ERemoteStoragePublishedFileVisibility eVisibility ); + // change the visibility of an UGC item + + + bool SetItemTags( UGCUpdateHandle_t updateHandle, const SteamParamStringArray_t *pTags ); + + bool SetItemTags( UGCUpdateHandle_t updateHandle, const SteamParamStringArray_t *pTags, bool bAllowAdminTags ); + // change the tags of an UGC item + + bool SetItemContent( UGCUpdateHandle_t handle, const char *pszContentFolder ); + // update item content from this local folder + + + bool SetItemPreview( UGCUpdateHandle_t handle, const char *pszPreviewFile ); + // change preview image file for this item. pszPreviewFile points to local image file, which must be under 1MB in size + + bool SetAllowLegacyUpload( UGCUpdateHandle_t handle, bool bAllowLegacyUpload ); + + bool RemoveAllItemKeyValueTags( UGCUpdateHandle_t handle ); + // remove all existing key-value tags (you can add new ones via the AddItemKeyValueTag function) + + bool RemoveItemKeyValueTags( UGCUpdateHandle_t handle, const char *pchKey ); + // remove any existing key-value tags with the specified key + + + bool AddItemKeyValueTag( UGCUpdateHandle_t handle, const char *pchKey, const char *pchValue ); + // add new key-value tags for the item. Note that there can be multiple values for a tag. + + bool AddItemPreviewFile( UGCUpdateHandle_t handle, const char *pszPreviewFile, EItemPreviewType type ); + // add preview file for this item. pszPreviewFile points to local file, which must be under 1MB in size + + bool AddItemPreviewVideo( UGCUpdateHandle_t handle, const char *pszVideoID ); + // add preview video for this item + + bool UpdateItemPreviewFile( UGCUpdateHandle_t handle, uint32 index, const char *pszPreviewFile ); + // updates an existing preview file for this item. pszPreviewFile points to local file, which must be under 1MB in size + + + bool UpdateItemPreviewVideo( UGCUpdateHandle_t handle, uint32 index, const char *pszVideoID ); + // updates an existing preview video for this item + + + bool RemoveItemPreview( UGCUpdateHandle_t handle, uint32 index ); + // remove a preview by index starting at 0 (previews are sorted) + + bool AddContentDescriptor( UGCUpdateHandle_t handle, EUGCContentDescriptorID descid ); + + bool RemoveContentDescriptor( UGCUpdateHandle_t handle, EUGCContentDescriptorID descid ); + + STEAM_CALL_RESULT( SubmitItemUpdateResult_t ) + SteamAPICall_t SubmitItemUpdate( UGCUpdateHandle_t handle, const char *pchChangeNote ); + // commit update process started with StartItemUpdate() + + + EItemUpdateStatus GetItemUpdateProgress( UGCUpdateHandle_t handle, uint64 *punBytesProcessed, uint64* punBytesTotal ); + + + // Steam Workshop Consumer API + + STEAM_CALL_RESULT( SetUserItemVoteResult_t ) + SteamAPICall_t SetUserItemVote( PublishedFileId_t nPublishedFileID, bool bVoteUp ); + + + STEAM_CALL_RESULT( GetUserItemVoteResult_t ) + SteamAPICall_t GetUserItemVote( PublishedFileId_t nPublishedFileID ); + + + STEAM_CALL_RESULT( UserFavoriteItemsListChanged_t ) + SteamAPICall_t AddItemToFavorites( AppId_t nAppId, PublishedFileId_t nPublishedFileID ); + + + STEAM_CALL_RESULT( UserFavoriteItemsListChanged_t ) + SteamAPICall_t RemoveItemFromFavorites( AppId_t nAppId, PublishedFileId_t nPublishedFileID ); + + + STEAM_CALL_RESULT( RemoteStorageSubscribePublishedFileResult_t ) + SteamAPICall_t SubscribeItem( PublishedFileId_t nPublishedFileID ); + // subscribe to this item, will be installed ASAP + + STEAM_CALL_RESULT( RemoteStorageUnsubscribePublishedFileResult_t ) + SteamAPICall_t UnsubscribeItem( PublishedFileId_t nPublishedFileID ); + // unsubscribe from this item, will be uninstalled after game quits + + uint32 GetNumSubscribedItems(); + // number of subscribed items + + uint32 GetSubscribedItems( PublishedFileId_t* pvecPublishedFileID, uint32 cMaxEntries ); + // all subscribed item PublishFileIDs + + // get EItemState flags about item on this client + uint32 GetItemState( PublishedFileId_t nPublishedFileID ); + + + // get info about currently installed content on disc for items that have k_EItemStateInstalled set + // if k_EItemStateLegacyItem is set, pchFolder contains the path to the legacy file itself (not a folder) + bool GetItemInstallInfo( PublishedFileId_t nPublishedFileID, uint64 *punSizeOnDisk, STEAM_OUT_STRING_COUNT( cchFolderSize ) char *pchFolder, uint32 cchFolderSize, uint32 *punTimeStamp ); + + + // get info about pending update for items that have k_EItemStateNeedsUpdate set. punBytesTotal will be valid after download started once + bool GetItemDownloadInfo( PublishedFileId_t nPublishedFileID, uint64 *punBytesDownloaded, uint64 *punBytesTotal ); + + bool GetItemInstallInfo( PublishedFileId_t nPublishedFileID, uint64 *punSizeOnDisk, STEAM_OUT_STRING_COUNT( cchFolderSize ) char *pchFolder, uint32 cchFolderSize, bool *pbLegacyItem ); // returns true if item is installed + + + bool GetItemUpdateInfo( PublishedFileId_t nPublishedFileID, bool *pbNeedsUpdate, bool *pbIsDownloading, uint64 *punBytesDownloaded, uint64 *punBytesTotal ); + + bool GetItemInstallInfo( PublishedFileId_t nPublishedFileID, uint64 *punSizeOnDisk, char *pchFolder, uint32 cchFolderSize ); // returns true if item is installed + + + // download new or update already installed item. If function returns true, wait for DownloadItemResult_t. If the item is already installed, + // then files on disk should not be used until callback received. If item is not subscribed to, it will be cached for some time. + // If bHighPriority is set, any other item download will be suspended and this item downloaded ASAP. + bool DownloadItem( PublishedFileId_t nPublishedFileID, bool bHighPriority ); + + + // game servers can set a specific workshop folder before issuing any UGC commands. + // This is helpful if you want to support multiple game servers running out of the same install folder + bool BInitWorkshopForGameServer( DepotId_t unWorkshopDepotID, const char *pszFolder ); + + + // SuspendDownloads( true ) will suspend all workshop downloads until SuspendDownloads( false ) is called or the game ends + void SuspendDownloads( bool bSuspend ); + + + // usage tracking + STEAM_CALL_RESULT( StartPlaytimeTrackingResult_t ) + SteamAPICall_t StartPlaytimeTracking( PublishedFileId_t *pvecPublishedFileID, uint32 unNumPublishedFileIDs ); + + STEAM_CALL_RESULT( StopPlaytimeTrackingResult_t ) + SteamAPICall_t StopPlaytimeTracking( PublishedFileId_t *pvecPublishedFileID, uint32 unNumPublishedFileIDs ); + + STEAM_CALL_RESULT( StopPlaytimeTrackingResult_t ) + SteamAPICall_t StopPlaytimeTrackingForAllItems(); + + + // parent-child relationship or dependency management + STEAM_CALL_RESULT( AddUGCDependencyResult_t ) + SteamAPICall_t AddDependency( PublishedFileId_t nParentPublishedFileID, PublishedFileId_t nChildPublishedFileID ); + + STEAM_CALL_RESULT( RemoveUGCDependencyResult_t ) + SteamAPICall_t RemoveDependency( PublishedFileId_t nParentPublishedFileID, PublishedFileId_t nChildPublishedFileID ); + + + // add/remove app dependence/requirements (usually DLC) + STEAM_CALL_RESULT( AddAppDependencyResult_t ) + SteamAPICall_t AddAppDependency( PublishedFileId_t nPublishedFileID, AppId_t nAppID ); + + STEAM_CALL_RESULT( RemoveAppDependencyResult_t ) + SteamAPICall_t RemoveAppDependency( PublishedFileId_t nPublishedFileID, AppId_t nAppID ); + + // request app dependencies. note that whatever callback you register for GetAppDependenciesResult_t may be called multiple times + // until all app dependencies have been returned + STEAM_CALL_RESULT( GetAppDependenciesResult_t ) + SteamAPICall_t GetAppDependencies( PublishedFileId_t nPublishedFileID ); + + + // delete the item without prompting the user + STEAM_CALL_RESULT( DeleteItemResult_t ) + SteamAPICall_t DeleteItem( PublishedFileId_t nPublishedFileID ); + + // Show the app's latest Workshop EULA to the user in an overlay window, where they can accept it or not + bool ShowWorkshopEULA(); + + // Retrieve information related to the user's acceptance or not of the app's specific Workshop EULA + STEAM_CALL_RESULT( WorkshopEULAStatus_t ) + SteamAPICall_t GetWorkshopEULAStatus(); + + // Return the user's community content descriptor preferences + uint32 GetUserContentDescriptorPreferences( EUGCContentDescriptorID *pvecDescriptors, uint32 cMaxEntries ); }; + +#endif // __INCLUDED_STEAM_UGC_H__ diff --git a/dll/dll/steam_unified_messages.h b/dll/dll/steam_unified_messages.h index f7672d2b..bc0c99d6 100644 --- a/dll/dll/steam_unified_messages.h +++ b/dll/dll/steam_unified_messages.h @@ -15,114 +15,53 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_UNIFIED_MESSAGES_H__ +#define __INCLUDED_STEAM_UNIFIED_MESSAGES_H__ + #include "base.h" -class Steam_Unified_Messages : +class Steam_Unified_Messages: public ISteamUnifiedMessages { - class Settings *settings; - class Networking *network; - class SteamCallResults *callback_results; - class SteamCallBacks *callbacks; - class RunEveryRunCB *run_every_runcb; + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; + + static void network_callback(void *object, Common_Message *msg); + static void steam_runcb(void *object); public: -static void steam_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); + Steam_Unified_Messages(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_Unified_Messages(); - Steam_Unified_Messages *steam_steamunifiedmessages = (Steam_Unified_Messages *)object; - steam_steamunifiedmessages->Callback(msg); -} - -static void steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - Steam_Unified_Messages *steam_steamunifiedmessages = (Steam_Unified_Messages *)object; - steam_steamunifiedmessages->RunCallbacks(); -} - -Steam_Unified_Messages(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->run_every_runcb = run_every_runcb; - // this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Unified_Messages::steam_callback, this); - // this->run_every_runcb->add(&Steam_Unified_Messages::steam_run_every_runcb, this); - - this->callback_results = callback_results; - this->callbacks = callbacks; -} - -~Steam_Unified_Messages() -{ - // this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Unified_Messages::steam_callback, this); - // this->run_every_runcb->remove(&Steam_Unified_Messages::steam_run_every_runcb, this); -} - -// Sends a service method (in binary serialized form) using the Steam Client. -// Returns a unified message handle (k_InvalidUnifiedMessageHandle if could not send the message). -ClientUnifiedMessageHandle SendMethod( const char *pchServiceMethod, const void *pRequestBuffer, uint32 unRequestBufferSize, uint64 unContext ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return ISteamUnifiedMessages::k_InvalidUnifiedMessageHandle; -} + // Sends a service method (in binary serialized form) using the Steam Client. + // Returns a unified message handle (k_InvalidUnifiedMessageHandle if could not send the message). + ClientUnifiedMessageHandle SendMethod( const char *pchServiceMethod, const void *pRequestBuffer, uint32 unRequestBufferSize, uint64 unContext ); -// Gets the size of the response and the EResult. Returns false if the response is not ready yet. -bool GetMethodResponseInfo( ClientUnifiedMessageHandle hHandle, uint32 *punResponseSize, EResult *peResult ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} + // Gets the size of the response and the EResult. Returns false if the response is not ready yet. + bool GetMethodResponseInfo( ClientUnifiedMessageHandle hHandle, uint32 *punResponseSize, EResult *peResult ); -// Gets a response in binary serialized form (and optionally release the corresponding allocated memory). -bool GetMethodResponseData( ClientUnifiedMessageHandle hHandle, void *pResponseBuffer, uint32 unResponseBufferSize, bool bAutoRelease ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} + // Gets a response in binary serialized form (and optionally release the corresponding allocated memory). + bool GetMethodResponseData( ClientUnifiedMessageHandle hHandle, void *pResponseBuffer, uint32 unResponseBufferSize, bool bAutoRelease ); -// Releases the message and its corresponding allocated memory. -bool ReleaseMethod( ClientUnifiedMessageHandle hHandle ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} + // Releases the message and its corresponding allocated memory. + bool ReleaseMethod( ClientUnifiedMessageHandle hHandle ); -// Sends a service notification (in binary serialized form) using the Steam Client. -// Returns true if the notification was sent successfully. -bool SendNotification( const char *pchServiceNotification, const void *pNotificationBuffer, uint32 unNotificationBufferSize ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} + // Sends a service notification (in binary serialized form) using the Steam Client. + // Returns true if the notification was sent successfully. + bool SendNotification( const char *pchServiceNotification, const void *pNotificationBuffer, uint32 unNotificationBufferSize ); -void RunCallbacks() -{ -} + void RunCallbacks(); -void Callback(Common_Message *msg) -{ - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - - } - - if (msg->low_level().type() == Low_Level::DISCONNECT) { - - } - } -} + void Callback(Common_Message *msg); }; + +#endif // __INCLUDED_STEAM_UNIFIED_MESSAGES_H__ diff --git a/dll/dll/steam_user.h b/dll/dll/steam_user.h index a505fb30..8e6991db 100644 --- a/dll/dll/steam_user.h +++ b/dll/dll/steam_user.h @@ -15,9 +15,11 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_USER_H__ +#define __INCLUDED_STEAM_USER_H__ + #include "base.h" #include "auth.h" -#include "appticket.h" class Steam_User : public ISteamUser009, @@ -35,524 +37,223 @@ public ISteamUser020, public ISteamUser021, public ISteamUser { - Settings *settings; - class Networking *network; - class SteamCallBacks *callbacks; - class SteamCallResults *callback_results; - Local_Storage *local_storage; + Settings *settings{}; + class Networking *network{}; + class SteamCallBacks *callbacks{}; + class SteamCallResults *callback_results{}; + Local_Storage *local_storage{}; bool recording = false; - std::chrono::high_resolution_clock::time_point last_get_voice; - std::string encrypted_app_ticket; - Auth_Manager *auth_manager; + std::chrono::high_resolution_clock::time_point last_get_voice{}; + std::string encrypted_app_ticket{}; + Auth_Manager *auth_manager{}; public: - -Steam_User(Settings *settings, Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks) -{ - this->settings = settings; - this->local_storage = local_storage; - this->network = network; - this->callbacks = callbacks; - this->callback_results = callback_results; - recording = false; - auth_manager = new Auth_Manager(settings, network, callbacks); -} - -~Steam_User() -{ - delete auth_manager; -} - -// returns the HSteamUser this interface represents -// this is only used internally by the API, and by a few select interfaces that support multi-user -HSteamUser GetHSteamUser() -{ - PRINT_DEBUG_ENTRY(); - return CLIENT_HSTEAMUSER; -} - -// returns true if the Steam client current has a live connection to the Steam servers. -// If false, it means there is no active connection due to either a networking issue on the local machine, or the Steam server is down/busy. -// The Steam client will automatically be trying to recreate the connection as often as possible. -bool BLoggedOn() -{ - PRINT_DEBUG_ENTRY(); - return !settings->is_offline(); -} - -// returns the CSteamID of the account currently logged into the Steam client -// a CSteamID is a unique identifier for an account, and used to differentiate users in all parts of the Steamworks API -CSteamID GetSteamID() -{ - PRINT_DEBUG_ENTRY(); - CSteamID id = settings->get_local_steam_id(); - - return id; -} - -// Multiplayer Authentication functions - -// InitiateGameConnection() starts the state machine for authenticating the game client with the game server -// It is the client portion of a three-way handshake between the client, the game server, and the steam servers -// -// Parameters: -// void *pAuthBlob - a pointer to empty memory that will be filled in with the authentication token. -// int cbMaxAuthBlob - the number of bytes of allocated memory in pBlob. Should be at least 2048 bytes. -// CSteamID steamIDGameServer - the steamID of the game server, received from the game server by the client -// CGameID gameID - the ID of the current game. For games without mods, this is just CGameID( ) -// uint32 unIPServer, uint16 usPortServer - the IP address of the game server -// bool bSecure - whether or not the client thinks that the game server is reporting itself as secure (i.e. VAC is running) -// -// return value - returns the number of bytes written to pBlob. If the return is 0, then the buffer passed in was too small, and the call has failed -// The contents of pBlob should then be sent to the game server, for it to use to complete the authentication process. - -//steam returns 206 bytes -#define INITIATE_GAME_CONNECTION_TICKET_SIZE 206 - -int InitiateGameConnection( void *pAuthBlob, int cbMaxAuthBlob, CSteamID steamIDGameServer, uint32 unIPServer, uint16 usPortServer, bool bSecure ) -{ - PRINT_DEBUG("%i %llu %u %u %u %p", cbMaxAuthBlob, steamIDGameServer.ConvertToUint64(), unIPServer, usPortServer, bSecure, pAuthBlob); - std::lock_guard lock(global_mutex); - if (cbMaxAuthBlob < INITIATE_GAME_CONNECTION_TICKET_SIZE) return 0; - if (!pAuthBlob) return 0; - uint32 out_size = INITIATE_GAME_CONNECTION_TICKET_SIZE; - auth_manager->getTicketData(pAuthBlob, INITIATE_GAME_CONNECTION_TICKET_SIZE, &out_size); - if (out_size > INITIATE_GAME_CONNECTION_TICKET_SIZE) - return 0; - return out_size; -} - -int InitiateGameConnection( void *pAuthBlob, int cbMaxAuthBlob, CSteamID steamIDGameServer, CGameID gameID, uint32 unIPServer, uint16 usPortServer, bool bSecure ) -{ - PRINT_DEBUG_ENTRY(); - return InitiateGameConnection(pAuthBlob, cbMaxAuthBlob, steamIDGameServer, unIPServer, usPortServer, bSecure); -} - -// notify of disconnect -// needs to occur when the game client leaves the specified game server, needs to match with the InitiateGameConnection() call -void TerminateGameConnection( uint32 unIPServer, uint16 usPortServer ) -{ - PRINT_DEBUG_TODO(); -} - -// Legacy functions - -// used by only a few games to track usage events -void TrackAppUsageEvent( CGameID gameID, int eAppUsageEvent, const char *pchExtraInfo) -{ - PRINT_DEBUG_TODO(); -} - -void RefreshSteam2Login() -{ - PRINT_DEBUG_TODO(); -} - -// get the local storage folder for current Steam account to write application data, e.g. save games, configs etc. -// this will usually be something like "C:\Progam Files\Steam\userdata\\\local" -bool GetUserDataFolder( char *pchBuffer, int cubBuffer ) -{ - PRINT_DEBUG_ENTRY(); - if (!cubBuffer) return false; - - std::string user_data = local_storage->get_path(Local_Storage::user_data_storage); - strncpy(pchBuffer, user_data.c_str(), cubBuffer - 1); - pchBuffer[cubBuffer - 1] = 0; - return true; -} - -// Starts voice recording. Once started, use GetVoice() to get the data -void StartVoiceRecording( ) -{ - PRINT_DEBUG_ENTRY(); - last_get_voice = std::chrono::high_resolution_clock::now(); - recording = true; - //TODO:fix - recording = false; -} - -// Stops voice recording. Because people often release push-to-talk keys early, the system will keep recording for -// a little bit after this function is called. GetVoice() should continue to be called until it returns -// k_eVoiceResultNotRecording -void StopVoiceRecording( ) -{ - PRINT_DEBUG_ENTRY(); - recording = false; -} - -// Determine the size of captured audio data that is available from GetVoice. -// Most applications will only use compressed data and should ignore the other -// parameters, which exist primarily for backwards compatibility. See comments -// below for further explanation of "uncompressed" data. -EVoiceResult GetAvailableVoice( uint32 *pcbCompressed, uint32 *pcbUncompressed_Deprecated, uint32 nUncompressedVoiceDesiredSampleRate_Deprecated ) -{ - PRINT_DEBUG_ENTRY(); - if (pcbCompressed) *pcbCompressed = 0; - if (pcbUncompressed_Deprecated) *pcbUncompressed_Deprecated = 0; - if (!recording) return k_EVoiceResultNotRecording; - double seconds = std::chrono::duration_cast>(std::chrono::high_resolution_clock::now() - last_get_voice).count(); - if (pcbCompressed) *pcbCompressed = seconds * 1024.0 * 64.0 / 8.0; - if (pcbUncompressed_Deprecated) *pcbUncompressed_Deprecated = seconds * (double)nUncompressedVoiceDesiredSampleRate_Deprecated * 2.0; - - return k_EVoiceResultOK; -} - -EVoiceResult GetAvailableVoice(uint32 *pcbCompressed, uint32 *pcbUncompressed) -{ - PRINT_DEBUG("old"); - return GetAvailableVoice(pcbCompressed, pcbUncompressed, 11025); -} - -// --------------------------------------------------------------------------- -// NOTE: "uncompressed" audio is a deprecated feature and should not be used -// by most applications. It is raw single-channel 16-bit PCM wave data which -// may have been run through preprocessing filters and/or had silence removed, -// so the uncompressed audio could have a shorter duration than you expect. -// There may be no data at all during long periods of silence. Also, fetching -// uncompressed audio will cause GetVoice to discard any leftover compressed -// audio, so you must fetch both types at once. Finally, GetAvailableVoice is -// not precisely accurate when the uncompressed size is requested. So if you -// really need to use uncompressed audio, you should call GetVoice frequently -// with two very large (20kb+) output buffers instead of trying to allocate -// perfectly-sized buffers. But most applications should ignore all of these -// details and simply leave the "uncompressed" parameters as NULL/zero. -// --------------------------------------------------------------------------- - -// Read captured audio data from the microphone buffer. This should be called -// at least once per frame, and preferably every few milliseconds, to keep the -// microphone input delay as low as possible. Most applications will only use -// compressed data and should pass NULL/zero for the "uncompressed" parameters. -// Compressed data can be transmitted by your application and decoded into raw -// using the DecompressVoice function below. -EVoiceResult GetVoice( bool bWantCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten, bool bWantUncompressed_Deprecated, void *pUncompressedDestBuffer_Deprecated , uint32 cbUncompressedDestBufferSize_Deprecated , uint32 *nUncompressBytesWritten_Deprecated , uint32 nUncompressedVoiceDesiredSampleRate_Deprecated ) -{ - PRINT_DEBUG_ENTRY(); - if (!recording) return k_EVoiceResultNotRecording; - double seconds = std::chrono::duration_cast>(std::chrono::high_resolution_clock::now() - last_get_voice).count(); - if (bWantCompressed) { - uint32 towrite = seconds * 1024.0 * 64.0 / 8.0; - if (cbDestBufferSize < towrite) towrite = cbDestBufferSize; - if (pDestBuffer) memset(pDestBuffer, 0, towrite); - if (nBytesWritten) *nBytesWritten = towrite; - } - - if (bWantUncompressed_Deprecated) { - PRINT_DEBUG("Wanted Uncompressed"); - } - - last_get_voice = std::chrono::high_resolution_clock::now(); - return k_EVoiceResultOK; -} - -EVoiceResult GetVoice( bool bWantCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten, bool bWantUncompressed, void *pUncompressedDestBuffer, uint32 cbUncompressedDestBufferSize, uint32 *nUncompressBytesWritten ) -{ - PRINT_DEBUG("old"); - return GetVoice(bWantCompressed, pDestBuffer, cbDestBufferSize, nBytesWritten, bWantUncompressed, pUncompressedDestBuffer, cbUncompressedDestBufferSize, nUncompressBytesWritten, 11025); -} - -EVoiceResult GetCompressedVoice( void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten ) -{ - PRINT_DEBUG_ENTRY(); - return GetVoice(true, pDestBuffer, cbDestBufferSize, nBytesWritten, false, NULL, 0, NULL); -} - -// Decodes the compressed voice data returned by GetVoice. The output data is -// raw single-channel 16-bit PCM audio. The decoder supports any sample rate -// from 11025 to 48000; see GetVoiceOptimalSampleRate() below for details. -// If the output buffer is not large enough, then *nBytesWritten will be set -// to the required buffer size, and k_EVoiceResultBufferTooSmall is returned. -// It is suggested to start with a 20kb buffer and reallocate as necessary. -EVoiceResult DecompressVoice( const void *pCompressed, uint32 cbCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten, uint32 nDesiredSampleRate ) -{ - PRINT_DEBUG_ENTRY(); - if (!recording) return k_EVoiceResultNotRecording; - uint32 uncompressed = (double)cbCompressed * ((double)nDesiredSampleRate / 8192.0); - if(nBytesWritten) *nBytesWritten = uncompressed; - if (uncompressed > cbDestBufferSize) uncompressed = cbDestBufferSize; - if (pDestBuffer) memset(pDestBuffer, 0, uncompressed); - - return k_EVoiceResultOK; -} - -EVoiceResult DecompressVoice( const void *pCompressed, uint32 cbCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten ) -{ - PRINT_DEBUG("old"); - return DecompressVoice(pCompressed, cbCompressed, pDestBuffer, cbDestBufferSize, nBytesWritten, 11025); -} - -EVoiceResult DecompressVoice( void *pCompressed, uint32 cbCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten ) -{ - PRINT_DEBUG("older"); - return DecompressVoice(pCompressed, cbCompressed, pDestBuffer, cbDestBufferSize, nBytesWritten, 11025); -} - -// This returns the native sample rate of the Steam voice decompressor -// this sample rate for DecompressVoice will perform the least CPU processing. -// However, the final audio quality will depend on how well the audio device -// (and/or your application's audio output SDK) deals with lower sample rates. -// You may find that you get the best audio output quality when you ignore -// this function and use the native sample rate of your audio output device, -// which is usually 48000 or 44100. -uint32 GetVoiceOptimalSampleRate() -{ - PRINT_DEBUG_ENTRY(); - return 48000; -} - -// Retrieve ticket to be sent to the entity who wishes to authenticate you. -// pcbTicket retrieves the length of the actual ticket. -HAuthTicket GetAuthSessionTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket ) -{ - return GetAuthSessionTicket(pTicket, cbMaxTicket, pcbTicket, NULL); -} -// SteamNetworkingIdentity is an optional input parameter to hold the public IP address or SteamID of the entity you are connecting to -// if an IP address is passed Steam will only allow the ticket to be used by an entity with that IP address -// if a Steam ID is passed Steam will only allow the ticket to be used by that Steam ID -HAuthTicket GetAuthSessionTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket, const SteamNetworkingIdentity *pSteamNetworkingIdentity ) -{ - PRINT_DEBUG("%p [%i] %p", pTicket, cbMaxTicket, pcbTicket); - std::lock_guard lock(global_mutex); - - if (!pTicket) return k_HAuthTicketInvalid; - - return auth_manager->getTicket(pTicket, cbMaxTicket, pcbTicket); -} - -// Request a ticket which will be used for webapi "ISteamUserAuth\AuthenticateUserTicket" -// pchIdentity is an optional input parameter to identify the service the ticket will be sent to -// the ticket will be returned in callback GetTicketForWebApiResponse_t -HAuthTicket GetAuthTicketForWebApi( const char *pchIdentity ) -{ - PRINT_DEBUG("%s", pchIdentity); - std::lock_guard lock(global_mutex); - - return auth_manager->getWebApiTicket(pchIdentity); -} - -// Authenticate ticket from entity steamID to be sure it is valid and isnt reused -// Registers for callbacks if the entity goes offline or cancels the ticket ( see ValidateAuthTicketResponse_t callback and EAuthSessionResponse ) -EBeginAuthSessionResult BeginAuthSession( const void *pAuthTicket, int cbAuthTicket, CSteamID steamID ) -{ - PRINT_DEBUG("%i %llu", cbAuthTicket, steamID.ConvertToUint64()); - std::lock_guard lock(global_mutex); - - return auth_manager->beginAuth(pAuthTicket, cbAuthTicket, steamID); -} - -// Stop tracking started by BeginAuthSession - called when no longer playing game with this entity -void EndAuthSession( CSteamID steamID ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - auth_manager->endAuth(steamID); -} - -// Cancel auth ticket from GetAuthSessionTicket, called when no longer playing game with the entity you gave the ticket to -void CancelAuthTicket( HAuthTicket hAuthTicket ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - auth_manager->cancelTicket(hAuthTicket); -} - -// After receiving a user's authentication data, and passing it to BeginAuthSession, use this function -// to determine if the user owns downloadable content specified by the provided AppID. -EUserHasLicenseForAppResult UserHasLicenseForApp( CSteamID steamID, AppId_t appID ) -{ - PRINT_DEBUG_ENTRY(); - return k_EUserHasLicenseResultHasLicense; -} - -// returns true if this users looks like they are behind a NAT device. Only valid once the user has connected to steam -// (i.e a SteamServersConnected_t has been issued) and may not catch all forms of NAT. -bool BIsBehindNAT() -{ - PRINT_DEBUG_ENTRY(); - return false; -} - -// set data to be replicated to friends so that they can join your game -// CSteamID steamIDGameServer - the steamID of the game server, received from the game server by the client -// uint32 unIPServer, uint16 usPortServer - the IP address of the game server -void AdvertiseGame( CSteamID steamIDGameServer, uint32 unIPServer, uint16 usPortServer ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - Gameserver *server = new Gameserver(); - server->set_id(steamIDGameServer.ConvertToUint64()); - server->set_ip(unIPServer); - server->set_port(usPortServer); - server->set_query_port(usPortServer); - server->set_appid(settings->get_local_game_id().ToUint64()); - - if (settings->matchmaking_server_list_always_lan_type) - server->set_type(eLANServer); - else - server->set_type(eFriendsServer); - - Common_Message msg; - msg.set_allocated_gameserver(server); - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - network->sendToAllIndividuals(&msg, true); -} - -// Requests a ticket encrypted with an app specific shared key -// pDataToInclude, cbDataToInclude will be encrypted into the ticket -// ( This is asynchronous, you must wait for the ticket to be completed by the server ) -STEAM_CALL_RESULT( EncryptedAppTicketResponse_t ) -SteamAPICall_t RequestEncryptedAppTicket( void *pDataToInclude, int cbDataToInclude ) -{ - PRINT_DEBUG("%i", cbDataToInclude); - std::lock_guard lock(global_mutex); - EncryptedAppTicketResponse_t data; - data.m_eResult = k_EResultOK; - - DecryptedAppTicket ticket; - ticket.TicketV1.Reset(); - ticket.TicketV2.Reset(); - ticket.TicketV4.Reset(); - - ticket.TicketV1.TicketVersion = 1; - if (pDataToInclude) { - ticket.TicketV1.UserData.assign((uint8_t*)pDataToInclude, (uint8_t*)pDataToInclude + cbDataToInclude); - } - - ticket.TicketV2.TicketVersion = 4; - ticket.TicketV2.SteamID = settings->get_local_steam_id().ConvertToUint64(); - ticket.TicketV2.TicketIssueTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - ticket.TicketV2.TicketValidityEnd = ticket.TicketV2.TicketIssueTime + (21 * 24 * 60 * 60); - - for (int i = 0; i < 140; ++i) - { - AppId_t appid; - bool available; - std::string name; - if (!settings->getDLC(appid, appid, available, name)) break; - ticket.TicketV4.AppIDs.emplace_back(appid); - } - - ticket.TicketV4.HasVACStatus = true; - ticket.TicketV4.VACStatus = 0; - - auto serialized = ticket.SerializeTicket(); - - SteamAppTicket_pb pb; - pb.set_ticket_version_no(1); - pb.set_crc_encryptedticket(0); // TODO: Find out how to compute the CRC - pb.set_cb_encrypteduserdata(cbDataToInclude); - pb.set_cb_encrypted_appownershipticket(serialized.size() - 16); - pb.mutable_encrypted_ticket()->assign(serialized.begin(), serialized.end()); // TODO: Find how to encrypt datas - - encrypted_app_ticket = pb.SerializeAsString(); - - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -// retrieve a finished ticket -bool GetEncryptedAppTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket ) -{ - PRINT_DEBUG("%i", cbMaxTicket); - unsigned int ticket_size = encrypted_app_ticket.size(); - if (!cbMaxTicket) { - if (!pcbTicket) return false; - *pcbTicket = ticket_size; - return true; - } - - if (!pTicket) return false; - if (ticket_size > cbMaxTicket) return false; - encrypted_app_ticket.copy((char *)pTicket, cbMaxTicket); - if (pcbTicket) *pcbTicket = ticket_size; - - return true; -} - -// Trading Card badges data access -// if you only have one set of cards, the series will be 1 -// the user has can have two different badges for a series; the regular (max level 5) and the foil (max level 1) -int GetGameBadgeLevel( int nSeries, bool bFoil ) -{ - PRINT_DEBUG_ENTRY(); - return 0; -} - -// gets the Steam Level of the user, as shown on their profile -int GetPlayerSteamLevel() -{ - PRINT_DEBUG_ENTRY(); - return 100; -} - -// Requests a URL which authenticates an in-game browser for store check-out, -// and then redirects to the specified URL. As long as the in-game browser -// accepts and handles session cookies, Steam microtransaction checkout pages -// will automatically recognize the user instead of presenting a login page. -// The result of this API call will be a StoreAuthURLResponse_t callback. -// NOTE: The URL has a very short lifetime to prevent history-snooping attacks, -// so you should only call this API when you are about to launch the browser, -// or else immediately navigate to the result URL using a hidden browser window. -// NOTE 2: The resulting authorization cookie has an expiration time of one day, -// so it would be a good idea to request and visit a new auth URL every 12 hours. -STEAM_CALL_RESULT( StoreAuthURLResponse_t ) -SteamAPICall_t RequestStoreAuthURL( const char *pchRedirectURL ) -{ - PRINT_DEBUG_ENTRY(); - return 0; -} - -// gets whether the users phone number is verified -bool BIsPhoneVerified() -{ - PRINT_DEBUG_ENTRY(); - return true; -} - -// gets whether the user has two factor enabled on their account -bool BIsTwoFactorEnabled() -{ - PRINT_DEBUG_ENTRY(); - return true; -} - -// gets whether the users phone number is identifying -bool BIsPhoneIdentifying() -{ - PRINT_DEBUG_ENTRY(); - return false; -} - -// gets whether the users phone number is awaiting (re)verification -bool BIsPhoneRequiringVerification() -{ - PRINT_DEBUG_ENTRY(); - return false; -} - -STEAM_CALL_RESULT( MarketEligibilityResponse_t ) -SteamAPICall_t GetMarketEligibility() -{ - PRINT_DEBUG_ENTRY(); - return 0; -} - -// Retrieves anti indulgence / duration control for current user -STEAM_CALL_RESULT( DurationControl_t ) -SteamAPICall_t GetDurationControl() -{ - PRINT_DEBUG_ENTRY(); - return 0; -} - -// Advise steam china duration control system about the online state of the game. -// This will prevent offline gameplay time from counting against a user's -// playtime limits. -bool BSetDurationControlOnlineState( EDurationControlOnlineState eNewState ) -{ - PRINT_DEBUG_ENTRY(); - return false; -} + Steam_User(Settings *settings, Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks); + ~Steam_User(); + + // returns the HSteamUser this interface represents + // this is only used internally by the API, and by a few select interfaces that support multi-user + HSteamUser GetHSteamUser(); + + // returns true if the Steam client current has a live connection to the Steam servers. + // If false, it means there is no active connection due to either a networking issue on the local machine, or the Steam server is down/busy. + // The Steam client will automatically be trying to recreate the connection as often as possible. + bool BLoggedOn(); + + // returns the CSteamID of the account currently logged into the Steam client + // a CSteamID is a unique identifier for an account, and used to differentiate users in all parts of the Steamworks API + CSteamID GetSteamID(); + + // Multiplayer Authentication functions + + // InitiateGameConnection() starts the state machine for authenticating the game client with the game server + // It is the client portion of a three-way handshake between the client, the game server, and the steam servers + // + // Parameters: + // void *pAuthBlob - a pointer to empty memory that will be filled in with the authentication token. + // int cbMaxAuthBlob - the number of bytes of allocated memory in pBlob. Should be at least 2048 bytes. + // CSteamID steamIDGameServer - the steamID of the game server, received from the game server by the client + // CGameID gameID - the ID of the current game. For games without mods, this is just CGameID( ) + // uint32 unIPServer, uint16 usPortServer - the IP address of the game server + // bool bSecure - whether or not the client thinks that the game server is reporting itself as secure (i.e. VAC is running) + // + // return value - returns the number of bytes written to pBlob. If the return is 0, then the buffer passed in was too small, and the call has failed + // The contents of pBlob should then be sent to the game server, for it to use to complete the authentication process. + + int InitiateGameConnection( void *pAuthBlob, int cbMaxAuthBlob, CSteamID steamIDGameServer, uint32 unIPServer, uint16 usPortServer, bool bSecure ); + + int InitiateGameConnection( void *pAuthBlob, int cbMaxAuthBlob, CSteamID steamIDGameServer, CGameID gameID, uint32 unIPServer, uint16 usPortServer, bool bSecure ); + + // notify of disconnect + // needs to occur when the game client leaves the specified game server, needs to match with the InitiateGameConnection() call + void TerminateGameConnection( uint32 unIPServer, uint16 usPortServer ); + + // Legacy functions + + // used by only a few games to track usage events + void TrackAppUsageEvent( CGameID gameID, int eAppUsageEvent, const char *pchExtraInfo); + + void RefreshSteam2Login(); + + // get the local storage folder for current Steam account to write application data, e.g. save games, configs etc. + // this will usually be something like "C:\Progam Files\Steam\userdata\\\local" + bool GetUserDataFolder( char *pchBuffer, int cubBuffer ); + + // Starts voice recording. Once started, use GetVoice() to get the data + void StartVoiceRecording( ); + + // Stops voice recording. Because people often release push-to-talk keys early, the system will keep recording for + // a little bit after this function is called. GetVoice() should continue to be called until it returns + // k_eVoiceResultNotRecording + void StopVoiceRecording( ); + + // Determine the size of captured audio data that is available from GetVoice. + // Most applications will only use compressed data and should ignore the other + // parameters, which exist primarily for backwards compatibility. See comments + // below for further explanation of "uncompressed" data. + EVoiceResult GetAvailableVoice( uint32 *pcbCompressed, uint32 *pcbUncompressed_Deprecated, uint32 nUncompressedVoiceDesiredSampleRate_Deprecated ); + + EVoiceResult GetAvailableVoice(uint32 *pcbCompressed, uint32 *pcbUncompressed); + + // --------------------------------------------------------------------------- + // NOTE: "uncompressed" audio is a deprecated feature and should not be used + // by most applications. It is raw single-channel 16-bit PCM wave data which + // may have been run through preprocessing filters and/or had silence removed, + // so the uncompressed audio could have a shorter duration than you expect. + // There may be no data at all during long periods of silence. Also, fetching + // uncompressed audio will cause GetVoice to discard any leftover compressed + // audio, so you must fetch both types at once. Finally, GetAvailableVoice is + // not precisely accurate when the uncompressed size is requested. So if you + // really need to use uncompressed audio, you should call GetVoice frequently + // with two very large (20kb+) output buffers instead of trying to allocate + // perfectly-sized buffers. But most applications should ignore all of these + // details and simply leave the "uncompressed" parameters as NULL/zero. + // --------------------------------------------------------------------------- + + // Read captured audio data from the microphone buffer. This should be called + // at least once per frame, and preferably every few milliseconds, to keep the + // microphone input delay as low as possible. Most applications will only use + // compressed data and should pass NULL/zero for the "uncompressed" parameters. + // Compressed data can be transmitted by your application and decoded into raw + // using the DecompressVoice function below. + EVoiceResult GetVoice( bool bWantCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten, bool bWantUncompressed_Deprecated, void *pUncompressedDestBuffer_Deprecated , uint32 cbUncompressedDestBufferSize_Deprecated , uint32 *nUncompressBytesWritten_Deprecated , uint32 nUncompressedVoiceDesiredSampleRate_Deprecated ); + + EVoiceResult GetVoice( bool bWantCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten, bool bWantUncompressed, void *pUncompressedDestBuffer, uint32 cbUncompressedDestBufferSize, uint32 *nUncompressBytesWritten ); + + EVoiceResult GetCompressedVoice( void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten ); + + // Decodes the compressed voice data returned by GetVoice. The output data is + // raw single-channel 16-bit PCM audio. The decoder supports any sample rate + // from 11025 to 48000; see GetVoiceOptimalSampleRate() below for details. + // If the output buffer is not large enough, then *nBytesWritten will be set + // to the required buffer size, and k_EVoiceResultBufferTooSmall is returned. + // It is suggested to start with a 20kb buffer and reallocate as necessary. + EVoiceResult DecompressVoice( const void *pCompressed, uint32 cbCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten, uint32 nDesiredSampleRate ); + + EVoiceResult DecompressVoice( const void *pCompressed, uint32 cbCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten ); + + EVoiceResult DecompressVoice( void *pCompressed, uint32 cbCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten ); + + // This returns the native sample rate of the Steam voice decompressor + // this sample rate for DecompressVoice will perform the least CPU processing. + // However, the final audio quality will depend on how well the audio device + // (and/or your application's audio output SDK) deals with lower sample rates. + // You may find that you get the best audio output quality when you ignore + // this function and use the native sample rate of your audio output device, + // which is usually 48000 or 44100. + uint32 GetVoiceOptimalSampleRate(); + + // Retrieve ticket to be sent to the entity who wishes to authenticate you. + // pcbTicket retrieves the length of the actual ticket. + HAuthTicket GetAuthSessionTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket ); + // SteamNetworkingIdentity is an optional input parameter to hold the public IP address or SteamID of the entity you are connecting to + // if an IP address is passed Steam will only allow the ticket to be used by an entity with that IP address + // if a Steam ID is passed Steam will only allow the ticket to be used by that Steam ID + HAuthTicket GetAuthSessionTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket, const SteamNetworkingIdentity *pSteamNetworkingIdentity ); + + // Request a ticket which will be used for webapi "ISteamUserAuth\AuthenticateUserTicket" + // pchIdentity is an optional input parameter to identify the service the ticket will be sent to + // the ticket will be returned in callback GetTicketForWebApiResponse_t + HAuthTicket GetAuthTicketForWebApi( const char *pchIdentity ); + + // Authenticate ticket from entity steamID to be sure it is valid and isnt reused + // Registers for callbacks if the entity goes offline or cancels the ticket ( see ValidateAuthTicketResponse_t callback and EAuthSessionResponse ) + EBeginAuthSessionResult BeginAuthSession( const void *pAuthTicket, int cbAuthTicket, CSteamID steamID ); + + // Stop tracking started by BeginAuthSession - called when no longer playing game with this entity + void EndAuthSession( CSteamID steamID ); + + // Cancel auth ticket from GetAuthSessionTicket, called when no longer playing game with the entity you gave the ticket to + void CancelAuthTicket( HAuthTicket hAuthTicket ); + + // After receiving a user's authentication data, and passing it to BeginAuthSession, use this function + // to determine if the user owns downloadable content specified by the provided AppID. + EUserHasLicenseForAppResult UserHasLicenseForApp( CSteamID steamID, AppId_t appID ); + + // returns true if this users looks like they are behind a NAT device. Only valid once the user has connected to steam + // (i.e a SteamServersConnected_t has been issued) and may not catch all forms of NAT. + bool BIsBehindNAT(); + + // set data to be replicated to friends so that they can join your game + // CSteamID steamIDGameServer - the steamID of the game server, received from the game server by the client + // uint32 unIPServer, uint16 usPortServer - the IP address of the game server + void AdvertiseGame( CSteamID steamIDGameServer, uint32 unIPServer, uint16 usPortServer ); + + // Requests a ticket encrypted with an app specific shared key + // pDataToInclude, cbDataToInclude will be encrypted into the ticket + // ( This is asynchronous, you must wait for the ticket to be completed by the server ) + STEAM_CALL_RESULT( EncryptedAppTicketResponse_t ) + SteamAPICall_t RequestEncryptedAppTicket( void *pDataToInclude, int cbDataToInclude ); + + // retrieve a finished ticket + bool GetEncryptedAppTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket ); + + // Trading Card badges data access + // if you only have one set of cards, the series will be 1 + // the user has can have two different badges for a series; the regular (max level 5) and the foil (max level 1) + int GetGameBadgeLevel( int nSeries, bool bFoil ); + + // gets the Steam Level of the user, as shown on their profile + int GetPlayerSteamLevel(); + + // Requests a URL which authenticates an in-game browser for store check-out, + // and then redirects to the specified URL. As long as the in-game browser + // accepts and handles session cookies, Steam microtransaction checkout pages + // will automatically recognize the user instead of presenting a login page. + // The result of this API call will be a StoreAuthURLResponse_t callback. + // NOTE: The URL has a very short lifetime to prevent history-snooping attacks, + // so you should only call this API when you are about to launch the browser, + // or else immediately navigate to the result URL using a hidden browser window. + // NOTE 2: The resulting authorization cookie has an expiration time of one day, + // so it would be a good idea to request and visit a new auth URL every 12 hours. + STEAM_CALL_RESULT( StoreAuthURLResponse_t ) + SteamAPICall_t RequestStoreAuthURL( const char *pchRedirectURL ); + + // gets whether the users phone number is verified + bool BIsPhoneVerified(); + + // gets whether the user has two factor enabled on their account + bool BIsTwoFactorEnabled(); + + // gets whether the users phone number is identifying + bool BIsPhoneIdentifying(); + + // gets whether the users phone number is awaiting (re)verification + bool BIsPhoneRequiringVerification(); + + STEAM_CALL_RESULT( MarketEligibilityResponse_t ) + SteamAPICall_t GetMarketEligibility(); + + // Retrieves anti indulgence / duration control for current user + STEAM_CALL_RESULT( DurationControl_t ) + SteamAPICall_t GetDurationControl(); + + // Advise steam china duration control system about the online state of the game. + // This will prevent offline gameplay time from counting against a user's + // playtime limits. + bool BSetDurationControlOnlineState( EDurationControlOnlineState eNewState ); }; + +#endif // __INCLUDED_STEAM_USER_H__ diff --git a/dll/dll/steam_user_stats.h b/dll/dll/steam_user_stats.h index 71fba56a..8681a492 100644 --- a/dll/dll/steam_user_stats.h +++ b/dll/dll/steam_user_stats.h @@ -50,21 +50,8 @@ struct achievement_trigger { std::string min_value{}; std::string max_value{}; - bool check_triggered(float stat) { - try { - if (std::stof(max_value) <= stat) return true; - } catch (...) {} - - return false; - } - - bool check_triggered(int32 stat) { - try { - if (std::stoi(max_value) <= stat) return true; - } catch (...) {} - - return false; - } + bool check_triggered(float stat) const; + bool check_triggered(int32 stat) const; }; class Steam_User_Stats : @@ -91,9 +78,9 @@ private: T current_val{}; }; - Local_Storage *local_storage{}; - Settings *settings{}; - SteamCallResults *callback_results{}; + class Local_Storage *local_storage{}; + class Settings *settings{}; + class SteamCallResults *callback_results{}; class SteamCallBacks *callbacks{}; class Networking *network{}; class RunEveryRunCB *run_every_runcb{}; @@ -115,7 +102,6 @@ private: GameServerStats_Messages::AllStats pending_server_updates{}; - void load_achievements_db(); void load_achievements(); void save_achievements(); diff --git a/dll/dll/steam_utils.h b/dll/dll/steam_utils.h index 0b305cf2..7a06bcb4 100644 --- a/dll/dll/steam_utils.h +++ b/dll/dll/steam_utils.h @@ -1,5 +1,3 @@ -#ifndef __STEAM_UTILS_H__ -#define __STEAM_UTILS_H__ /* Copyright (C) 2019 Mr Goldberg This file is part of the Goldberg Emulator @@ -18,6 +16,9 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __STEAM_UTILS_H__ +#define __STEAM_UTILS_H__ + #include "common_includes.h" #include "local_storage.h" #include "overlay/steam_overlay.h" @@ -35,10 +36,10 @@ public ISteamUtils009, public ISteamUtils { private: - Settings *settings; - class SteamCallResults *callback_results; + Settings *settings{}; + class SteamCallResults *callback_results{}; class SteamCallBacks *callbacks{}; - Steam_Overlay* overlay; + Steam_Overlay* overlay{}; public: Steam_Utils(Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, Steam_Overlay *overlay); diff --git a/dll/dll/steam_video.h b/dll/dll/steam_video.h index 9a1282d9..49db2a77 100644 --- a/dll/dll/steam_video.h +++ b/dll/dll/steam_video.h @@ -15,9 +15,13 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_VIDEO_H__ +#define __INCLUDED_STEAM_VIDEO_H__ + #include "base.h" -class Steam_Video : public ISteamVideo +class Steam_Video : +public ISteamVideo { public: @@ -32,3 +36,5 @@ public: void GetOPFSettings( AppId_t unVideoAppID ); bool GetOPFStringForApp( AppId_t unVideoAppID, char *pchBuffer, int32 *pnBufferSize ); }; + +#endif // __INCLUDED_STEAM_VIDEO_H__ diff --git a/dll/dll/ugc_remote_storage_bridge.h b/dll/dll/ugc_remote_storage_bridge.h index d60838d1..95b8987a 100644 --- a/dll/dll/ugc_remote_storage_bridge.h +++ b/dll/dll/ugc_remote_storage_bridge.h @@ -1,3 +1,19 @@ +/* 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_UGC_REMOTE_STORAGE_BRIDGE_H__ #define __INCLUDED_UGC_REMOTE_STORAGE_BRIDGE_H__ @@ -8,19 +24,20 @@ class Ugc_Remote_Storage_Bridge { public: struct QueryInfo { - PublishedFileId_t mod_id; // mod id - bool is_primary_file; // was this query for the primary mod file or preview file + PublishedFileId_t mod_id{}; // mod id + bool is_primary_file{}; // was this query for the primary mod file or preview file }; private: - class Settings *settings; + class Settings *settings{}; // key: UGCHandle_t which is the file handle (primary or preview) // value: the mod id, true if UGCHandle_t of primary file | false if UGCHandle_t of preview file std::map steam_ugc_queries{}; - std::set subscribed; // just to keep the running state of subscription + std::set subscribed{}; // just to keep the running state of subscription public: Ugc_Remote_Storage_Bridge(class Settings *settings); + ~Ugc_Remote_Storage_Bridge(); // called from Steam_UGC::SendQueryUGCRequest() after a successful query void add_ugc_query_result(UGCHandle_t file_handle, PublishedFileId_t fileid, bool handle_of_primary_file); @@ -34,7 +51,6 @@ public: std::set::iterator subbed_mods_itr_begin() const; std::set::iterator subbed_mods_itr_end() const; - ~Ugc_Remote_Storage_Bridge(); }; diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index eaa55205..2b6de0de 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -121,6 +121,19 @@ static void merge_ini(const CSimpleIniA &new_ini, bool overwrite = false) { } +Overlay_Appearance::NotificationPosition Overlay_Appearance::translate_notification_position(const std::string &str) +{ + if (str == "top_left") return NotificationPosition::top_left; + else if (str == "top_center") return NotificationPosition::top_center; + else if (str == "top_right") return NotificationPosition::top_right; + else if (str == "bot_left") return NotificationPosition::bot_left; + else if (str == "bot_center") return NotificationPosition::bot_center; + else if (str == "bot_right") return NotificationPosition::bot_right; + + PRINT_DEBUG("Invalid position '%s'", str.c_str()); + return default_pos; +} + // custom_broadcasts.txt static void load_custom_broadcasts(const std::string &base_path, std::set &custom_broadcasts) diff --git a/dll/source_query.cpp b/dll/source_query.cpp index a2448272..7aaf3b0a 100644 --- a/dll/source_query.cpp +++ b/dll/source_query.cpp @@ -18,58 +18,48 @@ #include "dll/source_query.h" #include "dll/dll.h" -using lock_t = std::lock_guard; - -enum class source_query_magic : uint32_t -{ +enum class source_query_magic : uint32_t { simple = 0xFFFFFFFFul, multi = 0xFFFFFFFEul, // <--- TODO ? }; -enum class source_query_header : uint8_t -{ +enum class source_query_header : uint8_t { A2S_INFO = 'T', A2S_PLAYER = 'U', A2S_RULES = 'V', }; -enum class source_response_header : uint8_t -{ +enum class source_response_header : uint8_t { A2S_CHALLENGE = 'A', A2S_INFO = 'I', A2S_PLAYER = 'D', A2S_RULES = 'E', }; -enum class source_server_type : uint8_t -{ +enum class source_server_type : uint8_t { dedicated = 'd', non_dedicated = 'i', source_tc = 'p', }; -enum class source_server_env : uint8_t -{ +enum class source_server_env : uint8_t { linux = 'l', windows = 'w', old_mac = 'm', mac = 'o', }; -enum class source_server_visibility : uint8_t -{ +enum class source_server_visibility : uint8_t { _public = 0, _private = 1, }; -enum class source_server_vac : uint8_t -{ +enum class source_server_vac : uint8_t { unsecured = 0, secured = 1, }; -enum source_server_extra_flag : uint8_t -{ +enum source_server_extra_flag : uint8_t { none = 0x00, gameid = 0x01, steamid = 0x10, @@ -79,21 +69,20 @@ enum source_server_extra_flag : uint8_t }; #if defined(STEAM_WIN32) -static constexpr source_server_env my_server_env = source_server_env::windows; +static constexpr const source_server_env my_server_env = source_server_env::windows; #else -static constexpr source_server_env my_server_env = source_server_env::linux; +static constexpr const source_server_env my_server_env = source_server_env::linux; #endif + #pragma pack(push) #pragma pack(1) +constexpr static const char a2s_info_payload[] = "Source Engine Query"; +constexpr static const size_t a2s_info_payload_size = sizeof(a2s_info_payload); -constexpr char a2s_info_payload[] = "Source Engine Query"; -constexpr size_t a2s_info_payload_size = sizeof(a2s_info_payload); - -struct source_query_data -{ - source_query_magic magic; - source_query_header header; +struct source_query_data { + source_query_magic magic{}; + source_query_header header{}; union { char a2s_info_payload[a2s_info_payload_size]; @@ -101,12 +90,12 @@ struct source_query_data }; }; -static constexpr size_t source_query_header_size = sizeof(source_query_magic) + sizeof(source_query_header); -static constexpr size_t a2s_query_info_size = source_query_header_size + sizeof(source_query_data::a2s_info_payload); -static constexpr size_t a2s_query_challenge_size = source_query_header_size + sizeof(source_query_data::challenge); - +static constexpr const size_t source_query_header_size = sizeof(source_query_magic) + sizeof(source_query_header); +static constexpr const size_t a2s_query_info_size = source_query_header_size + sizeof(source_query_data::a2s_info_payload); +static constexpr const size_t a2s_query_challenge_size = source_query_header_size + sizeof(source_query_data::challenge); #pragma pack(pop) + void serialize_response(std::vector& buffer, const void* _data, size_t len) { const uint8_t* data = reinterpret_cast(_data); @@ -156,10 +145,9 @@ std::vector Source_Query::handle_source_query(const void* buffer, size_ switch (query.header) { - case source_query_header::A2S_INFO: + case source_query_header::A2S_INFO: { PRINT_DEBUG("got request for server info"); - if (len >= a2s_query_info_size && !strncmp(query.a2s_info_payload, a2s_info_payload, a2s_info_payload_size)) - { + if (len >= a2s_query_info_size && !strncmp(query.a2s_info_payload, a2s_info_payload, a2s_info_payload_size)) { std::vector> const& players = *get_steam_client()->steam_gameserver->get_players(); serialize_response(output_buffer, source_query_magic::simple); @@ -181,93 +169,77 @@ std::vector Source_Query::handle_source_query(const void* buffer, size_ uint8_t flags = source_server_extra_flag::none; - if (gs.port() != 0) - flags |= source_server_extra_flag::port; + if (gs.port() != 0) flags |= source_server_extra_flag::port; - if (gs.spectator_port() != 0) - flags |= source_server_extra_flag::spectator; + if (gs.spectator_port() != 0) flags |= source_server_extra_flag::spectator; - if(CGameID(gs.appid()).IsValid()) - flags |= source_server_extra_flag::gameid; + if (CGameID(gs.appid()).IsValid()) flags |= source_server_extra_flag::gameid; - if (flags != source_server_extra_flag::none) - serialize_response(output_buffer, flags); + if (flags != source_server_extra_flag::none) serialize_response(output_buffer, flags); - if (flags & source_server_extra_flag::port) - serialize_response(output_buffer, static_cast(gs.port())); + if (flags & source_server_extra_flag::port) serialize_response(output_buffer, static_cast(gs.port())); // add steamid - if (flags & source_server_extra_flag::spectator) - { + if (flags & source_server_extra_flag::spectator) { serialize_response(output_buffer, static_cast(gs.spectator_port())); serialize_response(output_buffer, gs.spectator_server_name()); } // keywords - if (flags & source_server_extra_flag::gameid) - serialize_response(output_buffer, CGameID(gs.appid()).ToUint64()); + if (flags & source_server_extra_flag::gameid) serialize_response(output_buffer, CGameID(gs.appid()).ToUint64()); } - break; + } + break; - case source_query_header::A2S_PLAYER: + case source_query_header::A2S_PLAYER: { PRINT_DEBUG("got request for player info"); - if (len >= a2s_query_challenge_size) - { - if (query.challenge == 0xFFFFFFFFul) - { + if (len >= a2s_query_challenge_size) { + if (query.challenge == 0xFFFFFFFFul) { get_challenge(output_buffer); - } - else if (query.challenge == 0x00112233ul) - { + } else if (query.challenge == 0x00112233ul) { std::vector> const& players = *get_steam_client()->steam_gameserver->get_players(); serialize_response(output_buffer, source_query_magic::simple); serialize_response(output_buffer, source_response_header::A2S_PLAYER); serialize_response(output_buffer, static_cast(players.size())); // num_players - for (int i = 0; i < players.size(); ++i) - { + for (int i = 0; i < players.size(); ++i) { serialize_response(output_buffer, static_cast(i)); // player index serialize_response(output_buffer, players[i].second.name); // player name serialize_response(output_buffer, players[i].second.score); // player score serialize_response(output_buffer, static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - players[i].second.join_time).count())); } - } } - break; + } + break; - case source_query_header::A2S_RULES: + case source_query_header::A2S_RULES: { PRINT_DEBUG("got request for rules info"); - if (len >= a2s_query_challenge_size) - { - if (query.challenge == 0xFFFFFFFFul) - { + if (len >= a2s_query_challenge_size) { + if (query.challenge == 0xFFFFFFFFul) { get_challenge(output_buffer); - } - else if (query.challenge == 0x00112233ul) - { + } else if (query.challenge == 0x00112233ul) { auto values = gs.values(); serialize_response(output_buffer, source_query_magic::simple); serialize_response(output_buffer, source_response_header::A2S_RULES); serialize_response(output_buffer, static_cast(values.size())); - for (auto const& i : values) - { + for (const auto &i : values) { serialize_response(output_buffer, i.first); serialize_response(output_buffer, i.second); } } } - break; - - default: - PRINT_DEBUG("got unknown request"); - break; } + break; + + default: PRINT_DEBUG("got unknown request"); break; + } + return output_buffer; } diff --git a/dll/steam_HTMLsurface.cpp b/dll/steam_HTMLsurface.cpp new file mode 100644 index 00000000..8baceffc --- /dev/null +++ b/dll/steam_HTMLsurface.cpp @@ -0,0 +1,363 @@ +/* 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 + . */ + +#include "dll/steam_HTMLsurface.h" + +Steam_HTMLsurface::Steam_HTMLsurface(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks) +{ + this->settings = settings; + this->network = network; + this->callback_results = callback_results; + this->callbacks = callbacks; +} + + +// Must call init and shutdown when starting/ending use of the interface +bool Steam_HTMLsurface::Init() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return true; +} + +bool Steam_HTMLsurface::Shutdown() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return true; +} + + +// Create a browser object for display of a html page, when creation is complete the call handle +// will return a HTML_BrowserReady_t callback for the HHTMLBrowser of your new browser. +// The user agent string is a substring to be added to the general user agent string so you can +// identify your client on web servers. +// The userCSS string lets you apply a CSS style sheet to every displayed page, leave null if +// you do not require this functionality. +// +// YOU MUST HAVE IMPLEMENTED HANDLERS FOR HTML_BrowserReady_t, HTML_StartRequest_t, +// HTML_JSAlert_t, HTML_JSConfirm_t, and HTML_FileOpenDialog_t! See the CALLBACKS +// section of this interface (AllowStartRequest, etc) for more details. If you do +// not implement these callback handlers, the browser may appear to hang instead of +// navigating to new pages or triggering javascript popups. +// +STEAM_CALL_RESULT( HTML_BrowserReady_t ) +SteamAPICall_t Steam_HTMLsurface::CreateBrowser( const char *pchUserAgent, const char *pchUserCSS ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + HTML_BrowserReady_t data; + data.unBrowserHandle = 1234869; + + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return ret; +} + + +// Call this when you are done with a html surface, this lets us free the resources being used by it +void Steam_HTMLsurface::RemoveBrowser( HHTMLBrowser unBrowserHandle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// Navigate to this URL, results in a HTML_StartRequest_t as the request commences +void Steam_HTMLsurface::LoadURL( HHTMLBrowser unBrowserHandle, const char *pchURL, const char *pchPostData ) +{ + PRINT_DEBUG("TODO %s %s", pchURL, pchPostData); + std::lock_guard lock(global_mutex); + static char url[256]; + strncpy(url, pchURL, sizeof(url)); + static char target[] = "_self"; + static char title[] = "title"; + + { + HTML_StartRequest_t data; + data.unBrowserHandle = unBrowserHandle; + data.pchURL = url; + data.pchTarget = target; + data.pchPostData = ""; + data.bIsRedirect = false; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + } + + { + HTML_FinishedRequest_t data; + data.unBrowserHandle = unBrowserHandle; + data.pchURL = url; + data.pchPageTitle = title; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.8); + } + +} + + +// Tells the surface the size in pixels to display the surface +void Steam_HTMLsurface::SetSize( HHTMLBrowser unBrowserHandle, uint32 unWidth, uint32 unHeight ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// Stop the load of the current html page +void Steam_HTMLsurface::StopLoad( HHTMLBrowser unBrowserHandle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// Reload (most likely from local cache) the current page +void Steam_HTMLsurface::Reload( HHTMLBrowser unBrowserHandle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// navigate back in the page history +void Steam_HTMLsurface::GoBack( HHTMLBrowser unBrowserHandle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// navigate forward in the page history +void Steam_HTMLsurface::GoForward( HHTMLBrowser unBrowserHandle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// add this header to any url requests from this browser +void Steam_HTMLsurface::AddHeader( HHTMLBrowser unBrowserHandle, const char *pchKey, const char *pchValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// run this javascript script in the currently loaded page +void Steam_HTMLsurface::ExecuteJavascript( HHTMLBrowser unBrowserHandle, const char *pchScript ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// Mouse click and mouse movement commands +void Steam_HTMLsurface::MouseUp( HHTMLBrowser unBrowserHandle, EHTMLMouseButton eMouseButton ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +void Steam_HTMLsurface::MouseDown( HHTMLBrowser unBrowserHandle, EHTMLMouseButton eMouseButton ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +void Steam_HTMLsurface::MouseDoubleClick( HHTMLBrowser unBrowserHandle, EHTMLMouseButton eMouseButton ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// x and y are relative to the HTML bounds +void Steam_HTMLsurface::MouseMove( HHTMLBrowser unBrowserHandle, int x, int y ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// nDelta is pixels of scroll +void Steam_HTMLsurface::MouseWheel( HHTMLBrowser unBrowserHandle, int32 nDelta ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// keyboard interactions, native keycode is the key code value from your OS +void Steam_HTMLsurface::KeyDown( HHTMLBrowser unBrowserHandle, uint32 nNativeKeyCode, EHTMLKeyModifiers eHTMLKeyModifiers, bool bIsSystemKey ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +void Steam_HTMLsurface::KeyDown( HHTMLBrowser unBrowserHandle, uint32 nNativeKeyCode, EHTMLKeyModifiers eHTMLKeyModifiers) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + KeyDown(unBrowserHandle, nNativeKeyCode, eHTMLKeyModifiers, false); +} + + +void Steam_HTMLsurface::KeyUp( HHTMLBrowser unBrowserHandle, uint32 nNativeKeyCode, EHTMLKeyModifiers eHTMLKeyModifiers ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); +} + +// cUnicodeChar is the unicode character point for this keypress (and potentially multiple chars per press) +void Steam_HTMLsurface::KeyChar( HHTMLBrowser unBrowserHandle, uint32 cUnicodeChar, EHTMLKeyModifiers eHTMLKeyModifiers ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// programmatically scroll this many pixels on the page +void Steam_HTMLsurface::SetHorizontalScroll( HHTMLBrowser unBrowserHandle, uint32 nAbsolutePixelScroll ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +void Steam_HTMLsurface::SetVerticalScroll( HHTMLBrowser unBrowserHandle, uint32 nAbsolutePixelScroll ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// tell the html control if it has key focus currently, controls showing the I-beam cursor in text controls amongst other things +void Steam_HTMLsurface::SetKeyFocus( HHTMLBrowser unBrowserHandle, bool bHasKeyFocus ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// open the current pages html code in the local editor of choice, used for debugging +void Steam_HTMLsurface::ViewSource( HHTMLBrowser unBrowserHandle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// copy the currently selected text on the html page to the local clipboard +void Steam_HTMLsurface::CopyToClipboard( HHTMLBrowser unBrowserHandle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// paste from the local clipboard to the current html page +void Steam_HTMLsurface::PasteFromClipboard( HHTMLBrowser unBrowserHandle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// find this string in the browser, if bCurrentlyInFind is true then instead cycle to the next matching element +void Steam_HTMLsurface::Find( HHTMLBrowser unBrowserHandle, const char *pchSearchStr, bool bCurrentlyInFind, bool bReverse ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// cancel a currently running find +void Steam_HTMLsurface::StopFind( HHTMLBrowser unBrowserHandle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// return details about the link at position x,y on the current page +void Steam_HTMLsurface::GetLinkAtPosition( HHTMLBrowser unBrowserHandle, int x, int y ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// set a webcookie for the hostname in question +void Steam_HTMLsurface::SetCookie( const char *pchHostname, const char *pchKey, const char *pchValue, const char *pchPath, RTime32 nExpires, bool bSecure, bool bHTTPOnly ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// Zoom the current page by flZoom ( from 0.0 to 2.0, so to zoom to 120% use 1.2 ), zooming around point X,Y in the page (use 0,0 if you don't care) +void Steam_HTMLsurface::SetPageScaleFactor( HHTMLBrowser unBrowserHandle, float flZoom, int nPointX, int nPointY ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// Enable/disable low-resource background mode, where javascript and repaint timers are throttled, resources are +// more aggressively purged from memory, and audio/video elements are paused. When background mode is enabled, +// all HTML5 video and audio objects will execute ".pause()" and gain the property "._steam_background_paused = 1". +// When background mode is disabled, any video or audio objects with that property will resume with ".play()". +void Steam_HTMLsurface::SetBackgroundMode( HHTMLBrowser unBrowserHandle, bool bBackgroundMode ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// Scale the output display space by this factor, this is useful when displaying content on high dpi devices. +// Specifies the ratio between physical and logical pixels. +void Steam_HTMLsurface::SetDPIScalingFactor( HHTMLBrowser unBrowserHandle, float flDPIScaling ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +void Steam_HTMLsurface::OpenDeveloperTools( HHTMLBrowser unBrowserHandle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// CALLBACKS +// +// These set of functions are used as responses to callback requests +// + +// You MUST call this in response to a HTML_StartRequest_t callback +// Set bAllowed to true to allow this navigation, false to cancel it and stay +// on the current page. You can use this feature to limit the valid pages +// allowed in your HTML surface. +void Steam_HTMLsurface::AllowStartRequest( HHTMLBrowser unBrowserHandle, bool bAllowed ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// You MUST call this in response to a HTML_JSAlert_t or HTML_JSConfirm_t callback +// Set bResult to true for the OK option of a confirm, use false otherwise +void Steam_HTMLsurface::JSDialogResponse( HHTMLBrowser unBrowserHandle, bool bResult ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// You MUST call this in response to a HTML_FileOpenDialog_t callback +STEAM_IGNOREATTR() +void Steam_HTMLsurface::FileLoadDialogResponse( HHTMLBrowser unBrowserHandle, const char **pchSelectedFiles ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index c6ef3fe7..68d4345e 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -18,13 +18,13 @@ #include "dll/steam_client.h" #include "dll/settings_parser.h" -static std::mutex kill_background_thread_mutex; -static std::condition_variable kill_background_thread_cv; -static bool kill_background_thread; +static std::mutex kill_background_thread_mutex{}; +static std::condition_variable kill_background_thread_cv{}; +static bool kill_background_thread{}; static void background_thread(Steam_Client *client) { // max allowed time in which RunCallbacks() might not be called - constexpr const static auto max_stall_ms = std::chrono::milliseconds(200); + constexpr const static auto max_stall_ms = std::chrono::milliseconds(300); // wait 1 sec { @@ -228,12 +228,13 @@ Steam_Client::~Steam_Client() delete ugc_bridge; ugc_bridge = nullptr; - delete network; network = nullptr; - delete run_every_runcb; run_every_runcb = nullptr; delete callbacks_server; callbacks_server = nullptr; delete callbacks_client; callbacks_client = nullptr; delete callback_results_server; callback_results_server = nullptr; delete callback_results_client; callback_results_client = nullptr; + + delete network; network = nullptr; + delete run_every_runcb; run_every_runcb = nullptr; } void Steam_Client::userLogIn() @@ -275,11 +276,13 @@ void Steam_Client::setAppID(uint32 appid) settings_server->set_game_id(CGameID(appid)); local_storage->setAppId(appid); network->setAppID(appid); - set_env_variable("SteamAppId", std::to_string(appid)); - set_env_variable("SteamGameId", std::to_string(appid)); + + std::string appid_str(std::to_string(appid)); + set_env_variable("SteamAppId", appid_str); + set_env_variable("SteamGameId", appid_str); if (!settings_client->disable_steamoverlaygameid_env_var) { - set_env_variable("SteamOverlayGameId", std::to_string(appid)); + set_env_variable("SteamOverlayGameId", appid_str); } } @@ -1974,5 +1977,5 @@ void Steam_Client::RunCallbacks(bool runClientCB, bool runGameserverCB) void Steam_Client::DestroyAllInterfaces() { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG_TODO(); } diff --git a/dll/steam_controller.cpp b/dll/steam_controller.cpp new file mode 100644 index 00000000..feed2e0d --- /dev/null +++ b/dll/steam_controller.cpp @@ -0,0 +1,1167 @@ +/* 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 + . */ + +#include "dll/steam_controller.h" + +#define JOY_ID_START 10 +#define STICK_DPAD 3 +#define DEADZONE_BUTTON_STICK 0.3 + + + +#if !defined(CONTROLLER_SUPPORT) + +inline void GamepadInit(void) {} +inline void GamepadShutdown(void) {} +inline void GamepadUpdate(void) {} +inline GAMEPAD_BOOL GamepadIsConnected(GAMEPAD_DEVICE device) { return GAMEPAD_FALSE; } +inline GAMEPAD_BOOL GamepadButtonDown(GAMEPAD_DEVICE device, GAMEPAD_BUTTON button) { return GAMEPAD_FALSE; } +inline float GamepadTriggerLength(GAMEPAD_DEVICE device, GAMEPAD_TRIGGER trigger) { return 0.0; } +inline GAMEPAD_STICKDIR GamepadStickDir(GAMEPAD_DEVICE device, GAMEPAD_STICK stick) { return STICKDIR_CENTER; } +inline void GamepadStickNormXY(GAMEPAD_DEVICE device, GAMEPAD_STICK stick, float* outX, float* outY) {} +inline float GamepadStickLength(GAMEPAD_DEVICE device, GAMEPAD_STICK stick) { return 0.0; } +inline void GamepadSetRumble(GAMEPAD_DEVICE device, float left, float right, unsigned int rumble_length_ms) {} + +#endif + + + +Controller_Action::Controller_Action(ControllerHandle_t controller_handle) { + this->controller_handle = controller_handle; +} + +void Controller_Action::activate_action_set(ControllerDigitalActionHandle_t active_set, std::map &controller_maps) { + auto map = controller_maps.find(active_set); + if (map == controller_maps.end()) return; + this->active_set = active_set; + this->active_map = map->second; +} + +std::set Controller_Action::button_id(ControllerDigitalActionHandle_t handle) { + auto a = active_map.active_digital.find(handle); + if (a == active_map.active_digital.end()) return {}; + return a->second; +} + +std::pair, enum EInputSourceMode> Controller_Action::analog_id(ControllerAnalogActionHandle_t handle) { + auto a = active_map.active_analog.find(handle); + if (a == active_map.active_analog.end()) return std::pair, enum EInputSourceMode>({}, k_EInputSourceMode_None); + return a->second; +} + + + +const std::map Steam_Controller::button_strings = { + {"DUP", BUTTON_DPAD_UP}, + {"DDOWN", BUTTON_DPAD_DOWN}, + {"DLEFT", BUTTON_DPAD_LEFT}, + {"DRIGHT", BUTTON_DPAD_RIGHT}, + {"START", BUTTON_START}, + {"BACK", BUTTON_BACK}, + {"LSTICK", BUTTON_LEFT_THUMB}, + {"RSTICK", BUTTON_RIGHT_THUMB}, + {"LBUMPER", BUTTON_LEFT_SHOULDER}, + {"RBUMPER", BUTTON_RIGHT_SHOULDER}, + {"A", BUTTON_A}, + {"B", BUTTON_B}, + {"X", BUTTON_X}, + {"Y", BUTTON_Y}, + {"DLTRIGGER", BUTTON_LTRIGGER}, + {"DRTRIGGER", BUTTON_RTRIGGER}, + {"DLJOYUP", BUTTON_STICK_LEFT_UP}, + {"DLJOYDOWN", BUTTON_STICK_LEFT_DOWN}, + {"DLJOYLEFT", BUTTON_STICK_LEFT_LEFT}, + {"DLJOYRIGHT", BUTTON_STICK_LEFT_RIGHT}, + {"DRJOYUP", BUTTON_STICK_RIGHT_UP}, + {"DRJOYDOWN", BUTTON_STICK_RIGHT_DOWN}, + {"DRJOYLEFT", BUTTON_STICK_RIGHT_LEFT}, + {"DRJOYRIGHT", BUTTON_STICK_RIGHT_RIGHT}, +}; + +const std::map Steam_Controller::analog_strings = { + {"LTRIGGER", TRIGGER_LEFT}, + {"RTRIGGER", TRIGGER_RIGHT}, + {"LJOY", STICK_LEFT + JOY_ID_START}, + {"RJOY", STICK_RIGHT + JOY_ID_START}, + {"DPAD", STICK_DPAD + JOY_ID_START}, +}; + +const std::map Steam_Controller::analog_input_modes = { + {"joystick_move", k_EInputSourceMode_JoystickMove}, + {"joystick_camera", k_EInputSourceMode_JoystickCamera}, + {"trigger", k_EInputSourceMode_Trigger}, +}; + + +void Steam_Controller::set_handles(std::map, std::string>>> action_sets) +{ + uint64 handle_num = 1; + for (auto & set : action_sets) { + ControllerActionSetHandle_t action_handle_num = handle_num; + ++handle_num; + + action_handles[set.first] = action_handle_num; + for (auto & config_key : set.second) { + uint64 current_handle_num = handle_num; + ++handle_num; + + for (auto & button_string : config_key.second.first) { + auto digital = button_strings.find(button_string); + if (digital != button_strings.end()) { + ControllerDigitalActionHandle_t digital_handle_num = current_handle_num; + + if (digital_action_handles.find(config_key.first) == digital_action_handles.end()) { + digital_action_handles[config_key.first] = digital_handle_num; + } else { + digital_handle_num = digital_action_handles[config_key.first]; + } + + controller_maps[action_handle_num].active_digital[digital_handle_num].insert(digital->second); + } else { + auto analog = analog_strings.find(button_string); + if (analog != analog_strings.end()) { + ControllerAnalogActionHandle_t analog_handle_num = current_handle_num; + + enum EInputSourceMode source_mode; + if (analog->second == TRIGGER_LEFT || analog->second == TRIGGER_RIGHT) { + source_mode = k_EInputSourceMode_Trigger; + } else { + source_mode = k_EInputSourceMode_JoystickMove; + } + + auto input_mode = analog_input_modes.find(config_key.second.second); + if (input_mode != analog_input_modes.end()) { + source_mode = input_mode->second; + } + + if (analog_action_handles.find(config_key.first) == analog_action_handles.end()) { + analog_action_handles[config_key.first] = analog_handle_num; + } else { + analog_handle_num = analog_action_handles[config_key.first]; + } + + controller_maps[action_handle_num].active_analog[analog_handle_num].first.insert(analog->second); + controller_maps[action_handle_num].active_analog[analog_handle_num].second = source_mode; + + } else { + PRINT_DEBUG("Did not recognize controller button %s", button_string.c_str()); + continue; + } + } + } + } + } +} + + +void Steam_Controller::background_rumble(Rumble_Thread_Data *data) +{ + while (true) { + unsigned short left, right; + unsigned int rumble_length_ms; + int gamepad = -1; + while (gamepad == -1) { + std::unique_lock lck(data->rumble_mutex); + if (data->kill_rumble_thread) { + return; + } + + data->rumble_thread_cv.wait_for(lck, std::chrono::milliseconds(1000)); + if (data->kill_rumble_thread) { + return; + } + + for (int i = 0; i < GAMEPAD_COUNT; ++i) { + if (data->data[i].new_data) { + left = data->data[i].left; + right = data->data[i].right; + rumble_length_ms = data->data[i].rumble_length_ms; + data->data[i].new_data = false; + if (data->data[i].last_left != left || data->data[i].last_right != right) { + gamepad = i; + data->data[i].last_left = left; + data->data[i].last_right = right; + break; + } + } + } + } + + GamepadSetRumble((GAMEPAD_DEVICE)gamepad, ((double)left) / 65535.0, ((double)right) / 65535.0, rumble_length_ms); + } +} + +void Steam_Controller::steam_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Controller *steam_controller = (Steam_Controller *)object; + steam_controller->RunCallbacks(); +} + +Steam_Controller::Steam_Controller(class Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) +{ + this->settings = settings; + this->callback_results = callback_results; + this->callbacks = callbacks; + this->run_every_runcb = run_every_runcb; + + set_handles(settings->controller_settings.action_sets); + disabled = !action_handles.size(); + initialized = false; + + this->run_every_runcb->add(&Steam_Controller::steam_run_every_runcb, this); +} + +Steam_Controller::~Steam_Controller() +{ + //TODO rm network callbacks + //TODO rumble thread + this->run_every_runcb->remove(&Steam_Controller::steam_run_every_runcb, this); +} + +// Init and Shutdown must be called when starting/ending use of this interface +bool Steam_Controller::Init(bool bExplicitlyCallRunFrame) +{ + PRINT_DEBUG("%u", bExplicitlyCallRunFrame); + std::lock_guard lock(global_mutex); + if (disabled || initialized) { + return true; + } + + GamepadInit(); + GamepadUpdate(); + + for (int i = 1; i < 5; ++i) { + struct Controller_Action cont_action(i); + //Activate the first action set. + //TODO: check exactly what decides which gets activated by default + if (action_handles.size() >= 1) { + cont_action.activate_action_set(action_handles.begin()->second, controller_maps); + } + + controllers.insert(std::pair(i, cont_action)); + } + + rumble_thread_data = new Rumble_Thread_Data(); + background_rumble_thread = std::thread(background_rumble, rumble_thread_data); + + initialized = true; + explicitly_call_run_frame = bExplicitlyCallRunFrame; + return true; +} + +bool Steam_Controller::Init( const char *pchAbsolutePathToControllerConfigVDF ) +{ + PRINT_DEBUG("old"); + return Init(); +} + +bool Steam_Controller::Init() +{ + return Init(true); +} + +bool Steam_Controller::Shutdown() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (disabled || !initialized) { + return true; + } + + controllers = std::map(); + rumble_thread_data->rumble_mutex.lock(); + rumble_thread_data->kill_rumble_thread = true; + rumble_thread_data->rumble_mutex.unlock(); + rumble_thread_data->rumble_thread_cv.notify_one(); + background_rumble_thread.join(); + delete rumble_thread_data; + GamepadShutdown(); + initialized = false; + return true; +} + +void Steam_Controller::SetOverrideMode( const char *pchMode ) +{ + PRINT_DEBUG_TODO(); +} + +// Set the absolute path to the Input Action Manifest file containing the in-game actions +// and file paths to the official configurations. Used in games that bundle Steam Input +// configurations inside of the game depot instead of using the Steam Workshop +bool Steam_Controller::SetInputActionManifestFilePath( const char *pchInputActionManifestAbsolutePath ) +{ + PRINT_DEBUG_TODO(); + //TODO SteamInput005 + return false; +} + +bool Steam_Controller::BWaitForData( bool bWaitForever, uint32 unTimeout ) +{ + PRINT_DEBUG_TODO(); + //TODO SteamInput005 + return false; +} + +// Returns true if new data has been received since the last time action data was accessed +// via GetDigitalActionData or GetAnalogActionData. The game will still need to call +// SteamInput()->RunFrame() or SteamAPI_RunCallbacks() before this to update the data stream +bool Steam_Controller::BNewDataAvailable() +{ + PRINT_DEBUG_TODO(); + //TODO SteamInput005 + return false; +} + +// Enable SteamInputDeviceConnected_t and SteamInputDeviceDisconnected_t callbacks. +// Each controller that is already connected will generate a device connected +// callback when you enable them +void Steam_Controller::EnableDeviceCallbacks() +{ + PRINT_DEBUG_TODO(); + //TODO SteamInput005 + return; +} + +// Enable SteamInputActionEvent_t callbacks. Directly calls your callback function +// for lower latency than standard Steam callbacks. Supports one callback at a time. +// Note: this is called within either SteamInput()->RunFrame or by SteamAPI_RunCallbacks +void Steam_Controller::EnableActionEventCallbacks( SteamInputActionEventCallbackPointer pCallback ) +{ + PRINT_DEBUG_TODO(); + //TODO SteamInput005 + return; +} + +// Synchronize API state with the latest Steam Controller inputs available. This +// is performed automatically by SteamAPI_RunCallbacks, but for the absolute lowest +// possible latency, you call this directly before reading controller state. +void Steam_Controller::RunFrame(bool bReservedValue) +{ + PRINT_DEBUG_ENTRY(); + if (disabled || !initialized) { + return; + } + + GamepadUpdate(); +} + +void Steam_Controller::RunFrame() +{ + RunFrame(true); +} + +bool Steam_Controller::GetControllerState( uint32 unControllerIndex, SteamControllerState001_t *pState ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +// Enumerate currently connected controllers +// handlesOut should point to a STEAM_CONTROLLER_MAX_COUNT sized array of ControllerHandle_t handles +// Returns the number of handles written to handlesOut +int Steam_Controller::GetConnectedControllers( ControllerHandle_t *handlesOut ) +{ + PRINT_DEBUG_ENTRY(); + if (!handlesOut) return 0; + if (disabled) { + return 0; + } + + int count = 0; + if (GamepadIsConnected(GAMEPAD_0)) {*handlesOut = GAMEPAD_0 + 1; ++handlesOut; ++count;}; + if (GamepadIsConnected(GAMEPAD_1)) {*handlesOut = GAMEPAD_1 + 1; ++handlesOut; ++count;}; + if (GamepadIsConnected(GAMEPAD_2)) {*handlesOut = GAMEPAD_2 + 1; ++handlesOut; ++count;}; + if (GamepadIsConnected(GAMEPAD_3)) {*handlesOut = GAMEPAD_3 + 1; ++handlesOut; ++count;}; + + PRINT_DEBUG("returned %i connected controllers", count); + return count; +} + + +// Invokes the Steam overlay and brings up the binding screen +// Returns false is overlay is disabled / unavailable, or the user is not in Big Picture mode +bool Steam_Controller::ShowBindingPanel( ControllerHandle_t controllerHandle ) +{ + PRINT_DEBUG_TODO(); + return false; +} + + +// ACTION SETS +// Lookup the handle for an Action Set. Best to do this once on startup, and store the handles for all future API calls. +ControllerActionSetHandle_t Steam_Controller::GetActionSetHandle( const char *pszActionSetName ) +{ + PRINT_DEBUG("%s", pszActionSetName); + if (!pszActionSetName) return 0; + std::string upper_action_name(pszActionSetName); + std::transform(upper_action_name.begin(), upper_action_name.end(), upper_action_name.begin(),[](unsigned char c){ return std::toupper(c); }); + + auto set_handle = action_handles.find(upper_action_name); + if (set_handle == action_handles.end()) return 0; + + PRINT_DEBUG("%s ret %llu", pszActionSetName, set_handle->second); + return set_handle->second; +} + + +// Reconfigure the controller to use the specified action set (ie 'Menu', 'Walk' or 'Drive') +// This is cheap, and can be safely called repeatedly. It's often easier to repeatedly call it in +// your state loops, instead of trying to place it in all of your state transitions. +void Steam_Controller::ActivateActionSet( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle ) +{ + PRINT_DEBUG("%llu %llu", controllerHandle, actionSetHandle); + if (controllerHandle == STEAM_CONTROLLER_HANDLE_ALL_CONTROLLERS) { + for (auto & c: controllers) { + c.second.activate_action_set(actionSetHandle, controller_maps); + } + } + + auto controller = controllers.find(controllerHandle); + if (controller == controllers.end()) return; + + controller->second.activate_action_set(actionSetHandle, controller_maps); +} + +ControllerActionSetHandle_t Steam_Controller::GetCurrentActionSet( ControllerHandle_t controllerHandle ) +{ + //TODO: should return zero if no action set specifically activated with ActivateActionSet + PRINT_DEBUG("%llu", controllerHandle); + auto controller = controllers.find(controllerHandle); + if (controller == controllers.end()) return 0; + + return controller->second.active_set; +} + + +void Steam_Controller::ActivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ) +{ + PRINT_DEBUG_TODO(); +} + +void Steam_Controller::DeactivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ) +{ + PRINT_DEBUG_TODO(); +} + +void Steam_Controller::DeactivateAllActionSetLayers( ControllerHandle_t controllerHandle ) +{ + PRINT_DEBUG_TODO(); +} + +int Steam_Controller::GetActiveActionSetLayers( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t *handlesOut ) +{ + PRINT_DEBUG_TODO(); + return 0; +} + + + +// ACTIONS +// Lookup the handle for a digital action. Best to do this once on startup, and store the handles for all future API calls. +ControllerDigitalActionHandle_t Steam_Controller::GetDigitalActionHandle( const char *pszActionName ) +{ + PRINT_DEBUG("%s", pszActionName); + if (!pszActionName) return 0; + std::string upper_action_name(pszActionName); + std::transform(upper_action_name.begin(), upper_action_name.end(), upper_action_name.begin(),[](unsigned char c){ return std::toupper(c); }); + + auto handle = digital_action_handles.find(upper_action_name); + if (handle == digital_action_handles.end()) { + //apparently GetDigitalActionHandle also works with analog handles + handle = analog_action_handles.find(upper_action_name); + if (handle == analog_action_handles.end()) return 0; + } + + PRINT_DEBUG("%s ret %llu", pszActionName, handle->second); + return handle->second; +} + + +// Returns the current state of the supplied digital game action +ControllerDigitalActionData_t Steam_Controller::GetDigitalActionData( ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle ) +{ + PRINT_DEBUG("%llu %llu", controllerHandle, digitalActionHandle); + ControllerDigitalActionData_t digitalData; + digitalData.bActive = false; + digitalData.bState = false; + + auto controller = controllers.find(controllerHandle); + if (controller == controllers.end()) return digitalData; + + std::set buttons = controller->second.button_id(digitalActionHandle); + if (!buttons.size()) return digitalData; + digitalData.bActive = true; + + GAMEPAD_DEVICE device = (GAMEPAD_DEVICE)(controllerHandle - 1); + + for (auto button : buttons) { + bool pressed = false; + if (button < BUTTON_COUNT) { + pressed = GamepadButtonDown(device, (GAMEPAD_BUTTON)button); + } else { + switch (button) { + case BUTTON_LTRIGGER: + pressed = GamepadTriggerLength(device, TRIGGER_LEFT) > 0.8; + break; + case BUTTON_RTRIGGER: + pressed = GamepadTriggerLength(device, TRIGGER_RIGHT) > 0.8; + break; + case BUTTON_STICK_LEFT_UP: + case BUTTON_STICK_LEFT_DOWN: + case BUTTON_STICK_LEFT_LEFT: + case BUTTON_STICK_LEFT_RIGHT: { + float x = 0, y = 0, len = GamepadStickLength(device, STICK_LEFT); + GamepadStickNormXY(device, STICK_LEFT, &x, &y); + x *= len; + y *= len; + if (button == BUTTON_STICK_LEFT_UP) pressed = y > DEADZONE_BUTTON_STICK; + if (button == BUTTON_STICK_LEFT_DOWN) pressed = y < -DEADZONE_BUTTON_STICK; + if (button == BUTTON_STICK_LEFT_RIGHT) pressed = x > DEADZONE_BUTTON_STICK; + if (button == BUTTON_STICK_LEFT_LEFT) pressed = x < -DEADZONE_BUTTON_STICK; + break; + } + case BUTTON_STICK_RIGHT_UP: + case BUTTON_STICK_RIGHT_DOWN: + case BUTTON_STICK_RIGHT_LEFT: + case BUTTON_STICK_RIGHT_RIGHT: { + float x = 0, y = 0, len = GamepadStickLength(device, STICK_RIGHT); + GamepadStickNormXY(device, STICK_RIGHT, &x, &y); + x *= len; + y *= len; + if (button == BUTTON_STICK_RIGHT_UP) pressed = y > DEADZONE_BUTTON_STICK; + if (button == BUTTON_STICK_RIGHT_DOWN) pressed = y < -DEADZONE_BUTTON_STICK; + if (button == BUTTON_STICK_RIGHT_RIGHT) pressed = x > DEADZONE_BUTTON_STICK; + if (button == BUTTON_STICK_RIGHT_LEFT) pressed = x < -DEADZONE_BUTTON_STICK; + break; + } + default: + break; + } + } + + if (pressed) { + digitalData.bState = true; + break; + } + } + + return digitalData; +} + + +// Get the origin(s) for a digital action within an action set. Returns the number of origins supplied in originsOut. Use this to display the appropriate on-screen prompt for the action. +// originsOut should point to a STEAM_CONTROLLER_MAX_ORIGINS sized array of EControllerActionOrigin handles +int Steam_Controller::GetDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerDigitalActionHandle_t digitalActionHandle, EControllerActionOrigin *originsOut ) +{ + PRINT_DEBUG_ENTRY(); + EInputActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; + int ret = GetDigitalActionOrigins(controllerHandle, actionSetHandle, digitalActionHandle, origins ); + for (int i = 0; i < ret; ++i) { + originsOut[i] = (EControllerActionOrigin)(origins[i] - (k_EInputActionOrigin_XBox360_A - k_EControllerActionOrigin_XBox360_A)); + } + + return ret; +} + +int Steam_Controller::GetDigitalActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputDigitalActionHandle_t digitalActionHandle, EInputActionOrigin *originsOut ) +{ + PRINT_DEBUG_ENTRY(); + auto controller = controllers.find(inputHandle); + if (controller == controllers.end()) return 0; + + auto map = controller_maps.find(actionSetHandle); + if (map == controller_maps.end()) return 0; + + auto a = map->second.active_digital.find(digitalActionHandle); + if (a == map->second.active_digital.end()) return 0; + + int count = 0; + for (auto button: a->second) { + switch (button) { + case BUTTON_A: + originsOut[count] = k_EInputActionOrigin_XBox360_A; + break; + case BUTTON_B: + originsOut[count] = k_EInputActionOrigin_XBox360_B; + break; + case BUTTON_X: + originsOut[count] = k_EInputActionOrigin_XBox360_X; + break; + case BUTTON_Y: + originsOut[count] = k_EInputActionOrigin_XBox360_Y; + break; + case BUTTON_LEFT_SHOULDER: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftBumper; + break; + case BUTTON_RIGHT_SHOULDER: + originsOut[count] = k_EInputActionOrigin_XBox360_RightBumper; + break; + case BUTTON_START: + originsOut[count] = k_EInputActionOrigin_XBox360_Start; + break; + case BUTTON_BACK: + originsOut[count] = k_EInputActionOrigin_XBox360_Back; + break; + case BUTTON_LTRIGGER: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftTrigger_Click; + break; + case BUTTON_RTRIGGER: + originsOut[count] = k_EInputActionOrigin_XBox360_RightTrigger_Click; + break; + case BUTTON_LEFT_THUMB: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_Click; + break; + case BUTTON_RIGHT_THUMB: + originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_Click; + break; + + case BUTTON_STICK_LEFT_UP: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_DPadNorth; + break; + case BUTTON_STICK_LEFT_DOWN: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_DPadSouth; + break; + case BUTTON_STICK_LEFT_LEFT: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_DPadWest; + break; + case BUTTON_STICK_LEFT_RIGHT: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_DPadEast; + break; + + case BUTTON_STICK_RIGHT_UP: + originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_DPadNorth; + break; + case BUTTON_STICK_RIGHT_DOWN: + originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_DPadSouth; + break; + case BUTTON_STICK_RIGHT_LEFT: + originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_DPadWest; + break; + case BUTTON_STICK_RIGHT_RIGHT: + originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_DPadEast; + break; + + case BUTTON_DPAD_UP: + originsOut[count] = k_EInputActionOrigin_XBox360_DPad_North; + break; + case BUTTON_DPAD_DOWN: + originsOut[count] = k_EInputActionOrigin_XBox360_DPad_South; + break; + case BUTTON_DPAD_LEFT: + originsOut[count] = k_EInputActionOrigin_XBox360_DPad_West; + break; + case BUTTON_DPAD_RIGHT: + originsOut[count] = k_EInputActionOrigin_XBox360_DPad_East; + break; + + default: + originsOut[count] = k_EInputActionOrigin_None; + break; + } + + ++count; + if (count >= STEAM_INPUT_MAX_ORIGINS) { + break; + } + } + + return count; +} + +// Returns a localized string (from Steam's language setting) for the user-facing action name corresponding to the specified handle +const char* Steam_Controller::GetStringForDigitalActionName( InputDigitalActionHandle_t eActionHandle ) +{ + PRINT_DEBUG_TODO(); + //TODO SteamInput005 + return "Button String"; +} + +// Lookup the handle for an analog action. Best to do this once on startup, and store the handles for all future API calls. +ControllerAnalogActionHandle_t Steam_Controller::GetAnalogActionHandle( const char *pszActionName ) +{ + PRINT_DEBUG("%s", pszActionName); + if (!pszActionName) return 0; + std::string upper_action_name(pszActionName); + std::transform(upper_action_name.begin(), upper_action_name.end(), upper_action_name.begin(),[](unsigned char c){ return std::toupper(c); }); + + auto handle = analog_action_handles.find(upper_action_name); + if (handle == analog_action_handles.end()) return 0; + + return handle->second; +} + + +// Returns the current state of these supplied analog game action +ControllerAnalogActionData_t Steam_Controller::GetAnalogActionData( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle ) +{ + PRINT_DEBUG("%llu %llu", controllerHandle, analogActionHandle); + GAMEPAD_DEVICE device = (GAMEPAD_DEVICE)(controllerHandle - 1); + + ControllerAnalogActionData_t data; + data.eMode = k_EInputSourceMode_None; + data.x = data.y = 0; + data.bActive = false; + + auto controller = controllers.find(controllerHandle); + if (controller == controllers.end()) return data; + + auto analog = controller->second.analog_id(analogActionHandle); + if (!analog.first.size()) return data; + + data.bActive = true; + data.eMode = analog.second; + + for (auto a : analog.first) { + if (a >= JOY_ID_START) { + int joystick_id = a - JOY_ID_START; + if (joystick_id == STICK_DPAD) { + int mov_y = (int)GamepadButtonDown(device, BUTTON_DPAD_UP) - (int)GamepadButtonDown(device, BUTTON_DPAD_DOWN); + int mov_x = (int)GamepadButtonDown(device, BUTTON_DPAD_RIGHT) - (int)GamepadButtonDown(device, BUTTON_DPAD_LEFT); + if (mov_y || mov_x) { + data.x = mov_x; + data.y = mov_y; + double length = 1.0 / std::sqrt(data.x * data.x + data.y * data.y); + data.x = data.x * length; + data.y = data.y * length; + } + } else { + GamepadStickNormXY(device, (GAMEPAD_STICK) joystick_id, &data.x, &data.y); + float length = GamepadStickLength(device, (GAMEPAD_STICK) joystick_id); + data.x = data.x * length; + data.y = data.y * length; + } + } else { + data.x = GamepadTriggerLength(device, (GAMEPAD_TRIGGER) a); + } + + if (data.x || data.y) { + break; + } + } + + return data; +} + + +// Get the origin(s) for an analog action within an action set. Returns the number of origins supplied in originsOut. Use this to display the appropriate on-screen prompt for the action. +// originsOut should point to a STEAM_CONTROLLER_MAX_ORIGINS sized array of EControllerActionOrigin handles +int Steam_Controller::GetAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerAnalogActionHandle_t analogActionHandle, EControllerActionOrigin *originsOut ) +{ + PRINT_DEBUG_ENTRY(); + EInputActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; + int ret = GetAnalogActionOrigins(controllerHandle, actionSetHandle, analogActionHandle, origins ); + for (int i = 0; i < ret; ++i) { + originsOut[i] = (EControllerActionOrigin)(origins[i] - (k_EInputActionOrigin_XBox360_A - k_EControllerActionOrigin_XBox360_A)); + } + + return ret; +} + +int Steam_Controller::GetAnalogActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputAnalogActionHandle_t analogActionHandle, EInputActionOrigin *originsOut ) +{ + PRINT_DEBUG_ENTRY(); + auto controller = controllers.find(inputHandle); + if (controller == controllers.end()) return 0; + + auto map = controller_maps.find(actionSetHandle); + if (map == controller_maps.end()) return 0; + + auto a = map->second.active_analog.find(analogActionHandle); + if (a == map->second.active_analog.end()) return 0; + + int count = 0; + for (auto b: a->second.first) { + switch (b) { + case TRIGGER_LEFT: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftTrigger_Pull; + break; + case TRIGGER_RIGHT: + originsOut[count] = k_EInputActionOrigin_XBox360_RightTrigger_Pull; + break; + case STICK_LEFT + JOY_ID_START: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_Move; + break; + case STICK_RIGHT + JOY_ID_START: + originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_Move; + break; + case STICK_DPAD + JOY_ID_START: + originsOut[count] = k_EInputActionOrigin_XBox360_DPad_Move; + break; + default: + originsOut[count] = k_EInputActionOrigin_None; + break; + } + + ++count; + if (count >= STEAM_INPUT_MAX_ORIGINS) { + break; + } + } + + return count; +} + + +void Steam_Controller::StopAnalogActionMomentum( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t eAction ) +{ + PRINT_DEBUG("%llu %llu", controllerHandle, eAction); +} + + +// Trigger a haptic pulse on a controller +void Steam_Controller::TriggerHapticPulse( ControllerHandle_t controllerHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec ) +{ + PRINT_DEBUG_TODO(); +} + +// Trigger a haptic pulse on a controller +void Steam_Controller::Legacy_TriggerHapticPulse( InputHandle_t inputHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec ) +{ + PRINT_DEBUG_TODO(); + TriggerHapticPulse(inputHandle, eTargetPad, usDurationMicroSec ); +} + +void Steam_Controller::TriggerHapticPulse( uint32 unControllerIndex, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec ) +{ + PRINT_DEBUG("old"); + TriggerHapticPulse(unControllerIndex, eTargetPad, usDurationMicroSec ); +} + +// Trigger a pulse with a duty cycle of usDurationMicroSec / usOffMicroSec, unRepeat times. +// nFlags is currently unused and reserved for future use. +void Steam_Controller::TriggerRepeatedHapticPulse( ControllerHandle_t controllerHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec, unsigned short usOffMicroSec, unsigned short unRepeat, unsigned int nFlags ) +{ + PRINT_DEBUG_TODO(); +} + +void Steam_Controller::Legacy_TriggerRepeatedHapticPulse( InputHandle_t inputHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec, unsigned short usOffMicroSec, unsigned short unRepeat, unsigned int nFlags ) +{ + PRINT_DEBUG_TODO(); + TriggerRepeatedHapticPulse(inputHandle, eTargetPad, usDurationMicroSec, usOffMicroSec, unRepeat, nFlags); +} + + +// Send a haptic pulse, works on Steam Deck and Steam Controller devices +void Steam_Controller::TriggerSimpleHapticEvent( InputHandle_t inputHandle, EControllerHapticLocation eHapticLocation, uint8 nIntensity, char nGainDB, uint8 nOtherIntensity, char nOtherGainDB ) +{ + PRINT_DEBUG_TODO(); +} + +// Tigger a vibration event on supported controllers. +void Steam_Controller::TriggerVibration( ControllerHandle_t controllerHandle, unsigned short usLeftSpeed, unsigned short usRightSpeed ) +{ + PRINT_DEBUG("%hu %hu", usLeftSpeed, usRightSpeed); + auto controller = controllers.find(controllerHandle); + if (controller == controllers.end()) return; + + unsigned int rumble_length_ms = 0; + +#if defined(__linux__) + //FIXME: shadow of the tomb raider on linux doesn't seem to turn off the rumble so I made it expire after 100ms. Need to check if this is how linux steam actually behaves. + rumble_length_ms = 100; +#endif + + unsigned gamepad_device = (controllerHandle - 1); + if (gamepad_device > GAMEPAD_COUNT) return; + rumble_thread_data->rumble_mutex.lock(); + rumble_thread_data->data[gamepad_device].new_data = true; + rumble_thread_data->data[gamepad_device].left = usLeftSpeed; + rumble_thread_data->data[gamepad_device].right = usRightSpeed; + rumble_thread_data->data[gamepad_device].rumble_length_ms = rumble_length_ms; + rumble_thread_data->rumble_mutex.unlock(); + rumble_thread_data->rumble_thread_cv.notify_one(); +} + +// Trigger a vibration event on supported controllers including Xbox trigger impulse rumble - Steam will translate these commands into haptic pulses for Steam Controllers +void Steam_Controller::TriggerVibrationExtended( InputHandle_t inputHandle, unsigned short usLeftSpeed, unsigned short usRightSpeed, unsigned short usLeftTriggerSpeed, unsigned short usRightTriggerSpeed ) +{ + PRINT_DEBUG_TODO(); + TriggerVibration(inputHandle, usLeftSpeed, usRightSpeed); + //TODO trigger impulse rumbles +} + +// Set the controller LED color on supported controllers. +void Steam_Controller::SetLEDColor( ControllerHandle_t controllerHandle, uint8 nColorR, uint8 nColorG, uint8 nColorB, unsigned int nFlags ) +{ + PRINT_DEBUG_TODO(); +} + + +// Returns the associated gamepad index for the specified controller, if emulating a gamepad +int Steam_Controller::GetGamepadIndexForController( ControllerHandle_t ulControllerHandle ) +{ + PRINT_DEBUG_ENTRY(); + auto controller = controllers.find(ulControllerHandle); + if (controller == controllers.end()) return -1; + + return ulControllerHandle - 1; +} + + +// Returns the associated controller handle for the specified emulated gamepad +ControllerHandle_t Steam_Controller::GetControllerForGamepadIndex( int nIndex ) +{ + PRINT_DEBUG("%i", nIndex); + ControllerHandle_t out = nIndex + 1; + auto controller = controllers.find(out); + if (controller == controllers.end()) return 0; + return out; +} + + +// Returns raw motion data from the specified controller +ControllerMotionData_t Steam_Controller::GetMotionData( ControllerHandle_t controllerHandle ) +{ + PRINT_DEBUG_TODO(); + ControllerMotionData_t data = {}; + return data; +} + + +// Attempt to display origins of given action in the controller HUD, for the currently active action set +// Returns false is overlay is disabled / unavailable, or the user is not in Big Picture mode +bool Steam_Controller::ShowDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle, float flScale, float flXPosition, float flYPosition ) +{ + PRINT_DEBUG_TODO(); + return true; +} + +bool Steam_Controller::ShowAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle, float flScale, float flXPosition, float flYPosition ) +{ + PRINT_DEBUG_TODO(); + return true; +} + + +// Returns a localized string (from Steam's language setting) for the specified origin +const char* Steam_Controller::GetStringForActionOrigin( EControllerActionOrigin eOrigin ) +{ + PRINT_DEBUG_TODO(); + return "Button String"; +} + +const char* Steam_Controller::GetStringForActionOrigin( EInputActionOrigin eOrigin ) +{ + PRINT_DEBUG_TODO(); + return "Button String"; +} + +// Returns a localized string (from Steam's language setting) for the user-facing action name corresponding to the specified handle +const char* Steam_Controller::GetStringForAnalogActionName( InputAnalogActionHandle_t eActionHandle ) +{ + PRINT_DEBUG_TODO(); + //TODO SteamInput005 + return "Button String"; +} + +// Get a local path to art for on-screen glyph for a particular origin +const char* Steam_Controller::GetGlyphForActionOrigin( EControllerActionOrigin eOrigin ) +{ + PRINT_DEBUG("%i", eOrigin); + + if (steamcontroller_glyphs.empty()) { + std::string dir = settings->glyphs_directory; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_A] = dir + "button_a.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_B] = dir + "button_b.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_X] = dir + "button_x.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_Y] = dir + "button_y.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftBumper] = dir + "shoulder_l.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightBumper] = dir + "shoulder_r.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_Start] = dir + "xbox_button_start.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_Back] = dir + "xbox_button_select.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftTrigger_Pull] = dir + "trigger_l_pull.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftTrigger_Click] = dir + "trigger_l_click.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightTrigger_Pull] = dir + "trigger_r_pull.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightTrigger_Click] = dir + "trigger_r_click.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftStick_Move] = dir + "stick_l_move.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftStick_Click] = dir + "stick_l_click.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftStick_DPadNorth] = dir + "stick_dpad_n.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftStick_DPadSouth] = dir + "stick_dpad_s.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftStick_DPadWest] = dir + "stick_dpad_w.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_LeftStick_DPadEast] = dir + "stick_dpad_e.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightStick_Move] = dir + "stick_r_move.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightStick_Click] = dir + "stick_r_click.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightStick_DPadNorth] = dir + "stick_dpad_n.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightStick_DPadSouth] = dir + "stick_dpad_s.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightStick_DPadWest] = dir + "stick_dpad_w.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_RightStick_DPadEast] = dir + "stick_dpad_e.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_DPad_North] = dir + "xbox_button_dpad_n.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_DPad_South] = dir + "xbox_button_dpad_s.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_DPad_West] = dir + "xbox_button_dpad_w.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_DPad_East] = dir + "xbox_button_dpad_e.png"; + steamcontroller_glyphs[k_EControllerActionOrigin_XBox360_DPad_Move] = dir + "xbox_button_dpad_move.png"; + } + + auto glyph = steamcontroller_glyphs.find(eOrigin); + if (glyph == steamcontroller_glyphs.end()) return ""; + return glyph->second.c_str(); +} + +const char* Steam_Controller::GetGlyphForActionOrigin( EInputActionOrigin eOrigin ) +{ + PRINT_DEBUG("steaminput %i", eOrigin); + if (steaminput_glyphs.empty()) { + std::string dir = settings->glyphs_directory; + steaminput_glyphs[k_EInputActionOrigin_XBox360_A] = dir + "button_a.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_B] = dir + "button_b.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_X] = dir + "button_x.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_Y] = dir + "button_y.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftBumper] = dir + "shoulder_l.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_RightBumper] = dir + "shoulder_r.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_Start] = dir + "xbox_button_start.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_Back] = dir + "xbox_button_select.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftTrigger_Pull] = dir + "trigger_l_pull.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftTrigger_Click] = dir + "trigger_l_click.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_RightTrigger_Pull] = dir + "trigger_r_pull.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_RightTrigger_Click] = dir + "trigger_r_click.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftStick_Move] = dir + "stick_l_move.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftStick_Click] = dir + "stick_l_click.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftStick_DPadNorth] = dir + "stick_dpad_n.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftStick_DPadSouth] = dir + "stick_dpad_s.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftStick_DPadWest] = dir + "stick_dpad_w.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_LeftStick_DPadEast] = dir + "stick_dpad_e.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_RightStick_Move] = dir + "stick_r_move.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_RightStick_Click] = dir + "stick_r_click.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_RightStick_DPadNorth] = dir + "stick_dpad_n.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_RightStick_DPadSouth] = dir + "stick_dpad_s.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_RightStick_DPadWest] = dir + "stick_dpad_w.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_RightStick_DPadEast] = dir + "stick_dpad_e.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_DPad_North] = dir + "xbox_button_dpad_n.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_DPad_South] = dir + "xbox_button_dpad_s.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_DPad_West] = dir + "xbox_button_dpad_w.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_DPad_East] = dir + "xbox_button_dpad_e.png"; + steaminput_glyphs[k_EInputActionOrigin_XBox360_DPad_Move] = dir + "xbox_button_dpad_move.png"; + //steaminput_glyphs[] = dir + ""; + } + + auto glyph = steaminput_glyphs.find(eOrigin); + if (glyph == steaminput_glyphs.end()) return ""; + return glyph->second.c_str(); +} + +// Get a local path to a PNG file for the provided origin's glyph. +const char* Steam_Controller::GetGlyphPNGForActionOrigin( EInputActionOrigin eOrigin, ESteamInputGlyphSize eSize, uint32 unFlags ) +{ + PRINT_DEBUG_TODO(); + //TODO SteamInput005 + return GetGlyphForActionOrigin(eOrigin); +} + +// Get a local path to a SVG file for the provided origin's glyph. +const char* Steam_Controller::GetGlyphSVGForActionOrigin( EInputActionOrigin eOrigin, uint32 unFlags ) +{ + PRINT_DEBUG_TODO(); + //TODO SteamInput005 + return ""; +} + +// Get a local path to an older, Big Picture Mode-style PNG file for a particular origin +const char* Steam_Controller::GetGlyphForActionOrigin_Legacy( EInputActionOrigin eOrigin ) +{ + PRINT_DEBUG_ENTRY(); + return GetGlyphForActionOrigin(eOrigin); +} + +// Returns the input type for a particular handle +ESteamInputType Steam_Controller::GetInputTypeForHandle( ControllerHandle_t controllerHandle ) +{ + PRINT_DEBUG("%llu", controllerHandle); + auto controller = controllers.find(controllerHandle); + if (controller == controllers.end()) return k_ESteamInputType_Unknown; + return k_ESteamInputType_XBox360Controller; +} + +const char* Steam_Controller::GetStringForXboxOrigin( EXboxOrigin eOrigin ) +{ + PRINT_DEBUG_TODO(); + return ""; +} + +const char* Steam_Controller::GetGlyphForXboxOrigin( EXboxOrigin eOrigin ) +{ + PRINT_DEBUG_TODO(); + return ""; +} + +EControllerActionOrigin Steam_Controller::GetActionOriginFromXboxOrigin_( ControllerHandle_t controllerHandle, EXboxOrigin eOrigin ) +{ + PRINT_DEBUG_TODO(); + return k_EControllerActionOrigin_None; +} + +EInputActionOrigin Steam_Controller::GetActionOriginFromXboxOrigin( InputHandle_t inputHandle, EXboxOrigin eOrigin ) +{ + PRINT_DEBUG_TODO(); + return k_EInputActionOrigin_None; +} + +EControllerActionOrigin Steam_Controller::TranslateActionOrigin( ESteamInputType eDestinationInputType, EControllerActionOrigin eSourceOrigin ) +{ + PRINT_DEBUG_TODO(); + return k_EControllerActionOrigin_None; +} + +EInputActionOrigin Steam_Controller::TranslateActionOrigin( ESteamInputType eDestinationInputType, EInputActionOrigin eSourceOrigin ) +{ + PRINT_DEBUG("steaminput destinationinputtype %d sourceorigin %d", eDestinationInputType, eSourceOrigin ); + + if (eDestinationInputType == k_ESteamInputType_XBox360Controller) + return eSourceOrigin; + + return k_EInputActionOrigin_None; +} + +bool Steam_Controller::GetControllerBindingRevision( ControllerHandle_t controllerHandle, int *pMajor, int *pMinor ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +bool Steam_Controller::GetDeviceBindingRevision( InputHandle_t inputHandle, int *pMajor, int *pMinor ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +uint32 Steam_Controller::GetRemotePlaySessionID( InputHandle_t inputHandle ) +{ + PRINT_DEBUG_TODO(); + return 0; +} + +// Get a bitmask of the Steam Input Configuration types opted in for the current session. Returns ESteamInputConfigurationEnableType values.? +// Note: user can override the settings from the Steamworks Partner site so the returned values may not exactly match your default configuration +uint16 Steam_Controller::GetSessionInputConfigurationSettings() +{ + PRINT_DEBUG_TODO(); + return 0; +} + +// Set the trigger effect for a DualSense controller +void Steam_Controller::SetDualSenseTriggerEffect( InputHandle_t inputHandle, const ScePadTriggerEffectParam *pParam ) +{ + PRINT_DEBUG_TODO(); +} + +void Steam_Controller::RunCallbacks() +{ + if (explicitly_call_run_frame) { + RunFrame(); + } +} diff --git a/dll/steam_friends.cpp b/dll/steam_friends.cpp new file mode 100644 index 00000000..85fb161a --- /dev/null +++ b/dll/steam_friends.cpp @@ -0,0 +1,1291 @@ +/* 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 + . */ + +#include "dll/steam_friends.h" + +#define SEND_FRIEND_RATE 4.0 + + +Friend* Steam_Friends::find_friend(CSteamID id) +{ + auto f = std::find_if(friends.begin(), friends.end(), [&id](Friend const& item) { return item.id() == id.ConvertToUint64(); }); + if (friends.end() == f) + return NULL; + + return &(*f); +} + +void Steam_Friends::persona_change(CSteamID id, EPersonaChange flags) +{ + PersonaStateChange_t data; + data.m_ulSteamID = id.ConvertToUint64(); + data.m_nChangeFlags = flags; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); +} + +void Steam_Friends::rich_presence_updated(CSteamID id, AppId_t appid) +{ + FriendRichPresenceUpdate_t data; + data.m_steamIDFriend = id; + data.m_nAppID = appid; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); +} + +bool Steam_Friends::isAppIdCompatible(Friend *f) +{ + if (settings->is_lobby_connect) return true; + if (f == &us) return true; + return settings->get_local_game_id().AppID() == f->appid(); +} + +struct Avatar_Numbers Steam_Friends::add_friend_avatars(CSteamID id) +{ + uint64 steam_id = id.ConvertToUint64(); + auto avatar_ids = avatars.find(steam_id); + if (avatar_ids != avatars.end()) { + return avatar_ids->second; + } + + struct Avatar_Numbers avatar_numbers{}; + std::string small_avatar(32 * 32 * 4, 0); + std::string medium_avatar(64 * 64 * 4, 0); + std::string large_avatar(184 * 184 * 4, 0); + + static const std::initializer_list avatar_icons = { + "account_avatar.png", + "account_avatar.jpg", + "account_avatar.jpeg", + }; + + if (!settings->disable_account_avatar && (id == settings->get_local_steam_id())) { + std::string file_path{}; + unsigned long long file_size{}; + + // try local location first, then try global location + for (const auto &settings_path : { Local_Storage::get_game_settings_path(), local_storage->get_global_settings_path() }) { + for (const auto &file_name : avatar_icons) { + file_path = settings_path + file_name; + file_size = file_size_(file_path); + if (file_size) break; + } + if (file_size) break; + } + + // no else statement here for default otherwise this breaks default images for friends + if (file_size) { + small_avatar = Local_Storage::load_image_resized(file_path, "", 32); + medium_avatar = Local_Storage::load_image_resized(file_path, "", 64); + large_avatar = Local_Storage::load_image_resized(file_path, "", 184); + } + } else if (!settings->disable_account_avatar) { + Friend *f = find_friend(id); + if (f && (large_avatar.compare(f->avatar()) != 0)) { + large_avatar = f->avatar(); + medium_avatar = Local_Storage::load_image_resized("", f->avatar(), 64); + small_avatar = Local_Storage::load_image_resized("", f->avatar(), 32); + } else { + std::string file_path{}; + unsigned long long file_size{}; + + // try local location first, then try global location + for (const auto &settings_path : { Local_Storage::get_game_settings_path(), local_storage->get_global_settings_path() }) { + for (const auto &file_name : avatar_icons) { + file_path = settings_path + file_name; + file_size = file_size_(file_path); + if (file_size) break; + } + if (file_size) break; + } + + if (file_size) { + small_avatar = Local_Storage::load_image_resized(file_path, "", 32); + medium_avatar = Local_Storage::load_image_resized(file_path, "", 64); + large_avatar = Local_Storage::load_image_resized(file_path, "", 184); + } + } + } + + avatar_numbers.smallest = settings->add_image(small_avatar, 32, 32); + avatar_numbers.medium = settings->add_image(medium_avatar, 64, 64); + avatar_numbers.large = settings->add_image(large_avatar, 184, 184); + + avatars[steam_id] = avatar_numbers; + return avatar_numbers; +} + +void Steam_Friends::steam_friends_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Friends *steam_friends = (Steam_Friends *)object; + steam_friends->Callback(msg); +} + +void Steam_Friends::steam_friends_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Friends *steam_friends = (Steam_Friends *)object; + steam_friends->RunCallbacks(); +} + +void Steam_Friends::resend_friend_data() +{ + modified = true; +} + +bool Steam_Friends::ok_friend_flags(int iFriendFlags) +{ + if (iFriendFlags & k_EFriendFlagImmediate) return true; + + return false; +} + + +Steam_Friends::Steam_Friends(Settings* settings, class Local_Storage* local_storage, Networking* network, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Steam_Overlay* overlay): + settings(settings), + local_storage(local_storage), + network(network), + callbacks(callbacks), + callback_results(callback_results), + run_every_runcb(run_every_runcb), + overlay(overlay) +{ + modified = false; + + this->network->setCallback(CALLBACK_ID_FRIEND, settings->get_local_steam_id(), &Steam_Friends::steam_friends_callback, this); + this->network->setCallback(CALLBACK_ID_FRIEND_MESSAGES, settings->get_local_steam_id(), &Steam_Friends::steam_friends_callback, this); + this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Friends::steam_friends_callback, this); + this->run_every_runcb->add(&Steam_Friends::steam_friends_run_every_runcb, this); +} + +Steam_Friends::~Steam_Friends() +{ + this->network->rmCallback(CALLBACK_ID_FRIEND, settings->get_local_steam_id(), &Steam_Friends::steam_friends_callback, this); + this->network->rmCallback(CALLBACK_ID_FRIEND_MESSAGES, settings->get_local_steam_id(), &Steam_Friends::steam_friends_callback, this); + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Friends::steam_friends_callback, this); + this->run_every_runcb->remove(&Steam_Friends::steam_friends_run_every_runcb, this); +} + + +// returns the local players name - guaranteed to not be NULL. +// this is the same name as on the users community profile page +// this is stored in UTF-8 format +// like all the other interface functions that return a char *, it's important that this pointer is not saved +// off; it will eventually be free'd or re-allocated +const char* Steam_Friends::GetPersonaName() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + const char *local_name = settings->get_local_name(); + + return local_name; +} + +// Sets the player name, stores it on the server and publishes the changes to all friends who are online. +// Changes take place locally immediately, and a PersonaStateChange_t is posted, presuming success. +// +// The final results are available through the return value SteamAPICall_t, using SetPersonaNameResponse_t. +// +// If the name change fails to happen on the server, then an additional global PersonaStateChange_t will be posted +// to change the name back, in addition to the SetPersonaNameResponse_t callback. +STEAM_CALL_RESULT( SetPersonaNameResponse_t ) +SteamAPICall_t Steam_Friends::SetPersonaName( const char *pchPersonaName ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + SetPersonaNameResponse_t data{}; + data.m_bSuccess = true; + data.m_bLocalSuccess = false; + data.m_result = k_EResultOK; + persona_change(settings->get_local_steam_id(), k_EPersonaChangeName); + + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + + { + PersonaStateChange_t data2{}; + data2.m_nChangeFlags = EPersonaChange::k_EPersonaChangeName; + data2.m_ulSteamID = settings->get_local_steam_id().ConvertToUint64(); + callbacks->addCBResult(data2.k_iCallback, &data2, sizeof(data2)); + } + + return ret; +} + +void Steam_Friends::SetPersonaName_old( const char *pchPersonaName ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + SetPersonaName(pchPersonaName); +} + +// gets the status of the current user +EPersonaState Steam_Friends::GetPersonaState() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + return k_EPersonaStateOnline; +} + + +// friend iteration +// takes a set of k_EFriendFlags, and returns the number of users the client knows about who meet that criteria +// then GetFriendByIndex() can then be used to return the id's of each of those users +int Steam_Friends::GetFriendCount( int iFriendFlags ) +{ + PRINT_DEBUG("%i", iFriendFlags); + std::lock_guard lock(global_mutex); + int count = 0; + if (ok_friend_flags(iFriendFlags)) count = friends.size(); + PRINT_DEBUG("count %i", count); + return count; +} + +int Steam_Friends::GetFriendCount( EFriendFlags eFriendFlags ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + return GetFriendCount((int)eFriendFlags); +} + +// returns the steamID of a user +// iFriend is a index of range [0, GetFriendCount()) +// iFriendsFlags must be the same value as used in GetFriendCount() +// the returned CSteamID can then be used by all the functions below to access details about the user +CSteamID Steam_Friends::GetFriendByIndex( int iFriend, int iFriendFlags ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + CSteamID id = k_steamIDNil; + if (ok_friend_flags(iFriendFlags)) if (iFriend < friends.size()) id = CSteamID((uint64)friends[iFriend].id()); + + return id; +} + +CSteamID Steam_Friends::GetFriendByIndex( int iFriend, EFriendFlags eFriendFlags ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + return GetFriendByIndex(iFriend, (int)eFriendFlags ); +} + +// returns a relationship to a user +EFriendRelationship Steam_Friends::GetFriendRelationship( CSteamID steamIDFriend ) +{ + PRINT_DEBUG("%llu", steamIDFriend.ConvertToUint64()); + std::lock_guard lock(global_mutex); + if (steamIDFriend == settings->get_local_steam_id()) return k_EFriendRelationshipNone; //Real steam behavior + if (find_friend(steamIDFriend)) return k_EFriendRelationshipFriend; + + return k_EFriendRelationshipNone; +} + + +// returns the current status of the specified user +// this will only be known by the local user if steamIDFriend is in their friends list; on the same game server; in a chat room or lobby; or in a small group with the local user +EPersonaState Steam_Friends::GetFriendPersonaState( CSteamID steamIDFriend ) +{ + PRINT_DEBUG("%llu", steamIDFriend.ConvertToUint64()); + std::lock_guard lock(global_mutex); + EPersonaState state = k_EPersonaStateOffline; + if (steamIDFriend == settings->get_local_steam_id() || find_friend(steamIDFriend)) { + state = k_EPersonaStateOnline; + } + + //works because all of those who could be in a lobby are our friends + return state; +} + + +// returns the name another user - guaranteed to not be NULL. +// same rules as GetFriendPersonaState() apply as to whether or not the user knowns the name of the other user +// note that on first joining a lobby, chat room or game server the local user will not known the name of the other users automatically; that information will arrive asyncronously +// +const char* Steam_Friends::GetFriendPersonaName( CSteamID steamIDFriend ) +{ + PRINT_DEBUG("%llu", steamIDFriend.ConvertToUint64()); + std::lock_guard lock(global_mutex); + const char *name = "Unknown User"; + if (steamIDFriend == settings->get_local_steam_id()) { + name = settings->get_local_name(); + } else { + Friend *f = find_friend(steamIDFriend); + if (f) name = f->name().c_str(); + } + + PRINT_DEBUG("returned '%s'", name); + return name; +} + + +// returns true if the friend is actually in a game, and fills in pFriendGameInfo with an extra details +bool Steam_Friends::GetFriendGamePlayed( CSteamID steamIDFriend, STEAM_OUT_STRUCT() FriendGameInfo_t *pFriendGameInfo ) +{ + PRINT_DEBUG("%llu %p", steamIDFriend.ConvertToUint64(), pFriendGameInfo); + std::lock_guard lock(global_mutex); + bool ret = false; + + if (steamIDFriend == settings->get_local_steam_id()) { + PRINT_DEBUG("found myself! %llu %llu", settings->get_local_game_id().ToUint64(), settings->get_lobby().ConvertToUint64()); + if (pFriendGameInfo) { + pFriendGameInfo->m_gameID = settings->get_local_game_id(); + pFriendGameInfo->m_unGameIP = 0; + pFriendGameInfo->m_usGamePort = 0; + pFriendGameInfo->m_usQueryPort = 0; + pFriendGameInfo->m_steamIDLobby = settings->get_lobby(); + } + + ret = true; + } else { + Friend *f = find_friend(steamIDFriend); + if (f) { + PRINT_DEBUG("found someone %u " "%" PRIu64 "", f->appid(), f->lobby_id()); + if (pFriendGameInfo) { + pFriendGameInfo->m_gameID = CGameID(f->appid()); + pFriendGameInfo->m_unGameIP = 0; + pFriendGameInfo->m_usGamePort = 0; + pFriendGameInfo->m_usQueryPort = 0; + pFriendGameInfo->m_steamIDLobby = CSteamID((uint64)f->lobby_id()); + } + + ret = true; + } + } + + return ret; +} + +bool Steam_Friends::GetFriendGamePlayed( CSteamID steamIDFriend, uint64 *pulGameID, uint32 *punGameIP, uint16 *pusGamePort, uint16 *pusQueryPort ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + FriendGameInfo_t info; + bool ret = GetFriendGamePlayed(steamIDFriend, &info); + if (ret) { + if (pulGameID) *pulGameID = info.m_gameID.ToUint64(); + if (punGameIP) *punGameIP = info.m_unGameIP; + if (pusGamePort) *pusGamePort = info.m_usGamePort; + if (pusQueryPort) *pusQueryPort = info.m_usQueryPort; + } + + return ret; +} + +// accesses old friends names - returns an empty string when their are no more items in the history +const char* Steam_Friends::GetFriendPersonaNameHistory( CSteamID steamIDFriend, int iPersonaName ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + const char *ret = ""; + if (iPersonaName == 0) ret = GetFriendPersonaName(steamIDFriend); + else if (iPersonaName == 1) ret = "Some Old Name"; + + return ret; +} + +// friends steam level +int Steam_Friends::GetFriendSteamLevel( CSteamID steamIDFriend ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 100; +} + + +// Returns nickname the current user has set for the specified player. Returns NULL if the no nickname has been set for that player. +const char* Steam_Friends::GetPlayerNickname( CSteamID steamIDPlayer ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return NULL; +} + + +// friend grouping (tag) apis +// returns the number of friends groups +int Steam_Friends::GetFriendsGroupCount() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// returns the friends group ID for the given index (invalid indices return k_FriendsGroupID_Invalid) +FriendsGroupID_t Steam_Friends::GetFriendsGroupIDByIndex( int iFG ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_FriendsGroupID_Invalid; +} + +// returns the name for the given friends group (NULL in the case of invalid friends group IDs) +const char* Steam_Friends::GetFriendsGroupName( FriendsGroupID_t friendsGroupID ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return NULL; +} + +// returns the number of members in a given friends group +int Steam_Friends::GetFriendsGroupMembersCount( FriendsGroupID_t friendsGroupID ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// gets up to nMembersCount members of the given friends group, if fewer exist than requested those positions' SteamIDs will be invalid +void Steam_Friends::GetFriendsGroupMembersList( FriendsGroupID_t friendsGroupID, STEAM_OUT_ARRAY_CALL(nMembersCount, GetFriendsGroupMembersCount, friendsGroupID ) CSteamID *pOutSteamIDMembers, int nMembersCount ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// returns true if the specified user meets any of the criteria specified in iFriendFlags +// iFriendFlags can be the union (binary or, |) of one or more k_EFriendFlags values +bool Steam_Friends::HasFriend( CSteamID steamIDFriend, int iFriendFlags ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + bool ret = false; + if (ok_friend_flags(iFriendFlags)) if (find_friend(steamIDFriend)) ret = true; + + return ret; +} + +bool Steam_Friends::HasFriend( CSteamID steamIDFriend, EFriendFlags eFriendFlags ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + return HasFriend(steamIDFriend, (int)eFriendFlags ); +} + +// clan (group) iteration and access functions +int Steam_Friends::GetClanCount() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + int counter = 0; + for (auto &c : settings->subscribed_groups_clans) counter++; + return counter; +} + +CSteamID Steam_Friends::GetClanByIndex( int iClan ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + int counter = 0; + for (auto &c : settings->subscribed_groups_clans) { + if (counter == iClan) return c.id; + counter++; + } + return k_steamIDNil; +} + +const char* Steam_Friends::GetClanName( CSteamID steamIDClan ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + for (auto &c : settings->subscribed_groups_clans) { + if (c.id.ConvertToUint64() == steamIDClan.ConvertToUint64()) return c.name.c_str(); + } + return ""; +} + +const char* Steam_Friends::GetClanTag( CSteamID steamIDClan ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + for (auto &c : settings->subscribed_groups_clans) { + if (c.id.ConvertToUint64() == steamIDClan.ConvertToUint64()) return c.tag.c_str(); + } + return ""; +} + +// returns the most recent information we have about what's happening in a clan +bool Steam_Friends::GetClanActivityCounts( CSteamID steamIDClan, int *pnOnline, int *pnInGame, int *pnChatting ) +{ + PRINT_DEBUG("TODO %llu", steamIDClan.ConvertToUint64()); + std::lock_guard lock(global_mutex); + return false; +} + +// for clans a user is a member of, they will have reasonably up-to-date information, but for others you'll have to download the info to have the latest +SteamAPICall_t Steam_Friends::DownloadClanActivityCounts( STEAM_ARRAY_COUNT(cClansToRequest) CSteamID *psteamIDClans, int cClansToRequest ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +// iterators for getting users in a chat room, lobby, game server or clan +// note that large clans that cannot be iterated by the local user +// note that the current user must be in a lobby to retrieve CSteamIDs of other users in that lobby +// steamIDSource can be the steamID of a group, game server, lobby or chat room +int Steam_Friends::GetFriendCountFromSource( CSteamID steamIDSource ) +{ + PRINT_DEBUG("TODO %llu", steamIDSource.ConvertToUint64()); + std::lock_guard lock(global_mutex); + //TODO + return 0; +} + +CSteamID Steam_Friends::GetFriendFromSourceByIndex( CSteamID steamIDSource, int iFriend ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_steamIDNil; +} + + +// returns true if the local user can see that steamIDUser is a member or in steamIDSource +bool Steam_Friends::IsUserInSource( CSteamID steamIDUser, CSteamID steamIDSource ) +{ + PRINT_DEBUG("%llu %llu", steamIDUser.ConvertToUint64(), steamIDSource.ConvertToUint64()); + std::lock_guard lock(global_mutex); + if (steamIDUser == settings->get_local_steam_id()) { + if (settings->get_lobby() == steamIDSource) { + return true; + } + + if (settings->subscribed_groups.find(steamIDSource.ConvertToUint64()) != settings->subscribed_groups.end()) { + return true; + } + } else { + Friend *f = find_friend(steamIDUser); + if (!f) return false; + if (f->lobby_id() == steamIDSource.ConvertToUint64()) return true; + } + //TODO + return false; +} + + +// User is in a game pressing the talk button (will suppress the microphone for all voice comms from the Steam friends UI) +void Steam_Friends::SetInGameVoiceSpeaking( CSteamID steamIDUser, bool bSpeaking ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// activates the game overlay, with an optional dialog to open +// valid options are "Friends", "Community", "Players", "Settings", "OfficialGameGroup", "Stats", "Achievements" +void Steam_Friends::ActivateGameOverlay( const char *pchDialog ) +{ + PRINT_DEBUG("%s", pchDialog); + std::lock_guard lock(global_mutex); + overlay->OpenOverlay(pchDialog); +} + + +// activates game overlay to a specific place +// valid options are +// "steamid" - opens the overlay web browser to the specified user or groups profile +// "chat" - opens a chat window to the specified user, or joins the group chat +// "jointrade" - opens a window to a Steam Trading session that was started with the ISteamEconomy/StartTrade Web API +// "stats" - opens the overlay web browser to the specified user's stats +// "achievements" - opens the overlay web browser to the specified user's achievements +// "friendadd" - opens the overlay in minimal mode prompting the user to add the target user as a friend +// "friendremove" - opens the overlay in minimal mode prompting the user to remove the target friend +// "friendrequestaccept" - opens the overlay in minimal mode prompting the user to accept an incoming friend invite +// "friendrequestignore" - opens the overlay in minimal mode prompting the user to ignore an incoming friend invite +void Steam_Friends::ActivateGameOverlayToUser( const char *pchDialog, CSteamID steamID ) +{ + PRINT_DEBUG("TODO %s %llu", pchDialog, steamID.ConvertToUint64()); + std::lock_guard lock(global_mutex); +} + + +// activates game overlay web browser directly to the specified URL +// full address with protocol type is required, e.g. http://www.steamgames.com/ +void Steam_Friends::ActivateGameOverlayToWebPage( const char *pchURL, EActivateGameOverlayToWebPageMode eMode ) +{ + PRINT_DEBUG("TODO %s %u", pchURL, eMode); + std::lock_guard lock(global_mutex); + overlay->OpenOverlayWebpage(pchURL); +} + +void Steam_Friends::ActivateGameOverlayToWebPage( const char *pchURL ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + ActivateGameOverlayToWebPage( pchURL, k_EActivateGameOverlayToWebPageMode_Default ); +} + +// activates game overlay to store page for app +void Steam_Friends::ActivateGameOverlayToStore( AppId_t nAppID, EOverlayToStoreFlag eFlag ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +void Steam_Friends::ActivateGameOverlayToStore( AppId_t nAppID) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); +} + +// Mark a target user as 'played with'. This is a client-side only feature that requires that the calling user is +// in game +void Steam_Friends::SetPlayedWith( CSteamID steamIDUserPlayedWith ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// activates game overlay to open the invite dialog. Invitations will be sent for the provided lobby. +void Steam_Friends::ActivateGameOverlayInviteDialog( CSteamID steamIDLobby ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + overlay->OpenOverlayInvite(steamIDLobby); +} + +// gets the small (32x32) avatar of the current user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if none set +int Steam_Friends::GetSmallFriendAvatar( CSteamID steamIDFriend ) +{ + PRINT_DEBUG_ENTRY(); + //IMPORTANT NOTE: don't change friend avatar numbers for the same friend or else some games endlessly allocate stuff. + std::lock_guard lock(global_mutex); + struct Avatar_Numbers numbers = add_friend_avatars(steamIDFriend); + return numbers.smallest; +} + + +// gets the medium (64x64) avatar of the current user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if none set +int Steam_Friends::GetMediumFriendAvatar( CSteamID steamIDFriend ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct Avatar_Numbers numbers = add_friend_avatars(steamIDFriend); + return numbers.medium; +} + + +// gets the large (184x184) avatar of the current user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if none set +// returns -1 if this image has yet to be loaded, in this case wait for a AvatarImageLoaded_t callback and then call this again +int Steam_Friends::GetLargeFriendAvatar( CSteamID steamIDFriend ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct Avatar_Numbers numbers = add_friend_avatars(steamIDFriend); + return numbers.large; +} + +int Steam_Friends::GetFriendAvatar( CSteamID steamIDFriend, int eAvatarSize ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (eAvatarSize == k_EAvatarSize32x32) { + return GetSmallFriendAvatar(steamIDFriend); + } else if (eAvatarSize == k_EAvatarSize64x64) { + return GetMediumFriendAvatar(steamIDFriend); + } else if (eAvatarSize == k_EAvatarSize184x184) { + return GetLargeFriendAvatar(steamIDFriend); + } else { + return 0; + } +} + +int Steam_Friends::GetFriendAvatar(CSteamID steamIDFriend) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + return GetFriendAvatar(steamIDFriend, k_EAvatarSize32x32); +} + +// requests information about a user - persona name & avatar +// if bRequireNameOnly is set, then the avatar of a user isn't downloaded +// - it's a lot slower to download avatars and churns the local cache, so if you don't need avatars, don't request them +// if returns true, it means that data is being requested, and a PersonaStateChanged_t callback will be posted when it's retrieved +// if returns false, it means that we already have all the details about that user, and functions can be called immediately +bool Steam_Friends::RequestUserInformation( CSteamID steamIDUser, bool bRequireNameOnly ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + //persona_change(steamIDUser, k_EPersonaChangeName); + //We already know everything + return false; +} + + +// requests information about a clan officer list +// when complete, data is returned in ClanOfficerListResponse_t call result +// this makes available the calls below +// you can only ask about clans that a user is a member of +// note that this won't download avatars automatically; if you get an officer, +// and no avatar image is available, call RequestUserInformation( steamID, false ) to download the avatar +STEAM_CALL_RESULT( ClanOfficerListResponse_t ) +SteamAPICall_t Steam_Friends::RequestClanOfficerList( CSteamID steamIDClan ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +// iteration of clan officers - can only be done when a RequestClanOfficerList() call has completed + +// returns the steamID of the clan owner +CSteamID Steam_Friends::GetClanOwner( CSteamID steamIDClan ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_steamIDNil; +} + +// returns the number of officers in a clan (including the owner) +int Steam_Friends::GetClanOfficerCount( CSteamID steamIDClan ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// returns the steamID of a clan officer, by index, of range [0,GetClanOfficerCount) +CSteamID Steam_Friends::GetClanOfficerByIndex( CSteamID steamIDClan, int iOfficer ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_steamIDNil; +} + +// if current user is chat restricted, he can't send or receive any text/voice chat messages. +// the user can't see custom avatars. But the user can be online and send/recv game invites. +// a chat restricted user can't add friends or join any groups. +uint32 Steam_Friends::GetUserRestrictions() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_nUserRestrictionNone; +} + +EUserRestriction Steam_Friends::GetUserRestrictions_old() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_nUserRestrictionNone; +} + +// Rich Presence data is automatically shared between friends who are in the same game +// Each user has a set of Key/Value pairs +// Note the following limits: k_cchMaxRichPresenceKeys, k_cchMaxRichPresenceKeyLength, k_cchMaxRichPresenceValueLength +// There are two magic keys: +// "status" - a UTF-8 string that will show up in the 'view game info' dialog in the Steam friends list +// "connect" - a UTF-8 string that contains the command-line for how a friend can connect to a game +// GetFriendRichPresence() returns an empty string "" if no value is set +// SetRichPresence() to a NULL or an empty string deletes the key +// You can iterate the current set of keys for a friend with GetFriendRichPresenceKeyCount() +// and GetFriendRichPresenceKeyByIndex() (typically only used for debugging) +bool Steam_Friends::SetRichPresence( const char *pchKey, const char *pchValue ) +{ + PRINT_DEBUG("%s %s", pchKey, pchValue ? pchValue : "NULL"); + std::lock_guard lock(global_mutex); + if (pchValue) { + auto prev_value = (*us.mutable_rich_presence()).find(pchKey); + if (prev_value == (*us.mutable_rich_presence()).end() || prev_value->second != pchValue) { + (*us.mutable_rich_presence())[pchKey] = pchValue; + resend_friend_data(); + } + } else { + auto to_remove = us.mutable_rich_presence()->find(pchKey); + if (to_remove != us.mutable_rich_presence()->end()) { + us.mutable_rich_presence()->erase(to_remove); + resend_friend_data(); + } + } + + return true; +} + +void Steam_Friends::ClearRichPresence() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + us.mutable_rich_presence()->clear(); + resend_friend_data(); + +} + +const char* Steam_Friends::GetFriendRichPresence( CSteamID steamIDFriend, const char *pchKey ) +{ + PRINT_DEBUG("%llu '%s'", steamIDFriend.ConvertToUint64(), pchKey); + std::lock_guard lock(global_mutex); + const char *value = ""; + + Friend *f = NULL; + if (settings->get_local_steam_id() == steamIDFriend) { + f = &us; + } else { + f = find_friend(steamIDFriend); + } + + if (f && isAppIdCompatible(f)) { + auto result = f->rich_presence().find(pchKey); + if (result != f->rich_presence().end()) value = result->second.c_str(); + } + + PRINT_DEBUG("returned '%s'", value); + return value; +} + +int Steam_Friends::GetFriendRichPresenceKeyCount( CSteamID steamIDFriend ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + int num = 0; + + Friend *f = NULL; + if (settings->get_local_steam_id() == steamIDFriend) { + f = &us; + } else { + f = find_friend(steamIDFriend); + } + + if (f && isAppIdCompatible(f)) num = f->rich_presence().size(); + + return num; +} + +const char* Steam_Friends::GetFriendRichPresenceKeyByIndex( CSteamID steamIDFriend, int iKey ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + const char *key = ""; + + Friend *f = NULL; + if (settings->get_local_steam_id() == steamIDFriend) { + f = &us; + } else { + f = find_friend(steamIDFriend); + } + + if (f && isAppIdCompatible(f) && f->rich_presence().size() > iKey && iKey >= 0) { + auto friend_data = f->rich_presence().begin(); + for (int i = 0; i < iKey; ++i) ++friend_data; + key = friend_data->first.c_str(); + } + + + return key; +} + +// Requests rich presence for a specific user. +void Steam_Friends::RequestFriendRichPresence( CSteamID steamIDFriend ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + Friend *f = find_friend(steamIDFriend); + if (f) rich_presence_updated(steamIDFriend, settings->get_local_game_id().AppID()); + +} + + +// rich invite support +// if the target accepts the invite, the pchConnectString gets added to the command-line for launching the game +// if the game is already running, a GameRichPresenceJoinRequested_t callback is posted containing the connect string +// invites can only be sent to friends +bool Steam_Friends::InviteUserToGame( CSteamID steamIDFriend, const char *pchConnectString ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + Friend *f = find_friend(steamIDFriend); + if (!f) return false; + + Common_Message msg; + Friend_Messages *friend_messages = new Friend_Messages(); + friend_messages->set_type(Friend_Messages::GAME_INVITE); + friend_messages->set_connect_str(pchConnectString); + msg.set_allocated_friend_messages(friend_messages); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(steamIDFriend.ConvertToUint64()); + return network->sendTo(&msg, true); +} + + +// recently-played-with friends iteration +// this iterates the entire list of users recently played with, across games +// GetFriendCoplayTime() returns as a unix time +int Steam_Friends::GetCoplayFriendCount() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +CSteamID Steam_Friends::GetCoplayFriend( int iCoplayFriend ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_steamIDNil; +} + +int Steam_Friends::GetFriendCoplayTime( CSteamID steamIDFriend ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +AppId_t Steam_Friends::GetFriendCoplayGame( CSteamID steamIDFriend ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +// chat interface for games +// this allows in-game access to group (clan) chats from in the game +// the behavior is somewhat sophisticated, because the user may or may not be already in the group chat from outside the game or in the overlay +// use ActivateGameOverlayToUser( "chat", steamIDClan ) to open the in-game overlay version of the chat +STEAM_CALL_RESULT( JoinClanChatRoomCompletionResult_t ) +SteamAPICall_t Steam_Friends::JoinClanChatRoom( CSteamID steamIDClan ) +{ + PRINT_DEBUG("TODO %llu", steamIDClan.ConvertToUint64()); + //TODO actually join a room + std::lock_guard lock(global_mutex); + JoinClanChatRoomCompletionResult_t data; + data.m_steamIDClanChat = steamIDClan; + data.m_eChatRoomEnterResponse = k_EChatRoomEnterResponseSuccess; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +bool Steam_Friends::LeaveClanChatRoom( CSteamID steamIDClan ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +int Steam_Friends::GetClanChatMemberCount( CSteamID steamIDClan ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +CSteamID Steam_Friends::GetChatMemberByIndex( CSteamID steamIDClan, int iUser ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_steamIDNil; +} + +bool Steam_Friends::SendClanChatMessage( CSteamID steamIDClanChat, const char *pchText ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +int Steam_Friends::GetClanChatMessage( CSteamID steamIDClanChat, int iMessage, void *prgchText, int cchTextMax, EChatEntryType *peChatEntryType, STEAM_OUT_STRUCT() CSteamID *psteamidChatter ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +bool Steam_Friends::IsClanChatAdmin( CSteamID steamIDClanChat, CSteamID steamIDUser ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +// interact with the Steam (game overlay / desktop) +bool Steam_Friends::IsClanChatWindowOpenInSteam( CSteamID steamIDClanChat ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +bool Steam_Friends::OpenClanChatWindowInSteam( CSteamID steamIDClanChat ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return true; +} + +bool Steam_Friends::CloseClanChatWindowInSteam( CSteamID steamIDClanChat ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return true; +} + + +// peer-to-peer chat interception +// this is so you can show P2P chats inline in the game +bool Steam_Friends::SetListenForFriendsMessages( bool bInterceptEnabled ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return true; +} + +bool Steam_Friends::ReplyToFriendMessage( CSteamID steamIDFriend, const char *pchMsgToSend ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +int Steam_Friends::GetFriendMessage( CSteamID steamIDFriend, int iMessageID, void *pvData, int cubData, EChatEntryType *peChatEntryType ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +// following apis +STEAM_CALL_RESULT( FriendsGetFollowerCount_t ) +SteamAPICall_t Steam_Friends::GetFollowerCount( CSteamID steamID ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +STEAM_CALL_RESULT( FriendsIsFollowing_t ) +SteamAPICall_t Steam_Friends::IsFollowing( CSteamID steamID ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +STEAM_CALL_RESULT( FriendsEnumerateFollowingList_t ) +SteamAPICall_t Steam_Friends::EnumerateFollowingList( uint32 unStartIndex ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +bool Steam_Friends::IsClanPublic( CSteamID steamIDClan ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +bool Steam_Friends::IsClanOfficialGameGroup( CSteamID steamIDClan ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +int Steam_Friends::GetNumChatsWithUnreadPriorityMessages() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +void Steam_Friends::ActivateGameOverlayRemotePlayTogetherInviteDialog( CSteamID steamIDLobby ) +{ + PRINT_DEBUG_ENTRY(); +} + +// Call this before calling ActivateGameOverlayToWebPage() to have the Steam Overlay Browser block navigations +// to your specified protocol (scheme) uris and instead dispatch a OverlayBrowserProtocolNavigation_t callback to your game. +// ActivateGameOverlayToWebPage() must have been called with k_EActivateGameOverlayToWebPageMode_Modal +bool Steam_Friends::RegisterProtocolInOverlayBrowser( const char *pchProtocol ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +// Activates the game overlay to open an invite dialog that will send the provided Rich Presence connect string to selected friends +void Steam_Friends::ActivateGameOverlayInviteDialogConnectString( const char *pchConnectString ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// Steam Community items equipped by a user on their profile +// You can register for EquippedProfileItemsChanged_t to know when a friend has changed their equipped profile items +STEAM_CALL_RESULT( EquippedProfileItems_t ) +SteamAPICall_t Steam_Friends::RequestEquippedProfileItems( CSteamID steamID ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +bool Steam_Friends::BHasEquippedProfileItem( CSteamID steamID, ECommunityProfileItemType itemType ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +const char* Steam_Friends::GetProfileItemPropertyString( CSteamID steamID, ECommunityProfileItemType itemType, ECommunityProfileItemProperty prop ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return ""; +} + +uint32 Steam_Friends::GetProfileItemPropertyUint( CSteamID steamID, ECommunityProfileItemType itemType, ECommunityProfileItemProperty prop ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + + +void Steam_Friends::RunCallbacks() +{ + // PRINT_DEBUG_ENTRY(); + if (settings->get_lobby() != lobby_id) { + lobby_id = settings->get_lobby(); + resend_friend_data(); + } + + if (modified) { + PRINT_DEBUG("sending modified data"); + Common_Message msg; + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + Friend *f = new Friend(us); + f->set_id(settings->get_local_steam_id().ConvertToUint64()); + f->set_name(settings->get_local_name()); + f->set_appid(settings->get_local_game_id().AppID()); + f->set_lobby_id(settings->get_lobby().ConvertToUint64()); + msg.set_allocated_friend_(f); + network->sendToAllIndividuals(&msg, true); + modified = false; + last_sent_friends = std::chrono::high_resolution_clock::now(); + } +} + +void Steam_Friends::Callback(Common_Message *msg) +{ + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::DISCONNECT) { + PRINT_DEBUG("Disconnect"); + uint64 id = msg->source_id(); + auto f = std::find_if(friends.begin(), friends.end(), [&id](Friend const& item) { return item.id() == id; }); + if (friends.end() != f) { + persona_change((uint64)f->id(), k_EPersonaChangeStatus); + overlay->FriendDisconnect(*f); + friends.erase(f); + } + } + + if (msg->low_level().type() == Low_Level::CONNECT) { + PRINT_DEBUG("Connect %llu", (uint64)msg->source_id()); + Common_Message msg_; + msg_.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg_.set_dest_id(msg->source_id()); + Friend *f = new Friend(us); + f->set_id(settings->get_local_steam_id().ConvertToUint64()); + f->set_name(settings->get_local_name()); + f->set_appid(settings->get_local_game_id().AppID()); + f->set_lobby_id(settings->get_lobby().ConvertToUint64()); + int avatar_number = GetLargeFriendAvatar(settings->get_local_steam_id()); + if (settings->images[avatar_number].data.length() > 0) f->set_avatar(settings->images[avatar_number].data); + else f->set_avatar(""); + msg_.set_allocated_friend_(f); + network->sendTo(&msg_, true); + } + } + + if (msg->has_friend_()) { + PRINT_DEBUG("Friend " "%" PRIu64 " " "%" PRIu64 "", msg->friend_().id(), msg->friend_().lobby_id()); + Friend *f = find_friend((uint64)msg->friend_().id()); + if (!f) { + if (msg->friend_().id() != settings->get_local_steam_id().ConvertToUint64()) { + friends.push_back(msg->friend_()); + overlay->FriendConnect(msg->friend_()); + persona_change((uint64)msg->friend_().id(), k_EPersonaChangeName); + GetLargeFriendAvatar((uint64)msg->friend_().id()); + } + } else { + std::map map1(f->rich_presence().begin(), f->rich_presence().end()); + std::map map2(msg->friend_().rich_presence().begin(), msg->friend_().rich_presence().end()); + + if (map1 != map2) { + //The App ID of the game. This should always be the current game. + if (isAppIdCompatible(f)) { + rich_presence_updated((uint64)msg->friend_().id(), (uint64)msg->friend_().appid()); + } + } + //TODO: callbacks? + *f = msg->friend_(); + } + } + + if (msg->has_friend_messages()) { + if (msg->friend_messages().type() == Friend_Messages::LOBBY_INVITE) { + PRINT_DEBUG("Got Lobby Invite"); + Friend *f = find_friend((uint64)msg->source_id()); + if (f) { + LobbyInvite_t data; + data.m_ulSteamIDUser = msg->source_id(); + data.m_ulSteamIDLobby = msg->friend_messages().lobby_id(); + data.m_ulGameID = f->appid(); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + if (overlay->Ready() && !settings->hasOverlayAutoAcceptInviteFromFriend(msg->source_id())) + { + //TODO: the user should accept the invite first but we auto accept it because there's no gui yet + // Then we will handle it ! + overlay->SetLobbyInvite(*find_friend(static_cast(msg->source_id())), msg->friend_messages().lobby_id()); + } + else + { + GameLobbyJoinRequested_t data; + data.m_steamIDLobby = CSteamID((uint64)msg->friend_messages().lobby_id()); + data.m_steamIDFriend = CSteamID((uint64)msg->source_id()); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + } + } + + if (msg->friend_messages().type() == Friend_Messages::GAME_INVITE) { + PRINT_DEBUG("Got Game Invite"); + //TODO: I'm pretty sure that the user should accept the invite before this is posted but we do like above + if (overlay->Ready() && !settings->hasOverlayAutoAcceptInviteFromFriend(msg->source_id())) + { + // Then we will handle it ! + overlay->SetRichInvite(*find_friend(static_cast(msg->source_id())), msg->friend_messages().connect_str().c_str()); + } + else + { + std::string const& connect_str = msg->friend_messages().connect_str(); + GameRichPresenceJoinRequested_t data = {}; + data.m_steamIDFriend = CSteamID((uint64)msg->source_id()); + strncpy(data.m_rgchConnect, connect_str.c_str(), k_cchMaxRichPresenceValueLength - 1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + } + } +} diff --git a/dll/steam_game_coordinator.cpp b/dll/steam_game_coordinator.cpp new file mode 100644 index 00000000..6f63e0f5 --- /dev/null +++ b/dll/steam_game_coordinator.cpp @@ -0,0 +1,146 @@ +/* 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 + . */ + +#include "dll/steam_game_coordinator.h" + +void Steam_Game_Coordinator::push_incoming(std::string message) +{ + outgoing_messages.push(message); + + struct GCMessageAvailable_t data; + data.m_nMessageSize = message.size(); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); +} + +void Steam_Game_Coordinator::steam_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Game_Coordinator *steam_gamecoordinator = (Steam_Game_Coordinator *)object; + steam_gamecoordinator->Callback(msg); +} + +void Steam_Game_Coordinator::steam_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Game_Coordinator *steam_gamecoordinator = (Steam_Game_Coordinator *)object; + steam_gamecoordinator->RunCallbacks(); +} + + +Steam_Game_Coordinator::Steam_Game_Coordinator(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_Game_Coordinator::steam_callback, this); + this->run_every_runcb->add(&Steam_Game_Coordinator::steam_run_every_runcb, this); +} + +Steam_Game_Coordinator::~Steam_Game_Coordinator() +{ + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Game_Coordinator::steam_callback, this); + this->run_every_runcb->remove(&Steam_Game_Coordinator::steam_run_every_runcb, this); +} + + +// sends a message to the Game Coordinator +EGCResults Steam_Game_Coordinator::SendMessage_( uint32 unMsgType, const void *pubData, uint32 cubData ) +{ + PRINT_DEBUG("%X %u len %u", unMsgType, (~protobuf_mask) & unMsgType, cubData); + std::lock_guard lock(global_mutex); + if (protobuf_mask & unMsgType) { + uint32 message_type = (~protobuf_mask) & unMsgType; + if (message_type == 4006) { //client hello + std::string message("\xA4\x0F\x00\x80\x00\x00\x00\x00\x08\x00", 10); + push_incoming(message); + } else + if (message_type == 4007) { //server hello + std::string message("\xA5\x0F\x00\x80\x00\x00\x00\x00\x08\x00", 10); + push_incoming(message); + } + } + + return k_EGCResultOK; +} + +// returns true if there is a message waiting from the game coordinator +bool Steam_Game_Coordinator::IsMessageAvailable( uint32 *pcubMsgSize ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (outgoing_messages.size()) { + if (pcubMsgSize) *pcubMsgSize = outgoing_messages.front().size(); + return true; + } else { + return false; + } +} + +// fills the provided buffer with the first message in the queue and returns k_EGCResultOK or +// returns k_EGCResultNoMessage if there is no message waiting. pcubMsgSize is filled with the message size. +// If the provided buffer is not large enough to fit the entire message, k_EGCResultBufferTooSmall is returned +// and the message remains at the head of the queue. +EGCResults Steam_Game_Coordinator::RetrieveMessage( uint32 *punMsgType, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (outgoing_messages.size()) { + if (outgoing_messages.front().size() > cubDest) { + return k_EGCResultBufferTooSmall; + } + + outgoing_messages.front().copy((char *)pubDest, cubDest); + if (pcubMsgSize) *pcubMsgSize = outgoing_messages.front().size(); + if (punMsgType && outgoing_messages.front().size() >= sizeof(uint32)) { + outgoing_messages.front().copy((char *)punMsgType, sizeof(uint32)); + *punMsgType = ntohl(*punMsgType); + } + + outgoing_messages.pop(); + return k_EGCResultOK; + } else { + return k_EGCResultNoMessage; + } +} + + + +void Steam_Game_Coordinator::RunCallbacks() +{ +} + +void Steam_Game_Coordinator::Callback(Common_Message *msg) +{ + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + + if (msg->low_level().type() == Low_Level::DISCONNECT) { + + } + } + + if (msg->has_networking_sockets()) { + + } +} diff --git a/dll/steam_gamesearch.cpp b/dll/steam_gamesearch.cpp new file mode 100644 index 00000000..f9289afa --- /dev/null +++ b/dll/steam_gamesearch.cpp @@ -0,0 +1,215 @@ +/* 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 + . */ + +#include "dll/steam_gamesearch.h" + +void Steam_Game_Search::steam_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Game_Search *steam_gamesearch = (Steam_Game_Search *)object; + steam_gamesearch->Callback(msg); +} + +void Steam_Game_Search::steam_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Game_Search *steam_gamesearch = (Steam_Game_Search *)object; + steam_gamesearch->RunCallbacks(); +} + +Steam_Game_Search::Steam_Game_Search(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_Game_Search::steam_callback, this); + this->run_every_runcb->add(&Steam_Game_Search::steam_run_every_runcb, this); +} + +Steam_Game_Search::~Steam_Game_Search() +{ + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Game_Search::steam_callback, this); + this->run_every_runcb->remove(&Steam_Game_Search::steam_run_every_runcb, this); +} + +// ============================================================================================= +// Game Player APIs + +// a keyname and a list of comma separated values: one of which is must be found in order for the match to qualify +// fails if a search is currently in progress +EGameSearchErrorCode_t Steam_Game_Search::AddGameSearchParams( const char *pchKeyToFind, const char *pchValuesToFind ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + + +// all players in lobby enter the queue and await a SearchForGameNotificationCallback_t callback. fails if another search is currently in progress +// if not the owner of the lobby or search already in progress this call fails +// periodic callbacks will be sent as queue time estimates change +EGameSearchErrorCode_t Steam_Game_Search::SearchForGameWithLobby( CSteamID steamIDLobby, int nPlayerMin, int nPlayerMax ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + + +// user enter the queue and await a SearchForGameNotificationCallback_t callback. fails if another search is currently in progress +// periodic callbacks will be sent as queue time estimates change +EGameSearchErrorCode_t Steam_Game_Search::SearchForGameSolo( int nPlayerMin, int nPlayerMax ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + + +// after receiving SearchForGameResultCallback_t, accept or decline the game +// multiple SearchForGameResultCallback_t will follow as players accept game until the host starts or cancels the game +EGameSearchErrorCode_t Steam_Game_Search::AcceptGame() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + +EGameSearchErrorCode_t Steam_Game_Search::DeclineGame() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + + +// after receiving GameStartedByHostCallback_t get connection details to server +EGameSearchErrorCode_t Steam_Game_Search::RetrieveConnectionDetails( CSteamID steamIDHost, char *pchConnectionDetails, int cubConnectionDetails ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + + +// leaves queue if still waiting +EGameSearchErrorCode_t Steam_Game_Search::EndGameSearch() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + + +// ============================================================================================= +// Game Host APIs + +// a keyname and a list of comma separated values: all the values you allow +EGameSearchErrorCode_t Steam_Game_Search::SetGameHostParams( const char *pchKey, const char *pchValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + + +// set connection details for players once game is found so they can connect to this server +EGameSearchErrorCode_t Steam_Game_Search::SetConnectionDetails( const char *pchConnectionDetails, int cubConnectionDetails ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + + +// mark server as available for more players with nPlayerMin,nPlayerMax desired +// accept no lobbies with playercount greater than nMaxTeamSize +// the set of lobbies returned must be partitionable into teams of no more than nMaxTeamSize +// RequestPlayersForGameNotificationCallback_t callback will be sent when the search has started +// multple RequestPlayersForGameResultCallback_t callbacks will follow when players are found +EGameSearchErrorCode_t Steam_Game_Search::RequestPlayersForGame( int nPlayerMin, int nPlayerMax, int nMaxTeamSize ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + + +// accept the player list and release connection details to players +// players will only be given connection details and host steamid when this is called +// ( allows host to accept after all players confirm, some confirm, or none confirm. decision is entirely up to the host ) +EGameSearchErrorCode_t Steam_Game_Search::HostConfirmGameStart( uint64 ullUniqueGameID ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + + +// cancel request and leave the pool of game hosts looking for players +// if a set of players has already been sent to host, all players will receive SearchForGameHostFailedToConfirm_t +EGameSearchErrorCode_t Steam_Game_Search::CancelRequestPlayersForGame() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + + +// submit a result for one player. does not end the game. ullUniqueGameID continues to describe this game +EGameSearchErrorCode_t Steam_Game_Search::SubmitPlayerResult( uint64 ullUniqueGameID, CSteamID steamIDPlayer, EPlayerResult_t EPlayerResult ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + + +// ends the game. no further SubmitPlayerResults for ullUniqueGameID will be accepted +// any future requests will provide a new ullUniqueGameID +EGameSearchErrorCode_t Steam_Game_Search::EndGame( uint64 ullUniqueGameID ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EGameSearchErrorCode_Failed_Offline; +} + +void Steam_Game_Search::RunCallbacks() +{ +} + +void Steam_Game_Search::Callback(Common_Message *msg) +{ + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + + if (msg->low_level().type() == Low_Level::DISCONNECT) { + + } + } + + if (msg->has_networking_sockets()) { + + } +} diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index 1c4c79ff..4bf31137 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -20,20 +20,24 @@ #define SEND_SERVER_RATE 5.0 + Steam_GameServer::Steam_GameServer(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks) { this->network = network; this->settings = settings; - server_data.set_id(settings->get_local_steam_id().ConvertToUint64()); this->callbacks = callbacks; auth_manager = new Auth_Manager(settings, network, callbacks); + + server_data.set_id(settings->get_local_steam_id().ConvertToUint64()); } Steam_GameServer::~Steam_GameServer() { delete auth_manager; + auth_manager = nullptr; } + std::vector>* Steam_GameServer::get_players() { return &players; @@ -803,6 +807,8 @@ SteamAPICall_t Steam_GameServer::ComputeNewPlayerCompatibility( CSteamID steamID return 0; } + + void Steam_GameServer::RunCallbacks() { bool temp_call_servers_connected = call_servers_connected; diff --git a/dll/steam_inventory.cpp b/dll/steam_inventory.cpp new file mode 100644 index 00000000..ff2f1c33 --- /dev/null +++ b/dll/steam_inventory.cpp @@ -0,0 +1,963 @@ +/* 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 + . */ + +#include "dll/steam_inventory.h" + + +bool Steam_Inventory_Requests::result_done() const +{ + return done; +} + +uint32 Steam_Inventory_Requests::timestamp() const +{ + return std::chrono::duration_cast>(time_created.time_since_epoch()).count(); +} + + + +struct Steam_Inventory_Requests* Steam_Inventory::new_inventory_result(bool full_query, const SteamItemInstanceID_t* pInstanceIDs, uint32 unCountInstanceIDs) +{ + static SteamInventoryResult_t result; + ++result; + + struct Steam_Inventory_Requests request; + request.inventory_result = result; + request.full_query = full_query; + if (pInstanceIDs && unCountInstanceIDs) { + request.instance_ids.reserve(unCountInstanceIDs); + std::copy(pInstanceIDs, pInstanceIDs + unCountInstanceIDs, std::back_inserter(request.instance_ids)); + } + + request.time_created = std::chrono::system_clock::now(); + inventory_requests.push_back(request); + + return &(inventory_requests.back()); +} + +struct Steam_Inventory_Requests* Steam_Inventory::get_inventory_result(SteamInventoryResult_t resultHandle) +{ + auto request = std::find_if(inventory_requests.begin(), inventory_requests.end(), [&resultHandle](struct Steam_Inventory_Requests const& item) { return item.inventory_result == resultHandle; }); + if (inventory_requests.end() == request) + return NULL; + + return &(*request); +} + +void Steam_Inventory::read_items_db() +{ + std::string items_db_path = Local_Storage::get_game_settings_path() + items_user_file; + PRINT_DEBUG("file path: %s", items_db_path.c_str()); + local_storage->load_json(items_db_path, defined_items); +} + +void Steam_Inventory::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("items file path: %s", items_db_path.c_str()); + local_storage->load_json(items_db_path, user_items); + } +} + + +void Steam_Inventory::run_every_runcb_cb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Inventory *obj = (Steam_Inventory *)object; + obj->RunCallbacks(); +} + + +Steam_Inventory::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) +{ + this->run_every_runcb->add(&Steam_Inventory::run_every_runcb_cb, this); +} + +Steam_Inventory::~Steam_Inventory() +{ + this->run_every_runcb->remove(&Steam_Inventory::run_every_runcb_cb, this); +} + + +// INVENTORY ASYNC RESULT MANAGEMENT +// +// Asynchronous inventory queries always output a result handle which can be used with +// GetResultStatus, GetResultItems, etc. A SteamInventoryResultReady_t callback will +// be triggered when the asynchronous result becomes ready (or fails). +// + +// Find out the status of an asynchronous inventory result handle. Possible values: +// k_EResultPending - still in progress +// k_EResultOK - done, result ready +// k_EResultExpired - done, result ready, maybe out of date (see DeserializeResult) +// k_EResultInvalidParam - ERROR: invalid API call parameters +// k_EResultServiceUnavailable - ERROR: service temporarily down, you may retry later +// k_EResultLimitExceeded - ERROR: operation would exceed per-user inventory limits +// k_EResultFail - ERROR: unknown / generic error +STEAM_METHOD_DESC(Find out the status of an asynchronous inventory result handle.) +EResult Steam_Inventory::GetResultStatus( SteamInventoryResult_t resultHandle ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle); + if (!request) return k_EResultInvalidParam; + if (!request->result_done()) return k_EResultPending; + return k_EResultOK; +} + + +// Copies the contents of a result set into a flat array. The specific +// contents of the result set depend on which query which was used. +STEAM_METHOD_DESC(Copies the contents of a result set into a flat array. The specific contents of the result set depend on which query which was used.) +bool Steam_Inventory::GetResultItems( SteamInventoryResult_t resultHandle, + STEAM_OUT_ARRAY_COUNT( punOutItemsArraySize,Output array) SteamItemDetails_t *pOutItemsArray, + uint32 *punOutItemsArraySize ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle); + if (!request) return false; + if (!request->result_done()) return false; + if (!inventory_loaded) return false; + + if (pOutItemsArray != nullptr) + { + SteamItemDetails_t *items_array_base = pOutItemsArray; + uint32 max_items = *punOutItemsArraySize; + + if (request->full_query) { + // We end if we reached the end of items or the end of buffer + for( auto i = user_items.begin(); i != user_items.end() && max_items; ++i, --max_items ) + { + pOutItemsArray->m_iDefinition = std::stoi(i.key()); + pOutItemsArray->m_itemId = pOutItemsArray->m_iDefinition; + try + { + pOutItemsArray->m_unQuantity = i.value().get(); + } + catch (...) + { + pOutItemsArray->m_unQuantity = 0; + } + pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; + ++pOutItemsArray; + } + } else { + for (auto &itemid : request->instance_ids) { + if (!max_items) break; + auto it = user_items.find(std::to_string(itemid)); + if (it != user_items.end()) { + pOutItemsArray->m_iDefinition = itemid; + pOutItemsArray->m_itemId = itemid; + + try + { + pOutItemsArray->m_unQuantity = it->get(); + } + catch (...) + { + pOutItemsArray->m_unQuantity = 0; + } + pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; + ++pOutItemsArray; + --max_items; + } + } + } + + *punOutItemsArraySize = pOutItemsArray - items_array_base; + } + else if (punOutItemsArraySize != nullptr) + { + 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("good"); + return true; +} + + +// In combination with GetResultItems, you can use GetResultItemProperty to retrieve +// dynamic string properties for a given item returned in the result set. +// +// Property names are always composed of ASCII letters, numbers, and/or underscores. +// +// Pass a NULL pointer for pchPropertyName to get a comma - separated list of available +// property names. +// +// If pchValueBuffer is NULL, *punValueBufferSize will contain the +// suggested buffer size. Otherwise it will be the number of bytes actually copied +// to pchValueBuffer. If the results do not fit in the given buffer, partial +// results may be copied. +bool Steam_Inventory::GetResultItemProperty( SteamInventoryResult_t resultHandle, + uint32 unItemIndex, + const char *pchPropertyName, + STEAM_OUT_STRING_COUNT( punValueBufferSizeOut ) char *pchValueBuffer, uint32 *punValueBufferSizeOut ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + //TODO + return false; +} + + +// Returns the server time at which the result was generated. Compare against +// the value of IClientUtils::GetServerRealTime() to determine age. +STEAM_METHOD_DESC(Returns Steam_Inventory::the server time at which the result was generated. Compare against the value of IClientUtils::GetServerRealTime() to determine age.) +uint32 Steam_Inventory::GetResultTimestamp( SteamInventoryResult_t resultHandle ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle); + if (!request || !request->result_done()) return 0; + return request->timestamp(); +} + + +// Returns true if the result belongs to the target steam ID, false if the +// result does not. This is important when using DeserializeResult, to verify +// that a remote player is not pretending to have a different user's inventory. +STEAM_METHOD_DESC(Returns true if the result belongs to the target steam ID or false if the result does not. This is important when using DeserializeResult to verify that a remote player is not pretending to have a different users inventory.) +bool Steam_Inventory::CheckResultSteamID( SteamInventoryResult_t resultHandle, CSteamID steamIDExpected ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + //TODO + return true; +} + + +// Destroys a result handle and frees all associated memory. +STEAM_METHOD_DESC(Destroys a result handle and frees all associated memory.) +void Steam_Inventory::DestroyResult( SteamInventoryResult_t resultHandle ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + auto request = std::find_if(inventory_requests.begin(), inventory_requests.end(), [&resultHandle](struct Steam_Inventory_Requests const& item) { return item.inventory_result == resultHandle; }); + if (inventory_requests.end() == request) + return; + + inventory_requests.erase(request); +} + + + +// INVENTORY ASYNC QUERY +// + +// Captures the entire state of the current user's Steam inventory. +// You must call DestroyResult on this handle when you are done with it. +// Returns false and sets *pResultHandle to zero if inventory is unavailable. +// Note: calls to this function are subject to rate limits and may return +// cached results if called too frequently. It is suggested that you call +// this function only when you are about to display the user's full inventory, +// or if you expect that the inventory may have changed. +STEAM_METHOD_DESC(Captures the entire state of the current users Steam inventory.) +bool Steam_Inventory::GetAllItems( SteamInventoryResult_t *pResultHandle ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct Steam_Inventory_Requests* request = new_inventory_result(); + + if (pResultHandle != nullptr) + *pResultHandle = request->inventory_result; + + return true; +} + + + +// Captures the state of a subset of the current user's Steam inventory, +// identified by an array of item instance IDs. The results from this call +// can be serialized and passed to other players to "prove" that the current +// user owns specific items, without exposing the user's entire inventory. +// For example, you could call GetItemsByID with the IDs of the user's +// currently equipped cosmetic items and serialize this to a buffer, and +// then transmit this buffer to other players upon joining a game. +STEAM_METHOD_DESC(Captures the state of a subset of the current users Steam inventory identified by an array of item instance IDs.) +bool Steam_Inventory::GetItemsByID( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT( unCountInstanceIDs ) const SteamItemInstanceID_t *pInstanceIDs, uint32 unCountInstanceIDs ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (pResultHandle) { + struct Steam_Inventory_Requests *request = new_inventory_result(false, pInstanceIDs, unCountInstanceIDs); + *pResultHandle = request->inventory_result; + return true; + } + + return false; +} + + + +// RESULT SERIALIZATION AND AUTHENTICATION +// +// Serialized result sets contain a short signature which can't be forged +// or replayed across different game sessions. A result set can be serialized +// on the local client, transmitted to other players via your game networking, +// and deserialized by the remote players. This is a secure way of preventing +// hackers from lying about posessing rare/high-value items. + +// Serializes a result set with signature bytes to an output buffer. Pass +// NULL as an output buffer to get the required size via punOutBufferSize. +// The size of a serialized result depends on the number items which are being +// serialized. When securely transmitting items to other players, it is +// recommended to use "GetItemsByID" first to create a minimal result set. +// Results have a built-in timestamp which will be considered "expired" after +// an hour has elapsed. See DeserializeResult for expiration handling. +bool Steam_Inventory::SerializeResult( SteamInventoryResult_t resultHandle, STEAM_OUT_BUFFER_COUNT(punOutBufferSize) void *pOutBuffer, uint32 *punOutBufferSize ) +{ + PRINT_DEBUG("%i", resultHandle); + std::lock_guard lock(global_mutex); + //TODO + struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle); + if (!request) return false; + if (!request->result_done()) return false; + + uint8 buffer[8 + 128] = {}; + memset(buffer, 0x5F, sizeof(buffer)); + + if (!punOutBufferSize) return false; + PRINT_DEBUG(" Size %u", *punOutBufferSize); + if (!pOutBuffer) { + *punOutBufferSize = sizeof(buffer); + return true; + } + + if (*punOutBufferSize < sizeof(buffer)) { + *punOutBufferSize = sizeof(buffer); + return false; //?? + } + + memcpy(pOutBuffer, buffer, sizeof(buffer)); + *punOutBufferSize = sizeof(buffer); + return true; +} + + +// Deserializes a result set and verifies the signature bytes. Returns false +// if bRequireFullOnlineVerify is set but Steam is running in Offline mode. +// Otherwise returns true and then delivers error codes via GetResultStatus. +// +// The bRESERVED_MUST_BE_FALSE flag is reserved for future use and should not +// be set to true by your game at this time. +// +// DeserializeResult has a potential soft-failure mode where the handle status +// is set to k_EResultExpired. GetResultItems() still succeeds in this mode. +// The "expired" result could indicate that the data may be out of date - not +// just due to timed expiration (one hour), but also because one of the items +// in the result set may have been traded or consumed since the result set was +// generated. You could compare the timestamp from GetResultTimestamp() to +// ISteamUtils::GetServerRealTime() to determine how old the data is. You could +// simply ignore the "expired" result code and continue as normal, or you +// could challenge the player with expired data to send an updated result set. +bool Steam_Inventory::DeserializeResult( SteamInventoryResult_t *pOutResultHandle, STEAM_BUFFER_COUNT(punOutBufferSize) const void *pBuffer, uint32 unBufferSize, bool bRESERVED_MUST_BE_FALSE) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + //TODO + if (pOutResultHandle) { + struct Steam_Inventory_Requests *request = new_inventory_result(false); + *pOutResultHandle = request->inventory_result; + return true; + } + + return false; +} + + + +// INVENTORY ASYNC MODIFICATION +// + +// GenerateItems() creates one or more items and then generates a SteamInventoryCallback_t +// notification with a matching nCallbackContext parameter. This API is only intended +// for prototyping - it is only usable by Steam accounts that belong to the publisher group +// for your game. +// If punArrayQuantity is not NULL, it should be the same length as pArrayItems and should +// describe the quantity of each item to generate. +bool Steam_Inventory::GenerateItems( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, STEAM_ARRAY_COUNT(unArrayLength) const uint32 *punArrayQuantity, uint32 unArrayLength ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + return false; +} + + +// GrantPromoItems() checks the list of promotional items for which the user may be eligible +// and grants the items (one time only). On success, the result set will include items which +// were granted, if any. If no items were granted because the user isn't eligible for any +// promotions, this is still considered a success. +STEAM_METHOD_DESC(GrantPromoItems() Steam_Inventory::checks the list of promotional items for which the user may be eligible and grants the items (one time only).) +bool Steam_Inventory::GrantPromoItems( SteamInventoryResult_t *pResultHandle ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct Steam_Inventory_Requests* request = new_inventory_result(false); + + if (pResultHandle != nullptr) + *pResultHandle = request->inventory_result; + return true; +} + + +// AddPromoItem() / AddPromoItems() are restricted versions of GrantPromoItems(). Instead of +// scanning for all eligible promotional items, the check is restricted to a single item +// definition or set of item definitions. This can be useful if your game has custom UI for +// showing a specific promo item to the user. +bool Steam_Inventory::AddPromoItem( SteamInventoryResult_t *pResultHandle, SteamItemDef_t itemDef ) +{ + PRINT_DEBUG_ENTRY(); + //TODO + std::lock_guard lock(global_mutex); + struct Steam_Inventory_Requests* request = new_inventory_result(false); + + if (pResultHandle != nullptr) + *pResultHandle = request->inventory_result; + return true; +} + +bool Steam_Inventory::AddPromoItems( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, uint32 unArrayLength ) +{ + PRINT_DEBUG_ENTRY(); + //TODO + std::lock_guard lock(global_mutex); + struct Steam_Inventory_Requests* request = new_inventory_result(false); + + if (pResultHandle != nullptr) + *pResultHandle = request->inventory_result; + return true; +} + + +// ConsumeItem() removes items from the inventory, permanently. They cannot be recovered. +// Not for the faint of heart - if your game implements item removal at all, a high-friction +// UI confirmation process is highly recommended. +STEAM_METHOD_DESC(ConsumeItem() removes items from the inventory permanently.) +bool Steam_Inventory::ConsumeItem( SteamInventoryResult_t *pResultHandle, SteamItemInstanceID_t itemConsume, uint32 unQuantity ) +{ + PRINT_DEBUG("%llu %u", itemConsume, unQuantity); + std::lock_guard lock(global_mutex); + + auto it = user_items.find(std::to_string(itemConsume)); + if (it != user_items.end()) { + try + { + uint32 current = it->get(); + PRINT_DEBUG("previous %u", current); + if (current < unQuantity) unQuantity = current; + uint32 result = current - unQuantity; + if (result == 0) { + user_items.erase(it); + } else { + *it = result; + } + } + catch (...) {} + + } else { + return false; + } + + struct Steam_Inventory_Requests* request = new_inventory_result(false, &itemConsume, 1); + + if (pResultHandle != nullptr) + *pResultHandle = request->inventory_result; + + return true; +} + + +// ExchangeItems() is an atomic combination of item generation and consumption. +// It can be used to implement crafting recipes or transmutations, or items which unpack +// themselves into other items (e.g., a chest). +// Exchange recipes are defined in the ItemDef, and explicitly list the required item +// types and resulting generated type. +// Exchange recipes are evaluated atomically by the Inventory Service; if the supplied +// components do not match the recipe, or do not contain sufficient quantity, the +// exchange will fail. +bool Steam_Inventory::ExchangeItems( SteamInventoryResult_t *pResultHandle, + STEAM_ARRAY_COUNT(unArrayGenerateLength) const SteamItemDef_t *pArrayGenerate, STEAM_ARRAY_COUNT(unArrayGenerateLength) const uint32 *punArrayGenerateQuantity, uint32 unArrayGenerateLength, + STEAM_ARRAY_COUNT(unArrayDestroyLength) const SteamItemInstanceID_t *pArrayDestroy, STEAM_ARRAY_COUNT(unArrayDestroyLength) const uint32 *punArrayDestroyQuantity, uint32 unArrayDestroyLength ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + + +// TransferItemQuantity() is intended for use with items which are "stackable" (can have +// quantity greater than one). It can be used to split a stack into two, or to transfer +// quantity from one stack into another stack of identical items. To split one stack into +// two, pass k_SteamItemInstanceIDInvalid for itemIdDest and a new item will be generated. +bool Steam_Inventory::TransferItemQuantity( SteamInventoryResult_t *pResultHandle, SteamItemInstanceID_t itemIdSource, uint32 unQuantity, SteamItemInstanceID_t itemIdDest ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + + +// TIMED DROPS AND PLAYTIME CREDIT +// + +// Deprecated. Calling this method is not required for proper playtime accounting. +STEAM_METHOD_DESC( Deprecated method. Playtime accounting is performed on the Steam servers. ) +void Steam_Inventory::SendItemDropHeartbeat() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// Playtime credit must be consumed and turned into item drops by your game. Only item +// definitions which are marked as "playtime item generators" can be spawned. The call +// will return an empty result set if there is not enough playtime credit for a drop. +// Your game should call TriggerItemDrop at an appropriate time for the user to receive +// new items, such as between rounds or while the player is dead. Note that players who +// hack their clients could modify the value of "dropListDefinition", so do not use it +// to directly control rarity. +// See your Steamworks configuration to set playtime drop rates for individual itemdefs. +// The client library will suppress too-frequent calls to this method. +STEAM_METHOD_DESC(Playtime credit must be consumed and turned into item drops by your game.) +bool Steam_Inventory::TriggerItemDrop( SteamInventoryResult_t *pResultHandle, SteamItemDef_t dropListDefinition ) +{ + PRINT_DEBUG("%p %i", pResultHandle, dropListDefinition); + //TODO: if gameserver return false + std::lock_guard lock(global_mutex); + struct Steam_Inventory_Requests* request = new_inventory_result(false); + + if (pResultHandle != nullptr) + *pResultHandle = request->inventory_result; + return true; +} + + + +// Deprecated. This method is not supported. +bool Steam_Inventory::TradeItems( SteamInventoryResult_t *pResultHandle, CSteamID steamIDTradePartner, + STEAM_ARRAY_COUNT(nArrayGiveLength) const SteamItemInstanceID_t *pArrayGive, STEAM_ARRAY_COUNT(nArrayGiveLength) const uint32 *pArrayGiveQuantity, uint32 nArrayGiveLength, + STEAM_ARRAY_COUNT(nArrayGetLength) const SteamItemInstanceID_t *pArrayGet, STEAM_ARRAY_COUNT(nArrayGetLength) const uint32 *pArrayGetQuantity, uint32 nArrayGetLength ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + + +// ITEM DEFINITIONS +// +// Item definitions are a mapping of "definition IDs" (integers between 1 and 1000000) +// to a set of string properties. Some of these properties are required to display items +// on the Steam community web site. Other properties can be defined by applications. +// Use of these functions is optional; there is no reason to call LoadItemDefinitions +// if your game hardcodes the numeric definition IDs (eg, purple face mask = 20, blue +// weapon mod = 55) and does not allow for adding new item types without a client patch. +// + +// LoadItemDefinitions triggers the automatic load and refresh of item definitions. +// Every time new item definitions are available (eg, from the dynamic addition of new +// item types while players are still in-game), a SteamInventoryDefinitionUpdate_t +// callback will be fired. +STEAM_METHOD_DESC(LoadItemDefinitions triggers the automatic load and refresh of item definitions.) +bool Steam_Inventory::LoadItemDefinitions() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + if (!item_definitions_loaded) { + call_definition_update = true; + } + + //real steam launches a SteamInventoryResultReady_t which is why I create a new inventory result + new_inventory_result(false); + return true; +} + + +// GetItemDefinitionIDs returns the set of all defined item definition IDs (which are +// defined via Steamworks configuration, and not necessarily contiguous integers). +// If pItemDefIDs is null, the call will return true and *punItemDefIDsArraySize will +// contain the total size necessary for a subsequent call. Otherwise, the call will +// return false if and only if there is not enough space in the output array. +bool Steam_Inventory::GetItemDefinitionIDs( + STEAM_OUT_ARRAY_COUNT(punItemDefIDsArraySize,List of item definition IDs) SteamItemDef_t *pItemDefIDs, + STEAM_DESC(Size of array is passed in and actual size used is returned in this param) uint32 *punItemDefIDsArraySize ) +{ + PRINT_DEBUG("%p", pItemDefIDs); + std::lock_guard lock(global_mutex); + if (!punItemDefIDsArraySize) + return false; + + PRINT_DEBUG(" array_size %u", *punItemDefIDsArraySize); + + if (!item_definitions_loaded) + return false; + + if (pItemDefIDs == nullptr || *punItemDefIDsArraySize == 0) + { + *punItemDefIDsArraySize = defined_items.size(); + return true; + } + + if (*punItemDefIDsArraySize < defined_items.size()) + return false; + + for (auto i = defined_items.begin(); i != defined_items.end(); ++i) + *pItemDefIDs++ = std::stoi(i.key()); + + return true; +} + + +// GetItemDefinitionProperty returns a string property from a given item definition. +// Note that some properties (for example, "name") may be localized and will depend +// on the current Steam language settings (see ISteamApps::GetCurrentGameLanguage). +// Property names are always composed of ASCII letters, numbers, and/or underscores. +// Pass a NULL pointer for pchPropertyName to get a comma - separated list of available +// property names. If pchValueBuffer is NULL, *punValueBufferSize will contain the +// suggested buffer size. Otherwise it will be the number of bytes actually copied +// to pchValueBuffer. If the results do not fit in the given buffer, partial +// results may be copied. +bool Steam_Inventory::GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPropertyName, + STEAM_OUT_STRING_COUNT(punValueBufferSizeOut) char *pchValueBuffer, uint32 *punValueBufferSizeOut ) +{ + PRINT_DEBUG("%i %s", iDefinition, pchPropertyName); + std::lock_guard lock(global_mutex); + + auto item = defined_items.find(std::to_string(iDefinition)); + if (item != defined_items.end()) + { + if (pchPropertyName != nullptr) + { + // Should I check for punValueBufferSizeOut == nullptr ? + // Try to get the property + auto attr = item.value().find(pchPropertyName); + if (attr != item.value().end()) + { + std::string val; + try + { + val = attr.value().get(); + } + catch (...) + { + pchPropertyName = ""; + *punValueBufferSizeOut = 0; + PRINT_DEBUG(" Error, item: %d, attr: %s is not a string!", iDefinition, pchPropertyName); + return true; + } + if (pchValueBuffer != nullptr) + { + // copy what we can + strncpy(pchValueBuffer, val.c_str(), *punValueBufferSizeOut); + *punValueBufferSizeOut = std::min(static_cast(val.length() + 1), *punValueBufferSizeOut); + } + else + { + // Set punValueBufferSizeOut to the property size + *punValueBufferSizeOut = val.length() + 1; + } + + if (pchValueBuffer != nullptr) + { + // Make sure we have a null terminator + pchValueBuffer[*punValueBufferSizeOut-1] = '\0'; + } + } + // Property not found + else + { + *punValueBufferSizeOut = 0; + PRINT_DEBUG(" Attr %s not found for item %d", pchPropertyName, iDefinition); + return false; + } + } + else // Pass a NULL pointer for pchPropertyName to get a comma - separated list of available property names. + { + // If pchValueBuffer is NULL, *punValueBufferSize will contain the suggested buffer size + if (pchValueBuffer == nullptr) + { + // Should I check for punValueBufferSizeOut == nullptr ? + *punValueBufferSizeOut = 0; + for (auto i = item.value().begin(); i != item.value().end(); ++i) + *punValueBufferSizeOut += i.key().length() + 1; // Size of key + comma, and the last is not a comma but null char + } + else + { + // strncat always add the null terminator, so remove 1 to the string length + uint32_t len = *punValueBufferSizeOut-1; + *punValueBufferSizeOut = 0; + memset(pchValueBuffer, 0, len); + for( auto i = item.value().begin(); i != item.value().end() && len > 0; ++i ) + { + strncat(pchValueBuffer, i.key().c_str(), len); + // Count how many chars we copied + // Either the string length or the buffer size if its too small + uint32 x = std::min(len, static_cast(i.key().length())); + *punValueBufferSizeOut += x; + len -= x; + + if (len && std::distance(i, item.value().end()) != 1) // If this is not the last item, add a comma + strncat(pchValueBuffer, ",", len--); + + // Always add 1, its a comma or the null terminator + ++*punValueBufferSizeOut; + } + } + } + + return true; + } + + return false; +} + + +// Request the list of "eligible" promo items that can be manually granted to the given +// user. These are promo items of type "manual" that won't be granted automatically. +// An example usage of this is an item that becomes available every week. +STEAM_CALL_RESULT( SteamInventoryEligiblePromoItemDefIDs_t ) +SteamAPICall_t Steam_Inventory::RequestEligiblePromoItemDefinitionsIDs( CSteamID steamID ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +// After handling a SteamInventoryEligiblePromoItemDefIDs_t call result, use this +// function to pull out the list of item definition ids that the user can be +// manually granted via the AddPromoItems() call. +bool Steam_Inventory::GetEligiblePromoItemDefinitionIDs( + CSteamID steamID, + STEAM_OUT_ARRAY_COUNT(punItemDefIDsArraySize,List of item definition IDs) SteamItemDef_t *pItemDefIDs, + STEAM_DESC(Size of array is passed in and actual size used is returned in this param) uint32 *punItemDefIDsArraySize ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +// Starts the purchase process for the given item definitions. The callback SteamInventoryStartPurchaseResult_t +// will be posted if Steam was able to initialize the transaction. +// +// Once the purchase has been authorized and completed by the user, the callback SteamInventoryResultReady_t +// will be posted. +STEAM_CALL_RESULT( SteamInventoryStartPurchaseResult_t ) +SteamAPICall_t Steam_Inventory::StartPurchase( STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, STEAM_ARRAY_COUNT(unArrayLength) const uint32 *punArrayQuantity, uint32 unArrayLength ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +// Request current prices for all applicable item definitions +STEAM_CALL_RESULT( SteamInventoryRequestPricesResult_t ) +SteamAPICall_t Steam_Inventory::RequestPrices() +{ + PRINT_DEBUG_ENTRY(); + SteamInventoryRequestPricesResult_t data{}; + data.m_result = k_EResultOK; + memcpy(data.m_rgchCurrency, "USD", 4); + + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.2); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.2); + return ret; +} + + +// Returns the number of items with prices. Need to call RequestPrices() first. +uint32 Steam_Inventory::GetNumItemsWithPrices() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +bool Steam_Inventory::GetItemsWithPrices( STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pArrayItemDefs, Items with prices) SteamItemDef_t *pArrayItemDefs, + STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pCurrentPrices, + STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pBasePrices, + uint32 unArrayLength ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +// Returns item definition ids and their prices in the user's local currency. +// Need to call RequestPrices() first. +bool Steam_Inventory::GetItemsWithPrices( STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pArrayItemDefs, Items with prices) SteamItemDef_t *pArrayItemDefs, + STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pPrices, + uint32 unArrayLength ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return GetItemsWithPrices(pArrayItemDefs, pPrices, NULL, unArrayLength); +} + +bool Steam_Inventory::GetItemPrice( SteamItemDef_t iDefinition, uint64 *pCurrentPrice, uint64 *pBasePrice ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +// Retrieves the price for the item definition id +// Returns false if there is no price stored for the item definition. +bool Steam_Inventory::GetItemPrice( SteamItemDef_t iDefinition, uint64 *pPrice ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return GetItemPrice(iDefinition, pPrice, NULL); +} + + +// Create a request to update properties on items +SteamInventoryUpdateHandle_t Steam_Inventory::StartUpdateProperties() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// Remove the property on the item +bool Steam_Inventory::RemoveProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +// Accessor methods to set properties on items +bool Steam_Inventory::SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, const char *pchPropertyValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +bool Steam_Inventory::SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, bool bValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +bool Steam_Inventory::SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, int64 nValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +bool Steam_Inventory::SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, float flValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +// Submit the update request by handle +bool Steam_Inventory::SubmitUpdateProperties( SteamInventoryUpdateHandle_t handle, SteamInventoryResult_t * pResultHandle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +bool Steam_Inventory::InspectItem( SteamInventoryResult_t *pResultHandle, const char *pchItemToken ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +void Steam_Inventory::RunCallbacks() +{ + if (call_definition_update || !inventory_requests.empty()) { + if (!item_definitions_loaded) { + read_items_db(); + item_definitions_loaded = true; + + //only gets called once + //also gets called when getting items + SteamInventoryDefinitionUpdate_t data = {}; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.05); + } + + call_definition_update = false; + } + + if (!inventory_requests.empty() && !inventory_loaded) { + read_inventory_db(); + inventory_loaded = true; + } + + if (inventory_loaded) + { + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + for (auto& r : inventory_requests) { + if (!r.done && std::chrono::duration_cast>(now - r.time_created).count() > r.timeout) { + if (r.full_query) { + // SteamInventoryFullUpdate_t callbacks are triggered when GetAllItems + // successfully returns a result which is newer / fresher than the last + // known result. + struct SteamInventoryFullUpdate_t data; + data.m_handle = r.inventory_result; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + + { + struct SteamInventoryResultReady_t data; + data.m_handle = r.inventory_result; + data.m_result = k_EResultOK; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + + r.done = true; + } + } + } +} diff --git a/dll/steam_masterserver_updater.cpp b/dll/steam_masterserver_updater.cpp new file mode 100644 index 00000000..965e99dc --- /dev/null +++ b/dll/steam_masterserver_updater.cpp @@ -0,0 +1,224 @@ +/* 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 + . */ + +#include "dll/steam_masterserver_updater.h" + + +void Steam_Masterserver_Updater::steam_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Masterserver_Updater *steam_masterserverupdater = (Steam_Masterserver_Updater *)object; + steam_masterserverupdater->Callback(msg); +} + +void Steam_Masterserver_Updater::steam_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Masterserver_Updater *steam_masterserverupdater = (Steam_Masterserver_Updater *)object; + steam_masterserverupdater->RunCallbacks(); +} + + +Steam_Masterserver_Updater::Steam_Masterserver_Updater(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_Masterserver_Updater::steam_callback, this); + this->run_every_runcb->add(&Steam_Masterserver_Updater::steam_run_every_runcb, this); +} + +Steam_Masterserver_Updater::~Steam_Masterserver_Updater() +{ + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Masterserver_Updater::steam_callback, this); + this->run_every_runcb->remove(&Steam_Masterserver_Updater::steam_run_every_runcb, this); +} + + +// Call this as often as you like to tell the master server updater whether or not +// you want it to be active (default: off). +void Steam_Masterserver_Updater::SetActive( bool bActive ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// You usually don't need to modify this. +// Pass -1 to use the default value for iHeartbeatInterval. +// Some mods change this. +void Steam_Masterserver_Updater::SetHeartbeatInterval( int iHeartbeatInterval ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + + +// These are in GameSocketShare mode, where instead of ISteamMasterServerUpdater creating its own +// socket to talk to the master server on, it lets the game use its socket to forward messages +// back and forth. This prevents us from requiring server ops to open up yet another port +// in their firewalls. +// +// the IP address and port should be in host order, i.e 127.0.0.1 == 0x7f000001 + +// These are used when you've elected to multiplex the game server's UDP socket +// rather than having the master server updater use its own sockets. +// +// Source games use this to simplify the job of the server admins, so they +// don't have to open up more ports on their firewalls. + +// Call this when a packet that starts with 0xFFFFFFFF comes in. That means +// it's for us. +bool Steam_Masterserver_Updater::HandleIncomingPacket( const void *pData, int cbData, uint32 srcIP, uint16 srcPort ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return true; +} + + +// AFTER calling HandleIncomingPacket for any packets that came in that frame, call this. +// This gets a packet that the master server updater needs to send out on UDP. +// It returns the length of the packet it wants to send, or 0 if there are no more packets to send. +// Call this each frame until it returns 0. +int Steam_Masterserver_Updater::GetNextOutgoingPacket( void *pOut, int cbMaxOut, uint32 *pNetAdr, uint16 *pPort ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + + +// Functions to set various fields that are used to respond to queries. + +// Call this to set basic data that is passed to the server browser. +void Steam_Masterserver_Updater::SetBasicServerData( + unsigned short nProtocolVersion, + bool bDedicatedServer, + const char *pRegionName, + const char *pProductName, + unsigned short nMaxReportedClients, + bool bPasswordProtected, + const char *pGameDescription ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// Call this to clear the whole list of key/values that are sent in rules queries. +void Steam_Masterserver_Updater::ClearAllKeyValues() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// Call this to add/update a key/value pair. +void Steam_Masterserver_Updater::SetKeyValue( const char *pKey, const char *pValue ) +{ + PRINT_DEBUG("TODO '%s'='%s'", pKey, pValue); + std::lock_guard lock(global_mutex); +} + + + +// You can call this upon shutdown to clear out data stored for this game server and +// to tell the master servers that this server is going away. +void Steam_Masterserver_Updater::NotifyShutdown() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// Returns true if the master server has requested a restart. +// Only returns true once per request. +bool Steam_Masterserver_Updater::WasRestartRequested() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +// Force it to request a heartbeat from the master servers. +void Steam_Masterserver_Updater::ForceHeartbeat() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// Manually edit and query the master server list. +// It will provide name resolution and use the default master server port if none is provided. +bool Steam_Masterserver_Updater::AddMasterServer( const char *pServerAddress ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return true; +} + +bool Steam_Masterserver_Updater::RemoveMasterServer( const char *pServerAddress ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return true; +} + + +int Steam_Masterserver_Updater::GetNumMasterServers() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +// Returns the # of bytes written to pOut. +int Steam_Masterserver_Updater::GetMasterServerAddress( int iServer, char *pOut, int outBufferSize ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + + +void Steam_Masterserver_Updater::RunCallbacks() +{ +} + +void Steam_Masterserver_Updater::Callback(Common_Message *msg) +{ + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + + if (msg->low_level().type() == Low_Level::DISCONNECT) { + + } + } +} diff --git a/dll/steam_matchmaking.cpp b/dll/steam_matchmaking.cpp new file mode 100644 index 00000000..27e87874 --- /dev/null +++ b/dll/steam_matchmaking.cpp @@ -0,0 +1,1624 @@ +/* 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 + . */ + +#include "dll/steam_matchmaking.h" + +#define SEND_LOBBY_RATE 5.0 + +#define PENDING_JOIN_TIMEOUT 10.0 +#define REQUEST_LOBBY_DATA_TIMEOUT 6.0 +#define LOBBY_DELETED_TIMEOUT 2 + +#define LOBBY_CREATE_DELAY 0.07 //artificial delay for lobby creation + +#define FILTER_MAX_DEFAULT 4096 + +#define LOBBY_SEARCH_TIMEOUT 0.2 //Tested on real steam + + +google::protobuf::Map::const_iterator Steam_Matchmaking::caseinsensitive_find(const ::google::protobuf::Map< ::std::string, ::std::string >& map, std::string key) +{ + auto x = map.begin(); + while (x != map.end()) { + if (common_helpers::str_cmp_insensitive(key, x->first)) { + break; + } + ++x; + } + + return x; +} + +Lobby* Steam_Matchmaking::get_lobby(CSteamID id) +{ + if (!id.IsLobby()) + return NULL; + + auto lobby = std::find_if(lobbies.begin(), lobbies.end(), [&id](Lobby const& item) { return (item.room_id() & 0xFFFFFFFF) == (id.GetAccountID()); }); + if (lobbies.end() == lobby) + return NULL; + + return &(*lobby); +} + +void Steam_Matchmaking::send_lobby_data() +{ + PRINT_DEBUG("lobbies %zu", lobbies.size()); + + for(auto & l: lobbies) { + if (get_lobby_member(&l, settings->get_local_steam_id()) && l.owner() == settings->get_local_steam_id().ConvertToUint64() && !l.deleted()) { + PRINT_DEBUG("lobby " "%" PRIu64 "", l.room_id()); + Common_Message msg = Common_Message(); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_allocated_lobby(new Lobby(l)); + network->sendToAllIndividuals(&msg, true); + } + } +} + +void Steam_Matchmaking::trigger_lobby_dataupdate(CSteamID lobby, CSteamID member, bool success, double cb_timeout, bool send_changed_lobby) +{ + PRINT_DEBUG("%llu %llu", lobby.ConvertToUint64(), member.ConvertToUint64()); + LobbyDataUpdate_t data{}; + data.m_ulSteamIDLobby = lobby.ConvertToUint64(); + data.m_bSuccess = success; + data.m_ulSteamIDMember = member.ConvertToUint64(); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), cb_timeout, true); + + // if this was a user data update, then trigger another callback for the lobby itself + if (lobby != member) { + data.m_ulSteamIDMember = lobby.ConvertToUint64(); + //Is this really necessary? + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), cb_timeout, true); + } + + Lobby *l = get_lobby(lobby); + if (l && l->owner() == settings->get_local_steam_id().ConvertToUint64()) { + if (send_changed_lobby) { + PRINT_DEBUG("resending new data"); + Common_Message msg = Common_Message(); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_allocated_lobby(new Lobby(*l)); + network->sendToAllIndividuals(&msg, true); + } + } +} + +void Steam_Matchmaking::trigger_lobby_member_join_leave(CSteamID lobby, CSteamID member, bool leaving, bool success, double cb_timeout) +{ + LobbyChatUpdate_t data{}; + data.m_ulSteamIDLobby = lobby.ConvertToUint64(); + data.m_ulSteamIDUserChanged = member.ConvertToUint64(); + data.m_ulSteamIDMakingChange = member.ConvertToUint64(); + uint32 member_state_change = 0; //EChatMemberStateChange + + if (!leaving) { + member_state_change |= k_EChatMemberStateChangeEntered; + } else { + member_state_change |= k_EChatMemberStateChangeLeft; + } + + data.m_rgfChatMemberStateChange = member_state_change; + + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), cb_timeout); + // trigger_lobby_dataupdate(lobby, member, success, cb_timeout); + trigger_lobby_dataupdate(lobby, lobby, success, cb_timeout); +} + +bool Steam_Matchmaking::send_owner_packet(CSteamID lobby_id, Lobby_Messages *message) +{ + Lobby *lobby = get_lobby(lobby_id); + + if (!lobby) { + return false; + } + + Common_Message msg; + msg.set_allocated_lobby_messages(message); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id((uint64)lobby->owner()); + msg.mutable_lobby_messages()->set_id(lobby_id.ConvertToUint64()); + return network->sendTo(&msg, true); +} + +bool Steam_Matchmaking::send_clients_packet(CSteamID lobby_id, Lobby_Messages *message) +{ + Lobby *lobby = get_lobby(lobby_id); + + if (!lobby) { + return false; + } + + Common_Message msg; + msg.set_allocated_lobby_messages(message); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.mutable_lobby_messages()->set_id(lobby_id.ConvertToUint64()); + return network->sendToAllIndividuals(&msg, true); +} + +bool Steam_Matchmaking::send_lobby_members_packet(CSteamID lobby_id, Lobby_Messages *message) +{ + Lobby *lobby = get_lobby(lobby_id); + + if (!lobby) { + return false; + } + + Common_Message msg; + msg.set_allocated_lobby_messages(message); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.mutable_lobby_messages()->set_id(lobby_id.ConvertToUint64()); + + for (auto & m : lobby->members()) { + msg.set_dest_id((uint64)m.id()); + network->sendTo(&msg, true); + } + + return true; +} + +bool Steam_Matchmaking::change_owner(Lobby *lobby, CSteamID new_owner) +{ + Lobby_Messages *message = new Lobby_Messages(); + message->set_type(Lobby_Messages::CHANGE_OWNER); + message->set_idata(new_owner.ConvertToUint64()); + lobby->set_owner(new_owner.ConvertToUint64()); + send_owner_packet((uint64)lobby->room_id(), message); + trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)lobby->room_id(), true); + return true; +} + +void Steam_Matchmaking::send_gameservercreated_cb(uint64 room_id, uint64 server_id, uint32 ip, uint16 port) +{ + LobbyGameCreated_t data; + data.m_ulSteamIDLobby = room_id; + data.m_ulSteamIDGameServer = server_id; + data.m_unIP = ip; + data.m_usPort = port; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); +} + +void Steam_Matchmaking::on_self_enter_leave_lobby(CSteamID id, int type, bool leaving) +{ + if (type == k_ELobbyTypeInvisible) return; + + if (!leaving) { + settings->set_lobby(id); + } else { + settings->set_lobby(k_steamIDNil); + } + + //TODO: handle cases where in two lobbies of type not invisible + //steam says a user can only be in one regular lobby but we all know how well documented steam is +} + +void Steam_Matchmaking::steam_matchmaking_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Matchmaking *steam_matchmaking = (Steam_Matchmaking *)object; + steam_matchmaking->Callback(msg); +} + +void Steam_Matchmaking::steam_matchmaking_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Matchmaking *steam_matchmaking = (Steam_Matchmaking *)object; + steam_matchmaking->RunCallbacks(); +} + +bool Steam_Matchmaking::add_member_to_lobby(Lobby *lobby, CSteamID id) +{ + if (get_lobby_member(lobby, id)) return false; // player already exists + + Lobby_Member *member = lobby->add_members(); + member->set_id(id.ConvertToUint64()); + PRINT_DEBUG("added lobby member %llu", (uint64)id.ConvertToUint64()); + return true; +} + +bool Steam_Matchmaking::leave_lobby(Lobby *lobby, CSteamID id) +{ + auto member = std::find_if(lobby->mutable_members()->begin(), lobby->mutable_members()->end(), [&id](Lobby_Member const& item) { return item.id() == id.ConvertToUint64(); }); + if (member != lobby->mutable_members()->end()) { + lobby->mutable_members()->erase(member); + return true; + } + + return false; +} + +Lobby_Member* Steam_Matchmaking::get_lobby_member(Lobby *lobby, CSteamID user_id) +{ + if (!lobby) return NULL; + + auto member = std::find_if(lobby->mutable_members()->begin(), lobby->mutable_members()->end(), [&user_id](Lobby_Member const& item) { return item.id() == user_id.ConvertToUint64(); }); + if (lobby->mutable_members()->end() == member) + return NULL; + + return &(*member); +} + + +Steam_Matchmaking::Steam_Matchmaking(class Settings *settings, class Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) +{ + this->settings = settings; + this->local_storage = local_storage; + this->network = network; + this->callback_results = callback_results; + this->callbacks = callbacks; + this->run_every_runcb = run_every_runcb; + + this->filter_max_results = FILTER_MAX_DEFAULT; + search_call_api_id = 0; + searching = false; + + this->network->setCallback(CALLBACK_ID_LOBBY, settings->get_local_steam_id(), &Steam_Matchmaking::steam_matchmaking_callback, this); + this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Matchmaking::steam_matchmaking_callback, this); + this->run_every_runcb->add(&Steam_Matchmaking::steam_matchmaking_run_every_runcb, this); +} + +Steam_Matchmaking::~Steam_Matchmaking() +{ + this->network->rmCallback(CALLBACK_ID_LOBBY, settings->get_local_steam_id(), &Steam_Matchmaking::steam_matchmaking_callback, this); + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Matchmaking::steam_matchmaking_callback, this); + this->run_every_runcb->remove(&Steam_Matchmaking::steam_matchmaking_run_every_runcb, this); +} + + +// game server favorites storage +// saves basic details about a multiplayer game server locally + +// returns the number of favorites servers the user has stored +int Steam_Matchmaking::GetFavoriteGameCount() +{ + PRINT_DEBUG_ENTRY(); + std::string file_path = local_storage->get_current_save_directory() + "7" + PATH_SEPARATOR + Local_Storage::remote_storage_folder + PATH_SEPARATOR + "serverbrowser_favorites.txt"; + unsigned long long file_size = file_size_(file_path); + if (file_size) { + std::string list{}; + list.resize(file_size); + Local_Storage::get_file_data(file_path, (char *)&list[0], file_size, 0); + auto list_lines = std::count(list.begin(), list.end(), '\n'); + list_lines += (!list.empty() && list.back() != '\n'); + return list_lines; + } + return 0; +} + + +// returns the details of the game server +// iGame is of range [0,GetFavoriteGameCount()) +// *pnIP, *pnConnPort are filled in the with IP:port of the game server +// *punFlags specify whether the game server was stored as an explicit favorite or in the history of connections +// *pRTime32LastPlayedOnServer is filled in the with the Unix time the favorite was added +bool Steam_Matchmaking::GetFavoriteGame( int iGame, AppId_t *pnAppID, uint32 *pnIP, uint16 *pnConnPort, uint16 *pnQueryPort, uint32 *punFlags, uint32 *pRTime32LastPlayedOnServer ) +{ + PRINT_DEBUG_ENTRY(); + return false; +} + + +// adds the game server to the local list; updates the time played of the server if it already exists in the list +int Steam_Matchmaking::AddFavoriteGame( AppId_t nAppID, uint32 nIP, uint16 nConnPort, uint16 nQueryPort, uint32 unFlags, uint32 rTime32LastPlayedOnServer ) +{ + PRINT_DEBUG("%u %u %hu %hu %u %u", nAppID, nIP, nConnPort, nQueryPort, unFlags, rTime32LastPlayedOnServer); + + std::string file_path; + unsigned long long file_size; + + if (unFlags == 1) { + file_path = local_storage->get_current_save_directory() + "7" + PATH_SEPARATOR + Local_Storage::remote_storage_folder + PATH_SEPARATOR + "serverbrowser_favorites.txt"; + file_size = file_size_(file_path); + } + else if (unFlags == 2) { + file_path = local_storage->get_current_save_directory() + "7" + PATH_SEPARATOR + Local_Storage::remote_storage_folder + PATH_SEPARATOR + "serverbrowser_history.txt"; + file_size = file_size_(file_path); + } + else { + return 0; + } + + unsigned char ip[4]; + ip[0] = nIP & 0xFF; + ip[1] = (nIP >> 8) & 0xFF; + ip[2] = (nIP >> 16) & 0xFF; + ip[3] = (nIP >> 24) & 0xFF; + char newip[24]; + snprintf(newip, sizeof(newip), "%d.%d.%d.%d:%d\n", ip[3], ip[2], ip[1], ip[0], nConnPort); + std::string newip_string; + newip_string.append(newip); + + if (file_size) { + std::string list{}; + list.resize(file_size); + Local_Storage::get_file_data(file_path, (char *)&list[0], file_size, 0); + auto list_lines = std::count(list.begin(), list.end(), '\n'); + list_lines += (!list.empty() && list.back() != '\n'); + + std::size_t find_ip = list.find(newip_string); + if (find_ip == std::string::npos) { + list.append(newip_string); + list.append("\n"); + + std::size_t file_directory = file_path.rfind("/"); + std::string directory_path; + std::string file_name; + if (file_directory != std::string::npos) { + directory_path = file_path.substr(0, file_directory); + file_name = file_path.substr(file_directory); + } + Local_Storage::store_file_data(directory_path, file_name, (char *)list.data(), list.size()); + + return ++list_lines; + } + + return list_lines; + } + else { + newip_string.append("\n"); + + std::size_t file_directory = file_path.rfind("/"); + std::string directory_path; + std::string file_name; + if (file_directory != std::string::npos) { + directory_path = file_path.substr(0, file_directory); + file_name = file_path.substr(file_directory); + } + Local_Storage::store_file_data(directory_path, file_name, (char *)newip_string.data(), newip_string.size()); + + return 1; + } +} + + +// removes the game server from the local storage; returns true if one was removed +bool Steam_Matchmaking::RemoveFavoriteGame( AppId_t nAppID, uint32 nIP, uint16 nConnPort, uint16 nQueryPort, uint32 unFlags ) +{ + PRINT_DEBUG_ENTRY(); + + std::string file_path; + unsigned long long file_size; + + if (unFlags == 1) { + file_path = local_storage->get_current_save_directory() + "7" + PATH_SEPARATOR + Local_Storage::remote_storage_folder + "serverbrowser_favorites.txt"; + file_size = file_size_(file_path); + } + else if (unFlags == 2) { + file_path = local_storage->get_current_save_directory() + "7" + PATH_SEPARATOR + Local_Storage::remote_storage_folder + "serverbrowser_history.txt"; + file_size = file_size_(file_path); + } + else { + return false; + } + + if (file_size) { + std::string list{}; + list.resize(file_size); + Local_Storage::get_file_data(file_path, (char *)&list[0], file_size, 0); + + unsigned char ip[4]; + ip[0] = nIP & 0xFF; + ip[1] = (nIP >> 8) & 0xFF; + ip[2] = (nIP >> 16) & 0xFF; + ip[3] = (nIP >> 24) & 0xFF; + char newip[24]; + snprintf((char *)newip, sizeof(newip), "%d.%d.%d.%d:%d\n", ip[3], ip[2], ip[1], ip[0], nConnPort); + std::string newip_string; + newip_string.append(newip); + + std::size_t list_ip = list.find(newip_string); + if (list_ip != std::string::npos) { + list.erase(list_ip, newip_string.length()); + + std::size_t file_directory = file_path.rfind("/"); + std::string directory_path; + std::string file_name; + if (file_directory != std::string::npos) { + directory_path = file_path.substr(0, file_directory); + file_name = file_path.substr(file_directory); + } + Local_Storage::store_file_data(directory_path, file_name, (char *)list.data(), list.size()); + + return true; + } + } + + return false; +} + + +/////// +// Game lobby functions + +// Get a list of relevant lobbies +// this is an asynchronous request +// results will be returned by LobbyMatchList_t callback & call result, with the number of lobbies found +// this will never return lobbies that are full +// to add more filter, the filter calls below need to be call before each and every RequestLobbyList() call +// use the CCallResult<> object in steam_api.h to match the SteamAPICall_t call result to a function in an object, e.g. +/* + class CMyLobbyListManager + { + CCallResult m_CallResultLobbyMatchList; + void FindLobbies() + { + // SteamMatchmaking()->AddRequestLobbyListFilter*() functions would be called here, before RequestLobbyList(); + + m_CallResultLobbyMatchList.Set( hSteamAPICall, this, &CMyLobbyListManager::OnLobbyMatchList ); + + } + + void OnLobbyMatchList( LobbyMatchList_t *pLobbyMatchList, bool bIOFailure ) + { + // lobby list has be retrieved from Steam back-end, use results + } + } +*/ +// +STEAM_CALL_RESULT( LobbyMatchList_t ) +SteamAPICall_t Steam_Matchmaking::RequestLobbyList() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + filtered_lobbies.clear(); + lobby_last_search = std::chrono::high_resolution_clock::now(); + filter_values_copy = filter_values; + filter_max_results_copy = filter_max_results; + filter_values.clear(); + filter_max_results = FILTER_MAX_DEFAULT; + searching = true; + if (search_call_api_id) callback_results->rmCallBack(search_call_api_id, NULL); + search_call_api_id = callback_results->reserveCallResult(); + + return search_call_api_id; +} + +void Steam_Matchmaking::RequestLobbyList_OLD() +{ + RequestLobbyList(); +} + +// filters for lobbies +// this needs to be called before RequestLobbyList() to take effect +// these are cleared on each call to RequestLobbyList() +void Steam_Matchmaking::AddRequestLobbyListStringFilter( const char *pchKeyToMatch, const char *pchValueToMatch, ELobbyComparison eComparisonType ) +{ + PRINT_DEBUG("'%s'=='%s' %i", pchKeyToMatch, pchValueToMatch, eComparisonType); + if (!pchValueToMatch) return; + + std::lock_guard lock(global_mutex); + struct Filter_Values fv; + fv.key = std::string(pchKeyToMatch); + fv.value_string = std::string(pchValueToMatch); + fv.is_int = false; + fv.eComparisonType = eComparisonType; + filter_values.push_back(fv); + +} + +// numerical comparison +void Steam_Matchmaking::AddRequestLobbyListNumericalFilter( const char *pchKeyToMatch, int nValueToMatch, ELobbyComparison eComparisonType ) +{ + PRINT_DEBUG("'%s'==%i %i", pchKeyToMatch, nValueToMatch, eComparisonType); + std::lock_guard lock(global_mutex); + struct Filter_Values fv; + fv.key = std::string(pchKeyToMatch); + fv.value_int = nValueToMatch; + fv.is_int = true; + fv.eComparisonType = eComparisonType; + filter_values.push_back(fv); + +} + +// returns results closest to the specified value. Multiple near filters can be added, with early filters taking precedence +void Steam_Matchmaking::AddRequestLobbyListNearValueFilter( const char *pchKeyToMatch, int nValueToBeCloseTo ) +{ + PRINT_DEBUG("'%s'==%u", pchKeyToMatch, nValueToBeCloseTo); + std::lock_guard lock(global_mutex); + + +} + +// returns only lobbies with the specified number of slots available +void Steam_Matchmaking::AddRequestLobbyListFilterSlotsAvailable( int nSlotsAvailable ) +{ + PRINT_DEBUG("%i", nSlotsAvailable); + std::lock_guard lock(global_mutex); + + +} + +// sets the distance for which we should search for lobbies (based on users IP address to location map on the Steam backed) +void Steam_Matchmaking::AddRequestLobbyListDistanceFilter( ELobbyDistanceFilter eLobbyDistanceFilter ) +{ + PRINT_DEBUG("%i", eLobbyDistanceFilter); + std::lock_guard lock(global_mutex); + + +} + +// sets how many results to return, the lower the count the faster it is to download the lobby results & details to the client +void Steam_Matchmaking::AddRequestLobbyListResultCountFilter( int cMaxResults ) +{ + PRINT_DEBUG("%i", cMaxResults); + std::lock_guard lock(global_mutex); + filter_max_results = cMaxResults; + +} + + +void Steam_Matchmaking::AddRequestLobbyListCompatibleMembersFilter( CSteamID steamIDLobby ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + +} + +void Steam_Matchmaking::AddRequestLobbyListFilter( const char *pchKeyToMatch, const char *pchValueToMatch ) +{ + AddRequestLobbyListStringFilter(pchKeyToMatch, pchValueToMatch, k_ELobbyComparisonEqual); +} + +void Steam_Matchmaking::AddRequestLobbyListNumericalFilter( const char *pchKeyToMatch, int nValueToMatch, int nComparisonType ) +{ + AddRequestLobbyListNumericalFilter(pchKeyToMatch, nValueToMatch, (ELobbyComparison) nComparisonType ); +} + +void Steam_Matchmaking::AddRequestLobbyListSlotsAvailableFilter() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + +} + +// returns the CSteamID of a lobby, as retrieved by a RequestLobbyList call +// should only be called after a LobbyMatchList_t callback is received +// iLobby is of the range [0, LobbyMatchList_t::m_nLobbiesMatching) +// the returned CSteamID::IsValid() will be false if iLobby is out of range +CSteamID Steam_Matchmaking::GetLobbyByIndex( int iLobby ) +{ + PRINT_DEBUG("%i", iLobby); + std::lock_guard lock(global_mutex); + CSteamID id = k_steamIDNil; + if (0 <= iLobby && iLobby < filtered_lobbies.size()) id = filtered_lobbies[iLobby]; + PRINT_DEBUG("found lobby %llu", id.ConvertToUint64()); + return id; +} + +// Create a lobby on the Steam servers. +// If private, then the lobby will not be returned by any RequestLobbyList() call; the CSteamID +// of the lobby will need to be communicated via game channels or via InviteUserToLobby() +// this is an asynchronous request +// results will be returned by LobbyCreated_t callback and call result; lobby is joined & ready to use at this point +// a LobbyEnter_t callback will also be received (since the local user is joining their own lobby) +STEAM_CALL_RESULT( LobbyCreated_t ) +SteamAPICall_t Steam_Matchmaking::CreateLobby( ELobbyType eLobbyType, int cMaxMembers ) +{ + PRINT_DEBUG("type: %i max_members: %i", eLobbyType, cMaxMembers); + std::lock_guard lock(global_mutex); + struct Pending_Creates p_c{}; + p_c.api_id = callback_results->reserveCallResult(); + p_c.eLobbyType = eLobbyType; + p_c.cMaxMembers = cMaxMembers; + p_c.created = std::chrono::high_resolution_clock::now(); + pending_creates.push_back(p_c); + return p_c.api_id; +} + +SteamAPICall_t Steam_Matchmaking::CreateLobby( ELobbyType eLobbyType ) +{ + PRINT_DEBUG("old"); + return CreateLobby(eLobbyType, 0); +} + +void Steam_Matchmaking::CreateLobby_OLD( ELobbyType eLobbyType ) +{ + CreateLobby(eLobbyType); +} + +void Steam_Matchmaking::CreateLobby( bool bPrivate ) +{ + CreateLobby(bPrivate ? k_ELobbyTypePrivate : k_ELobbyTypePublic); +} + +// Joins an existing lobby +// this is an asynchronous request +// results will be returned by LobbyEnter_t callback & call result, check m_EChatRoomEnterResponse to see if was successful +// lobby metadata is available to use immediately on this call completing +STEAM_CALL_RESULT( LobbyEnter_t ) +SteamAPICall_t Steam_Matchmaking::JoinLobby( CSteamID steamIDLobby ) +{ + PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); + std::lock_guard lock(global_mutex); + + auto pj = std::find_if(pending_joins.begin(), pending_joins.end(), [&steamIDLobby](Pending_Joins const& item) {return item.lobby_id == steamIDLobby;}); + if (pj != pending_joins.end()) { + PRINT_DEBUG("already found in pending joins list"); + return pj->api_id; + } + + Pending_Joins pending_join{}; + pending_join.api_id = callback_results->reserveCallResult(); + pending_join.lobby_id = steamIDLobby; + pending_join.joined = std::chrono::high_resolution_clock::now(); + pending_joins.push_back(pending_join); + + Lobby_Messages *message = new Lobby_Messages(); + message->set_type(Lobby_Messages::JOIN); + pending_join.message_sent = send_owner_packet(steamIDLobby, message); + + PRINT_DEBUG("added new entry to pending joins"); + return pending_join.api_id; +} + +void Steam_Matchmaking::JoinLobby_OLD( CSteamID steamIDLobby ) +{ + JoinLobby(steamIDLobby); +} + +// Leave a lobby; this will take effect immediately on the client side +// other users in the lobby will be notified by a LobbyChatUpdate_t callback +void Steam_Matchmaking::LeaveLobby( CSteamID steamIDLobby ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + PRINT_DEBUG("pass mutex"); + Lobby *lobby = get_lobby(steamIDLobby); + if (lobby) { + if (!lobby->deleted()) { + on_self_enter_leave_lobby((uint64)lobby->room_id(), lobby->type(), true); + self_lobby_member_data.erase(lobby->room_id()); + if (lobby->owner() != settings->get_local_steam_id().ConvertToUint64()) { + PRINT_DEBUG("not owner"); + leave_lobby(&(*lobby), settings->get_local_steam_id()); + Lobby_Messages *message = new Lobby_Messages(); + message->set_type(Lobby_Messages::LEAVE); + send_owner_packet(steamIDLobby, message); + } else { + PRINT_DEBUG("owner"); + Lobby_Messages *message = new Lobby_Messages(); + message->set_type(Lobby_Messages::LEAVE); + + if (lobby->members().size() > 1) { + leave_lobby(&(*lobby), settings->get_local_steam_id()); + change_owner(&(*lobby), (uint64)lobby->members(0).id()); + send_owner_packet(steamIDLobby, message); + } else { + send_clients_packet(steamIDLobby, message); + lobby->set_deleted(true); + lobby->set_time_deleted(std::chrono::duration_cast>(std::chrono::system_clock::now().time_since_epoch()).count()); + } + } + } + } + + PRINT_DEBUG("Done"); + +} + + +// Invite another user to the lobby +// the target user will receive a LobbyInvite_t callback +// will return true if the invite is successfully sent, whether or not the target responds +// returns false if the local user is not connected to the Steam servers +// if the other user clicks the join link, a GameLobbyJoinRequested_t will be posted if the user is in-game, +// or if the game isn't running yet the game will be launched with the parameter +connect_lobby <64-bit lobby id> +bool Steam_Matchmaking::InviteUserToLobby( CSteamID steamIDLobby, CSteamID steamIDInvitee ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + if (!lobby) return false; + + Common_Message msg; + Friend_Messages *friend_messages = new Friend_Messages(); + friend_messages->set_type(Friend_Messages::LOBBY_INVITE); + friend_messages->set_lobby_id(steamIDLobby.ConvertToUint64()); + msg.set_allocated_friend_messages(friend_messages); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(steamIDInvitee.ConvertToUint64()); + return network->sendTo(&msg, true); +} + + +// Lobby iteration, for viewing details of users in a lobby +// only accessible if the lobby user is a member of the specified lobby +// persona information for other lobby members (name, avatar, etc.) will be asynchronously received +// and accessible via ISteamFriends interface + +// returns the number of users in the specified lobby +int Steam_Matchmaking::GetNumLobbyMembers( CSteamID steamIDLobby ) +{ + PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + int ret = 0; + if (lobby) ret = lobby->members().size(); + + PRINT_DEBUG("count=%i", ret); + return ret; +} + +// returns the CSteamID of a user in the lobby +// iMember is of range [0,GetNumLobbyMembers()) +// note that the current user must be in a lobby to retrieve CSteamIDs of other users in that lobby +CSteamID Steam_Matchmaking::GetLobbyMemberByIndex( CSteamID steamIDLobby, int iMember ) +{ + PRINT_DEBUG("%llu %i", steamIDLobby.ConvertToUint64(), iMember); + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + CSteamID id = k_steamIDNil; + if (lobby && !lobby->deleted() && lobby->members().size() > iMember && iMember >= 0) id = (uint64)lobby->members(iMember).id(); + PRINT_DEBUG("found member: %llu", id.ConvertToUint64()); + return id; +} + + +// Get data associated with this lobby +// takes a simple key, and returns the string associated with it +// "" will be returned if no value is set, or if steamIDLobby is invalid +const char* Steam_Matchmaking::GetLobbyData( CSteamID steamIDLobby, const char *pchKey ) +{ + PRINT_DEBUG("%llu '%s'", steamIDLobby.ConvertToUint64(), pchKey); + std::lock_guard lock(global_mutex); + if (!pchKey) return ""; + + Lobby *lobby = get_lobby(steamIDLobby); + const char *ret = ""; + if (lobby) { + auto result = caseinsensitive_find(lobby->values(), pchKey); + if (result != lobby->values().end()) ret = result->second.c_str(); + } + + PRINT_DEBUG("returned '%s'", ret); + return ret; +} + +// Sets a key/value pair in the lobby metadata +// each user in the lobby will be broadcast this new value, and any new users joining will receive any existing data +// this can be used to set lobby names, map, etc. +// to reset a key, just set it to "" +// other users in the lobby will receive notification of the lobby data change via a LobbyDataUpdate_t callback +bool Steam_Matchmaking::SetLobbyData( CSteamID steamIDLobby, const char *pchKey, const char *pchValue ) +{ + PRINT_DEBUG("[%llu] '%s'='%s'", steamIDLobby.ConvertToUint64(), pchKey, pchValue); + std::lock_guard lock(global_mutex); + if (!pchKey) return false; + if (!pchValue) pchValue = ""; + + Lobby *lobby = get_lobby(steamIDLobby); + if (!lobby || lobby->deleted()) { + return false; + } + + bool changed = true; + //callback is always triggered when setlobbydata is called from non owner however no data is actually changed. + if (lobby->owner() == settings->get_local_steam_id().ConvertToUint64()) { + auto result = caseinsensitive_find(lobby->values(), pchKey); + if (result == lobby->values().end()) { + (*lobby->mutable_values())[pchKey] = pchValue; + } else { + if (result->second == std::string(pchValue)) changed = false; + (*lobby->mutable_values())[result->first] = pchValue; + } + } + + if (changed) + trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true, 0.005, changed); + + return true; +} + + +// returns the number of metadata keys set on the specified lobby +int Steam_Matchmaking::GetLobbyDataCount( CSteamID steamIDLobby ) +{ + PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); + std::lock_guard lock(global_mutex); + + Lobby *lobby = get_lobby(steamIDLobby); + int size = 0; + if (lobby) size = lobby->values().size(); + + + return size; +} + + +// returns a lobby metadata key/values pair by index, of range [0, GetLobbyDataCount()) +bool Steam_Matchmaking::GetLobbyDataByIndex( CSteamID steamIDLobby, int iLobbyData, char *pchKey, int cchKeyBufferSize, char *pchValue, int cchValueBufferSize ) +{ + PRINT_DEBUG("%llu [%i] key size=%i, value size=%i", steamIDLobby.ConvertToUint64(), iLobbyData, cchKeyBufferSize, cchValueBufferSize); + std::lock_guard lock(global_mutex); + + Lobby *lobby = get_lobby(steamIDLobby); + bool ret = false; + + if (lobby && lobby->values().size() > iLobbyData && iLobbyData >= 0) { + auto lobby_data = lobby->values().begin(); + for (int i = 0; i < iLobbyData; ++i) ++lobby_data; + if (pchKey && cchKeyBufferSize > 0) { + strncpy(pchKey, lobby_data->first.c_str(), cchKeyBufferSize - 1); + pchKey[cchKeyBufferSize - 1] = 0; + } + + if (pchValue && cchValueBufferSize > 0) { + strncpy(pchValue, lobby_data->second.c_str(), cchValueBufferSize - 1); + pchValue[cchValueBufferSize - 1] = 0; + } + + PRINT_DEBUG("ret '%s'='%s'", pchKey, pchValue); + ret = true; + } + + + return ret; +} + + +// removes a metadata key from the lobby +bool Steam_Matchmaking::DeleteLobbyData( CSteamID steamIDLobby, const char *pchKey ) +{ + PRINT_DEBUG("'%s'", pchKey); + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + if (!lobby || lobby->owner() != settings->get_local_steam_id().ConvertToUint64() || lobby->deleted()) { + return false; + } + + lobby->mutable_values()->erase(pchKey); + trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true); + + return true; +} + + +// Gets per-user metadata for someone in this lobby +const char* Steam_Matchmaking::GetLobbyMemberData( CSteamID steamIDLobby, CSteamID steamIDUser, const char *pchKey ) +{ + PRINT_DEBUG("'%s' %llu %llu", pchKey, steamIDLobby.ConvertToUint64(), steamIDUser.ConvertToUint64()); + std::lock_guard lock(global_mutex); + if (!pchKey) return ""; + + struct Lobby_Member *member = get_lobby_member(get_lobby(steamIDLobby), steamIDUser); + const char *ret = ""; + if (member) { + if (steamIDUser == settings->get_local_steam_id()) { + auto result = self_lobby_member_data.find(steamIDLobby.ConvertToUint64()); + if (result != self_lobby_member_data.end()) { + auto value = caseinsensitive_find(result->second, std::string(pchKey)); + if (value != result->second.end()) { + ret = value->second.c_str(); + } + } + } else { + auto result = caseinsensitive_find(member->values(), std::string(pchKey)); + if (result == member->values().end()) return ""; + ret = result->second.c_str(); + } + } + + PRINT_DEBUG("res '%s'", ret); + return ret; +} + +// Sets per-user metadata (for the local user implicitly) +void Steam_Matchmaking::SetLobbyMemberData( CSteamID steamIDLobby, const char *pchKey, const char *pchValue ) +{ + PRINT_DEBUG("%llu '%s'='%s'", steamIDLobby.ConvertToUint64(), pchKey, pchValue); + if (!pchKey) return; + char empty_string[] = ""; + if (!pchValue) pchValue = empty_string; + + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + if (!lobby || lobby->deleted()) return; + + Lobby_Member *member = get_lobby_member(lobby, settings->get_local_steam_id()); + if (member) { + if (lobby->owner() == settings->get_local_steam_id().ConvertToUint64()) { + auto result = caseinsensitive_find(member->values(), std::string(pchKey)); + if (result == member->values().end()) { + (*member->mutable_values())[pchKey] = pchValue; + } else { + (*member->mutable_values())[result->first] = pchValue; + } + trigger_lobby_dataupdate(steamIDLobby, (uint64)member->id(), true); + } else { + Lobby_Messages *message = new Lobby_Messages(); + message->set_type(Lobby_Messages::MEMBER_DATA); + (*message->mutable_map())[pchKey] = pchValue; + send_owner_packet(steamIDLobby, message); + } + + { + auto result = self_lobby_member_data.find(steamIDLobby.ConvertToUint64()); + if (result != self_lobby_member_data.end()) { + auto value = caseinsensitive_find(result->second, std::string(pchKey)); + if (value != result->second.end()) { + self_lobby_member_data[steamIDLobby.ConvertToUint64()][value->first] = pchValue; + } else { + self_lobby_member_data[steamIDLobby.ConvertToUint64()][pchKey] = pchValue; + } + } else { + self_lobby_member_data[steamIDLobby.ConvertToUint64()][pchKey] = pchValue; + } + } + } +} + + +// Broadcasts a chat message to the all the users in the lobby +// users in the lobby (including the local user) will receive a LobbyChatMsg_t callback +// returns true if the message is successfully sent +// pvMsgBody can be binary or text data, up to 4k +// if pvMsgBody is text, cubMsgBody should be strlen( text ) + 1, to include the null terminator +bool Steam_Matchmaking::SendLobbyChatMsg( CSteamID steamIDLobby, const void *pvMsgBody, int cubMsgBody ) +{ + PRINT_DEBUG("%llu %i", steamIDLobby.ConvertToUint64(), cubMsgBody); + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + if (!lobby || lobby->deleted()) return false; + + Lobby_Messages *message = new Lobby_Messages(); + message->set_type(Lobby_Messages::CHAT_MESSAGE); + message->set_bdata(pvMsgBody, cubMsgBody); + return send_lobby_members_packet(steamIDLobby, message); +} + +// Get a chat message as specified in a LobbyChatMsg_t callback +// iChatID is the LobbyChatMsg_t::m_iChatID value in the callback +// *pSteamIDUser is filled in with the CSteamID of the member +// *pvData is filled in with the message itself +// return value is the number of bytes written into the buffer +int Steam_Matchmaking::GetLobbyChatEntry( CSteamID steamIDLobby, int iChatID, STEAM_OUT_STRUCT() CSteamID *pSteamIDUser, void *pvData, int cubData, EChatEntryType *peChatEntryType ) +{ + PRINT_DEBUG("%llu %i %p %p %i %p", steamIDLobby.ConvertToUint64(), iChatID, pSteamIDUser, pvData, cubData, peChatEntryType); + std::lock_guard lock(global_mutex); + if (iChatID >= chat_entries.size() || iChatID < 0 || cubData < 0) return 0; + if (chat_entries[iChatID].lobby_id != steamIDLobby) return 0; + if (pSteamIDUser) *pSteamIDUser = chat_entries[iChatID].user_id; + if (peChatEntryType) *peChatEntryType = chat_entries[iChatID].type; + if (pvData) { + if (chat_entries[iChatID].message.size() <= cubData) { + cubData = chat_entries[iChatID].message.size(); + memcpy(pvData, chat_entries[iChatID].message.data(), cubData); + PRINT_DEBUG(" Returned chat of len: %i", cubData); + return cubData; + } + } + + return 0; +} + + +// Refreshes metadata for a lobby you're not necessarily in right now +// you never do this for lobbies you're a member of, only if your +// this will send down all the metadata associated with a lobby +// this is an asynchronous call +// returns false if the local user is not connected to the Steam servers +// results will be returned by a LobbyDataUpdate_t callback +// if the specified lobby doesn't exist, LobbyDataUpdate_t::m_bSuccess will be set to false +bool Steam_Matchmaking::RequestLobbyData( CSteamID steamIDLobby ) +{ + PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); + std::lock_guard lock(global_mutex); + + struct Data_Requested requested{}; + requested.lobby_id = steamIDLobby; + requested.requested = std::chrono::high_resolution_clock::now(); + data_requested.push_back(requested); + return true; +} + +// sets the game server associated with the lobby +// usually at this point, the users will join the specified game server +// either the IP/Port or the steamID of the game server has to be valid, depending on how you want the clients to be able to connect +void Steam_Matchmaking::SetLobbyGameServer( CSteamID steamIDLobby, uint32 unGameServerIP, uint16 unGameServerPort, CSteamID steamIDGameServer ) +{ + PRINT_DEBUG("%llu %llu %hhu.%hhu.%hhu.%hhu:%hu", + steamIDLobby.ConvertToUint64(), steamIDGameServer.ConvertToUint64(), ((unsigned char *)&unGameServerIP)[3], ((unsigned char *)&unGameServerIP)[2], ((unsigned char *)&unGameServerIP)[1], ((unsigned char *)&unGameServerIP)[0], unGameServerPort + ); + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + if (lobby) { + if (lobby->deleted()) return; + + lobby->mutable_gameserver()->set_id(steamIDGameServer.ConvertToUint64()); + lobby->mutable_gameserver()->set_ip(unGameServerIP); + lobby->mutable_gameserver()->set_port(unGameServerPort); + lobby->mutable_gameserver()->set_num_update(lobby->gameserver().num_update() + 1); + + send_gameservercreated_cb(lobby->room_id(), lobby->gameserver().id(), lobby->gameserver().ip(), lobby->gameserver().port()); + trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true); + } + + +} + +// returns the details of a game server set in a lobby - returns false if there is no game server set, or that lobby doesn't exist +bool Steam_Matchmaking::GetLobbyGameServer( CSteamID steamIDLobby, uint32 *punGameServerIP, uint16 *punGameServerPort, STEAM_OUT_STRUCT() CSteamID *psteamIDGameServer ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + if (!lobby) { + + return false; + } + + CSteamID server_id((uint64)lobby->gameserver().id()); + if (server_id.IsValid() || lobby->gameserver().port()) { + if (psteamIDGameServer) *psteamIDGameServer = server_id; + if (punGameServerIP) *punGameServerIP = lobby->gameserver().ip(); + if (punGameServerPort) *punGameServerPort = lobby->gameserver().port(); + + return true; + } + + + return false; +} + + +// set the limit on the # of users who can join the lobby +bool Steam_Matchmaking::SetLobbyMemberLimit( CSteamID steamIDLobby, int cMaxMembers ) +{ + PRINT_DEBUG("%llu %i", steamIDLobby.ConvertToUint64(), cMaxMembers); + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + if (!lobby || lobby->owner() != settings->get_local_steam_id().ConvertToUint64() || lobby->deleted()) { + + return false; + } + + lobby->set_member_limit(cMaxMembers); + trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true); + + return true; +} + +// returns the current limit on the # of users who can join the lobby; returns 0 if no limit is defined +int Steam_Matchmaking::GetLobbyMemberLimit( CSteamID steamIDLobby ) +{ + PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + int limit = 0; + if (lobby) limit = lobby->member_limit(); + + PRINT_DEBUG(" limit %i", limit); + return limit; +} + +void Steam_Matchmaking::SetLobbyVoiceEnabled( CSteamID steamIDLobby, bool bVoiceEnabled ) +{ + PRINT_DEBUG_ENTRY(); +} + +// updates which type of lobby it is +// only lobbies that are k_ELobbyTypePublic or k_ELobbyTypeInvisible, and are set to joinable, will be returned by RequestLobbyList() calls +bool Steam_Matchmaking::SetLobbyType( CSteamID steamIDLobby, ELobbyType eLobbyType ) +{ + PRINT_DEBUG("%i", eLobbyType); + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + if (!lobby || lobby->owner() != settings->get_local_steam_id().ConvertToUint64() || lobby->deleted()) { + return false; + } + + if (lobby->type() != eLobbyType) { + //maybe rename those functions? + if (lobby->type() == k_ELobbyTypeInvisible) on_self_enter_leave_lobby(steamIDLobby, eLobbyType, false); + if (eLobbyType == k_ELobbyTypeInvisible) on_self_enter_leave_lobby(steamIDLobby, lobby->type(), true); + + lobby->set_type(eLobbyType); + trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true); + } + + return true; +} + + +// sets whether or not a lobby is joinable - defaults to true for a new lobby +// if set to false, no user can join, even if they are a friend or have been invited +bool Steam_Matchmaking::SetLobbyJoinable( CSteamID steamIDLobby, bool bLobbyJoinable ) +{ + PRINT_DEBUG("%u", bLobbyJoinable); + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + if (!lobby || lobby->owner() != settings->get_local_steam_id().ConvertToUint64() || lobby->deleted()) { + return false; + } + + if (lobby->joinable() != bLobbyJoinable) { + lobby->set_joinable(bLobbyJoinable); + trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true); + } + + return true; +} + + +// returns the current lobby owner +// you must be a member of the lobby to access this (Mr_Goldberg note: This is a lie) +// there always one lobby owner - if the current owner leaves, another user will become the owner +// it is possible (bur rare) to join a lobby just as the owner is leaving, thus entering a lobby with self as the owner +CSteamID Steam_Matchmaking::GetLobbyOwner( CSteamID steamIDLobby ) +{ + PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + if (!lobby || lobby->deleted()) return k_steamIDNil; + + //TODO: might be better to require the lobby info to be at least requested first. + return (uint64)lobby->owner(); +} + +// asks the Steam servers for a list of lobbies that friends are in +// returns results by posting one RequestFriendsLobbiesResponse_t callback per friend/lobby pair +// if no friends are in lobbies, RequestFriendsLobbiesResponse_t will be posted but with 0 results +// filters don't apply to lobbies (currently) +bool Steam_Matchmaking::RequestFriendsLobbies() +{ + PRINT_DEBUG_ENTRY(); + RequestFriendsLobbiesResponse_t data = {}; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return true; +} + +float Steam_Matchmaking::GetLobbyDistance( CSteamID steamIDLobby ) +{ + PRINT_DEBUG("%llu", steamIDLobby.ConvertToUint64()); + return 0.0; +} + +// changes who the lobby owner is +// you must be the lobby owner for this to succeed, and steamIDNewOwner must be in the lobby +// after completion, the local user will no longer be the owner +bool Steam_Matchmaking::SetLobbyOwner( CSteamID steamIDLobby, CSteamID steamIDNewOwner ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + Lobby *lobby = get_lobby(steamIDLobby); + if (!lobby || lobby->owner() != settings->get_local_steam_id().ConvertToUint64() || lobby->deleted()) return false; + Lobby_Member *member = get_lobby_member(lobby, steamIDNewOwner); + if (member) { + change_owner(&(*lobby), (uint64)member->id()); + trigger_lobby_dataupdate(steamIDLobby, steamIDLobby, true); + + return true; + } + + + return false; +} + + +// link two lobbies for the purposes of checking player compatibility +// you must be the lobby owner of both lobbies +bool Steam_Matchmaking::SetLinkedLobby( CSteamID steamIDLobby, CSteamID steamIDLobbyDependent ) +{ + PRINT_DEBUG_ENTRY(); + return false; +} + + + +void Steam_Matchmaking::remove_lobbies() +{ + uint64 current_time = std::chrono::duration_cast>(std::chrono::system_clock::now().time_since_epoch()).count(); + auto g = std::begin(lobbies); + while (g != std::end(lobbies)) { + if (g->members().size() == 0 || (g->deleted() && (g->time_deleted() + LOBBY_DELETED_TIMEOUT < current_time))) { + PRINT_DEBUG("LOBBY " "%" PRIu64 "", g->room_id()); + self_lobby_member_data.erase(g->room_id()); + g = lobbies.erase(g); + } else { + ++g; + } + } +} + +void Steam_Matchmaking::create_pending_lobbies() +{ + auto p_c = std::begin(pending_creates); + while (p_c != std::end(pending_creates)) { + if (check_timedout(p_c->created, LOBBY_CREATE_DELAY)) { + Lobby lobby{}; + CSteamID lobby_id = generate_steam_id_lobby(); + lobby.set_room_id(lobby_id.ConvertToUint64()); + lobby.set_joinable(true); + lobby.set_member_limit(p_c->cMaxMembers); + lobby.set_type(p_c->eLobbyType); + lobby.set_owner(settings->get_local_steam_id().ConvertToUint64()); + lobby.set_appid(settings->get_local_game_id().AppID()); + add_member_to_lobby(&lobby, settings->get_local_steam_id()); + lobbies.push_back(lobby); + + if (settings->disable_lobby_creation) { + LobbyCreated_t data; + data.m_eResult = k_EResultFail; + data.m_ulSteamIDLobby = 0; + callback_results->addCallResult(p_c->api_id, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } else { + LobbyCreated_t data; + data.m_eResult = k_EResultOK; + data.m_ulSteamIDLobby = lobby.room_id(); + callback_results->addCallResult(p_c->api_id, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + { + LobbyEnter_t data2{}; + data2.m_ulSteamIDLobby = lobby.room_id(); + data2.m_rgfChatPermissions = 0; //Unused - Always 0 + if (p_c->eLobbyType == k_ELobbyTypePrivate) + data2.m_bLocked = true; + else + data2.m_bLocked = false; + data2.m_EChatRoomEnterResponse = k_EChatRoomEnterResponseSuccess; + callbacks->addCBResult(data2.k_iCallback, &data2, sizeof(data2)); + } + + on_self_enter_leave_lobby(lobby_id, p_c->eLobbyType, false); + trigger_lobby_dataupdate(lobby_id, lobby_id, true); + } + + p_c = pending_creates.erase(p_c); + } else { + ++p_c; + } + } +} + +void Steam_Matchmaking::run_background() +{ + remove_lobbies(); + create_pending_lobbies(); + + if (check_timedout(last_sent_lobbies, SEND_LOBBY_RATE)) { + send_lobby_data(); + last_sent_lobbies = std::chrono::high_resolution_clock::now(); + } +} + +void Steam_Matchmaking::RunCallbacks() +{ + run_background(); + + if (searching) { + PRINT_DEBUG("for lobbies %zu", lobbies.size()); + for(auto & l: lobbies) { + bool use = l.joinable() && (l.type() == k_ELobbyTypePublic || l.type() == k_ELobbyTypeInvisible || l.type() == k_ELobbyTypeFriendsOnly) && !l.deleted(); + PRINT_DEBUG("use lobby: %u, filters: %zu, joinable: %u, type: %u, deleted: %u", use, filter_values_copy.size(), l.joinable(), l.type(), l.deleted()); + for (auto & f : filter_values_copy) { + PRINT_DEBUG("'%s':'%s'/%i %u %i", f.key.c_str(), f.value_string.c_str(), f.value_int, f.is_int, f.eComparisonType); + auto value = caseinsensitive_find(l.values(), f.key); + if (value != l.values().end()) { + //TODO: eComparisonType + if (!f.is_int) { + PRINT_DEBUG("Compare Values %s %s", value->second.c_str(), f.value_string.c_str()); + if (f.eComparisonType == k_ELobbyComparisonEqual) { + if (value->second == f.value_string) { + PRINT_DEBUG("Equal (non-int)"); + //use = use; + } else { + PRINT_DEBUG("Not Equal (non-int)"); + use = false; + } + } else { + PRINT_DEBUG("TODO UNSUPPORTED compare type (non-int) %i", (int)f.eComparisonType); + } + } else { + try { + PRINT_DEBUG("%s", value->second.c_str()); + int compare_to = 0; + //TODO: check if this is how real steam behaves + if (value->second.size()) { + compare_to = std::stoll(value->second, 0, 0); + } + PRINT_DEBUG("Compare Values %i %i", compare_to, f.value_int); + if (f.eComparisonType == k_ELobbyComparisonEqual) { + if (compare_to == f.value_int) { + PRINT_DEBUG("Equal (int)"); + //use = use; + } else { + PRINT_DEBUG("Not Equal (int)"); + use = false; + } + } else { + PRINT_DEBUG("TODO UNSUPPORTED compare type (int) %i", (int)f.eComparisonType); + } + } catch (...) { + //Same case as if the key is not in the lobby? + use = false; + } + //TODO: add more comparisons + } + } else { + PRINT_DEBUG("Compare Key not in lobby"); + if (f.eComparisonType == k_ELobbyComparisonEqual) { + //If the key is not in the lobby do we take it into account? + use = false; + } + } + } + + PRINT_DEBUG("Lobby " "%" PRIu64 " use %u", l.room_id(), use); + if (use) PUSH_BACK_IF_NOT_IN(filtered_lobbies, (uint64)l.room_id()); + if (filtered_lobbies.size() >= filter_max_results_copy) { + PRINT_DEBUG("returning lobby search results, count=%zu", filtered_lobbies.size()); + searching = false; + LobbyMatchList_t data{}; + data.m_nLobbiesMatching = filtered_lobbies.size(); + callback_results->addCallResult(search_call_api_id, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + search_call_api_id = 0; + } + } + } + + if (searching && check_timedout(lobby_last_search, LOBBY_SEARCH_TIMEOUT)) { + PRINT_DEBUG("LOBBY_SEARCH_TIMEOUT %zu", filtered_lobbies.size()); + LobbyMatchList_t data{}; + data.m_nLobbiesMatching = filtered_lobbies.size(); + callback_results->addCallResult(search_call_api_id, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + searching = false; + search_call_api_id = 0; + } + + auto g = std::begin(pending_joins); + while (g != std::end(pending_joins)) { + if (!g->message_sent) { + PRINT_DEBUG("resending join lobby"); + Lobby_Messages *message = new Lobby_Messages(); + message->set_type(Lobby_Messages::JOIN); + g->message_sent = send_owner_packet(g->lobby_id, message); + } + + Lobby *lobby = get_lobby(g->lobby_id); + if (lobby && lobby->deleted()) { + PRINT_DEBUG("lobby deleted %llu", g->lobby_id.ConvertToUint64()); + LobbyEnter_t data{}; + data.m_ulSteamIDLobby = lobby->room_id(); + data.m_rgfChatPermissions = 0; //Unused - Always 0 + data.m_bLocked = false; + data.m_EChatRoomEnterResponse = k_EChatRoomEnterResponseDoesntExist; + callback_results->addCallResult(g->api_id, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + g = pending_joins.erase(g); + } else if (get_lobby_member(lobby, settings->get_local_steam_id())) { + PRINT_DEBUG("lobby joined %llu", g->lobby_id.ConvertToUint64()); + LobbyEnter_t data{}; + data.m_ulSteamIDLobby = lobby->room_id(); + data.m_rgfChatPermissions = 0; //Unused - Always 0 + data.m_bLocked = false; + data.m_EChatRoomEnterResponse = k_EChatRoomEnterResponseSuccess; + callback_results->addCallResult(g->api_id, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + g = pending_joins.erase(g); + trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)lobby->room_id(), true); + } else if (check_timedout(g->joined, PENDING_JOIN_TIMEOUT)) { + PRINT_DEBUG("pending join timeout %llu", g->lobby_id.ConvertToUint64()); + LobbyEnter_t data{}; + data.m_ulSteamIDLobby = g->lobby_id.ConvertToUint64(); + data.m_rgfChatPermissions = 0; //Unused - Always 0 + data.m_bLocked = false; + data.m_EChatRoomEnterResponse = k_EChatRoomEnterResponseDoesntExist; + callback_results->addCallResult(g->api_id, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + g = pending_joins.erase(g); + } else { + ++g; + } + } + + + auto dr = std::begin(data_requested); + while (dr != std::end(data_requested)) { + if (get_lobby(dr->lobby_id)) { + trigger_lobby_dataupdate(dr->lobby_id, dr->lobby_id, true); + dr = data_requested.erase(dr); + continue; + } + + if (check_timedout(dr->requested, REQUEST_LOBBY_DATA_TIMEOUT)) { + trigger_lobby_dataupdate(dr->lobby_id, dr->lobby_id, false); + dr = data_requested.erase(dr); + continue; + } + + ++dr; + } +} + + + +void Steam_Matchmaking::Callback(Common_Message *msg) +{ + if (msg->has_lobby()) { + PRINT_DEBUG("GOT A LOBBY appid: %u " "%" PRIu64 "", msg->lobby().appid(), msg->lobby().owner()); + if (msg->lobby().owner() != settings->get_local_steam_id().ConvertToUint64() && msg->lobby().appid() == settings->get_local_game_id().AppID()) { + Lobby *lobby = get_lobby((uint64)msg->lobby().room_id()); + if (!lobby) { + size_t old_size = lobbies.size(); + lobbies.resize(old_size + 1); + lobbies[old_size].set_room_id(msg->lobby().room_id()); + lobby = &(lobbies[old_size]); + } + + if (!lobby->deleted()) { + if (!protobuf_message_equal(*lobby, msg->lobby())) { + bool we_are_in_lobby = !!get_lobby_member(lobby, settings->get_local_steam_id()); + if (we_are_in_lobby) trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)lobby->room_id(), true); + + for (auto & m : lobby->members()) { + int count = 0; + Lobby_Member *member = get_lobby_member(msg->mutable_lobby(), (uint64)m.id()); + + if (we_are_in_lobby) { + if (!member) { + trigger_lobby_member_join_leave((uint64)lobby->room_id(), (uint64)m.id(), true, true, 0.2); + } else if (!protobuf_message_equal(*member, m)) { + trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)m.id(), true); + } + } + } + + bool joined = false; + for (auto & m : msg->lobby().members()) { + Lobby_Member *member = get_lobby_member(lobby, (uint64)m.id()); + if (!member) { + if (m.id() == settings->get_local_steam_id().ConvertToUint64()) { + CSteamID id((uint64)lobby->room_id()); + auto pd = pending_joins.begin(); + while (pd != pending_joins.end()) { + if (pd->lobby_id == id) { + bool success = true; + LobbyEnter_t data; + data.m_ulSteamIDLobby = lobby->room_id(); + data.m_rgfChatPermissions = 0; //Unused - Always 0 + data.m_bLocked = false; + data.m_EChatRoomEnterResponse = success ? k_EChatRoomEnterResponseSuccess : k_EChatRoomEnterResponseError; + callback_results->addCallResult(pd->api_id, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + pd = pending_joins.erase(pd); + joined = true; + } else { + ++pd; + } + } + if (joined) { + on_self_enter_leave_lobby((uint64)lobby->room_id(), lobby->type(), false); + trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)lobby->room_id(), true); + } + } else { + if (we_are_in_lobby) trigger_lobby_member_join_leave((uint64)lobby->room_id(), (uint64)m.id(), false, true); + } + } + } + + if (joined) { + for (auto & m : msg->lobby().members()) { + if (m.id() != settings->get_local_steam_id().ConvertToUint64()) { + //TODO: is this good? + //trigger_lobby_member_join_leave((uint64)lobby->room_id(), (uint64)m.id(), false, true); + if (m.values().size()) { + //TODO: check if this is what steam does + //trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)m.id(), true); + } + } + } + } + + if ((joined && msg->lobby().gameserver().num_update()) || (we_are_in_lobby && (lobby->gameserver().num_update() != msg->lobby().gameserver().num_update()))) { + send_gameservercreated_cb(lobby->room_id(), msg->lobby().gameserver().id(), msg->lobby().gameserver().ip(), msg->lobby().gameserver().port()); + trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)lobby->room_id(), true); + } + + *lobby = msg->lobby(); + } + } + } + } + + + if (msg->has_lobby_messages()) { + PRINT_DEBUG("LOBBY MESSAGE %u " "%" PRIu64 "", msg->lobby_messages().type(), msg->lobby_messages().id()); + Lobby *lobby = get_lobby((uint64)msg->lobby_messages().id()); + if (lobby && !lobby->deleted()) { + bool we_are_in_lobby = !!get_lobby_member(lobby, settings->get_local_steam_id()); + if (lobby->owner() == settings->get_local_steam_id().ConvertToUint64()) { + if (msg->lobby_messages().type() == Lobby_Messages::JOIN) { + PRINT_DEBUG("LOBBY MESSAGE: JOIN, lobby=%llu from=%llu", (uint64)lobby->room_id(), (uint64)msg->source_id()); + if (add_member_to_lobby(lobby, (uint64)msg->source_id())) { + trigger_lobby_member_join_leave((uint64)lobby->room_id(), (uint64)msg->source_id(), false, true, 0.01); + } + } + + if (msg->lobby_messages().type() == Lobby_Messages::MEMBER_DATA) { + PRINT_DEBUG("LOBBY MESSAGE: MEMBER_DATA"); + Lobby_Member *member = get_lobby_member(lobby, (uint64)msg->source_id()); + if (member) { + for (auto const &p : msg->lobby_messages().map()) { + PRINT_DEBUG("member data '%s'='%s'", p.first.c_str(), p.second.c_str()); + auto result = caseinsensitive_find(member->values(), p.first); + if (result == member->values().end()) { + (*member->mutable_values())[p.first] = p.second; + } else { + (*member->mutable_values())[result->first] = p.second; + } + } + + trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)member->id(), true); + } + } + } + + if (msg->lobby_messages().type() == Lobby_Messages::LEAVE) { + PRINT_DEBUG("LOBBY MESSAGE: LEAVE " "%" PRIu64 "", msg->source_id()); + leave_lobby(lobby, (uint64)msg->source_id()); + if (we_are_in_lobby) trigger_lobby_member_join_leave((uint64)lobby->room_id(), (uint64)msg->source_id(), true, true, 0.2); + } + + if (msg->lobby_messages().type() == Lobby_Messages::CHANGE_OWNER) { + PRINT_DEBUG("LOBBY MESSAGE: CHANGE OWNER"); + lobby->set_owner(msg->lobby_messages().idata()); + if (we_are_in_lobby) trigger_lobby_dataupdate((uint64)lobby->room_id(), (uint64)lobby->room_id(), true); + } + + if (msg->lobby_messages().type() == Lobby_Messages::CHAT_MESSAGE) { + PRINT_DEBUG("LOBBY MESSAGE: CHAT MESSAGE"); + if (we_are_in_lobby) { + struct Chat_Entry entry{}; + entry.type = k_EChatEntryTypeChatMsg; + entry.message = msg->lobby_messages().bdata(); + entry.lobby_id = CSteamID((uint64)msg->lobby_messages().id()); + entry.user_id = CSteamID((uint64)msg->source_id()); + LobbyChatMsg_t data{}; + data.m_ulSteamIDLobby = msg->lobby_messages().id(); + data.m_ulSteamIDUser = msg->source_id(); + data.m_eChatEntryType = entry.type; + data.m_iChatID = chat_entries.size(); + chat_entries.push_back(entry); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + } + } + } + + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + + if (msg->low_level().type() == Low_Level::DISCONNECT) { + for (auto & l: lobbies) { + if (leave_lobby(&(l), (uint64)msg->source_id())) + trigger_lobby_member_join_leave((uint64)l.room_id(), (uint64)msg->source_id(), true, true, 0.0); + } + } + } + + remove_lobbies(); +} diff --git a/dll/steam_matchmaking_servers.cpp b/dll/steam_matchmaking_servers.cpp index 9703edc5..7fb487ea 100644 --- a/dll/steam_matchmaking_servers.cpp +++ b/dll/steam_matchmaking_servers.cpp @@ -17,35 +17,51 @@ #include "dll/dll.h" +#define SERVER_TIMEOUT 10.0 +#define DIRECT_IP_DELAY 0.05 -static void network_callback(void *object, Common_Message *msg) + +static HServerQuery new_server_query() { - PRINT_DEBUG("steam_matchmaking_servers_callback"); + std::lock_guard lock(global_mutex); + static unsigned int a = 0; + ++a; + if (!a) ++a; + return a; +} + + +void Steam_Matchmaking_Servers::network_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); Steam_Matchmaking_Servers *obj = (Steam_Matchmaking_Servers *)object; obj->Callback(msg); } + Steam_Matchmaking_Servers::Steam_Matchmaking_Servers(class Settings *settings, class Local_Storage *local_storage, class Networking *network) { this->settings = settings; this->local_storage = local_storage; this->network = network; - this->network->setCallback(CALLBACK_ID_GAMESERVER, (uint64) 0, &network_callback, this); + + this->network->setCallback(CALLBACK_ID_GAMESERVER, (uint64)0, &network_callback, this); } Steam_Matchmaking_Servers::~Steam_Matchmaking_Servers() { - this->network->rmCallback(CALLBACK_ID_GAMESERVER, (uint64) 0, &network_callback, this); + this->network->rmCallback(CALLBACK_ID_GAMESERVER, (uint64)0, &network_callback, this); } -static unsigned server_list_request = 0; HServerListRequest Steam_Matchmaking_Servers::RequestServerList(AppId_t iApp, ISteamMatchmakingServerListResponse *pRequestServersResponse, EMatchMakingType type) { PRINT_DEBUG("%u %p, %i", iApp, pRequestServersResponse, (int)type); std::lock_guard lock(global_mutex); + static unsigned server_list_request = 0; + ++server_list_request; if (!server_list_request) server_list_request = 1; HServerListRequest id = (char *)0 + server_list_request; // (char *)0 silences the compiler warning @@ -353,6 +369,221 @@ void Steam_Matchmaking_Servers::ReleaseRequest( HServerListRequest hServerListRe - Server passes the filter if it's a linux server */ +// Get details on a given server in the list, you can get the valid range of index +// values by calling GetServerCount(). You will also receive index values in +// ISteamMatchmakingServerListResponse::ServerResponded() callbacks +gameserveritem_t *Steam_Matchmaking_Servers::GetServerDetails( HServerListRequest hRequest, int iServer ) +{ + PRINT_DEBUG("%p %i", hRequest, iServer); + std::lock_guard lock(global_mutex); + + std::vector gameservers_filtered; + auto g = std::begin(requests); + while (g != std::end(requests)) { + PRINT_DEBUG(" equal? %p %p", hRequest, g->id); + if (g->id == hRequest) { + gameservers_filtered = g->gameservers_filtered; + PRINT_DEBUG(" found %zu", gameservers_filtered.size()); + break; + } + + ++g; + } + + if (iServer >= gameservers_filtered.size() || iServer < 0) { + return NULL; + } + + Gameserver *gs = &gameservers_filtered[iServer].server; + gameserveritem_t *server = new gameserveritem_t(); //TODO: is the new here ok? + server_details(gs, server); + PRINT_DEBUG(" Returned server details"); + return server; +} + + +// Cancel an request which is operation on the given list type. You should call this to cancel +// any in-progress requests before destructing a callback object that may have been passed +// to one of the above list request calls. Not doing so may result in a crash when a callback +// occurs on the destructed object. +// Canceling a query does not release the allocated request handle. +// The request handle must be released using ReleaseRequest( hRequest ) +void Steam_Matchmaking_Servers::CancelQuery( HServerListRequest hRequest ) +{ + PRINT_DEBUG("%p", hRequest); + auto g = std::begin(requests); + while (g != std::end(requests)) { + if (g->id == hRequest) { + g->cancelled = true; + PRINT_DEBUG("canceled request with id: %p", g->id); + } + + ++g; + } +} + + +// Ping every server in your list again but don't update the list of servers +// Query callback installed when the server list was requested will be used +// again to post notifications and RefreshComplete, so the callback must remain +// valid until another RefreshComplete is called on it or the request +// is released with ReleaseRequest( hRequest ) +void Steam_Matchmaking_Servers::RefreshQuery( HServerListRequest hRequest ) +{ + PRINT_DEBUG("%p", hRequest); +} + + +// Returns true if the list is currently refreshing its server list +bool Steam_Matchmaking_Servers::IsRefreshing( HServerListRequest hRequest ) +{ + PRINT_DEBUG("%p", hRequest); + return false; +} + + +// How many servers in the given list, GetServerDetails above takes 0... GetServerCount() - 1 +int Steam_Matchmaking_Servers::GetServerCount( HServerListRequest hRequest ) +{ + PRINT_DEBUG("%p", hRequest); + std::lock_guard lock(global_mutex); + int size = 0; + auto g = std::begin(requests); + while (g != std::end(requests)) { + if (g->id == hRequest) { + size = g->gameservers_filtered.size(); + break; + } + + ++g; + } + + PRINT_DEBUG("final count = %i", size); + return size; +} + + +// Refresh a single server inside of a query (rather than all the servers ) +void Steam_Matchmaking_Servers::RefreshServer( HServerListRequest hRequest, int iServer ) +{ + PRINT_DEBUG("%p", hRequest); + //TODO +} + + + +// Get details on a given server in the list, you can get the valid range of index +// values by calling GetServerCount(). You will also receive index values in +// ISteamMatchmakingServerListResponse::ServerResponded() callbacks +gameserveritem_t* Steam_Matchmaking_Servers::GetServerDetails( EMatchMakingType eType, int iServer ) +{ + PRINT_DEBUG_ENTRY(); + return GetServerDetails((HServerListRequest) eType , iServer ); +} + +// Cancel an request which is operation on the given list type. You should call this to cancel +// any in-progress requests before destructing a callback object that may have been passed +// to one of the above list request calls. Not doing so may result in a crash when a callback +// occurs on the destructed object. +void Steam_Matchmaking_Servers::CancelQuery( EMatchMakingType eType ) +{ + PRINT_DEBUG_ENTRY(); + return CancelQuery((HServerListRequest) eType); +} + +// Ping every server in your list again but don't update the list of servers +void Steam_Matchmaking_Servers::RefreshQuery( EMatchMakingType eType ) +{ + PRINT_DEBUG_ENTRY(); + return RefreshQuery((HServerListRequest) eType); +} + +// Returns true if the list is currently refreshing its server list +bool Steam_Matchmaking_Servers::IsRefreshing( EMatchMakingType eType ) +{ + return IsRefreshing((HServerListRequest) eType); +} + +// How many servers in the given list, GetServerDetails above takes 0... GetServerCount() - 1 +int Steam_Matchmaking_Servers::GetServerCount( EMatchMakingType eType ) +{ + PRINT_DEBUG_ENTRY(); + return GetServerCount((HServerListRequest) eType); +} + +// Refresh a single server inside of a query (rather than all the servers ) +void Steam_Matchmaking_Servers::RefreshServer( EMatchMakingType eType, int iServer ) +{ + PRINT_DEBUG_ENTRY(); + return RefreshServer((HServerListRequest) eType, iServer); +} + + +//----------------------------------------------------------------------------- +// Queries to individual servers directly via IP/Port +//----------------------------------------------------------------------------- + +// Request updated ping time and other details from a single server +HServerQuery Steam_Matchmaking_Servers::PingServer( uint32 unIP, uint16 usPort, ISteamMatchmakingPingResponse *pRequestServersResponse ) +{ + PRINT_DEBUG("%hhu.%hhu.%hhu.%hhu:%hu", ((unsigned char *)&unIP)[3], ((unsigned char *)&unIP)[2], ((unsigned char *)&unIP)[1], ((unsigned char *)&unIP)[0], usPort); + std::lock_guard lock(global_mutex); + Steam_Matchmaking_Servers_Direct_IP_Request r; + r.id = new_server_query(); + r.ip = unIP; + r.port = usPort; + r.ping_response = pRequestServersResponse; + r.created = std::chrono::high_resolution_clock::now(); + direct_ip_requests.push_back(r); + return r.id; +} + +// Request the list of players currently playing on a server +HServerQuery Steam_Matchmaking_Servers::PlayerDetails( uint32 unIP, uint16 usPort, ISteamMatchmakingPlayersResponse *pRequestServersResponse ) +{ + PRINT_DEBUG("%hhu.%hhu.%hhu.%hhu:%hu", ((unsigned char *)&unIP)[3], ((unsigned char *)&unIP)[2], ((unsigned char *)&unIP)[1], ((unsigned char *)&unIP)[0], usPort); + std::lock_guard lock(global_mutex); + Steam_Matchmaking_Servers_Direct_IP_Request r; + r.id = new_server_query(); + r.ip = unIP; + r.port = usPort; + r.players_response = pRequestServersResponse; + r.created = std::chrono::high_resolution_clock::now(); + direct_ip_requests.push_back(r); + return r.id; +} + + +// Request the list of rules that the server is running (See ISteamGameServer::SetKeyValue() to set the rules server side) +HServerQuery Steam_Matchmaking_Servers::ServerRules( uint32 unIP, uint16 usPort, ISteamMatchmakingRulesResponse *pRequestServersResponse ) +{ + PRINT_DEBUG("%hhu.%hhu.%hhu.%hhu:%hu", ((unsigned char *)&unIP)[3], ((unsigned char *)&unIP)[2], ((unsigned char *)&unIP)[1], ((unsigned char *)&unIP)[0], usPort); + std::lock_guard lock(global_mutex); + Steam_Matchmaking_Servers_Direct_IP_Request r; + r.id = new_server_query(); + r.ip = unIP; + r.port = usPort; + r.rules_response = pRequestServersResponse; + r.created = std::chrono::high_resolution_clock::now(); + direct_ip_requests.push_back(r); + return r.id; +} + + +// Cancel an outstanding Ping/Players/Rules query from above. You should call this to cancel +// any in-progress requests before destructing a callback object that may have been passed +// to one of the above calls to avoid crashing when callbacks occur. +void Steam_Matchmaking_Servers::CancelServerQuery( HServerQuery hServerQuery ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + auto r = std::find_if(direct_ip_requests.begin(), direct_ip_requests.end(), [&hServerQuery](Steam_Matchmaking_Servers_Direct_IP_Request const& item) { return item.id == hServerQuery; }); + if (direct_ip_requests.end() == r) return; + direct_ip_requests.erase(r); +} + + + void Steam_Matchmaking_Servers::server_details(Gameserver *g, gameserveritem_t *server) { PRINT_DEBUG_ENTRY(); @@ -559,179 +790,6 @@ void Steam_Matchmaking_Servers::server_details_rules(Gameserver *g, Steam_Matchm PRINT_DEBUG(" " "%" PRIu64 "", g->id()); } -// Get details on a given server in the list, you can get the valid range of index -// values by calling GetServerCount(). You will also receive index values in -// ISteamMatchmakingServerListResponse::ServerResponded() callbacks -gameserveritem_t *Steam_Matchmaking_Servers::GetServerDetails( HServerListRequest hRequest, int iServer ) -{ - PRINT_DEBUG("%p %i", hRequest, iServer); - std::lock_guard lock(global_mutex); - - std::vector gameservers_filtered; - auto g = std::begin(requests); - while (g != std::end(requests)) { - PRINT_DEBUG(" equal? %p %p", hRequest, g->id); - if (g->id == hRequest) { - gameservers_filtered = g->gameservers_filtered; - PRINT_DEBUG(" found %zu", gameservers_filtered.size()); - break; - } - - ++g; - } - - if (iServer >= gameservers_filtered.size() || iServer < 0) { - return NULL; - } - - Gameserver *gs = &gameservers_filtered[iServer].server; - gameserveritem_t *server = new gameserveritem_t(); //TODO: is the new here ok? - server_details(gs, server); - PRINT_DEBUG(" Returned server details"); - return server; -} - - -// Cancel an request which is operation on the given list type. You should call this to cancel -// any in-progress requests before destructing a callback object that may have been passed -// to one of the above list request calls. Not doing so may result in a crash when a callback -// occurs on the destructed object. -// Canceling a query does not release the allocated request handle. -// The request handle must be released using ReleaseRequest( hRequest ) -void Steam_Matchmaking_Servers::CancelQuery( HServerListRequest hRequest ) -{ - PRINT_DEBUG("%p", hRequest); - auto g = std::begin(requests); - while (g != std::end(requests)) { - if (g->id == hRequest) { - g->cancelled = true; - PRINT_DEBUG("canceled request with id: %p", g->id); - } - - ++g; - } -} - - -// Ping every server in your list again but don't update the list of servers -// Query callback installed when the server list was requested will be used -// again to post notifications and RefreshComplete, so the callback must remain -// valid until another RefreshComplete is called on it or the request -// is released with ReleaseRequest( hRequest ) -void Steam_Matchmaking_Servers::RefreshQuery( HServerListRequest hRequest ) -{ - PRINT_DEBUG("%p", hRequest); -} - - -// Returns true if the list is currently refreshing its server list -bool Steam_Matchmaking_Servers::IsRefreshing( HServerListRequest hRequest ) -{ - PRINT_DEBUG("%p", hRequest); - return false; -} - - -// How many servers in the given list, GetServerDetails above takes 0... GetServerCount() - 1 -int Steam_Matchmaking_Servers::GetServerCount( HServerListRequest hRequest ) -{ - PRINT_DEBUG("%p", hRequest); - std::lock_guard lock(global_mutex); - int size = 0; - auto g = std::begin(requests); - while (g != std::end(requests)) { - if (g->id == hRequest) { - size = g->gameservers_filtered.size(); - break; - } - - ++g; - } - - PRINT_DEBUG("final count = %i", size); - return size; -} - - -// Refresh a single server inside of a query (rather than all the servers ) -void Steam_Matchmaking_Servers::RefreshServer( HServerListRequest hRequest, int iServer ) -{ - PRINT_DEBUG("%p", hRequest); - //TODO -} - -static HServerQuery new_server_query() -{ - std::lock_guard lock(global_mutex); - static int a = 0; - ++a; - if (!a) ++a; - return a; -} - -//----------------------------------------------------------------------------- -// Queries to individual servers directly via IP/Port -//----------------------------------------------------------------------------- - -// Request updated ping time and other details from a single server -HServerQuery Steam_Matchmaking_Servers::PingServer( uint32 unIP, uint16 usPort, ISteamMatchmakingPingResponse *pRequestServersResponse ) -{ - PRINT_DEBUG("%hhu.%hhu.%hhu.%hhu:%hu", ((unsigned char *)&unIP)[3], ((unsigned char *)&unIP)[2], ((unsigned char *)&unIP)[1], ((unsigned char *)&unIP)[0], usPort); - std::lock_guard lock(global_mutex); - Steam_Matchmaking_Servers_Direct_IP_Request r; - r.id = new_server_query(); - r.ip = unIP; - r.port = usPort; - r.ping_response = pRequestServersResponse; - r.created = std::chrono::high_resolution_clock::now(); - direct_ip_requests.push_back(r); - return r.id; -} - -// Request the list of players currently playing on a server -HServerQuery Steam_Matchmaking_Servers::PlayerDetails( uint32 unIP, uint16 usPort, ISteamMatchmakingPlayersResponse *pRequestServersResponse ) -{ - PRINT_DEBUG("%hhu.%hhu.%hhu.%hhu:%hu", ((unsigned char *)&unIP)[3], ((unsigned char *)&unIP)[2], ((unsigned char *)&unIP)[1], ((unsigned char *)&unIP)[0], usPort); - std::lock_guard lock(global_mutex); - Steam_Matchmaking_Servers_Direct_IP_Request r; - r.id = new_server_query(); - r.ip = unIP; - r.port = usPort; - r.players_response = pRequestServersResponse; - r.created = std::chrono::high_resolution_clock::now(); - direct_ip_requests.push_back(r); - return r.id; -} - - -// Request the list of rules that the server is running (See ISteamGameServer::SetKeyValue() to set the rules server side) -HServerQuery Steam_Matchmaking_Servers::ServerRules( uint32 unIP, uint16 usPort, ISteamMatchmakingRulesResponse *pRequestServersResponse ) -{ - PRINT_DEBUG("%hhu.%hhu.%hhu.%hhu:%hu", ((unsigned char *)&unIP)[3], ((unsigned char *)&unIP)[2], ((unsigned char *)&unIP)[1], ((unsigned char *)&unIP)[0], usPort); - std::lock_guard lock(global_mutex); - Steam_Matchmaking_Servers_Direct_IP_Request r; - r.id = new_server_query(); - r.ip = unIP; - r.port = usPort; - r.rules_response = pRequestServersResponse; - r.created = std::chrono::high_resolution_clock::now(); - direct_ip_requests.push_back(r); - return r.id; -} - - -// Cancel an outstanding Ping/Players/Rules query from above. You should call this to cancel -// any in-progress requests before destructing a callback object that may have been passed -// to one of the above calls to avoid crashing when callbacks occur. -void Steam_Matchmaking_Servers::CancelServerQuery( HServerQuery hServerQuery ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - auto r = std::find_if(direct_ip_requests.begin(), direct_ip_requests.end(), [&hServerQuery](Steam_Matchmaking_Servers_Direct_IP_Request const& item) { return item.id == hServerQuery; }); - if (direct_ip_requests.end() == r) return; - direct_ip_requests.erase(r); -} - void Steam_Matchmaking_Servers::RunCallbacks() { // PRINT_DEBUG_ENTRY(); diff --git a/dll/steam_networking.cpp b/dll/steam_networking.cpp new file mode 100644 index 00000000..55743ccb --- /dev/null +++ b/dll/steam_networking.cpp @@ -0,0 +1,972 @@ +/* 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 + . */ + +#include "dll/steam_networking.h" + + +//packet timeout in seconds for non connections +#define ORPHANED_PACKET_TIMEOUT (20) +#define NEW_CONNECTION_TIMEOUT (20.0) + +//kingdom 2 crowns doesn't work with a 0.3 delay or lower +#define NEW_CONNECTION_DELAY (0.4) + +#define OLD_CHANNEL_NUMBER 1 + + +bool Steam_Networking::connection_exists(CSteamID id) +{ + std::lock_guard lock(connections_edit_mutex); + return std::find_if(connections.begin(), connections.end(), [&id](struct Steam_Networking_Connection const& conn) { return conn.remote == id;}) != connections.end(); +} + +struct Steam_Networking_Connection* Steam_Networking::get_or_create_connection(CSteamID id) +{ + std::lock_guard lock(connections_edit_mutex); + auto conn = std::find_if(connections.begin(), connections.end(), [&id](struct Steam_Networking_Connection const& conn) { return conn.remote == id;}); + + if (connections.end() == conn) { + struct Steam_Networking_Connection connection; + connection.remote = id; + connections.push_back(connection); + return &(connections[connections.size() - 1]); + } else { + return &(*conn); + } +} + +void Steam_Networking::remove_connection(CSteamID id) +{ + { + std::lock_guard lock(connections_edit_mutex); + auto conn = std::begin(connections); + while (conn != std::end(connections)) { + if (conn->remote == id) { + + conn = connections.erase(conn); + } else { + ++conn; + } + } + } + + //pretty sure steam also clears the entire queue of messages for that connection + { + std::lock_guard lock(messages_mutex); + auto msg = std::begin(messages); + while (msg != std::end(messages)) { + if (msg->source_id() == id.ConvertToUint64()) { + msg = messages.erase(msg); + } else { + ++msg; + } + } + } + + { + auto msg = std::begin(unprocessed_messages); + while (msg != std::end(unprocessed_messages)) { + if (msg->source_id() == id.ConvertToUint64()) { + msg = unprocessed_messages.erase(msg); + } else { + ++msg; + } + } + } +} + +SNetSocket_t Steam_Networking::create_connection_socket(CSteamID target, int nVirtualPort, uint32 nIP, uint16 nPort, SNetListenSocket_t id, enum steam_socket_connection_status status, SNetSocket_t other_id) +{ + static SNetSocket_t socket_number = 0; + bool found = false; + do { + found = false; + ++socket_number; + for (auto & c: connection_sockets) { + if (c.id == socket_number || socket_number == 0) { + found = true; + break; + } + } + } while (found); + + struct steam_connection_socket socket{}; + socket.id = socket_number; + socket.listen_id = id; + socket.status = status; + socket.target = target; + socket.nVirtualPort = nVirtualPort; + socket.nIP = nIP; + socket.nPort = nPort; + socket.other_id = other_id; + connection_sockets.push_back(socket); + + Common_Message msg{}; + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(target.ConvertToUint64()); + msg.set_allocated_network_old(new Network_Old); + if (nPort) { + msg.mutable_network_old()->set_type(Network_Old::CONNECTION_REQUEST_IP); + msg.mutable_network_old()->set_port(nPort); + } else { + msg.mutable_network_old()->set_type(Network_Old::CONNECTION_REQUEST_STEAMID); + msg.mutable_network_old()->set_port(nVirtualPort); + } + + if (socket.status == SOCKET_CONNECTED) { + msg.mutable_network_old()->set_type(Network_Old::CONNECTION_ACCEPTED); + } + + msg.mutable_network_old()->set_connection_id(socket.other_id); + msg.mutable_network_old()->set_connection_id_from(socket.id); + + if (target.IsValid()) { + network->sendTo(&msg, true); + } else if (nIP) { + network->sendToIPPort(&msg, nIP, nPort, true); + } + + return socket.id; +} + +struct steam_connection_socket* Steam_Networking::get_connection_socket(SNetSocket_t id) +{ + auto conn = std::find_if(connection_sockets.begin(), connection_sockets.end(), [&id](struct steam_connection_socket const& conn) { + return conn.id == id; + }); + if (connection_sockets.end() == conn) return NULL; + + return &(*conn); +} + +void Steam_Networking::remove_killed_connection_sockets() +{ + auto socket = std::begin(connection_sockets); + while (socket != std::end(connection_sockets)) { + if (socket->status == SOCKET_KILLED || socket->status == SOCKET_DISCONNECTED) { + socket = connection_sockets.erase(socket); + } else { + ++socket; + } + } +} + +void Steam_Networking::steam_networking_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Networking *steam_networking = (Steam_Networking *)object; + steam_networking->Callback(msg); +} + +void Steam_Networking::steam_networking_run_every_runcp(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Networking *steam_networking = (Steam_Networking *)object; + steam_networking->RunCallbacks(); +} + +Steam_Networking::Steam_Networking(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) +{ + this->settings = settings; + this->network = network; + this->callbacks = callbacks; + this->run_every_runcb = run_every_runcb; + + this->network->setCallback(CALLBACK_ID_NETWORKING, settings->get_local_steam_id(), &Steam_Networking::steam_networking_callback, this); + this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking::steam_networking_callback, this); + this->run_every_runcb->add(&Steam_Networking::steam_networking_run_every_runcp, this); + + PRINT_DEBUG("user id %llu messages: %p", settings->get_local_steam_id().ConvertToUint64(), &messages); +} + +Steam_Networking::~Steam_Networking() +{ + this->network->rmCallback(CALLBACK_ID_NETWORKING, settings->get_local_steam_id(), &Steam_Networking::steam_networking_callback, this); + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking::steam_networking_callback, this); + this->run_every_runcb->remove(&Steam_Networking::steam_networking_run_every_runcp, this); +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// Session-less connection functions +// automatically establishes NAT-traversing or Relay server connections + +// Sends a P2P packet to the specified user +// UDP-like, unreliable and a max packet size of 1200 bytes +// the first packet send may be delayed as the NAT-traversal code runs +// if we can't get through to the user, an error will be posted via the callback P2PSessionConnectFail_t +// see EP2PSend enum above for the descriptions of the different ways of sending packets +// +// nChannel is a routing number you can use to help route message to different systems - you'll have to call ReadP2PPacket() +// with the same channel number in order to retrieve the data on the other end +// using different channels to talk to the same user will still use the same underlying p2p connection, saving on resources +bool Steam_Networking::SendP2PPacket( CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType, int nChannel) +{ + PRINT_DEBUG("len %u sendtype: %u channel: %u to: %llu", cubData, eP2PSendType, nChannel, steamIDRemote.ConvertToUint64()); + std::lock_guard lock(global_mutex); + bool reliable = false; + if (eP2PSendType == k_EP2PSendReliable || eP2PSendType == k_EP2PSendReliableWithBuffering) reliable = true; + Common_Message msg; + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(steamIDRemote.ConvertToUint64()); + msg.set_allocated_network(new Network_pb); + + if (!connection_exists(steamIDRemote)) { + msg.mutable_network()->set_type(Network_pb::NEW_CONNECTION); + network->sendTo(&msg, true); + } + + msg.mutable_network()->set_channel(nChannel); + msg.mutable_network()->set_data(pubData, cubData); + msg.mutable_network()->set_type(Network_pb::DATA); + + struct Steam_Networking_Connection *conn = get_or_create_connection(steamIDRemote); + new_connection_times.erase(steamIDRemote); + + conn->open_channels.insert(nChannel); + bool ret = network->sendTo(&msg, reliable); + PRINT_DEBUG("Sent message with size: %zu %u", msg.network().data().size(), ret); + return ret; +} + +bool Steam_Networking::SendP2PPacket( CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType ) +{ + PRINT_DEBUG("old"); + return SendP2PPacket(steamIDRemote, pubData, cubData, eP2PSendType, OLD_CHANNEL_NUMBER); +} + +// returns true if any data is available for read, and the amount of data that will need to be read +bool Steam_Networking::IsP2PPacketAvailable( uint32 *pcubMsgSize, int nChannel) +{ + PRINT_DEBUG("channel: %i", nChannel); + std::lock_guard lock(messages_mutex); + //Not sure if this should be here because it slightly screws up games that don't like such low "pings" + //Commenting it out for now because it looks like it causes a bug where 20xx gets stuck in an infinite receive packet loop + //this->network->Run(); + //RunCallbacks(); + + PRINT_DEBUG("Messages %zu %p", messages.size(), &messages); + for (auto &msg : messages) { + if (connection_exists((uint64)msg.source_id()) && msg.mutable_network()->channel() == nChannel && msg.network().processed()) { + uint32 size = msg.mutable_network()->data().size(); + if (pcubMsgSize) *pcubMsgSize = size; + PRINT_DEBUG("available with size: %u", size); + return true; + } + } + + PRINT_DEBUG("(not available)"); + if (pcubMsgSize) *pcubMsgSize = 0; + return false; +} + +bool Steam_Networking::IsP2PPacketAvailable( uint32 *pcubMsgSize) +{ + PRINT_DEBUG("old"); + return IsP2PPacketAvailable(pcubMsgSize, OLD_CHANNEL_NUMBER); +} + +// reads in a packet that has been sent from another user via SendP2PPacket() +// returns the size of the message and the steamID of the user who sent it in the last two parameters +// if the buffer passed in is too small, the message will be truncated +// this call is not blocking, and will return false if no data is available +bool Steam_Networking::ReadP2PPacket( void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote, int nChannel) +{ + PRINT_DEBUG("%u %i", cubDest, nChannel); + std::lock_guard lock(messages_mutex); + //Not sure if this should be here because it slightly screws up games that don't like such low "pings" + //Commenting it out for now because it looks like it causes a bug where 20xx gets stuck in an infinite receive packet loop + //this->network->Run(); + //RunCallbacks(); + + bool read = false; + PRINT_DEBUG("Number messages %zu", messages.size()); + auto msg = std::begin(messages); + while (msg != std::end(messages)) { + if (connection_exists((uint64)msg->source_id()) && msg->network().channel() == nChannel && msg->network().processed()) { + uint32 msg_size = msg->network().data().size(); + if (msg_size > cubDest) msg_size = cubDest; + if (pcubMsgSize) *pcubMsgSize = msg_size; + memcpy(pubDest, msg->network().data().data(), msg_size); + + PRINT_DEBUG("%s", + common_helpers::uint8_vector_to_hex_string(std::vector((uint8_t*)pubDest, (uint8_t*)pubDest + msg_size)).c_str()); + + *psteamIDRemote = CSteamID((uint64)msg->source_id()); + PRINT_DEBUG("len %u channel: %u from: " "%" PRIu64 "", msg_size, nChannel, msg->source_id()); + msg = messages.erase(msg); + return true; + } + + ++msg; + } + + if (pcubMsgSize) *pcubMsgSize = 0; + if (psteamIDRemote) *psteamIDRemote = k_steamIDNil; + return false; +} + +bool Steam_Networking::ReadP2PPacket( void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote) +{ + PRINT_DEBUG("old"); + return ReadP2PPacket(pubDest, cubDest, pcubMsgSize, psteamIDRemote, OLD_CHANNEL_NUMBER); +} + +// AcceptP2PSessionWithUser() should only be called in response to a P2PSessionRequest_t callback +// P2PSessionRequest_t will be posted if another user tries to send you a packet that you haven't talked to yet +// if you don't want to talk to the user, just ignore the request +// if the user continues to send you packets, another P2PSessionRequest_t will be posted periodically +// this may be called multiple times for a single user +// (if you've called SendP2PPacket() on the other user, this implicitly accepts the session request) +bool Steam_Networking::AcceptP2PSessionWithUser( CSteamID steamIDRemote ) +{ + PRINT_DEBUG("%llu", steamIDRemote.ConvertToUint64()); + std::lock_guard lock(global_mutex); + struct Steam_Networking_Connection *conn = get_or_create_connection(steamIDRemote); + if (conn) new_connection_times.erase(steamIDRemote); + return !!conn; +} + + +// call CloseP2PSessionWithUser() when you're done talking to a user, will free up resources under-the-hood +// if the remote user tries to send data to you again, another P2PSessionRequest_t callback will be posted +bool Steam_Networking::CloseP2PSessionWithUser( CSteamID steamIDRemote ) +{ + PRINT_DEBUG("%llu", steamIDRemote.ConvertToUint64()); + std::lock_guard lock(global_mutex); + if (!connection_exists(steamIDRemote)) { + + return false; + } + + remove_connection(steamIDRemote); + + return true; +} + + +// call CloseP2PChannelWithUser() when you're done talking to a user on a specific channel. Once all channels +// open channels to a user have been closed, the open session to the user will be closed and new data from this +// user will trigger a P2PSessionRequest_t callback +bool Steam_Networking::CloseP2PChannelWithUser( CSteamID steamIDRemote, int nChannel ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!connection_exists(steamIDRemote)) { + return false; + } + + struct Steam_Networking_Connection *conn = get_or_create_connection(steamIDRemote); + + conn->open_channels.erase(nChannel); + if (conn->open_channels.size() == 0) { + remove_connection(steamIDRemote); + } + + return true; +} + + +// fills out P2PSessionState_t structure with details about the underlying connection to the user +// should only needed for debugging purposes +// returns false if no connection exists to the specified user +bool Steam_Networking::GetP2PSessionState( CSteamID steamIDRemote, P2PSessionState_t *pConnectionState ) +{ + PRINT_DEBUG("%llu", steamIDRemote.ConvertToUint64()); + std::lock_guard lock(global_mutex); + if (!connection_exists(steamIDRemote) && (steamIDRemote != settings->get_local_steam_id())) { + if (pConnectionState) { + pConnectionState->m_bConnectionActive = false; + pConnectionState->m_bConnecting = false; + pConnectionState->m_eP2PSessionError = 0; + pConnectionState->m_bUsingRelay = false; + pConnectionState->m_nBytesQueuedForSend = 0; + pConnectionState->m_nPacketsQueuedForSend = 0; + pConnectionState->m_nRemoteIP = 0; + pConnectionState->m_nRemotePort = 0; + } + + PRINT_DEBUG("No Connection"); + return false; + } + + if (pConnectionState) { + pConnectionState->m_bConnectionActive = true; + pConnectionState->m_bConnecting = false; + pConnectionState->m_eP2PSessionError = 0; + pConnectionState->m_bUsingRelay = false; + pConnectionState->m_nBytesQueuedForSend = 0; + pConnectionState->m_nPacketsQueuedForSend = 0; + pConnectionState->m_nRemoteIP = network->getIP(steamIDRemote); + pConnectionState->m_nRemotePort = 12345; + } + + PRINT_DEBUG("Connection"); + return true; +} + + +// Allow P2P connections to fall back to being relayed through the Steam servers if a direct connection +// or NAT-traversal cannot be established. Only applies to connections created after setting this value, +// or to existing connections that need to automatically reconnect after this value is set. +// +// P2P packet relay is allowed by default +bool Steam_Networking::AllowP2PPacketRelay( bool bAllow ) +{ + PRINT_DEBUG("%u", bAllow); + return true; +} + + + +//////////////////////////////////////////////////////////////////////////////////////////// +// LISTEN / CONNECT style interface functions +// +// This is an older set of functions designed around the Berkeley TCP sockets model +// it's preferential that you use the above P2P functions, they're more robust +// and these older functions will be removed eventually +// +//////////////////////////////////////////////////////////////////////////////////////////// + +SNetListenSocket_t socket_number = 0; +// creates a socket and listens others to connect +// will trigger a SocketStatusCallback_t callback on another client connecting +// nVirtualP2PPort is the unique ID that the client will connect to, in case you have multiple ports +// this can usually just be 0 unless you want multiple sets of connections +// unIP is the local IP address to bind to +// pass in 0 if you just want the default local IP +// unPort is the port to use +// pass in 0 if you don't want users to be able to connect via IP/Port, but expect to be always peer-to-peer connections only +SNetListenSocket_t Steam_Networking::CreateListenSocket( int nVirtualP2PPort, uint32 nIP, uint16 nPort, bool bAllowUseOfPacketRelay ) +{ + PRINT_DEBUG("old %i %u %hu %u", nVirtualP2PPort, nIP, nPort, bAllowUseOfPacketRelay); + std::lock_guard lock(global_mutex); + for (auto & c : listen_sockets) { + if (c.nVirtualP2PPort == nVirtualP2PPort || c.nPort == nPort) + return 0; + } + + ++socket_number; + if (!socket_number) ++socket_number; + + struct steam_listen_socket socket; + socket.id = socket_number; + socket.nVirtualP2PPort = nVirtualP2PPort; + socket.nIP = nIP; + socket.nPort = nPort; + listen_sockets.push_back(socket); + return socket.id; +} + +SNetListenSocket_t Steam_Networking::CreateListenSocket( int nVirtualP2PPort, SteamIPAddress_t nIP, uint16 nPort, bool bAllowUseOfPacketRelay ) +{ + PRINT_DEBUG("%i %i %u %hu %u", nVirtualP2PPort, nIP.m_eType, nIP.m_unIPv4, nPort, bAllowUseOfPacketRelay); + //TODO: ipv6 + return CreateListenSocket(nVirtualP2PPort, nIP.m_unIPv4, nPort, bAllowUseOfPacketRelay); +} + +SNetListenSocket_t Steam_Networking::CreateListenSocket( int nVirtualP2PPort, uint32 nIP, uint16 nPort ) +{ + PRINT_DEBUG("old"); + return CreateListenSocket(nVirtualP2PPort, nIP, nPort, true); +} + +// creates a socket and begin connection to a remote destination +// can connect via a known steamID (client or game server), or directly to an IP +// on success will trigger a SocketStatusCallback_t callback +// on failure or timeout will trigger a SocketStatusCallback_t callback with a failure code in m_eSNetSocketState +SNetSocket_t Steam_Networking::CreateP2PConnectionSocket( CSteamID steamIDTarget, int nVirtualPort, int nTimeoutSec, bool bAllowUseOfPacketRelay ) +{ + PRINT_DEBUG("%llu %i %i %u", steamIDTarget.ConvertToUint64(), nVirtualPort, nTimeoutSec, bAllowUseOfPacketRelay); + std::lock_guard lock(global_mutex); + //TODO: nTimeoutSec + return create_connection_socket(steamIDTarget, nVirtualPort, 0, 0); +} + +SNetSocket_t Steam_Networking::CreateP2PConnectionSocket( CSteamID steamIDTarget, int nVirtualPort, int nTimeoutSec ) +{ + PRINT_DEBUG("old"); + return CreateP2PConnectionSocket(steamIDTarget, nVirtualPort, nTimeoutSec, true); +} + +SNetSocket_t Steam_Networking::CreateConnectionSocket( uint32 nIP, uint16 nPort, int nTimeoutSec ) +{ + PRINT_DEBUG("%u %hu %i", nIP, nPort, nTimeoutSec); + std::lock_guard lock(global_mutex); + //TODO: nTimeoutSec + return create_connection_socket((uint64)0, 0, nIP, nPort); +} + +SNetSocket_t Steam_Networking::CreateConnectionSocket( SteamIPAddress_t nIP, uint16 nPort, int nTimeoutSec ) +{ + PRINT_DEBUG("%i %u %hu %i", nIP.m_eType, nIP.m_unIPv4, nPort, nTimeoutSec); + //TODO: ipv6 + return CreateConnectionSocket(nIP.m_unIPv4, nPort, nTimeoutSec); +} + +// disconnects the connection to the socket, if any, and invalidates the handle +// any unread data on the socket will be thrown away +// if bNotifyRemoteEnd is set, socket will not be completely destroyed until the remote end acknowledges the disconnect +bool Steam_Networking::DestroySocket( SNetSocket_t hSocket, bool bNotifyRemoteEnd ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct steam_connection_socket *socket = get_connection_socket(hSocket); + if (!socket || socket->status == SOCKET_KILLED) return false; + socket->status = SOCKET_KILLED; + return true; +} + +// destroying a listen socket will automatically kill all the regular sockets generated from it +bool Steam_Networking::DestroyListenSocket( SNetListenSocket_t hSocket, bool bNotifyRemoteEnd ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + auto c = std::begin(listen_sockets); + while (c != std::end(listen_sockets)) { + if (c->id == hSocket) { + c = listen_sockets.erase(c); + for (auto & socket : connection_sockets) { + if (socket.listen_id == hSocket) { + socket.status = SOCKET_KILLED; + } + } + return true; + } else { + ++c; + } + } + + return false; +} + + +// sending data +// must be a handle to a connected socket +// data is all sent via UDP, and thus send sizes are limited to 1200 bytes; after this, many routers will start dropping packets +// use the reliable flag with caution; although the resend rate is pretty aggressive, +// it can still cause stalls in receiving data (like TCP) +bool Steam_Networking::SendDataOnSocket( SNetSocket_t hSocket, void *pubData, uint32 cubData, bool bReliable ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct steam_connection_socket *socket = get_connection_socket(hSocket); + if (!socket || socket->status != SOCKET_CONNECTED) return false; + + Common_Message msg; + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(socket->target.ConvertToUint64()); + msg.set_allocated_network_old(new Network_Old); + msg.mutable_network_old()->set_type(Network_Old::DATA); + msg.mutable_network_old()->set_connection_id(socket->other_id); + msg.mutable_network_old()->set_data(pubData, cubData); + return network->sendTo(&msg, bReliable); +} + + +// receiving data +// returns false if there is no data remaining +// fills out *pcubMsgSize with the size of the next message, in bytes +bool Steam_Networking::IsDataAvailableOnSocket( SNetSocket_t hSocket, uint32 *pcubMsgSize ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct steam_connection_socket *socket = get_connection_socket(hSocket); + if (!socket) { + if (pcubMsgSize) *pcubMsgSize = 0; + return false; + } + + if (socket->data_packets.size() == 0) return false; + if (pcubMsgSize) *pcubMsgSize = socket->data_packets[0].data().size(); + return true; +} + + +// fills in pubDest with the contents of the message +// messages are always complete, of the same size as was sent (i.e. packetized, not streaming) +// if *pcubMsgSize < cubDest, only partial data is written +// returns false if no data is available +bool Steam_Networking::RetrieveDataFromSocket( SNetSocket_t hSocket, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct steam_connection_socket *socket = get_connection_socket(hSocket); + if (!socket || socket->data_packets.size() == 0) return false; + + auto msg = std::begin(socket->data_packets); + if (msg != std::end(socket->data_packets)) { + uint32 msg_size = msg->data().size(); + if (msg_size > cubDest) msg_size = cubDest; + if (pcubMsgSize) *pcubMsgSize = msg_size; + memcpy(pubDest, msg->data().data(), msg_size); + msg = socket->data_packets.erase(msg); + return true; + } + + return false; +} + + +// checks for data from any socket that has been connected off this listen socket +// returns false if there is no data remaining +// fills out *pcubMsgSize with the size of the next message, in bytes +// fills out *phSocket with the socket that data is available on +bool Steam_Networking::IsDataAvailable( SNetListenSocket_t hListenSocket, uint32 *pcubMsgSize, SNetSocket_t *phSocket ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!hListenSocket) return false; + + for (auto & socket : connection_sockets) { + if (socket.listen_id == hListenSocket && socket.data_packets.size()) { + if (pcubMsgSize) *pcubMsgSize = socket.data_packets[0].data().size(); + if (phSocket) *phSocket = socket.id; + return true; + } + } + + return false; +} + + +// retrieves data from any socket that has been connected off this listen socket +// fills in pubDest with the contents of the message +// messages are always complete, of the same size as was sent (i.e. packetized, not streaming) +// if *pcubMsgSize < cubDest, only partial data is written +// returns false if no data is available +// fills out *phSocket with the socket that data is available on +bool Steam_Networking::RetrieveData( SNetListenSocket_t hListenSocket, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, SNetSocket_t *phSocket ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!hListenSocket) return false; + + for (auto & socket : connection_sockets) { + if (socket.listen_id == hListenSocket && socket.data_packets.size()) { + auto msg = std::begin(socket.data_packets); + if (msg != std::end(socket.data_packets)) { + uint32 msg_size = msg->data().size(); + if (msg_size > cubDest) msg_size = cubDest; + if (pcubMsgSize) *pcubMsgSize = msg_size; + if (phSocket) *phSocket = socket.id; + memcpy(pubDest, msg->data().data(), msg_size); + msg = socket.data_packets.erase(msg); + return true; + } + } + } + + return false; +} + + +// returns information about the specified socket, filling out the contents of the pointers +bool Steam_Networking::GetSocketInfo( SNetSocket_t hSocket, CSteamID *pSteamIDRemote, int *peSocketStatus, uint32 *punIPRemote, uint16 *punPortRemote ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct steam_connection_socket *socket = get_connection_socket(hSocket); + if (!socket) return false; + if (pSteamIDRemote) *pSteamIDRemote = socket->target; + if (peSocketStatus) { + //TODO: I'm not sure what peSocketStatus is supposed to be but I'm guessing it's ESNetSocketState + if (socket->status == SOCKET_CONNECTED) { + *peSocketStatus = k_ESNetSocketStateConnected; + } else if (socket->status == SOCKET_CONNECTING) { + *peSocketStatus = k_ESNetSocketStateInitiated; + } else if (socket->status == SOCKET_DISCONNECTED) { + *peSocketStatus = k_ESNetSocketStateDisconnecting; + } else if (socket->status == SOCKET_KILLED) { + *peSocketStatus = k_ESNetSocketStateConnectionBroken; + } else { + *peSocketStatus = k_ESNetSocketStateInvalid; + } + } + + if (punIPRemote) *punIPRemote = socket->nIP; + if (punPortRemote) *punPortRemote = socket->nPort; + return true; +} + +bool Steam_Networking::GetSocketInfo( SNetSocket_t hSocket, CSteamID *pSteamIDRemote, int *peSocketStatus, SteamIPAddress_t *punIPRemote, uint16 *punPortRemote ) +{ + PRINT_DEBUG_ENTRY(); + //TODO: ipv6 + uint32 *ip_remote = NULL; + if (punIPRemote) { + ip_remote = &(punIPRemote->m_unIPv4); + } + + bool ret = GetSocketInfo(hSocket, pSteamIDRemote, peSocketStatus, ip_remote, punPortRemote ); + if (punIPRemote && ret) { + punIPRemote->m_eType = k_ESteamIPTypeIPv4; + } + + return ret; +} + +// returns which local port the listen socket is bound to +// *pnIP and *pnPort will be 0 if the socket is set to listen for P2P connections only +bool Steam_Networking::GetListenSocketInfo( SNetListenSocket_t hListenSocket, uint32 *pnIP, uint16 *pnPort ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + auto conn = std::find_if(listen_sockets.begin(), listen_sockets.end(), [&hListenSocket](struct steam_listen_socket const& conn) { return conn.id == hListenSocket;}); + if (conn == listen_sockets.end()) return false; + if (pnIP) *pnIP = conn->nIP; + if (pnPort) *pnPort = conn->nPort; + return true; +} + +bool Steam_Networking::GetListenSocketInfo( SNetListenSocket_t hListenSocket, SteamIPAddress_t *pnIP, uint16 *pnPort ) +{ + PRINT_DEBUG_ENTRY(); + //TODO: ipv6 + uint32 *ip = NULL; + if (pnIP) { + ip = &(pnIP->m_unIPv4); + } + + bool ret = GetListenSocketInfo(hListenSocket, ip, pnPort ); + if (pnIP && ret) { + pnIP->m_eType = k_ESteamIPTypeIPv4; + } + + return ret; +} + +// returns true to describe how the socket ended up connecting +ESNetSocketConnectionType Steam_Networking::GetSocketConnectionType( SNetSocket_t hSocket ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct steam_connection_socket *socket = get_connection_socket(hSocket); + if (!socket || socket->status != SOCKET_CONNECTED) return k_ESNetSocketConnectionTypeNotConnected; + else return k_ESNetSocketConnectionTypeUDP; +} + + +// max packet size, in bytes +int Steam_Networking::GetMaxPacketSize( SNetSocket_t hSocket ) +{ + PRINT_DEBUG_ENTRY(); + return 1500; +} + +void Steam_Networking::RunCallbacks() +{ + uint64 current_time = std::chrono::duration_cast>(std::chrono::system_clock::now().time_since_epoch()).count(); + + { + std::lock_guard lock(messages_mutex); + + { + auto msg = std::begin(unprocessed_messages); + while (msg != std::end(unprocessed_messages)) { + CSteamID source_id((uint64)msg->source_id()); + if (!connection_exists(source_id)) { + if (new_connection_times.find(source_id) == new_connection_times.end()) { + new_connections_to_call_cb.push(source_id); + new_connection_times[source_id] = std::chrono::high_resolution_clock::now(); + } + } else { + struct Steam_Networking_Connection *conn = get_or_create_connection(source_id); + conn->open_channels.insert(msg->network().channel()); + } + + msg->mutable_network()->set_processed(true); + msg->mutable_network()->set_time_processed(current_time); + messages.push_back(*msg); + msg = unprocessed_messages.erase(msg); + } + } + + auto msg = std::begin(messages); + while (msg != std::end(messages)) { + bool deleted = false; + if (msg->network().processed()) { + if (!connection_exists((uint64)msg->source_id())) { + if (msg->network().time_processed() + ORPHANED_PACKET_TIMEOUT < current_time) { + deleted = true; + } + } + } + + if (deleted) { + msg = messages.erase(msg); + } else { + ++msg; + } + } + + } + + while (!new_connections_to_call_cb.empty()) { + CSteamID source_id = new_connections_to_call_cb.front(); + auto t = new_connection_times.find(source_id); + if (t == new_connection_times.end()) { + new_connections_to_call_cb.pop(); + continue; + } + + if (!check_timedout(t->second, NEW_CONNECTION_DELAY)) { + break; + } + + P2PSessionRequest_t data; + memset(&data, 0, sizeof(data)); + data.m_steamIDRemote = source_id; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + new_connections_to_call_cb.pop(); + } + + //TODO: not sure if sockets should be wiped right away + remove_killed_connection_sockets(); + + for(auto it = new_connection_times.begin(); it != new_connection_times.end(); ) { + if (std::chrono::duration_cast>(std::chrono::high_resolution_clock::now() - it->second).count() > NEW_CONNECTION_TIMEOUT) { + it = new_connection_times.erase(it); + //TODO send packet to other side to tell them connection has "failed". + } else { + ++it; + } + } +} + +void Steam_Networking::Callback(Common_Message *msg) +{ + if (msg->has_network()) { + PRINT_DEBUG("got msg from: " "%" PRIu64 " to: " "%" PRIu64 " size %zu type %u | messages %p: %zu", + msg->source_id(), msg->dest_id(), msg->network().data().size(), msg->network().type(), &messages, messages.size() + ); + PRINT_DEBUG("msg data: '%s'", + common_helpers::uint8_vector_to_hex_string(std::vector(msg->network().data().begin(), msg->network().data().end())).c_str()); + + if (msg->network().type() == Network_pb::DATA) { + unprocessed_messages.push_back(Common_Message(*msg)); + } + + if (msg->network().type() == Network_pb::NEW_CONNECTION) { + std::lock_guard lock(messages_mutex); + auto msg_temp = std::begin(messages); + while (msg_temp != std::end(messages)) { + //only delete processed to handle unreliable message arriving at the same time. + if (msg_temp->source_id() == msg->source_id() && msg_temp->network().processed()) { + msg_temp = messages.erase(msg_temp); + } else { + ++msg_temp; + } + } + } + } + + if (msg->has_network_old()) { + PRINT_DEBUG("got network socket msg %u", msg->network_old().type()); + if (msg->network_old().type() == Network_Old::CONNECTION_REQUEST_IP) { + for (auto & listen : listen_sockets) { + if (listen.nPort == msg->network_old().port()) { + SNetSocket_t new_sock = create_connection_socket((uint64)msg->source_id(), 0, 0, msg->network_old().port(), listen.id, SOCKET_CONNECTED, msg->network_old().connection_id_from()); + if (new_sock) { + struct SocketStatusCallback_t data; + data.m_hSocket = new_sock; + data.m_hListenSocket = listen.id; + data.m_steamIDRemote = (uint64)msg->source_id(); + data.m_eSNetSocketState = k_ESNetSocketStateConnected; //TODO is this the right state? + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + } + } + } else if (msg->network_old().type() == Network_Old::CONNECTION_REQUEST_STEAMID) { + for (auto & listen : listen_sockets) { + if (listen.nVirtualP2PPort == msg->network_old().port()) { + SNetSocket_t new_sock = create_connection_socket((uint64)msg->source_id(), msg->network_old().port(), 0, 0, listen.id, SOCKET_CONNECTED, msg->network_old().connection_id_from()); + if (new_sock) { + struct SocketStatusCallback_t data; + data.m_hSocket = new_sock; + data.m_hListenSocket = listen.id; + data.m_steamIDRemote = (uint64)msg->source_id(); + data.m_eSNetSocketState = k_ESNetSocketStateConnected; //TODO is this the right state? + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + } + } + + } else if (msg->network_old().type() == Network_Old::CONNECTION_ACCEPTED) { + struct steam_connection_socket *socket = get_connection_socket(msg->network_old().connection_id()); + if (socket && socket->nPort && socket->status == SOCKET_CONNECTING && !socket->target.IsValid()) { + socket->target = (uint64)msg->source_id(); + } + + if (socket && socket->status == SOCKET_CONNECTING && msg->source_id() == socket->target.ConvertToUint64()) { + socket->status = SOCKET_CONNECTED; + socket->other_id = msg->network_old().connection_id_from(); + struct SocketStatusCallback_t data; + data.m_hSocket = socket->id; + data.m_hListenSocket = socket->listen_id; + data.m_steamIDRemote = socket->target; + data.m_eSNetSocketState = k_ESNetSocketStateConnected; //TODO is this the right state? + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + } else if (msg->network_old().type() == Network_Old::CONNECTION_END) { + struct steam_connection_socket *socket = get_connection_socket(msg->network_old().connection_id()); + if (socket && socket->status == SOCKET_CONNECTED && msg->source_id() == socket->target.ConvertToUint64()) { + struct SocketStatusCallback_t data; + socket->status = SOCKET_DISCONNECTED; + data.m_hSocket = socket->id; + data.m_hListenSocket = socket->listen_id; + data.m_steamIDRemote = socket->target; + data.m_eSNetSocketState = k_ESNetSocketStateRemoteEndDisconnected; //TODO is this the right state? + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + } else if (msg->network_old().type() == Network_Old::DATA) { + struct steam_connection_socket *socket = get_connection_socket(msg->network_old().connection_id()); + if (socket && socket->status == SOCKET_CONNECTED && msg->source_id() == socket->target.ConvertToUint64()) { + socket->data_packets.push_back(msg->network_old()); + } + } + } + + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::DISCONNECT) { + CSteamID source_id((uint64)msg->source_id()); + if (connection_exists(source_id)) { + P2PSessionConnectFail_t data; + data.m_steamIDRemote = source_id; + data.m_eP2PSessionError = k_EP2PSessionErrorDestinationNotLoggedIn; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + + for (auto & socket : connection_sockets) { + if (socket.target.ConvertToUint64() == msg->source_id()) { + struct SocketStatusCallback_t data; + socket.status = SOCKET_DISCONNECTED; + data.m_hSocket = socket.id; + data.m_hListenSocket = socket.listen_id; + data.m_steamIDRemote = socket.target; + data.m_eSNetSocketState = k_ESNetSocketStateConnectionBroken; //TODO is this the right state? + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + } + } else + + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + } +} diff --git a/dll/steam_networking_messages.cpp b/dll/steam_networking_messages.cpp new file mode 100644 index 00000000..fdaa23db --- /dev/null +++ b/dll/steam_networking_messages.cpp @@ -0,0 +1,419 @@ +/* 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 + . */ + +#include "dll/steam_networking_messages.h" + + +#define NETWORKING_MESSAGES_TIMEOUT 30.0 + + +void Steam_Networking_Messages::steam_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Networking_Messages *steam_networking_messages = (Steam_Networking_Messages *)object; + steam_networking_messages->Callback(msg); +} + +void Steam_Networking_Messages::steam_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Networking_Messages *steam_networking_messages = (Steam_Networking_Messages *)object; + steam_networking_messages->RunCallbacks(); +} + +void Steam_Networking_Messages::free_steam_message_data(SteamNetworkingMessage_t *pMsg) +{ + free(pMsg->m_pData); + pMsg->m_pData = NULL; +} + +void Steam_Networking_Messages::delete_steam_message(SteamNetworkingMessage_t *pMsg) +{ + if (pMsg->m_pfnFreeData) pMsg->m_pfnFreeData(pMsg); + delete pMsg; +} + +void Steam_Networking_Messages::end_connection(CSteamID steam_id) +{ + auto conn = connections.find(steam_id); + if (conn != connections.end()) { + conn->second.dead = true; + } +} + +std::map::iterator Steam_Networking_Messages::find_or_create_message_connection(SteamNetworkingIdentity identityRemote, bool incoming, bool restartbroken) +{ + auto conn = connections.find(identityRemote.GetSteamID()); + if (conn == connections.end() || (conn->second.dead && restartbroken)) { + ++id_counter; + struct Steam_Message_Connection con; + con.remote_identity = identityRemote; + con.id = id_counter; + connections[identityRemote.GetSteamID()] = con; + + Common_Message msg; + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(con.remote_identity.GetSteamID64()); + msg.set_allocated_networking_messages(new Networking_Messages); + if (incoming) { + msg.mutable_networking_messages()->set_type(Networking_Messages::CONNECTION_ACCEPT); + } else { + msg.mutable_networking_messages()->set_type(Networking_Messages::CONNECTION_NEW); + } + msg.mutable_networking_messages()->set_channel(0); + msg.mutable_networking_messages()->set_id_from(con.id); + network->sendTo(&msg, true); + + conn = connections.find(identityRemote.GetSteamID()); + + if (incoming) { + SteamNetworkingMessagesSessionRequest_t data; + data.m_identityRemote = con.remote_identity; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + } + + if (!incoming) { + conn->second.accepted = true; + } + + return conn; +} + + +Steam_Networking_Messages::Steam_Networking_Messages(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->created = std::chrono::steady_clock::now(); + + this->network->setCallback(CALLBACK_ID_NETWORKING_MESSAGES, settings->get_local_steam_id(), &Steam_Networking_Messages::steam_callback, this); + this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Messages::steam_callback, this); + this->run_every_runcb->add(&Steam_Networking_Messages::steam_run_every_runcb, this); +} + +Steam_Networking_Messages::~Steam_Networking_Messages() +{ + this->network->rmCallback(CALLBACK_ID_NETWORKING_MESSAGES, settings->get_local_steam_id(), &Steam_Networking_Messages::steam_callback, this); + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Messages::steam_callback, this); + this->run_every_runcb->remove(&Steam_Networking_Messages::steam_run_every_runcb, this); +} + + +/// Sends a message to the specified host. If we don't already have a session with that user, +/// a session is implicitly created. There might be some handshaking that needs to happen +/// before we can actually begin sending message data. If this handshaking fails and we can't +/// get through, an error will be posted via the callback SteamNetworkingMessagesSessionFailed_t. +/// There is no notification when the operation succeeds. (You should have the peer send a reply +/// for this purpose.) +/// +/// Sending a message to a host will also implicitly accept any incoming connection from that host. +/// +/// nSendFlags is a bitmask of k_nSteamNetworkingSend_xxx options +/// +/// nRemoteChannel is a routing number you can use to help route message to different systems. +/// You'll have to call ReceiveMessagesOnChannel() with the same channel number in order to retrieve +/// the data on the other end. +/// +/// Using different channels to talk to the same user will still use the same underlying +/// connection, saving on resources. If you don't need this feature, use 0. +/// Otherwise, small integers are the most efficient. +/// +/// It is guaranteed that reliable messages to the same host on the same channel +/// will be be received by the remote host (if they are received at all) exactly once, +/// and in the same order that they were send. +/// +/// NO other order guarantees exist! In particular, unreliable messages may be dropped, +/// received out of order with respect to each other and with respect to reliable data, +/// or may be received multiple times. Messages on different channels are *not* guaranteed +/// to be received in the order they were sent. +/// +/// A note for those familiar with TCP/IP ports, or converting an existing codebase that +/// opened multiple sockets: You might notice that there is only one channel, and with +/// TCP/IP each endpoint has a port number. You can think of the channel number as the +/// *destination* port. If you need each message to also include a "source port" (so the +/// recipient can route the reply), then just put that in your message. That is essentially +/// how UDP works! +/// +/// Returns: +/// - k_EREsultOK on success. +/// - k_EResultNoConnection will be returned if the session has failed or was closed by the peer, +/// and k_nSteamNetworkingSend_AutoRestartBrokwnSession is not used. (You can use +/// GetSessionConnectionInfo to get the details.) In order to acknowledge the broken session +/// and start a new one, you must call CloseSessionWithUser +/// - See SendMessageToConnection::SendMessageToConnection for more +EResult Steam_Networking_Messages::SendMessageToUser( const SteamNetworkingIdentity &identityRemote, const void *pubData, uint32 cubData, int nSendFlags, int nRemoteChannel ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + const SteamNetworkingIPAddr *ip = identityRemote.GetIPAddr(); + bool reliable = false; + if (nSendFlags & k_nSteamNetworkingSend_Reliable) { + reliable = true; + } + + bool restart_broken = false; + if (nSendFlags & k_nSteamNetworkingSend_AutoRestartBrokenSession) { + restart_broken = true; + } + + if (identityRemote.m_eType == k_ESteamNetworkingIdentityType_SteamID) { + PRINT_DEBUG("%llu", identityRemote.GetSteamID64()); + //steam id identity + } else if (ip) { + PRINT_DEBUG("%u:%u ipv4? %u", ip->GetIPv4(), ip->m_port, ip->IsIPv4()); + //ip addr + return k_EResultNoConnection; //TODO + } else { + return k_EResultNoConnection; + } + + auto conn = find_or_create_message_connection(identityRemote, false, restart_broken); + if (conn->second.dead) { + return k_EResultNoConnection; + } + + Common_Message msg; + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(conn->second.remote_identity.GetSteamID64()); + msg.set_allocated_networking_messages(new Networking_Messages); + msg.mutable_networking_messages()->set_type(Networking_Messages::DATA); + msg.mutable_networking_messages()->set_channel(nRemoteChannel); + msg.mutable_networking_messages()->set_id_from(conn->second.id); + msg.mutable_networking_messages()->set_data(pubData, cubData); + + network->sendTo(&msg, reliable); + return k_EResultOK; +} + +/// Reads the next message that has been sent from another user via SendMessageToUser() on the given channel. +/// Returns number of messages returned into your list. (0 if no message are available on that channel.) +/// +/// When you're done with the message object(s), make sure and call Release()! +int Steam_Networking_Messages::ReceiveMessagesOnChannel( int nLocalChannel, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + int message_counter = 0; + + for (auto & conn : connections) { + auto chan = conn.second.data.find(nLocalChannel); + if (chan != conn.second.data.end()) { + while (!chan->second.empty() && message_counter < nMaxMessages) { + SteamNetworkingMessage_t *pMsg = new SteamNetworkingMessage_t(); //TODO size is wrong + unsigned long size = chan->second.front().size(); + pMsg->m_pData = malloc(size); + pMsg->m_cbSize = size; + memcpy(pMsg->m_pData, chan->second.front().data(), size); + pMsg->m_conn = conn.second.id; + pMsg->m_identityPeer = conn.second.remote_identity; + pMsg->m_nConnUserData = -1; + pMsg->m_usecTimeReceived = std::chrono::duration_cast(std::chrono::steady_clock::now() - created).count(); + //TODO: messagenumber? + // pMsg->m_nMessageNumber = connect_socket->second.packet_receive_counter; + // ++connect_socket->second.packet_receive_counter; + + pMsg->m_pfnFreeData = &free_steam_message_data; + pMsg->m_pfnRelease = &delete_steam_message; + pMsg->m_nChannel = nLocalChannel; + ppOutMessages[message_counter] = pMsg; + ++message_counter; + chan->second.pop(); + } + } + + if (message_counter >= nMaxMessages) { + break; + } + } + + PRINT_DEBUG("got %u", message_counter); + return message_counter; +} + +/// AcceptSessionWithUser() should only be called in response to a SteamP2PSessionRequest_t callback +/// SteamP2PSessionRequest_t will be posted if another user tries to send you a message, and you haven't +/// tried to talk to them. If you don't want to talk to them, just ignore the request. +/// If the user continues to send you messages, SteamP2PSessionRequest_t callbacks will continue to +/// be posted periodically. This may be called multiple times for a single user. +/// +/// Calling SendMessage() on the other user, this implicitly accepts any pending session request. +bool Steam_Networking_Messages::AcceptSessionWithUser( const SteamNetworkingIdentity &identityRemote ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + auto conn = connections.find(identityRemote.GetSteamID()); + if (conn == connections.end()) { + return false; + } + + conn->second.accepted = true; + return true; +} + +/// Call this when you're done talking to a user to immediately free up resources under-the-hood. +/// If the remote user tries to send data to you again, another P2PSessionRequest_t callback will +/// be posted. +/// +/// Note that sessions that go unused for a few minutes are automatically timed out. +bool Steam_Networking_Messages::CloseSessionWithUser( const SteamNetworkingIdentity &identityRemote ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + auto conn = connections.find(identityRemote.GetSteamID()); + if (conn == connections.end()) { + return false; + } + + Common_Message msg; + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(conn->second.remote_identity.GetSteamID64()); + msg.set_allocated_networking_messages(new Networking_Messages); + msg.mutable_networking_messages()->set_type(Networking_Messages::CONNECTION_END); + msg.mutable_networking_messages()->set_channel(0); + msg.mutable_networking_messages()->set_id_from(conn->second.id); + network->sendTo(&msg, true); + + connections.erase(conn); + return true; +} + +/// Call this when you're done talking to a user on a specific channel. Once all +/// open channels to a user have been closed, the open session to the user will be +/// closed, and any new data from this user will trigger a SteamP2PSessionRequest_t +/// callback +bool Steam_Networking_Messages::CloseChannelWithUser( const SteamNetworkingIdentity &identityRemote, int nLocalChannel ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + //TODO + return false; +} + +/// Returns information about the latest state of a connection, if any, with the given peer. +/// Primarily intended for debugging purposes, but can also be used to get more detailed +/// failure information. (See SendMessageToUser and k_nSteamNetworkingSend_AutoRestartBrokwnSession.) +/// +/// Returns the value of SteamNetConnectionInfo_t::m_eState, or k_ESteamNetworkingConnectionState_None +/// if no connection exists with specified peer. You may pass nullptr for either parameter if +/// you do not need the corresponding details. Note that sessions time out after a while, +/// so if a connection fails, or SendMessageToUser returns SendMessageToUser, you cannot wait +/// indefinitely to obtain the reason for failure. +ESteamNetworkingConnectionState Steam_Networking_Messages::GetSessionConnectionInfo( const SteamNetworkingIdentity &identityRemote, SteamNetConnectionInfo_t *pConnectionInfo, SteamNetConnectionRealTimeStatus_t *pQuickStatus ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + auto conn = connections.find(identityRemote.GetSteamID()); + if (conn == connections.end()) { + return k_ESteamNetworkingConnectionState_None; + } + + ESteamNetworkingConnectionState state = k_ESteamNetworkingConnectionState_Connected; + if (conn->second.remote_id == 0 || !conn->second.accepted) { + state = k_ESteamNetworkingConnectionState_Connecting; + } else if (conn->second.dead) { + state = k_ESteamNetworkingConnectionState_ClosedByPeer; + } + + if (pConnectionInfo) { + memset(pConnectionInfo, 0, sizeof(SteamNetConnectionInfo_t)); + pConnectionInfo->m_eState = state; + pConnectionInfo->m_identityRemote = conn->second.remote_identity; + //TODO + } + + if (pQuickStatus) { + memset(pQuickStatus, 0, sizeof(SteamNetConnectionRealTimeStatus_t)); + pQuickStatus->m_eState = state; + pQuickStatus->m_nPing = 10; //TODO: calculate real numbers? + pQuickStatus->m_flConnectionQualityLocal = 1.0; + pQuickStatus->m_flConnectionQualityRemote = 1.0; + //TODO + } + + return k_ESteamNetworkingConnectionState_Connected; +} + +void Steam_Networking_Messages::RunCallbacks() +{ + auto msg = std::begin(incoming_data); + while (msg != std::end(incoming_data)) { + CSteamID source_id((uint64)msg->source_id()); + + auto conn = connections.find(source_id); + if (conn != connections.end()) { + if (conn->second.remote_id == msg->networking_messages().id_from()) + conn->second.data[msg->networking_messages().channel()].push(msg->networking_messages().data()); + } + + msg = incoming_data.erase(msg); + } + + auto conn = std::begin(connections); + while (conn != std::end(connections)) { + if (!conn->second.accepted && check_timedout(conn->second.created, NETWORKING_MESSAGES_TIMEOUT)) { + conn = connections.erase(conn); + } else { + ++conn; + } + } +} + +void Steam_Networking_Messages::Callback(Common_Message *msg) +{ + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + + if (msg->low_level().type() == Low_Level::DISCONNECT) { + end_connection((uint64)msg->source_id()); + } + } + + if (msg->has_networking_messages()) { + PRINT_DEBUG("got network socket msg %u", msg->networking_messages().type()); + if (msg->networking_messages().type() == Networking_Messages::CONNECTION_NEW) { + SteamNetworkingIdentity identity; + identity.SetSteamID64(msg->source_id()); + auto conn = find_or_create_message_connection(identity, true, false); + conn->second.remote_id = msg->networking_messages().id_from(); + conn->second.dead = false; + } + + if (msg->networking_messages().type() == Networking_Messages::CONNECTION_ACCEPT) { + auto conn = connections.find((uint64)msg->source_id()); + if (conn != connections.end()) { + conn->second.remote_id = msg->networking_messages().id_from(); + } + } + + if (msg->networking_messages().type() == Networking_Messages::CONNECTION_END) { + end_connection((uint64)msg->source_id()); + } + + if (msg->networking_messages().type() == Networking_Messages::DATA) { + incoming_data.push_back(Common_Message(*msg)); + } + } +} diff --git a/dll/steam_networking_sockets.cpp b/dll/steam_networking_sockets.cpp new file mode 100644 index 00000000..b67d7a0f --- /dev/null +++ b/dll/steam_networking_sockets.cpp @@ -0,0 +1,2098 @@ +/* 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 + . */ + +#include "dll/steam_networking_sockets.h" + + +void Steam_Networking_Sockets::steam_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Networking_Sockets *steam_networkingsockets = (Steam_Networking_Sockets *)object; + steam_networkingsockets->Callback(msg); +} + +void Steam_Networking_Sockets::steam_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Networking_Sockets *steam_networkingsockets = (Steam_Networking_Sockets *)object; + steam_networkingsockets->RunCallbacks(); +} + +SteamNetworkingMessage_t* Steam_Networking_Sockets::get_steam_message_connection(HSteamNetConnection hConn) +{ + auto connect_socket = sbcs->connect_sockets.find(hConn); + if (connect_socket == sbcs->connect_sockets.end()) return NULL; + if (connect_socket->second.data.empty()) return NULL; + SteamNetworkingMessage_t *pMsg = new SteamNetworkingMessage_t(); + unsigned long size = connect_socket->second.data.top().data().size(); + pMsg->m_pData = malloc(size); + pMsg->m_cbSize = size; + memcpy(pMsg->m_pData, connect_socket->second.data.top().data().data(), size); + pMsg->m_conn = hConn; + pMsg->m_identityPeer = connect_socket->second.remote_identity; + pMsg->m_nConnUserData = connect_socket->second.user_data; + pMsg->m_usecTimeReceived = std::chrono::duration_cast(std::chrono::steady_clock::now() - created).count(); + //TODO: check where messagenumber starts + pMsg->m_nMessageNumber = connect_socket->second.data.top().message_number(); + + pMsg->m_pfnFreeData = &free_steam_message_data; + pMsg->m_pfnRelease = &delete_steam_message; + pMsg->m_nChannel = 0; + connect_socket->second.data.pop(); + PRINT_DEBUG("get_steam_message_connection %u %lu, %llu", hConn, size, pMsg->m_nMessageNumber); + return pMsg; +} + +void Steam_Networking_Sockets::free_steam_message_data(SteamNetworkingMessage_t *pMsg) +{ + free(pMsg->m_pData); + pMsg->m_pData = NULL; +} + +void Steam_Networking_Sockets::delete_steam_message(SteamNetworkingMessage_t *pMsg) +{ + if (pMsg->m_pfnFreeData) pMsg->m_pfnFreeData(pMsg); + delete pMsg; +} + +unsigned long Steam_Networking_Sockets::get_socket_id() +{ + static unsigned long socket_id; + socket_id++; + return socket_id; +} + +HSteamNetConnection Steam_Networking_Sockets::new_connect_socket(SteamNetworkingIdentity remote_identity, int virtual_port, int real_port, enum connect_socket_status status, HSteamListenSocket listen_socket_id, HSteamNetConnection remote_id) +{ + Connect_Socket socket = {}; + socket.remote_identity = remote_identity; + socket.virtual_port = virtual_port; + socket.real_port = real_port; + socket.listen_socket_id = listen_socket_id; + socket.remote_id = remote_id; + socket.status = status; + socket.user_data = -1; + socket.poll_group = k_HSteamNetPollGroup_Invalid; + socket.created_by = settings->get_local_steam_id(); + socket.connect_request_last_sent = std::chrono::steady_clock::now(); + socket.connect_requests_sent = 0; + socket.packet_send_counter = 1; + + HSteamNetConnection socket_id = get_socket_id(); + if (socket_id == k_HSteamNetConnection_Invalid) ++socket_id; + + if (sbcs->connect_sockets.insert(std::make_pair(socket_id, socket)).second == false) { + return k_HSteamNetConnection_Invalid; + } + + return socket_id; +} + +struct Listen_Socket* Steam_Networking_Sockets::get_connection_socket(HSteamListenSocket id) +{ + auto conn = std::find_if(sbcs->listen_sockets.begin(), sbcs->listen_sockets.end(), [&id](struct Listen_Socket const& conn) { return conn.socket_id == id;}); + if (conn == sbcs->listen_sockets.end()) return NULL; + return &(*conn); +} + +bool Steam_Networking_Sockets::send_packet_new_connection(HSteamNetConnection m_hConn) +{ + auto connect_socket = sbcs->connect_sockets.find(m_hConn); + if (connect_socket == sbcs->connect_sockets.end()) return false; + + //TODO: right now this only supports connecting with steam id, might need to make ip/port connections work in the future when I find a game that uses them. + + Common_Message msg; + msg.set_source_id(connect_socket->second.created_by.ConvertToUint64()); + msg.set_allocated_networking_sockets(new Networking_Sockets); + if (connect_socket->second.status == CONNECT_SOCKET_CONNECTING) { + msg.mutable_networking_sockets()->set_type(Networking_Sockets::CONNECTION_REQUEST); + } else if (connect_socket->second.status == CONNECT_SOCKET_CONNECTED) { + msg.mutable_networking_sockets()->set_type(Networking_Sockets::CONNECTION_ACCEPTED); + } + + msg.mutable_networking_sockets()->set_virtual_port(connect_socket->second.virtual_port); + msg.mutable_networking_sockets()->set_real_port(connect_socket->second.real_port); + msg.mutable_networking_sockets()->set_connection_id_from(connect_socket->first); + msg.mutable_networking_sockets()->set_connection_id(connect_socket->second.remote_id); + + uint64_t steam_id = connect_socket->second.remote_identity.GetSteamID64(); + if (steam_id) { + msg.set_dest_id(steam_id); + return network->sendTo(&msg, true); + } + + const SteamNetworkingIPAddr *ip_addr = connect_socket->second.remote_identity.GetIPAddr(); + if (ip_addr) { + return network->sendToIPPort(&msg, ip_addr->GetIPv4(), ip_addr->m_port, true); + } + + return false; +} + +shared_between_client_server* Steam_Networking_Sockets::get_shared_between_client_server() +{ + return sbcs; +} + +HSteamListenSocket Steam_Networking_Sockets::new_listen_socket(int nSteamConnectVirtualPort, int real_port) +{ + HSteamListenSocket socket_id = get_socket_id(); + if (socket_id == k_HSteamListenSocket_Invalid) ++socket_id; + CSteamID steam_id = settings->get_local_steam_id(); + + auto conn = std::find_if(sbcs->listen_sockets.begin(), sbcs->listen_sockets.end(), [&nSteamConnectVirtualPort,&steam_id](struct Listen_Socket const& conn) { return conn.virtual_port == nSteamConnectVirtualPort && conn.created_by == steam_id;}); + if (conn != sbcs->listen_sockets.end()) return k_HSteamListenSocket_Invalid; + + struct Listen_Socket listen_socket; + listen_socket.socket_id = socket_id; + listen_socket.virtual_port = nSteamConnectVirtualPort; + listen_socket.real_port = real_port; + listen_socket.created_by = steam_id; + sbcs->listen_sockets.push_back(listen_socket); + return socket_id; +} + +ESteamNetworkingConnectionState Steam_Networking_Sockets::convert_status(enum connect_socket_status old_status) +{ + if (old_status == CONNECT_SOCKET_NO_CONNECTION) return k_ESteamNetworkingConnectionState_None; + if (old_status == CONNECT_SOCKET_CONNECTING) return k_ESteamNetworkingConnectionState_Connecting; + if (old_status == CONNECT_SOCKET_NOT_ACCEPTED) return k_ESteamNetworkingConnectionState_Connecting; + if (old_status == CONNECT_SOCKET_CONNECTED) return k_ESteamNetworkingConnectionState_Connected; + if (old_status == CONNECT_SOCKET_CLOSED) return k_ESteamNetworkingConnectionState_ClosedByPeer; + if (old_status == CONNECT_SOCKET_TIMEDOUT) return k_ESteamNetworkingConnectionState_ProblemDetectedLocally; + return k_ESteamNetworkingConnectionState_None; +} + +void Steam_Networking_Sockets::set_steamnetconnectioninfo(std::map::iterator connect_socket, SteamNetConnectionInfo_t *pInfo) +{ + pInfo->m_identityRemote = connect_socket->second.remote_identity; + pInfo->m_nUserData = connect_socket->second.user_data; + pInfo->m_hListenSocket = connect_socket->second.listen_socket_id; + pInfo->m_addrRemote.Clear(); //TODO + if (connect_socket->second.real_port != SNS_DISABLED_PORT) { + pInfo->m_addrRemote.SetIPv4(network->getIP(connect_socket->second.remote_identity.GetSteamID()), connect_socket->first); + } + + pInfo->m_idPOPRemote = 0; + pInfo->m_idPOPRelay = 0; + pInfo->m_eState = convert_status(connect_socket->second.status); + pInfo->m_eEndReason = 0; //TODO + pInfo->m_szEndDebug[0] = 0; + sprintf(pInfo->m_szConnectionDescription, "%u", connect_socket->first); + + //Note some games might not allocate a struct the whole size of SteamNetConnectionInfo_t when calling GetConnectionInfo + //keep this in mind in future interface updates +} + +void Steam_Networking_Sockets::launch_callback(HSteamNetConnection m_hConn, enum connect_socket_status old_status) +{ + auto connect_socket = sbcs->connect_sockets.find(m_hConn); + if (connect_socket == sbcs->connect_sockets.end()) return; + + struct SteamNetConnectionStatusChangedCallback_t data = {}; + data.m_hConn = connect_socket->first; + data.m_eOldState = convert_status(old_status); + set_steamnetconnectioninfo(connect_socket, &data.m_info); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); +} + + +Steam_Networking_Sockets::Steam_Networking_Sockets(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, shared_between_client_server *sbcs) +{ + this->settings = settings; + this->network = network; + this->run_every_runcb = run_every_runcb; + this->callback_results = callback_results; + this->callbacks = callbacks; + + this->created = std::chrono::steady_clock::now(); + + if (!sbcs) { // client + this->sbcs = new shared_between_client_server(); + this->sbcs->used = 0; + } else { // game server + this->sbcs = sbcs; + ++this->sbcs->used; + } + + this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Sockets::steam_callback, this); + this->network->setCallback(CALLBACK_ID_NETWORKING_SOCKETS, settings->get_local_steam_id(), &Steam_Networking_Sockets::steam_callback, this); + this->run_every_runcb->add(&Steam_Networking_Sockets::steam_run_every_runcb, this); + +} + +Steam_Networking_Sockets::~Steam_Networking_Sockets() +{ + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Sockets::steam_callback, this); + this->network->rmCallback(CALLBACK_ID_NETWORKING_SOCKETS, settings->get_local_steam_id(), &Steam_Networking_Sockets::steam_callback, this); + this->run_every_runcb->remove(&Steam_Networking_Sockets::steam_run_every_runcb, this); + + if (this->sbcs) { + if (this->sbcs->used) { + --this->sbcs->used; + } else { + delete this->sbcs; + this->sbcs = nullptr; + } + } +} + +/// Creates a "server" socket that listens for clients to connect to, either by calling +/// ConnectSocketBySteamID or ConnectSocketByIPv4Address. +/// +/// nSteamConnectVirtualPort specifies how clients can connect to this socket using +/// ConnectBySteamID. A negative value indicates that this functionality is +/// disabled and clients must connect by IP address. It's very common for applications +/// to only have one listening socket; in that case, use zero. If you need to open +/// multiple listen sockets and have clients be able to connect to one or the other, then +/// nSteamConnectVirtualPort should be a small integer constant unique to each listen socket +/// you create. +/// +/// In the open-source version of this API, you must pass -1 for nSteamConnectVirtualPort +/// +/// If you want clients to connect to you by your IPv4 addresses using +/// ConnectByIPv4Address, then you must set nPort to be nonzero. Steam will +/// bind a UDP socket to the specified local port, and clients will send packets using +/// ordinary IP routing. It's up to you to take care of NAT, protecting your server +/// from DoS, etc. If you don't need clients to connect to you by IP, then set nPort=0. +/// Use nIP if you wish to bind to a particular local interface. Typically you will use 0, +/// which means to listen on all interfaces, and accept the default outbound IP address. +/// If nPort is zero, then nIP must also be zero. +/// +/// A SocketStatusCallback_t callback when another client attempts a connection. +HSteamListenSocket Steam_Networking_Sockets::CreateListenSocket( int nSteamConnectVirtualPort, uint32 nIP, uint16 nPort ) +{ + PRINT_DEBUG("%i %u %u", nSteamConnectVirtualPort, nIP, nPort); + std::lock_guard lock(global_mutex); + return new_listen_socket(nSteamConnectVirtualPort, nPort); +} + +/// Creates a "server" socket that listens for clients to connect to by +/// calling ConnectByIPAddress, over ordinary UDP (IPv4 or IPv6) +/// +/// You must select a specific local port to listen on and set it +/// the port field of the local address. +/// +/// Usually you wil set the IP portion of the address to zero, (SteamNetworkingIPAddr::Clear()). +/// This means that you will not bind to any particular local interface. In addition, +/// if possible the socket will be bound in "dual stack" mode, which means that it can +/// accept both IPv4 and IPv6 clients. If you wish to bind a particular interface, then +/// set the local address to the appropriate IPv4 or IPv6 IP. +/// +/// When a client attempts to connect, a SteamNetConnectionStatusChangedCallback_t +/// will be posted. The connection will be in the connecting state. +HSteamListenSocket Steam_Networking_Sockets::CreateListenSocketIP( const SteamNetworkingIPAddr &localAddress ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + return new_listen_socket(SNS_DISABLED_PORT, localAddress.m_port); +} + +HSteamListenSocket Steam_Networking_Sockets::CreateListenSocketIP( const SteamNetworkingIPAddr *localAddress ) +{ + PRINT_DEBUG("old1"); + std::lock_guard lock(global_mutex); + return new_listen_socket(SNS_DISABLED_PORT, localAddress->m_port); +} + +HSteamListenSocket Steam_Networking_Sockets::CreateListenSocketIP( const SteamNetworkingIPAddr &localAddress, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + return new_listen_socket(SNS_DISABLED_PORT, localAddress.m_port); +} + +/// Creates a connection and begins talking to a "server" over UDP at the +/// given IPv4 or IPv6 address. The remote host must be listening with a +/// matching call to CreateListenSocketIP on the specified port. +/// +/// A SteamNetConnectionStatusChangedCallback_t callback will be triggered when we start +/// connecting, and then another one on either timeout or successful connection. +/// +/// If the server does not have any identity configured, then their network address +/// will be the only identity in use. Or, the network host may provide a platform-specific +/// identity with or without a valid certificate to authenticate that identity. (These +/// details will be contained in the SteamNetConnectionStatusChangedCallback_t.) It's +/// up to your application to decide whether to allow the connection. +/// +/// By default, all connections will get basic encryption sufficient to prevent +/// casual eavesdropping. But note that without certificates (or a shared secret +/// distributed through some other out-of-band mechanism), you don't have any +/// way of knowing who is actually on the other end, and thus are vulnerable to +/// man-in-the-middle attacks. +HSteamNetConnection Steam_Networking_Sockets::ConnectByIPAddress( const SteamNetworkingIPAddr &address ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + SteamNetworkingIdentity ip_id; + ip_id.SetIPAddr(address); + HSteamNetConnection socket = new_connect_socket(ip_id, SNS_DISABLED_PORT, address.m_port); + send_packet_new_connection(socket); + return socket; +} + +HSteamNetConnection Steam_Networking_Sockets::ConnectByIPAddress( const SteamNetworkingIPAddr *address ) +{ + PRINT_DEBUG("old1"); + std::lock_guard lock(global_mutex); + SteamNetworkingIdentity ip_id; + ip_id.SetIPAddr(*address); + HSteamNetConnection socket = new_connect_socket(ip_id, SNS_DISABLED_PORT, address->m_port); + send_packet_new_connection(socket); + return socket; +} + +HSteamNetConnection Steam_Networking_Sockets::ConnectByIPAddress( const SteamNetworkingIPAddr &address, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) +{ + PRINT_DEBUG("%X", address.GetIPv4()); + std::lock_guard lock(global_mutex); + SteamNetworkingIdentity ip_id; + ip_id.SetIPAddr(address); + HSteamNetConnection socket = new_connect_socket(ip_id, SNS_DISABLED_PORT, address.m_port); + send_packet_new_connection(socket); + return socket; +} + +/// Like CreateListenSocketIP, but clients will connect using ConnectP2P +/// +/// nVirtualPort specifies how clients can connect to this socket using +/// ConnectP2P. It's very common for applications to only have one listening socket; +/// in that case, use zero. If you need to open multiple listen sockets and have clients +/// be able to connect to one or the other, then nVirtualPort should be a small integer (<1000) +/// unique to each listen socket you create. +/// +/// If you use this, you probably want to call ISteamNetworkingUtils::InitializeRelayNetworkAccess() +/// when your app initializes +HSteamListenSocket Steam_Networking_Sockets::CreateListenSocketP2P( int nVirtualPort ) +{ + PRINT_DEBUG("old %i", nVirtualPort); + std::lock_guard lock(global_mutex); + return new_listen_socket(nVirtualPort, SNS_DISABLED_PORT); +} + +HSteamListenSocket Steam_Networking_Sockets::CreateListenSocketP2P( int nVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) +{ + PRINT_DEBUG("%i", nVirtualPort); + //TODO config options + std::lock_guard lock(global_mutex); + return new_listen_socket(nVirtualPort, SNS_DISABLED_PORT); +} + +/// Begin connecting to a server that is identified using a platform-specific identifier. +/// This requires some sort of third party rendezvous service, and will depend on the +/// platform and what other libraries and services you are integrating with. +/// +/// At the time of this writing, there is only one supported rendezvous service: Steam. +/// Set the SteamID (whether "user" or "gameserver") and Steam will determine if the +/// client is online and facilitate a relay connection. Note that all P2P connections on +/// Steam are currently relayed. +/// +/// If you use this, you probably want to call ISteamNetworkingUtils::InitializeRelayNetworkAccess() +/// when your app initializes +HSteamNetConnection Steam_Networking_Sockets::ConnectP2P( const SteamNetworkingIdentity &identityRemote, int nVirtualPort ) +{ + PRINT_DEBUG("old %i", nVirtualPort); + std::lock_guard lock(global_mutex); + + const SteamNetworkingIPAddr *ip = identityRemote.GetIPAddr(); + + if (identityRemote.m_eType == k_ESteamNetworkingIdentityType_SteamID) { + PRINT_DEBUG("%llu", identityRemote.GetSteamID64()); + //steam id identity + } else if (ip) { + PRINT_DEBUG("%u:%u ipv4? %u", ip->GetIPv4(), ip->m_port, ip->IsIPv4()); + //ip addr + } else { + return k_HSteamNetConnection_Invalid; + } + + HSteamNetConnection socket = new_connect_socket(identityRemote, nVirtualPort, SNS_DISABLED_PORT); + send_packet_new_connection(socket); + return socket; +} + +HSteamNetConnection Steam_Networking_Sockets::ConnectP2P( const SteamNetworkingIdentity *identityRemote, int nVirtualPort ) +{ + PRINT_DEBUG("old1"); + return ConnectP2P(*identityRemote, nVirtualPort); +} + +HSteamNetConnection Steam_Networking_Sockets::ConnectP2P( const SteamNetworkingIdentity &identityRemote, int nVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) +{ + PRINT_DEBUG("%i", nVirtualPort); + //TODO config options + return ConnectP2P(identityRemote, nVirtualPort); +} + +/// Creates a connection and begins talking to a remote destination. The remote host +/// must be listening with a matching call to CreateListenSocket. +/// +/// Use ConnectBySteamID to connect using the SteamID (client or game server) as the network address. +/// Use ConnectByIPv4Address to connect by IP address. +/// +/// A SteamNetConnectionStatusChangedCallback_t callback will be triggered when we start connecting, +/// and then another one on timeout or successful connection +//#ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE +HSteamNetConnection Steam_Networking_Sockets::ConnectBySteamID( CSteamID steamIDTarget, int nVirtualPort ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_HSteamNetConnection_Invalid; +} + +//#endif +HSteamNetConnection Steam_Networking_Sockets::ConnectByIPv4Address( uint32 nIP, uint16 nPort ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_HSteamNetConnection_Invalid; +} + + +/// Accept an incoming connection that has been received on a listen socket. +/// +/// When a connection attempt is received (perhaps after a few basic handshake +/// packets have been exchanged to prevent trivial spoofing), a connection interface +/// object is created in the k_ESteamNetworkingConnectionState_Connecting state +/// and a SteamNetConnectionStatusChangedCallback_t is posted. At this point, your +/// application MUST either accept or close the connection. (It may not ignore it.) +/// Accepting the connection will transition it either into the connected state, +/// of the finding route state, depending on the connection type. +/// +/// You should take action within a second or two, because accepting the connection is +/// what actually sends the reply notifying the client that they are connected. If you +/// delay taking action, from the client's perspective it is the same as the network +/// being unresponsive, and the client may timeout the connection attempt. In other +/// words, the client cannot distinguish between a delay caused by network problems +/// and a delay caused by the application. +/// +/// This means that if your application goes for more than a few seconds without +/// processing callbacks (for example, while loading a map), then there is a chance +/// that a client may attempt to connect in that interval and fail due to timeout. +/// +/// If the application does not respond to the connection attempt in a timely manner, +/// and we stop receiving communication from the client, the connection attempt will +/// be timed out locally, transitioning the connection to the +/// k_ESteamNetworkingConnectionState_ProblemDetectedLocally state. The client may also +/// close the connection before it is accepted, and a transition to the +/// k_ESteamNetworkingConnectionState_ClosedByPeer is also possible depending the exact +/// sequence of events. +/// +/// Returns k_EResultInvalidParam if the handle is invalid. +/// Returns k_EResultInvalidState if the connection is not in the appropriate state. +/// (Remember that the connection state could change in between the time that the +/// notification being posted to the queue and when it is received by the application.) +EResult Steam_Networking_Sockets::AcceptConnection( HSteamNetConnection hConn ) +{ + PRINT_DEBUG("%u", hConn); + std::lock_guard lock(global_mutex); + + auto connect_socket = sbcs->connect_sockets.find(hConn); + if (connect_socket == sbcs->connect_sockets.end()) return k_EResultInvalidParam; + if (connect_socket->second.status != CONNECT_SOCKET_NOT_ACCEPTED) return k_EResultInvalidState; + connect_socket->second.status = CONNECT_SOCKET_CONNECTED; + send_packet_new_connection(connect_socket->first); + launch_callback(connect_socket->first, CONNECT_SOCKET_NOT_ACCEPTED); + + return k_EResultOK; +} + + +/// Disconnects from the remote host and invalidates the connection handle. +/// Any unread data on the connection is discarded. +/// +/// nReason is an application defined code that will be received on the other +/// end and recorded (when possible) in backend analytics. The value should +/// come from a restricted range. (See ESteamNetConnectionEnd.) If you don't need +/// to communicate any information to the remote host, and do not want analytics to +/// be able to distinguish "normal" connection terminations from "exceptional" ones, +/// You may pass zero, in which case the generic value of +/// k_ESteamNetConnectionEnd_App_Generic will be used. +/// +/// pszDebug is an optional human-readable diagnostic string that will be received +/// by the remote host and recorded (when possible) in backend analytics. +/// +/// If you wish to put the socket into a "linger" state, where an attempt is made to +/// flush any remaining sent data, use bEnableLinger=true. Otherwise reliable data +/// is not flushed. +/// +/// If the connection has already ended and you are just freeing up the +/// connection interface, the reason code, debug string, and linger flag are +/// ignored. +bool Steam_Networking_Sockets::CloseConnection( HSteamNetConnection hPeer, int nReason, const char *pszDebug, bool bEnableLinger ) +{ + PRINT_DEBUG("%u", hPeer); + std::lock_guard lock(global_mutex); + + auto connect_socket = sbcs->connect_sockets.find(hPeer); + if (connect_socket == sbcs->connect_sockets.end()) return false; + + if (connect_socket->second.status != CONNECT_SOCKET_CLOSED && connect_socket->second.status != CONNECT_SOCKET_TIMEDOUT) { + //TODO send/nReason and pszDebug + Common_Message msg; + msg.set_source_id(connect_socket->second.created_by.ConvertToUint64()); + msg.set_dest_id(connect_socket->second.remote_identity.GetSteamID64()); + msg.set_allocated_networking_sockets(new Networking_Sockets); + msg.mutable_networking_sockets()->set_type(Networking_Sockets::CONNECTION_END); + msg.mutable_networking_sockets()->set_virtual_port(connect_socket->second.virtual_port); + msg.mutable_networking_sockets()->set_real_port(connect_socket->second.real_port); + msg.mutable_networking_sockets()->set_connection_id_from(connect_socket->first); + msg.mutable_networking_sockets()->set_connection_id(connect_socket->second.remote_id); + network->sendTo(&msg, true); + } + + sbcs->connect_sockets.erase(connect_socket); + return true; +} + + +/// Destroy a listen socket, and all the client sockets generated by accepting connections +/// on the listen socket. +/// +/// pszNotifyRemoteReason determines what cleanup actions are performed on the client +/// sockets being destroyed. (See DestroySocket for more details.) +/// +/// Note that if cleanup is requested and you have requested the listen socket bound to a +/// particular local port to facilitate direct UDP/IPv4 connections, then the underlying UDP +/// socket must remain open until all clients have been cleaned up. +bool Steam_Networking_Sockets::CloseListenSocket( HSteamListenSocket hSocket, const char *pszNotifyRemoteReason ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + return false; +} + +/// Destroy a listen socket. All the connections that were accepting on the listen +/// socket are closed ungracefully. +bool Steam_Networking_Sockets::CloseListenSocket( HSteamListenSocket hSocket ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + auto conn = std::find_if(sbcs->listen_sockets.begin(), sbcs->listen_sockets.end(), [&hSocket](struct Listen_Socket const& conn) { return conn.socket_id == hSocket;}); + if (conn == sbcs->listen_sockets.end()) return false; + + std::queue to_close; + + auto socket_conn = std::begin(sbcs->connect_sockets); + while (socket_conn != std::end(sbcs->connect_sockets)) { + if (socket_conn->second.listen_socket_id == hSocket) { + to_close.push(socket_conn->first); + } + + ++socket_conn; + } + + while (to_close.size()) { + CloseConnection(to_close.front(), 0, "", false); + to_close.pop(); + } + + sbcs->listen_sockets.erase(conn); + return true; +} + +/// Set connection user data. Returns false if the handle is invalid. +bool Steam_Networking_Sockets::SetConnectionUserData( HSteamNetConnection hPeer, int64 nUserData ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + auto connect_socket = sbcs->connect_sockets.find(hPeer); + if (connect_socket == sbcs->connect_sockets.end()) return false; + connect_socket->second.user_data = nUserData; + return true; +} + + +/// Fetch connection user data. Returns -1 if handle is invalid +/// or if you haven't set any userdata on the connection. +int64 Steam_Networking_Sockets::GetConnectionUserData( HSteamNetConnection hPeer ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + auto connect_socket = sbcs->connect_sockets.find(hPeer); + if (connect_socket == sbcs->connect_sockets.end()) return -1; + return connect_socket->second.user_data; +} + + +/// Set a name for the connection, used mostly for debugging +void Steam_Networking_Sockets::SetConnectionName( HSteamNetConnection hPeer, const char *pszName ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +/// Fetch connection name. Returns false if handle is invalid +bool Steam_Networking_Sockets::GetConnectionName( HSteamNetConnection hPeer, char *pszName, int nMaxLen ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +/// Send a message to the remote host on the connected socket. +/// +/// eSendType determines the delivery guarantees that will be provided, +/// when data should be buffered, etc. +/// +/// Note that the semantics we use for messages are not precisely +/// the same as the semantics of a standard "stream" socket. +/// (SOCK_STREAM) For an ordinary stream socket, the boundaries +/// between chunks are not considered relevant, and the sizes of +/// the chunks of data written will not necessarily match up to +/// the sizes of the chunks that are returned by the reads on +/// the other end. The remote host might read a partial chunk, +/// or chunks might be coalesced. For the message semantics +/// used here, however, the sizes WILL match. Each send call +/// will match a successful read call on the remote host +/// one-for-one. If you are porting existing stream-oriented +/// code to the semantics of reliable messages, your code should +/// work the same, since reliable message semantics are more +/// strict than stream semantics. The only caveat is related to +/// performance: there is per-message overhead to retain the +/// messages sizes, and so if your code sends many small chunks +/// of data, performance will suffer. Any code based on stream +/// sockets that does not write excessively small chunks will +/// work without any changes. +EResult Steam_Networking_Sockets::SendMessageToConnection( HSteamNetConnection hConn, const void *pData, uint32 cbData, ESteamNetworkingSendType eSendType ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + return k_EResultFail; +} + +/// Send a message to the remote host on the specified connection. +/// +/// nSendFlags determines the delivery guarantees that will be provided, +/// when data should be buffered, etc. E.g. k_nSteamNetworkingSend_Unreliable +/// +/// Note that the semantics we use for messages are not precisely +/// the same as the semantics of a standard "stream" socket. +/// (SOCK_STREAM) For an ordinary stream socket, the boundaries +/// between chunks are not considered relevant, and the sizes of +/// the chunks of data written will not necessarily match up to +/// the sizes of the chunks that are returned by the reads on +/// the other end. The remote host might read a partial chunk, +/// or chunks might be coalesced. For the message semantics +/// used here, however, the sizes WILL match. Each send call +/// will match a successful read call on the remote host +/// one-for-one. If you are porting existing stream-oriented +/// code to the semantics of reliable messages, your code should +/// work the same, since reliable message semantics are more +/// strict than stream semantics. The only caveat is related to +/// performance: there is per-message overhead to retain the +/// message sizes, and so if your code sends many small chunks +/// of data, performance will suffer. Any code based on stream +/// sockets that does not write excessively small chunks will +/// work without any changes. +/// +/// The pOutMessageNumber is an optional pointer to receive the +/// message number assigned to the message, if sending was successful. +/// +/// Returns: +/// - k_EResultInvalidParam: invalid connection handle, or the individual message is too big. +/// (See k_cbMaxSteamNetworkingSocketsMessageSizeSend) +/// - k_EResultInvalidState: connection is in an invalid state +/// - k_EResultNoConnection: connection has ended +/// - k_EResultIgnored: You used k_nSteamNetworkingSend_NoDelay, and the message was dropped because +/// we were not ready to send it. +/// - k_EResultLimitExceeded: there was already too much data queued to be sent. +/// (See k_ESteamNetworkingConfig_SendBufferSize) +EResult Steam_Networking_Sockets::SendMessageToConnection( HSteamNetConnection hConn, const void *pData, uint32 cbData, int nSendFlags, int64 *pOutMessageNumber ) +{ + PRINT_DEBUG("%u, len %u, flags %i", hConn, cbData, nSendFlags); + std::lock_guard lock(global_mutex); + + auto connect_socket = sbcs->connect_sockets.find(hConn); + if (connect_socket == sbcs->connect_sockets.end()) return k_EResultInvalidParam; + if (connect_socket->second.status == CONNECT_SOCKET_CLOSED) return k_EResultNoConnection; + if (connect_socket->second.status == CONNECT_SOCKET_TIMEDOUT) return k_EResultNoConnection; + if (connect_socket->second.status != CONNECT_SOCKET_CONNECTED && connect_socket->second.status != CONNECT_SOCKET_CONNECTING) return k_EResultInvalidState; + + Common_Message msg; + msg.set_source_id(connect_socket->second.created_by.ConvertToUint64()); + msg.set_dest_id(connect_socket->second.remote_identity.GetSteamID64()); + msg.set_allocated_networking_sockets(new Networking_Sockets); + msg.mutable_networking_sockets()->set_type(Networking_Sockets::DATA); + msg.mutable_networking_sockets()->set_virtual_port(connect_socket->second.virtual_port); + msg.mutable_networking_sockets()->set_real_port(connect_socket->second.real_port); + msg.mutable_networking_sockets()->set_connection_id_from(connect_socket->first); + msg.mutable_networking_sockets()->set_connection_id(connect_socket->second.remote_id); + msg.mutable_networking_sockets()->set_data(pData, cbData); + uint64 message_number = connect_socket->second.packet_send_counter; + msg.mutable_networking_sockets()->set_message_number(message_number); + connect_socket->second.packet_send_counter += 1; + + bool reliable = false; + if (nSendFlags & k_nSteamNetworkingSend_Reliable) reliable = true; + if (network->sendTo(&msg, reliable)) { + if (pOutMessageNumber) *pOutMessageNumber = message_number; + return k_EResultOK; + } + + return k_EResultFail; +} + +EResult Steam_Networking_Sockets::SendMessageToConnection( HSteamNetConnection hConn, const void *pData, uint32 cbData, int nSendFlags ) +{ + PRINT_DEBUG("old %u, len %u, flags %i", hConn, cbData, nSendFlags); + return SendMessageToConnection(hConn, pData, cbData, nSendFlags, NULL); +} + +/// Send one or more messages without copying the message payload. +/// This is the most efficient way to send messages. To use this +/// function, you must first allocate a message object using +/// ISteamNetworkingUtils::AllocateMessage. (Do not declare one +/// on the stack or allocate your own.) +/// +/// You should fill in the message payload. You can either let +/// it allocate the buffer for you and then fill in the payload, +/// or if you already have a buffer allocated, you can just point +/// m_pData at your buffer and set the callback to the appropriate function +/// to free it. Note that if you use your own buffer, it MUST remain valid +/// until the callback is executed. And also note that your callback can be +/// invoked at ant time from any thread (perhaps even before SendMessages +/// returns!), so it MUST be fast and threadsafe. +/// +/// You MUST also fill in: +/// - m_conn - the handle of the connection to send the message to +/// - m_nFlags - bitmask of k_nSteamNetworkingSend_xxx flags. +/// +/// All other fields are currently reserved and should not be modified. +/// +/// The library will take ownership of the message structures. They may +/// be modified or become invalid at any time, so you must not read them +/// after passing them to this function. +/// +/// pOutMessageNumberOrResult is an optional array that will receive, +/// for each message, the message number that was assigned to the message +/// if sending was successful. If sending failed, then a negative EResult +/// valid is placed into the array. For example, the array will hold +/// -k_EResultInvalidState if the connection was in an invalid state. +/// See ISteamNetworkingSockets::SendMessageToConnection for possible +/// failure codes. +void Steam_Networking_Sockets::SendMessages( int nMessages, SteamNetworkingMessage_t *const *pMessages, int64 *pOutMessageNumberOrResult ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + for (int i = 0; i < nMessages; ++i) { + int64 out_number = 0; + int result = SendMessageToConnection(pMessages[i]->m_conn, pMessages[i]->m_pData, pMessages[i]->m_cbSize, pMessages[i]->m_nFlags, &out_number); + if (pOutMessageNumberOrResult) { + if (result == k_EResultOK) { + pOutMessageNumberOrResult[i] = out_number; + } else { + pOutMessageNumberOrResult[i] = -result; + } + } + + pMessages[i]->m_pfnFreeData(pMessages[i]); + pMessages[i]->Release(); + } +} + + +/// If Nagle is enabled (its on by default) then when calling +/// SendMessageToConnection the message will be queued up the Nagle time +/// before being sent to merge small messages into the same packet. +/// +/// Call this function to flush any queued messages and send them immediately +/// on the next transmission time (often that means right now). +EResult Steam_Networking_Sockets::FlushMessagesOnConnection( HSteamNetConnection hConn ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EResultOK; +} + +/// Fetch the next available message(s) from the connection, if any. +/// Returns the number of messages returned into your array, up to nMaxMessages. +/// If the connection handle is invalid, -1 is returned. +/// +/// The order of the messages returned in the array is relevant. +/// Reliable messages will be received in the order they were sent (and with the +/// same sizes --- see SendMessageToConnection for on this subtle difference from a stream socket). +/// +/// Unreliable messages may be dropped, or delivered out of order withrespect to +/// each other or with respect to reliable messages. The same unreliable message +/// may be received multiple times. +/// +/// If any messages are returned, you MUST call SteamNetworkingMessage_t::Release() on each +/// of them free up resources after you are done. It is safe to keep the object alive for +/// a little while (put it into some queue, etc), and you may call Release() from any thread. +int Steam_Networking_Sockets::ReceiveMessagesOnConnection( HSteamNetConnection hConn, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) +{ + PRINT_DEBUG("%u %i", hConn, nMaxMessages); + std::lock_guard lock(global_mutex); + if (!ppOutMessages || !nMaxMessages) return 0; + + SteamNetworkingMessage_t *msg = NULL; + int messages = 0; + while (messages < nMaxMessages && (msg = get_steam_message_connection(hConn))) { + ppOutMessages[messages] = msg; + ++messages; + } + + PRINT_DEBUG("messages %u", messages); + return messages; +} + +/// Same as ReceiveMessagesOnConnection, but will return the next message available +/// on any connection that was accepted through the specified listen socket. Examine +/// SteamNetworkingMessage_t::m_conn to know which client connection. +/// +/// Delivery order of messages among different clients is not defined. They may +/// be returned in an order different from what they were actually received. (Delivery +/// order of messages from the same client is well defined, and thus the order of the +/// messages is relevant!) +int Steam_Networking_Sockets::ReceiveMessagesOnListenSocket( HSteamListenSocket hSocket, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) +{ + PRINT_DEBUG("%u %i", hSocket, nMaxMessages); + std::lock_guard lock(global_mutex); + if (!ppOutMessages || !nMaxMessages) return 0; + + SteamNetworkingMessage_t *msg = NULL; + int messages = 0; + + auto socket_conn = std::begin(sbcs->connect_sockets); + while (socket_conn != std::end(sbcs->connect_sockets) && messages < nMaxMessages) { + if (socket_conn->second.listen_socket_id == hSocket) { + while (messages < nMaxMessages && (msg = get_steam_message_connection(socket_conn->first))) { + ppOutMessages[messages] = msg; + ++messages; + } + } + + ++socket_conn; + } + + return messages; +} + +/// Returns basic information about the high-level state of the connection. +bool Steam_Networking_Sockets::GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo_t *pInfo ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!pInfo) return false; + + auto connect_socket = sbcs->connect_sockets.find(hConn); + if (connect_socket == sbcs->connect_sockets.end()) return false; + + set_steamnetconnectioninfo(connect_socket, pInfo); + + //Note some games might not allocate a struct the whole size of SteamNetConnectionInfo_t + //keep this in mind in future interface updates + + return true; +} + +/// Returns a small set of information about the real-time state of the connection +/// and the queue status of each lane. +/// +/// - pStatus may be NULL if the information is not desired. (E.g. you are only interested +/// in the lane information.) +/// - On entry, nLanes specifies the length of the pLanes array. This may be 0 +/// if you do not wish to receive any lane data. It's OK for this to be smaller than +/// the total number of configured lanes. +/// - pLanes points to an array that will receive lane-specific info. It can be NULL +/// if this is not needed. +/// +/// Return value: +/// - k_EResultNoConnection - connection handle is invalid or connection has been closed. +/// - k_EResultInvalidParam - nLanes is bad +EResult Steam_Networking_Sockets::GetConnectionRealTimeStatus( HSteamNetConnection hConn, SteamNetConnectionRealTimeStatus_t *pStatus, int nLanes, SteamNetConnectionRealTimeLaneStatus_t *pLanes ) +{ + PRINT_DEBUG("%s %u %p %i %p", __FUNCTION__, hConn, pStatus, nLanes, pLanes); + std::lock_guard lock(global_mutex); + auto connect_socket = sbcs->connect_sockets.find(hConn); + if (connect_socket == sbcs->connect_sockets.end()) return k_EResultNoConnection; + + if (pStatus) { + pStatus->m_eState = convert_status(connect_socket->second.status); + pStatus->m_nPing = 10; //TODO: calculate real numbers? + pStatus->m_flConnectionQualityLocal = 1.0; + pStatus->m_flConnectionQualityRemote = 1.0; + //TODO: rest + pStatus->m_flOutPacketsPerSec = 0.0; + pStatus->m_flOutBytesPerSec = 0.0; + pStatus->m_flInPacketsPerSec = 0.0; + pStatus->m_flInBytesPerSec = 0.0; + pStatus->m_cbSentUnackedReliable = 0.0; + pStatus->m_usecQueueTime = 0.0; + + //Note some games (volcanoids) might not allocate a struct the whole size of SteamNetworkingQuickConnectionStatus + //keep this in mind in future interface updates + //NOTE: need to implement GetQuickConnectionStatus seperately if this changes. + } + + //TODO: lanes + return k_EResultOK; +} + +/// Fetch the next available message(s) from the socket, if any. +/// Returns the number of messages returned into your array, up to nMaxMessages. +/// If the connection handle is invalid, -1 is returned. +/// +/// The order of the messages returned in the array is relevant. +/// Reliable messages will be received in the order they were sent (and with the +/// same sizes --- see SendMessageToConnection for on this subtle difference from a stream socket). +/// +/// FIXME - We're still debating the exact set of guarantees for unreliable, so this might change. +/// Unreliable messages may not be received. The order of delivery of unreliable messages +/// is NOT specified. They may be received out of order with respect to each other or +/// reliable messages. They may be received multiple times! +/// +/// If any messages are returned, you MUST call Release() to each of them free up resources +/// after you are done. It is safe to keep the object alive for a little while (put it +/// into some queue, etc), and you may call Release() from any thread. +int Steam_Networking_Sockets::ReceiveMessagesOnConnection( HSteamNetConnection hConn, SteamNetworkingMessage001_t **ppOutMessages, int nMaxMessages ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return -1; +} + + +/// Same as ReceiveMessagesOnConnection, but will return the next message available +/// on any client socket that was accepted through the specified listen socket. Examine +/// SteamNetworkingMessage_t::m_conn to know which client connection. +/// +/// Delivery order of messages among different clients is not defined. They may +/// be returned in an order different from what they were actually received. (Delivery +/// order of messages from the same client is well defined, and thus the order of the +/// messages is relevant!) +int Steam_Networking_Sockets::ReceiveMessagesOnListenSocket( HSteamListenSocket hSocket, SteamNetworkingMessage001_t **ppOutMessages, int nMaxMessages ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return -1; +} + + +/// Returns information about the specified connection. +bool Steam_Networking_Sockets::GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo001_t *pInfo ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +/// Returns brief set of connection status that you might want to display +/// to the user in game. +bool Steam_Networking_Sockets::GetQuickConnectionStatus( HSteamNetConnection hConn, SteamNetworkingQuickConnectionStatus *pStats ) +{ + PRINT_DEBUG_ENTRY(); + if (!pStats) + return false; + + return GetConnectionRealTimeStatus(hConn, pStats, 0, NULL) == k_EResultOK; +} + + +/// Returns detailed connection stats in text format. Useful +/// for dumping to a log, etc. +/// +/// Returns: +/// -1 failure (bad connection handle) +/// 0 OK, your buffer was filled in and '\0'-terminated +/// >0 Your buffer was either nullptr, or it was too small and the text got truncated. Try again with a buffer of at least N bytes. +int Steam_Networking_Sockets::GetDetailedConnectionStatus( HSteamNetConnection hConn, char *pszBuf, int cbBuf ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return -1; +} + +/// Returns local IP and port that a listen socket created using CreateListenSocketIP is bound to. +/// +/// An IPv6 address of ::0 means "any IPv4 or IPv6" +/// An IPv6 address of ::ffff:0000:0000 means "any IPv4" +bool Steam_Networking_Sockets::GetListenSocketAddress( HSteamListenSocket hSocket, SteamNetworkingIPAddr *address ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +/// Returns information about the listen socket. +/// +/// *pnIP and *pnPort will be 0 if the socket is set to listen for connections based +/// on SteamID only. If your listen socket accepts connections on IPv4, then both +/// fields will return nonzero, even if you originally passed a zero IP. However, +/// note that the address returned may be a private address (e.g. 10.0.0.x or 192.168.x.x), +/// and may not be reachable by a general host on the Internet. +bool Steam_Networking_Sockets::GetListenSocketInfo( HSteamListenSocket hSocket, uint32 *pnIP, uint16 *pnPort ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + struct Listen_Socket *socket = get_connection_socket(hSocket); + if (!socket) return false; + if (pnIP) *pnIP = 0;//socket->ip; + if (pnPort) *pnPort = 0;//socket->port; + return true; +} + + +/// Create a pair of connections that are talking to each other, e.g. a loopback connection. +/// This is very useful for testing, or so that your client/server code can work the same +/// even when you are running a local "server". +/// +/// The two connections will immediately be placed into the connected state, and no callbacks +/// will be posted immediately. After this, if you close either connection, the other connection +/// will receive a callback, exactly as if they were communicating over the network. You must +/// close *both* sides in order to fully clean up the resources! +/// +/// By default, internal buffers are used, completely bypassing the network, the chopping up of +/// messages into packets, encryption, copying the payload, etc. This means that loopback +/// packets, by default, will not simulate lag or loss. Passing true for bUseNetworkLoopback will +/// cause the socket pair to send packets through the local network loopback device (127.0.0.1) +/// on ephemeral ports. Fake lag and loss are supported in this case, and CPU time is expended +/// to encrypt and decrypt. +/// +/// The SteamID assigned to both ends of the connection will be the SteamID of this interface. +bool Steam_Networking_Sockets::CreateSocketPair( HSteamNetConnection *pOutConnection1, HSteamNetConnection *pOutConnection2, bool bUseNetworkLoopback ) +{ + PRINT_DEBUG("old"); + return CreateSocketPair(pOutConnection1, pOutConnection2, bUseNetworkLoopback, NULL, NULL); +} + +/// Create a pair of connections that are talking to each other, e.g. a loopback connection. +/// This is very useful for testing, or so that your client/server code can work the same +/// even when you are running a local "server". +/// +/// The two connections will immediately be placed into the connected state, and no callbacks +/// will be posted immediately. After this, if you close either connection, the other connection +/// will receive a callback, exactly as if they were communicating over the network. You must +/// close *both* sides in order to fully clean up the resources! +/// +/// By default, internal buffers are used, completely bypassing the network, the chopping up of +/// messages into packets, encryption, copying the payload, etc. This means that loopback +/// packets, by default, will not simulate lag or loss. Passing true for bUseNetworkLoopback will +/// cause the socket pair to send packets through the local network loopback device (127.0.0.1) +/// on ephemeral ports. Fake lag and loss are supported in this case, and CPU time is expended +/// to encrypt and decrypt. +/// +/// If you wish to assign a specific identity to either connection, you may pass a particular +/// identity. Otherwise, if you pass nullptr, the respective connection will assume a generic +/// "localhost" identity. If you use real network loopback, this might be translated to the +/// actual bound loopback port. Otherwise, the port will be zero. +bool Steam_Networking_Sockets::CreateSocketPair( HSteamNetConnection *pOutConnection1, HSteamNetConnection *pOutConnection2, bool bUseNetworkLoopback, const SteamNetworkingIdentity *pIdentity1, const SteamNetworkingIdentity *pIdentity2 ) +{ + PRINT_DEBUG("%u %p %p", bUseNetworkLoopback, pIdentity1, pIdentity2); + std::lock_guard lock(global_mutex); + if (!pOutConnection1 || !pOutConnection1) return false; + + SteamNetworkingIdentity remote_identity; + remote_identity.SetSteamID(settings->get_local_steam_id()); + HSteamNetConnection con1 = new_connect_socket(remote_identity, 0, SNS_DISABLED_PORT, CONNECT_SOCKET_CONNECTED, k_HSteamListenSocket_Invalid, k_HSteamNetConnection_Invalid); + HSteamNetConnection con2 = new_connect_socket(remote_identity, 0, SNS_DISABLED_PORT, CONNECT_SOCKET_CONNECTED, k_HSteamListenSocket_Invalid, con1); + sbcs->connect_sockets[con1].remote_id = con2; + *pOutConnection1 = con1; + *pOutConnection2 = con2; + return true; +} + +/// Configure multiple outbound messages streams ("lanes") on a connection, and +/// control head-of-line blocking between them. Messages within a given lane +/// are always sent in the order they are queued, but messages from different +/// lanes may be sent out of order. Each lane has its own message number +/// sequence. The first message sent on each lane will be assigned the number 1. +/// +/// Each lane has a "priority". Lower priority lanes will only be processed +/// when all higher-priority lanes are empty. The magnitudes of the priority +/// values are not relevant, only their sort order. Higher numeric values +/// take priority over lower numeric values. +/// +/// Each lane also is assigned a weight, which controls the approximate proportion +/// of the bandwidth that will be consumed by the lane, relative to other lanes +/// of the same priority. (This is assuming the lane stays busy. An idle lane +/// does not build up "credits" to be be spent once a message is queued.) +/// This value is only meaningful as a proportion, relative to other lanes with +/// the same priority. For lanes with different priorities, the strict priority +/// order will prevail, and their weights relative to each other are not relevant. +/// Thus, if a lane has a unique priority value, the weight value for that lane is +/// not relevant. +/// +/// Example: 3 lanes, with priorities [ 0, 10, 10 ] and weights [ (NA), 20, 5 ]. +/// Messages sent on the first will always be sent first, before messages in the +/// other two lanes. Its weight value is irrelevant, since there are no other +/// lanes with priority=0. The other two lanes will share bandwidth, with the second +/// and third lanes sharing bandwidth using a ratio of approximately 4:1. +/// (The weights [ NA, 4, 1 ] would be equivalent.) +/// +/// Notes: +/// - At the time of this writing, some code has performance cost that is linear +/// in the number of lanes, so keep the number of lanes to an absolute minimum. +/// 3 or so is fine; >8 is a lot. The max number of lanes on Steam is 255, +/// which is a very large number and not recommended! If you are compiling this +/// library from source, see STEAMNETWORKINGSOCKETS_MAX_LANES.) +/// - Lane priority values may be any int. Their absolute value is not relevant, +/// only the order matters. +/// - Weights must be positive, and due to implementation details, they are restricted +/// to 16-bit values. The absolute magnitudes don't matter, just the proportions. +/// - Messages sent on a lane index other than 0 have a small overhead on the wire, +/// so for maximum wire efficiency, lane 0 should be the "most common" lane, regardless +/// of priorities or weights. +/// - A connection has a single lane by default. Calling this function with +/// nNumLanes=1 is legal, but pointless, since the priority and weight values are +/// irrelevant in that case. +/// - You may reconfigure connection lanes at any time, however reducing the number of +/// lanes is not allowed. +/// - Reconfiguring lanes might restart any bandwidth sharing balancing. Usually you +/// will call this function once, near the start of the connection, perhaps after +/// exchanging a few messages. +/// - To assign all lanes the same priority, you may use pLanePriorities=NULL. +/// - If you wish all lanes with the same priority to share bandwidth equally (or +/// if no two lanes have the same priority value, and thus priority values are +/// irrelevant), you may use pLaneWeights=NULL +/// - Priorities and weights determine the order that messages are SENT on the wire. +/// There are NO GUARANTEES on the order that messages are RECEIVED! Due to packet +/// loss, out-of-order delivery, and subtle details of packet serialization, messages +/// might still be received slightly out-of-order! The *only* strong guarantee is that +/// *reliable* messages on the *same lane* will be delivered in the order they are sent. +/// - Each host configures the lanes for the packets they send; the lanes for the flow +/// in one direction are completely unrelated to the lanes in the opposite direction. +/// +/// Return value: +/// - k_EResultNoConnection - bad hConn +/// - k_EResultInvalidParam - Invalid number of lanes, bad weights, or you tried to reduce the number of lanes +/// - k_EResultInvalidState - Connection is already dead, etc +/// +/// See also: +/// SteamNetworkingMessage_t::m_idxLane +EResult Steam_Networking_Sockets::ConfigureConnectionLanes( HSteamNetConnection hConn, int nNumLanes, const int *pLanePriorities, const uint16 *pLaneWeights ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + auto connect_socket = sbcs->connect_sockets.find(hConn); + if (connect_socket == sbcs->connect_sockets.end()) return k_EResultNoConnection; + //TODO + return k_EResultOK; +} + + +/// Get the identity assigned to this interface. +/// E.g. on Steam, this is the user's SteamID, or for the gameserver interface, the SteamID assigned +/// to the gameserver. Returns false and sets the result to an invalid identity if we don't know +/// our identity yet. (E.g. GameServer has not logged in. On Steam, the user will know their SteamID +/// even if they are not signed into Steam.) +bool Steam_Networking_Sockets::GetIdentity( SteamNetworkingIdentity *pIdentity ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!pIdentity) return false; + pIdentity->SetSteamID(settings->get_local_steam_id()); + return true; +} + +/// Indicate our desire to be ready participate in authenticated communications. +/// If we are currently not ready, then steps will be taken to obtain the necessary +/// certificates. (This includes a certificate for us, as well as any CA certificates +/// needed to authenticate peers.) +/// +/// You can call this at program init time if you know that you are going to +/// be making authenticated connections, so that we will be ready immediately when +/// those connections are attempted. (Note that essentially all connections require +/// authentication, with the exception of ordinary UDP connections with authentication +/// disabled using k_ESteamNetworkingConfig_IP_AllowWithoutAuth.) If you don't call +/// this function, we will wait until a feature is utilized that that necessitates +/// these resources. +/// +/// You can also call this function to force a retry, if failure has occurred. +/// Once we make an attempt and fail, we will not automatically retry. +/// In this respect, the behavior of the system after trying and failing is the same +/// as before the first attempt: attempting authenticated communication or calling +/// this function will call the system to attempt to acquire the necessary resources. +/// +/// You can use GetAuthenticationStatus or listen for SteamNetAuthenticationStatus_t +/// to monitor the status. +/// +/// Returns the current value that would be returned from GetAuthenticationStatus. +ESteamNetworkingAvailability Steam_Networking_Sockets::InitAuthentication() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_ESteamNetworkingAvailability_Current; +} + +/// Query our readiness to participate in authenticated communications. A +/// SteamNetAuthenticationStatus_t callback is posted any time this status changes, +/// but you can use this function to query it at any time. +/// +/// The value of SteamNetAuthenticationStatus_t::m_eAvail is returned. If you only +/// want this high level status, you can pass NULL for pDetails. If you want further +/// details, pass non-NULL to receive them. +ESteamNetworkingAvailability Steam_Networking_Sockets::GetAuthenticationStatus( SteamNetAuthenticationStatus_t *pDetails ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_ESteamNetworkingAvailability_Current; +} + +/// Create a new poll group. +/// +/// You should destroy the poll group when you are done using DestroyPollGroup +HSteamNetPollGroup Steam_Networking_Sockets::CreatePollGroup() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + static HSteamNetPollGroup poll_group_counter; + ++poll_group_counter; + + HSteamNetPollGroup poll_group_number = poll_group_counter; + sbcs->poll_groups[poll_group_number] = std::list(); + return poll_group_number; +} + +/// Destroy a poll group created with CreatePollGroup(). +/// +/// If there are any connections in the poll group, they are removed from the group, +/// and left in a state where they are not part of any poll group. +/// Returns false if passed an invalid poll group handle. +bool Steam_Networking_Sockets::DestroyPollGroup( HSteamNetPollGroup hPollGroup ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + auto group = sbcs->poll_groups.find(hPollGroup); + if (group == sbcs->poll_groups.end()) { + return false; + } + + for (auto c : group->second) { + auto connect_socket = sbcs->connect_sockets.find(c); + if (connect_socket != sbcs->connect_sockets.end()) { + connect_socket->second.poll_group = k_HSteamNetPollGroup_Invalid; + } + } + + sbcs->poll_groups.erase(group); + return true; +} + +/// Assign a connection to a poll group. Note that a connection may only belong to a +/// single poll group. Adding a connection to a poll group implicitly removes it from +/// any other poll group it is in. +/// +/// You can pass k_HSteamNetPollGroup_Invalid to remove a connection from its current +/// poll group without adding it to a new poll group. +/// +/// If there are received messages currently pending on the connection, an attempt +/// is made to add them to the queue of messages for the poll group in approximately +/// the order that would have applied if the connection was already part of the poll +/// group at the time that the messages were received. +/// +/// Returns false if the connection handle is invalid, or if the poll group handle +/// is invalid (and not k_HSteamNetPollGroup_Invalid). +bool Steam_Networking_Sockets::SetConnectionPollGroup( HSteamNetConnection hConn, HSteamNetPollGroup hPollGroup ) +{ + PRINT_DEBUG("%u %u", hConn, hPollGroup); + std::lock_guard lock(global_mutex); + auto connect_socket = sbcs->connect_sockets.find(hConn); + if (connect_socket == sbcs->connect_sockets.end()) { + return false; + } + + auto group = sbcs->poll_groups.find(hPollGroup); + if (group == sbcs->poll_groups.end() && hPollGroup != k_HSteamNetPollGroup_Invalid) { + return false; + } + + HSteamNetPollGroup old_poll_group = connect_socket->second.poll_group; + if (old_poll_group != k_HSteamNetPollGroup_Invalid) { + auto group = sbcs->poll_groups.find(hPollGroup); + if (group != sbcs->poll_groups.end()) { + group->second.remove(hConn); + } + } + + connect_socket->second.poll_group = hPollGroup; + if (hPollGroup == k_HSteamNetPollGroup_Invalid) { + return true; + } + + group->second.push_back(hConn); + return true; +} + +/// Same as ReceiveMessagesOnConnection, but will return the next messages available +/// on any connection in the poll group. Examine SteamNetworkingMessage_t::m_conn +/// to know which connection. (SteamNetworkingMessage_t::m_nConnUserData might also +/// be useful.) +/// +/// Delivery order of messages among different connections will usually match the +/// order that the last packet was received which completed the message. But this +/// is not a strong guarantee, especially for packets received right as a connection +/// is being assigned to poll group. +/// +/// Delivery order of messages on the same connection is well defined and the +/// same guarantees are present as mentioned in ReceiveMessagesOnConnection. +/// (But the messages are not grouped by connection, so they will not necessarily +/// appear consecutively in the list; they may be interleaved with messages for +/// other connections.) +int Steam_Networking_Sockets::ReceiveMessagesOnPollGroup( HSteamNetPollGroup hPollGroup, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) +{ + PRINT_DEBUG("%u %i", hPollGroup, nMaxMessages); + std::lock_guard lock(global_mutex); + auto group = sbcs->poll_groups.find(hPollGroup); + if (group == sbcs->poll_groups.end()) { + return 0; + } + + SteamNetworkingMessage_t *msg = NULL; + int messages = 0; + + for (auto c : group->second) { + while (messages < nMaxMessages && (msg = get_steam_message_connection(c))) { + ppOutMessages[messages] = msg; + ++messages; + } + } + + PRINT_DEBUG("out %i", messages); + return messages; +} + + +//#ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE + +// +// Clients connecting to dedicated servers hosted in a data center, +// using central-authority-granted tickets. +// + +/// Called when we receive a ticket from our central matchmaking system. Puts the +/// ticket into a persistent cache, and optionally returns the parsed ticket. +/// +/// See stamdatagram_ticketgen.h for more details. +bool Steam_Networking_Sockets::ReceivedRelayAuthTicket( const void *pvTicket, int cbTicket, SteamDatagramRelayAuthTicket *pOutParsedTicket ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +/// Search cache for a ticket to talk to the server on the specified virtual port. +/// If found, returns the number of second until the ticket expires, and optionally +/// the complete cracked ticket. Returns 0 if we don't have a ticket. +/// +/// Typically this is useful just to confirm that you have a ticket, before you +/// call ConnectToHostedDedicatedServer to connect to the server. +int Steam_Networking_Sockets::FindRelayAuthTicketForServer( CSteamID steamID, int nVirtualPort, SteamDatagramRelayAuthTicket *pOutParsedTicket ) +{ + PRINT_DEBUG("old"); + return 0; +} + +/// Search cache for a ticket to talk to the server on the specified virtual port. +/// If found, returns the number of seconds until the ticket expires, and optionally +/// the complete cracked ticket. Returns 0 if we don't have a ticket. +/// +/// Typically this is useful just to confirm that you have a ticket, before you +/// call ConnectToHostedDedicatedServer to connect to the server. +int Steam_Networking_Sockets::FindRelayAuthTicketForServer( const SteamNetworkingIdentity *identityGameServer, int nVirtualPort, SteamDatagramRelayAuthTicket *pOutParsedTicket ) +{ + PRINT_DEBUG("old1"); + return 0; +} + +int Steam_Networking_Sockets::FindRelayAuthTicketForServer( const SteamNetworkingIdentity &identityGameServer, int nVirtualPort, SteamDatagramRelayAuthTicket *pOutParsedTicket ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +/// Client call to connect to a server hosted in a Valve data center, on the specified virtual +/// port. You must have placed a ticket for this server into the cache, or else this connect attempt will fail! +/// +/// You may wonder why tickets are stored in a cache, instead of simply being passed as an argument +/// here. The reason is to make reconnection to a gameserver robust, even if the client computer loses +/// connection to Steam or the central backend, or the app is restarted or crashes, etc. +/// +/// If you use this, you probably want to call ISteamNetworkingUtils::InitializeRelayNetworkAccess() +/// when your app initializes +HSteamNetConnection Steam_Networking_Sockets::ConnectToHostedDedicatedServer( const SteamNetworkingIdentity &identityTarget, int nVirtualPort ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + return k_HSteamListenSocket_Invalid; +} + +HSteamNetConnection Steam_Networking_Sockets::ConnectToHostedDedicatedServer( const SteamNetworkingIdentity *identityTarget, int nVirtualPort ) +{ + PRINT_DEBUG("old1"); + std::lock_guard lock(global_mutex); + return k_HSteamListenSocket_Invalid; +} + +/// Client call to connect to a server hosted in a Valve data center, on the specified virtual +/// port. You should have received a ticket for this server, or else this connect call will fail! +/// +/// You may wonder why tickets are stored in a cache, instead of simply being passed as an argument +/// here. The reason is to make reconnection to a gameserver robust, even if the client computer loses +/// connection to Steam or the central backend, or the app is restarted or crashes, etc. +HSteamNetConnection Steam_Networking_Sockets::ConnectToHostedDedicatedServer( CSteamID steamIDTarget, int nVirtualPort ) +{ + PRINT_DEBUG("older"); + std::lock_guard lock(global_mutex); + return k_HSteamListenSocket_Invalid; +} + +HSteamNetConnection Steam_Networking_Sockets::ConnectToHostedDedicatedServer( const SteamNetworkingIdentity &identityTarget, int nVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_HSteamListenSocket_Invalid; +} + +// +// Servers hosted in Valve data centers +// + +/// Returns the value of the SDR_LISTEN_PORT environment variable. +uint16 Steam_Networking_Sockets::GetHostedDedicatedServerPort() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + //TODO? + return 27054; +} + + +/// If you are running in a production data center, this will return the data +/// center code. Returns 0 otherwise. +SteamNetworkingPOPID Steam_Networking_Sockets::GetHostedDedicatedServerPOPID() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +/// Return info about the hosted server. You will need to send this information to your +/// backend, and put it in tickets, so that the relays will know how to forward traffic from +/// clients to your server. See SteamDatagramRelayAuthTicket for more info. +/// +/// NOTE ABOUT DEVELOPMENT ENVIRONMENTS: +/// In production in our data centers, these parameters are configured via environment variables. +/// In development, the only one you need to set is SDR_LISTEN_PORT, which is the local port you +/// want to listen on. Furthermore, if you are running your server behind a corporate firewall, +/// you probably will not be able to put the routing information returned by this function into +/// tickets. Instead, it should be a public internet address that the relays can use to send +/// data to your server. So you might just end up hardcoding a public address and setup port +/// forwarding on your corporate firewall. In that case, the port you put into the ticket +/// needs to be the public-facing port opened on your firewall, if it is different from the +/// actual server port. +/// +/// This function will fail if SteamDatagramServer_Init has not been called. +/// +/// Returns false if the SDR_LISTEN_PORT environment variable is not set. +bool Steam_Networking_Sockets::GetHostedDedicatedServerAddress001( SteamDatagramHostedAddress *pRouting ) +{ + PRINT_DEBUG("%p", pRouting); + return GetHostedDedicatedServerAddress(pRouting) == k_EResultOK; +} + +/// Return info about the hosted server. This contains the PoPID of the server, +/// and opaque routing information that can be used by the relays to send traffic +/// to your server. +/// +/// You will need to send this information to your backend, and put it in tickets, +/// so that the relays will know how to forward traffic from +/// clients to your server. See SteamDatagramRelayAuthTicket for more info. +/// +/// Also, note that the routing information is contained in SteamDatagramGameCoordinatorServerLogin, +/// so if possible, it's preferred to use GetGameCoordinatorServerLogin to send this info +/// to your game coordinator service, and also login securely at the same time. +/// +/// On a successful exit, k_EResultOK is returned +/// +/// Unsuccessful exit: +/// - Something other than k_EResultOK is returned. +/// - k_EResultInvalidState: We are not configured to listen for SDR (SDR_LISTEN_SOCKET +/// is not set.) +/// - k_EResultPending: we do not (yet) have the authentication information needed. +/// (See GetAuthenticationStatus.) If you use environment variables to pre-fetch +/// the network config, this data should always be available immediately. +/// - A non-localized diagnostic debug message will be placed in m_data that describes +/// the cause of the failure. +/// +/// NOTE: The returned blob is not encrypted. Send it to your backend, but don't +/// directly share it with clients. +EResult Steam_Networking_Sockets::GetHostedDedicatedServerAddress( SteamDatagramHostedAddress *pRouting ) +{ + PRINT_DEBUG("%p", pRouting); + std::lock_guard lock(global_mutex); + pRouting->SetDevAddress(network->getOwnIP(), 27054); + return k_EResultOK; +} + +/// Create a listen socket on the specified virtual port. The physical UDP port to use +/// will be determined by the SDR_LISTEN_PORT environment variable. If a UDP port is not +/// configured, this call will fail. +/// +/// Note that this call MUST be made through the SteamNetworkingSocketsGameServer() interface +HSteamListenSocket Steam_Networking_Sockets::CreateHostedDedicatedServerListenSocket( int nVirtualPort ) +{ + PRINT_DEBUG("old %i", nVirtualPort); + std::lock_guard lock(global_mutex); + return new_listen_socket(nVirtualPort, SNS_DISABLED_PORT); +} + +/// Create a listen socket on the specified virtual port. The physical UDP port to use +/// will be determined by the SDR_LISTEN_PORT environment variable. If a UDP port is not +/// configured, this call will fail. +/// +/// Note that this call MUST be made through the SteamGameServerNetworkingSockets() interface +/// +/// If you need to set any initial config options, pass them here. See +/// SteamNetworkingConfigValue_t for more about why this is preferable to +/// setting the options "immediately" after creation. +HSteamListenSocket Steam_Networking_Sockets::CreateHostedDedicatedServerListenSocket( int nVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) +{ + PRINT_DEBUG("old %i", nVirtualPort); + //TODO config options + std::lock_guard lock(global_mutex); + return new_listen_socket(nVirtualPort, SNS_DISABLED_PORT); +} + + +//#endif // #ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE + +// +// Gets some debug text from the connection +// +bool Steam_Networking_Sockets::GetConnectionDebugText( HSteamNetConnection hConn, char *pOut, int nOutCCH ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +// +// Set and get configuration values, see ESteamNetworkingConfigurationValue for individual descriptions. +// +// Returns the value or -1 is eConfigValue is invalid +int32 Steam_Networking_Sockets::GetConfigurationValue( ESteamNetworkingConfigurationValue eConfigValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return -1; +} + +// Returns true if successfully set +bool Steam_Networking_Sockets::SetConfigurationValue( ESteamNetworkingConfigurationValue eConfigValue, int32 nValue ) +{ + PRINT_DEBUG("%i: %i", eConfigValue, nValue); + std::lock_guard lock(global_mutex); + return true; +} + + +// Return the name of an int configuration value, or NULL if config value isn't known +const char* Steam_Networking_Sockets::GetConfigurationValueName( ESteamNetworkingConfigurationValue eConfigValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return NULL; +} + + +// +// Set and get configuration strings, see ESteamNetworkingConfigurationString for individual descriptions. +// +// Get the configuration string, returns length of string needed if pDest is nullpr or destSize is 0 +// returns -1 if the eConfigValue is invalid +int32 Steam_Networking_Sockets::GetConfigurationString( ESteamNetworkingConfigurationString eConfigString, char *pDest, int32 destSize ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return -1; +} + +bool Steam_Networking_Sockets::SetConfigurationString( ESteamNetworkingConfigurationString eConfigString, const char *pString ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +// Return the name of a string configuration value, or NULL if config value isn't known +const char* Steam_Networking_Sockets::GetConfigurationStringName( ESteamNetworkingConfigurationString eConfigString ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return NULL; +} + + +// +// Set and get configuration values, see ESteamNetworkingConnectionConfigurationValue for individual descriptions. +// +// Returns the value or -1 is eConfigValue is invalid +int32 Steam_Networking_Sockets::GetConnectionConfigurationValue( HSteamNetConnection hConn, ESteamNetworkingConnectionConfigurationValue eConfigValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return -1; +} + +// Returns true if successfully set +bool Steam_Networking_Sockets::SetConnectionConfigurationValue( HSteamNetConnection hConn, ESteamNetworkingConnectionConfigurationValue eConfigValue, int32 nValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +/// Generate an authentication blob that can be used to securely login with +/// your backend, using SteamDatagram_ParseHostedServerLogin. (See +/// steamdatagram_gamecoordinator.h) +/// +/// Before calling the function: +/// - Populate the app data in pLoginInfo (m_cbAppData and m_appData). You can leave +/// all other fields uninitialized. +/// - *pcbSignedBlob contains the size of the buffer at pBlob. (It should be +/// at least k_cbMaxSteamDatagramGameCoordinatorServerLoginSerialized.) +/// +/// On a successful exit: +/// - k_EResultOK is returned +/// - All of the remaining fields of pLoginInfo will be filled out. +/// - *pcbSignedBlob contains the size of the serialized blob that has been +/// placed into pBlob. +/// +/// Unsuccessful exit: +/// - Something other than k_EResultOK is returned. +/// - k_EResultNotLoggedOn: you are not logged in (yet) +/// - See GetHostedDedicatedServerAddress for more potential failure return values. +/// - A non-localized diagnostic debug message will be placed in pBlob that describes +/// the cause of the failure. +/// +/// This works by signing the contents of the SteamDatagramGameCoordinatorServerLogin +/// with the cert that is issued to this server. In dev environments, it's OK if you do +/// not have a cert. (You will need to enable insecure dev login in SteamDatagram_ParseHostedServerLogin.) +/// Otherwise, you will need a signed cert. +/// +/// NOTE: The routing blob returned here is not encrypted. Send it to your backend +/// and don't share it directly with clients. +EResult Steam_Networking_Sockets::GetGameCoordinatorServerLogin( SteamDatagramGameCoordinatorServerLogin *pLoginInfo, int *pcbSignedBlob, void *pBlob ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EResultFail; +} + +// +// Relayed connections using custom signaling protocol +// +// This is used if you have your own method of sending out-of-band +// signaling / rendezvous messages through a mutually trusted channel. +// + +/// Create a P2P "client" connection that does signaling over a custom +/// rendezvous/signaling channel. +/// +/// pSignaling points to a new object that you create just for this connection. +/// It must stay valid until Release() is called. Once you pass the +/// object to this function, it assumes ownership. Release() will be called +/// from within the function call if the call fails. Furthermore, until Release() +/// is called, you should be prepared for methods to be invoked on your +/// object from any thread! You need to make sure your object is threadsafe! +/// Furthermore, you should make sure that dispatching the methods is done +/// as quickly as possible. +/// +/// This function will immediately construct a connection in the "connecting" +/// state. Soon after (perhaps before this function returns, perhaps in another thread), +/// the connection will begin sending signaling messages by calling +/// ISteamNetworkingConnectionCustomSignaling::SendSignal. +/// +/// When the remote peer accepts the connection (See +/// ISteamNetworkingCustomSignalingRecvContext::OnConnectRequest), +/// it will begin sending signaling messages. When these messages are received, +/// you can pass them to the connection using ReceivedP2PCustomSignal. +/// +/// If you know the identity of the peer that you expect to be on the other end, +/// you can pass their identity to improve debug output or just detect bugs. +/// If you don't know their identity yet, you can pass NULL, and their +/// identity will be established in the connection handshake. +/// +/// If you use this, you probably want to call ISteamNetworkingUtils::InitRelayNetworkAccess() +/// when your app initializes +/// +/// If you need to set any initial config options, pass them here. See +/// SteamNetworkingConfigValue_t for more about why this is preferable to +/// setting the options "immediately" after creation. +HSteamNetConnection Steam_Networking_Sockets::ConnectP2PCustomSignaling( ISteamNetworkingConnectionCustomSignaling *pSignaling, const SteamNetworkingIdentity *pPeerIdentity, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + //return ConnectP2PCustomSignaling(pSignaling, pPeerIdentity, 0, nOptions, pOptions); + return k_HSteamNetConnection_Invalid; +} + +//HSteamNetConnection ConnectP2PCustomSignaling( ISteamNetworkingConnectionCustomSignaling *pSignaling, const SteamNetworkingIdentity *pPeerIdentity, int nRemoteVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) +HSteamNetConnection Steam_Networking_Sockets::ConnectP2PCustomSignaling( ISteamNetworkingConnectionSignaling *pSignaling, const SteamNetworkingIdentity *pPeerIdentity, int nRemoteVirtualPort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_HSteamNetConnection_Invalid; +} + +/// Called when custom signaling has received a message. When your +/// signaling channel receives a message, it should save off whatever +/// routing information was in the envelope into the context object, +/// and then pass the payload to this function. +/// +/// A few different things can happen next, depending on the message: +/// +/// - If the signal is associated with existing connection, it is dealt +/// with immediately. If any replies need to be sent, they will be +/// dispatched using the ISteamNetworkingConnectionCustomSignaling +/// associated with the connection. +/// - If the message represents a connection request (and the request +/// is not redundant for an existing connection), a new connection +/// will be created, and ReceivedConnectRequest will be called on your +/// context object to determine how to proceed. +/// - Otherwise, the message is for a connection that does not +/// exist (anymore). In this case, we *may* call SendRejectionReply +/// on your context object. +/// +/// In any case, we will not save off pContext or access it after this +/// function returns. +/// +/// Returns true if the message was parsed and dispatched without anything +/// unusual or suspicious happening. Returns false if there was some problem +/// with the message that prevented ordinary handling. (Debug output will +/// usually have more information.) +/// +/// If you expect to be using relayed connections, then you probably want +/// to call ISteamNetworkingUtils::InitRelayNetworkAccess() when your app initializes +bool Steam_Networking_Sockets::ReceivedP2PCustomSignal( const void *pMsg, int cbMsg, ISteamNetworkingCustomSignalingRecvContext *pContext ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + return false; +} + +bool Steam_Networking_Sockets::ReceivedP2PCustomSignal( const void *pMsg, int cbMsg, ISteamNetworkingSignalingRecvContext *pContext ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +// +// Certificate provision by the application. On Steam, we normally handle all this automatically +// and you will not need to use these advanced functions. +// + +/// Get blob that describes a certificate request. You can send this to your game coordinator. +/// Upon entry, *pcbBlob should contain the size of the buffer. On successful exit, it will +/// return the number of bytes that were populated. You can pass pBlob=NULL to query for the required +/// size. (256 bytes is a very conservative estimate.) +/// +/// Pass this blob to your game coordinator and call SteamDatagram_CreateCert. +bool Steam_Networking_Sockets::GetCertificateRequest( int *pcbBlob, void *pBlob, SteamNetworkingErrMsg &errMsg ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +/// Set the certificate. The certificate blob should be the output of +/// SteamDatagram_CreateCert. +bool Steam_Networking_Sockets::SetCertificate( const void *pCertificate, int cbCertificate, SteamNetworkingErrMsg &errMsg ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +/// Reset the identity associated with this instance. +/// Any open connections are closed. Any previous certificates, etc are discarded. +/// You can pass a specific identity that you want to use, or you can pass NULL, +/// in which case the identity will be invalid until you set it using SetCertificate +/// +/// NOTE: This function is not actually supported on Steam! It is included +/// for use on other platforms where the active user can sign out and +/// a new user can sign in. +void Steam_Networking_Sockets::ResetIdentity( const SteamNetworkingIdentity *pIdentity ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +// +// "FakeIP" system. +// +// A FakeIP is essentially a temporary, arbitrary identifier that +// happens to be a valid IPv4 address. The purpose of this system is to make it +// easy to integrate with existing code that identifies hosts using IPv4 addresses. +// The FakeIP address will never actually be used to send or receive any packets +// on the Internet, it is strictly an identifier. +// +// FakeIP addresses are designed to (hopefully) pass through existing code as +// transparently as possible, while conflicting with "real" addresses that might +// be in use on networks (both the Internet and LANs) in the same code as little +// as possible. At the time this comment is being written, they come from the +// 169.254.0.0/16 range, and the port number will always be >1024. HOWEVER, +// this is subject to change! Do not make assumptions about these addresses, +// or your code might break in the future. In particular, you should use +// functions such as ISteamNetworkingUtils::IsFakeIP to determine if an IP +// address is a "fake" one used by this system. +// + +/// Begin asynchronous process of allocating a fake IPv4 address that other +/// peers can use to contact us via P2P. IP addresses returned by this +/// function are globally unique for a given appid. +/// +/// nNumPorts is the numbers of ports you wish to reserve. This is useful +/// for the same reason that listening on multiple UDP ports is useful for +/// different types of traffic. Because these allocations come from a global +/// namespace, there is a relatively strict limit on the maximum number of +/// ports you may request. (At the time of this writing, the limit is 4.) +/// The Port assignments are *not* guaranteed to have any particular order +/// or relationship! Do *not* assume they are contiguous, even though that +/// may often occur in practice. +/// +/// Returns false if a request was already in progress, true if a new request +/// was started. A SteamNetworkingFakeIPResult_t will be posted when the request +/// completes. +/// +/// For gameservers, you *must* call this after initializing the SDK but before +/// beginning login. Steam needs to know in advance that FakeIP will be used. +/// Everywhere your public IP would normally appear (such as the server browser) will be +/// replaced by the FakeIP, and the fake port at index 0. The request is actually queued +/// until the logon completes, so you must not wait until the allocation completes +/// before logging in. Except for trivial failures that can be detected locally +/// (e.g. invalid parameter), a SteamNetworkingFakeIPResult_t callback (whether success or +/// failure) will not be posted until after we have logged in. Furthermore, it is assumed +/// that FakeIP allocation is essential for your application to function, and so failure +/// will not be reported until *several* retries have been attempted. This process may +/// last several minutes. It is *highly* recommended to treat failure as fatal. +/// +/// To communicate using a connection-oriented (TCP-style) API: +/// - Server creates a listen socket using CreateListenSocketP2PFakeIP +/// - Client connects using ConnectByIPAddress, passing in the FakeIP address. +/// - The connection will behave mostly like a P2P connection. The identities +/// that appear in SteamNetConnectionInfo_t will be the FakeIP identity until +/// we know the real identity. Then it will be the real identity. If the +/// SteamNetConnectionInfo_t::m_addrRemote is valid, it will be a real IPv4 +/// address of a NAT-punched connection. Otherwise, it will not be valid. +/// +/// To communicate using an ad-hoc sendto/recv from (UDP-style) API, +/// use CreateFakeUDPPort. +bool Steam_Networking_Sockets::BeginAsyncRequestFakeIP( int nNumPorts ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +/// Return info about the FakeIP and port(s) that we have been assigned, +/// if any. idxFirstPort is currently reserved and must be zero. +/// Make sure and check SteamNetworkingFakeIPResult_t::m_eResult +void Steam_Networking_Sockets::GetFakeIP( int idxFirstPort, SteamNetworkingFakeIPResult_t *pInfo ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +/// Create a listen socket that will listen for P2P connections sent +/// to our FakeIP. A peer can initiate connections to this listen +/// socket by calling ConnectByIPAddress. +/// +/// idxFakePort refers to the *index* of the fake port requested, +/// not the actual port number. For example, pass 0 to refer to the +/// first port in the reservation. You must call this only after calling +/// BeginAsyncRequestFakeIP. However, you do not need to wait for the +/// request to complete before creating the listen socket. +HSteamListenSocket Steam_Networking_Sockets::CreateListenSocketP2PFakeIP( int idxFakePort, int nOptions, const SteamNetworkingConfigValue_t *pOptions ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_HSteamListenSocket_Invalid; +} + +/// If the connection was initiated using the "FakeIP" system, then we +/// we can get an IP address for the remote host. If the remote host had +/// a global FakeIP at the time the connection was established, this +/// function will return that global IP. Otherwise, a FakeIP that is +/// unique locally will be allocated from the local FakeIP address space, +/// and that will be returned. +/// +/// The allocation of local FakeIPs attempts to assign addresses in +/// a consistent manner. If multiple connections are made to the +/// same remote host, they *probably* will return the same FakeIP. +/// However, since the namespace is limited, this cannot be guaranteed. +/// +/// On failure, returns: +/// - k_EResultInvalidParam: invalid connection handle +/// - k_EResultIPNotFound: This connection wasn't made using FakeIP system +EResult Steam_Networking_Sockets::GetRemoteFakeIPForConnection( HSteamNetConnection hConn, SteamNetworkingIPAddr *pOutAddr ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EResultNone; +} + +/// Get an interface that can be used like a UDP port to send/receive +/// datagrams to a FakeIP address. This is intended to make it easy +/// to port existing UDP-based code to take advantage of SDR. +/// +/// idxFakeServerPort refers to the *index* of the port allocated using +/// BeginAsyncRequestFakeIP and is used to create "server" ports. You may +/// call this before the allocation has completed. However, any attempts +/// to send packets will fail until the allocation has succeeded. When +/// the peer receives packets sent from this interface, the from address +/// of the packet will be the globally-unique FakeIP. If you call this +/// function multiple times and pass the same (nonnegative) fake port index, +/// the same object will be returned, and this object is not reference counted. +/// +/// To create a "client" port (e.g. the equivalent of an ephemeral UDP port) +/// pass -1. In this case, a distinct object will be returned for each call. +/// When the peer receives packets sent from this interface, the peer will +/// assign a FakeIP from its own locally-controlled namespace. +ISteamNetworkingFakeUDPPort* Steam_Networking_Sockets::CreateFakeUDPPort( int idxFakeServerPort ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return NULL; +} + +// TEMP KLUDGE Call to invoke all queued callbacks. +// Eventually this function will go away, and callwacks will be ordinary Steamworks callbacks. +// You should call this at the same time you call SteamAPI_RunCallbacks and SteamGameServer_RunCallbacks +// to minimize potential changes in timing when that change happens. +void Steam_Networking_Sockets::RunCallbacks( ISteamNetworkingSocketsCallbacks *pCallbacks ) +{ + // PRINT_DEBUG_ENTRY(); +} + + +void Steam_Networking_Sockets::RunCallbacks() +{ + // PRINT_DEBUG_ENTRY(); + + //TODO: timeout unaccepted connections after a few seconds or so + auto current_time = std::chrono::steady_clock::now(); + auto socket_conn = std::begin(sbcs->connect_sockets); + while (socket_conn != std::end(sbcs->connect_sockets)) { + if (socket_conn->second.connect_requests_sent < 10 && socket_conn->second.status == CONNECT_SOCKET_CONNECTING && (std::chrono::duration_cast(current_time - socket_conn->second.connect_request_last_sent).count() > 3000)) { + send_packet_new_connection(socket_conn->first); + socket_conn->second.connect_request_last_sent = current_time; + socket_conn->second.connect_requests_sent += 1; + } + + ++socket_conn; + } +} + + +void Steam_Networking_Sockets::Callback(Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + + if (msg->low_level().type() == Low_Level::DISCONNECT) { + for (auto & connect_socket : sbcs->connect_sockets) { + if (connect_socket.second.remote_identity.GetSteamID64() == msg->source_id()) { + enum connect_socket_status old_status = connect_socket.second.status; + connect_socket.second.status = CONNECT_SOCKET_TIMEDOUT; + launch_callback(connect_socket.first, old_status); + } + } + } + } + + if (msg->has_networking_sockets()) { + PRINT_DEBUG("got network socket msg %u " "%" PRIu64 "", msg->networking_sockets().type(), msg->source_id()); + if (msg->networking_sockets().type() == Networking_Sockets::CONNECTION_REQUEST) { + int virtual_port = msg->networking_sockets().virtual_port(); + int real_port = msg->networking_sockets().real_port(); + uint64 dest_id = msg->dest_id(); + std::vector::iterator conn; + if (virtual_port == SNS_DISABLED_PORT) { + conn = std::find_if(sbcs->listen_sockets.begin(), sbcs->listen_sockets.end(), [&real_port,&dest_id](struct Listen_Socket const& conn) { return conn.real_port == real_port && dest_id == conn.created_by.ConvertToUint64();}); + } else { + conn = std::find_if(sbcs->listen_sockets.begin(), sbcs->listen_sockets.end(), [&virtual_port,&dest_id](struct Listen_Socket const& conn) { return conn.virtual_port == virtual_port && dest_id == conn.created_by.ConvertToUint64();}); + } + + if (conn != sbcs->listen_sockets.end()) { + auto connect_socket = std::find_if(sbcs->connect_sockets.begin(), sbcs->connect_sockets.end(), [msg](const auto &in) {return in.second.remote_identity.GetSteamID64() == msg->source_id() && (in.second.status == CONNECT_SOCKET_NOT_ACCEPTED || in.second.status == CONNECT_SOCKET_CONNECTED) && in.second.remote_id == msg->networking_sockets().connection_id_from();}); + if (connect_socket == sbcs->connect_sockets.end()) { + SteamNetworkingIdentity identity; + identity.SetSteamID64(msg->source_id()); + HSteamNetConnection new_connection = new_connect_socket(identity, virtual_port, real_port, CONNECT_SOCKET_NOT_ACCEPTED, conn->socket_id, msg->networking_sockets().connection_id_from()); + launch_callback(new_connection, CONNECT_SOCKET_NO_CONNECTION); + } + } + + } else if (msg->networking_sockets().type() == Networking_Sockets::CONNECTION_ACCEPTED) { + auto connect_socket = sbcs->connect_sockets.find(msg->networking_sockets().connection_id()); + if (connect_socket != sbcs->connect_sockets.end()) { + if (connect_socket->second.remote_identity.GetSteamID64() == 0) { + connect_socket->second.remote_identity.SetSteamID64(msg->source_id()); + } + + if (connect_socket->second.remote_identity.GetSteamID64() == msg->source_id() && connect_socket->second.status == CONNECT_SOCKET_CONNECTING) { + connect_socket->second.remote_id = msg->networking_sockets().connection_id_from(); + connect_socket->second.status = CONNECT_SOCKET_CONNECTED; + launch_callback(connect_socket->first, CONNECT_SOCKET_CONNECTING); + } + } + } else if (msg->networking_sockets().type() == Networking_Sockets::DATA) { + auto connect_socket = sbcs->connect_sockets.find(msg->networking_sockets().connection_id()); + if (connect_socket != sbcs->connect_sockets.end()) { + if (connect_socket->second.remote_identity.GetSteamID64() == msg->source_id() && (connect_socket->second.status == CONNECT_SOCKET_CONNECTED)) { + PRINT_DEBUG("got data len %zu, num " "%" PRIu64 " on connection %u", msg->networking_sockets().data().size(), msg->networking_sockets().message_number(), connect_socket->first); + connect_socket->second.data.push(msg->networking_sockets()); + } + } else { + connect_socket = std::find_if(sbcs->connect_sockets.begin(), sbcs->connect_sockets.end(), [msg](const auto &in) {return in.second.remote_identity.GetSteamID64() == msg->source_id() && (in.second.status == CONNECT_SOCKET_NOT_ACCEPTED || in.second.status == CONNECT_SOCKET_CONNECTED) && in.second.remote_id == msg->networking_sockets().connection_id_from();}); + if (connect_socket != sbcs->connect_sockets.end()) { + PRINT_DEBUG("got data len %zu, num " "%" PRIu64 " on not accepted connection %u", msg->networking_sockets().data().size(), msg->networking_sockets().message_number(), connect_socket->first); + connect_socket->second.data.push(msg->networking_sockets()); + } + } + } else if (msg->networking_sockets().type() == Networking_Sockets::CONNECTION_END) { + auto connect_socket = sbcs->connect_sockets.find(msg->networking_sockets().connection_id()); + if (connect_socket != sbcs->connect_sockets.end()) { + if (connect_socket->second.remote_identity.GetSteamID64() == msg->source_id() && connect_socket->second.status == CONNECT_SOCKET_CONNECTED) { + enum connect_socket_status old_status = connect_socket->second.status; + connect_socket->second.status = CONNECT_SOCKET_CLOSED; + launch_callback(connect_socket->first, old_status); + } + } + } + } +} diff --git a/dll/steam_networking_socketsserialized.cpp b/dll/steam_networking_socketsserialized.cpp new file mode 100644 index 00000000..468e0472 --- /dev/null +++ b/dll/steam_networking_socketsserialized.cpp @@ -0,0 +1,155 @@ +/* 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 + . */ + +#include "dll/steam_networking_socketsserialized.h" + + +void Steam_Networking_Sockets_Serialized::steam_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Networking_Sockets_Serialized *steam_networkingsockets = (Steam_Networking_Sockets_Serialized *)object; + steam_networkingsockets->Callback(msg); +} + +void Steam_Networking_Sockets_Serialized::steam_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Networking_Sockets_Serialized *steam_networkingsockets = (Steam_Networking_Sockets_Serialized *)object; + steam_networkingsockets->RunCallbacks(); +} + +Steam_Networking_Sockets_Serialized::Steam_Networking_Sockets_Serialized(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_Networking_Sockets_Serialized::steam_callback, this); + this->run_every_runcb->add(&Steam_Networking_Sockets_Serialized::steam_run_every_runcb, this); + +} + +Steam_Networking_Sockets_Serialized::~Steam_Networking_Sockets_Serialized() +{ + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Sockets_Serialized::steam_callback, this); + this->run_every_runcb->remove(&Steam_Networking_Sockets_Serialized::steam_run_every_runcb, this); +} + +void Steam_Networking_Sockets_Serialized::SendP2PRendezvous( CSteamID steamIDRemote, uint32 unConnectionIDSrc, const void *pMsgRendezvous, uint32 cbRendezvous ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +void Steam_Networking_Sockets_Serialized::SendP2PConnectionFailure( CSteamID steamIDRemote, uint32 unConnectionIDDest, uint32 nReason, const char *pszReason ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +SteamAPICall_t Steam_Networking_Sockets_Serialized::GetCertAsync() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + struct SteamNetworkingSocketsCert_t data = {}; + data.m_eResult = k_EResultOK; + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +int Steam_Networking_Sockets_Serialized::GetNetworkConfigJSON( void *buf, uint32 cbBuf, const char *pszLauncherPartner ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +int Steam_Networking_Sockets_Serialized::GetNetworkConfigJSON( void *buf, uint32 cbBuf ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return GetNetworkConfigJSON(buf, cbBuf, ""); +} + +void Steam_Networking_Sockets_Serialized::CacheRelayTicket( const void *pTicket, uint32 cbTicket ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +uint32 Steam_Networking_Sockets_Serialized::GetCachedRelayTicketCount() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +int Steam_Networking_Sockets_Serialized::GetCachedRelayTicket( uint32 idxTicket, void *buf, uint32 cbBuf ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +void Steam_Networking_Sockets_Serialized::PostConnectionStateMsg( const void *pMsg, uint32 cbMsg ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +bool Steam_Networking_Sockets_Serialized::GetSTUNServer(int dont_know, char *buf, unsigned int len) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +bool Steam_Networking_Sockets_Serialized::BAllowDirectConnectToPeer(SteamNetworkingIdentity const &identity) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return true; +} + +int Steam_Networking_Sockets_Serialized::BeginAsyncRequestFakeIP(int a) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return true; +} + +void Steam_Networking_Sockets_Serialized::RunCallbacks() +{ + +} + +void Steam_Networking_Sockets_Serialized::Callback(Common_Message *msg) +{ + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + + if (msg->low_level().type() == Low_Level::DISCONNECT) { + + } + } +} diff --git a/dll/steam_networking_utils.cpp b/dll/steam_networking_utils.cpp new file mode 100644 index 00000000..e1f2378a --- /dev/null +++ b/dll/steam_networking_utils.cpp @@ -0,0 +1,730 @@ +/* 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 + . */ + +#include "dll/steam_networking_utils.h" + +void Steam_Networking_Utils::steam_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Networking_Utils *steam_networkingutils = (Steam_Networking_Utils *)object; + steam_networkingutils->Callback(msg); +} + +void Steam_Networking_Utils::steam_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Networking_Utils *steam_networkingutils = (Steam_Networking_Utils *)object; + steam_networkingutils->RunCallbacks(); +} + +Steam_Networking_Utils::Steam_Networking_Utils(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_Networking_Utils::steam_callback, this); + this->run_every_runcb->add(&Steam_Networking_Utils::steam_run_every_runcb, this); +} + +Steam_Networking_Utils::~Steam_Networking_Utils() +{ + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Utils::steam_callback, this); + this->run_every_runcb->remove(&Steam_Networking_Utils::steam_run_every_runcb, this); +} + +void Steam_Networking_Utils::free_steam_message_data(SteamNetworkingMessage_t *pMsg) +{ + free(pMsg->m_pData); + pMsg->m_pData = NULL; +} + +void Steam_Networking_Utils::delete_steam_message(SteamNetworkingMessage_t *pMsg) +{ + if (pMsg->m_pfnFreeData) pMsg->m_pfnFreeData(pMsg); + delete pMsg; +} + +/// Allocate and initialize a message object. Usually the reason +/// you call this is to pass it to ISteamNetworkingSockets::SendMessages. +/// The returned object will have all of the relevant fields cleared to zero. +/// +/// Optionally you can also request that this system allocate space to +/// hold the payload itself. If cbAllocateBuffer is nonzero, the system +/// will allocate memory to hold a payload of at least cbAllocateBuffer bytes. +/// m_pData will point to the allocated buffer, m_cbSize will be set to the +/// size, and m_pfnFreeData will be set to the proper function to free up +/// the buffer. +/// +/// If cbAllocateBuffer=0, then no buffer is allocated. m_pData will be NULL, +/// m_cbSize will be zero, and m_pfnFreeData will be NULL. You will need to +/// set each of these. +/// +/// You can use SteamNetworkingMessage_t::Release to free up the message +/// bookkeeping object and any associated buffer. See +/// ISteamNetworkingSockets::SendMessages for details on reference +/// counting and ownership. +SteamNetworkingMessage_t* Steam_Networking_Utils::AllocateMessage( int cbAllocateBuffer ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + SteamNetworkingMessage_t *pMsg = new SteamNetworkingMessage_t(); + pMsg->m_pfnFreeData = &free_steam_message_data; + pMsg->m_pfnRelease = &delete_steam_message; + if (cbAllocateBuffer < 0) + cbAllocateBuffer = 0; + + pMsg->m_pData = nullptr; + if (cbAllocateBuffer) + pMsg->m_pData = malloc(cbAllocateBuffer); + + pMsg->m_cbSize = cbAllocateBuffer; + return pMsg; +} + +bool Steam_Networking_Utils::InitializeRelayAccess() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + init_relay = true; + return relay_initialized; +} + +SteamRelayNetworkStatus_t Steam_Networking_Utils::get_network_status() +{ + SteamRelayNetworkStatus_t data = {}; + data.m_eAvail = k_ESteamNetworkingAvailability_Current; + data.m_bPingMeasurementInProgress = 0; + data.m_eAvailAnyRelay = k_ESteamNetworkingAvailability_Current; + data.m_eAvailNetworkConfig = k_ESteamNetworkingAvailability_Current; + strcpy(data.m_debugMsg, "OK"); + return data; +} + +/// Fetch current status of the relay network. +/// +/// SteamRelayNetworkStatus_t is also a callback. It will be triggered on +/// both the user and gameserver interfaces any time the status changes, or +/// ping measurement starts or stops. +/// +/// SteamRelayNetworkStatus_t::m_eAvail is returned. If you want +/// more details, you can pass a non-NULL value. +ESteamNetworkingAvailability Steam_Networking_Utils::GetRelayNetworkStatus( SteamRelayNetworkStatus_t *pDetails ) +{ + PRINT_DEBUG("TODO %p", pDetails); + std::lock_guard lock(global_mutex); + + //TODO: check if this is how real steam returns it + SteamRelayNetworkStatus_t data = {}; + if (relay_initialized) { + data = get_network_status(); + } + + if (pDetails) { + *pDetails = data; + } + + return k_ESteamNetworkingAvailability_Current; +} + +float Steam_Networking_Utils::GetLocalPingLocation( SteamNetworkPingLocation_t &result ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (relay_initialized) { + result.m_data[2] = 123; + result.m_data[8] = 67; + return 2.0; + } + + return -1; +} + +int Steam_Networking_Utils::EstimatePingTimeBetweenTwoLocations( const SteamNetworkPingLocation_t &location1, const SteamNetworkPingLocation_t &location2 ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + //return k_nSteamNetworkingPing_Unknown; + return 2; +} + + +int Steam_Networking_Utils::EstimatePingTimeFromLocalHost( const SteamNetworkPingLocation_t &remoteLocation ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 2; +} + + +void Steam_Networking_Utils::ConvertPingLocationToString( const SteamNetworkPingLocation_t &location, char *pszBuf, int cchBufSize ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + strncpy(pszBuf, "fra=10+2", cchBufSize); +} + + +bool Steam_Networking_Utils::ParsePingLocationString( const char *pszString, SteamNetworkPingLocation_t &result ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return true; +} + + +bool Steam_Networking_Utils::CheckPingDataUpToDate( float flMaxAgeSeconds ) +{ + PRINT_DEBUG("TODO %f", flMaxAgeSeconds); + init_relay = true; + return relay_initialized; +} + + +bool Steam_Networking_Utils::IsPingMeasurementInProgress() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +int Steam_Networking_Utils::GetPingToDataCenter( SteamNetworkingPOPID popID, SteamNetworkingPOPID *pViaRelayPoP ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +int Steam_Networking_Utils::GetDirectPingToPOP( SteamNetworkingPOPID popID ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +int Steam_Networking_Utils::GetPOPCount() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +int Steam_Networking_Utils::GetPOPList( SteamNetworkingPOPID *list, int nListSz ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +// +// Misc +// + +/// Fetch current timestamp. This timer has the following properties: +/// +/// - Monotonicity is guaranteed. +/// - The initial value will be at least 24*3600*30*1e6, i.e. about +/// 30 days worth of microseconds. In this way, the timestamp value of +/// 0 will always be at least "30 days ago". Also, negative numbers +/// will never be returned. +/// - Wraparound / overflow is not a practical concern. +/// +/// If you are running under the debugger and stop the process, the clock +/// might not advance the full wall clock time that has elapsed between +/// calls. If the process is not blocked from normal operation, the +/// timestamp values will track wall clock time, even if you don't call +/// the function frequently. +/// +/// The value is only meaningful for this run of the process. Don't compare +/// it to values obtained on another computer, or other runs of the same process. +SteamNetworkingMicroseconds Steam_Networking_Utils::GetLocalTimestamp() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + return std::chrono::duration_cast(std::chrono::steady_clock::now() - initialized_time).count() + (SteamNetworkingMicroseconds)24*3600*30*1e6; +} + + +/// Set a function to receive network-related information that is useful for debugging. +/// This can be very useful during development, but it can also be useful for troubleshooting +/// problems with tech savvy end users. If you have a console or other log that customers +/// can examine, these log messages can often be helpful to troubleshoot network issues. +/// (Especially any warning/error messages.) +/// +/// The detail level indicates what message to invoke your callback on. Lower numeric +/// value means more important, and the value you pass is the lowest priority (highest +/// numeric value) you wish to receive callbacks for. +/// +/// Except when debugging, you should only use k_ESteamNetworkingSocketsDebugOutputType_Msg +/// or k_ESteamNetworkingSocketsDebugOutputType_Warning. For best performance, do NOT +/// request a high detail level and then filter out messages in your callback. Instead, +/// call function function to adjust the desired level of detail. +/// +/// IMPORTANT: This may be called from a service thread, while we own a mutex, etc. +/// Your output function must be threadsafe and fast! Do not make any other +/// Steamworks calls from within the handler. +void Steam_Networking_Utils::SetDebugOutputFunction( ESteamNetworkingSocketsDebugOutputType eDetailLevel, FSteamNetworkingSocketsDebugOutput pfnFunc ) +{ + PRINT_DEBUG("%i", eDetailLevel); + std::lock_guard lock(global_mutex); + if (eDetailLevel != k_ESteamNetworkingSocketsDebugOutputType_None) { + debug_function = pfnFunc; + } +} + +// +// Fake IP +// +// Useful for interfacing with code that assumes peers are identified using an IPv4 address +// + +/// Return true if an IPv4 address is one that might be used as a "fake" one. +/// This function is fast; it just does some logical tests on the IP and does +/// not need to do any lookup operations. +// inline bool IsFakeIPv4( uint32 nIPv4 ) { return GetIPv4FakeIPType( nIPv4 ) > k_ESteamNetworkingFakeIPType_NotFake; } +ESteamNetworkingFakeIPType Steam_Networking_Utils::GetIPv4FakeIPType( uint32 nIPv4 ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_ESteamNetworkingFakeIPType_NotFake; +} + +/// Get the real identity associated with a given FakeIP. +/// +/// On failure, returns: +/// - k_EResultInvalidParam: the IP is not a FakeIP. +/// - k_EResultNoMatch: we don't recognize that FakeIP and don't know the corresponding identity. +/// +/// FakeIP's used by active connections, or the FakeIPs assigned to local identities, +/// will always work. FakeIPs for recently destroyed connections will continue to +/// return results for a little while, but not forever. At some point, we will forget +/// FakeIPs to save space. It's reasonably safe to assume that you can read back the +/// real identity of a connection very soon after it is destroyed. But do not wait +/// indefinitely. +EResult Steam_Networking_Utils::GetRealIdentityForFakeIP( const SteamNetworkingIPAddr &fakeIP, SteamNetworkingIdentity *pOutRealIdentity ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_EResultNoMatch; +} + + +// +// Set and get configuration values, see ESteamNetworkingConfigValue for individual descriptions. +// + +// Shortcuts for common cases. (Implemented as inline functions below) +/* +bool Steam_Networking_Utils::SetGlobalConfigValueInt32( ESteamNetworkingConfigValue eValue, int32 val ); +bool Steam_Networking_Utils::SetGlobalConfigValueFloat( ESteamNetworkingConfigValue eValue, float val ); +bool Steam_Networking_Utils::SetGlobalConfigValueString( ESteamNetworkingConfigValue eValue, const char *val ); +bool Steam_Networking_Utils::SetConnectionConfigValueInt32( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, int32 val ); +bool Steam_Networking_Utils::SetConnectionConfigValueFloat( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, float val ); +bool Steam_Networking_Utils::SetConnectionConfigValueString( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, const char *val ); +*/ +/// Set a configuration value. +/// - eValue: which value is being set +/// - eScope: Onto what type of object are you applying the setting? +/// - scopeArg: Which object you want to change? (Ignored for global scope). E.g. connection handle, listen socket handle, interface pointer, etc. +/// - eDataType: What type of data is in the buffer at pValue? This must match the type of the variable exactly! +/// - pArg: Value to set it to. You can pass NULL to remove a non-global sett at this scope, +/// causing the value for that object to use global defaults. Or at global scope, passing NULL +/// will reset any custom value and restore it to the system default. +/// NOTE: When setting callback functions, do not pass the function pointer directly. +/// Your argument should be a pointer to a function pointer. +bool Steam_Networking_Utils::SetConfigValue( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigScope eScopeType, intptr_t scopeObj, + ESteamNetworkingConfigDataType eDataType, const void *pArg ) +{ + PRINT_DEBUG("TODO %i %i " "%" PRIdPTR " %i %p", eValue, eScopeType, scopeObj, eDataType, pArg); + std::lock_guard lock(global_mutex); + return true; +} + + +/// Get a configuration value. +/// - eValue: which value to fetch +/// - eScopeType: query setting on what type of object +/// - eScopeArg: the object to query the setting for +/// - pOutDataType: If non-NULL, the data type of the value is returned. +/// - pResult: Where to put the result. Pass NULL to query the required buffer size. (k_ESteamNetworkingGetConfigValue_BufferTooSmall will be returned.) +/// - cbResult: IN: the size of your buffer. OUT: the number of bytes filled in or required. +ESteamNetworkingGetConfigValueResult Steam_Networking_Utils::GetConfigValue( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigScope eScopeType, intptr_t scopeObj, + ESteamNetworkingConfigDataType *pOutDataType, void *pResult, size_t *cbResult ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_ESteamNetworkingGetConfigValue_BadValue; +} + + +/// Returns info about a configuration value. Returns false if the value does not exist. +/// pOutNextValue can be used to iterate through all of the known configuration values. +/// (Use GetFirstConfigValue() to begin the iteration, will be k_ESteamNetworkingConfig_Invalid on the last value) +/// Any of the output parameters can be NULL if you do not need that information. +bool Steam_Networking_Utils::GetConfigValueInfo( ESteamNetworkingConfigValue eValue, const char **pOutName, ESteamNetworkingConfigDataType *pOutDataType, ESteamNetworkingConfigScope *pOutScope, ESteamNetworkingConfigValue *pOutNextValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + //TODO flat api + return false; +} + +/// Get info about a configuration value. Returns the name of the value, +/// or NULL if the value doesn't exist. Other output parameters can be NULL +/// if you do not need them. +const char* Steam_Networking_Utils::GetConfigValueInfo( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigDataType *pOutDataType, ESteamNetworkingConfigScope *pOutScope ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + //TODO flat api + return NULL; +} + +/// Return the lowest numbered configuration value available in the current environment. +ESteamNetworkingConfigValue Steam_Networking_Utils::GetFirstConfigValue() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_ESteamNetworkingConfig_Invalid; +} + +/// Iterate the list of all configuration values in the current environment that it might +/// be possible to display or edit using a generic UI. To get the first iterable value, +/// pass k_ESteamNetworkingConfig_Invalid. Returns k_ESteamNetworkingConfig_Invalid +/// to signal end of list. +/// +/// The bEnumerateDevVars argument can be used to include "dev" vars. These are vars that +/// are recommended to only be editable in "debug" or "dev" mode and typically should not be +/// shown in a retail environment where a malicious local user might use this to cheat. +ESteamNetworkingConfigValue Steam_Networking_Utils::IterateGenericEditableConfigValues( ESteamNetworkingConfigValue eCurrent, bool bEnumerateDevVars ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_ESteamNetworkingConfig_Invalid; +} + + +// String conversions. You'll usually access these using the respective +// inline methods. +void Steam_Networking_Utils::SteamNetworkingIPAddr_ToString( const SteamNetworkingIPAddr &addr, char *buf, size_t cbBuf, bool bWithPort ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (buf == nullptr || cbBuf == 0) return; + + char buffer[64]{}; // enough for ipv4 & ipv6 + port + std::string str_addr{}; + if (addr.IsIPv4()) { + in_addr ipv4_addr; + ipv4_addr.s_addr = htonl(addr.GetIPv4()); + + if (inet_ntop(AF_INET, &ipv4_addr, buffer, sizeof(buffer) / sizeof(*buffer)) != nullptr) { + if (bWithPort) { + str_addr = buffer; + str_addr += ':'; + str_addr += std::to_string(addr.m_port); + } else { + str_addr = buffer; + } + } + } else { + in6_addr ipv6_addr{}; + memcpy(ipv6_addr.s6_addr, addr.m_ipv6, sizeof(addr.m_ipv6)); + + if (inet_ntop(AF_INET6, &ipv6_addr, buffer, sizeof(buffer) / sizeof(*buffer)) != nullptr) { + if (bWithPort) { + str_addr = '['; + str_addr += buffer; + str_addr += "]:"; + str_addr += std::to_string(addr.m_port); + } else { + str_addr = buffer; + } + } + } + + cbBuf = std::min(cbBuf, str_addr.length() + 1); + strncpy(buf, str_addr.c_str(), cbBuf); + buf[cbBuf - 1] = '\0'; +} + +bool Steam_Networking_Utils::SteamNetworkingIPAddr_ParseString( SteamNetworkingIPAddr *pAddr, const char *pszStr ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + bool valid = false; + + if (pAddr == nullptr || pszStr == nullptr) return valid; + + std::string str(pszStr); + size_t pos = str.find(':'); + + if (pos != std::string::npos) {// Try ipv4 with port + in_addr ipv4_addr; + std::string tmp(str); + tmp[pos] = 0; + const char* ip = tmp.c_str(); + const char* port = &tmp[pos + 1]; + + if (inet_pton(AF_INET, ip, &ipv4_addr) == 1) + { + valid = true; + pAddr->SetIPv4(ntohl(ipv4_addr.s_addr), strtoul(port, nullptr, 10)); + } + } else {// Try ipv4 without port + in_addr ipv4_addr; + if (inet_pton(AF_INET, str.c_str(), &ipv4_addr) == 1) + { + valid = true; + pAddr->SetIPv4(ntohl(ipv4_addr.s_addr), 0); + } + } + + if (!valid) {// Try ipv6 + addrinfo* info = nullptr; + addrinfo hints = {}; + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; + + size_t sep_pos = 0; + std::string ip; + int sep_count = 0; + for (int i = 0; i < str.length(); ++i) { + if (str[i] == ':') { + sep_pos = i; + ++sep_count; + } + } + + if (sep_count == 8) { + ip = std::move(std::string(str.begin(), str.begin() + sep_pos)); + } else { + ip = str; + } + + if (getaddrinfo(ip.c_str(), nullptr, &hints, &info) == 0) { + sockaddr_in6* maddr = (sockaddr_in6*)info->ai_addr; + + size_t pos = str.find(']'); + std::string str_port("0"); + if (pos != std::string::npos) { + str_port = std::move(std::string(str.begin() + pos + 2, str.end())); + } else if (sep_count == 8) { + str_port = std::move(std::string(str.begin() + sep_pos + 1, str.end())); + } + + try { + int port = std::stoi(str_port); + if (port >= 0 && port <= 65535) { + pAddr->SetIPv6(maddr->sin6_addr.s6_addr, port); + valid = true; + } + } + catch(...) { } + } + + if (info) { + freeaddrinfo(info); + } + } + + if (!valid) { + pAddr->Clear(); + } + + return valid; +} + +ESteamNetworkingFakeIPType Steam_Networking_Utils::SteamNetworkingIPAddr_GetFakeIPType( const SteamNetworkingIPAddr &addr ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_ESteamNetworkingFakeIPType_NotFake; +} + + +void Steam_Networking_Utils::SteamNetworkingIdentity_ToString( const SteamNetworkingIdentity &identity, char *buf, size_t cbBuf ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (buf == nullptr) + return; + + std::string str; + str.reserve(SteamNetworkingIdentity::k_cchMaxString); + switch (identity.m_eType) + { + case k_ESteamNetworkingIdentityType_SteamID: + { + str = "steamid:"; + str += std::move(std::to_string(identity.GetSteamID64())); + } + break; + + case k_ESteamNetworkingIdentityType_IPAddress: + { + str = "ip:"; + char buff[SteamNetworkingIPAddr::k_cchMaxString]; + auto& addr = *identity.GetIPAddr(); + SteamNetworkingIPAddr_ToString(addr, buff, sizeof(buff), true); + str += buff; + } + break; + + case k_ESteamNetworkingIdentityType_GenericBytes: + { + int generic_len; + const uint8* pBuf = identity.GetGenericBytes(generic_len); + + str = "gen:"; + str.resize(4 + (generic_len * 2)); + + char* pDest = &str[4]; + while(generic_len--) + { + // I don't care for the last char, I've reserved the max string size + snprintf(pDest, 3, "%02x", *pBuf); + ++pBuf; + pDest += 2; + } + } + break; + + case k_ESteamNetworkingIdentityType_GenericString: + { + str = "str:"; + str += identity.GetGenericString(); + } + break; + + case k_ESteamNetworkingIdentityType_UnknownType: + { + str = identity.m_szUnknownRawString; + } + break; + } + cbBuf = std::min(cbBuf, str.length() + 1); + strncpy(buf, str.c_str(), cbBuf); + buf[cbBuf - 1] = '\0'; +} + +bool Steam_Networking_Utils::SteamNetworkingIdentity_ParseString( SteamNetworkingIdentity *pIdentity, const char *pszStr ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + bool valid = false; + if (pIdentity == nullptr) + { + return valid; + } + + if (pszStr != nullptr) + { + const char* end = strchr(pszStr, ':'); + if (end != nullptr) + { + ++end; + if (strncmp(pszStr, "gen:", end - pszStr) == 0) + { + size_t length = strlen(end); + if (!(length % 2) && length <= (sizeof(pIdentity->m_genericBytes) * 2)) + {// Must be even + valid = true; + length /= 2; + pIdentity->m_eType = k_ESteamNetworkingIdentityType_GenericBytes; + pIdentity->m_cbSize = length; + uint8* pBytes = pIdentity->m_genericBytes; + + char hex[3] = { 0,0,0 }; + while (length) + { + hex[0] = end[0]; + hex[1] = end[1]; + // Steam doesn't check if wasn't a hex char + *pBytes = strtol(hex, nullptr, 16); + + ++pBytes; + end += 2; + --length; + } + } + } + else if (strncmp(pszStr, "steamid:", end - pszStr) == 0) + { + CSteamID steam_id(uint64(strtoull(end, nullptr, 10))); + if (steam_id.IsValid()) + { + valid = true; + pIdentity->SetSteamID(steam_id); + } + } + else if (strncmp(pszStr, "str:", end - pszStr) == 0) + { + valid = pIdentity->SetGenericString(end); + } + else if (strncmp(pszStr, "ip:", end - pszStr) == 0) + { + SteamNetworkingIPAddr steam_addr; + if (SteamNetworkingIPAddr_ParseString(&steam_addr, end)) + { + valid = true; + pIdentity->SetIPAddr(steam_addr); + } + } + } + } + + return valid; +} + + +void Steam_Networking_Utils::RunCallbacks() +{ + if (init_relay && !relay_initialized) { + relay_initialized = true; + SteamRelayNetworkStatus_t data = get_network_status(); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } +} + +void Steam_Networking_Utils::Callback(Common_Message *msg) +{ + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + + if (msg->low_level().type() == Low_Level::DISCONNECT) { + + } + } + + if (msg->has_networking_sockets()) { + + } +} diff --git a/dll/steam_parties.cpp b/dll/steam_parties.cpp new file mode 100644 index 00000000..ff162a91 --- /dev/null +++ b/dll/steam_parties.cpp @@ -0,0 +1,195 @@ +/* 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 + . */ + +#include "dll/steam_parties.h" + + +void Steam_Parties::steam_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Parties *steam_parties = (Steam_Parties *)object; + steam_parties->Callback(msg); +} + +void Steam_Parties::steam_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Parties *steam_parties = (Steam_Parties *)object; + steam_parties->RunCallbacks(); +} + + +Steam_Parties::Steam_Parties(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_Parties::steam_callback, this); + this->run_every_runcb->add(&Steam_Parties::steam_run_every_runcb, this); +} + +Steam_Parties::~Steam_Parties() +{ + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Parties::steam_callback, this); + this->run_every_runcb->remove(&Steam_Parties::steam_run_every_runcb, this); +} + + +// ============================================================================================= +// Party Client APIs + +// Enumerate any active beacons for parties you may wish to join +uint32 Steam_Parties::GetNumActiveBeacons() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +PartyBeaconID_t Steam_Parties::GetBeaconByIndex( uint32 unIndex ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_ulPartyBeaconIdInvalid; +} + +bool Steam_Parties::GetBeaconDetails( PartyBeaconID_t ulBeaconID, CSteamID *pSteamIDBeaconOwner, STEAM_OUT_STRUCT() SteamPartyBeaconLocation_t *pLocation, STEAM_OUT_STRING_COUNT(cchMetadata) char *pchMetadata, int cchMetadata ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +// Join an open party. Steam will reserve one beacon slot for your SteamID, +// and return the necessary JoinGame string for you to use to connect +STEAM_CALL_RESULT( JoinPartyCallback_t ) +SteamAPICall_t Steam_Parties::JoinParty( PartyBeaconID_t ulBeaconID ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +// ============================================================================================= +// Party Host APIs + +// Get a list of possible beacon locations +bool Steam_Parties::GetNumAvailableBeaconLocations( uint32 *puNumLocations ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +bool Steam_Parties::GetAvailableBeaconLocations( SteamPartyBeaconLocation_t *pLocationList, uint32 uMaxNumLocations ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +// Create a new party beacon and activate it in the selected location. +// unOpenSlots is the maximum number of users that Steam will send to you. +// When people begin responding to your beacon, Steam will send you +// PartyReservationCallback_t callbacks to let you know who is on the way. +STEAM_CALL_RESULT( CreateBeaconCallback_t ) +SteamAPICall_t Steam_Parties::CreateBeacon( uint32 unOpenSlots, SteamPartyBeaconLocation_t *pBeaconLocation, const char *pchConnectString, const char *pchMetadata ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +// Call this function when a user that had a reservation (see callback below) +// has successfully joined your party. +// Steam will manage the remaining open slots automatically. +void Steam_Parties::OnReservationCompleted( PartyBeaconID_t ulBeacon, CSteamID steamIDUser ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// To cancel a reservation (due to timeout or user input), call this. +// Steam will open a new reservation slot. +// Note: The user may already be in-flight to your game, so it's possible they will still connect and try to join your party. +void Steam_Parties::CancelReservation( PartyBeaconID_t ulBeacon, CSteamID steamIDUser ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + + +// Change the number of open beacon reservation slots. +// Call this if, for example, someone without a reservation joins your party (eg a friend, or via your own matchmaking system). +STEAM_CALL_RESULT( ChangeNumOpenSlotsCallback_t ) +SteamAPICall_t Steam_Parties::ChangeNumOpenSlots( PartyBeaconID_t ulBeacon, uint32 unOpenSlots ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + + +// Turn off the beacon. +bool Steam_Parties::DestroyBeacon( PartyBeaconID_t ulBeacon ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +// Utils +bool Steam_Parties::GetBeaconLocationData( SteamPartyBeaconLocation_t BeaconLocation, ESteamPartyBeaconLocationData eData, STEAM_OUT_STRING_COUNT(cchDataStringOut) char *pchDataStringOut, int cchDataStringOut ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +void Steam_Parties::RunCallbacks() +{ + +} + +void Steam_Parties::Callback(Common_Message *msg) +{ + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + + if (msg->low_level().type() == Low_Level::DISCONNECT) { + + } + } + + if (msg->has_networking_sockets()) { + + } +} diff --git a/dll/steam_remote_storage.cpp b/dll/steam_remote_storage.cpp new file mode 100644 index 00000000..526fca00 --- /dev/null +++ b/dll/steam_remote_storage.cpp @@ -0,0 +1,1187 @@ +/* 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 + . */ + +#include "dll/steam_remote_storage.h" + + +Downloaded_File::Downloaded_File() +{ + +} + +Downloaded_File::~Downloaded_File() +{ + +} + +static void copy_file(const std::string &src_filepath, const std::string &dst_filepath) +{ + try + { + PRINT_DEBUG("copying file '%s' to '%s'", src_filepath.c_str(), dst_filepath.c_str()); + const std::filesystem::path src_p(std::filesystem::u8path(src_filepath)); + + if (!common_helpers::file_exist(src_p)) return; + + const std::filesystem::path dst_p(std::filesystem::u8path(dst_filepath)); + std::filesystem::create_directories(dst_p.parent_path()); // make the folder tree if needed + std::filesystem::copy_file(src_p, dst_p, std::filesystem::copy_options::overwrite_existing); + } catch(...) {} +} + +Steam_Remote_Storage::Steam_Remote_Storage(class Settings *settings, class Ugc_Remote_Storage_Bridge *ugc_bridge, class Local_Storage *local_storage, class SteamCallResults *callback_results) +{ + this->settings = settings; + this->ugc_bridge = ugc_bridge; + this->local_storage = local_storage; + this->callback_results = callback_results; + + steam_cloud_enabled = true; +} + +// NOTE +// +// Filenames are case-insensitive, and will be converted to lowercase automatically. +// So "foo.bar" and "Foo.bar" are the same file, and if you write "Foo.bar" then +// iterate the files, the filename returned will be "foo.bar". +// + +// file operations +bool Steam_Remote_Storage::FileWrite( const char *pchFile, const void *pvData, int32 cubData ) +{ + PRINT_DEBUG("'%s' %p %u", pchFile, pvData, cubData); + std::lock_guard lock(global_mutex); + + if (!pchFile || !pchFile[0] || cubData <= 0 || cubData > k_unMaxCloudFileChunkSize || !pvData) { + return false; + } + + int data_stored = local_storage->store_data(Local_Storage::remote_storage_folder, pchFile, (char* )pvData, cubData); + PRINT_DEBUG("%i, %u", data_stored, data_stored == cubData); + return data_stored == cubData; +} + +int32 Steam_Remote_Storage::FileRead( const char *pchFile, void *pvData, int32 cubDataToRead ) +{ + PRINT_DEBUG("'%s' %p %i", pchFile, pvData, cubDataToRead); + std::lock_guard lock(global_mutex); + + if (!pchFile || !pchFile[0] || !pvData || !cubDataToRead) return 0; + int read_data = local_storage->get_data(Local_Storage::remote_storage_folder, pchFile, (char* )pvData, cubDataToRead); + if (read_data < 0) read_data = 0; + PRINT_DEBUG(" Read %i", read_data); + return read_data; +} + +STEAM_CALL_RESULT( RemoteStorageFileWriteAsyncComplete_t ) +SteamAPICall_t Steam_Remote_Storage::FileWriteAsync( const char *pchFile, const void *pvData, uint32 cubData ) +{ + PRINT_DEBUG("'%s' %p %u", pchFile, pvData, cubData); + std::lock_guard lock(global_mutex); + + if (!pchFile || !pchFile[0] || cubData > k_unMaxCloudFileChunkSize || cubData == 0 || !pvData) { + return k_uAPICallInvalid; + } + + bool success = local_storage->store_data(Local_Storage::remote_storage_folder, pchFile, (char* )pvData, cubData) == cubData; + RemoteStorageFileWriteAsyncComplete_t data; + data.m_eResult = success ? k_EResultOK : k_EResultFail; + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.01); +} + + +STEAM_CALL_RESULT( RemoteStorageFileReadAsyncComplete_t ) +SteamAPICall_t Steam_Remote_Storage::FileReadAsync( const char *pchFile, uint32 nOffset, uint32 cubToRead ) +{ + PRINT_DEBUG("'%s' %u %u", pchFile, nOffset, cubToRead); + std::lock_guard lock(global_mutex); + + if (!pchFile || !pchFile[0]) return k_uAPICallInvalid; + unsigned int size = local_storage->file_size(Local_Storage::remote_storage_folder, pchFile); + + RemoteStorageFileReadAsyncComplete_t data; + if (size <= nOffset) { + return k_uAPICallInvalid; + } + + if ((size - nOffset) < cubToRead) cubToRead = size - nOffset; + + struct Async_Read a_read{}; + data.m_eResult = k_EResultOK; + a_read.offset = data.m_nOffset = nOffset; + a_read.api_call = data.m_hFileReadAsync = callback_results->reserveCallResult(); + a_read.to_read = data.m_cubRead = cubToRead; + a_read.file_name = std::string(pchFile); + a_read.size = size; + + async_reads.push_back(a_read); + callback_results->addCallResult(data.m_hFileReadAsync, data.k_iCallback, &data, sizeof(data), 0.0); + return data.m_hFileReadAsync; +} + +bool Steam_Remote_Storage::FileReadAsyncComplete( SteamAPICall_t hReadCall, void *pvBuffer, uint32 cubToRead ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!pvBuffer) return false; + + auto a_read = std::find_if(async_reads.begin(), async_reads.end(), [&hReadCall](Async_Read const& item) { return item.api_call == hReadCall; }); + if (async_reads.end() == a_read) + return false; + + if (cubToRead < a_read->to_read) + return false; + + char *temp = new char[a_read->size]; + int read_data = local_storage->get_data(Local_Storage::remote_storage_folder, a_read->file_name, (char* )temp, a_read->size); + if (read_data < a_read->to_read + a_read->offset) { + delete[] temp; + return false; + } + + memcpy(pvBuffer, temp + a_read->offset, a_read->to_read); + delete[] temp; + async_reads.erase(a_read); + return true; +} + + +bool Steam_Remote_Storage::FileForget( const char *pchFile ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!pchFile || !pchFile[0]) return false; + + return true; +} + +bool Steam_Remote_Storage::FileDelete( const char *pchFile ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!pchFile || !pchFile[0]) return false; + + return local_storage->file_delete(Local_Storage::remote_storage_folder, pchFile); +} + +STEAM_CALL_RESULT( RemoteStorageFileShareResult_t ) +SteamAPICall_t Steam_Remote_Storage::FileShare( const char *pchFile ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!pchFile || !pchFile[0]) return k_uAPICallInvalid; + + RemoteStorageFileShareResult_t data = {}; + if (local_storage->file_exists(Local_Storage::remote_storage_folder, pchFile)) { + data.m_eResult = k_EResultOK; + data.m_hFile = generate_steam_api_call_id(); + strncpy(data.m_rgchFilename, pchFile, sizeof(data.m_rgchFilename) - 1); + shared_files[data.m_hFile] = pchFile; + } else { + data.m_eResult = k_EResultFileNotFound; + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +bool Steam_Remote_Storage::SetSyncPlatforms( const char *pchFile, ERemoteStoragePlatform eRemoteStoragePlatform ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!pchFile || !pchFile[0]) return false; + + return true; +} + + +// file operations that cause network IO +UGCFileWriteStreamHandle_t Steam_Remote_Storage::FileWriteStreamOpen( const char *pchFile ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!pchFile || !pchFile[0]) return k_UGCFileStreamHandleInvalid; + + static UGCFileWriteStreamHandle_t handle; + ++handle; + struct Stream_Write stream_write; + stream_write.file_name = std::string(pchFile); + stream_write.write_stream_handle = handle; + stream_writes.push_back(stream_write); + return stream_write.write_stream_handle; +} + +bool Steam_Remote_Storage::FileWriteStreamWriteChunk( UGCFileWriteStreamHandle_t writeHandle, const void *pvData, int32 cubData ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!pvData || cubData < 0) return false; + + auto request = std::find_if(stream_writes.begin(), stream_writes.end(), [&writeHandle](struct Stream_Write const& item) { return item.write_stream_handle == writeHandle; }); + if (stream_writes.end() == request) + return false; + + std::copy((char *)pvData, (char *)pvData + cubData, std::back_inserter(request->file_data)); + return true; +} + +bool Steam_Remote_Storage::FileWriteStreamClose( UGCFileWriteStreamHandle_t writeHandle ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + auto request = std::find_if(stream_writes.begin(), stream_writes.end(), [&writeHandle](struct Stream_Write const& item) { return item.write_stream_handle == writeHandle; }); + if (stream_writes.end() == request) + return false; + + local_storage->store_data(Local_Storage::remote_storage_folder, request->file_name, request->file_data.data(), request->file_data.size()); + stream_writes.erase(request); + return true; +} + +bool Steam_Remote_Storage::FileWriteStreamCancel( UGCFileWriteStreamHandle_t writeHandle ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + auto request = std::find_if(stream_writes.begin(), stream_writes.end(), [&writeHandle](struct Stream_Write const& item) { return item.write_stream_handle == writeHandle; }); + if (stream_writes.end() == request) + return false; + + stream_writes.erase(request); + return true; +} + +// file information +bool Steam_Remote_Storage::FileExists( const char *pchFile ) +{ + PRINT_DEBUG("%s", pchFile); + std::lock_guard lock(global_mutex); + if (!pchFile || !pchFile[0]) return false; + + return local_storage->file_exists(Local_Storage::remote_storage_folder, pchFile); +} + +bool Steam_Remote_Storage::FilePersisted( const char *pchFile ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!pchFile || !pchFile[0]) return false; + + return local_storage->file_exists(Local_Storage::remote_storage_folder, pchFile); +} + +int32 Steam_Remote_Storage::GetFileSize( const char *pchFile ) +{ + PRINT_DEBUG("%s", pchFile); + std::lock_guard lock(global_mutex); + if (!pchFile || !pchFile[0]) return 0; + + return local_storage->file_size(Local_Storage::remote_storage_folder, pchFile); +} + +int64 Steam_Remote_Storage::GetFileTimestamp( const char *pchFile ) +{ + PRINT_DEBUG("'%s'", pchFile); + std::lock_guard lock(global_mutex); + if (!pchFile || !pchFile[0]) return 0; + + return local_storage->file_timestamp(Local_Storage::remote_storage_folder, pchFile); +} + +ERemoteStoragePlatform Steam_Remote_Storage::GetSyncPlatforms( const char *pchFile ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return k_ERemoteStoragePlatformAll; +} + + +// iteration +int32 Steam_Remote_Storage::GetFileCount() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + int32 num = local_storage->count_files(Local_Storage::remote_storage_folder); + PRINT_DEBUG("count: %i", num); + return num; +} + +const char* Steam_Remote_Storage::GetFileNameAndSize( int iFile, int32 *pnFileSizeInBytes ) +{ + PRINT_DEBUG("%i", iFile); + std::lock_guard lock(global_mutex); + + static char output_filename[MAX_FILENAME_LENGTH]; + if (local_storage->iterate_file(Local_Storage::remote_storage_folder, iFile, output_filename, pnFileSizeInBytes)) { + PRINT_DEBUG("|%s|, size: %i", output_filename, pnFileSizeInBytes ? *pnFileSizeInBytes : 0); + return output_filename; + } else { + return ""; + } +} + + +// configuration management +bool Steam_Remote_Storage::GetQuota( uint64 *pnTotalBytes, uint64 *puAvailableBytes ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + uint64 quota = 2 << 26; + if (pnTotalBytes) *pnTotalBytes = quota; + if (puAvailableBytes) *puAvailableBytes = (quota); + return true; +} + +bool Steam_Remote_Storage::GetQuota( int32 *pnTotalBytes, int32 *puAvailableBytes ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + uint64 quota = 2 << 26; + if (pnTotalBytes) *pnTotalBytes = quota; + if (puAvailableBytes) *puAvailableBytes = (quota); + return true; +} + +bool Steam_Remote_Storage::IsCloudEnabledForAccount() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return true; +} + +bool Steam_Remote_Storage::IsCloudEnabledForApp() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return steam_cloud_enabled; +} + +bool Steam_Remote_Storage::IsCloudEnabledThisApp() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return steam_cloud_enabled; +} + +void Steam_Remote_Storage::SetCloudEnabledForApp( bool bEnabled ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + steam_cloud_enabled = bEnabled; +} + +bool Steam_Remote_Storage::SetCloudEnabledThisApp( bool bEnabled ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + steam_cloud_enabled = bEnabled; + return true; +} + +// user generated content + +// Downloads a UGC file. A priority value of 0 will download the file immediately, +// otherwise it will wait to download the file until all downloads with a lower priority +// value are completed. Downloads with equal priority will occur simultaneously. +STEAM_CALL_RESULT( RemoteStorageDownloadUGCResult_t ) +SteamAPICall_t Steam_Remote_Storage::UGCDownload( UGCHandle_t hContent, uint32 unPriority ) +{ + PRINT_DEBUG("%llu", hContent); + std::lock_guard lock(global_mutex); + if (hContent == k_UGCHandleInvalid) return k_uAPICallInvalid; + + RemoteStorageDownloadUGCResult_t data{}; + data.m_hFile = hContent; + + if (shared_files.count(hContent)) { + data.m_eResult = k_EResultOK; + data.m_nAppID = settings->get_local_game_id().AppID(); + data.m_ulSteamIDOwner = settings->get_local_steam_id().ConvertToUint64(); + data.m_nSizeInBytes = local_storage->file_size(Local_Storage::remote_storage_folder, shared_files[hContent]); + + shared_files[hContent].copy(data.m_pchFileName, sizeof(data.m_pchFileName) - 1); + + downloaded_files[hContent].source = Downloaded_File::DownloadSource::AfterFileShare; + downloaded_files[hContent].file = shared_files[hContent]; + downloaded_files[hContent].total_size = data.m_nSizeInBytes; + } else if (auto query_res = ugc_bridge->get_ugc_query_result(hContent)) { + auto mod = settings->getMod(query_res.value().mod_id); + auto &mod_name = query_res.value().is_primary_file + ? mod.primaryFileName + : mod.previewFileName; + int32 mod_size = query_res.value().is_primary_file + ? mod.primaryFileSize + : mod.previewFileSize; + + data.m_eResult = k_EResultOK; + data.m_nAppID = settings->get_local_game_id().AppID(); + data.m_ulSteamIDOwner = mod.steamIDOwner; + data.m_nSizeInBytes = mod_size; + data.m_ulSteamIDOwner = mod.steamIDOwner; + + mod_name.copy(data.m_pchFileName, sizeof(data.m_pchFileName) - 1); + + downloaded_files[hContent].source = Downloaded_File::DownloadSource::AfterSendQueryUGCRequest; + downloaded_files[hContent].file = mod_name; + downloaded_files[hContent].total_size = mod_size; + + downloaded_files[hContent].mod_query_info = query_res.value(); + + } else { + data.m_eResult = k_EResultFileNotFound; //TODO: not sure if this is the right result + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +STEAM_CALL_RESULT( RemoteStorageDownloadUGCResult_t ) +SteamAPICall_t Steam_Remote_Storage::UGCDownload( UGCHandle_t hContent ) +{ + PRINT_DEBUG("old"); + return UGCDownload(hContent, 1); +} + + +// Gets the amount of data downloaded so far for a piece of content. pnBytesExpected can be 0 if function returns false +// or if the transfer hasn't started yet, so be careful to check for that before dividing to get a percentage +bool Steam_Remote_Storage::GetUGCDownloadProgress( UGCHandle_t hContent, int32 *pnBytesDownloaded, int32 *pnBytesExpected ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + +bool Steam_Remote_Storage::GetUGCDownloadProgress( UGCHandle_t hContent, uint32 *pnBytesDownloaded, uint32 *pnBytesExpected ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + + return false; +} + + +// Gets metadata for a file after it has been downloaded. This is the same metadata given in the RemoteStorageDownloadUGCResult_t call result +bool Steam_Remote_Storage::GetUGCDetails( UGCHandle_t hContent, AppId_t *pnAppID, STEAM_OUT_STRING() char **ppchName, int32 *pnFileSizeInBytes, STEAM_OUT_STRUCT() CSteamID *pSteamIDOwner ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + + +// After download, gets the content of the file. +// Small files can be read all at once by calling this function with an offset of 0 and cubDataToRead equal to the size of the file. +// Larger files can be read in chunks to reduce memory usage (since both sides of the IPC client and the game itself must allocate +// enough memory for each chunk). Once the last byte is read, the file is implicitly closed and further calls to UGCRead will fail +// unless UGCDownload is called again. +// For especially large files (anything over 100MB) it is a requirement that the file is read in chunks. +int32 Steam_Remote_Storage::UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead, uint32 cOffset, EUGCReadAction eAction ) +{ + PRINT_DEBUG("%llu, %p, %i, %u, %i", hContent, pvData, cubDataToRead, cOffset, eAction); + std::lock_guard lock(global_mutex); + + auto f_itr = downloaded_files.find(hContent); + if (hContent == k_UGCHandleInvalid || (downloaded_files.end() == f_itr) || cubDataToRead < 0) { + return -1; //TODO: is this the right return value? + } + + int read_data = -1; + uint64 total_size = 0; + Downloaded_File &dwf = f_itr->second; + + // depending on the download source, we have to decide where to grab the content/data + switch (dwf.source) + { + case Downloaded_File::DownloadSource::AfterFileShare: { + PRINT_DEBUG(" source = AfterFileShare '%s'", dwf.file.c_str()); + read_data = local_storage->get_data(Local_Storage::remote_storage_folder, dwf.file, (char *)pvData, cubDataToRead, cOffset); + total_size = dwf.total_size; + } + break; + + case Downloaded_File::DownloadSource::AfterSendQueryUGCRequest: + case Downloaded_File::DownloadSource::FromUGCDownloadToLocation: { + PRINT_DEBUG(" source = AfterSendQueryUGCRequest || FromUGCDownloadToLocation [%i]", (int)dwf.source); + auto mod = settings->getMod(dwf.mod_query_info.mod_id); + auto &mod_name = dwf.mod_query_info.is_primary_file + ? mod.primaryFileName + : mod.previewFileName; + + std::string mod_fullpath{}; + if (dwf.source == Downloaded_File::DownloadSource::AfterSendQueryUGCRequest) { + std::string mod_base_path = dwf.mod_query_info.is_primary_file + ? mod.path + : Local_Storage::get_game_settings_path() + "mod_images" + PATH_SEPARATOR + std::to_string(mod.id); + + mod_fullpath = common_helpers::to_absolute(mod_name, mod_base_path); + } else { // Downloaded_File::DownloadSource::FromUGCDownloadToLocation + mod_fullpath = dwf.download_to_location_fullpath; + } + + read_data = Local_Storage::get_file_data(mod_fullpath, (char *)pvData, cubDataToRead, cOffset); + PRINT_DEBUG(" mod file '%s' [%i]", mod_fullpath.c_str(), read_data); + total_size = dwf.total_size; + } + break; + + default: + PRINT_DEBUG(" unhandled download source %i", (int)dwf.source); + return -1; //TODO: is this the right return value? + break; + } + + PRINT_DEBUG(" read bytes = %i", read_data); + if (read_data < 0) return -1; //TODO: is this the right return value? + + if (eAction == k_EUGCRead_Close || + (eAction == k_EUGCRead_ContinueReadingUntilFinished && (read_data < cubDataToRead || (cOffset + cubDataToRead) >= total_size))) { + downloaded_files.erase(hContent); + } + + return read_data; +} + +int32 Steam_Remote_Storage::UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + + return UGCRead( hContent, pvData, cubDataToRead, 0); +} + +int32 Steam_Remote_Storage::UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead, uint32 cOffset) +{ + PRINT_DEBUG("old2"); + std::lock_guard lock(global_mutex); + + return UGCRead(hContent, pvData, cubDataToRead, cOffset, k_EUGCRead_ContinueReadingUntilFinished); +} + +// Functions to iterate through UGC that has finished downloading but has not yet been read via UGCRead() +int32 Steam_Remote_Storage::GetCachedUGCCount() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return 0; +} + +UGCHandle_t Steam_Remote_Storage::GetCachedUGCHandle( int32 iCachedContent ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return k_UGCHandleInvalid; +} + + +// The following functions are only necessary on the Playstation 3. On PC & Mac, the Steam client will handle these operations for you +// On Playstation 3, the game controls which files are stored in the cloud, via FilePersist, FileFetch, and FileForget. + +#if defined(_PS3) || defined(_SERVER) +// Connect to Steam and get a list of files in the Cloud - results in a RemoteStorageAppSyncStatusCheck_t callback +void Steam_Remote_Storage::GetFileListFromServer() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + +} + +// Indicate this file should be downloaded in the next sync +bool Steam_Remote_Storage::FileFetch( const char *pchFile ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return true; +} + +// Indicate this file should be persisted in the next sync +bool Steam_Remote_Storage::FilePersist( const char *pchFile ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return true; +} + +// Pull any requested files down from the Cloud - results in a RemoteStorageAppSyncedClient_t callback +bool Steam_Remote_Storage::SynchronizeToClient() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + +} + +// Upload any requested files to the Cloud - results in a RemoteStorageAppSyncedServer_t callback +bool Steam_Remote_Storage::SynchronizeToServer() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + +} + +// Reset any fetch/persist/etc requests +bool Steam_Remote_Storage::ResetFileRequestState() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + +} + +#endif + +// publishing UGC +STEAM_CALL_RESULT( RemoteStoragePublishFileProgress_t ) +SteamAPICall_t Steam_Remote_Storage::PublishWorkshopFile( const char *pchFile, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags, EWorkshopFileType eWorkshopFileType ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return k_uAPICallInvalid; +} + +PublishedFileUpdateHandle_t Steam_Remote_Storage::CreatePublishedFileUpdateRequest( PublishedFileId_t unPublishedFileId ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return k_PublishedFileUpdateHandleInvalid; +} + +bool Steam_Remote_Storage::UpdatePublishedFileFile( PublishedFileUpdateHandle_t updateHandle, const char *pchFile ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return false; +} + +SteamAPICall_t Steam_Remote_Storage::PublishFile( const char *pchFile, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return k_uAPICallInvalid; +} + +SteamAPICall_t Steam_Remote_Storage::PublishWorkshopFile( const char *pchFile, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, SteamParamStringArray_t *pTags ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return k_uAPICallInvalid; +} + +SteamAPICall_t Steam_Remote_Storage::UpdatePublishedFile( RemoteStorageUpdatePublishedFileRequest_t updatePublishedFileRequest ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return k_uAPICallInvalid; +} + +bool Steam_Remote_Storage::UpdatePublishedFilePreviewFile( PublishedFileUpdateHandle_t updateHandle, const char *pchPreviewFile ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return false; +} + +bool Steam_Remote_Storage::UpdatePublishedFileTitle( PublishedFileUpdateHandle_t updateHandle, const char *pchTitle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return false; +} + +bool Steam_Remote_Storage::UpdatePublishedFileDescription( PublishedFileUpdateHandle_t updateHandle, const char *pchDescription ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return false; +} + +bool Steam_Remote_Storage::UpdatePublishedFileVisibility( PublishedFileUpdateHandle_t updateHandle, ERemoteStoragePublishedFileVisibility eVisibility ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return false; +} + +bool Steam_Remote_Storage::UpdatePublishedFileTags( PublishedFileUpdateHandle_t updateHandle, SteamParamStringArray_t *pTags ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return false; +} + +STEAM_CALL_RESULT( RemoteStorageUpdatePublishedFileResult_t ) +SteamAPICall_t Steam_Remote_Storage::CommitPublishedFileUpdate( PublishedFileUpdateHandle_t updateHandle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return k_uAPICallInvalid; +} + +// Gets published file details for the given publishedfileid. If unMaxSecondsOld is greater than 0, +// cached data may be returned, depending on how long ago it was cached. A value of 0 will force a refresh. +// A value of k_WorkshopForceLoadPublishedFileDetailsFromCache will use cached data if it exists, no matter how old it is. +STEAM_CALL_RESULT( RemoteStorageGetPublishedFileDetailsResult_t ) +SteamAPICall_t Steam_Remote_Storage::GetPublishedFileDetails( PublishedFileId_t unPublishedFileId, uint32 unMaxSecondsOld ) +{ + PRINT_DEBUG("TODO %llu %u", unPublishedFileId, unMaxSecondsOld); + //TODO: check what this function really returns + // TODO is this implementation correct? + std::lock_guard lock(global_mutex); + if (unPublishedFileId == k_PublishedFileIdInvalid) return k_uAPICallInvalid; + + RemoteStorageGetPublishedFileDetailsResult_t data{}; + data.m_nPublishedFileId = unPublishedFileId; + + if (settings->isModInstalled(unPublishedFileId)) { + auto mod = settings->getMod(unPublishedFileId); + data.m_eResult = EResult::k_EResultOK; + data.m_bAcceptedForUse = mod.acceptedForUse; + data.m_bBanned = mod.banned; + data.m_bTagsTruncated = mod.tagsTruncated; + data.m_eFileType = mod.fileType; + data.m_eVisibility = mod.visibility; + data.m_hFile = mod.handleFile; + data.m_hPreviewFile = mod.handlePreviewFile; + data.m_nConsumerAppID = settings->get_local_game_id().AppID(); // TODO is this correct? + data.m_nCreatorAppID = settings->get_local_game_id().AppID(); // TODO is this correct? + data.m_nFileSize = mod.primaryFileSize; + data.m_nPreviewFileSize = mod.previewFileSize; + data.m_rtimeCreated = mod.timeCreated; + data.m_rtimeUpdated = mod.timeUpdated; + data.m_ulSteamIDOwner = mod.steamIDOwner; + + mod.primaryFileName.copy(data.m_pchFileName, sizeof(data.m_pchFileName) - 1); + mod.description.copy(data.m_rgchDescription, sizeof(data.m_rgchDescription) - 1); + mod.tags.copy(data.m_rgchTags, sizeof(data.m_rgchTags) - 1); + mod.title.copy(data.m_rgchTitle, sizeof(data.m_rgchTitle) - 1); + mod.workshopItemURL.copy(data.m_rgchURL, sizeof(data.m_rgchURL) - 1); + + } else { + data.m_eResult = EResult::k_EResultFail; // TODO is this correct? + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + + // return 0; +/* + std::lock_guard lock(global_mutex); + RemoteStorageGetPublishedFileDetailsResult_t data = {}; + data.m_eResult = k_EResultFail; + data.m_nPublishedFileId = unPublishedFileId; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +*/ +} + +STEAM_CALL_RESULT( RemoteStorageGetPublishedFileDetailsResult_t ) +SteamAPICall_t Steam_Remote_Storage::GetPublishedFileDetails( PublishedFileId_t unPublishedFileId ) +{ + PRINT_DEBUG_TODO(); + return GetPublishedFileDetails(unPublishedFileId, 0); +} + +STEAM_CALL_RESULT( RemoteStorageDeletePublishedFileResult_t ) +SteamAPICall_t Steam_Remote_Storage::DeletePublishedFile( PublishedFileId_t unPublishedFileId ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return k_uAPICallInvalid; +} + +// enumerate the files that the current user published with this app +STEAM_CALL_RESULT( RemoteStorageEnumerateUserPublishedFilesResult_t ) +SteamAPICall_t Steam_Remote_Storage::EnumerateUserPublishedFiles( uint32 unStartIndex ) +{ + PRINT_DEBUG("TODO %u", unStartIndex); + // TODO is this implementation correct? + std::lock_guard lock(global_mutex); + RemoteStorageEnumerateUserPublishedFilesResult_t data{}; + + // collect all published mods by this user + auto mods = settings->modSet(); + std::vector user_pubed{}; + for (auto& id : mods) { + auto mod = settings->getMod(id); + if (mod.steamIDOwner == settings->get_local_steam_id().ConvertToUint64()) { + user_pubed.push_back(id); + } + } + uint32_t modCount = (uint32_t)user_pubed.size(); + + if (unStartIndex >= modCount) { + data.m_eResult = EResult::k_EResultInvalidParam; // TODO is this correct? + } else { + data.m_eResult = EResult::k_EResultOK; + data.m_nTotalResultCount = modCount - unStartIndex; // total count starting from this index + std::vector::iterator i = user_pubed.begin(); + std::advance(i, unStartIndex); + uint32_t iterated = 0; + for (; i != user_pubed.end() && iterated < k_unEnumeratePublishedFilesMaxResults; i++) { + PublishedFileId_t modId = *i; + auto mod = settings->getMod(modId); + data.m_rgPublishedFileId[iterated] = modId; + iterated++; + PRINT_DEBUG(" EnumerateUserPublishedFiles file %llu", modId); + } + data.m_nResultsReturned = iterated; + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +STEAM_CALL_RESULT( RemoteStorageSubscribePublishedFileResult_t ) +SteamAPICall_t Steam_Remote_Storage::SubscribePublishedFile( PublishedFileId_t unPublishedFileId ) +{ + PRINT_DEBUG("TODO %llu", unPublishedFileId); + std::lock_guard lock(global_mutex); + if (unPublishedFileId == k_PublishedFileIdInvalid) return k_uAPICallInvalid; + + // TODO is this implementation correct? + RemoteStorageSubscribePublishedFileResult_t data{}; + data.m_nPublishedFileId = unPublishedFileId; + + if (settings->isModInstalled(unPublishedFileId)) { + data.m_eResult = EResult::k_EResultOK; + ugc_bridge->add_subbed_mod(unPublishedFileId); + } else { + data.m_eResult = EResult::k_EResultFail; // TODO is this correct? + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +STEAM_CALL_RESULT( RemoteStorageEnumerateUserSubscribedFilesResult_t ) +SteamAPICall_t Steam_Remote_Storage::EnumerateUserSubscribedFiles( uint32 unStartIndex ) +{ + // https://partner.steamgames.com/doc/api/ISteamRemoteStorage + PRINT_DEBUG("%u", unStartIndex); + std::lock_guard lock(global_mutex); + // Get ready for a working but bad implementation - Detanup01 + RemoteStorageEnumerateUserSubscribedFilesResult_t data{}; + uint32_t modCount = (uint32_t)ugc_bridge->subbed_mods_count(); + if (unStartIndex >= modCount) { + data.m_eResult = EResult::k_EResultInvalidParam; // is this correct? + } else { + data.m_eResult = k_EResultOK; + data.m_nTotalResultCount = modCount - unStartIndex; // total amount starting from given index + std::set::iterator i = ugc_bridge->subbed_mods_itr_begin(); + std::advance(i, unStartIndex); + uint32_t iterated = 0; + for (; i != ugc_bridge->subbed_mods_itr_end() && iterated < k_unEnumeratePublishedFilesMaxResults; i++) { + PublishedFileId_t modId = *i; + auto mod = settings->getMod(modId); + uint32 time = mod.timeAddedToUserList; //this can be changed, default is 1554997000 + data.m_rgPublishedFileId[iterated] = modId; + data.m_rgRTimeSubscribed[iterated] = time; + iterated++; + PRINT_DEBUG(" EnumerateUserSubscribedFiles file %llu", modId); + } + data.m_nResultsReturned = iterated; + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +STEAM_CALL_RESULT( RemoteStorageUnsubscribePublishedFileResult_t ) +SteamAPICall_t Steam_Remote_Storage::UnsubscribePublishedFile( PublishedFileId_t unPublishedFileId ) +{ + PRINT_DEBUG("TODO %llu", unPublishedFileId); + std::lock_guard lock(global_mutex); + if (unPublishedFileId == k_PublishedFileIdInvalid) return k_uAPICallInvalid; + + // TODO is this implementation correct? + RemoteStorageUnsubscribePublishedFileResult_t data{}; + data.m_nPublishedFileId = unPublishedFileId; + // TODO is this correct? + if (ugc_bridge->has_subbed_mod(unPublishedFileId)) { + data.m_eResult = k_EResultOK; + ugc_bridge->remove_subbed_mod(unPublishedFileId); + } else { + data.m_eResult = k_EResultFail; + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +bool Steam_Remote_Storage::UpdatePublishedFileSetChangeDescription( PublishedFileUpdateHandle_t updateHandle, const char *pchChangeDescription ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + +STEAM_CALL_RESULT( RemoteStorageGetPublishedItemVoteDetailsResult_t ) +SteamAPICall_t Steam_Remote_Storage::GetPublishedItemVoteDetails( PublishedFileId_t unPublishedFileId ) +{ + PRINT_DEBUG_TODO(); + // TODO s this implementation correct? + std::lock_guard lock(global_mutex); + if (unPublishedFileId == k_PublishedFileIdInvalid) return k_uAPICallInvalid; + + RemoteStorageGetPublishedItemVoteDetailsResult_t data{}; + data.m_unPublishedFileId = unPublishedFileId; + if (settings->isModInstalled(unPublishedFileId)) { + data.m_eResult = EResult::k_EResultOK; + auto mod = settings->getMod(unPublishedFileId); + data.m_fScore = mod.score; + data.m_nReports = 0; // TODO is this ok? + data.m_nVotesAgainst = mod.votesDown; + data.m_nVotesFor = mod.votesUp; + } else { + data.m_eResult = EResult::k_EResultFail; // TODO is this correct? + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +STEAM_CALL_RESULT( RemoteStorageUpdateUserPublishedItemVoteResult_t ) +SteamAPICall_t Steam_Remote_Storage::UpdateUserPublishedItemVote( PublishedFileId_t unPublishedFileId, bool bVoteUp ) +{ + // I assume this function is supposed to increase the upvotes of the mod, + // given that the mod owner is the current user + PRINT_DEBUG_TODO(); + // TODO is this implementation correct? + std::lock_guard lock(global_mutex); + if (unPublishedFileId == k_PublishedFileIdInvalid) return k_uAPICallInvalid; + + RemoteStorageUpdateUserPublishedItemVoteResult_t data{}; + data.m_nPublishedFileId = unPublishedFileId; + if (settings->isModInstalled(unPublishedFileId)) { + auto mod = settings->getMod(unPublishedFileId); + if (mod.steamIDOwner == settings->get_local_steam_id().ConvertToUint64()) { + data.m_eResult = EResult::k_EResultOK; + } else { // not published by this user + data.m_eResult = EResult::k_EResultFail; // TODO is this correct? + } + } else { // mod not installed + data.m_eResult = EResult::k_EResultFail; // TODO is this correct? + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +STEAM_CALL_RESULT( RemoteStorageGetPublishedItemVoteDetailsResult_t ) +SteamAPICall_t Steam_Remote_Storage::GetUserPublishedItemVoteDetails( PublishedFileId_t unPublishedFileId ) +{ + PRINT_DEBUG_ENTRY(); + + // TODO is this implementation correct? + std::lock_guard lock(global_mutex); + if (unPublishedFileId == k_PublishedFileIdInvalid) return k_uAPICallInvalid; + + RemoteStorageGetPublishedItemVoteDetailsResult_t data{}; + data.m_unPublishedFileId = unPublishedFileId; + if (settings->isModInstalled(unPublishedFileId)) { + auto mod = settings->getMod(unPublishedFileId); + if (mod.steamIDOwner == settings->get_local_steam_id().ConvertToUint64()) { + data.m_eResult = EResult::k_EResultOK; + data.m_fScore = mod.score; + data.m_nReports = 0; // TODO is this ok? + data.m_nVotesAgainst = mod.votesDown; + data.m_nVotesFor = mod.votesUp; + } else { // not published by this user + data.m_eResult = EResult::k_EResultFail; // TODO is this correct? + } + } else { // mod not installed + data.m_eResult = EResult::k_EResultFail; // TODO is this correct? + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + + return 0; +} + +STEAM_CALL_RESULT( RemoteStorageEnumerateUserPublishedFilesResult_t ) +SteamAPICall_t Steam_Remote_Storage::EnumerateUserSharedWorkshopFiles( CSteamID steamId, uint32 unStartIndex, SteamParamStringArray_t *pRequiredTags, SteamParamStringArray_t *pExcludedTags ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + RemoteStorageEnumerateUserPublishedFilesResult_t data{}; + data.m_eResult = k_EResultOK; + data.m_nResultsReturned = 0; + data.m_nTotalResultCount = 0; + //data.m_rgPublishedFileId; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +STEAM_CALL_RESULT( RemoteStorageEnumerateUserPublishedFilesResult_t ) +SteamAPICall_t Steam_Remote_Storage::EnumerateUserSharedWorkshopFiles(AppId_t nAppId, CSteamID steamId, uint32 unStartIndex, SteamParamStringArray_t *pRequiredTags, SteamParamStringArray_t *pExcludedTags ) +{ + PRINT_DEBUG("old"); + return EnumerateUserSharedWorkshopFiles(steamId, unStartIndex, pRequiredTags, pExcludedTags); +} + +STEAM_CALL_RESULT( RemoteStoragePublishFileProgress_t ) +SteamAPICall_t Steam_Remote_Storage::PublishVideo( EWorkshopVideoProvider eVideoProvider, const char *pchVideoAccount, const char *pchVideoIdentifier, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_uAPICallInvalid; +} + +STEAM_CALL_RESULT( RemoteStoragePublishFileProgress_t ) +SteamAPICall_t Steam_Remote_Storage::PublishVideo(const char *pchFileName, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_uAPICallInvalid; +} + +STEAM_CALL_RESULT( RemoteStorageSetUserPublishedFileActionResult_t ) +SteamAPICall_t Steam_Remote_Storage::SetUserPublishedFileAction( PublishedFileId_t unPublishedFileId, EWorkshopFileAction eAction ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_uAPICallInvalid; +} + +STEAM_CALL_RESULT( RemoteStorageEnumeratePublishedFilesByUserActionResult_t ) +SteamAPICall_t Steam_Remote_Storage::EnumeratePublishedFilesByUserAction( EWorkshopFileAction eAction, uint32 unStartIndex ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_uAPICallInvalid; +} + +// this method enumerates the public view of workshop files +STEAM_CALL_RESULT( RemoteStorageEnumerateWorkshopFilesResult_t ) +SteamAPICall_t Steam_Remote_Storage::EnumeratePublishedWorkshopFiles( EWorkshopEnumerationType eEnumerationType, uint32 unStartIndex, uint32 unCount, uint32 unDays, SteamParamStringArray_t *pTags, SteamParamStringArray_t *pUserTags ) +{ + PRINT_DEBUG_TODO(); + // TODO is this implementation correct? + std::lock_guard lock(global_mutex); + RemoteStorageEnumerateWorkshopFilesResult_t data{}; + data.m_eResult = EResult::k_EResultOK; + data.m_nResultsReturned = 0; + data.m_nTotalResultCount = 0; + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + + +STEAM_CALL_RESULT( RemoteStorageDownloadUGCResult_t ) +SteamAPICall_t Steam_Remote_Storage::UGCDownloadToLocation( UGCHandle_t hContent, const char *pchLocation, uint32 unPriority ) +{ + PRINT_DEBUG("TODO %llu %s", hContent, pchLocation); + // TODO is this implementation correct? + std::lock_guard lock(global_mutex); + //TODO: not sure if this is the right result + if (hContent == k_UGCHandleInvalid || !pchLocation || !pchLocation[0]) return k_uAPICallInvalid; + + RemoteStorageDownloadUGCResult_t data{}; + data.m_hFile = hContent; + data.m_nAppID = settings->get_local_game_id().AppID(); + + auto query_res = ugc_bridge->get_ugc_query_result(hContent); + if (query_res) { + auto mod = settings->getMod(query_res.value().mod_id); + auto &mod_name = query_res.value().is_primary_file + ? mod.primaryFileName + : mod.previewFileName; + std::string mod_base_path = query_res.value().is_primary_file + ? mod.path + : Local_Storage::get_game_settings_path() + "mod_images" + PATH_SEPARATOR + std::to_string(mod.id); + int32 mod_size = query_res.value().is_primary_file + ? mod.primaryFileSize + : mod.previewFileSize; + + data.m_eResult = k_EResultOK; + data.m_nAppID = settings->get_local_game_id().AppID(); + data.m_ulSteamIDOwner = mod.steamIDOwner; + data.m_nSizeInBytes = mod_size; + data.m_ulSteamIDOwner = mod.steamIDOwner; + + mod_name.copy(data.m_pchFileName, sizeof(data.m_pchFileName) - 1); + + // copy the file + const auto mod_fullpath = common_helpers::to_absolute(mod_name, mod_base_path); + copy_file(mod_fullpath, pchLocation); + + // TODO not sure about this though + downloaded_files[hContent].source = Downloaded_File::DownloadSource::FromUGCDownloadToLocation; + downloaded_files[hContent].file = mod_name; + downloaded_files[hContent].total_size = mod_size; + + downloaded_files[hContent].mod_query_info = query_res.value(); + downloaded_files[hContent].download_to_location_fullpath = pchLocation; + + } else { + data.m_eResult = k_EResultFileNotFound; //TODO: not sure if this is the right result + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +// Cloud dynamic state change notification +int32 Steam_Remote_Storage::GetLocalFileChangeCount() +{ + PRINT_DEBUG("GetLocalFileChangeCount"); + std::lock_guard lock(global_mutex); + + return 0; +} + +const char* Steam_Remote_Storage::GetLocalFileChange( int iFile, ERemoteStorageLocalFileChange *pEChangeType, ERemoteStorageFilePathType *pEFilePathType ) +{ + PRINT_DEBUG("GetLocalFileChange"); + std::lock_guard lock(global_mutex); + + return ""; +} + +// Indicate to Steam the beginning / end of a set of local file +// operations - for example, writing a game save that requires updating two files. +bool Steam_Remote_Storage::BeginFileWriteBatch() +{ + PRINT_DEBUG("BeginFileWriteBatch"); + std::lock_guard lock(global_mutex); + + return true; +} + +bool Steam_Remote_Storage::EndFileWriteBatch() +{ + PRINT_DEBUG("EndFileWriteBatch"); + std::lock_guard lock(global_mutex); + + return true; +} + diff --git a/dll/steam_remoteplay.cpp b/dll/steam_remoteplay.cpp new file mode 100644 index 00000000..9c6d411c --- /dev/null +++ b/dll/steam_remoteplay.cpp @@ -0,0 +1,142 @@ +/* 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 + . */ + +#include "dll/steam_remoteplay.h" + +void Steam_RemotePlay::steam_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_RemotePlay *steam_remoteplay = (Steam_RemotePlay *)object; + steam_remoteplay->Callback(msg); +} + +void Steam_RemotePlay::steam_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_RemotePlay *steam_remoteplay = (Steam_RemotePlay *)object; + steam_remoteplay->RunCallbacks(); +} + +Steam_RemotePlay::Steam_RemotePlay(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_RemotePlay::steam_callback, this); + this->run_every_runcb->add(&Steam_RemotePlay::steam_run_every_runcb, this); +} + +Steam_RemotePlay::~Steam_RemotePlay() +{ + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_RemotePlay::steam_callback, this); + this->run_every_runcb->remove(&Steam_RemotePlay::steam_run_every_runcb, this); +} + +// Get the number of currently connected Steam Remote Play sessions +uint32 Steam_RemotePlay::GetSessionCount() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// Get the currently connected Steam Remote Play session ID at the specified index. Returns zero if index is out of bounds. +uint32 Steam_RemotePlay::GetSessionID( int iSessionIndex ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// Get the SteamID of the connected user +CSteamID Steam_RemotePlay::GetSessionSteamID( uint32 unSessionID ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_steamIDNil; +} + +// Get the name of the session client device +// This returns NULL if the sessionID is not valid +const char* Steam_RemotePlay::GetSessionClientName( uint32 unSessionID ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return NULL; +} + +// Get the form factor of the session client device +ESteamDeviceFormFactor Steam_RemotePlay::GetSessionClientFormFactor( uint32 unSessionID ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return k_ESteamDeviceFormFactorUnknown; +} + +// Get the resolution, in pixels, of the session client device +// This is set to 0x0 if the resolution is not available +bool Steam_RemotePlay::BGetSessionClientResolution( uint32 unSessionID, int *pnResolutionX, int *pnResolutionY ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (pnResolutionX) *pnResolutionX = 0; + if (pnResolutionY) *pnResolutionY = 0; + return false; +} + +bool Steam_RemotePlay::BStartRemotePlayTogether( bool bShowOverlay ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +// Invite a friend to Remote Play Together +// This returns false if the invite can't be sent +bool Steam_RemotePlay::BSendRemotePlayTogetherInvite( CSteamID steamIDFriend ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +void Steam_RemotePlay::RunCallbacks() +{ + +} + +void Steam_RemotePlay::Callback(Common_Message *msg) +{ + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + + if (msg->low_level().type() == Low_Level::DISCONNECT) { + + } + } + + if (msg->has_networking_sockets()) { + + } +} diff --git a/dll/steam_screenshots.cpp b/dll/steam_screenshots.cpp index 88e33eac..22d99e90 100644 --- a/dll/steam_screenshots.cpp +++ b/dll/steam_screenshots.cpp @@ -21,6 +21,7 @@ Steam_Screenshots::Steam_Screenshots(class Local_Storage* local_storage, class S local_storage(local_storage), callbacks(callbacks) { + } ScreenshotHandle Steam_Screenshots::create_screenshot_handle() diff --git a/dll/steam_tv.cpp b/dll/steam_tv.cpp new file mode 100644 index 00000000..1a0159d4 --- /dev/null +++ b/dll/steam_tv.cpp @@ -0,0 +1,119 @@ +/* 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 + . */ + +#include "dll/steam_tv.h" + +void Steam_TV::steam_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_TV *steam_parties = (Steam_TV *)object; + steam_parties->Callback(msg); +} + +void Steam_TV::steam_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_TV *steam_parties = (Steam_TV *)object; + steam_parties->RunCallbacks(); +} + +Steam_TV::Steam_TV(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_TV::steam_callback, this); + this->run_every_runcb->add(&Steam_TV::steam_run_every_runcb, this); + +} + +Steam_TV::~Steam_TV() +{ + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_TV::steam_callback, this); + this->run_every_runcb->remove(&Steam_TV::steam_run_every_runcb, this); +} + +bool Steam_TV::IsBroadcasting(int *pnNumViewers) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +void Steam_TV::AddBroadcastGameData(const char * pchKey, const char * pchValue) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +void Steam_TV::RemoveBroadcastGameData(const char * pchKey) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +void Steam_TV::AddTimelineMarker(const char * pchTemplateName, bool bPersistent, uint8 nColorR, uint8 nColorG, uint8 nColorB) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +void Steam_TV::RemoveTimelineMarker() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +uint32 Steam_TV::AddRegion(const char * pchElementName, const char * pchTimelineDataSection, const SteamTVRegion_t * pSteamTVRegion, ESteamTVRegionBehavior eSteamTVRegionBehavior) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +void Steam_TV::RemoveRegion(uint32 unRegionHandle) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); +} + +void Steam_TV::RunCallbacks() +{ + +} + +void Steam_TV::Callback(Common_Message *msg) +{ + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + + if (msg->low_level().type() == Low_Level::DISCONNECT) { + + } + } + + if (msg->has_networking_sockets()) { + + } +} diff --git a/dll/steam_ugc.cpp b/dll/steam_ugc.cpp new file mode 100644 index 00000000..65feda8d --- /dev/null +++ b/dll/steam_ugc.cpp @@ -0,0 +1,1429 @@ +/* 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 + . */ + +#include "dll/steam_ugc.h" + +UGCQueryHandle_t Steam_UGC::new_ugc_query(bool return_all_subscribed, std::set return_only) +{ + std::lock_guard lock(global_mutex); + + ++handle; + if ((handle == 0) || (handle == k_UGCQueryHandleInvalid)) handle = 50; + + struct UGC_query query{}; + query.handle = handle; + query.return_all_subscribed = return_all_subscribed; + query.return_only = return_only; + ugc_queries.push_back(query); + PRINT_DEBUG("handle = %llu", query.handle); + return query.handle; +} + +std::optional Steam_UGC::get_query_ugc(UGCQueryHandle_t handle, uint32 index) +{ + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return std::nullopt; + if (index >= request->results.size()) return std::nullopt; + + auto it = request->results.begin(); + std::advance(it, index); + + PublishedFileId_t file_id = *it; + if (!settings->isModInstalled(file_id)) return std::nullopt; + + return settings->getMod(file_id); +} + +std::optional> Steam_UGC::get_query_ugc_tags(UGCQueryHandle_t handle, uint32 index) +{ + auto res = get_query_ugc(handle, index); + if (!res.has_value()) return std::nullopt; + + auto tags_tokens = std::vector{}; + std::stringstream ss(res.value().tags); + std::string tmp{}; + while(ss >> tmp) { + if (tmp.back() == ',') tmp = tmp.substr(0, tmp.size() - 1); + tags_tokens.push_back(tmp); + } + + return tags_tokens; + +} + +void Steam_UGC::set_details(PublishedFileId_t id, SteamUGCDetails_t *pDetails) +{ + if (pDetails) { + memset(pDetails, 0, sizeof(SteamUGCDetails_t)); + + pDetails->m_nPublishedFileId = id; + + if (settings->isModInstalled(id)) { + PRINT_DEBUG(" mod is installed, setting details"); + pDetails->m_eResult = k_EResultOK; + + auto mod = settings->getMod(id); + pDetails->m_bAcceptedForUse = mod.acceptedForUse; + pDetails->m_bBanned = mod.banned; + pDetails->m_bTagsTruncated = mod.tagsTruncated; + pDetails->m_eFileType = mod.fileType; + pDetails->m_eVisibility = mod.visibility; + pDetails->m_hFile = mod.handleFile; + pDetails->m_hPreviewFile = mod.handlePreviewFile; + pDetails->m_nConsumerAppID = settings->get_local_game_id().AppID(); + pDetails->m_nCreatorAppID = settings->get_local_game_id().AppID(); + pDetails->m_nFileSize = mod.primaryFileSize; + pDetails->m_nPreviewFileSize = mod.previewFileSize; + pDetails->m_rtimeCreated = mod.timeCreated; + pDetails->m_rtimeUpdated = mod.timeUpdated; + pDetails->m_ulSteamIDOwner = settings->get_local_steam_id().ConvertToUint64(); + + pDetails->m_rtimeAddedToUserList = mod.timeAddedToUserList; + pDetails->m_unVotesUp = mod.votesUp; + pDetails->m_unVotesDown = mod.votesDown; + pDetails->m_flScore = mod.score; + + mod.primaryFileName.copy(pDetails->m_pchFileName, sizeof(pDetails->m_pchFileName) - 1); + mod.description.copy(pDetails->m_rgchDescription, sizeof(pDetails->m_rgchDescription) - 1); + mod.tags.copy(pDetails->m_rgchTags, sizeof(pDetails->m_rgchTags) - 1); + mod.title.copy(pDetails->m_rgchTitle, sizeof(pDetails->m_rgchTitle) - 1); + mod.workshopItemURL.copy(pDetails->m_rgchURL, sizeof(pDetails->m_rgchURL) - 1); + + // TODO should we enable this? + // pDetails->m_unNumChildren = mod.numChildren; + } else { + PRINT_DEBUG(" mod isn't installed, returning failure"); + pDetails->m_eResult = k_EResultFail; + } + } +} + +void Steam_UGC::read_ugc_favorites() +{ + if (!local_storage->file_exists("", ugc_favorits_file)) return; + + unsigned int size = local_storage->file_size("", ugc_favorits_file); + if (!size) return; + + std::string data(size, '\0'); + int read = local_storage->get_data("", ugc_favorits_file, &data[0], (unsigned int)data.size()); + if ((size_t)read != data.size()) return; + + std::stringstream ss(data); + std::string line{}; + while (std::getline(ss, line)) { + try + { + unsigned long long fav_id = std::stoull(line); + favorites.insert(fav_id); + PRINT_DEBUG("added item to favorites %llu", fav_id); + } catch(...) { } + } + +} + +bool Steam_UGC::write_ugc_favorites() +{ + std::stringstream ss{}; + for (auto id : favorites) { + ss << id << "\n"; + ss.flush(); + } + auto file_data = ss.str(); + int stored = local_storage->store_data("", ugc_favorits_file, &file_data[0], file_data.size()); + return (size_t)stored == file_data.size(); +} + + +Steam_UGC::Steam_UGC(class Settings *settings, class Ugc_Remote_Storage_Bridge *ugc_bridge, class Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks) +{ + this->settings = settings; + this->ugc_bridge = ugc_bridge; + this->local_storage = local_storage; + this->callbacks = callbacks; + this->callback_results = callback_results; + + read_ugc_favorites(); +} + + +// Query UGC associated with a user. Creator app id or consumer app id must be valid and be set to the current running app. unPage should start at 1. +UGCQueryHandle_t Steam_UGC::CreateQueryUserUGCRequest( AccountID_t unAccountID, EUserUGCList eListType, EUGCMatchingUGCType eMatchingUGCType, EUserUGCListSortOrder eSortOrder, AppId_t nCreatorAppID, AppId_t nConsumerAppID, uint32 unPage ) +{ + PRINT_DEBUG("%u %i %i %i %u %u %u", unAccountID, eListType, eMatchingUGCType, eSortOrder, nCreatorAppID, nConsumerAppID, unPage); + std::lock_guard lock(global_mutex); + + if (nCreatorAppID != settings->get_local_game_id().AppID() || nConsumerAppID != settings->get_local_game_id().AppID()) return k_UGCQueryHandleInvalid; + if (unPage < 1) return k_UGCQueryHandleInvalid; + if (eListType < 0) return k_UGCQueryHandleInvalid; + if (unAccountID != settings->get_local_steam_id().GetAccountID()) return k_UGCQueryHandleInvalid; + + // TODO + return new_ugc_query(eListType == k_EUserUGCList_Subscribed || eListType == k_EUserUGCList_Published); +} + + +// Query for all matching UGC. Creator app id or consumer app id must be valid and be set to the current running app. unPage should start at 1. +UGCQueryHandle_t Steam_UGC::CreateQueryAllUGCRequest( EUGCQuery eQueryType, EUGCMatchingUGCType eMatchingeMatchingUGCTypeFileType, AppId_t nCreatorAppID, AppId_t nConsumerAppID, uint32 unPage ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + if (nCreatorAppID != settings->get_local_game_id().AppID() || nConsumerAppID != settings->get_local_game_id().AppID()) return k_UGCQueryHandleInvalid; + if (unPage < 1) return k_UGCQueryHandleInvalid; + if (eQueryType < 0) return k_UGCQueryHandleInvalid; + + // TODO + return new_ugc_query(); +} + +// Query for all matching UGC using the new deep paging interface. Creator app id or consumer app id must be valid and be set to the current running app. pchCursor should be set to NULL or "*" to get the first result set. +UGCQueryHandle_t Steam_UGC::CreateQueryAllUGCRequest( EUGCQuery eQueryType, EUGCMatchingUGCType eMatchingeMatchingUGCTypeFileType, AppId_t nCreatorAppID, AppId_t nConsumerAppID, const char *pchCursor ) +{ + PRINT_DEBUG("other"); + std::lock_guard lock(global_mutex); + + if (nCreatorAppID != settings->get_local_game_id().AppID() || nConsumerAppID != settings->get_local_game_id().AppID()) return k_UGCQueryHandleInvalid; + if (eQueryType < 0) return k_UGCQueryHandleInvalid; + + // TODO + return new_ugc_query(); +} + +// Query for the details of the given published file ids (the RequestUGCDetails call is deprecated and replaced with this) +UGCQueryHandle_t Steam_UGC::CreateQueryUGCDetailsRequest( PublishedFileId_t *pvecPublishedFileID, uint32 unNumPublishedFileIDs ) +{ + PRINT_DEBUG("%p, max file IDs = [%u]", pvecPublishedFileID, unNumPublishedFileIDs); + std::lock_guard lock(global_mutex); + + if (!pvecPublishedFileID) return k_UGCQueryHandleInvalid; + if (unNumPublishedFileIDs < 1) return k_UGCQueryHandleInvalid; + + // TODO + std::set only(pvecPublishedFileID, pvecPublishedFileID + unNumPublishedFileIDs); + +#ifndef EMU_RELEASE_BUILD + for (const auto &id : only) { + PRINT_DEBUG(" file ID = %llu", id); + } +#endif + + return new_ugc_query(false, only); +} + + +// Send the query to Steam +STEAM_CALL_RESULT( SteamUGCQueryCompleted_t ) +SteamAPICall_t Steam_UGC::SendQueryUGCRequest( UGCQueryHandle_t handle ) +{ + PRINT_DEBUG("%llu", handle); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return k_uAPICallInvalid; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) + return k_uAPICallInvalid; + + if (request->return_all_subscribed) { + request->results = std::set(ugc_bridge->subbed_mods_itr_begin(), ugc_bridge->subbed_mods_itr_end()); + } + + if (request->return_only.size()) { + for (auto & s : request->return_only) { + if (ugc_bridge->has_subbed_mod(s)) { + request->results.insert(s); + } + } + } + + // send these handles to steam_remote_storage since the game will later + // call Steam_Remote_Storage::UGCDownload() with these files handles (primary + preview) + for (auto fileid : request->results) { + auto mod = settings->getMod(fileid); + ugc_bridge->add_ugc_query_result(mod.handleFile, fileid, true); + ugc_bridge->add_ugc_query_result(mod.handlePreviewFile, fileid, false); + } + + SteamUGCQueryCompleted_t data = {}; + data.m_handle = handle; + data.m_eResult = k_EResultOK; + data.m_unNumResultsReturned = request->results.size(); + data.m_unTotalMatchingResults = request->results.size(); + data.m_bCachedData = false; + + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return ret; +} + + +// Retrieve an individual result after receiving the callback for querying UGC +bool Steam_UGC::GetQueryUGCResult( UGCQueryHandle_t handle, uint32 index, SteamUGCDetails_t *pDetails ) +{ + PRINT_DEBUG("%llu %u %p", handle, index, pDetails); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) { + return false; + } + + if (index >= request->results.size()) { + return false; + } + + auto it = request->results.begin(); + std::advance(it, index); + PublishedFileId_t file_id = *it; + set_details(file_id, pDetails); + return true; +} + +std::optional Steam_UGC::get_query_ugc_tag(UGCQueryHandle_t handle, uint32 index, uint32 indexTag) +{ + auto res = get_query_ugc_tags(handle, index); + if (!res.has_value()) return std::nullopt; + if (indexTag >= res.value().size()) return std::nullopt; + + std::string tmp = res.value()[indexTag]; + if (tmp.back() == ',') { + tmp = tmp.substr(0, tmp.size() - 1); + } + return tmp; +} + +uint32 Steam_UGC::GetQueryUGCNumTags( UGCQueryHandle_t handle, uint32 index ) +{ + PRINT_DEBUG_TODO(); + // TODO is this correct? + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return 0; + + auto res = get_query_ugc_tags(handle, index); + return res.has_value() ? res.value().size() : 0; +} + +bool Steam_UGC::GetQueryUGCTag( UGCQueryHandle_t handle, uint32 index, uint32 indexTag, STEAM_OUT_STRING_COUNT( cchValueSize ) char* pchValue, uint32 cchValueSize ) +{ + PRINT_DEBUG_TODO(); + // TODO is this correct? + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + if (!pchValue || !cchValueSize) return false; + + auto res = get_query_ugc_tag(handle, index, indexTag); + if (!res.has_value()) return false; + + memset(pchValue, 0, cchValueSize); + res.value().copy(pchValue, cchValueSize - 1); + return true; +} + +bool Steam_UGC::GetQueryUGCTagDisplayName( UGCQueryHandle_t handle, uint32 index, uint32 indexTag, STEAM_OUT_STRING_COUNT( cchValueSize ) char* pchValue, uint32 cchValueSize ) +{ + PRINT_DEBUG_TODO(); + // TODO is this correct? + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + if (!pchValue || !cchValueSize) return false; + + auto res = get_query_ugc_tag(handle, index, indexTag); + if (!res.has_value()) return false; + + memset(pchValue, 0, cchValueSize); + res.value().copy(pchValue, cchValueSize - 1); + return true; +} + +bool Steam_UGC::GetQueryUGCPreviewURL( UGCQueryHandle_t handle, uint32 index, STEAM_OUT_STRING_COUNT(cchURLSize) char *pchURL, uint32 cchURLSize ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + //TODO: escape simulator tries downloading this url and unsubscribes if it fails + if (handle == k_UGCQueryHandleInvalid) return false; + if (!pchURL || !cchURLSize) return false; + + auto res = get_query_ugc(handle, index); + if (!res.has_value()) return false; + + auto mod = res.value(); + PRINT_DEBUG("Steam_UGC:GetQueryUGCPreviewURL: '%s'", mod.previewURL.c_str()); + mod.previewURL.copy(pchURL, cchURLSize - 1); + return true; +} + + +bool Steam_UGC::GetQueryUGCMetadata( UGCQueryHandle_t handle, uint32 index, STEAM_OUT_STRING_COUNT(cchMetadatasize) char *pchMetadata, uint32 cchMetadatasize ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return false; +} + + +bool Steam_UGC::GetQueryUGCChildren( UGCQueryHandle_t handle, uint32 index, PublishedFileId_t* pvecPublishedFileID, uint32 cMaxEntries ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return false; +} + + +bool Steam_UGC::GetQueryUGCStatistic( UGCQueryHandle_t handle, uint32 index, EItemStatistic eStatType, uint64 *pStatValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return false; +} + +bool Steam_UGC::GetQueryUGCStatistic( UGCQueryHandle_t handle, uint32 index, EItemStatistic eStatType, uint32 *pStatValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return false; +} + +uint32 Steam_UGC::GetQueryUGCNumAdditionalPreviews( UGCQueryHandle_t handle, uint32 index ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return 0; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return 0; + + return 0; +} + + +bool Steam_UGC::GetQueryUGCAdditionalPreview( UGCQueryHandle_t handle, uint32 index, uint32 previewIndex, STEAM_OUT_STRING_COUNT(cchURLSize) char *pchURLOrVideoID, uint32 cchURLSize, STEAM_OUT_STRING_COUNT(cchURLSize) char *pchOriginalFileName, uint32 cchOriginalFileNameSize, EItemPreviewType *pPreviewType ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return false; +} + +bool Steam_UGC::GetQueryUGCAdditionalPreview( UGCQueryHandle_t handle, uint32 index, uint32 previewIndex, char *pchURLOrVideoID, uint32 cchURLSize, bool *hz ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return false; +} + +uint32 Steam_UGC::GetQueryUGCNumKeyValueTags( UGCQueryHandle_t handle, uint32 index ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return 0; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return 0; + + return 0; +} + + +bool Steam_UGC::GetQueryUGCKeyValueTag( UGCQueryHandle_t handle, uint32 index, uint32 keyValueTagIndex, STEAM_OUT_STRING_COUNT(cchKeySize) char *pchKey, uint32 cchKeySize, STEAM_OUT_STRING_COUNT(cchValueSize) char *pchValue, uint32 cchValueSize ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return false; +} + +bool Steam_UGC::GetQueryUGCKeyValueTag( UGCQueryHandle_t handle, uint32 index, const char *pchKey, STEAM_OUT_STRING_COUNT(cchValueSize) char *pchValue, uint32 cchValueSize ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return false; +} + +uint32 Steam_UGC::GetQueryUGCContentDescriptors( UGCQueryHandle_t handle, uint32 index, EUGCContentDescriptorID *pvecDescriptors, uint32 cMaxEntries ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return 0; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return 0; + + return 0; +} + +// Release the request to free up memory, after retrieving results +bool Steam_UGC::ReleaseQueryUGCRequest( UGCQueryHandle_t handle ) +{ + PRINT_DEBUG("%llu", handle); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + ugc_queries.erase(request); + return true; +} + + +// Options to set for querying UGC +bool Steam_UGC::AddRequiredTag( UGCQueryHandle_t handle, const char *pTagName ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + +bool Steam_UGC::AddRequiredTagGroup( UGCQueryHandle_t handle, const SteamParamStringArray_t *pTagGroups ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + +bool Steam_UGC::AddExcludedTag( UGCQueryHandle_t handle, const char *pTagName ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +bool Steam_UGC::SetReturnOnlyIDs( UGCQueryHandle_t handle, bool bReturnOnlyIDs ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +bool Steam_UGC::SetReturnKeyValueTags( UGCQueryHandle_t handle, bool bReturnKeyValueTags ) +{ + PRINT_DEBUG_TODO(); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +bool Steam_UGC::SetReturnLongDescription( UGCQueryHandle_t handle, bool bReturnLongDescription ) +{ + PRINT_DEBUG_TODO(); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +bool Steam_UGC::SetReturnMetadata( UGCQueryHandle_t handle, bool bReturnMetadata ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +bool Steam_UGC::SetReturnChildren( UGCQueryHandle_t handle, bool bReturnChildren ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +bool Steam_UGC::SetReturnAdditionalPreviews( UGCQueryHandle_t handle, bool bReturnAdditionalPreviews ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +bool Steam_UGC::SetReturnTotalOnly( UGCQueryHandle_t handle, bool bReturnTotalOnly ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +bool Steam_UGC::SetReturnPlaytimeStats( UGCQueryHandle_t handle, uint32 unDays ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +bool Steam_UGC::SetLanguage( UGCQueryHandle_t handle, const char *pchLanguage ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +bool Steam_UGC::SetAllowCachedResponse( UGCQueryHandle_t handle, uint32 unMaxAgeSeconds ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +// Options only for querying user UGC +bool Steam_UGC::SetCloudFileNameFilter( UGCQueryHandle_t handle, const char *pMatchCloudFileName ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +// Options only for querying all UGC +bool Steam_UGC::SetMatchAnyTag( UGCQueryHandle_t handle, bool bMatchAnyTag ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +bool Steam_UGC::SetSearchText( UGCQueryHandle_t handle, const char *pSearchText ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +bool Steam_UGC::SetRankedByTrendDays( UGCQueryHandle_t handle, uint32 unDays ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + + +bool Steam_UGC::AddRequiredKeyValueTag( UGCQueryHandle_t handle, const char *pKey, const char *pValue ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + +bool Steam_UGC::SetTimeCreatedDateRange( UGCQueryHandle_t handle, RTime32 rtStart, RTime32 rtEnd ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + +bool Steam_UGC::SetTimeUpdatedDateRange( UGCQueryHandle_t handle, RTime32 rtStart, RTime32 rtEnd ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (handle == k_UGCQueryHandleInvalid) return false; + + auto request = std::find_if(ugc_queries.begin(), ugc_queries.end(), [&handle](struct UGC_query const& item) { return item.handle == handle; }); + if (ugc_queries.end() == request) return false; + + return true; +} + +// DEPRECATED - Use CreateQueryUGCDetailsRequest call above instead! +SteamAPICall_t Steam_UGC::RequestUGCDetails( PublishedFileId_t nPublishedFileID, uint32 unMaxAgeSeconds ) +{ + PRINT_DEBUG("%llu", nPublishedFileID); + std::lock_guard lock(global_mutex); + + SteamUGCRequestUGCDetailsResult_t data{}; + data.m_bCachedData = false; + set_details(nPublishedFileID, &(data.m_details)); + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +SteamAPICall_t Steam_UGC::RequestUGCDetails( PublishedFileId_t nPublishedFileID ) +{ + PRINT_DEBUG("old"); + return RequestUGCDetails(nPublishedFileID, 0); +} + + +// Steam Workshop Creator API +STEAM_CALL_RESULT( CreateItemResult_t ) +SteamAPICall_t Steam_UGC::CreateItem( AppId_t nConsumerAppId, EWorkshopFileType eFileType ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return k_uAPICallInvalid; +} + // create new item for this app with no content attached yet + + +UGCUpdateHandle_t Steam_UGC::StartItemUpdate( AppId_t nConsumerAppId, PublishedFileId_t nPublishedFileID ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return k_UGCUpdateHandleInvalid; +} + // start an UGC item update. Set changed properties before commiting update with CommitItemUpdate() + + +bool Steam_UGC::SetItemTitle( UGCUpdateHandle_t handle, const char *pchTitle ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // change the title of an UGC item + + +bool Steam_UGC::SetItemDescription( UGCUpdateHandle_t handle, const char *pchDescription ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // change the description of an UGC item + + +bool Steam_UGC::SetItemUpdateLanguage( UGCUpdateHandle_t handle, const char *pchLanguage ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // specify the language of the title or description that will be set + + +bool Steam_UGC::SetItemMetadata( UGCUpdateHandle_t handle, const char *pchMetaData ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // change the metadata of an UGC item (max = k_cchDeveloperMetadataMax) + + +bool Steam_UGC::SetItemVisibility( UGCUpdateHandle_t handle, ERemoteStoragePublishedFileVisibility eVisibility ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // change the visibility of an UGC item + + +bool Steam_UGC::SetItemTags( UGCUpdateHandle_t updateHandle, const SteamParamStringArray_t *pTags ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + + return false; +} + +bool Steam_UGC::SetItemTags( UGCUpdateHandle_t updateHandle, const SteamParamStringArray_t *pTags, bool bAllowAdminTags ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // change the tags of an UGC item + +bool Steam_UGC::SetItemContent( UGCUpdateHandle_t handle, const char *pszContentFolder ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // update item content from this local folder + + +bool Steam_UGC::SetItemPreview( UGCUpdateHandle_t handle, const char *pszPreviewFile ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // change preview image file for this item. pszPreviewFile points to local image file, which must be under 1MB in size + +bool Steam_UGC::SetAllowLegacyUpload( UGCUpdateHandle_t handle, bool bAllowLegacyUpload ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + +bool Steam_UGC::RemoveAllItemKeyValueTags( UGCUpdateHandle_t handle ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // remove all existing key-value tags (you can add new ones via the AddItemKeyValueTag function) + +bool Steam_UGC::RemoveItemKeyValueTags( UGCUpdateHandle_t handle, const char *pchKey ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // remove any existing key-value tags with the specified key + + +bool Steam_UGC::AddItemKeyValueTag( UGCUpdateHandle_t handle, const char *pchKey, const char *pchValue ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // add new key-value tags for the item. Note that there can be multiple values for a tag. + + +bool Steam_UGC::AddItemPreviewFile( UGCUpdateHandle_t handle, const char *pszPreviewFile, EItemPreviewType type ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // add preview file for this item. pszPreviewFile points to local file, which must be under 1MB in size + + +bool Steam_UGC::AddItemPreviewVideo( UGCUpdateHandle_t handle, const char *pszVideoID ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // add preview video for this item + + +bool Steam_UGC::UpdateItemPreviewFile( UGCUpdateHandle_t handle, uint32 index, const char *pszPreviewFile ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // updates an existing preview file for this item. pszPreviewFile points to local file, which must be under 1MB in size + + +bool Steam_UGC::UpdateItemPreviewVideo( UGCUpdateHandle_t handle, uint32 index, const char *pszVideoID ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + // updates an existing preview video for this item + + +bool Steam_UGC::RemoveItemPreview( UGCUpdateHandle_t handle, uint32 index ) +{ + PRINT_DEBUG("%llu %u", handle, index); + std::lock_guard lock(global_mutex); + + return false; +} + // remove a preview by index starting at 0 (previews are sorted) + +bool Steam_UGC::AddContentDescriptor( UGCUpdateHandle_t handle, EUGCContentDescriptorID descid ) +{ + PRINT_DEBUG("%llu %u", handle, descid); + std::lock_guard lock(global_mutex); + + return false; +} + +bool Steam_UGC::RemoveContentDescriptor( UGCUpdateHandle_t handle, EUGCContentDescriptorID descid ) +{ + PRINT_DEBUG("%llu %u", handle, descid); + std::lock_guard lock(global_mutex); + + return false; +} + +STEAM_CALL_RESULT( SubmitItemUpdateResult_t ) +SteamAPICall_t Steam_UGC::SubmitItemUpdate( UGCUpdateHandle_t handle, const char *pchChangeNote ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return k_uAPICallInvalid; +} + // commit update process started with StartItemUpdate() + + +EItemUpdateStatus Steam_UGC::GetItemUpdateProgress( UGCUpdateHandle_t handle, uint64 *punBytesProcessed, uint64* punBytesTotal ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return k_EItemUpdateStatusInvalid; +} + + +// Steam Workshop Consumer API + +STEAM_CALL_RESULT( SetUserItemVoteResult_t ) +SteamAPICall_t Steam_UGC::SetUserItemVote( PublishedFileId_t nPublishedFileID, bool bVoteUp ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!settings->isModInstalled(nPublishedFileID)) return k_uAPICallInvalid; // TODO is this correct + + auto mod = settings->getMod(nPublishedFileID); + SetUserItemVoteResult_t data{}; + data.m_eResult = EResult::k_EResultOK; + data.m_nPublishedFileId = nPublishedFileID; + if (bVoteUp) { + ++mod.votesUp; + } else { + ++mod.votesDown; + } + settings->addModDetails(nPublishedFileID, mod); + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + + +STEAM_CALL_RESULT( GetUserItemVoteResult_t ) +SteamAPICall_t Steam_UGC::GetUserItemVote( PublishedFileId_t nPublishedFileID ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (nPublishedFileID == k_PublishedFileIdInvalid || !settings->isModInstalled(nPublishedFileID)) return k_uAPICallInvalid; // TODO is this correct + + auto mod = settings->getMod(nPublishedFileID); + GetUserItemVoteResult_t data{}; + data.m_eResult = EResult::k_EResultOK; + data.m_nPublishedFileId = nPublishedFileID; + data.m_bVotedDown = mod.votesDown; + data.m_bVotedUp = mod.votesUp; + data.m_bVoteSkipped = true; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + + +STEAM_CALL_RESULT( UserFavoriteItemsListChanged_t ) +SteamAPICall_t Steam_UGC::AddItemToFavorites( AppId_t nAppId, PublishedFileId_t nPublishedFileID ) +{ + PRINT_DEBUG("%u %llu", nAppId, nPublishedFileID); + std::lock_guard lock(global_mutex); + if (nAppId == k_uAppIdInvalid || nAppId != settings->get_local_game_id().AppID()) return k_uAPICallInvalid; // TODO is this correct + if (nPublishedFileID == k_PublishedFileIdInvalid || !settings->isModInstalled(nPublishedFileID)) return k_uAPICallInvalid; // TODO is this correct + + UserFavoriteItemsListChanged_t data{}; + data.m_nPublishedFileId = nPublishedFileID; + data.m_bWasAddRequest = true; + + auto add = favorites.insert(nPublishedFileID); + if (add.second) { // if new insertion + PRINT_DEBUG(" adding new item to favorites"); + bool ok = write_ugc_favorites(); + data.m_eResult = ok ? EResult::k_EResultOK : EResult::k_EResultFail; + } else { // nPublishedFileID already exists + data.m_eResult = EResult::k_EResultOK; + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + + +STEAM_CALL_RESULT( UserFavoriteItemsListChanged_t ) +SteamAPICall_t Steam_UGC::RemoveItemFromFavorites( AppId_t nAppId, PublishedFileId_t nPublishedFileID ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (nAppId == k_uAppIdInvalid || nAppId != settings->get_local_game_id().AppID()) return k_uAPICallInvalid; // TODO is this correct + if (nPublishedFileID == k_PublishedFileIdInvalid || !settings->isModInstalled(nPublishedFileID)) return k_uAPICallInvalid; // TODO is this correct + + UserFavoriteItemsListChanged_t data{}; + data.m_nPublishedFileId = nPublishedFileID; + data.m_bWasAddRequest = false; + + auto removed = favorites.erase(nPublishedFileID); + if (removed) { + PRINT_DEBUG(" removing item from favorites"); + bool ok = write_ugc_favorites(); + data.m_eResult = ok ? EResult::k_EResultOK : EResult::k_EResultFail; + } else { // nPublishedFileID didn't exist + data.m_eResult = EResult::k_EResultOK; + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + + +STEAM_CALL_RESULT( RemoteStorageSubscribePublishedFileResult_t ) +SteamAPICall_t Steam_UGC::SubscribeItem( PublishedFileId_t nPublishedFileID ) +{ + PRINT_DEBUG("%llu", nPublishedFileID); + std::lock_guard lock(global_mutex); + + RemoteStorageSubscribePublishedFileResult_t data; + data.m_nPublishedFileId = nPublishedFileID; + if (settings->isModInstalled(nPublishedFileID)) { + data.m_eResult = k_EResultOK; + ugc_bridge->add_subbed_mod(nPublishedFileID); + } else { + data.m_eResult = k_EResultFail; + } + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + // subscribe to this item, will be installed ASAP + +STEAM_CALL_RESULT( RemoteStorageUnsubscribePublishedFileResult_t ) +SteamAPICall_t Steam_UGC::UnsubscribeItem( PublishedFileId_t nPublishedFileID ) +{ + PRINT_DEBUG("%llu", nPublishedFileID); + std::lock_guard lock(global_mutex); + + RemoteStorageUnsubscribePublishedFileResult_t data; + data.m_nPublishedFileId = nPublishedFileID; + if (!ugc_bridge->has_subbed_mod(nPublishedFileID)) { + data.m_eResult = k_EResultFail; //TODO: check if this is accurate + } else { + data.m_eResult = k_EResultOK; + ugc_bridge->remove_subbed_mod(nPublishedFileID); + } + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + // unsubscribe from this item, will be uninstalled after game quits + +uint32 Steam_UGC::GetNumSubscribedItems() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + PRINT_DEBUG(" Steam_UGC::GetNumSubscribedItems = %zu", ugc_bridge->subbed_mods_count()); + return (uint32)ugc_bridge->subbed_mods_count(); +} + // number of subscribed items + +uint32 Steam_UGC::GetSubscribedItems( PublishedFileId_t* pvecPublishedFileID, uint32 cMaxEntries ) +{ + PRINT_DEBUG("%p %u", pvecPublishedFileID, cMaxEntries); + std::lock_guard lock(global_mutex); + if ((size_t)cMaxEntries > ugc_bridge->subbed_mods_count()) { + cMaxEntries = (uint32)ugc_bridge->subbed_mods_count(); + } + + std::copy_n(ugc_bridge->subbed_mods_itr_begin(), cMaxEntries, pvecPublishedFileID); + return cMaxEntries; +} + // all subscribed item PublishFileIDs + +// get EItemState flags about item on this client +uint32 Steam_UGC::GetItemState( PublishedFileId_t nPublishedFileID ) +{ + PRINT_DEBUG("%llu", nPublishedFileID); + std::lock_guard lock(global_mutex); + if (ugc_bridge->has_subbed_mod(nPublishedFileID)) { + if (settings->isModInstalled(nPublishedFileID)) { + PRINT_DEBUG(" mod is subscribed and installed"); + return k_EItemStateInstalled | k_EItemStateSubscribed; + } + + PRINT_DEBUG(" mod is subscribed"); + return k_EItemStateSubscribed; + } + + PRINT_DEBUG(" mod isn't found"); + return k_EItemStateNone; +} + + +// get info about currently installed content on disc for items that have k_EItemStateInstalled set +// if k_EItemStateLegacyItem is set, pchFolder contains the path to the legacy file itself (not a folder) +bool Steam_UGC::GetItemInstallInfo( PublishedFileId_t nPublishedFileID, uint64 *punSizeOnDisk, STEAM_OUT_STRING_COUNT( cchFolderSize ) char *pchFolder, uint32 cchFolderSize, uint32 *punTimeStamp ) +{ + PRINT_DEBUG("%llu %p %p [%u] %p", nPublishedFileID, punSizeOnDisk, pchFolder, cchFolderSize, punTimeStamp); + std::lock_guard lock(global_mutex); + if (!cchFolderSize) return false; + if (!settings->isModInstalled(nPublishedFileID)) return false; + + auto mod = settings->getMod(nPublishedFileID); + + // I don't know if this is accurate behavior, but to avoid returning true with invalid data + if ((cchFolderSize - 1) < mod.path.size()) { // -1 because the last char is reserved for null terminator + PRINT_DEBUG(" ERROR mod path: '%s' [%zu bytes] cannot fit into the given buffer", mod.path.c_str(), mod.path.size()); + return false; + } + + if (punSizeOnDisk) *punSizeOnDisk = mod.primaryFileSize; + if (punTimeStamp) *punTimeStamp = mod.timeUpdated; + if (pchFolder && cchFolderSize) { + // human fall flat doesn't send a nulled buffer, and won't recognize the proper mod path because of that + memset(pchFolder, 0, cchFolderSize); + mod.path.copy(pchFolder, cchFolderSize - 1); + PRINT_DEBUG(" copied mod path: '%s'", pchFolder); + } + + return true; +} + + +// get info about pending update for items that have k_EItemStateNeedsUpdate set. punBytesTotal will be valid after download started once +bool Steam_UGC::GetItemDownloadInfo( PublishedFileId_t nPublishedFileID, uint64 *punBytesDownloaded, uint64 *punBytesTotal ) +{ + PRINT_DEBUG("%llu", nPublishedFileID); + std::lock_guard lock(global_mutex); + if (!settings->isModInstalled(nPublishedFileID)) return false; + + auto mod = settings->getMod(nPublishedFileID); + if (punBytesDownloaded) *punBytesDownloaded = mod.primaryFileSize; + if (punBytesTotal) *punBytesTotal = mod.primaryFileSize; + return true; +} + +bool Steam_UGC::GetItemInstallInfo( PublishedFileId_t nPublishedFileID, uint64 *punSizeOnDisk, STEAM_OUT_STRING_COUNT( cchFolderSize ) char *pchFolder, uint32 cchFolderSize, bool *pbLegacyItem ) // returns true if item is installed +{ + PRINT_DEBUG("old"); + return GetItemInstallInfo(nPublishedFileID, punSizeOnDisk, pchFolder, cchFolderSize, (uint32*) nullptr); +} + +bool Steam_UGC::GetItemUpdateInfo( PublishedFileId_t nPublishedFileID, bool *pbNeedsUpdate, bool *pbIsDownloading, uint64 *punBytesDownloaded, uint64 *punBytesTotal ) +{ + PRINT_DEBUG("old"); + std::lock_guard lock(global_mutex); + bool res = GetItemDownloadInfo(nPublishedFileID, punBytesDownloaded, punBytesTotal); + if (res) { + if (pbNeedsUpdate) *pbNeedsUpdate = false; + if (pbIsDownloading) *pbIsDownloading = false; + } + return res; +} + +bool Steam_UGC::GetItemInstallInfo( PublishedFileId_t nPublishedFileID, uint64 *punSizeOnDisk, char *pchFolder, uint32 cchFolderSize ) // returns true if item is installed +{ + PRINT_DEBUG("older"); + return GetItemInstallInfo(nPublishedFileID, punSizeOnDisk, pchFolder, cchFolderSize, (uint32*) nullptr); +} + + +// download new or update already installed item. If function returns true, wait for DownloadItemResult_t. If the item is already installed, +// then files on disk should not be used until callback received. If item is not subscribed to, it will be cached for some time. +// If bHighPriority is set, any other item download will be suspended and this item downloaded ASAP. +bool Steam_UGC::DownloadItem( PublishedFileId_t nPublishedFileID, bool bHighPriority ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + + +// game servers can set a specific workshop folder before issuing any UGC commands. +// This is helpful if you want to support multiple game servers running out of the same install folder +bool Steam_UGC::BInitWorkshopForGameServer( DepotId_t unWorkshopDepotID, const char *pszFolder ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + return false; +} + + +// SuspendDownloads( true ) will suspend all workshop downloads until SuspendDownloads( false ) is called or the game ends +void Steam_UGC::SuspendDownloads( bool bSuspend ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + +} + + +// usage tracking +STEAM_CALL_RESULT( StartPlaytimeTrackingResult_t ) +SteamAPICall_t Steam_UGC::StartPlaytimeTracking( PublishedFileId_t *pvecPublishedFileID, uint32 unNumPublishedFileIDs ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + StopPlaytimeTrackingResult_t data; + data.m_eResult = k_EResultOK; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +STEAM_CALL_RESULT( StopPlaytimeTrackingResult_t ) +SteamAPICall_t Steam_UGC::StopPlaytimeTracking( PublishedFileId_t *pvecPublishedFileID, uint32 unNumPublishedFileIDs ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + StopPlaytimeTrackingResult_t data; + data.m_eResult = k_EResultOK; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +STEAM_CALL_RESULT( StopPlaytimeTrackingResult_t ) +SteamAPICall_t Steam_UGC::StopPlaytimeTrackingForAllItems() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + StopPlaytimeTrackingResult_t data; + data.m_eResult = k_EResultOK; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + + +// parent-child relationship or dependency management +STEAM_CALL_RESULT( AddUGCDependencyResult_t ) +SteamAPICall_t Steam_UGC::AddDependency( PublishedFileId_t nParentPublishedFileID, PublishedFileId_t nChildPublishedFileID ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + if (nParentPublishedFileID == k_PublishedFileIdInvalid) return k_uAPICallInvalid; + + return k_uAPICallInvalid; +} + +STEAM_CALL_RESULT( RemoveUGCDependencyResult_t ) +SteamAPICall_t Steam_UGC::RemoveDependency( PublishedFileId_t nParentPublishedFileID, PublishedFileId_t nChildPublishedFileID ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + if (nParentPublishedFileID == k_PublishedFileIdInvalid) return k_uAPICallInvalid; + + return k_uAPICallInvalid; +} + + +// add/remove app dependence/requirements (usually DLC) +STEAM_CALL_RESULT( AddAppDependencyResult_t ) +SteamAPICall_t Steam_UGC::AddAppDependency( PublishedFileId_t nPublishedFileID, AppId_t nAppID ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + if (nPublishedFileID == k_PublishedFileIdInvalid) return k_uAPICallInvalid; + + return k_uAPICallInvalid; +} + +STEAM_CALL_RESULT( RemoveAppDependencyResult_t ) +SteamAPICall_t Steam_UGC::RemoveAppDependency( PublishedFileId_t nPublishedFileID, AppId_t nAppID ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + if (nPublishedFileID == k_PublishedFileIdInvalid) return k_uAPICallInvalid; + + return k_uAPICallInvalid; +} + +// request app dependencies. note that whatever callback you register for GetAppDependenciesResult_t may be called multiple times +// until all app dependencies have been returned +STEAM_CALL_RESULT( GetAppDependenciesResult_t ) +SteamAPICall_t Steam_UGC::GetAppDependencies( PublishedFileId_t nPublishedFileID ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + if (nPublishedFileID == k_PublishedFileIdInvalid) return k_uAPICallInvalid; + + return k_uAPICallInvalid; +} + + +// delete the item without prompting the user +STEAM_CALL_RESULT( DeleteItemResult_t ) +SteamAPICall_t Steam_UGC::DeleteItem( PublishedFileId_t nPublishedFileID ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + if (nPublishedFileID == k_PublishedFileIdInvalid) return k_uAPICallInvalid; + + return k_uAPICallInvalid; +} + +// Show the app's latest Workshop EULA to the user in an overlay window, where they can accept it or not +bool Steam_UGC::ShowWorkshopEULA() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return false; +} + +// Retrieve information related to the user's acceptance or not of the app's specific Workshop EULA +STEAM_CALL_RESULT( WorkshopEULAStatus_t ) +SteamAPICall_t Steam_UGC::GetWorkshopEULAStatus() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return k_uAPICallInvalid; +} + +// Return the user's community content descriptor preferences +uint32 Steam_UGC::GetUserContentDescriptorPreferences( EUGCContentDescriptorID *pvecDescriptors, uint32 cMaxEntries ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return 0; +} diff --git a/dll/steam_unified_messages.cpp b/dll/steam_unified_messages.cpp new file mode 100644 index 00000000..a6c3d028 --- /dev/null +++ b/dll/steam_unified_messages.cpp @@ -0,0 +1,119 @@ +/* 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 + . */ + +#include "dll/steam_unified_messages.h" + + +void Steam_Unified_Messages::network_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Unified_Messages *steam_steamunifiedmessages = (Steam_Unified_Messages *)object; + steam_steamunifiedmessages->Callback(msg); +} + +void Steam_Unified_Messages::steam_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + Steam_Unified_Messages *steam_steamunifiedmessages = (Steam_Unified_Messages *)object; + steam_steamunifiedmessages->RunCallbacks(); +} + + +Steam_Unified_Messages::Steam_Unified_Messages(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_Unified_Messages::network_callback, this); + this->run_every_runcb->add(&Steam_Unified_Messages::steam_runcb, this); + +} + +Steam_Unified_Messages::~Steam_Unified_Messages() +{ + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Unified_Messages::network_callback, this); + this->run_every_runcb->remove(&Steam_Unified_Messages::steam_runcb, this); +} + +// Sends a service method (in binary serialized form) using the Steam Client. +// Returns a unified message handle (k_InvalidUnifiedMessageHandle if could not send the message). +ClientUnifiedMessageHandle Steam_Unified_Messages::SendMethod( const char *pchServiceMethod, const void *pRequestBuffer, uint32 unRequestBufferSize, uint64 unContext ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return ISteamUnifiedMessages::k_InvalidUnifiedMessageHandle; +} + + +// Gets the size of the response and the EResult. Returns false if the response is not ready yet. +bool Steam_Unified_Messages::GetMethodResponseInfo( ClientUnifiedMessageHandle hHandle, uint32 *punResponseSize, EResult *peResult ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +// Gets a response in binary serialized form (and optionally release the corresponding allocated memory). +bool Steam_Unified_Messages::GetMethodResponseData( ClientUnifiedMessageHandle hHandle, void *pResponseBuffer, uint32 unResponseBufferSize, bool bAutoRelease ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +// Releases the message and its corresponding allocated memory. +bool Steam_Unified_Messages::ReleaseMethod( ClientUnifiedMessageHandle hHandle ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +// Sends a service notification (in binary serialized form) using the Steam Client. +// Returns true if the notification was sent successfully. +bool Steam_Unified_Messages::SendNotification( const char *pchServiceNotification, const void *pNotificationBuffer, uint32 unNotificationBufferSize ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +void Steam_Unified_Messages::RunCallbacks() +{ +} + +void Steam_Unified_Messages::Callback(Common_Message *msg) +{ + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + + if (msg->low_level().type() == Low_Level::DISCONNECT) { + + } + } +} diff --git a/dll/steam_user.cpp b/dll/steam_user.cpp new file mode 100644 index 00000000..2bef1970 --- /dev/null +++ b/dll/steam_user.cpp @@ -0,0 +1,528 @@ +/* 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 + . */ + +#include "dll/steam_user.h" +#include "dll/auth.h" +#include "dll/appticket.h" + +Steam_User::Steam_User(Settings *settings, Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks) +{ + this->settings = settings; + this->local_storage = local_storage; + this->network = network; + this->callbacks = callbacks; + this->callback_results = callback_results; + + recording = false; + auth_manager = new Auth_Manager(settings, network, callbacks); +} + +Steam_User::~Steam_User() +{ + delete auth_manager; +} + +// returns the HSteamUser this interface represents +// this is only used internally by the API, and by a few select interfaces that support multi-user +HSteamUser Steam_User::GetHSteamUser() +{ + PRINT_DEBUG_ENTRY(); + return CLIENT_HSTEAMUSER; +} + +// returns true if the Steam client current has a live connection to the Steam servers. +// If false, it means there is no active connection due to either a networking issue on the local machine, or the Steam server is down/busy. +// The Steam client will automatically be trying to recreate the connection as often as possible. +bool Steam_User::BLoggedOn() +{ + PRINT_DEBUG_ENTRY(); + return !settings->is_offline(); +} + +// returns the CSteamID of the account currently logged into the Steam client +// a CSteamID is a unique identifier for an account, and used to differentiate users in all parts of the Steamworks API +CSteamID Steam_User::GetSteamID() +{ + PRINT_DEBUG_ENTRY(); + CSteamID id = settings->get_local_steam_id(); + + return id; +} + +// Multiplayer Authentication functions + +// InitiateGameConnection() starts the state machine for authenticating the game client with the game server +// It is the client portion of a three-way handshake between the client, the game server, and the steam servers +// +// Parameters: +// void *pAuthBlob - a pointer to empty memory that will be filled in with the authentication token. +// int cbMaxAuthBlob - the number of bytes of allocated memory in pBlob. Should be at least 2048 bytes. +// CSteamID steamIDGameServer - the steamID of the game server, received from the game server by the client +// CGameID gameID - the ID of the current game. For games without mods, this is just CGameID( ) +// uint32 unIPServer, uint16 usPortServer - the IP address of the game server +// bool bSecure - whether or not the client thinks that the game server is reporting itself as secure (i.e. VAC is running) +// +// return value - returns the number of bytes written to pBlob. If the return is 0, then the buffer passed in was too small, and the call has failed +// The contents of pBlob should then be sent to the game server, for it to use to complete the authentication process. + +//steam returns 206 bytes +#define INITIATE_GAME_CONNECTION_TICKET_SIZE 206 + +int Steam_User::InitiateGameConnection( void *pAuthBlob, int cbMaxAuthBlob, CSteamID steamIDGameServer, uint32 unIPServer, uint16 usPortServer, bool bSecure ) +{ + PRINT_DEBUG("%i %llu %u %u %u %p", cbMaxAuthBlob, steamIDGameServer.ConvertToUint64(), unIPServer, usPortServer, bSecure, pAuthBlob); + std::lock_guard lock(global_mutex); + if (cbMaxAuthBlob < INITIATE_GAME_CONNECTION_TICKET_SIZE) return 0; + if (!pAuthBlob) return 0; + uint32 out_size = INITIATE_GAME_CONNECTION_TICKET_SIZE; + auth_manager->getTicketData(pAuthBlob, INITIATE_GAME_CONNECTION_TICKET_SIZE, &out_size); + if (out_size > INITIATE_GAME_CONNECTION_TICKET_SIZE) + return 0; + return out_size; +} + +int Steam_User::InitiateGameConnection( void *pAuthBlob, int cbMaxAuthBlob, CSteamID steamIDGameServer, CGameID gameID, uint32 unIPServer, uint16 usPortServer, bool bSecure ) +{ + PRINT_DEBUG_ENTRY(); + return InitiateGameConnection(pAuthBlob, cbMaxAuthBlob, steamIDGameServer, unIPServer, usPortServer, bSecure); +} + +// notify of disconnect +// needs to occur when the game client leaves the specified game server, needs to match with the InitiateGameConnection() call +void Steam_User::TerminateGameConnection( uint32 unIPServer, uint16 usPortServer ) +{ + PRINT_DEBUG_TODO(); +} + +// Legacy functions + +// used by only a few games to track usage events +void Steam_User::TrackAppUsageEvent( CGameID gameID, int eAppUsageEvent, const char *pchExtraInfo) +{ + PRINT_DEBUG_TODO(); +} + +void Steam_User::RefreshSteam2Login() +{ + PRINT_DEBUG_TODO(); +} + +// get the local storage folder for current Steam account to write application data, e.g. save games, configs etc. +// this will usually be something like "C:\Progam Files\Steam\userdata\\\local" +bool Steam_User::GetUserDataFolder( char *pchBuffer, int cubBuffer ) +{ + PRINT_DEBUG_ENTRY(); + if (!cubBuffer) return false; + + std::string user_data = local_storage->get_path(Local_Storage::user_data_storage); + strncpy(pchBuffer, user_data.c_str(), cubBuffer - 1); + pchBuffer[cubBuffer - 1] = 0; + return true; +} + +// Starts voice recording. Once started, use GetVoice() to get the data +void Steam_User::StartVoiceRecording( ) +{ + PRINT_DEBUG_ENTRY(); + last_get_voice = std::chrono::high_resolution_clock::now(); + recording = true; + //TODO:fix + recording = false; +} + +// Stops voice recording. Because people often release push-to-talk keys early, the system will keep recording for +// a little bit after this function is called. GetVoice() should continue to be called until it returns +// k_eVoiceResultNotRecording +void Steam_User::StopVoiceRecording( ) +{ + PRINT_DEBUG_ENTRY(); + recording = false; +} + +// Determine the size of captured audio data that is available from GetVoice. +// Most applications will only use compressed data and should ignore the other +// parameters, which exist primarily for backwards compatibility. See comments +// below for further explanation of "uncompressed" data. +EVoiceResult Steam_User::GetAvailableVoice( uint32 *pcbCompressed, uint32 *pcbUncompressed_Deprecated, uint32 nUncompressedVoiceDesiredSampleRate_Deprecated ) +{ + PRINT_DEBUG_ENTRY(); + if (pcbCompressed) *pcbCompressed = 0; + if (pcbUncompressed_Deprecated) *pcbUncompressed_Deprecated = 0; + if (!recording) return k_EVoiceResultNotRecording; + double seconds = std::chrono::duration_cast>(std::chrono::high_resolution_clock::now() - last_get_voice).count(); + if (pcbCompressed) *pcbCompressed = seconds * 1024.0 * 64.0 / 8.0; + if (pcbUncompressed_Deprecated) *pcbUncompressed_Deprecated = seconds * (double)nUncompressedVoiceDesiredSampleRate_Deprecated * 2.0; + + return k_EVoiceResultOK; +} + +EVoiceResult Steam_User::GetAvailableVoice(uint32 *pcbCompressed, uint32 *pcbUncompressed) +{ + PRINT_DEBUG("old"); + return GetAvailableVoice(pcbCompressed, pcbUncompressed, 11025); +} + +// --------------------------------------------------------------------------- +// NOTE: "uncompressed" audio is a deprecated feature and should not be used +// by most applications. It is raw single-channel 16-bit PCM wave data which +// may have been run through preprocessing filters and/or had silence removed, +// so the uncompressed audio could have a shorter duration than you expect. +// There may be no data at all during long periods of silence. Also, fetching +// uncompressed audio will cause GetVoice to discard any leftover compressed +// audio, so you must fetch both types at once. Finally, GetAvailableVoice is +// not precisely accurate when the uncompressed size is requested. So if you +// really need to use uncompressed audio, you should call GetVoice frequently +// with two very large (20kb+) output buffers instead of trying to allocate +// perfectly-sized buffers. But most applications should ignore all of these +// details and simply leave the "uncompressed" parameters as NULL/zero. +// --------------------------------------------------------------------------- + +// Read captured audio data from the microphone buffer. This should be called +// at least once per frame, and preferably every few milliseconds, to keep the +// microphone input delay as low as possible. Most applications will only use +// compressed data and should pass NULL/zero for the "uncompressed" parameters. +// Compressed data can be transmitted by your application and decoded into raw +// using the DecompressVoice function below. +EVoiceResult Steam_User::GetVoice( bool bWantCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten, bool bWantUncompressed_Deprecated, void *pUncompressedDestBuffer_Deprecated , uint32 cbUncompressedDestBufferSize_Deprecated , uint32 *nUncompressBytesWritten_Deprecated , uint32 nUncompressedVoiceDesiredSampleRate_Deprecated ) +{ + PRINT_DEBUG_ENTRY(); + if (!recording) return k_EVoiceResultNotRecording; + double seconds = std::chrono::duration_cast>(std::chrono::high_resolution_clock::now() - last_get_voice).count(); + if (bWantCompressed) { + uint32 towrite = seconds * 1024.0 * 64.0 / 8.0; + if (cbDestBufferSize < towrite) towrite = cbDestBufferSize; + if (pDestBuffer) memset(pDestBuffer, 0, towrite); + if (nBytesWritten) *nBytesWritten = towrite; + } + + if (bWantUncompressed_Deprecated) { + PRINT_DEBUG("Wanted Uncompressed"); + } + + last_get_voice = std::chrono::high_resolution_clock::now(); + return k_EVoiceResultOK; +} + +EVoiceResult Steam_User::GetVoice( bool bWantCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten, bool bWantUncompressed, void *pUncompressedDestBuffer, uint32 cbUncompressedDestBufferSize, uint32 *nUncompressBytesWritten ) +{ + PRINT_DEBUG("old"); + return GetVoice(bWantCompressed, pDestBuffer, cbDestBufferSize, nBytesWritten, bWantUncompressed, pUncompressedDestBuffer, cbUncompressedDestBufferSize, nUncompressBytesWritten, 11025); +} + +EVoiceResult Steam_User::GetCompressedVoice( void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten ) +{ + PRINT_DEBUG_ENTRY(); + return GetVoice(true, pDestBuffer, cbDestBufferSize, nBytesWritten, false, NULL, 0, NULL); +} + +// Decodes the compressed voice data returned by GetVoice. The output data is +// raw single-channel 16-bit PCM audio. The decoder supports any sample rate +// from 11025 to 48000; see GetVoiceOptimalSampleRate() below for details. +// If the output buffer is not large enough, then *nBytesWritten will be set +// to the required buffer size, and k_EVoiceResultBufferTooSmall is returned. +// It is suggested to start with a 20kb buffer and reallocate as necessary. +EVoiceResult Steam_User::DecompressVoice( const void *pCompressed, uint32 cbCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten, uint32 nDesiredSampleRate ) +{ + PRINT_DEBUG_ENTRY(); + if (!recording) return k_EVoiceResultNotRecording; + uint32 uncompressed = (double)cbCompressed * ((double)nDesiredSampleRate / 8192.0); + if(nBytesWritten) *nBytesWritten = uncompressed; + if (uncompressed > cbDestBufferSize) uncompressed = cbDestBufferSize; + if (pDestBuffer) memset(pDestBuffer, 0, uncompressed); + + return k_EVoiceResultOK; +} + +EVoiceResult Steam_User::DecompressVoice( const void *pCompressed, uint32 cbCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten ) +{ + PRINT_DEBUG("old"); + return DecompressVoice(pCompressed, cbCompressed, pDestBuffer, cbDestBufferSize, nBytesWritten, 11025); +} + +EVoiceResult Steam_User::DecompressVoice( void *pCompressed, uint32 cbCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten ) +{ + PRINT_DEBUG("older"); + return DecompressVoice(pCompressed, cbCompressed, pDestBuffer, cbDestBufferSize, nBytesWritten, 11025); +} + +// This returns the native sample rate of the Steam voice decompressor +// this sample rate for DecompressVoice will perform the least CPU processing. +// However, the final audio quality will depend on how well the audio device +// (and/or your application's audio output SDK) deals with lower sample rates. +// You may find that you get the best audio output quality when you ignore +// this function and use the native sample rate of your audio output device, +// which is usually 48000 or 44100. +uint32 Steam_User::GetVoiceOptimalSampleRate() +{ + PRINT_DEBUG_ENTRY(); + return 48000; +} + +// Retrieve ticket to be sent to the entity who wishes to authenticate you. +// pcbTicket retrieves the length of the actual ticket. +HAuthTicket Steam_User::GetAuthSessionTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket ) +{ + return GetAuthSessionTicket(pTicket, cbMaxTicket, pcbTicket, NULL); +} +// SteamNetworkingIdentity is an optional input parameter to hold the public IP address or SteamID of the entity you are connecting to +// if an IP address is passed Steam will only allow the ticket to be used by an entity with that IP address +// if a Steam ID is passed Steam will only allow the ticket to be used by that Steam ID +HAuthTicket Steam_User::GetAuthSessionTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket, const SteamNetworkingIdentity *pSteamNetworkingIdentity ) +{ + PRINT_DEBUG("%p [%i] %p", pTicket, cbMaxTicket, pcbTicket); + std::lock_guard lock(global_mutex); + + if (!pTicket) return k_HAuthTicketInvalid; + + return auth_manager->getTicket(pTicket, cbMaxTicket, pcbTicket); +} + +// Request a ticket which will be used for webapi "ISteamUserAuth\AuthenticateUserTicket" +// pchIdentity is an optional input parameter to identify the service the ticket will be sent to +// the ticket will be returned in callback GetTicketForWebApiResponse_t +HAuthTicket Steam_User::GetAuthTicketForWebApi( const char *pchIdentity ) +{ + PRINT_DEBUG("%s", pchIdentity); + std::lock_guard lock(global_mutex); + + return auth_manager->getWebApiTicket(pchIdentity); +} + +// Authenticate ticket from entity steamID to be sure it is valid and isnt reused +// Registers for callbacks if the entity goes offline or cancels the ticket ( see ValidateAuthTicketResponse_t callback and EAuthSessionResponse ) +EBeginAuthSessionResult Steam_User::BeginAuthSession( const void *pAuthTicket, int cbAuthTicket, CSteamID steamID ) +{ + PRINT_DEBUG("%i %llu", cbAuthTicket, steamID.ConvertToUint64()); + std::lock_guard lock(global_mutex); + + return auth_manager->beginAuth(pAuthTicket, cbAuthTicket, steamID); +} + +// Stop tracking started by BeginAuthSession - called when no longer playing game with this entity +void Steam_User::EndAuthSession( CSteamID steamID ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + auth_manager->endAuth(steamID); +} + +// Cancel auth ticket from GetAuthSessionTicket, called when no longer playing game with the entity you gave the ticket to +void Steam_User::CancelAuthTicket( HAuthTicket hAuthTicket ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + auth_manager->cancelTicket(hAuthTicket); +} + +// After receiving a user's authentication data, and passing it to BeginAuthSession, use this function +// to determine if the user owns downloadable content specified by the provided AppID. +EUserHasLicenseForAppResult Steam_User::UserHasLicenseForApp( CSteamID steamID, AppId_t appID ) +{ + PRINT_DEBUG_ENTRY(); + return k_EUserHasLicenseResultHasLicense; +} + +// returns true if this users looks like they are behind a NAT device. Only valid once the user has connected to steam +// (i.e a SteamServersConnected_t has been issued) and may not catch all forms of NAT. +bool Steam_User::BIsBehindNAT() +{ + PRINT_DEBUG_ENTRY(); + return false; +} + +// set data to be replicated to friends so that they can join your game +// CSteamID steamIDGameServer - the steamID of the game server, received from the game server by the client +// uint32 unIPServer, uint16 usPortServer - the IP address of the game server +void Steam_User::AdvertiseGame( CSteamID steamIDGameServer, uint32 unIPServer, uint16 usPortServer ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + Gameserver *server = new Gameserver(); + server->set_id(steamIDGameServer.ConvertToUint64()); + server->set_ip(unIPServer); + server->set_port(usPortServer); + server->set_query_port(usPortServer); + server->set_appid(settings->get_local_game_id().ToUint64()); + + if (settings->matchmaking_server_list_always_lan_type) + server->set_type(eLANServer); + else + server->set_type(eFriendsServer); + + Common_Message msg; + msg.set_allocated_gameserver(server); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + network->sendToAllIndividuals(&msg, true); +} + +// Requests a ticket encrypted with an app specific shared key +// pDataToInclude, cbDataToInclude will be encrypted into the ticket +// ( This is asynchronous, you must wait for the ticket to be completed by the server ) +STEAM_CALL_RESULT( EncryptedAppTicketResponse_t ) +SteamAPICall_t Steam_User::RequestEncryptedAppTicket( void *pDataToInclude, int cbDataToInclude ) +{ + PRINT_DEBUG("%i", cbDataToInclude); + std::lock_guard lock(global_mutex); + EncryptedAppTicketResponse_t data; + data.m_eResult = k_EResultOK; + + DecryptedAppTicket ticket; + ticket.TicketV1.Reset(); + ticket.TicketV2.Reset(); + ticket.TicketV4.Reset(); + + ticket.TicketV1.TicketVersion = 1; + if (pDataToInclude) { + ticket.TicketV1.UserData.assign((uint8_t*)pDataToInclude, (uint8_t*)pDataToInclude + cbDataToInclude); + } + + ticket.TicketV2.TicketVersion = 4; + ticket.TicketV2.SteamID = settings->get_local_steam_id().ConvertToUint64(); + ticket.TicketV2.TicketIssueTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + ticket.TicketV2.TicketValidityEnd = ticket.TicketV2.TicketIssueTime + (21 * 24 * 60 * 60); + + for (int i = 0; i < 140; ++i) + { + AppId_t appid; + bool available; + std::string name; + if (!settings->getDLC(appid, appid, available, name)) break; + ticket.TicketV4.AppIDs.emplace_back(appid); + } + + ticket.TicketV4.HasVACStatus = true; + ticket.TicketV4.VACStatus = 0; + + auto serialized = ticket.SerializeTicket(); + + SteamAppTicket_pb pb; + pb.set_ticket_version_no(1); + pb.set_crc_encryptedticket(0); // TODO: Find out how to compute the CRC + pb.set_cb_encrypteduserdata(cbDataToInclude); + pb.set_cb_encrypted_appownershipticket(serialized.size() - 16); + pb.mutable_encrypted_ticket()->assign(serialized.begin(), serialized.end()); // TODO: Find how to encrypt datas + + encrypted_app_ticket = pb.SerializeAsString(); + + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +// retrieve a finished ticket +bool Steam_User::GetEncryptedAppTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket ) +{ + PRINT_DEBUG("%i", cbMaxTicket); + unsigned int ticket_size = encrypted_app_ticket.size(); + if (!cbMaxTicket) { + if (!pcbTicket) return false; + *pcbTicket = ticket_size; + return true; + } + + if (!pTicket) return false; + if (ticket_size > cbMaxTicket) return false; + encrypted_app_ticket.copy((char *)pTicket, cbMaxTicket); + if (pcbTicket) *pcbTicket = ticket_size; + + return true; +} + +// Trading Card badges data access +// if you only have one set of cards, the series will be 1 +// the user has can have two different badges for a series; the regular (max level 5) and the foil (max level 1) +int Steam_User::GetGameBadgeLevel( int nSeries, bool bFoil ) +{ + PRINT_DEBUG_ENTRY(); + return 0; +} + +// gets the Steam Level of the user, as shown on their profile +int Steam_User::GetPlayerSteamLevel() +{ + PRINT_DEBUG_ENTRY(); + return 100; +} + +// Requests a URL which authenticates an in-game browser for store check-out, +// and then redirects to the specified URL. As long as the in-game browser +// accepts and handles session cookies, Steam microtransaction checkout pages +// will automatically recognize the user instead of presenting a login page. +// The result of this API call will be a StoreAuthURLResponse_t callback. +// NOTE: The URL has a very short lifetime to prevent history-snooping attacks, +// so you should only call this API when you are about to launch the browser, +// or else immediately navigate to the result URL using a hidden browser window. +// NOTE 2: The resulting authorization cookie has an expiration time of one day, +// so it would be a good idea to request and visit a new auth URL every 12 hours. +STEAM_CALL_RESULT( StoreAuthURLResponse_t ) +SteamAPICall_t Steam_User::RequestStoreAuthURL( const char *pchRedirectURL ) +{ + PRINT_DEBUG_ENTRY(); + return 0; +} + +// gets whether the users phone number is verified +bool Steam_User::BIsPhoneVerified() +{ + PRINT_DEBUG_ENTRY(); + return true; +} + +// gets whether the user has two factor enabled on their account +bool Steam_User::BIsTwoFactorEnabled() +{ + PRINT_DEBUG_ENTRY(); + return true; +} + +// gets whether the users phone number is identifying +bool Steam_User::BIsPhoneIdentifying() +{ + PRINT_DEBUG_ENTRY(); + return false; +} + +// gets whether the users phone number is awaiting (re)verification +bool Steam_User::BIsPhoneRequiringVerification() +{ + PRINT_DEBUG_ENTRY(); + return false; +} + +STEAM_CALL_RESULT( MarketEligibilityResponse_t ) +SteamAPICall_t Steam_User::GetMarketEligibility() +{ + PRINT_DEBUG_ENTRY(); + return 0; +} + +// Retrieves anti indulgence / duration control for current user +STEAM_CALL_RESULT( DurationControl_t ) +SteamAPICall_t Steam_User::GetDurationControl() +{ + PRINT_DEBUG_ENTRY(); + return 0; +} + +// Advise steam china duration control system about the online state of the game. +// This will prevent offline gameplay time from counting against a user's +// playtime limits. +bool Steam_User::BSetDurationControlOnlineState( EDurationControlOnlineState eNewState ) +{ + PRINT_DEBUG_ENTRY(); + return false; +} diff --git a/dll/steam_user_stats.cpp b/dll/steam_user_stats.cpp index 596afc84..1c194942 100644 --- a/dll/steam_user_stats.cpp +++ b/dll/steam_user_stats.cpp @@ -1,2143 +1,2165 @@ -/* 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 - . */ - -#include "dll/steam_user_stats.h" -#include - - -// --- Steam_Leaderboard --- - -Steam_Leaderboard_Entry* Steam_Leaderboard::find_recent_entry(const CSteamID &steamid) const -{ - auto my_it = std::find_if(entries.begin(), entries.end(), [&steamid](const Steam_Leaderboard_Entry &item) { - return item.steam_id == steamid; - }); - if (entries.end() == my_it) return nullptr; - return const_cast(&*my_it); -} - -void Steam_Leaderboard::remove_entries(const CSteamID &steamid) -{ - auto rm_it = std::remove_if(entries.begin(), entries.end(), [&](const Steam_Leaderboard_Entry &item){ - return item.steam_id == steamid; - }); - if (entries.end() != rm_it) entries.erase(rm_it, entries.end()); -} - -void Steam_Leaderboard::remove_duplicate_entries() -{ - if (entries.size() <= 1) return; - - auto rm = std::remove_if(entries.begin(), entries.end(), [&](const Steam_Leaderboard_Entry& item) { - auto recent = find_recent_entry(item.steam_id); - return &item != recent; - }); - if (entries.end() != rm) entries.erase(rm, entries.end()); -} - -void Steam_Leaderboard::sort_entries() -{ - if (sort_method == k_ELeaderboardSortMethodNone) return; - if (entries.size() <= 1) return; - - std::sort(entries.begin(), entries.end(), [this](const Steam_Leaderboard_Entry &item1, const Steam_Leaderboard_Entry &item2) { - if (sort_method == k_ELeaderboardSortMethodAscending) { - return item1.score < item2.score; - } else { // k_ELeaderboardSortMethodDescending - return item1.score > item2.score; - } - }); - -} - -// --- Steam_Leaderboard --- - - - -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); -} - -void Steam_User_Stats::load_achievements() -{ - local_storage->load_json_file("", achievements_user_file, user_achievements); -} - -void Steam_User_Stats::save_achievements() -{ - local_storage->write_json_file("", achievements_user_file, user_achievements); -} - - -nlohmann::detail::iter_impl Steam_User_Stats::defined_achievements_find(const std::string &key) -{ - return std::find_if( - defined_achievements.begin(), defined_achievements.end(), - [&key](const nlohmann::json& item) { - const std::string &name = static_cast( item.value("name", std::string()) ); - return common_helpers::str_cmp_insensitive(key, name); - } - ); -} - -std::string Steam_User_Stats::get_value_for_language(nlohmann::json &json, std::string key, std::string language) -{ - auto x = json.find(key); - if (x == json.end()) return ""; - if (x.value().is_string()) { - return x.value().get(); - } else if (x.value().is_object()) { - auto l = x.value().find(language); - if (l != x.value().end()) { - return l.value().get(); - } - - l = x.value().find("english"); - if (l != x.value().end()) { - return l.value().get(); - } - - l = x.value().begin(); - if (l != x.value().end()) { - if (l.key() == "token") { - std::string token_value = l.value().get(); - l++; - if (l != x.value().end()) { - return l.value().get(); - } - - return token_value; - } - - return l.value().get(); - } - } - - return ""; -} - - -/* -layout of each item in the leaderboard file -| steamid - lower 32-bits | steamid - higher 32-bits | score (4 bytes) | score details count (4 bytes) | score details array (4 bytes each) ... - [0] | [1] | [2] | [3] | [4] - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ main header ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -*/ - -std::vector Steam_User_Stats::load_leaderboard_entries(const std::string &name) -{ - constexpr const static unsigned int MAIN_HEADER_ELEMENTS_COUNT = 4; - constexpr const static unsigned int ELEMENT_SIZE = (unsigned int)sizeof(uint32_t); - - std::vector out{}; - - std::string leaderboard_name(common_helpers::ascii_to_lowercase(name)); - unsigned read_bytes = local_storage->file_size(Local_Storage::leaderboard_storage_folder, leaderboard_name); - if ((read_bytes == 0) || - (read_bytes < (ELEMENT_SIZE * MAIN_HEADER_ELEMENTS_COUNT)) || - (read_bytes % ELEMENT_SIZE) != 0) { - return out; - } - - std::vector output(read_bytes / ELEMENT_SIZE); - if (local_storage->get_data(Local_Storage::leaderboard_storage_folder, leaderboard_name, (char* )output.data(), read_bytes) != read_bytes) return out; - - unsigned int i = 0; - while (true) { - if ((i + MAIN_HEADER_ELEMENTS_COUNT) > output.size()) break; // invalid main header, or end of buffer - - Steam_Leaderboard_Entry new_entry{}; - new_entry.steam_id = CSteamID((uint64)output[i] + (((uint64)output[i + 1]) << 32)); - new_entry.score = (int32)output[i + 2]; - uint32_t details_count = output[i + 3]; - i += MAIN_HEADER_ELEMENTS_COUNT; // skip main header - - if ((i + details_count) > output.size()) break; // invalid score details count - - for (uint32_t j = 0; j < details_count; ++j) { - new_entry.score_details.push_back(output[i]); - ++i; // move past this score detail - } - - PRINT_DEBUG("'%s': user %llu, score %i, details count = %zu", - name.c_str(), new_entry.steam_id.ConvertToUint64(), new_entry.score, new_entry.score_details.size() - ); - out.push_back(new_entry); - } - - PRINT_DEBUG("'%s' total entries = %zu", name.c_str(), out.size()); - return out; -} - -void Steam_User_Stats::save_my_leaderboard_entry(const Steam_Leaderboard &leaderboard) -{ - auto my_entry = leaderboard.find_recent_entry(settings->get_local_steam_id()); - if (!my_entry) return; // we don't have a score entry - - PRINT_DEBUG("saving entries for leaderboard '%s'", leaderboard.name.c_str()); - - std::vector output{}; - - uint64_t steam_id = my_entry->steam_id.ConvertToUint64(); - output.push_back((uint32_t)(steam_id & 0xFFFFFFFF)); // lower 4 bytes - output.push_back((uint32_t)(steam_id >> 32)); // higher 4 bytes - - output.push_back(my_entry->score); - output.push_back((uint32_t)my_entry->score_details.size()); - for (const auto &detail : my_entry->score_details) { - output.push_back(detail); - } - - std::string leaderboard_name(common_helpers::ascii_to_lowercase(leaderboard.name)); - local_storage->store_data(Local_Storage::leaderboard_storage_folder, leaderboard_name, (char* )&output[0], output.size() * sizeof(output[0])); -} - -Steam_Leaderboard_Entry* Steam_User_Stats::update_leaderboard_entry(Steam_Leaderboard &leaderboard, const Steam_Leaderboard_Entry &entry, bool overwrite) -{ - bool added = false; - auto user_entry = leaderboard.find_recent_entry(entry.steam_id); - if (!user_entry) { // user doesn't have an entry yet, create one - added = true; - leaderboard.entries.push_back(entry); - user_entry = &leaderboard.entries.back(); - } else if (overwrite) { - added = true; - *user_entry = entry; - } - - if (added) { // if we added a new entry then we have to sort and find the target entry again - leaderboard.sort_entries(); - user_entry = leaderboard.find_recent_entry(entry.steam_id); - PRINT_DEBUG("added/updated entry for user %llu", entry.steam_id.ConvertToUint64()); - } - - return user_entry; -} - - -unsigned int Steam_User_Stats::find_cached_leaderboard(const std::string &name) -{ - unsigned index = 1; - for (const auto &leaderboard : cached_leaderboards) { - if (common_helpers::str_cmp_insensitive(leaderboard.name, name)) return index; - - ++index; - } - - return 0; -} - -unsigned int Steam_User_Stats::cache_leaderboard_ifneeded(const std::string &name, ELeaderboardSortMethod eLeaderboardSortMethod, ELeaderboardDisplayType eLeaderboardDisplayType) -{ - unsigned int board_handle = find_cached_leaderboard(name); - if (board_handle) return board_handle; - // PRINT_DEBUG("cache miss '%s'", name.c_str()); - - // create a new entry in-memory and try reading the entries from disk - struct Steam_Leaderboard new_board{}; - new_board.name = common_helpers::ascii_to_lowercase(name); - new_board.sort_method = eLeaderboardSortMethod; - new_board.display_type = eLeaderboardDisplayType; - new_board.entries = load_leaderboard_entries(name); - - new_board.sort_entries(); - new_board.remove_duplicate_entries(); - - // save it in memory for later - cached_leaderboards.push_back(new_board); - board_handle = cached_leaderboards.size(); - - PRINT_DEBUG("cached a new leaderboard '%s' %i %i", - new_board.name.c_str(), (int)eLeaderboardSortMethod, (int)eLeaderboardDisplayType - ); - return board_handle; -} - -void Steam_User_Stats::send_my_leaderboard_score(const Steam_Leaderboard &board, const CSteamID *steamid, bool want_scores_back) -{ - if (!settings->share_leaderboards_over_network) return; - - const auto my_entry = board.find_recent_entry(settings->get_local_steam_id()); - Leaderboards_Messages::UserScoreEntry *score_entry_msg = nullptr; - - if (my_entry) { - score_entry_msg = new Leaderboards_Messages::UserScoreEntry(); - score_entry_msg->set_score(my_entry->score); - score_entry_msg->mutable_score_details()->Assign(my_entry->score_details.begin(), my_entry->score_details.end()); - } - - auto board_info_msg = new Leaderboards_Messages::LeaderboardInfo(); - board_info_msg->set_allocated_board_name(new std::string(board.name)); - board_info_msg->set_sort_method(board.sort_method); - board_info_msg->set_display_type(board.display_type); - - auto board_msg = new Leaderboards_Messages(); - if (want_scores_back) board_msg->set_type(Leaderboards_Messages::UpdateUserScoreMutual); - else board_msg->set_type(Leaderboards_Messages::UpdateUserScore); - board_msg->set_appid(settings->get_local_game_id().AppID()); - board_msg->set_allocated_leaderboard_info(board_info_msg); - // if we have an entry - if (score_entry_msg) board_msg->set_allocated_user_score_entry(score_entry_msg); - - Common_Message common_msg{}; - common_msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - if (steamid) common_msg.set_dest_id(steamid->ConvertToUint64()); - common_msg.set_allocated_leaderboards_messages(board_msg); - - if (steamid) network->sendTo(&common_msg, false); - else network->sendToAll(&common_msg, false); -} - -void Steam_User_Stats::request_user_leaderboard_entry(const Steam_Leaderboard &board, const CSteamID &steamid) -{ - if (!settings->share_leaderboards_over_network) return; - - auto board_info_msg = new Leaderboards_Messages::LeaderboardInfo(); - board_info_msg->set_allocated_board_name(new std::string(board.name)); - board_info_msg->set_sort_method(board.sort_method); - board_info_msg->set_display_type(board.display_type); - - auto board_msg = new Leaderboards_Messages(); - board_msg->set_type(Leaderboards_Messages::RequestUserScore); - board_msg->set_appid(settings->get_local_game_id().AppID()); - board_msg->set_allocated_leaderboard_info(board_info_msg); - - Common_Message common_msg{}; - common_msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - common_msg.set_dest_id(steamid.ConvertToUint64()); - common_msg.set_allocated_leaderboards_messages(board_msg); - - network->sendTo(&common_msg, false); -} - - -// change stats/achievements without sending back to server -bool Steam_User_Stats::clear_stats_internal() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - bool notify_server = false; - - stats_cache_int.clear(); - stats_cache_float.clear(); - - for (const auto &stat : settings->getStats()) { - std::string stat_name(common_helpers::ascii_to_lowercase(stat.first)); - - switch (stat.second.type) - { - case GameServerStats_Messages::StatInfo::STAT_TYPE_INT: { - auto data = stat.second.default_value_int; - - bool needs_disk_write = false; - auto it_res = stats_cache_int.find(stat_name); - if (stats_cache_int.end() == it_res || it_res->second != data) { - needs_disk_write = true; - notify_server = true; - } - - stats_cache_int[stat_name] = data; - - auto stat_trigger = achievement_stat_trigger.find(stat_name); - if (stat_trigger != achievement_stat_trigger.end()) { - for (auto &t : stat_trigger->second) { - if (t.check_triggered(data)) { - set_achievement_internal(t.name.c_str()); - } - } - } - - if (needs_disk_write) local_storage->store_data(Local_Storage::stats_storage_folder, stat_name, (char *)&data, sizeof(data)); - } - break; - - case GameServerStats_Messages::StatInfo::STAT_TYPE_FLOAT: - case GameServerStats_Messages::StatInfo::STAT_TYPE_AVGRATE: { - auto data = stat.second.default_value_float; - - bool needs_disk_write = false; - auto it_res = stats_cache_float.find(stat_name); - if (stats_cache_float.end() == it_res || it_res->second != data) { - needs_disk_write = true; - notify_server = true; - } - - stats_cache_float[stat_name] = data; - - auto stat_trigger = achievement_stat_trigger.find(stat_name); - if (stat_trigger != achievement_stat_trigger.end()) { - for (auto &t : stat_trigger->second) { - if (t.check_triggered(data)) { - set_achievement_internal(t.name.c_str()); - } - } - } - - if (needs_disk_write) local_storage->store_data(Local_Storage::stats_storage_folder, stat_name, (char *)&data, sizeof(data)); - } - break; - - default: PRINT_DEBUG("unhandled type %i", (int)stat.second.type); break; - } - } - - return notify_server; -} - -Steam_User_Stats::InternalSetResult Steam_User_Stats::set_stat_internal( const char *pchName, int32 nData ) -{ - PRINT_DEBUG(" '%s' = %i", pchName, nData); - std::lock_guard lock(global_mutex); - Steam_User_Stats::InternalSetResult result{}; - - if (!pchName) return result; - std::string stat_name(common_helpers::ascii_to_lowercase(pchName)); - - const auto &stats_config = settings->getStats(); - auto stats_data = stats_config.find(stat_name); - if (stats_config.end() == stats_data) return result; - if (stats_data->second.type != GameServerStats_Messages::StatInfo::STAT_TYPE_INT) return result; - - result.internal_name = stat_name; - result.current_val = nData; - - auto cached_stat = stats_cache_int.find(stat_name); - if (cached_stat != stats_cache_int.end()) { - if (cached_stat->second == nData) { - result.success = true; - return result; - } - } - - auto stat_trigger = achievement_stat_trigger.find(stat_name); - if (stat_trigger != achievement_stat_trigger.end()) { - for (auto &t : stat_trigger->second) { - if (t.check_triggered(nData)) { - set_achievement_internal(t.name.c_str()); - } - } - } - - if (local_storage->store_data(Local_Storage::stats_storage_folder, stat_name, (char* )&nData, sizeof(nData)) == sizeof(nData)) { - stats_cache_int[stat_name] = nData; - result.success = true; - result.notify_server = !settings->disable_sharing_stats_with_gameserver; - return result; - } - - return result; -} - -Steam_User_Stats::InternalSetResult> Steam_User_Stats::set_stat_internal( const char *pchName, float fData ) -{ - PRINT_DEBUG(" '%s' = %f", pchName, fData); - std::lock_guard lock(global_mutex); - Steam_User_Stats::InternalSetResult> result{}; - - if (!pchName) return result; - std::string stat_name(common_helpers::ascii_to_lowercase(pchName)); - - const auto &stats_config = settings->getStats(); - auto stats_data = stats_config.find(stat_name); - if (stats_config.end() == stats_data) return result; - if (stats_data->second.type == GameServerStats_Messages::StatInfo::STAT_TYPE_INT) return result; - - result.internal_name = stat_name; - result.current_val.first = stats_data->second.type; - result.current_val.second = fData; - - auto cached_stat = stats_cache_float.find(stat_name); - if (cached_stat != stats_cache_float.end()) { - if (cached_stat->second == fData) { - result.success = true; - return result; - } - } - - auto stat_trigger = achievement_stat_trigger.find(stat_name); - if (stat_trigger != achievement_stat_trigger.end()) { - for (auto &t : stat_trigger->second) { - if (t.check_triggered(fData)) { - set_achievement_internal(t.name.c_str()); - } - } - } - - if (local_storage->store_data(Local_Storage::stats_storage_folder, stat_name, (char* )&fData, sizeof(fData)) == sizeof(fData)) { - stats_cache_float[stat_name] = fData; - result.success = true; - result.notify_server = !settings->disable_sharing_stats_with_gameserver; - return result; - } - - return result; -} - -Steam_User_Stats::InternalSetResult> Steam_User_Stats::update_avg_rate_stat_internal( const char *pchName, float flCountThisSession, double dSessionLength ) -{ - PRINT_DEBUG("%s", pchName); - std::lock_guard lock(global_mutex); - Steam_User_Stats::InternalSetResult> result{}; - - if (!pchName) return result; - std::string stat_name(common_helpers::ascii_to_lowercase(pchName)); - - const auto &stats_config = settings->getStats(); - auto stats_data = stats_config.find(stat_name); - if (stats_config.end() == stats_data) return result; - if (stats_data->second.type == GameServerStats_Messages::StatInfo::STAT_TYPE_INT) return result; - - result.internal_name = stat_name; - - char data[sizeof(float) + sizeof(float) + sizeof(double)]; - int read_data = local_storage->get_data(Local_Storage::stats_storage_folder, stat_name, (char* )data, sizeof(*data)); - float oldcount = 0; - double oldsessionlength = 0; - if (read_data == sizeof(data)) { - memcpy(&oldcount, data + sizeof(float), sizeof(oldcount)); - memcpy(&oldsessionlength, data + sizeof(float) + sizeof(double), sizeof(oldsessionlength)); - } - - oldcount += flCountThisSession; - oldsessionlength += dSessionLength; - - float average = oldcount / oldsessionlength; - memcpy(data, &average, sizeof(average)); - memcpy(data + sizeof(float), &oldcount, sizeof(oldcount)); - memcpy(data + sizeof(float) * 2, &oldsessionlength, sizeof(oldsessionlength)); - - result.current_val.first = stats_data->second.type; - result.current_val.second = average; - - if (local_storage->store_data(Local_Storage::stats_storage_folder, stat_name, data, sizeof(data)) == sizeof(data)) { - stats_cache_float[stat_name] = average; - result.success = true; - result.notify_server = !settings->disable_sharing_stats_with_gameserver; - return result; - } - - return result; -} - -Steam_User_Stats::InternalSetResult Steam_User_Stats::set_achievement_internal( const char *pchName ) -{ - PRINT_DEBUG("'%s'", pchName); - std::lock_guard lock(global_mutex); - Steam_User_Stats::InternalSetResult result{}; - - if (!pchName) return result; - - std::string org_name(pchName); - - if (settings->achievement_bypass) { - auto &trig = store_stats_trigger[common_helpers::to_lower(org_name)]; - trig.m_bGroupAchievement = false; - trig.m_nCurProgress = 100; - trig.m_nGameID = settings->get_local_game_id().ToUint64(); - trig.m_nMaxProgress = 100; - memset(trig.m_rgchAchievementName, 0, sizeof(trig.m_rgchAchievementName)); - org_name.copy(trig.m_rgchAchievementName, sizeof(trig.m_rgchAchievementName) - 1); - - result.success = true; - return result; - } - - nlohmann::detail::iter_impl it = defined_achievements.end(); - try { - it = defined_achievements_find(org_name); - } catch(...) { } - if (defined_achievements.end() == it) return result; - - result.current_val = true; - result.internal_name = org_name; - result.success = true; - - try { - std::string internal_name = it->value("name", std::string()); - - result.internal_name = internal_name; - - auto ach = user_achievements.find(internal_name); - if (user_achievements.end() == ach || ach->value("earned", false) == false) { - user_achievements[internal_name]["earned"] = true; - user_achievements[internal_name]["earned_time"] = - std::chrono::duration_cast>(std::chrono::system_clock::now().time_since_epoch()).count(); - - save_achievements(); - - result.notify_server = !settings->disable_sharing_stats_with_gameserver; - - if (!settings->disable_overlay) overlay->AddAchievementNotification(it.value()); - - } - } catch (...) {} - - auto &trig = store_stats_trigger[common_helpers::to_lower(org_name)]; - trig.m_bGroupAchievement = false; - trig.m_nCurProgress = 100; - trig.m_nGameID = settings->get_local_game_id().ToUint64(); - trig.m_nMaxProgress = 100; - memset(trig.m_rgchAchievementName, 0, sizeof(trig.m_rgchAchievementName)); - org_name.copy(trig.m_rgchAchievementName, sizeof(trig.m_rgchAchievementName) - 1); - - return result; -} - -Steam_User_Stats::InternalSetResult Steam_User_Stats::clear_achievement_internal( const char *pchName ) -{ - PRINT_DEBUG("'%s'", pchName); - std::lock_guard lock(global_mutex); - Steam_User_Stats::InternalSetResult result{}; - - if (!pchName) return result; - - std::string org_name(pchName); - - nlohmann::detail::iter_impl it = defined_achievements.end(); - try { - it = defined_achievements_find(org_name); - } catch(...) { } - if (defined_achievements.end() == it) return result; - - result.current_val = false; - result.internal_name = org_name; - result.success = true; - - try { - std::string internal_name = it->value("name", std::string()); - - result.internal_name = internal_name; - - auto ach = user_achievements.find(internal_name); - // assume "earned" is true in case the json obj exists, but the key is absent - // assume "earned_time" is UINT32_MAX in case the json obj exists, but the key is absent - if (user_achievements.end() == ach || - ach->value("earned", true) == true || - ach->value("earned_time", static_cast(UINT32_MAX)) == UINT32_MAX) { - - user_achievements[internal_name]["earned"] = false; - user_achievements[internal_name]["earned_time"] = static_cast(0); - save_achievements(); - - result.notify_server = !settings->disable_sharing_stats_with_gameserver; - - } - } catch (...) {} - - store_stats_trigger.erase(common_helpers::to_lower(org_name)); - - return result; -} - - -void Steam_User_Stats::steam_user_stats_network_low_level(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); - - auto inst = (Steam_User_Stats *)object; - inst->network_callback_low_level(msg); -} - -void Steam_User_Stats::steam_user_stats_network_stats(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); - - auto inst = (Steam_User_Stats *)object; - inst->network_callback_stats(msg); -} - -void Steam_User_Stats::steam_user_stats_network_leaderboards(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); - - auto inst = (Steam_User_Stats *)object; - inst->network_callback_leaderboards(msg); -} - -void Steam_User_Stats::steam_user_stats_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - auto inst = (Steam_User_Stats *)object; - inst->steam_run_callback(); -} - - -Steam_User_Stats::Steam_User_Stats(Settings *settings, class Networking *network, Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, Steam_Overlay* overlay): - settings(settings), - network(network), - local_storage(local_storage), - callback_results(callback_results), - callbacks(callbacks), - defined_achievements(nlohmann::json::object()), - user_achievements(nlohmann::json::object()), - run_every_runcb(run_every_runcb), - overlay(overlay) -{ - load_achievements_db(); // achievements db - load_achievements(); // achievements per user - - auto x = defined_achievements.begin(); - while (x != defined_achievements.end()) { - if (!x->contains("name")) { - x = defined_achievements.erase(x); - } else { - ++x; - } - } - - for (auto & it : defined_achievements) { - try { - std::string name = static_cast(it["name"]); - sorted_achievement_names.push_back(name); - if (user_achievements.find(name) == user_achievements.end()) { - user_achievements[name]["earned"] = false; - user_achievements[name]["earned_time"] = static_cast(0); - } - - achievement_trigger trig; - trig.name = name; - trig.value_operation = static_cast(it["progress"]["value"]["operation"]); - std::string stat_name = common_helpers::ascii_to_lowercase(static_cast(it["progress"]["value"]["operand1"])); - trig.min_value = static_cast(it["progress"]["min_val"]); - trig.max_value = static_cast(it["progress"]["max_val"]); - achievement_stat_trigger[stat_name].push_back(trig); - } catch (...) {} - - try { - it["hidden"] = std::to_string(it["hidden"].get()); - } catch (...) {} - - it["displayName"] = get_value_for_language(it, "displayName", settings->get_language()); - it["description"] = get_value_for_language(it, "description", settings->get_language()); - } - - //TODO: not sure if the sort is actually case insensitive, ach names seem to be treated by steam as case insensitive so I assume they are. - //need to find a game with achievements of different case names to confirm - std::sort(sorted_achievement_names.begin(), sorted_achievement_names.end(), [](const std::string lhs, const std::string rhs){ - const auto result = std::mismatch(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend(), [](const unsigned char lhs, const unsigned char rhs){return std::tolower(lhs) == std::tolower(rhs);}); - return result.second != rhs.cend() && (result.first == lhs.cend() || std::tolower(*result.first) < std::tolower(*result.second));} - ); - - if (!settings->disable_sharing_stats_with_gameserver) { - this->network->setCallback(CALLBACK_ID_GAMESERVER_STATS, settings->get_local_steam_id(), &Steam_User_Stats::steam_user_stats_network_stats, this); - } - if (settings->share_leaderboards_over_network) { - this->network->setCallback(CALLBACK_ID_LEADERBOARDS_STATS, settings->get_local_steam_id(), &Steam_User_Stats::steam_user_stats_network_leaderboards, this); - } - this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_User_Stats::steam_user_stats_network_low_level, this); - this->run_every_runcb->add(&Steam_User_Stats::steam_user_stats_run_every_runcb, this); -} - -Steam_User_Stats::~Steam_User_Stats() -{ - if (!settings->disable_sharing_stats_with_gameserver) { - this->network->rmCallback(CALLBACK_ID_GAMESERVER_STATS, settings->get_local_steam_id(), &Steam_User_Stats::steam_user_stats_network_stats, this); - } - if (settings->share_leaderboards_over_network) { - this->network->rmCallback(CALLBACK_ID_LEADERBOARDS_STATS, settings->get_local_steam_id(), &Steam_User_Stats::steam_user_stats_network_leaderboards, this); - } - this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_User_Stats::steam_user_stats_network_low_level, this); - this->run_every_runcb->remove(&Steam_User_Stats::steam_user_stats_run_every_runcb, this); -} - -// Ask the server to send down this user's data and achievements for this game -STEAM_CALL_BACK( UserStatsReceived_t ) -bool Steam_User_Stats::RequestCurrentStats() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - UserStatsReceived_t data{}; - data.m_nGameID = settings->get_local_game_id().ToUint64(); - data.m_eResult = k_EResultOK; - data.m_steamIDUser = settings->get_local_steam_id(); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); - return true; -} - - -// Data accessors -bool Steam_User_Stats::GetStat( const char *pchName, int32 *pData ) -{ - PRINT_DEBUG(" '%s' %p", pchName, pData); - std::lock_guard lock(global_mutex); - - if (!pchName) return false; - std::string stat_name = common_helpers::ascii_to_lowercase(pchName); - - const auto &stats_config = settings->getStats(); - auto stats_data = stats_config.find(stat_name); - if (stats_config.end() == stats_data) return false; - if (stats_data->second.type != GameServerStats_Messages::StatInfo::STAT_TYPE_INT) return false; - - auto cached_stat = stats_cache_int.find(stat_name); - if (cached_stat != stats_cache_int.end()) { - if (pData) *pData = cached_stat->second; - return true; - } - - int32 output = 0; - int read_data = local_storage->get_data(Local_Storage::stats_storage_folder, stat_name, (char* )&output, sizeof(output)); - if (read_data == sizeof(int32)) { - stats_cache_int[stat_name] = output; - if (pData) *pData = output; - return true; - } - - stats_cache_int[stat_name] = stats_data->second.default_value_int; - if (pData) *pData = stats_data->second.default_value_int; - return true; -} - -bool Steam_User_Stats::GetStat( const char *pchName, float *pData ) -{ - PRINT_DEBUG(" '%s' %p", pchName, pData); - std::lock_guard lock(global_mutex); - - if (!pchName) return false; - std::string stat_name = common_helpers::ascii_to_lowercase(pchName); - - const auto &stats_config = settings->getStats(); - auto stats_data = stats_config.find(stat_name); - if (stats_config.end() == stats_data) return false; - if (stats_data->second.type == GameServerStats_Messages::StatInfo::STAT_TYPE_INT) return false; - - auto cached_stat = stats_cache_float.find(stat_name); - if (cached_stat != stats_cache_float.end()) { - if (pData) *pData = cached_stat->second; - return true; - } - - float output = 0.0; - int read_data = local_storage->get_data(Local_Storage::stats_storage_folder, stat_name, (char* )&output, sizeof(output)); - if (read_data == sizeof(float)) { - stats_cache_float[stat_name] = output; - if (pData) *pData = output; - return true; - } - - stats_cache_float[stat_name] = stats_data->second.default_value_float; - if (pData) *pData = stats_data->second.default_value_float; - return true; -} - - -// Set / update data -bool Steam_User_Stats::SetStat( const char *pchName, int32 nData ) -{ - PRINT_DEBUG(" '%s' = %i", pchName, nData); - std::lock_guard lock(global_mutex); - - auto ret = set_stat_internal(pchName, nData ); - if (ret.success && ret.notify_server ) { - auto &new_stat = (*pending_server_updates.mutable_user_stats())[ret.internal_name]; - new_stat.set_stat_type(GameServerStats_Messages::StatInfo::STAT_TYPE_INT); - new_stat.set_value_int(ret.current_val); - - if (settings->immediate_gameserver_stats) send_updated_stats(); - } - - return ret.success; -} - -bool Steam_User_Stats::SetStat( const char *pchName, float fData ) -{ - PRINT_DEBUG(" '%s' = %f", pchName, fData); - std::lock_guard lock(global_mutex); - - auto ret = set_stat_internal(pchName, fData); - if (ret.success && ret.notify_server) { - auto &new_stat = (*pending_server_updates.mutable_user_stats())[ret.internal_name]; - new_stat.set_stat_type(ret.current_val.first); - new_stat.set_value_float(ret.current_val.second); - - if (settings->immediate_gameserver_stats) send_updated_stats(); - } - - return ret.success; -} - -bool Steam_User_Stats::UpdateAvgRateStat( const char *pchName, float flCountThisSession, double dSessionLength ) -{ - PRINT_DEBUG("'%s'", pchName); - std::lock_guard lock(global_mutex); - - auto ret = update_avg_rate_stat_internal(pchName, flCountThisSession, dSessionLength); - if (ret.success && ret.notify_server) { - auto &new_stat = (*pending_server_updates.mutable_user_stats())[ret.internal_name]; - new_stat.set_stat_type(ret.current_val.first); - new_stat.set_value_float(ret.current_val.second); - - if (settings->immediate_gameserver_stats) send_updated_stats(); - } - - return ret.success; -} - - -// Achievement flag accessors -bool Steam_User_Stats::GetAchievement( const char *pchName, bool *pbAchieved ) -{ - PRINT_DEBUG("'%s'", pchName); - std::lock_guard lock(global_mutex); - - if (!pchName) return false; - - nlohmann::detail::iter_impl it = defined_achievements.end(); - try { - it = defined_achievements_find(pchName); - } catch(...) { } - if (defined_achievements.end() == it) return false; - - // according to docs, the function returns true if the achievement was found, - // regardless achieved or not - if (!pbAchieved) return true; - - *pbAchieved = false; - try { - std::string pch_name = it->value("name", std::string()); - auto ach = user_achievements.find(pch_name); - if (user_achievements.end() != ach) { - *pbAchieved = ach->value("earned", false); - } - } catch (...) { } - - return true; -} - -bool Steam_User_Stats::SetAchievement( const char *pchName ) -{ - PRINT_DEBUG("'%s'", pchName); - std::lock_guard lock(global_mutex); - - auto ret = set_achievement_internal(pchName); - if (ret.success && ret.notify_server) { - auto &new_ach = (*pending_server_updates.mutable_user_achievements())[ret.internal_name]; - new_ach.set_achieved(ret.current_val); - - if (settings->immediate_gameserver_stats) send_updated_stats(); - } - - return ret.success; -} - -bool Steam_User_Stats::ClearAchievement( const char *pchName ) -{ - PRINT_DEBUG("'%s'", pchName); - std::lock_guard lock(global_mutex); - - auto ret = clear_achievement_internal(pchName); - if (ret.success && ret.notify_server) { - auto &new_ach = (*pending_server_updates.mutable_user_achievements())[ret.internal_name]; - new_ach.set_achieved(ret.current_val); - - if (settings->immediate_gameserver_stats) send_updated_stats(); - } - - return ret.success; -} - - -// Get the achievement status, and the time it was unlocked if unlocked. -// If the return value is true, but the unlock time is zero, that means it was unlocked before Steam -// began tracking achievement unlock times (December 2009). Time is seconds since January 1, 1970. -bool Steam_User_Stats::GetAchievementAndUnlockTime( const char *pchName, bool *pbAchieved, uint32 *punUnlockTime ) -{ - PRINT_DEBUG("'%s'", pchName); - std::lock_guard lock(global_mutex); - - if (!pchName) return false; - - nlohmann::detail::iter_impl it = defined_achievements.end(); - try { - it = defined_achievements_find(pchName); - } catch(...) { } - if (defined_achievements.end() == it) return false; - - if (pbAchieved) *pbAchieved = false; - if (punUnlockTime) *punUnlockTime = 0; - - try { - std::string pch_name = it->value("name", std::string()); - auto ach = user_achievements.find(pch_name); - if (user_achievements.end() != ach) { - if (pbAchieved) *pbAchieved = ach->value("earned", false); - if (punUnlockTime) *punUnlockTime = ach->value("earned_time", static_cast(0)); - } - } catch (...) {} - - return true; -} - - -// Store the current data on the server, will get a callback when set -// And one callback for every new achievement -// -// If the callback has a result of k_EResultInvalidParam, one or more stats -// uploaded has been rejected, either because they broke constraints -// or were out of date. In this case the server sends back updated values. -// The stats should be re-iterated to keep in sync. -bool Steam_User_Stats::StoreStats() -{ - // no need to exchange data with gameserver, we already do that in run_callback() and on each stat/ach update (immediate mode) - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - UserStatsStored_t data{}; - data.m_eResult = k_EResultOK; - data.m_nGameID = settings->get_local_game_id().ToUint64(); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.01); - - for (auto &kv : store_stats_trigger) { - callbacks->addCBResult(kv.second.k_iCallback, &kv.second, sizeof(kv.second)); - } - store_stats_trigger.clear(); - - return true; -} - - -// Achievement / GroupAchievement metadata - -// Gets the icon of the achievement, which is a handle to be used in ISteamUtils::GetImageRGBA(), or 0 if none set. -// A return value of 0 may indicate we are still fetching data, and you can wait for the UserAchievementIconFetched_t callback -// which will notify you when the bits are ready. If the callback still returns zero, then there is no image set for the -// specified achievement. -int Steam_User_Stats::GetAchievementIcon( const char *pchName ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - if (!pchName) return 0; - - // callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return 0; -} - -std::string Steam_User_Stats::get_achievement_icon_name( const char *pchName, bool pbAchieved ) -{ - std::lock_guard lock(global_mutex); - if (!pchName) return ""; - - nlohmann::detail::iter_impl it = defined_achievements.end(); - try { - it = defined_achievements_find(pchName); - } catch(...) { } - if (defined_achievements.end() == it) return ""; - - try { - if (pbAchieved) return it.value()["icon"].get(); - - std::string locked_icon = it.value().value("icon_gray", std::string()); - if (locked_icon.size()) return locked_icon; - else return it.value().value("icongray", std::string()); // old format - } catch (...) {} - - return ""; -} - - -// Get general attributes for an achievement. Accepts the following keys: -// - "name" and "desc" for retrieving the localized achievement name and description (returned in UTF8) -// - "hidden" for retrieving if an achievement is hidden (returns "0" when not hidden, "1" when hidden) -const char * Steam_User_Stats::GetAchievementDisplayAttribute( const char *pchName, const char *pchKey ) -{ - PRINT_DEBUG("[%s] [%s]", pchName, pchKey); - std::lock_guard lock(global_mutex); - - if (!pchName || !pchKey || !pchKey[0]) return ""; - - nlohmann::detail::iter_impl it = defined_achievements.end(); - try { - it = defined_achievements_find(pchName); - } catch(...) { } - if (defined_achievements.end() == it) return ""; - - if (strncmp(pchKey, "name", sizeof("name")) == 0) { - try { - return it.value()["displayName"].get_ptr()->c_str(); - } catch (...) {} - } else if (strncmp(pchKey, "desc", sizeof("desc")) == 0) { - try { - return it.value()["description"].get_ptr()->c_str(); - } catch (...) {} - } else if (strncmp(pchKey, "hidden", sizeof("hidden")) == 0) { - try { - return it.value()["hidden"].get_ptr()->c_str(); - } catch (...) {} - } - - return ""; -} - - -// Achievement progress - triggers an AchievementProgress callback, that is all. -// Calling this w/ N out of N progress will NOT set the achievement, the game must still do that. -bool Steam_User_Stats::IndicateAchievementProgress( const char *pchName, uint32 nCurProgress, uint32 nMaxProgress ) -{ - PRINT_DEBUG("%s", pchName); - std::lock_guard lock(global_mutex); - - if (!pchName) return false; - if (nCurProgress >= nMaxProgress) return false; - std::string ach_name(pchName); - - // find in achievements.json - nlohmann::detail::iter_impl it = defined_achievements.end(); - try { - it = defined_achievements_find(ach_name); - } catch(...) { } - if (defined_achievements.end() == it) return false; - - // get actual name from achievements.json - std::string actual_ach_name{}; - try { - actual_ach_name = it->value("name", std::string()); - } catch (...) { } - if (actual_ach_name.empty()) { // fallback - actual_ach_name = ach_name; - } - - // check if already achieved - bool achieved = false; - try { - auto ach = user_achievements.find(actual_ach_name); - if (ach != user_achievements.end()) { - achieved = ach->value("earned", false); - } - } catch (...) { } - if (achieved) return false; - - // save new progress - try { - user_achievements[actual_ach_name]["progress"] = nCurProgress; - user_achievements[actual_ach_name]["max_progress"] = nMaxProgress; - save_achievements(); - } catch (...) {} - - { - UserStatsStored_t data{}; - data.m_eResult = EResult::k_EResultOK; - data.m_nGameID = settings->get_local_game_id().ToUint64(); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - - UserAchievementStored_t data{}; - data.m_nGameID = settings->get_local_game_id().ToUint64(); - data.m_bGroupAchievement = false; - data.m_nCurProgress = nCurProgress; - data.m_nMaxProgress = nMaxProgress; - ach_name.copy(data.m_rgchAchievementName, sizeof(data.m_rgchAchievementName) - 1); - - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - // callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); // TODO was this correct? - return true; -} - - -// Used for iterating achievements. In general games should not need these functions because they should have a -// list of existing achievements compiled into them -uint32 Steam_User_Stats::GetNumAchievements() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - return (uint32)defined_achievements.size(); -} - -// Get achievement name iAchievement in [0,GetNumAchievements) -const char * Steam_User_Stats::GetAchievementName( uint32 iAchievement ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (iAchievement >= sorted_achievement_names.size()) { - return ""; - } - - return sorted_achievement_names[iAchievement].c_str(); -} - - -// Friends stats & achievements - -// downloads stats for the user -// returns a UserStatsReceived_t received when completed -// if the other user has no stats, UserStatsReceived_t.m_eResult will be set to k_EResultFail -// these stats won't be auto-updated; you'll need to call RequestUserStats() again to refresh any data -STEAM_CALL_RESULT( UserStatsReceived_t ) -SteamAPICall_t Steam_User_Stats::RequestUserStats( CSteamID steamIDUser ) -{ - PRINT_DEBUG("%llu", steamIDUser.ConvertToUint64()); - std::lock_guard lock(global_mutex); - - // Enable this to allow hot reload achievements status - //if (steamIDUser == settings->get_local_steam_id()) { - // load_achievements(); - //} - - UserStatsReceived_t data{}; - data.m_nGameID = settings->get_local_game_id().ToUint64(); - data.m_eResult = k_EResultOK; - data.m_steamIDUser = steamIDUser; - // appid 756800 expects both: a callback (global event occurring in the Steam environment), - // and a callresult (the specific result of this function call) - // otherwise it will lock-up and hang at startup - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); - return ret; -} - - -// requests stat information for a user, usable after a successful call to RequestUserStats() -bool Steam_User_Stats::GetUserStat( CSteamID steamIDUser, const char *pchName, int32 *pData ) -{ - PRINT_DEBUG("%s %llu", pchName, steamIDUser.ConvertToUint64()); - std::lock_guard lock(global_mutex); - - if (!pchName) return false; - - if (steamIDUser == settings->get_local_steam_id()) { - GetStat(pchName, pData); - } else { - *pData = 0; - } - - return true; -} - -bool Steam_User_Stats::GetUserStat( CSteamID steamIDUser, const char *pchName, float *pData ) -{ - PRINT_DEBUG("%s %llu", pchName, steamIDUser.ConvertToUint64()); - std::lock_guard lock(global_mutex); - - if (!pchName) return false; - - if (steamIDUser == settings->get_local_steam_id()) { - GetStat(pchName, pData); - } else { - *pData = 0; - } - - return true; -} - -bool Steam_User_Stats::GetUserAchievement( CSteamID steamIDUser, const char *pchName, bool *pbAchieved ) -{ - PRINT_DEBUG("%s", pchName); - std::lock_guard lock(global_mutex); - - if (!pchName) return false; - - if (steamIDUser == settings->get_local_steam_id()) { - return GetAchievement(pchName, pbAchieved); - } - - return false; -} - -// See notes for GetAchievementAndUnlockTime above -bool Steam_User_Stats::GetUserAchievementAndUnlockTime( CSteamID steamIDUser, const char *pchName, bool *pbAchieved, uint32 *punUnlockTime ) -{ - PRINT_DEBUG("%s", pchName); - std::lock_guard lock(global_mutex); - - if (!pchName) return false; - - if (steamIDUser == settings->get_local_steam_id()) { - return GetAchievementAndUnlockTime(pchName, pbAchieved, punUnlockTime); - } - return false; -} - - -// Reset stats -bool Steam_User_Stats::ResetAllStats( bool bAchievementsToo ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - clear_stats_internal(); - if (!settings->disable_sharing_stats_with_gameserver) { - for (const auto &stat : settings->getStats()) { - std::string stat_name(common_helpers::ascii_to_lowercase(stat.first)); - - auto &new_stat = (*pending_server_updates.mutable_user_stats())[stat_name]; - new_stat.set_stat_type(stat.second.type); - - switch (stat.second.type) - { - case GameServerStats_Messages::StatInfo::STAT_TYPE_INT: - new_stat.set_value_int(stat.second.default_value_int); - break; - - case GameServerStats_Messages::StatInfo::STAT_TYPE_AVGRATE: - case GameServerStats_Messages::StatInfo::STAT_TYPE_FLOAT: - new_stat.set_value_float(stat.second.default_value_float); - break; - - default: PRINT_DEBUG("unhandled type %i", (int)stat.second.type); break; - } - } - } - - if (bAchievementsToo) { - bool needs_disk_write = false; - for (auto &item : user_achievements) { - // assume "earned" is true in case the json obj exists, but the key is absent - if (item.value("earned", true) == true) needs_disk_write = true; - - item["earned"] = false; - item["earned_time"] = 0; - } - if (needs_disk_write) save_achievements(); - - if (!settings->disable_sharing_stats_with_gameserver) { - for (const auto &item : user_achievements.items()) { - auto &new_ach = (*pending_server_updates.mutable_user_achievements())[item.key()]; - new_ach.set_achieved(false); - } - } - } - - if (!settings->disable_sharing_stats_with_gameserver && settings->immediate_gameserver_stats) send_updated_stats(); - - return true; -} - - -// Leaderboard functions - -// asks the Steam back-end for a leaderboard by name, and will create it if it's not yet -// This call is asynchronous, with the result returned in LeaderboardFindResult_t -STEAM_CALL_RESULT(LeaderboardFindResult_t) -SteamAPICall_t Steam_User_Stats::FindOrCreateLeaderboard( const char *pchLeaderboardName, ELeaderboardSortMethod eLeaderboardSortMethod, ELeaderboardDisplayType eLeaderboardDisplayType ) -{ - PRINT_DEBUG("'%s'", pchLeaderboardName); - std::lock_guard lock(global_mutex); - if (!pchLeaderboardName) { - LeaderboardFindResult_t data{}; - data.m_hSteamLeaderboard = 0; - data.m_bLeaderboardFound = 0; - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return ret; - } - - unsigned int board_handle = cache_leaderboard_ifneeded(pchLeaderboardName, eLeaderboardSortMethod, eLeaderboardDisplayType); - send_my_leaderboard_score(cached_leaderboards[board_handle - 1], nullptr, true); - - LeaderboardFindResult_t data{}; - data.m_hSteamLeaderboard = board_handle; - data.m_bLeaderboardFound = 1; - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); // TODO is the timing ok? - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); - return ret; -} - - -// as above, but won't create the leaderboard if it's not found -// This call is asynchronous, with the result returned in LeaderboardFindResult_t -STEAM_CALL_RESULT( LeaderboardFindResult_t ) -SteamAPICall_t Steam_User_Stats::FindLeaderboard( const char *pchLeaderboardName ) -{ - PRINT_DEBUG("'%s'", pchLeaderboardName); - std::lock_guard lock(global_mutex); - if (!pchLeaderboardName) { - LeaderboardFindResult_t data{}; - data.m_hSteamLeaderboard = 0; - data.m_bLeaderboardFound = 0; - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return ret; - } - - std::string name_lower(common_helpers::ascii_to_lowercase(pchLeaderboardName)); - const auto &settings_Leaderboards = settings->getLeaderboards(); - auto it = settings_Leaderboards.begin(); - for (; settings_Leaderboards.end() != it; ++it) { - if (common_helpers::str_cmp_insensitive(it->first, name_lower)) break; - } - if (settings_Leaderboards.end() != it) { - auto &config = it->second; - return FindOrCreateLeaderboard(pchLeaderboardName, config.sort_method, config.display_type); - } else if (!settings->disable_leaderboards_create_unknown) { - return FindOrCreateLeaderboard(pchLeaderboardName, k_ELeaderboardSortMethodDescending, k_ELeaderboardDisplayTypeNumeric); - } else { - LeaderboardFindResult_t data{}; - data.m_hSteamLeaderboard = find_cached_leaderboard(name_lower); - data.m_bLeaderboardFound = !!data.m_hSteamLeaderboard; - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return ret; - } -} - - -// returns the name of a leaderboard -const char * Steam_User_Stats::GetLeaderboardName( SteamLeaderboard_t hSteamLeaderboard ) -{ - PRINT_DEBUG("%llu", hSteamLeaderboard); - std::lock_guard lock(global_mutex); - if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return ""; - - return cached_leaderboards[hSteamLeaderboard - 1].name.c_str(); -} - - -// returns the total number of entries in a leaderboard, as of the last request -int Steam_User_Stats::GetLeaderboardEntryCount( SteamLeaderboard_t hSteamLeaderboard ) -{ - PRINT_DEBUG("%llu", hSteamLeaderboard); - std::lock_guard lock(global_mutex); - if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return 0; - - return (int)cached_leaderboards[hSteamLeaderboard - 1].entries.size(); -} - - -// returns the sort method of the leaderboard -ELeaderboardSortMethod Steam_User_Stats::GetLeaderboardSortMethod( SteamLeaderboard_t hSteamLeaderboard ) -{ - PRINT_DEBUG("%llu", hSteamLeaderboard); - std::lock_guard lock(global_mutex); - if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return k_ELeaderboardSortMethodNone; - - return cached_leaderboards[hSteamLeaderboard - 1].sort_method; -} - - -// returns the display type of the leaderboard -ELeaderboardDisplayType Steam_User_Stats::GetLeaderboardDisplayType( SteamLeaderboard_t hSteamLeaderboard ) -{ - PRINT_DEBUG("%llu", hSteamLeaderboard); - std::lock_guard lock(global_mutex); - if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return k_ELeaderboardDisplayTypeNone; - - return cached_leaderboards[hSteamLeaderboard - 1].display_type; -} - - -// Asks the Steam back-end for a set of rows in the leaderboard. -// This call is asynchronous, with the result returned in LeaderboardScoresDownloaded_t -// LeaderboardScoresDownloaded_t will contain a handle to pull the results from GetDownloadedLeaderboardEntries() (below) -// You can ask for more entries than exist, and it will return as many as do exist. -// k_ELeaderboardDataRequestGlobal requests rows in the leaderboard from the full table, with nRangeStart & nRangeEnd in the range [1, TotalEntries] -// k_ELeaderboardDataRequestGlobalAroundUser requests rows around the current user, nRangeStart being negate -// e.g. DownloadLeaderboardEntries( hLeaderboard, k_ELeaderboardDataRequestGlobalAroundUser, -3, 3 ) will return 7 rows, 3 before the user, 3 after -// k_ELeaderboardDataRequestFriends requests all the rows for friends of the current user -STEAM_CALL_RESULT( LeaderboardScoresDownloaded_t ) -SteamAPICall_t Steam_User_Stats::DownloadLeaderboardEntries( SteamLeaderboard_t hSteamLeaderboard, ELeaderboardDataRequest eLeaderboardDataRequest, int nRangeStart, int nRangeEnd ) -{ - PRINT_DEBUG("%llu %i [%i, %i]", hSteamLeaderboard, eLeaderboardDataRequest, nRangeStart, nRangeEnd); - std::lock_guard lock(global_mutex); - if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return k_uAPICallInvalid; //might return callresult even if hSteamLeaderboard is invalid - - int entries_count = (int)cached_leaderboards[hSteamLeaderboard - 1].entries.size(); - // https://partner.steamgames.com/doc/api/ISteamUserStats#ELeaderboardDataRequest - if (eLeaderboardDataRequest != k_ELeaderboardDataRequestFriends) { - int required_count = nRangeEnd - nRangeStart + 1; - if (required_count < 0) required_count = 0; - - if (required_count < entries_count) entries_count = required_count; - } - LeaderboardScoresDownloaded_t data{}; - data.m_hSteamLeaderboard = hSteamLeaderboard; - data.m_hSteamLeaderboardEntries = hSteamLeaderboard; - data.m_cEntryCount = entries_count; - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); // TODO is this timing ok? - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); - return ret; -} - -// as above, but downloads leaderboard entries for an arbitrary set of users - ELeaderboardDataRequest is k_ELeaderboardDataRequestUsers -// if a user doesn't have a leaderboard entry, they won't be included in the result -// a max of 100 users can be downloaded at a time, with only one outstanding call at a time -STEAM_METHOD_DESC(Downloads leaderboard entries for an arbitrary set of users - ELeaderboardDataRequest is k_ELeaderboardDataRequestUsers) -STEAM_CALL_RESULT( LeaderboardScoresDownloaded_t ) -SteamAPICall_t Steam_User_Stats::DownloadLeaderboardEntriesForUsers( SteamLeaderboard_t hSteamLeaderboard, - STEAM_ARRAY_COUNT_D(cUsers, Array of users to retrieve) CSteamID *prgUsers, int cUsers ) -{ - PRINT_DEBUG("%i %llu", cUsers, cUsers > 0 ? prgUsers[0].ConvertToUint64() : 0); - std::lock_guard lock(global_mutex); - if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return k_uAPICallInvalid; //might return callresult even if hSteamLeaderboard is invalid - - auto &board = cached_leaderboards[hSteamLeaderboard - 1]; - bool ok = true; - int total_count = 0; - if (prgUsers && cUsers > 0) { - for (int i = 0; i < cUsers; ++i) { - const auto &user_steamid = prgUsers[i]; - if (!user_steamid.IsValid()) { - ok = false; - PRINT_DEBUG("bad userid %llu", user_steamid.ConvertToUint64()); - break; - } - if (board.find_recent_entry(user_steamid)) ++total_count; - - request_user_leaderboard_entry(board, user_steamid); - } - } - - PRINT_DEBUG("total count %i", total_count); - // https://partner.steamgames.com/doc/api/ISteamUserStats#DownloadLeaderboardEntriesForUsers - if (!ok || total_count > 100) return k_uAPICallInvalid; - - LeaderboardScoresDownloaded_t data{}; - data.m_hSteamLeaderboard = hSteamLeaderboard; - data.m_hSteamLeaderboardEntries = hSteamLeaderboard; - data.m_cEntryCount = total_count; - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); // TODO is this timing ok? - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); - return ret; -} - - -// Returns data about a single leaderboard entry -// use a for loop from 0 to LeaderboardScoresDownloaded_t::m_cEntryCount to get all the downloaded entries -// e.g. -// void OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *pLeaderboardScoresDownloaded ) -// { -// for ( int index = 0; index < pLeaderboardScoresDownloaded->m_cEntryCount; index++ ) -// { -// LeaderboardEntry_t leaderboardEntry; -// int32 details[3]; // we know this is how many we've stored previously -// GetDownloadedLeaderboardEntry( pLeaderboardScoresDownloaded->m_hSteamLeaderboardEntries, index, &leaderboardEntry, details, 3 ); -// assert( leaderboardEntry.m_cDetails == 3 ); -// ... -// } -// once you've accessed all the entries, the data will be free'd, and the SteamLeaderboardEntries_t handle will become invalid -bool Steam_User_Stats::GetDownloadedLeaderboardEntry( SteamLeaderboardEntries_t hSteamLeaderboardEntries, int index, LeaderboardEntry_t *pLeaderboardEntry, int32 *pDetails, int cDetailsMax ) -{ - PRINT_DEBUG("[%i] (%i) %llu %p %p", index, cDetailsMax, hSteamLeaderboardEntries, pLeaderboardEntry, pDetails); - std::lock_guard lock(global_mutex); - if (hSteamLeaderboardEntries > cached_leaderboards.size() || hSteamLeaderboardEntries <= 0) return false; - - const auto &board = cached_leaderboards[hSteamLeaderboardEntries - 1]; - if (index < 0 || index >= board.entries.size()) return false; - - const auto &target_entry = board.entries[index]; - - if (pLeaderboardEntry) { - LeaderboardEntry_t entry{}; - entry.m_steamIDUser = target_entry.steam_id; - entry.m_nGlobalRank = 1 + (int)(&target_entry - &board.entries[0]); - entry.m_nScore = target_entry.score; - - *pLeaderboardEntry = entry; - } - - if (pDetails && cDetailsMax > 0) { - for (int i = 0; i < target_entry.score_details.size() && i < cDetailsMax; ++i) { - pDetails[i] = target_entry.score_details[i]; - } - } - - return true; -} - - -// Uploads a user score to the Steam back-end. -// This call is asynchronous, with the result returned in LeaderboardScoreUploaded_t -// Details are extra game-defined information regarding how the user got that score -// pScoreDetails points to an array of int32's, cScoreDetailsCount is the number of int32's in the list -STEAM_CALL_RESULT( LeaderboardScoreUploaded_t ) -SteamAPICall_t Steam_User_Stats::UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, ELeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int32 nScore, const int32 *pScoreDetails, int cScoreDetailsCount ) -{ - PRINT_DEBUG("%llu %i", hSteamLeaderboard, nScore); - std::lock_guard lock(global_mutex); - if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return k_uAPICallInvalid; //TODO: might return callresult even if hSteamLeaderboard is invalid - - auto &board = cached_leaderboards[hSteamLeaderboard - 1]; - auto my_entry = board.find_recent_entry(settings->get_local_steam_id()); - int current_rank = my_entry - ? 1 + (int)(my_entry - &board.entries[0]) - : 0; - int new_rank = current_rank; - - bool score_updated = false; - if (my_entry) { - switch (eLeaderboardUploadScoreMethod) - { - case k_ELeaderboardUploadScoreMethodKeepBest: { // keep user's best score - if (board.sort_method == k_ELeaderboardSortMethodAscending) { // keep user's lowest score - score_updated = nScore < my_entry->score; - } else { // keep user's highest score - score_updated = nScore > my_entry->score; - } - } - break; - - case k_ELeaderboardUploadScoreMethodForceUpdate: { // always replace score with specified - score_updated = my_entry->score != nScore; - } - break; - - default: break; - } - } else { // no entry yet for us - score_updated = true; - } - - if (score_updated || (eLeaderboardUploadScoreMethod == k_ELeaderboardUploadScoreMethodForceUpdate)) { - Steam_Leaderboard_Entry new_entry{}; - new_entry.steam_id = settings->get_local_steam_id(); - new_entry.score = nScore; - if (pScoreDetails && cScoreDetailsCount > 0) { - for (int i = 0; i < cScoreDetailsCount; ++i) { - new_entry.score_details.push_back(pScoreDetails[i]); - } - } - - update_leaderboard_entry(board, new_entry); - new_rank = 1 + (int)(board.find_recent_entry(settings->get_local_steam_id()) - &board.entries[0]); - - // check again in case this was a forced update - // avoid disk write if score is the same - if (score_updated) save_my_leaderboard_entry(board); - send_my_leaderboard_score(board); - - } - - LeaderboardScoreUploaded_t data{}; - data.m_bSuccess = 1; //needs to be success or DOA6 freezes when uploading score. - data.m_hSteamLeaderboard = hSteamLeaderboard; - data.m_nScore = nScore; - data.m_bScoreChanged = score_updated; - data.m_nGlobalRankNew = new_rank; - data.m_nGlobalRankPrevious = current_rank; - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); // TODO is this timing ok? - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); - return ret; -} - -SteamAPICall_t Steam_User_Stats::UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, int32 nScore, int32 *pScoreDetails, int cScoreDetailsCount ) -{ - PRINT_DEBUG("old"); - return UploadLeaderboardScore(hSteamLeaderboard, k_ELeaderboardUploadScoreMethodKeepBest, nScore, pScoreDetails, cScoreDetailsCount); -} - - -// Attaches a piece of user generated content the user's entry on a leaderboard. -// hContent is a handle to a piece of user generated content that was shared using ISteamUserRemoteStorage::FileShare(). -// This call is asynchronous, with the result returned in LeaderboardUGCSet_t. -STEAM_CALL_RESULT( LeaderboardUGCSet_t ) -SteamAPICall_t Steam_User_Stats::AttachLeaderboardUGC( SteamLeaderboard_t hSteamLeaderboard, UGCHandle_t hUGC ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - LeaderboardUGCSet_t data{}; - if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) { - data.m_eResult = k_EResultFail; - } else { - data.m_eResult = k_EResultOK; - } - - data.m_hSteamLeaderboard = hSteamLeaderboard; - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return ret; -} - - -// Retrieves the number of players currently playing your game (online + offline) -// This call is asynchronous, with the result returned in NumberOfCurrentPlayers_t -STEAM_CALL_RESULT( NumberOfCurrentPlayers_t ) -SteamAPICall_t Steam_User_Stats::GetNumberOfCurrentPlayers() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - std::random_device rd{}; - std::mt19937 gen(rd()); - std::uniform_int_distribution distrib(117, 1017); - - NumberOfCurrentPlayers_t data{}; - data.m_bSuccess = 1; - data.m_cPlayers = distrib(gen); - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return ret; -} - - -// Requests that Steam fetch data on the percentage of players who have received each achievement -// for the game globally. -// This call is asynchronous, with the result returned in GlobalAchievementPercentagesReady_t. -STEAM_CALL_RESULT( GlobalAchievementPercentagesReady_t ) -SteamAPICall_t Steam_User_Stats::RequestGlobalAchievementPercentages() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - GlobalAchievementPercentagesReady_t data{}; - data.m_eResult = EResult::k_EResultOK; - data.m_nGameID = settings->get_local_game_id().ToUint64(); - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return ret; -} - - -// Get the info on the most achieved achievement for the game, returns an iterator index you can use to fetch -// the next most achieved afterwards. Will return -1 if there is no data on achievement -// percentages (ie, you haven't called RequestGlobalAchievementPercentages and waited on the callback). -int Steam_User_Stats::GetMostAchievedAchievementInfo( char *pchName, uint32 unNameBufLen, float *pflPercent, bool *pbAchieved ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (!pchName) return -1; - - std::string name(GetAchievementName(0)); - if (name.empty()) return -1; - - if (pchName && unNameBufLen) { - memset(pchName, 0, unNameBufLen); - name.copy(pchName, unNameBufLen - 1); - } - - if (pflPercent) *pflPercent = 90; - if (pbAchieved) { - bool achieved = false; - GetAchievement(name.c_str(), &achieved); - *pbAchieved = achieved; - } - - return 0; -} - - -// Get the info on the next most achieved achievement for the game. Call this after GetMostAchievedAchievementInfo or another -// GetNextMostAchievedAchievementInfo call passing the iterator from the previous call. Returns -1 after the last -// achievement has been iterated. -int Steam_User_Stats::GetNextMostAchievedAchievementInfo( int iIteratorPrevious, char *pchName, uint32 unNameBufLen, float *pflPercent, bool *pbAchieved ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - if (iIteratorPrevious < 0) return -1; - - int iIteratorCurrent = iIteratorPrevious + 1; - if (iIteratorCurrent >= defined_achievements.size()) return -1; - - std::string name(GetAchievementName(iIteratorCurrent)); - if (name.empty()) return -1; - - if (pchName && unNameBufLen) { - memset(pchName, 0, unNameBufLen); - name.copy(pchName, unNameBufLen - 1); - } - - if (pflPercent) { - *pflPercent = (float)(90 * (defined_achievements.size() - iIteratorCurrent) / defined_achievements.size()); - } - if (pbAchieved) { - bool achieved = false; - GetAchievement(name.c_str(), &achieved); - *pbAchieved = achieved; - } - - return iIteratorCurrent; -} - - -// Returns the percentage of users who have achieved the specified achievement. -bool Steam_User_Stats::GetAchievementAchievedPercent( const char *pchName, float *pflPercent ) -{ - PRINT_DEBUG("'%s'", pchName); - std::lock_guard lock(global_mutex); - - auto it = defined_achievements_find(pchName); - if (defined_achievements.end() == it) return false; - - size_t idx = it - defined_achievements.begin(); - if (pflPercent) { - *pflPercent = (float)(90 * (defined_achievements.size() - idx) / defined_achievements.size()); - } - - return true; -} - - -// Requests global stats data, which is available for stats marked as "aggregated". -// This call is asynchronous, with the results returned in GlobalStatsReceived_t. -// nHistoryDays specifies how many days of day-by-day history to retrieve in addition -// to the overall totals. The limit is 60. -STEAM_CALL_RESULT( GlobalStatsReceived_t ) -SteamAPICall_t Steam_User_Stats::RequestGlobalStats( int nHistoryDays ) -{ - PRINT_DEBUG("%i", nHistoryDays); - std::lock_guard lock(global_mutex); - GlobalStatsReceived_t data{}; - data.m_nGameID = settings->get_local_game_id().ToUint64(); - data.m_eResult = k_EResultOK; - auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return ret; -} - - -// Gets the lifetime totals for an aggregated stat -bool Steam_User_Stats::GetGlobalStat( const char *pchStatName, int64 *pData ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -bool Steam_User_Stats::GetGlobalStat( const char *pchStatName, double *pData ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - - -// Gets history for an aggregated stat. pData will be filled with daily values, starting with today. -// So when called, pData[0] will be today, pData[1] will be yesterday, and pData[2] will be two days ago, -// etc. cubData is the size in bytes of the pubData buffer. Returns the number of -// elements actually set. -int32 Steam_User_Stats::GetGlobalStatHistory( const char *pchStatName, STEAM_ARRAY_COUNT(cubData) int64 *pData, uint32 cubData ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -int32 Steam_User_Stats::GetGlobalStatHistory( const char *pchStatName, STEAM_ARRAY_COUNT(cubData) double *pData, uint32 cubData ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return 0; -} - -// For achievements that have related Progress stats, use this to query what the bounds of that progress are. -// You may want this info to selectively call IndicateAchievementProgress when appropriate milestones of progress -// have been made, to show a progress notification to the user. -bool Steam_User_Stats::GetAchievementProgressLimits( const char *pchName, int32 *pnMinProgress, int32 *pnMaxProgress ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - -bool Steam_User_Stats::GetAchievementProgressLimits( const char *pchName, float *pfMinProgress, float *pfMaxProgress ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; -} - - - -// --- steam callbacks - -void Steam_User_Stats::send_updated_stats() -{ - if (pending_server_updates.user_stats().empty() && pending_server_updates.user_achievements().empty()) return; - if (settings->disable_sharing_stats_with_gameserver) return; - - auto new_updates_msg = new GameServerStats_Messages::AllStats(pending_server_updates); - pending_server_updates.clear_user_stats(); - pending_server_updates.clear_user_achievements(); - - auto gameserverstats_msg = new GameServerStats_Messages(); - gameserverstats_msg->set_type(GameServerStats_Messages::UpdateUserStatsFromUser); - gameserverstats_msg->set_allocated_update_user_stats(new_updates_msg); - - Common_Message msg{}; - // https://protobuf.dev/reference/cpp/cpp-generated/#string - // set_allocated_xxx() takes ownership of the allocated object, no need to delete - msg.set_allocated_gameserver_stats_messages(gameserverstats_msg); - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - // here we send to all gameservers on the network because we don't know the server steamid - network->sendToAllGameservers(&msg, true); - - PRINT_DEBUG("sent updated stats: %zu stats, %zu achievements", - new_updates_msg->user_stats().size(), new_updates_msg->user_achievements().size() - ); -} - -void Steam_User_Stats::steam_run_callback() -{ - send_updated_stats(); -} - - - -// --- networking callbacks -// only triggered when we have a message - -void Steam_User_Stats::network_stats_initial(Common_Message *msg) -{ - if (!msg->gameserver_stats_messages().has_initial_user_stats()) { - PRINT_DEBUG("error empty msg"); - return; - } - - uint64 server_steamid = msg->source_id(); - - auto all_stats_msg = new GameServerStats_Messages::AllStats(); - - // get all stats - auto &stats_map = *all_stats_msg->mutable_user_stats(); - const auto ¤t_stats = settings->getStats(); - for (const auto &stat : current_stats) { - auto &this_stat = stats_map[stat.first]; - this_stat.set_stat_type(stat.second.type); - switch (stat.second.type) - { - case GameServerStats_Messages::StatInfo::STAT_TYPE_INT: { - int32 val = 0; - GetStat(stat.first.c_str(), &val); - this_stat.set_value_int(val); - } - break; - - case GameServerStats_Messages::StatInfo::STAT_TYPE_AVGRATE: // we set the float value also for avg - case GameServerStats_Messages::StatInfo::STAT_TYPE_FLOAT: { - float val = 0; - GetStat(stat.first.c_str(), &val); - this_stat.set_value_float(val); - } - break; - - default: - PRINT_DEBUG("Request_AllUserStats unhandled stat type %i", (int)stat.second.type); - break; - } - } - - // get all achievements - auto &achievements_map = *all_stats_msg->mutable_user_achievements(); - for (const auto &ach : defined_achievements) { - const std::string &name = static_cast( ach.value("name", std::string()) ); - auto &this_ach = achievements_map[name]; - bool achieved = false; - GetAchievement(name.c_str(), &achieved); - this_ach.set_achieved(achieved); - } - - auto initial_stats_msg = new GameServerStats_Messages::InitialAllStats(); - // send back same api call id - initial_stats_msg->set_steam_api_call(msg->gameserver_stats_messages().initial_user_stats().steam_api_call()); - initial_stats_msg->set_allocated_all_data(all_stats_msg); - - auto gameserverstats_msg = new GameServerStats_Messages(); - gameserverstats_msg->set_type(GameServerStats_Messages::Response_AllUserStats); - gameserverstats_msg->set_allocated_initial_user_stats(initial_stats_msg); - - Common_Message new_msg{}; - // https://protobuf.dev/reference/cpp/cpp-generated/#string - // set_allocated_xxx() takes ownership of the allocated object, no need to delete - new_msg.set_allocated_gameserver_stats_messages(gameserverstats_msg); - new_msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - new_msg.set_dest_id(server_steamid); - network->sendTo(&new_msg, true); - - PRINT_DEBUG("server requested all stats, sent %zu stats, %zu achievements", - initial_stats_msg->all_data().user_stats().size(), initial_stats_msg->all_data().user_achievements().size() - ); - - -} - -void Steam_User_Stats::network_stats_updated(Common_Message *msg) -{ - if (!msg->gameserver_stats_messages().has_update_user_stats()) { - PRINT_DEBUG("error empty msg"); - return; - } - - auto &new_user_data = msg->gameserver_stats_messages().update_user_stats(); - - // update our stats - for (auto &new_stat : new_user_data.user_stats()) { - switch (new_stat.second.stat_type()) - { - case GameServerStats_Messages::StatInfo::STAT_TYPE_INT: { - set_stat_internal(new_stat.first.c_str(), new_stat.second.value_int()); - } - break; - - case GameServerStats_Messages::StatInfo::STAT_TYPE_AVGRATE: - case GameServerStats_Messages::StatInfo::STAT_TYPE_FLOAT: { - set_stat_internal(new_stat.first.c_str(), new_stat.second.value_float()); - // non-INT values could have avg values - if (new_stat.second.has_value_avg()) { - auto &avg_val = new_stat.second.value_avg(); - update_avg_rate_stat_internal(new_stat.first.c_str(), avg_val.count_this_session(), avg_val.session_length()); - } - } - break; - - default: - PRINT_DEBUG("UpdateUserStats unhandled stat type %i", (int)new_stat.second.stat_type()); - break; - } - } - - // update achievements - for (auto &new_ach : new_user_data.user_achievements()) { - if (new_ach.second.achieved()) { - set_achievement_internal(new_ach.first.c_str()); - } else { - clear_achievement_internal(new_ach.first.c_str()); - } - } - - PRINT_DEBUG("server sent updated user stats, %zu stats, %zu achievements", - new_user_data.user_stats().size(), new_user_data.user_achievements().size() - ); -} - -void Steam_User_Stats::network_callback_stats(Common_Message *msg) -{ - // network->sendToAll() sends to current user also - if (msg->source_id() == settings->get_local_steam_id().ConvertToUint64()) return; - - uint64 server_steamid = msg->source_id(); - - switch (msg->gameserver_stats_messages().type()) - { - // server wants all stats - case GameServerStats_Messages::Request_AllUserStats: - network_stats_initial(msg); - break; - - // server has updated/new stats - case GameServerStats_Messages::UpdateUserStatsFromServer: - network_stats_updated(msg); - break; - - // a user has updated/new stats - case GameServerStats_Messages::UpdateUserStatsFromUser: - // nothing - break; - - default: - PRINT_DEBUG("unhandled type %i", (int)msg->gameserver_stats_messages().type()); - break; - } -} - - -// someone updated their score -void Steam_User_Stats::network_leaderboard_update_score(Common_Message *msg, Steam_Leaderboard &board, bool send_score_back) -{ - CSteamID sender_steamid((uint64)msg->source_id()); - PRINT_DEBUG("got score for user %llu on leaderboard '%s' (send our score back=%i)", - (uint64)msg->source_id(), board.name.c_str(), (int)send_score_back - ); - - // when players initally load a board, and they don't have an entry in it, - // they send this msg but without their user score entry - if (msg->leaderboards_messages().has_user_score_entry()) { - const auto &user_score_msg = msg->leaderboards_messages().user_score_entry(); - - Steam_Leaderboard_Entry updated_entry{}; - updated_entry.steam_id = sender_steamid; - updated_entry.score = user_score_msg.score(); - updated_entry.score_details.reserve(user_score_msg.score_details().size()); - updated_entry.score_details.assign(user_score_msg.score_details().begin(), user_score_msg.score_details().end()); - update_leaderboard_entry(board, updated_entry); - } - - // if the sender wants back our score, send it to all, not just them - // in case we have 3 or more players and none of them have our data - if (send_score_back) send_my_leaderboard_score(board); -} - -// someone is requesting our score on a leaderboard -void Steam_User_Stats::network_leaderboard_send_my_score(Common_Message *msg, const Steam_Leaderboard &board) -{ - CSteamID sender_steamid((uint64)msg->source_id()); - PRINT_DEBUG("user %llu requested our score for leaderboard '%s'", (uint64)msg->source_id(), board.name.c_str()); - - send_my_leaderboard_score(board, &sender_steamid); -} - -void Steam_User_Stats::network_callback_leaderboards(Common_Message *msg) -{ - // network->sendToAll() sends to current user also - if (msg->source_id() == settings->get_local_steam_id().ConvertToUint64()) return; - if (settings->get_local_game_id().AppID() != msg->leaderboards_messages().appid()) return; - - if (!msg->leaderboards_messages().has_leaderboard_info()) { - PRINT_DEBUG("error empty leaderboard msg"); - return; - } - - const auto &board_info_msg = msg->leaderboards_messages().leaderboard_info(); - - PRINT_DEBUG("attempting to cache leaderboard '%s'", board_info_msg.board_name().c_str()); - unsigned int board_handle = cache_leaderboard_ifneeded( - board_info_msg.board_name(), - (ELeaderboardSortMethod)board_info_msg.sort_method(), - (ELeaderboardDisplayType)board_info_msg.display_type() - ); - - switch (msg->leaderboards_messages().type()) { - // someone updated their score - case Leaderboards_Messages::UpdateUserScore: - network_leaderboard_update_score(msg, cached_leaderboards[board_handle - 1], false); - break; - - // someone updated their score and wants us to share back ours - case Leaderboards_Messages::UpdateUserScoreMutual: - network_leaderboard_update_score(msg, cached_leaderboards[board_handle - 1], true); - break; - - // someone is requesting our score on a leaderboard - case Leaderboards_Messages::RequestUserScore: - network_leaderboard_send_my_score(msg, cached_leaderboards[board_handle - 1]); - break; - - default: - PRINT_DEBUG("unhandled type %i", (int)msg->leaderboards_messages().type()); - break; - } - -} - - -// user connect/disconnect -void Steam_User_Stats::network_callback_low_level(Common_Message *msg) -{ - CSteamID steamid((uint64)msg->source_id()); - // this should never happen, but just in case - if (steamid == settings->get_local_steam_id()) return; - - switch (msg->low_level().type()) - { - case Low_Level::CONNECT: - // nothing - break; - - case Low_Level::DISCONNECT: { - for (auto &board : cached_leaderboards) { - board.remove_entries(steamid); - } - - // PRINT_DEBUG("removed user %llu", (uint64)steamid.ConvertToUint64()); - } - break; - - default: - PRINT_DEBUG("unknown type %i", (int)msg->low_level().type()); - break; - } -} +/* 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 + . */ + +#include "dll/steam_user_stats.h" +#include + + +// --- Steam_Leaderboard --- + +Steam_Leaderboard_Entry* Steam_Leaderboard::find_recent_entry(const CSteamID &steamid) const +{ + auto my_it = std::find_if(entries.begin(), entries.end(), [&steamid](const Steam_Leaderboard_Entry &item) { + return item.steam_id == steamid; + }); + if (entries.end() == my_it) return nullptr; + return const_cast(&*my_it); +} + +void Steam_Leaderboard::remove_entries(const CSteamID &steamid) +{ + auto rm_it = std::remove_if(entries.begin(), entries.end(), [&](const Steam_Leaderboard_Entry &item){ + return item.steam_id == steamid; + }); + if (entries.end() != rm_it) entries.erase(rm_it, entries.end()); +} + +void Steam_Leaderboard::remove_duplicate_entries() +{ + if (entries.size() <= 1) return; + + auto rm = std::remove_if(entries.begin(), entries.end(), [&](const Steam_Leaderboard_Entry& item) { + auto recent = find_recent_entry(item.steam_id); + return &item != recent; + }); + if (entries.end() != rm) entries.erase(rm, entries.end()); +} + +void Steam_Leaderboard::sort_entries() +{ + if (sort_method == k_ELeaderboardSortMethodNone) return; + if (entries.size() <= 1) return; + + std::sort(entries.begin(), entries.end(), [this](const Steam_Leaderboard_Entry &item1, const Steam_Leaderboard_Entry &item2) { + if (sort_method == k_ELeaderboardSortMethodAscending) { + return item1.score < item2.score; + } else { // k_ELeaderboardSortMethodDescending + return item1.score > item2.score; + } + }); + +} + +// --- Steam_Leaderboard --- + + + +// --- achievement_trigger --- +bool achievement_trigger::check_triggered(float stat) const +{ + try { + if (std::stof(max_value) <= stat) return true; + } catch (...) {} + + return false; +} + +bool achievement_trigger::check_triggered(int32 stat) const +{ + try { + if (std::stoi(max_value) <= stat) return true; + } catch (...) {} + + return false; +} +// --- achievement_trigger --- + + + +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); +} + +void Steam_User_Stats::load_achievements() +{ + local_storage->load_json_file("", achievements_user_file, user_achievements); +} + +void Steam_User_Stats::save_achievements() +{ + local_storage->write_json_file("", achievements_user_file, user_achievements); +} + + +nlohmann::detail::iter_impl Steam_User_Stats::defined_achievements_find(const std::string &key) +{ + return std::find_if( + defined_achievements.begin(), defined_achievements.end(), + [&key](const nlohmann::json& item) { + const std::string &name = static_cast( item.value("name", std::string()) ); + return common_helpers::str_cmp_insensitive(key, name); + } + ); +} + +std::string Steam_User_Stats::get_value_for_language(nlohmann::json &json, std::string key, std::string language) +{ + auto x = json.find(key); + if (x == json.end()) return ""; + if (x.value().is_string()) { + return x.value().get(); + } else if (x.value().is_object()) { + auto l = x.value().find(language); + if (l != x.value().end()) { + return l.value().get(); + } + + l = x.value().find("english"); + if (l != x.value().end()) { + return l.value().get(); + } + + l = x.value().begin(); + if (l != x.value().end()) { + if (l.key() == "token") { + std::string token_value = l.value().get(); + l++; + if (l != x.value().end()) { + return l.value().get(); + } + + return token_value; + } + + return l.value().get(); + } + } + + return ""; +} + + +/* +layout of each item in the leaderboard file +| steamid - lower 32-bits | steamid - higher 32-bits | score (4 bytes) | score details count (4 bytes) | score details array (4 bytes each) ... + [0] | [1] | [2] | [3] | [4] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ main header ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +*/ + +std::vector Steam_User_Stats::load_leaderboard_entries(const std::string &name) +{ + constexpr const static unsigned int MAIN_HEADER_ELEMENTS_COUNT = 4; + constexpr const static unsigned int ELEMENT_SIZE = (unsigned int)sizeof(uint32_t); + + std::vector out{}; + + std::string leaderboard_name(common_helpers::ascii_to_lowercase(name)); + unsigned read_bytes = local_storage->file_size(Local_Storage::leaderboard_storage_folder, leaderboard_name); + if ((read_bytes == 0) || + (read_bytes < (ELEMENT_SIZE * MAIN_HEADER_ELEMENTS_COUNT)) || + (read_bytes % ELEMENT_SIZE) != 0) { + return out; + } + + std::vector output(read_bytes / ELEMENT_SIZE); + if (local_storage->get_data(Local_Storage::leaderboard_storage_folder, leaderboard_name, (char* )output.data(), read_bytes) != read_bytes) return out; + + unsigned int i = 0; + while (true) { + if ((i + MAIN_HEADER_ELEMENTS_COUNT) > output.size()) break; // invalid main header, or end of buffer + + Steam_Leaderboard_Entry new_entry{}; + new_entry.steam_id = CSteamID((uint64)output[i] + (((uint64)output[i + 1]) << 32)); + new_entry.score = (int32)output[i + 2]; + uint32_t details_count = output[i + 3]; + i += MAIN_HEADER_ELEMENTS_COUNT; // skip main header + + if ((i + details_count) > output.size()) break; // invalid score details count + + for (uint32_t j = 0; j < details_count; ++j) { + new_entry.score_details.push_back(output[i]); + ++i; // move past this score detail + } + + PRINT_DEBUG("'%s': user %llu, score %i, details count = %zu", + name.c_str(), new_entry.steam_id.ConvertToUint64(), new_entry.score, new_entry.score_details.size() + ); + out.push_back(new_entry); + } + + PRINT_DEBUG("'%s' total entries = %zu", name.c_str(), out.size()); + return out; +} + +void Steam_User_Stats::save_my_leaderboard_entry(const Steam_Leaderboard &leaderboard) +{ + auto my_entry = leaderboard.find_recent_entry(settings->get_local_steam_id()); + if (!my_entry) return; // we don't have a score entry + + PRINT_DEBUG("saving entries for leaderboard '%s'", leaderboard.name.c_str()); + + std::vector output{}; + + uint64_t steam_id = my_entry->steam_id.ConvertToUint64(); + output.push_back((uint32_t)(steam_id & 0xFFFFFFFF)); // lower 4 bytes + output.push_back((uint32_t)(steam_id >> 32)); // higher 4 bytes + + output.push_back(my_entry->score); + output.push_back((uint32_t)my_entry->score_details.size()); + for (const auto &detail : my_entry->score_details) { + output.push_back(detail); + } + + std::string leaderboard_name(common_helpers::ascii_to_lowercase(leaderboard.name)); + local_storage->store_data(Local_Storage::leaderboard_storage_folder, leaderboard_name, (char* )&output[0], output.size() * sizeof(output[0])); +} + +Steam_Leaderboard_Entry* Steam_User_Stats::update_leaderboard_entry(Steam_Leaderboard &leaderboard, const Steam_Leaderboard_Entry &entry, bool overwrite) +{ + bool added = false; + auto user_entry = leaderboard.find_recent_entry(entry.steam_id); + if (!user_entry) { // user doesn't have an entry yet, create one + added = true; + leaderboard.entries.push_back(entry); + user_entry = &leaderboard.entries.back(); + } else if (overwrite) { + added = true; + *user_entry = entry; + } + + if (added) { // if we added a new entry then we have to sort and find the target entry again + leaderboard.sort_entries(); + user_entry = leaderboard.find_recent_entry(entry.steam_id); + PRINT_DEBUG("added/updated entry for user %llu", entry.steam_id.ConvertToUint64()); + } + + return user_entry; +} + + +unsigned int Steam_User_Stats::find_cached_leaderboard(const std::string &name) +{ + unsigned index = 1; + for (const auto &leaderboard : cached_leaderboards) { + if (common_helpers::str_cmp_insensitive(leaderboard.name, name)) return index; + + ++index; + } + + return 0; +} + +unsigned int Steam_User_Stats::cache_leaderboard_ifneeded(const std::string &name, ELeaderboardSortMethod eLeaderboardSortMethod, ELeaderboardDisplayType eLeaderboardDisplayType) +{ + unsigned int board_handle = find_cached_leaderboard(name); + if (board_handle) return board_handle; + // PRINT_DEBUG("cache miss '%s'", name.c_str()); + + // create a new entry in-memory and try reading the entries from disk + struct Steam_Leaderboard new_board{}; + new_board.name = common_helpers::ascii_to_lowercase(name); + new_board.sort_method = eLeaderboardSortMethod; + new_board.display_type = eLeaderboardDisplayType; + new_board.entries = load_leaderboard_entries(name); + + new_board.sort_entries(); + new_board.remove_duplicate_entries(); + + // save it in memory for later + cached_leaderboards.push_back(new_board); + board_handle = cached_leaderboards.size(); + + PRINT_DEBUG("cached a new leaderboard '%s' %i %i", + new_board.name.c_str(), (int)eLeaderboardSortMethod, (int)eLeaderboardDisplayType + ); + return board_handle; +} + +void Steam_User_Stats::send_my_leaderboard_score(const Steam_Leaderboard &board, const CSteamID *steamid, bool want_scores_back) +{ + if (!settings->share_leaderboards_over_network) return; + + const auto my_entry = board.find_recent_entry(settings->get_local_steam_id()); + Leaderboards_Messages::UserScoreEntry *score_entry_msg = nullptr; + + if (my_entry) { + score_entry_msg = new Leaderboards_Messages::UserScoreEntry(); + score_entry_msg->set_score(my_entry->score); + score_entry_msg->mutable_score_details()->Assign(my_entry->score_details.begin(), my_entry->score_details.end()); + } + + auto board_info_msg = new Leaderboards_Messages::LeaderboardInfo(); + board_info_msg->set_allocated_board_name(new std::string(board.name)); + board_info_msg->set_sort_method(board.sort_method); + board_info_msg->set_display_type(board.display_type); + + auto board_msg = new Leaderboards_Messages(); + if (want_scores_back) board_msg->set_type(Leaderboards_Messages::UpdateUserScoreMutual); + else board_msg->set_type(Leaderboards_Messages::UpdateUserScore); + board_msg->set_appid(settings->get_local_game_id().AppID()); + board_msg->set_allocated_leaderboard_info(board_info_msg); + // if we have an entry + if (score_entry_msg) board_msg->set_allocated_user_score_entry(score_entry_msg); + + Common_Message common_msg{}; + common_msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + if (steamid) common_msg.set_dest_id(steamid->ConvertToUint64()); + common_msg.set_allocated_leaderboards_messages(board_msg); + + if (steamid) network->sendTo(&common_msg, false); + else network->sendToAll(&common_msg, false); +} + +void Steam_User_Stats::request_user_leaderboard_entry(const Steam_Leaderboard &board, const CSteamID &steamid) +{ + if (!settings->share_leaderboards_over_network) return; + + auto board_info_msg = new Leaderboards_Messages::LeaderboardInfo(); + board_info_msg->set_allocated_board_name(new std::string(board.name)); + board_info_msg->set_sort_method(board.sort_method); + board_info_msg->set_display_type(board.display_type); + + auto board_msg = new Leaderboards_Messages(); + board_msg->set_type(Leaderboards_Messages::RequestUserScore); + board_msg->set_appid(settings->get_local_game_id().AppID()); + board_msg->set_allocated_leaderboard_info(board_info_msg); + + Common_Message common_msg{}; + common_msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + common_msg.set_dest_id(steamid.ConvertToUint64()); + common_msg.set_allocated_leaderboards_messages(board_msg); + + network->sendTo(&common_msg, false); +} + + +// change stats/achievements without sending back to server +bool Steam_User_Stats::clear_stats_internal() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + bool notify_server = false; + + stats_cache_int.clear(); + stats_cache_float.clear(); + + for (const auto &stat : settings->getStats()) { + std::string stat_name(common_helpers::ascii_to_lowercase(stat.first)); + + switch (stat.second.type) + { + case GameServerStats_Messages::StatInfo::STAT_TYPE_INT: { + auto data = stat.second.default_value_int; + + bool needs_disk_write = false; + auto it_res = stats_cache_int.find(stat_name); + if (stats_cache_int.end() == it_res || it_res->second != data) { + needs_disk_write = true; + notify_server = true; + } + + stats_cache_int[stat_name] = data; + + auto stat_trigger = achievement_stat_trigger.find(stat_name); + if (stat_trigger != achievement_stat_trigger.end()) { + for (auto &t : stat_trigger->second) { + if (t.check_triggered(data)) { + set_achievement_internal(t.name.c_str()); + } + } + } + + if (needs_disk_write) local_storage->store_data(Local_Storage::stats_storage_folder, stat_name, (char *)&data, sizeof(data)); + } + break; + + case GameServerStats_Messages::StatInfo::STAT_TYPE_FLOAT: + case GameServerStats_Messages::StatInfo::STAT_TYPE_AVGRATE: { + auto data = stat.second.default_value_float; + + bool needs_disk_write = false; + auto it_res = stats_cache_float.find(stat_name); + if (stats_cache_float.end() == it_res || it_res->second != data) { + needs_disk_write = true; + notify_server = true; + } + + stats_cache_float[stat_name] = data; + + auto stat_trigger = achievement_stat_trigger.find(stat_name); + if (stat_trigger != achievement_stat_trigger.end()) { + for (auto &t : stat_trigger->second) { + if (t.check_triggered(data)) { + set_achievement_internal(t.name.c_str()); + } + } + } + + if (needs_disk_write) local_storage->store_data(Local_Storage::stats_storage_folder, stat_name, (char *)&data, sizeof(data)); + } + break; + + default: PRINT_DEBUG("unhandled type %i", (int)stat.second.type); break; + } + } + + return notify_server; +} + +Steam_User_Stats::InternalSetResult Steam_User_Stats::set_stat_internal( const char *pchName, int32 nData ) +{ + PRINT_DEBUG(" '%s' = %i", pchName, nData); + std::lock_guard lock(global_mutex); + Steam_User_Stats::InternalSetResult result{}; + + if (!pchName) return result; + std::string stat_name(common_helpers::ascii_to_lowercase(pchName)); + + const auto &stats_config = settings->getStats(); + auto stats_data = stats_config.find(stat_name); + if (stats_config.end() == stats_data) return result; + if (stats_data->second.type != GameServerStats_Messages::StatInfo::STAT_TYPE_INT) return result; + + result.internal_name = stat_name; + result.current_val = nData; + + auto cached_stat = stats_cache_int.find(stat_name); + if (cached_stat != stats_cache_int.end()) { + if (cached_stat->second == nData) { + result.success = true; + return result; + } + } + + auto stat_trigger = achievement_stat_trigger.find(stat_name); + if (stat_trigger != achievement_stat_trigger.end()) { + for (auto &t : stat_trigger->second) { + if (t.check_triggered(nData)) { + set_achievement_internal(t.name.c_str()); + } + } + } + + if (local_storage->store_data(Local_Storage::stats_storage_folder, stat_name, (char* )&nData, sizeof(nData)) == sizeof(nData)) { + stats_cache_int[stat_name] = nData; + result.success = true; + result.notify_server = !settings->disable_sharing_stats_with_gameserver; + return result; + } + + return result; +} + +Steam_User_Stats::InternalSetResult> Steam_User_Stats::set_stat_internal( const char *pchName, float fData ) +{ + PRINT_DEBUG(" '%s' = %f", pchName, fData); + std::lock_guard lock(global_mutex); + Steam_User_Stats::InternalSetResult> result{}; + + if (!pchName) return result; + std::string stat_name(common_helpers::ascii_to_lowercase(pchName)); + + const auto &stats_config = settings->getStats(); + auto stats_data = stats_config.find(stat_name); + if (stats_config.end() == stats_data) return result; + if (stats_data->second.type == GameServerStats_Messages::StatInfo::STAT_TYPE_INT) return result; + + result.internal_name = stat_name; + result.current_val.first = stats_data->second.type; + result.current_val.second = fData; + + auto cached_stat = stats_cache_float.find(stat_name); + if (cached_stat != stats_cache_float.end()) { + if (cached_stat->second == fData) { + result.success = true; + return result; + } + } + + auto stat_trigger = achievement_stat_trigger.find(stat_name); + if (stat_trigger != achievement_stat_trigger.end()) { + for (auto &t : stat_trigger->second) { + if (t.check_triggered(fData)) { + set_achievement_internal(t.name.c_str()); + } + } + } + + if (local_storage->store_data(Local_Storage::stats_storage_folder, stat_name, (char* )&fData, sizeof(fData)) == sizeof(fData)) { + stats_cache_float[stat_name] = fData; + result.success = true; + result.notify_server = !settings->disable_sharing_stats_with_gameserver; + return result; + } + + return result; +} + +Steam_User_Stats::InternalSetResult> Steam_User_Stats::update_avg_rate_stat_internal( const char *pchName, float flCountThisSession, double dSessionLength ) +{ + PRINT_DEBUG("%s", pchName); + std::lock_guard lock(global_mutex); + Steam_User_Stats::InternalSetResult> result{}; + + if (!pchName) return result; + std::string stat_name(common_helpers::ascii_to_lowercase(pchName)); + + const auto &stats_config = settings->getStats(); + auto stats_data = stats_config.find(stat_name); + if (stats_config.end() == stats_data) return result; + if (stats_data->second.type == GameServerStats_Messages::StatInfo::STAT_TYPE_INT) return result; + + result.internal_name = stat_name; + + char data[sizeof(float) + sizeof(float) + sizeof(double)]; + int read_data = local_storage->get_data(Local_Storage::stats_storage_folder, stat_name, (char* )data, sizeof(*data)); + float oldcount = 0; + double oldsessionlength = 0; + if (read_data == sizeof(data)) { + memcpy(&oldcount, data + sizeof(float), sizeof(oldcount)); + memcpy(&oldsessionlength, data + sizeof(float) + sizeof(double), sizeof(oldsessionlength)); + } + + oldcount += flCountThisSession; + oldsessionlength += dSessionLength; + + float average = oldcount / oldsessionlength; + memcpy(data, &average, sizeof(average)); + memcpy(data + sizeof(float), &oldcount, sizeof(oldcount)); + memcpy(data + sizeof(float) * 2, &oldsessionlength, sizeof(oldsessionlength)); + + result.current_val.first = stats_data->second.type; + result.current_val.second = average; + + if (local_storage->store_data(Local_Storage::stats_storage_folder, stat_name, data, sizeof(data)) == sizeof(data)) { + stats_cache_float[stat_name] = average; + result.success = true; + result.notify_server = !settings->disable_sharing_stats_with_gameserver; + return result; + } + + return result; +} + +Steam_User_Stats::InternalSetResult Steam_User_Stats::set_achievement_internal( const char *pchName ) +{ + PRINT_DEBUG("'%s'", pchName); + std::lock_guard lock(global_mutex); + Steam_User_Stats::InternalSetResult result{}; + + if (!pchName) return result; + + std::string org_name(pchName); + + if (settings->achievement_bypass) { + auto &trig = store_stats_trigger[common_helpers::to_lower(org_name)]; + trig.m_bGroupAchievement = false; + trig.m_nCurProgress = 100; + trig.m_nGameID = settings->get_local_game_id().ToUint64(); + trig.m_nMaxProgress = 100; + memset(trig.m_rgchAchievementName, 0, sizeof(trig.m_rgchAchievementName)); + org_name.copy(trig.m_rgchAchievementName, sizeof(trig.m_rgchAchievementName) - 1); + + result.success = true; + return result; + } + + nlohmann::detail::iter_impl it = defined_achievements.end(); + try { + it = defined_achievements_find(org_name); + } catch(...) { } + if (defined_achievements.end() == it) return result; + + result.current_val = true; + result.internal_name = org_name; + result.success = true; + + try { + std::string internal_name = it->value("name", std::string()); + + result.internal_name = internal_name; + + auto ach = user_achievements.find(internal_name); + if (user_achievements.end() == ach || ach->value("earned", false) == false) { + user_achievements[internal_name]["earned"] = true; + user_achievements[internal_name]["earned_time"] = + std::chrono::duration_cast>(std::chrono::system_clock::now().time_since_epoch()).count(); + + save_achievements(); + + result.notify_server = !settings->disable_sharing_stats_with_gameserver; + + if (!settings->disable_overlay) overlay->AddAchievementNotification(it.value()); + + } + } catch (...) {} + + auto &trig = store_stats_trigger[common_helpers::to_lower(org_name)]; + trig.m_bGroupAchievement = false; + trig.m_nCurProgress = 100; + trig.m_nGameID = settings->get_local_game_id().ToUint64(); + trig.m_nMaxProgress = 100; + memset(trig.m_rgchAchievementName, 0, sizeof(trig.m_rgchAchievementName)); + org_name.copy(trig.m_rgchAchievementName, sizeof(trig.m_rgchAchievementName) - 1); + + return result; +} + +Steam_User_Stats::InternalSetResult Steam_User_Stats::clear_achievement_internal( const char *pchName ) +{ + PRINT_DEBUG("'%s'", pchName); + std::lock_guard lock(global_mutex); + Steam_User_Stats::InternalSetResult result{}; + + if (!pchName) return result; + + std::string org_name(pchName); + + nlohmann::detail::iter_impl it = defined_achievements.end(); + try { + it = defined_achievements_find(org_name); + } catch(...) { } + if (defined_achievements.end() == it) return result; + + result.current_val = false; + result.internal_name = org_name; + result.success = true; + + try { + std::string internal_name = it->value("name", std::string()); + + result.internal_name = internal_name; + + auto ach = user_achievements.find(internal_name); + // assume "earned" is true in case the json obj exists, but the key is absent + // assume "earned_time" is UINT32_MAX in case the json obj exists, but the key is absent + if (user_achievements.end() == ach || + ach->value("earned", true) == true || + ach->value("earned_time", static_cast(UINT32_MAX)) == UINT32_MAX) { + + user_achievements[internal_name]["earned"] = false; + user_achievements[internal_name]["earned_time"] = static_cast(0); + save_achievements(); + + result.notify_server = !settings->disable_sharing_stats_with_gameserver; + + } + } catch (...) {} + + store_stats_trigger.erase(common_helpers::to_lower(org_name)); + + return result; +} + + +void Steam_User_Stats::steam_user_stats_network_low_level(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + auto inst = (Steam_User_Stats *)object; + inst->network_callback_low_level(msg); +} + +void Steam_User_Stats::steam_user_stats_network_stats(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + auto inst = (Steam_User_Stats *)object; + inst->network_callback_stats(msg); +} + +void Steam_User_Stats::steam_user_stats_network_leaderboards(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + auto inst = (Steam_User_Stats *)object; + inst->network_callback_leaderboards(msg); +} + +void Steam_User_Stats::steam_user_stats_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + auto inst = (Steam_User_Stats *)object; + inst->steam_run_callback(); +} + + +Steam_User_Stats::Steam_User_Stats(Settings *settings, class Networking *network, Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, Steam_Overlay* overlay): + settings(settings), + network(network), + local_storage(local_storage), + callback_results(callback_results), + callbacks(callbacks), + defined_achievements(nlohmann::json::object()), + user_achievements(nlohmann::json::object()), + run_every_runcb(run_every_runcb), + overlay(overlay) +{ + load_achievements_db(); // achievements db + load_achievements(); // achievements per user + + auto x = defined_achievements.begin(); + while (x != defined_achievements.end()) { + if (!x->contains("name")) { + x = defined_achievements.erase(x); + } else { + ++x; + } + } + + for (auto & it : defined_achievements) { + try { + std::string name = static_cast(it["name"]); + sorted_achievement_names.push_back(name); + if (user_achievements.find(name) == user_achievements.end()) { + user_achievements[name]["earned"] = false; + user_achievements[name]["earned_time"] = static_cast(0); + } + + achievement_trigger trig; + trig.name = name; + trig.value_operation = static_cast(it["progress"]["value"]["operation"]); + std::string stat_name = common_helpers::ascii_to_lowercase(static_cast(it["progress"]["value"]["operand1"])); + trig.min_value = static_cast(it["progress"]["min_val"]); + trig.max_value = static_cast(it["progress"]["max_val"]); + achievement_stat_trigger[stat_name].push_back(trig); + } catch (...) {} + + try { + it["hidden"] = std::to_string(it["hidden"].get()); + } catch (...) {} + + it["displayName"] = get_value_for_language(it, "displayName", settings->get_language()); + it["description"] = get_value_for_language(it, "description", settings->get_language()); + } + + //TODO: not sure if the sort is actually case insensitive, ach names seem to be treated by steam as case insensitive so I assume they are. + //need to find a game with achievements of different case names to confirm + std::sort(sorted_achievement_names.begin(), sorted_achievement_names.end(), [](const std::string lhs, const std::string rhs){ + const auto result = std::mismatch(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend(), [](const unsigned char lhs, const unsigned char rhs){return std::tolower(lhs) == std::tolower(rhs);}); + return result.second != rhs.cend() && (result.first == lhs.cend() || std::tolower(*result.first) < std::tolower(*result.second));} + ); + + if (!settings->disable_sharing_stats_with_gameserver) { + this->network->setCallback(CALLBACK_ID_GAMESERVER_STATS, settings->get_local_steam_id(), &Steam_User_Stats::steam_user_stats_network_stats, this); + } + if (settings->share_leaderboards_over_network) { + this->network->setCallback(CALLBACK_ID_LEADERBOARDS_STATS, settings->get_local_steam_id(), &Steam_User_Stats::steam_user_stats_network_leaderboards, this); + } + this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_User_Stats::steam_user_stats_network_low_level, this); + this->run_every_runcb->add(&Steam_User_Stats::steam_user_stats_run_every_runcb, this); +} + +Steam_User_Stats::~Steam_User_Stats() +{ + if (!settings->disable_sharing_stats_with_gameserver) { + this->network->rmCallback(CALLBACK_ID_GAMESERVER_STATS, settings->get_local_steam_id(), &Steam_User_Stats::steam_user_stats_network_stats, this); + } + if (settings->share_leaderboards_over_network) { + this->network->rmCallback(CALLBACK_ID_LEADERBOARDS_STATS, settings->get_local_steam_id(), &Steam_User_Stats::steam_user_stats_network_leaderboards, this); + } + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_User_Stats::steam_user_stats_network_low_level, this); + this->run_every_runcb->remove(&Steam_User_Stats::steam_user_stats_run_every_runcb, this); +} + +// Ask the server to send down this user's data and achievements for this game +STEAM_CALL_BACK( UserStatsReceived_t ) +bool Steam_User_Stats::RequestCurrentStats() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + UserStatsReceived_t data{}; + data.m_nGameID = settings->get_local_game_id().ToUint64(); + data.m_eResult = k_EResultOK; + data.m_steamIDUser = settings->get_local_steam_id(); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return true; +} + + +// Data accessors +bool Steam_User_Stats::GetStat( const char *pchName, int32 *pData ) +{ + PRINT_DEBUG(" '%s' %p", pchName, pData); + std::lock_guard lock(global_mutex); + + if (!pchName) return false; + std::string stat_name = common_helpers::ascii_to_lowercase(pchName); + + const auto &stats_config = settings->getStats(); + auto stats_data = stats_config.find(stat_name); + if (stats_config.end() == stats_data) return false; + if (stats_data->second.type != GameServerStats_Messages::StatInfo::STAT_TYPE_INT) return false; + + auto cached_stat = stats_cache_int.find(stat_name); + if (cached_stat != stats_cache_int.end()) { + if (pData) *pData = cached_stat->second; + return true; + } + + int32 output = 0; + int read_data = local_storage->get_data(Local_Storage::stats_storage_folder, stat_name, (char* )&output, sizeof(output)); + if (read_data == sizeof(int32)) { + stats_cache_int[stat_name] = output; + if (pData) *pData = output; + return true; + } + + stats_cache_int[stat_name] = stats_data->second.default_value_int; + if (pData) *pData = stats_data->second.default_value_int; + return true; +} + +bool Steam_User_Stats::GetStat( const char *pchName, float *pData ) +{ + PRINT_DEBUG(" '%s' %p", pchName, pData); + std::lock_guard lock(global_mutex); + + if (!pchName) return false; + std::string stat_name = common_helpers::ascii_to_lowercase(pchName); + + const auto &stats_config = settings->getStats(); + auto stats_data = stats_config.find(stat_name); + if (stats_config.end() == stats_data) return false; + if (stats_data->second.type == GameServerStats_Messages::StatInfo::STAT_TYPE_INT) return false; + + auto cached_stat = stats_cache_float.find(stat_name); + if (cached_stat != stats_cache_float.end()) { + if (pData) *pData = cached_stat->second; + return true; + } + + float output = 0.0; + int read_data = local_storage->get_data(Local_Storage::stats_storage_folder, stat_name, (char* )&output, sizeof(output)); + if (read_data == sizeof(float)) { + stats_cache_float[stat_name] = output; + if (pData) *pData = output; + return true; + } + + stats_cache_float[stat_name] = stats_data->second.default_value_float; + if (pData) *pData = stats_data->second.default_value_float; + return true; +} + + +// Set / update data +bool Steam_User_Stats::SetStat( const char *pchName, int32 nData ) +{ + PRINT_DEBUG(" '%s' = %i", pchName, nData); + std::lock_guard lock(global_mutex); + + auto ret = set_stat_internal(pchName, nData ); + if (ret.success && ret.notify_server ) { + auto &new_stat = (*pending_server_updates.mutable_user_stats())[ret.internal_name]; + new_stat.set_stat_type(GameServerStats_Messages::StatInfo::STAT_TYPE_INT); + new_stat.set_value_int(ret.current_val); + + if (settings->immediate_gameserver_stats) send_updated_stats(); + } + + return ret.success; +} + +bool Steam_User_Stats::SetStat( const char *pchName, float fData ) +{ + PRINT_DEBUG(" '%s' = %f", pchName, fData); + std::lock_guard lock(global_mutex); + + auto ret = set_stat_internal(pchName, fData); + if (ret.success && ret.notify_server) { + auto &new_stat = (*pending_server_updates.mutable_user_stats())[ret.internal_name]; + new_stat.set_stat_type(ret.current_val.first); + new_stat.set_value_float(ret.current_val.second); + + if (settings->immediate_gameserver_stats) send_updated_stats(); + } + + return ret.success; +} + +bool Steam_User_Stats::UpdateAvgRateStat( const char *pchName, float flCountThisSession, double dSessionLength ) +{ + PRINT_DEBUG("'%s'", pchName); + std::lock_guard lock(global_mutex); + + auto ret = update_avg_rate_stat_internal(pchName, flCountThisSession, dSessionLength); + if (ret.success && ret.notify_server) { + auto &new_stat = (*pending_server_updates.mutable_user_stats())[ret.internal_name]; + new_stat.set_stat_type(ret.current_val.first); + new_stat.set_value_float(ret.current_val.second); + + if (settings->immediate_gameserver_stats) send_updated_stats(); + } + + return ret.success; +} + + +// Achievement flag accessors +bool Steam_User_Stats::GetAchievement( const char *pchName, bool *pbAchieved ) +{ + PRINT_DEBUG("'%s'", pchName); + std::lock_guard lock(global_mutex); + + if (!pchName) return false; + + nlohmann::detail::iter_impl it = defined_achievements.end(); + try { + it = defined_achievements_find(pchName); + } catch(...) { } + if (defined_achievements.end() == it) return false; + + // according to docs, the function returns true if the achievement was found, + // regardless achieved or not + if (!pbAchieved) return true; + + *pbAchieved = false; + try { + std::string pch_name = it->value("name", std::string()); + auto ach = user_achievements.find(pch_name); + if (user_achievements.end() != ach) { + *pbAchieved = ach->value("earned", false); + } + } catch (...) { } + + return true; +} + +bool Steam_User_Stats::SetAchievement( const char *pchName ) +{ + PRINT_DEBUG("'%s'", pchName); + std::lock_guard lock(global_mutex); + + auto ret = set_achievement_internal(pchName); + if (ret.success && ret.notify_server) { + auto &new_ach = (*pending_server_updates.mutable_user_achievements())[ret.internal_name]; + new_ach.set_achieved(ret.current_val); + + if (settings->immediate_gameserver_stats) send_updated_stats(); + } + + return ret.success; +} + +bool Steam_User_Stats::ClearAchievement( const char *pchName ) +{ + PRINT_DEBUG("'%s'", pchName); + std::lock_guard lock(global_mutex); + + auto ret = clear_achievement_internal(pchName); + if (ret.success && ret.notify_server) { + auto &new_ach = (*pending_server_updates.mutable_user_achievements())[ret.internal_name]; + new_ach.set_achieved(ret.current_val); + + if (settings->immediate_gameserver_stats) send_updated_stats(); + } + + return ret.success; +} + + +// Get the achievement status, and the time it was unlocked if unlocked. +// If the return value is true, but the unlock time is zero, that means it was unlocked before Steam +// began tracking achievement unlock times (December 2009). Time is seconds since January 1, 1970. +bool Steam_User_Stats::GetAchievementAndUnlockTime( const char *pchName, bool *pbAchieved, uint32 *punUnlockTime ) +{ + PRINT_DEBUG("'%s'", pchName); + std::lock_guard lock(global_mutex); + + if (!pchName) return false; + + nlohmann::detail::iter_impl it = defined_achievements.end(); + try { + it = defined_achievements_find(pchName); + } catch(...) { } + if (defined_achievements.end() == it) return false; + + if (pbAchieved) *pbAchieved = false; + if (punUnlockTime) *punUnlockTime = 0; + + try { + std::string pch_name = it->value("name", std::string()); + auto ach = user_achievements.find(pch_name); + if (user_achievements.end() != ach) { + if (pbAchieved) *pbAchieved = ach->value("earned", false); + if (punUnlockTime) *punUnlockTime = ach->value("earned_time", static_cast(0)); + } + } catch (...) {} + + return true; +} + + +// Store the current data on the server, will get a callback when set +// And one callback for every new achievement +// +// If the callback has a result of k_EResultInvalidParam, one or more stats +// uploaded has been rejected, either because they broke constraints +// or were out of date. In this case the server sends back updated values. +// The stats should be re-iterated to keep in sync. +bool Steam_User_Stats::StoreStats() +{ + // no need to exchange data with gameserver, we already do that in run_callback() and on each stat/ach update (immediate mode) + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + UserStatsStored_t data{}; + data.m_eResult = k_EResultOK; + data.m_nGameID = settings->get_local_game_id().ToUint64(); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.01); + + for (auto &kv : store_stats_trigger) { + callbacks->addCBResult(kv.second.k_iCallback, &kv.second, sizeof(kv.second)); + } + store_stats_trigger.clear(); + + return true; +} + + +// Achievement / GroupAchievement metadata + +// Gets the icon of the achievement, which is a handle to be used in ISteamUtils::GetImageRGBA(), or 0 if none set. +// A return value of 0 may indicate we are still fetching data, and you can wait for the UserAchievementIconFetched_t callback +// which will notify you when the bits are ready. If the callback still returns zero, then there is no image set for the +// specified achievement. +int Steam_User_Stats::GetAchievementIcon( const char *pchName ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + if (!pchName) return 0; + + // callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return 0; +} + +std::string Steam_User_Stats::get_achievement_icon_name( const char *pchName, bool pbAchieved ) +{ + std::lock_guard lock(global_mutex); + if (!pchName) return ""; + + nlohmann::detail::iter_impl it = defined_achievements.end(); + try { + it = defined_achievements_find(pchName); + } catch(...) { } + if (defined_achievements.end() == it) return ""; + + try { + if (pbAchieved) return it.value()["icon"].get(); + + std::string locked_icon = it.value().value("icon_gray", std::string()); + if (locked_icon.size()) return locked_icon; + else return it.value().value("icongray", std::string()); // old format + } catch (...) {} + + return ""; +} + + +// Get general attributes for an achievement. Accepts the following keys: +// - "name" and "desc" for retrieving the localized achievement name and description (returned in UTF8) +// - "hidden" for retrieving if an achievement is hidden (returns "0" when not hidden, "1" when hidden) +const char * Steam_User_Stats::GetAchievementDisplayAttribute( const char *pchName, const char *pchKey ) +{ + PRINT_DEBUG("[%s] [%s]", pchName, pchKey); + std::lock_guard lock(global_mutex); + + if (!pchName || !pchKey || !pchKey[0]) return ""; + + nlohmann::detail::iter_impl it = defined_achievements.end(); + try { + it = defined_achievements_find(pchName); + } catch(...) { } + if (defined_achievements.end() == it) return ""; + + if (strncmp(pchKey, "name", sizeof("name")) == 0) { + try { + return it.value()["displayName"].get_ptr()->c_str(); + } catch (...) {} + } else if (strncmp(pchKey, "desc", sizeof("desc")) == 0) { + try { + return it.value()["description"].get_ptr()->c_str(); + } catch (...) {} + } else if (strncmp(pchKey, "hidden", sizeof("hidden")) == 0) { + try { + return it.value()["hidden"].get_ptr()->c_str(); + } catch (...) {} + } + + return ""; +} + + +// Achievement progress - triggers an AchievementProgress callback, that is all. +// Calling this w/ N out of N progress will NOT set the achievement, the game must still do that. +bool Steam_User_Stats::IndicateAchievementProgress( const char *pchName, uint32 nCurProgress, uint32 nMaxProgress ) +{ + PRINT_DEBUG("%s", pchName); + std::lock_guard lock(global_mutex); + + if (!pchName) return false; + if (nCurProgress >= nMaxProgress) return false; + std::string ach_name(pchName); + + // find in achievements.json + nlohmann::detail::iter_impl it = defined_achievements.end(); + try { + it = defined_achievements_find(ach_name); + } catch(...) { } + if (defined_achievements.end() == it) return false; + + // get actual name from achievements.json + std::string actual_ach_name{}; + try { + actual_ach_name = it->value("name", std::string()); + } catch (...) { } + if (actual_ach_name.empty()) { // fallback + actual_ach_name = ach_name; + } + + // check if already achieved + bool achieved = false; + try { + auto ach = user_achievements.find(actual_ach_name); + if (ach != user_achievements.end()) { + achieved = ach->value("earned", false); + } + } catch (...) { } + if (achieved) return false; + + // save new progress + try { + user_achievements[actual_ach_name]["progress"] = nCurProgress; + user_achievements[actual_ach_name]["max_progress"] = nMaxProgress; + save_achievements(); + } catch (...) {} + + { + UserStatsStored_t data{}; + data.m_eResult = EResult::k_EResultOK; + data.m_nGameID = settings->get_local_game_id().ToUint64(); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + + UserAchievementStored_t data{}; + data.m_nGameID = settings->get_local_game_id().ToUint64(); + data.m_bGroupAchievement = false; + data.m_nCurProgress = nCurProgress; + data.m_nMaxProgress = nMaxProgress; + ach_name.copy(data.m_rgchAchievementName, sizeof(data.m_rgchAchievementName) - 1); + + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + // callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); // TODO was this correct? + return true; +} + + +// Used for iterating achievements. In general games should not need these functions because they should have a +// list of existing achievements compiled into them +uint32 Steam_User_Stats::GetNumAchievements() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + return (uint32)defined_achievements.size(); +} + +// Get achievement name iAchievement in [0,GetNumAchievements) +const char * Steam_User_Stats::GetAchievementName( uint32 iAchievement ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (iAchievement >= sorted_achievement_names.size()) { + return ""; + } + + return sorted_achievement_names[iAchievement].c_str(); +} + + +// Friends stats & achievements + +// downloads stats for the user +// returns a UserStatsReceived_t received when completed +// if the other user has no stats, UserStatsReceived_t.m_eResult will be set to k_EResultFail +// these stats won't be auto-updated; you'll need to call RequestUserStats() again to refresh any data +STEAM_CALL_RESULT( UserStatsReceived_t ) +SteamAPICall_t Steam_User_Stats::RequestUserStats( CSteamID steamIDUser ) +{ + PRINT_DEBUG("%llu", steamIDUser.ConvertToUint64()); + std::lock_guard lock(global_mutex); + + // Enable this to allow hot reload achievements status + //if (steamIDUser == settings->get_local_steam_id()) { + // load_achievements(); + //} + + UserStatsReceived_t data{}; + data.m_nGameID = settings->get_local_game_id().ToUint64(); + data.m_eResult = k_EResultOK; + data.m_steamIDUser = steamIDUser; + // appid 756800 expects both: a callback (global event occurring in the Steam environment), + // and a callresult (the specific result of this function call) + // otherwise it will lock-up and hang at startup + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; +} + + +// requests stat information for a user, usable after a successful call to RequestUserStats() +bool Steam_User_Stats::GetUserStat( CSteamID steamIDUser, const char *pchName, int32 *pData ) +{ + PRINT_DEBUG("%s %llu", pchName, steamIDUser.ConvertToUint64()); + std::lock_guard lock(global_mutex); + + if (!pchName) return false; + + if (steamIDUser == settings->get_local_steam_id()) { + GetStat(pchName, pData); + } else { + *pData = 0; + } + + return true; +} + +bool Steam_User_Stats::GetUserStat( CSteamID steamIDUser, const char *pchName, float *pData ) +{ + PRINT_DEBUG("%s %llu", pchName, steamIDUser.ConvertToUint64()); + std::lock_guard lock(global_mutex); + + if (!pchName) return false; + + if (steamIDUser == settings->get_local_steam_id()) { + GetStat(pchName, pData); + } else { + *pData = 0; + } + + return true; +} + +bool Steam_User_Stats::GetUserAchievement( CSteamID steamIDUser, const char *pchName, bool *pbAchieved ) +{ + PRINT_DEBUG("%s", pchName); + std::lock_guard lock(global_mutex); + + if (!pchName) return false; + + if (steamIDUser == settings->get_local_steam_id()) { + return GetAchievement(pchName, pbAchieved); + } + + return false; +} + +// See notes for GetAchievementAndUnlockTime above +bool Steam_User_Stats::GetUserAchievementAndUnlockTime( CSteamID steamIDUser, const char *pchName, bool *pbAchieved, uint32 *punUnlockTime ) +{ + PRINT_DEBUG("%s", pchName); + std::lock_guard lock(global_mutex); + + if (!pchName) return false; + + if (steamIDUser == settings->get_local_steam_id()) { + return GetAchievementAndUnlockTime(pchName, pbAchieved, punUnlockTime); + } + return false; +} + + +// Reset stats +bool Steam_User_Stats::ResetAllStats( bool bAchievementsToo ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + clear_stats_internal(); + if (!settings->disable_sharing_stats_with_gameserver) { + for (const auto &stat : settings->getStats()) { + std::string stat_name(common_helpers::ascii_to_lowercase(stat.first)); + + auto &new_stat = (*pending_server_updates.mutable_user_stats())[stat_name]; + new_stat.set_stat_type(stat.second.type); + + switch (stat.second.type) + { + case GameServerStats_Messages::StatInfo::STAT_TYPE_INT: + new_stat.set_value_int(stat.second.default_value_int); + break; + + case GameServerStats_Messages::StatInfo::STAT_TYPE_AVGRATE: + case GameServerStats_Messages::StatInfo::STAT_TYPE_FLOAT: + new_stat.set_value_float(stat.second.default_value_float); + break; + + default: PRINT_DEBUG("unhandled type %i", (int)stat.second.type); break; + } + } + } + + if (bAchievementsToo) { + bool needs_disk_write = false; + for (auto &item : user_achievements) { + // assume "earned" is true in case the json obj exists, but the key is absent + if (item.value("earned", true) == true) needs_disk_write = true; + + item["earned"] = false; + item["earned_time"] = 0; + } + if (needs_disk_write) save_achievements(); + + if (!settings->disable_sharing_stats_with_gameserver) { + for (const auto &item : user_achievements.items()) { + auto &new_ach = (*pending_server_updates.mutable_user_achievements())[item.key()]; + new_ach.set_achieved(false); + } + } + } + + if (!settings->disable_sharing_stats_with_gameserver && settings->immediate_gameserver_stats) send_updated_stats(); + + return true; +} + + +// Leaderboard functions + +// asks the Steam back-end for a leaderboard by name, and will create it if it's not yet +// This call is asynchronous, with the result returned in LeaderboardFindResult_t +STEAM_CALL_RESULT(LeaderboardFindResult_t) +SteamAPICall_t Steam_User_Stats::FindOrCreateLeaderboard( const char *pchLeaderboardName, ELeaderboardSortMethod eLeaderboardSortMethod, ELeaderboardDisplayType eLeaderboardDisplayType ) +{ + PRINT_DEBUG("'%s'", pchLeaderboardName); + std::lock_guard lock(global_mutex); + if (!pchLeaderboardName) { + LeaderboardFindResult_t data{}; + data.m_hSteamLeaderboard = 0; + data.m_bLeaderboardFound = 0; + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return ret; + } + + unsigned int board_handle = cache_leaderboard_ifneeded(pchLeaderboardName, eLeaderboardSortMethod, eLeaderboardDisplayType); + send_my_leaderboard_score(cached_leaderboards[board_handle - 1], nullptr, true); + + LeaderboardFindResult_t data{}; + data.m_hSteamLeaderboard = board_handle; + data.m_bLeaderboardFound = 1; + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); // TODO is the timing ok? + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; +} + + +// as above, but won't create the leaderboard if it's not found +// This call is asynchronous, with the result returned in LeaderboardFindResult_t +STEAM_CALL_RESULT( LeaderboardFindResult_t ) +SteamAPICall_t Steam_User_Stats::FindLeaderboard( const char *pchLeaderboardName ) +{ + PRINT_DEBUG("'%s'", pchLeaderboardName); + std::lock_guard lock(global_mutex); + if (!pchLeaderboardName) { + LeaderboardFindResult_t data{}; + data.m_hSteamLeaderboard = 0; + data.m_bLeaderboardFound = 0; + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return ret; + } + + std::string name_lower(common_helpers::ascii_to_lowercase(pchLeaderboardName)); + const auto &settings_Leaderboards = settings->getLeaderboards(); + auto it = settings_Leaderboards.begin(); + for (; settings_Leaderboards.end() != it; ++it) { + if (common_helpers::str_cmp_insensitive(it->first, name_lower)) break; + } + if (settings_Leaderboards.end() != it) { + auto &config = it->second; + return FindOrCreateLeaderboard(pchLeaderboardName, config.sort_method, config.display_type); + } else if (!settings->disable_leaderboards_create_unknown) { + return FindOrCreateLeaderboard(pchLeaderboardName, k_ELeaderboardSortMethodDescending, k_ELeaderboardDisplayTypeNumeric); + } else { + LeaderboardFindResult_t data{}; + data.m_hSteamLeaderboard = find_cached_leaderboard(name_lower); + data.m_bLeaderboardFound = !!data.m_hSteamLeaderboard; + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return ret; + } +} + + +// returns the name of a leaderboard +const char * Steam_User_Stats::GetLeaderboardName( SteamLeaderboard_t hSteamLeaderboard ) +{ + PRINT_DEBUG("%llu", hSteamLeaderboard); + std::lock_guard lock(global_mutex); + if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return ""; + + return cached_leaderboards[hSteamLeaderboard - 1].name.c_str(); +} + + +// returns the total number of entries in a leaderboard, as of the last request +int Steam_User_Stats::GetLeaderboardEntryCount( SteamLeaderboard_t hSteamLeaderboard ) +{ + PRINT_DEBUG("%llu", hSteamLeaderboard); + std::lock_guard lock(global_mutex); + if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return 0; + + return (int)cached_leaderboards[hSteamLeaderboard - 1].entries.size(); +} + + +// returns the sort method of the leaderboard +ELeaderboardSortMethod Steam_User_Stats::GetLeaderboardSortMethod( SteamLeaderboard_t hSteamLeaderboard ) +{ + PRINT_DEBUG("%llu", hSteamLeaderboard); + std::lock_guard lock(global_mutex); + if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return k_ELeaderboardSortMethodNone; + + return cached_leaderboards[hSteamLeaderboard - 1].sort_method; +} + + +// returns the display type of the leaderboard +ELeaderboardDisplayType Steam_User_Stats::GetLeaderboardDisplayType( SteamLeaderboard_t hSteamLeaderboard ) +{ + PRINT_DEBUG("%llu", hSteamLeaderboard); + std::lock_guard lock(global_mutex); + if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return k_ELeaderboardDisplayTypeNone; + + return cached_leaderboards[hSteamLeaderboard - 1].display_type; +} + + +// Asks the Steam back-end for a set of rows in the leaderboard. +// This call is asynchronous, with the result returned in LeaderboardScoresDownloaded_t +// LeaderboardScoresDownloaded_t will contain a handle to pull the results from GetDownloadedLeaderboardEntries() (below) +// You can ask for more entries than exist, and it will return as many as do exist. +// k_ELeaderboardDataRequestGlobal requests rows in the leaderboard from the full table, with nRangeStart & nRangeEnd in the range [1, TotalEntries] +// k_ELeaderboardDataRequestGlobalAroundUser requests rows around the current user, nRangeStart being negate +// e.g. DownloadLeaderboardEntries( hLeaderboard, k_ELeaderboardDataRequestGlobalAroundUser, -3, 3 ) will return 7 rows, 3 before the user, 3 after +// k_ELeaderboardDataRequestFriends requests all the rows for friends of the current user +STEAM_CALL_RESULT( LeaderboardScoresDownloaded_t ) +SteamAPICall_t Steam_User_Stats::DownloadLeaderboardEntries( SteamLeaderboard_t hSteamLeaderboard, ELeaderboardDataRequest eLeaderboardDataRequest, int nRangeStart, int nRangeEnd ) +{ + PRINT_DEBUG("%llu %i [%i, %i]", hSteamLeaderboard, eLeaderboardDataRequest, nRangeStart, nRangeEnd); + std::lock_guard lock(global_mutex); + if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return k_uAPICallInvalid; //might return callresult even if hSteamLeaderboard is invalid + + int entries_count = (int)cached_leaderboards[hSteamLeaderboard - 1].entries.size(); + // https://partner.steamgames.com/doc/api/ISteamUserStats#ELeaderboardDataRequest + if (eLeaderboardDataRequest != k_ELeaderboardDataRequestFriends) { + int required_count = nRangeEnd - nRangeStart + 1; + if (required_count < 0) required_count = 0; + + if (required_count < entries_count) entries_count = required_count; + } + LeaderboardScoresDownloaded_t data{}; + data.m_hSteamLeaderboard = hSteamLeaderboard; + data.m_hSteamLeaderboardEntries = hSteamLeaderboard; + data.m_cEntryCount = entries_count; + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); // TODO is this timing ok? + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; +} + +// as above, but downloads leaderboard entries for an arbitrary set of users - ELeaderboardDataRequest is k_ELeaderboardDataRequestUsers +// if a user doesn't have a leaderboard entry, they won't be included in the result +// a max of 100 users can be downloaded at a time, with only one outstanding call at a time +STEAM_METHOD_DESC(Downloads leaderboard entries for an arbitrary set of users - ELeaderboardDataRequest is k_ELeaderboardDataRequestUsers) +STEAM_CALL_RESULT( LeaderboardScoresDownloaded_t ) +SteamAPICall_t Steam_User_Stats::DownloadLeaderboardEntriesForUsers( SteamLeaderboard_t hSteamLeaderboard, + STEAM_ARRAY_COUNT_D(cUsers, Array of users to retrieve) CSteamID *prgUsers, int cUsers ) +{ + PRINT_DEBUG("%i %llu", cUsers, cUsers > 0 ? prgUsers[0].ConvertToUint64() : 0); + std::lock_guard lock(global_mutex); + if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return k_uAPICallInvalid; //might return callresult even if hSteamLeaderboard is invalid + + auto &board = cached_leaderboards[hSteamLeaderboard - 1]; + bool ok = true; + int total_count = 0; + if (prgUsers && cUsers > 0) { + for (int i = 0; i < cUsers; ++i) { + const auto &user_steamid = prgUsers[i]; + if (!user_steamid.IsValid()) { + ok = false; + PRINT_DEBUG("bad userid %llu", user_steamid.ConvertToUint64()); + break; + } + if (board.find_recent_entry(user_steamid)) ++total_count; + + request_user_leaderboard_entry(board, user_steamid); + } + } + + PRINT_DEBUG("total count %i", total_count); + // https://partner.steamgames.com/doc/api/ISteamUserStats#DownloadLeaderboardEntriesForUsers + if (!ok || total_count > 100) return k_uAPICallInvalid; + + LeaderboardScoresDownloaded_t data{}; + data.m_hSteamLeaderboard = hSteamLeaderboard; + data.m_hSteamLeaderboardEntries = hSteamLeaderboard; + data.m_cEntryCount = total_count; + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); // TODO is this timing ok? + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; +} + + +// Returns data about a single leaderboard entry +// use a for loop from 0 to LeaderboardScoresDownloaded_t::m_cEntryCount to get all the downloaded entries +// e.g. +// void OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *pLeaderboardScoresDownloaded ) +// { +// for ( int index = 0; index < pLeaderboardScoresDownloaded->m_cEntryCount; index++ ) +// { +// LeaderboardEntry_t leaderboardEntry; +// int32 details[3]; // we know this is how many we've stored previously +// GetDownloadedLeaderboardEntry( pLeaderboardScoresDownloaded->m_hSteamLeaderboardEntries, index, &leaderboardEntry, details, 3 ); +// assert( leaderboardEntry.m_cDetails == 3 ); +// ... +// } +// once you've accessed all the entries, the data will be free'd, and the SteamLeaderboardEntries_t handle will become invalid +bool Steam_User_Stats::GetDownloadedLeaderboardEntry( SteamLeaderboardEntries_t hSteamLeaderboardEntries, int index, LeaderboardEntry_t *pLeaderboardEntry, int32 *pDetails, int cDetailsMax ) +{ + PRINT_DEBUG("[%i] (%i) %llu %p %p", index, cDetailsMax, hSteamLeaderboardEntries, pLeaderboardEntry, pDetails); + std::lock_guard lock(global_mutex); + if (hSteamLeaderboardEntries > cached_leaderboards.size() || hSteamLeaderboardEntries <= 0) return false; + + const auto &board = cached_leaderboards[hSteamLeaderboardEntries - 1]; + if (index < 0 || index >= board.entries.size()) return false; + + const auto &target_entry = board.entries[index]; + + if (pLeaderboardEntry) { + LeaderboardEntry_t entry{}; + entry.m_steamIDUser = target_entry.steam_id; + entry.m_nGlobalRank = 1 + (int)(&target_entry - &board.entries[0]); + entry.m_nScore = target_entry.score; + + *pLeaderboardEntry = entry; + } + + if (pDetails && cDetailsMax > 0) { + for (int i = 0; i < target_entry.score_details.size() && i < cDetailsMax; ++i) { + pDetails[i] = target_entry.score_details[i]; + } + } + + return true; +} + + +// Uploads a user score to the Steam back-end. +// This call is asynchronous, with the result returned in LeaderboardScoreUploaded_t +// Details are extra game-defined information regarding how the user got that score +// pScoreDetails points to an array of int32's, cScoreDetailsCount is the number of int32's in the list +STEAM_CALL_RESULT( LeaderboardScoreUploaded_t ) +SteamAPICall_t Steam_User_Stats::UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, ELeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int32 nScore, const int32 *pScoreDetails, int cScoreDetailsCount ) +{ + PRINT_DEBUG("%llu %i", hSteamLeaderboard, nScore); + std::lock_guard lock(global_mutex); + if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return k_uAPICallInvalid; //TODO: might return callresult even if hSteamLeaderboard is invalid + + auto &board = cached_leaderboards[hSteamLeaderboard - 1]; + auto my_entry = board.find_recent_entry(settings->get_local_steam_id()); + int current_rank = my_entry + ? 1 + (int)(my_entry - &board.entries[0]) + : 0; + int new_rank = current_rank; + + bool score_updated = false; + if (my_entry) { + switch (eLeaderboardUploadScoreMethod) + { + case k_ELeaderboardUploadScoreMethodKeepBest: { // keep user's best score + if (board.sort_method == k_ELeaderboardSortMethodAscending) { // keep user's lowest score + score_updated = nScore < my_entry->score; + } else { // keep user's highest score + score_updated = nScore > my_entry->score; + } + } + break; + + case k_ELeaderboardUploadScoreMethodForceUpdate: { // always replace score with specified + score_updated = my_entry->score != nScore; + } + break; + + default: break; + } + } else { // no entry yet for us + score_updated = true; + } + + if (score_updated || (eLeaderboardUploadScoreMethod == k_ELeaderboardUploadScoreMethodForceUpdate)) { + Steam_Leaderboard_Entry new_entry{}; + new_entry.steam_id = settings->get_local_steam_id(); + new_entry.score = nScore; + if (pScoreDetails && cScoreDetailsCount > 0) { + for (int i = 0; i < cScoreDetailsCount; ++i) { + new_entry.score_details.push_back(pScoreDetails[i]); + } + } + + update_leaderboard_entry(board, new_entry); + new_rank = 1 + (int)(board.find_recent_entry(settings->get_local_steam_id()) - &board.entries[0]); + + // check again in case this was a forced update + // avoid disk write if score is the same + if (score_updated) save_my_leaderboard_entry(board); + send_my_leaderboard_score(board); + + } + + LeaderboardScoreUploaded_t data{}; + data.m_bSuccess = 1; //needs to be success or DOA6 freezes when uploading score. + data.m_hSteamLeaderboard = hSteamLeaderboard; + data.m_nScore = nScore; + data.m_bScoreChanged = score_updated; + data.m_nGlobalRankNew = new_rank; + data.m_nGlobalRankPrevious = current_rank; + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); // TODO is this timing ok? + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; +} + +SteamAPICall_t Steam_User_Stats::UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, int32 nScore, int32 *pScoreDetails, int cScoreDetailsCount ) +{ + PRINT_DEBUG("old"); + return UploadLeaderboardScore(hSteamLeaderboard, k_ELeaderboardUploadScoreMethodKeepBest, nScore, pScoreDetails, cScoreDetailsCount); +} + + +// Attaches a piece of user generated content the user's entry on a leaderboard. +// hContent is a handle to a piece of user generated content that was shared using ISteamUserRemoteStorage::FileShare(). +// This call is asynchronous, with the result returned in LeaderboardUGCSet_t. +STEAM_CALL_RESULT( LeaderboardUGCSet_t ) +SteamAPICall_t Steam_User_Stats::AttachLeaderboardUGC( SteamLeaderboard_t hSteamLeaderboard, UGCHandle_t hUGC ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + LeaderboardUGCSet_t data{}; + if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) { + data.m_eResult = k_EResultFail; + } else { + data.m_eResult = k_EResultOK; + } + + data.m_hSteamLeaderboard = hSteamLeaderboard; + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return ret; +} + + +// Retrieves the number of players currently playing your game (online + offline) +// This call is asynchronous, with the result returned in NumberOfCurrentPlayers_t +STEAM_CALL_RESULT( NumberOfCurrentPlayers_t ) +SteamAPICall_t Steam_User_Stats::GetNumberOfCurrentPlayers() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + std::random_device rd{}; + std::mt19937 gen(rd()); + std::uniform_int_distribution distrib(117, 1017); + + NumberOfCurrentPlayers_t data{}; + data.m_bSuccess = 1; + data.m_cPlayers = distrib(gen); + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return ret; +} + + +// Requests that Steam fetch data on the percentage of players who have received each achievement +// for the game globally. +// This call is asynchronous, with the result returned in GlobalAchievementPercentagesReady_t. +STEAM_CALL_RESULT( GlobalAchievementPercentagesReady_t ) +SteamAPICall_t Steam_User_Stats::RequestGlobalAchievementPercentages() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + GlobalAchievementPercentagesReady_t data{}; + data.m_eResult = EResult::k_EResultOK; + data.m_nGameID = settings->get_local_game_id().ToUint64(); + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return ret; +} + + +// Get the info on the most achieved achievement for the game, returns an iterator index you can use to fetch +// the next most achieved afterwards. Will return -1 if there is no data on achievement +// percentages (ie, you haven't called RequestGlobalAchievementPercentages and waited on the callback). +int Steam_User_Stats::GetMostAchievedAchievementInfo( char *pchName, uint32 unNameBufLen, float *pflPercent, bool *pbAchieved ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!pchName) return -1; + + std::string name(GetAchievementName(0)); + if (name.empty()) return -1; + + if (pchName && unNameBufLen) { + memset(pchName, 0, unNameBufLen); + name.copy(pchName, unNameBufLen - 1); + } + + if (pflPercent) *pflPercent = 90; + if (pbAchieved) { + bool achieved = false; + GetAchievement(name.c_str(), &achieved); + *pbAchieved = achieved; + } + + return 0; +} + + +// Get the info on the next most achieved achievement for the game. Call this after GetMostAchievedAchievementInfo or another +// GetNextMostAchievedAchievementInfo call passing the iterator from the previous call. Returns -1 after the last +// achievement has been iterated. +int Steam_User_Stats::GetNextMostAchievedAchievementInfo( int iIteratorPrevious, char *pchName, uint32 unNameBufLen, float *pflPercent, bool *pbAchieved ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (iIteratorPrevious < 0) return -1; + + int iIteratorCurrent = iIteratorPrevious + 1; + if (iIteratorCurrent >= defined_achievements.size()) return -1; + + std::string name(GetAchievementName(iIteratorCurrent)); + if (name.empty()) return -1; + + if (pchName && unNameBufLen) { + memset(pchName, 0, unNameBufLen); + name.copy(pchName, unNameBufLen - 1); + } + + if (pflPercent) { + *pflPercent = (float)(90 * (defined_achievements.size() - iIteratorCurrent) / defined_achievements.size()); + } + if (pbAchieved) { + bool achieved = false; + GetAchievement(name.c_str(), &achieved); + *pbAchieved = achieved; + } + + return iIteratorCurrent; +} + + +// Returns the percentage of users who have achieved the specified achievement. +bool Steam_User_Stats::GetAchievementAchievedPercent( const char *pchName, float *pflPercent ) +{ + PRINT_DEBUG("'%s'", pchName); + std::lock_guard lock(global_mutex); + + auto it = defined_achievements_find(pchName); + if (defined_achievements.end() == it) return false; + + size_t idx = it - defined_achievements.begin(); + if (pflPercent) { + *pflPercent = (float)(90 * (defined_achievements.size() - idx) / defined_achievements.size()); + } + + return true; +} + + +// Requests global stats data, which is available for stats marked as "aggregated". +// This call is asynchronous, with the results returned in GlobalStatsReceived_t. +// nHistoryDays specifies how many days of day-by-day history to retrieve in addition +// to the overall totals. The limit is 60. +STEAM_CALL_RESULT( GlobalStatsReceived_t ) +SteamAPICall_t Steam_User_Stats::RequestGlobalStats( int nHistoryDays ) +{ + PRINT_DEBUG("%i", nHistoryDays); + std::lock_guard lock(global_mutex); + GlobalStatsReceived_t data{}; + data.m_nGameID = settings->get_local_game_id().ToUint64(); + data.m_eResult = k_EResultOK; + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return ret; +} + + +// Gets the lifetime totals for an aggregated stat +bool Steam_User_Stats::GetGlobalStat( const char *pchStatName, int64 *pData ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +bool Steam_User_Stats::GetGlobalStat( const char *pchStatName, double *pData ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + +// Gets history for an aggregated stat. pData will be filled with daily values, starting with today. +// So when called, pData[0] will be today, pData[1] will be yesterday, and pData[2] will be two days ago, +// etc. cubData is the size in bytes of the pubData buffer. Returns the number of +// elements actually set. +int32 Steam_User_Stats::GetGlobalStatHistory( const char *pchStatName, STEAM_ARRAY_COUNT(cubData) int64 *pData, uint32 cubData ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +int32 Steam_User_Stats::GetGlobalStatHistory( const char *pchStatName, STEAM_ARRAY_COUNT(cubData) double *pData, uint32 cubData ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// For achievements that have related Progress stats, use this to query what the bounds of that progress are. +// You may want this info to selectively call IndicateAchievementProgress when appropriate milestones of progress +// have been made, to show a progress notification to the user. +bool Steam_User_Stats::GetAchievementProgressLimits( const char *pchName, int32 *pnMinProgress, int32 *pnMaxProgress ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +bool Steam_User_Stats::GetAchievementProgressLimits( const char *pchName, float *pfMinProgress, float *pfMaxProgress ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + + + +// --- steam callbacks + +void Steam_User_Stats::send_updated_stats() +{ + if (pending_server_updates.user_stats().empty() && pending_server_updates.user_achievements().empty()) return; + if (settings->disable_sharing_stats_with_gameserver) return; + + auto new_updates_msg = new GameServerStats_Messages::AllStats(pending_server_updates); + pending_server_updates.clear_user_stats(); + pending_server_updates.clear_user_achievements(); + + auto gameserverstats_msg = new GameServerStats_Messages(); + gameserverstats_msg->set_type(GameServerStats_Messages::UpdateUserStatsFromUser); + gameserverstats_msg->set_allocated_update_user_stats(new_updates_msg); + + Common_Message msg{}; + // https://protobuf.dev/reference/cpp/cpp-generated/#string + // set_allocated_xxx() takes ownership of the allocated object, no need to delete + msg.set_allocated_gameserver_stats_messages(gameserverstats_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + // here we send to all gameservers on the network because we don't know the server steamid + network->sendToAllGameservers(&msg, true); + + PRINT_DEBUG("sent updated stats: %zu stats, %zu achievements", + new_updates_msg->user_stats().size(), new_updates_msg->user_achievements().size() + ); +} + +void Steam_User_Stats::steam_run_callback() +{ + send_updated_stats(); +} + + + +// --- networking callbacks +// only triggered when we have a message + +void Steam_User_Stats::network_stats_initial(Common_Message *msg) +{ + if (!msg->gameserver_stats_messages().has_initial_user_stats()) { + PRINT_DEBUG("error empty msg"); + return; + } + + uint64 server_steamid = msg->source_id(); + + auto all_stats_msg = new GameServerStats_Messages::AllStats(); + + // get all stats + auto &stats_map = *all_stats_msg->mutable_user_stats(); + const auto ¤t_stats = settings->getStats(); + for (const auto &stat : current_stats) { + auto &this_stat = stats_map[stat.first]; + this_stat.set_stat_type(stat.second.type); + switch (stat.second.type) + { + case GameServerStats_Messages::StatInfo::STAT_TYPE_INT: { + int32 val = 0; + GetStat(stat.first.c_str(), &val); + this_stat.set_value_int(val); + } + break; + + case GameServerStats_Messages::StatInfo::STAT_TYPE_AVGRATE: // we set the float value also for avg + case GameServerStats_Messages::StatInfo::STAT_TYPE_FLOAT: { + float val = 0; + GetStat(stat.first.c_str(), &val); + this_stat.set_value_float(val); + } + break; + + default: + PRINT_DEBUG("Request_AllUserStats unhandled stat type %i", (int)stat.second.type); + break; + } + } + + // get all achievements + auto &achievements_map = *all_stats_msg->mutable_user_achievements(); + for (const auto &ach : defined_achievements) { + const std::string &name = static_cast( ach.value("name", std::string()) ); + auto &this_ach = achievements_map[name]; + bool achieved = false; + GetAchievement(name.c_str(), &achieved); + this_ach.set_achieved(achieved); + } + + auto initial_stats_msg = new GameServerStats_Messages::InitialAllStats(); + // send back same api call id + initial_stats_msg->set_steam_api_call(msg->gameserver_stats_messages().initial_user_stats().steam_api_call()); + initial_stats_msg->set_allocated_all_data(all_stats_msg); + + auto gameserverstats_msg = new GameServerStats_Messages(); + gameserverstats_msg->set_type(GameServerStats_Messages::Response_AllUserStats); + gameserverstats_msg->set_allocated_initial_user_stats(initial_stats_msg); + + Common_Message new_msg{}; + // https://protobuf.dev/reference/cpp/cpp-generated/#string + // set_allocated_xxx() takes ownership of the allocated object, no need to delete + new_msg.set_allocated_gameserver_stats_messages(gameserverstats_msg); + new_msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + new_msg.set_dest_id(server_steamid); + network->sendTo(&new_msg, true); + + PRINT_DEBUG("server requested all stats, sent %zu stats, %zu achievements", + initial_stats_msg->all_data().user_stats().size(), initial_stats_msg->all_data().user_achievements().size() + ); + + +} + +void Steam_User_Stats::network_stats_updated(Common_Message *msg) +{ + if (!msg->gameserver_stats_messages().has_update_user_stats()) { + PRINT_DEBUG("error empty msg"); + return; + } + + auto &new_user_data = msg->gameserver_stats_messages().update_user_stats(); + + // update our stats + for (auto &new_stat : new_user_data.user_stats()) { + switch (new_stat.second.stat_type()) + { + case GameServerStats_Messages::StatInfo::STAT_TYPE_INT: { + set_stat_internal(new_stat.first.c_str(), new_stat.second.value_int()); + } + break; + + case GameServerStats_Messages::StatInfo::STAT_TYPE_AVGRATE: + case GameServerStats_Messages::StatInfo::STAT_TYPE_FLOAT: { + set_stat_internal(new_stat.first.c_str(), new_stat.second.value_float()); + // non-INT values could have avg values + if (new_stat.second.has_value_avg()) { + auto &avg_val = new_stat.second.value_avg(); + update_avg_rate_stat_internal(new_stat.first.c_str(), avg_val.count_this_session(), avg_val.session_length()); + } + } + break; + + default: + PRINT_DEBUG("UpdateUserStats unhandled stat type %i", (int)new_stat.second.stat_type()); + break; + } + } + + // update achievements + for (auto &new_ach : new_user_data.user_achievements()) { + if (new_ach.second.achieved()) { + set_achievement_internal(new_ach.first.c_str()); + } else { + clear_achievement_internal(new_ach.first.c_str()); + } + } + + PRINT_DEBUG("server sent updated user stats, %zu stats, %zu achievements", + new_user_data.user_stats().size(), new_user_data.user_achievements().size() + ); +} + +void Steam_User_Stats::network_callback_stats(Common_Message *msg) +{ + // network->sendToAll() sends to current user also + if (msg->source_id() == settings->get_local_steam_id().ConvertToUint64()) return; + + uint64 server_steamid = msg->source_id(); + + switch (msg->gameserver_stats_messages().type()) + { + // server wants all stats + case GameServerStats_Messages::Request_AllUserStats: + network_stats_initial(msg); + break; + + // server has updated/new stats + case GameServerStats_Messages::UpdateUserStatsFromServer: + network_stats_updated(msg); + break; + + // a user has updated/new stats + case GameServerStats_Messages::UpdateUserStatsFromUser: + // nothing + break; + + default: + PRINT_DEBUG("unhandled type %i", (int)msg->gameserver_stats_messages().type()); + break; + } +} + + +// someone updated their score +void Steam_User_Stats::network_leaderboard_update_score(Common_Message *msg, Steam_Leaderboard &board, bool send_score_back) +{ + CSteamID sender_steamid((uint64)msg->source_id()); + PRINT_DEBUG("got score for user %llu on leaderboard '%s' (send our score back=%i)", + (uint64)msg->source_id(), board.name.c_str(), (int)send_score_back + ); + + // when players initally load a board, and they don't have an entry in it, + // they send this msg but without their user score entry + if (msg->leaderboards_messages().has_user_score_entry()) { + const auto &user_score_msg = msg->leaderboards_messages().user_score_entry(); + + Steam_Leaderboard_Entry updated_entry{}; + updated_entry.steam_id = sender_steamid; + updated_entry.score = user_score_msg.score(); + updated_entry.score_details.reserve(user_score_msg.score_details().size()); + updated_entry.score_details.assign(user_score_msg.score_details().begin(), user_score_msg.score_details().end()); + update_leaderboard_entry(board, updated_entry); + } + + // if the sender wants back our score, send it to all, not just them + // in case we have 3 or more players and none of them have our data + if (send_score_back) send_my_leaderboard_score(board); +} + +// someone is requesting our score on a leaderboard +void Steam_User_Stats::network_leaderboard_send_my_score(Common_Message *msg, const Steam_Leaderboard &board) +{ + CSteamID sender_steamid((uint64)msg->source_id()); + PRINT_DEBUG("user %llu requested our score for leaderboard '%s'", (uint64)msg->source_id(), board.name.c_str()); + + send_my_leaderboard_score(board, &sender_steamid); +} + +void Steam_User_Stats::network_callback_leaderboards(Common_Message *msg) +{ + // network->sendToAll() sends to current user also + if (msg->source_id() == settings->get_local_steam_id().ConvertToUint64()) return; + if (settings->get_local_game_id().AppID() != msg->leaderboards_messages().appid()) return; + + if (!msg->leaderboards_messages().has_leaderboard_info()) { + PRINT_DEBUG("error empty leaderboard msg"); + return; + } + + const auto &board_info_msg = msg->leaderboards_messages().leaderboard_info(); + + PRINT_DEBUG("attempting to cache leaderboard '%s'", board_info_msg.board_name().c_str()); + unsigned int board_handle = cache_leaderboard_ifneeded( + board_info_msg.board_name(), + (ELeaderboardSortMethod)board_info_msg.sort_method(), + (ELeaderboardDisplayType)board_info_msg.display_type() + ); + + switch (msg->leaderboards_messages().type()) { + // someone updated their score + case Leaderboards_Messages::UpdateUserScore: + network_leaderboard_update_score(msg, cached_leaderboards[board_handle - 1], false); + break; + + // someone updated their score and wants us to share back ours + case Leaderboards_Messages::UpdateUserScoreMutual: + network_leaderboard_update_score(msg, cached_leaderboards[board_handle - 1], true); + break; + + // someone is requesting our score on a leaderboard + case Leaderboards_Messages::RequestUserScore: + network_leaderboard_send_my_score(msg, cached_leaderboards[board_handle - 1]); + break; + + default: + PRINT_DEBUG("unhandled type %i", (int)msg->leaderboards_messages().type()); + break; + } + +} + + +// user connect/disconnect +void Steam_User_Stats::network_callback_low_level(Common_Message *msg) +{ + CSteamID steamid((uint64)msg->source_id()); + // this should never happen, but just in case + if (steamid == settings->get_local_steam_id()) return; + + switch (msg->low_level().type()) + { + case Low_Level::CONNECT: + // nothing + break; + + case Low_Level::DISCONNECT: { + for (auto &board : cached_leaderboards) { + board.remove_entries(steamid); + } + + // PRINT_DEBUG("removed user %llu", (uint64)steamid.ConvertToUint64()); + } + break; + + default: + PRINT_DEBUG("unknown type %i", (int)msg->low_level().type()); + break; + } +} diff --git a/dll/steam_utils.cpp b/dll/steam_utils.cpp index d6bc816d..75fee04e 100644 --- a/dll/steam_utils.cpp +++ b/dll/steam_utils.cpp @@ -20,10 +20,10 @@ Steam_Utils::Steam_Utils(Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, Steam_Overlay *overlay): -settings(settings), -callback_results(callback_results), -callbacks(callbacks), -overlay(overlay) + settings(settings), + callback_results(callback_results), + callbacks(callbacks), + overlay(overlay) { } diff --git a/dll/ugc_remote_storage_bridge.cpp b/dll/ugc_remote_storage_bridge.cpp index 7e747e9b..89a74ea1 100644 --- a/dll/ugc_remote_storage_bridge.cpp +++ b/dll/ugc_remote_storage_bridge.cpp @@ -1,71 +1,71 @@ -#include "dll/ugc_remote_storage_bridge.h" - - -Ugc_Remote_Storage_Bridge::Ugc_Remote_Storage_Bridge(class Settings *settings) -{ - this->settings = settings; - - // subscribe to all mods initially - subscribed = settings->modSet(); -} - -void Ugc_Remote_Storage_Bridge::add_ugc_query_result(UGCHandle_t file_handle, PublishedFileId_t fileid, bool handle_of_primary_file) -{ - std::lock_guard lock(global_mutex); - - steam_ugc_queries[file_handle].mod_id = fileid; - steam_ugc_queries[file_handle].is_primary_file = handle_of_primary_file; -} - -bool Ugc_Remote_Storage_Bridge::remove_ugc_query_result(UGCHandle_t file_handle) -{ - std::lock_guard lock(global_mutex); - - return !!steam_ugc_queries.erase(file_handle); -} - -std::optional Ugc_Remote_Storage_Bridge::get_ugc_query_result(UGCHandle_t file_handle) const -{ - std::lock_guard lock(global_mutex); - - auto it = steam_ugc_queries.find(file_handle); - if (steam_ugc_queries.end() == it) return std::nullopt; - return it->second; -} - -void Ugc_Remote_Storage_Bridge::add_subbed_mod(PublishedFileId_t id) -{ - subscribed.insert(id); -} - -void Ugc_Remote_Storage_Bridge::remove_subbed_mod(PublishedFileId_t id) -{ - subscribed.erase(id); -} - -size_t Ugc_Remote_Storage_Bridge::subbed_mods_count() const -{ - return subscribed.size(); -} - -bool Ugc_Remote_Storage_Bridge::has_subbed_mod(PublishedFileId_t id) const -{ - return !!subscribed.count(id); -} - -std::set::iterator Ugc_Remote_Storage_Bridge::subbed_mods_itr_begin() const -{ - return subscribed.begin(); -} - -std::set::iterator Ugc_Remote_Storage_Bridge::subbed_mods_itr_end() const -{ - return subscribed.end(); -} - -Ugc_Remote_Storage_Bridge::~Ugc_Remote_Storage_Bridge() -{ - std::lock_guard lock(global_mutex); - - steam_ugc_queries.clear(); -} +#include "dll/ugc_remote_storage_bridge.h" + + +Ugc_Remote_Storage_Bridge::Ugc_Remote_Storage_Bridge(class Settings *settings) +{ + this->settings = settings; + + // subscribe to all mods initially + subscribed = settings->modSet(); +} + +Ugc_Remote_Storage_Bridge::~Ugc_Remote_Storage_Bridge() +{ + std::lock_guard lock(global_mutex); + + steam_ugc_queries.clear(); +} + +void Ugc_Remote_Storage_Bridge::add_ugc_query_result(UGCHandle_t file_handle, PublishedFileId_t fileid, bool handle_of_primary_file) +{ + std::lock_guard lock(global_mutex); + + steam_ugc_queries[file_handle].mod_id = fileid; + steam_ugc_queries[file_handle].is_primary_file = handle_of_primary_file; +} + +bool Ugc_Remote_Storage_Bridge::remove_ugc_query_result(UGCHandle_t file_handle) +{ + std::lock_guard lock(global_mutex); + + return !!steam_ugc_queries.erase(file_handle); +} + +std::optional Ugc_Remote_Storage_Bridge::get_ugc_query_result(UGCHandle_t file_handle) const +{ + std::lock_guard lock(global_mutex); + + auto it = steam_ugc_queries.find(file_handle); + if (steam_ugc_queries.end() == it) return std::nullopt; + return it->second; +} + +void Ugc_Remote_Storage_Bridge::add_subbed_mod(PublishedFileId_t id) +{ + subscribed.insert(id); +} + +void Ugc_Remote_Storage_Bridge::remove_subbed_mod(PublishedFileId_t id) +{ + subscribed.erase(id); +} + +size_t Ugc_Remote_Storage_Bridge::subbed_mods_count() const +{ + return subscribed.size(); +} + +bool Ugc_Remote_Storage_Bridge::has_subbed_mod(PublishedFileId_t id) const +{ + return !!subscribed.count(id); +} + +std::set::iterator Ugc_Remote_Storage_Bridge::subbed_mods_itr_begin() const +{ + return subscribed.begin(); +} + +std::set::iterator Ugc_Remote_Storage_Bridge::subbed_mods_itr_end() const +{ + return subscribed.end(); +} diff --git a/overlay_experimental/overlay/steam_overlay.h b/overlay_experimental/overlay/steam_overlay.h index 4408eab7..8c49fd6a 100644 --- a/overlay_experimental/overlay/steam_overlay.h +++ b/overlay_experimental/overlay/steam_overlay.h @@ -254,7 +254,7 @@ public: void AddAchievementNotification(nlohmann::json const& ach); }; -#else +#else // EMU_OVERLAY class Steam_Overlay { @@ -288,6 +288,6 @@ public: void AddAchievementNotification(nlohmann::json const& ach) {} }; -#endif +#endif // EMU_OVERLAY -#endif//__INCLUDED_STEAM_OVERLAY_H__ +#endif //__INCLUDED_STEAM_OVERLAY_H__ diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 7db98cb6..daa9bcd8 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -6,8 +6,6 @@ // avoids confusing ImGui when another label has the same text "MyText" #include "overlay/steam_overlay.h" -#include "overlay/notification.h" -#include "overlay/steam_overlay_translations.h" #include #include @@ -17,14 +15,17 @@ #include #include "InGameOverlay/ImGui/imgui.h" +#include "InGameOverlay/RendererDetector.h" #include "dll/dll.h" #include "dll/settings_parser.h" -#include "InGameOverlay/RendererDetector.h" - +// translation +#include "overlay/steam_overlay_translations.h" // fonts #include "fonts/unifont.hpp" +// builtin audio +#include "overlay/notification.h" #define URL_WINDOW_NAME "URL Window" @@ -135,7 +136,7 @@ Steam_Overlay::Steam_Overlay(Settings* settings, Local_Storage *local_storage, S } this->network->setCallback(CALLBACK_ID_STEAM_MESSAGES, settings->get_local_steam_id(), &Steam_Overlay::overlay_networking_callback, this); - run_every_runcb->add(&Steam_Overlay::overlay_run_callback, this); + this->run_every_runcb->add(&Steam_Overlay::overlay_run_callback, this); } Steam_Overlay::~Steam_Overlay() @@ -143,7 +144,7 @@ Steam_Overlay::~Steam_Overlay() if (settings->disable_overlay) return; this->network->rmCallback(CALLBACK_ID_STEAM_MESSAGES, settings->get_local_steam_id(), &Steam_Overlay::overlay_networking_callback, this); - run_every_runcb->remove(&Steam_Overlay::overlay_run_callback, this); + this->run_every_runcb->remove(&Steam_Overlay::overlay_run_callback, this); } void Steam_Overlay::request_renderer_detector() @@ -1038,7 +1039,7 @@ void Steam_Overlay::build_notifications(int width, int height) friend_actions_temp.push(it->frd->first); // when we click "accept game invite" from someone else, we want to remove this notification immediately since it's no longer relevant // this assignment will make the notification elapsed time insanely large - it->start_time = std::chrono::milliseconds(0); + it->start_time = {}; } } break;