From 7efd3ee0ac8680623c16ade5f6bb03e45e02f5a0 Mon Sep 17 00:00:00 2001 From: universal963 <36097923+universal963@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:55:29 +0800 Subject: [PATCH 001/159] SDK 1.63 --- dll/dll.cpp | 2 +- dll/flat.cpp | 4 ++ sdk/steam/isteamcontroller.h | 87 ++++++++++++++++++++++++++++ sdk/steam/isteaminput.h | 87 ++++++++++++++++++++++++++++ sdk/steam/isteammatchmaking.h | 2 +- sdk/steam/isteamremoteplay.h | 20 ++++++- sdk/steam/steam_api_flat.h | 6 +- sdk/steam/steam_api_internal.h | 6 +- sdk/steam/steamclientpublic.h | 4 ++ sdk/steam/steamhttpenums.h | 100 +++++++++++++++++++-------------- 10 files changed, 266 insertions(+), 52 deletions(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index 9ac937e01..04d37bbb7 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -319,7 +319,7 @@ static void *create_client_interface(const char *ver) return static_cast(client_ptr); } else if (strcmp(ver, "SteamClient021") == 0) { return static_cast(client_ptr); - } else if (strcmp(ver, "SteamClient022") == 0) { + } else if (strcmp(ver, "SteamClient022") == 0) { // not public, based on reversing return static_cast(client_ptr); } else if (strcmp(ver, STEAMCLIENT_INTERFACE_VERSION) == 0) { return static_cast(client_ptr); diff --git a/dll/flat.cpp b/dll/flat.cpp index d313d222f..5e77ad29b 100644 --- a/dll/flat.cpp +++ b/dll/flat.cpp @@ -117,6 +117,7 @@ STEAMAPI_API ISteamScreenshots * SteamAPI_ISteamClient_GetISteamScreenshots( ISt return get_steam_client()->GetISteamScreenshots(hSteamuser, hSteamPipe, pchVersion); } +// removed in SDK 1.63 STEAMAPI_API ISteamGameSearch * SteamAPI_ISteamClient_GetISteamGameSearch( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ) { return get_steam_client()->GetISteamGameSearch(hSteamuser, hSteamPipe, pchVersion); @@ -167,6 +168,7 @@ STEAMAPI_API ISteamMusic * SteamAPI_ISteamClient_GetISteamMusic( ISteamClient* s return get_steam_client()->GetISteamMusic(hSteamuser, hSteamPipe, pchVersion); } +// removed in SDK 1.63 STEAMAPI_API ISteamMusicRemote * SteamAPI_ISteamClient_GetISteamMusicRemote( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ) { return get_steam_client()->GetISteamMusicRemote(hSteamuser, hSteamPipe, pchVersion); @@ -1646,6 +1648,7 @@ STEAMAPI_API void SteamAPI_ISteamMatchmakingServers_CancelServerQuery( ISteamMat return (get_steam_client()->steam_matchmaking_servers)->CancelServerQuery(hServerQuery); } +// whole interface removed in SDK 1.63 STEAMAPI_API ISteamGameSearch *SteamAPI_SteamGameSearch_v001() { return get_steam_client()->GetISteamGameSearch(flat_hsteamuser(), flat_hsteampipe(), "SteamMatchGameSearch001"); @@ -2806,6 +2809,7 @@ STEAMAPI_API float SteamAPI_ISteamMusic_GetVolume( ISteamMusic* self ) return self->GetVolume(); } +// whole interface removed in SDK 1.63 STEAMAPI_API ISteamMusicRemote *SteamAPI_SteamMusicRemote_v001() { return get_steam_client()->GetISteamMusicRemote(flat_hsteamuser(), flat_hsteampipe(), "STEAMMUSICREMOTE_INTERFACE_VERSION001"); diff --git a/sdk/steam/isteamcontroller.h b/sdk/steam/isteamcontroller.h index f0485dc8a..adb103eb8 100644 --- a/sdk/steam/isteamcontroller.h +++ b/sdk/steam/isteamcontroller.h @@ -506,6 +506,93 @@ enum EControllerActionOrigin k_EControllerActionOrigin_Horipad_L4, k_EControllerActionOrigin_Horipad_R4, + k_EControllerActionOrigin_LenovoLegionGo_A, + k_EControllerActionOrigin_LenovoLegionGo_B, + k_EControllerActionOrigin_LenovoLegionGo_X, + k_EControllerActionOrigin_LenovoLegionGo_Y, + k_EControllerActionOrigin_LenovoLegionGo_LB, + k_EControllerActionOrigin_LenovoLegionGo_RB, + k_EControllerActionOrigin_LenovoLegionGo_Menu, + k_EControllerActionOrigin_LenovoLegionGo_View, + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_Touch, // Left pad is only present on the original Legion Go + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_Swipe, + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_Click, + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_DPadNorth, + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_DPadSouth, + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_DPadWest, + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_DPadEast, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_Touch, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_Swipe, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_Click, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_DPadNorth, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_DPadSouth, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_DPadWest, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_DPadEast, + k_EControllerActionOrigin_LenovoLegionGo_LT_SoftPull, + k_EControllerActionOrigin_LenovoLegionGo_LT, + k_EControllerActionOrigin_LenovoLegionGo_RT_SoftPull, + k_EControllerActionOrigin_LenovoLegionGo_RT, + k_EControllerActionOrigin_LenovoLegionGo_LeftStick_Move, + k_EControllerActionOrigin_LenovoLegionGo_LS, + k_EControllerActionOrigin_LenovoLegionGo_LeftStick_DPadNorth, + k_EControllerActionOrigin_LenovoLegionGo_LeftStick_DPadSouth, + k_EControllerActionOrigin_LenovoLegionGo_LeftStick_DPadWest, + k_EControllerActionOrigin_LenovoLegionGo_LeftStick_DPadEast, + k_EControllerActionOrigin_LenovoLegionGo_RightStick_Move, + k_EControllerActionOrigin_LenovoLegionGo_RS, + k_EControllerActionOrigin_LenovoLegionGo_RightStick_DPadNorth, + k_EControllerActionOrigin_LenovoLegionGo_RightStick_DPadSouth, + k_EControllerActionOrigin_LenovoLegionGo_RightStick_DPadWest, + k_EControllerActionOrigin_LenovoLegionGo_RightStick_DPadEast, + k_EControllerActionOrigin_LenovoLegionGo_Y1, + k_EControllerActionOrigin_LenovoLegionGo_Y2, + k_EControllerActionOrigin_LenovoLegionGo_DPad_Move, + k_EControllerActionOrigin_LenovoLegionGo_DPad_North, + k_EControllerActionOrigin_LenovoLegionGo_DPad_South, + k_EControllerActionOrigin_LenovoLegionGo_DPad_West, + k_EControllerActionOrigin_LenovoLegionGo_DPad_East, + k_EControllerActionOrigin_LenovoLegionGo_Gyro_Move, + k_EControllerActionOrigin_LenovoLegionGo_Gyro_Pitch, + k_EControllerActionOrigin_LenovoLegionGo_Gyro_Yaw, + k_EControllerActionOrigin_LenovoLegionGo_Gyro_Roll, + k_EControllerActionOrigin_LenovoLegionGo_Reserved1, + k_EControllerActionOrigin_LenovoLegionGo_Reserved2, + k_EControllerActionOrigin_LenovoLegionGo_Reserved3, + k_EControllerActionOrigin_LenovoLegionGo_Reserved4, + k_EControllerActionOrigin_LenovoLegionGo_Reserved5, + k_EControllerActionOrigin_LenovoLegionGo_Reserved6, + k_EControllerActionOrigin_LenovoLegionGo_Reserved7, + k_EControllerActionOrigin_LenovoLegionGo_Reserved8, + k_EControllerActionOrigin_LenovoLegionGo_Reserved9, + k_EControllerActionOrigin_LenovoLegionGo_Reserved10, + k_EControllerActionOrigin_LenovoLegionGo_Reserved11, + k_EControllerActionOrigin_LenovoLegionGo_Reserved12, + k_EControllerActionOrigin_LenovoLegionGo_Reserved13, + k_EControllerActionOrigin_LenovoLegionGo_Reserved14, + k_EControllerActionOrigin_LenovoLegionGo_Reserved15, + k_EControllerActionOrigin_LenovoLegionGo_Reserved16, + k_EControllerActionOrigin_LenovoLegionGo_Reserved17, + k_EControllerActionOrigin_LenovoLegionGo_Reserved18, + k_EControllerActionOrigin_LenovoLegionGo_Reserved19, + k_EControllerActionOrigin_LenovoLegionGo_Reserved20, + + k_EControllerActionOrigin_Generic_L4, + k_EControllerActionOrigin_Generic_R4, + k_EControllerActionOrigin_Generic_L5, + k_EControllerActionOrigin_Generic_R5, + k_EControllerActionOrigin_Generic_PL, + k_EControllerActionOrigin_Generic_PR, + k_EControllerActionOrigin_Generic_C, + k_EControllerActionOrigin_Generic_Z, + k_EControllerActionOrigin_Generic_MISC1, + k_EControllerActionOrigin_Generic_MISC2, + k_EControllerActionOrigin_Generic_MISC3, + k_EControllerActionOrigin_Generic_MISC4, + k_EControllerActionOrigin_Generic_MISC5, + k_EControllerActionOrigin_Generic_MISC6, + k_EControllerActionOrigin_Generic_MISC7, + k_EControllerActionOrigin_Generic_MISC8, + k_EControllerActionOrigin_Count, // If Steam has added support for new controllers origins will go here. k_EControllerActionOrigin_MaximumPossibleValue = 32767, // Origins are currently a maximum of 16 bits. }; diff --git a/sdk/steam/isteaminput.h b/sdk/steam/isteaminput.h index 0c1827f98..2c0470603 100644 --- a/sdk/steam/isteaminput.h +++ b/sdk/steam/isteaminput.h @@ -509,6 +509,93 @@ enum EInputActionOrigin k_EInputActionOrigin_Horipad_L4, k_EInputActionOrigin_Horipad_R4, + k_EInputActionOrigin_LenovoLegionGo_A, + k_EInputActionOrigin_LenovoLegionGo_B, + k_EInputActionOrigin_LenovoLegionGo_X, + k_EInputActionOrigin_LenovoLegionGo_Y, + k_EInputActionOrigin_LenovoLegionGo_LB, + k_EInputActionOrigin_LenovoLegionGo_RB, + k_EInputActionOrigin_LenovoLegionGo_Menu, + k_EInputActionOrigin_LenovoLegionGo_View, + k_EInputActionOrigin_LenovoLegionGo_LeftPad_Touch, // Left pad is only present on the original Legion Go + k_EInputActionOrigin_LenovoLegionGo_LeftPad_Swipe, + k_EInputActionOrigin_LenovoLegionGo_LeftPad_Click, + k_EInputActionOrigin_LenovoLegionGo_LeftPad_DPadNorth, + k_EInputActionOrigin_LenovoLegionGo_LeftPad_DPadSouth, + k_EInputActionOrigin_LenovoLegionGo_LeftPad_DPadWest, + k_EInputActionOrigin_LenovoLegionGo_LeftPad_DPadEast, + k_EInputActionOrigin_LenovoLegionGo_RightPad_Touch, + k_EInputActionOrigin_LenovoLegionGo_RightPad_Swipe, + k_EInputActionOrigin_LenovoLegionGo_RightPad_Click, + k_EInputActionOrigin_LenovoLegionGo_RightPad_DPadNorth, + k_EInputActionOrigin_LenovoLegionGo_RightPad_DPadSouth, + k_EInputActionOrigin_LenovoLegionGo_RightPad_DPadWest, + k_EInputActionOrigin_LenovoLegionGo_RightPad_DPadEast, + k_EInputActionOrigin_LenovoLegionGo_LT_SoftPull, + k_EInputActionOrigin_LenovoLegionGo_LT, + k_EInputActionOrigin_LenovoLegionGo_RT_SoftPull, + k_EInputActionOrigin_LenovoLegionGo_RT, + k_EInputActionOrigin_LenovoLegionGo_LeftStick_Move, + k_EInputActionOrigin_LenovoLegionGo_LS, + k_EInputActionOrigin_LenovoLegionGo_LeftStick_DPadNorth, + k_EInputActionOrigin_LenovoLegionGo_LeftStick_DPadSouth, + k_EInputActionOrigin_LenovoLegionGo_LeftStick_DPadWest, + k_EInputActionOrigin_LenovoLegionGo_LeftStick_DPadEast, + k_EInputActionOrigin_LenovoLegionGo_RightStick_Move, + k_EInputActionOrigin_LenovoLegionGo_RS, + k_EInputActionOrigin_LenovoLegionGo_RightStick_DPadNorth, + k_EInputActionOrigin_LenovoLegionGo_RightStick_DPadSouth, + k_EInputActionOrigin_LenovoLegionGo_RightStick_DPadWest, + k_EInputActionOrigin_LenovoLegionGo_RightStick_DPadEast, + k_EInputActionOrigin_LenovoLegionGo_Y1, + k_EInputActionOrigin_LenovoLegionGo_Y2, + k_EInputActionOrigin_LenovoLegionGo_DPad_Move, + k_EInputActionOrigin_LenovoLegionGo_DPad_North, + k_EInputActionOrigin_LenovoLegionGo_DPad_South, + k_EInputActionOrigin_LenovoLegionGo_DPad_West, + k_EInputActionOrigin_LenovoLegionGo_DPad_East, + k_EInputActionOrigin_LenovoLegionGo_Gyro_Move, + k_EInputActionOrigin_LenovoLegionGo_Gyro_Pitch, + k_EInputActionOrigin_LenovoLegionGo_Gyro_Yaw, + k_EInputActionOrigin_LenovoLegionGo_Gyro_Roll, + k_EInputActionOrigin_LenovoLegionGo_Reserved1, + k_EInputActionOrigin_LenovoLegionGo_Reserved2, + k_EInputActionOrigin_LenovoLegionGo_Reserved3, + k_EInputActionOrigin_LenovoLegionGo_Reserved4, + k_EInputActionOrigin_LenovoLegionGo_Reserved5, + k_EInputActionOrigin_LenovoLegionGo_Reserved6, + k_EInputActionOrigin_LenovoLegionGo_Reserved7, + k_EInputActionOrigin_LenovoLegionGo_Reserved8, + k_EInputActionOrigin_LenovoLegionGo_Reserved9, + k_EInputActionOrigin_LenovoLegionGo_Reserved10, + k_EInputActionOrigin_LenovoLegionGo_Reserved11, + k_EInputActionOrigin_LenovoLegionGo_Reserved12, + k_EInputActionOrigin_LenovoLegionGo_Reserved13, + k_EInputActionOrigin_LenovoLegionGo_Reserved14, + k_EInputActionOrigin_LenovoLegionGo_Reserved15, + k_EInputActionOrigin_LenovoLegionGo_Reserved16, + k_EInputActionOrigin_LenovoLegionGo_Reserved17, + k_EInputActionOrigin_LenovoLegionGo_Reserved18, + k_EInputActionOrigin_LenovoLegionGo_Reserved19, + k_EInputActionOrigin_LenovoLegionGo_Reserved20, + + k_EInputActionOrigin_Generic_L4, + k_EInputActionOrigin_Generic_R4, + k_EInputActionOrigin_Generic_L5, + k_EInputActionOrigin_Generic_R5, + k_EInputActionOrigin_Generic_PL, + k_EInputActionOrigin_Generic_PR, + k_EInputActionOrigin_Generic_C, + k_EInputActionOrigin_Generic_Z, + k_EInputActionOrigin_Generic_MISC1, + k_EInputActionOrigin_Generic_MISC2, + k_EInputActionOrigin_Generic_MISC3, + k_EInputActionOrigin_Generic_MISC4, + k_EInputActionOrigin_Generic_MISC5, + k_EInputActionOrigin_Generic_MISC6, + k_EInputActionOrigin_Generic_MISC7, + k_EInputActionOrigin_Generic_MISC8, + k_EInputActionOrigin_Count, // If Steam has added support for new controllers origins will go here. k_EInputActionOrigin_MaximumPossibleValue = 32767, // Origins are currently a maximum of 16 bits. }; diff --git a/sdk/steam/isteammatchmaking.h b/sdk/steam/isteammatchmaking.h index 9de3b5e47..13c24fc99 100644 --- a/sdk/steam/isteammatchmaking.h +++ b/sdk/steam/isteammatchmaking.h @@ -18,7 +18,7 @@ enum ELobbyType { k_ELobbyTypePrivate = 0, // only way to join the lobby is to invite to someone else - k_ELobbyTypeFriendsOnly = 1, // shows for friends or invitees, but not in lobby list + k_ELobbyTypeFriendsOnly = 1, // shows for friends or invitees, but not in public lobby list, allows those who join to invite their own friends k_ELobbyTypePublic = 2, // visible for friends and in lobby list k_ELobbyTypeInvisible = 3, // returned by search, but not visible to other friends // useful if you want a user in two lobbies, for example matching groups together diff --git a/sdk/steam/isteamremoteplay.h b/sdk/steam/isteamremoteplay.h index a248595f1..99f67ea18 100644 --- a/sdk/steam/isteamremoteplay.h +++ b/sdk/steam/isteamremoteplay.h @@ -161,6 +161,23 @@ enum ERemotePlayScancode k_ERemotePlayScancodeDown = 81, k_ERemotePlayScancodeUp = 82, + k_ERemotePlayScancodeKeypadDivide = 84, + k_ERemotePlayScancodeKeypadMultiply = 85, + k_ERemotePlayScancodeKeypadMinus = 86, + k_ERemotePlayScancodeKeypadPlus = 87, + k_ERemotePlayScancodeKeypadEnter = 88, + k_ERemotePlayScancodeKeypad1 = 89, + k_ERemotePlayScancodeKeypad2 = 90, + k_ERemotePlayScancodeKeypad3 = 91, + k_ERemotePlayScancodeKeypad4 = 92, + k_ERemotePlayScancodeKeypad5 = 93, + k_ERemotePlayScancodeKeypad6 = 94, + k_ERemotePlayScancodeKeypad7 = 95, + k_ERemotePlayScancodeKeypad8 = 96, + k_ERemotePlayScancodeKeypad9 = 97, + k_ERemotePlayScancodeKeypad0 = 98, + k_ERemotePlayScancodeKeypadPeriod = 99, + k_ERemotePlayScancodeLeftControl = 224, k_ERemotePlayScancodeLeftShift = 225, k_ERemotePlayScancodeLeftAlt = 226, @@ -283,8 +300,7 @@ class ISteamRemotePlay // This returns false if your game is not configured for Remote Play Together virtual bool ShowRemotePlayTogetherUI() = 0; - // Invite a friend to Remote Play Together, or create a guest invite if steamIDFriend is empty - // This will automatically start Remote Play Together if it hasn't already been started + // Invite a friend to Remote Play Together, or create a guest invite if steamIDFriend is CSteamID() // This returns false if the invite can't be sent or your game is not configured for Remote Play Together virtual bool BSendRemotePlayTogetherInvite( CSteamID steamIDFriend ) = 0; diff --git a/sdk/steam/steam_api_flat.h b/sdk/steam/steam_api_flat.h index fd24be3e7..a49941a2b 100644 --- a/sdk/steam/steam_api_flat.h +++ b/sdk/steam/steam_api_flat.h @@ -37,7 +37,7 @@ STEAMAPI_API ISteamApps * SteamAPI_ISteamClient_GetISteamApps( ISteamClient* sel STEAMAPI_API ISteamNetworking * SteamAPI_ISteamClient_GetISteamNetworking( ISteamClient* self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char * pchVersion ); STEAMAPI_API ISteamRemoteStorage * SteamAPI_ISteamClient_GetISteamRemoteStorage( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); STEAMAPI_API ISteamScreenshots * SteamAPI_ISteamClient_GetISteamScreenshots( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); -STEAMAPI_API ISteamGameSearch * SteamAPI_ISteamClient_GetISteamGameSearch( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); +STEAMAPI_API ISteamGameSearch * SteamAPI_ISteamClient_GetISteamGameSearch( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); // removed in SDK 1.63 STEAMAPI_API uint32 SteamAPI_ISteamClient_GetIPCCallCount( ISteamClient* self ); STEAMAPI_API void SteamAPI_ISteamClient_SetWarningMessageHook( ISteamClient* self, SteamAPIWarningMessageHook_t pFunction ); STEAMAPI_API steam_bool SteamAPI_ISteamClient_BShutdownIfAllPipesClosed( ISteamClient* self ); @@ -46,7 +46,7 @@ STEAMAPI_API ISteamController * SteamAPI_ISteamClient_GetISteamController( IStea STEAMAPI_API ISteamUGC * SteamAPI_ISteamClient_GetISteamUGC( ISteamClient* self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char * pchVersion ); STEAMAPI_API ISteamAppList * SteamAPI_ISteamClient_GetISteamAppList( ISteamClient* self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char * pchVersion ); STEAMAPI_API ISteamMusic * SteamAPI_ISteamClient_GetISteamMusic( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); -STEAMAPI_API ISteamMusicRemote * SteamAPI_ISteamClient_GetISteamMusicRemote( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); +STEAMAPI_API ISteamMusicRemote * SteamAPI_ISteamClient_GetISteamMusicRemote( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); // removed in SDK 1.63 STEAMAPI_API ISteamHTMLSurface * SteamAPI_ISteamClient_GetISteamHTMLSurface( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); STEAMAPI_API ISteamInventory * SteamAPI_ISteamClient_GetISteamInventory( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); STEAMAPI_API ISteamVideo * SteamAPI_ISteamClient_GetISteamVideo( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); @@ -306,6 +306,7 @@ STEAMAPI_API HServerQuery SteamAPI_ISteamMatchmakingServers_ServerRules( ISteamM STEAMAPI_API void SteamAPI_ISteamMatchmakingServers_CancelServerQuery( ISteamMatchmakingServers* self, HServerQuery hServerQuery ); // ISteamGameSearch +// whole interface removed in SDK 1.63 STEAMAPI_API ISteamGameSearch *SteamAPI_SteamGameSearch_v001(); STEAMAPI_API EGameSearchErrorCode_t SteamAPI_ISteamGameSearch_AddGameSearchParams( ISteamGameSearch* self, const char * pchKeyToFind, const char * pchValuesToFind ); STEAMAPI_API EGameSearchErrorCode_t SteamAPI_ISteamGameSearch_SearchForGameWithLobby( ISteamGameSearch* self, uint64_steamid steamIDLobby, int nPlayerMin, int nPlayerMax ); @@ -541,6 +542,7 @@ STEAMAPI_API void SteamAPI_ISteamMusic_SetVolume( ISteamMusic* self, float flVol STEAMAPI_API float SteamAPI_ISteamMusic_GetVolume( ISteamMusic* self ); // ISteamMusicRemote +// whole interface removed in SDK 1.63 STEAMAPI_API ISteamMusicRemote *SteamAPI_SteamMusicRemote_v001(); STEAMAPI_API steam_bool SteamAPI_ISteamMusicRemote_RegisterSteamMusicRemote( ISteamMusicRemote* self, const char * pchName ); STEAMAPI_API steam_bool SteamAPI_ISteamMusicRemote_DeregisterSteamMusicRemote( ISteamMusicRemote* self ); diff --git a/sdk/steam/steam_api_internal.h b/sdk/steam/steam_api_internal.h index c2e46b74d..fe5d39478 100644 --- a/sdk/steam/steam_api_internal.h +++ b/sdk/steam/steam_api_internal.h @@ -229,7 +229,7 @@ class ISteamNetworking; class ISteamRemoteStorage; class ISteamScreenshots; class ISteamMusic; -class ISteamMusicRemote; +class ISteamMusicRemote; // removed in SDK 1.63 class ISteamGameServerStats; class ISteamHTTP; class ISteamController; @@ -239,7 +239,7 @@ class ISteamHTMLSurface; class ISteamInventory; class ISteamVideo; class ISteamParentalSettings; -class ISteamGameSearch; +class ISteamGameSearch; // removed in SDK 1.63 class ISteamInput; class ISteamParties; class ISteamTV; @@ -298,7 +298,7 @@ enum { k_iClientShortcutsCallbacks = 3700 }; enum { k_iClientRemoteControlManagerCallbacks = 3800 }; enum { k_iSteamAppListCallbacks = 3900 }; enum { k_iSteamMusicCallbacks = 4000 }; -enum { k_iSteamMusicRemoteCallbacks = 4100 }; +enum { k_iSteamMusicRemoteCallbacks = 4100 }; // removed in SDK 1.63 enum { k_iClientVRCallbacks = 4200 }; enum { k_iClientGameNotificationCallbacks = 4300 }; enum { k_iSteamGameNotificationCallbacks = 4400 }; diff --git a/sdk/steam/steamclientpublic.h b/sdk/steam/steamclientpublic.h index 5af797c58..3fed12470 100644 --- a/sdk/steam/steamclientpublic.h +++ b/sdk/steam/steamclientpublic.h @@ -156,6 +156,7 @@ enum EResult k_EResultNotSupported = 128, // The data being accessed is not supported by this API k_EResultFamilySizeLimitExceeded = 129, // Reached the maximum size of the family k_EResultOfflineAppCacheInvalid = 130, // The local data for the offline mode cache is insufficient to login + k_EResultTryLater = 131, // retry the operation later }; // Error codes for use with the voice functions @@ -685,6 +686,9 @@ enum EMarketNotAllowedReasonFlags // User accepted a wallet gift that was recently purchased k_EMarketNotAllowedReason_AcceptedWalletGift = (1 << 15), + + // User did something that triggered a trade cooldown (like reversing trades) + k_EMarketNotAllowedReason_TradeCooldown = (1 << 16), }; diff --git a/sdk/steam/steamhttpenums.h b/sdk/steam/steamhttpenums.h index 004b3a93c..9ea1d9fbe 100644 --- a/sdk/steam/steamhttpenums.h +++ b/sdk/steam/steamhttpenums.h @@ -38,62 +38,76 @@ enum EHTTPMethod enum EHTTPStatusCode { // Invalid status code (this isn't defined in HTTP, used to indicate unset in our code) - k_EHTTPStatusCodeInvalid = 0, + k_EHTTPStatusCodeInvalid = 0, // Informational codes - k_EHTTPStatusCode100Continue = 100, - k_EHTTPStatusCode101SwitchingProtocols = 101, + k_EHTTPStatusCode100Continue = 100, + k_EHTTPStatusCode101SwitchingProtocols = 101, // Success codes - k_EHTTPStatusCode200OK = 200, - k_EHTTPStatusCode201Created = 201, - k_EHTTPStatusCode202Accepted = 202, - k_EHTTPStatusCode203NonAuthoritative = 203, - k_EHTTPStatusCode204NoContent = 204, - k_EHTTPStatusCode205ResetContent = 205, - k_EHTTPStatusCode206PartialContent = 206, + k_EHTTPStatusCode200OK = 200, + k_EHTTPStatusCode201Created = 201, + k_EHTTPStatusCode202Accepted = 202, + k_EHTTPStatusCode203NonAuthoritative = 203, + k_EHTTPStatusCode204NoContent = 204, + k_EHTTPStatusCode205ResetContent = 205, + k_EHTTPStatusCode206PartialContent = 206, // Redirection codes - k_EHTTPStatusCode300MultipleChoices = 300, - k_EHTTPStatusCode301MovedPermanently = 301, - k_EHTTPStatusCode302Found = 302, - k_EHTTPStatusCode303SeeOther = 303, - k_EHTTPStatusCode304NotModified = 304, - k_EHTTPStatusCode305UseProxy = 305, + k_EHTTPStatusCode300MultipleChoices = 300, + k_EHTTPStatusCode301MovedPermanently = 301, + k_EHTTPStatusCode302Found = 302, + k_EHTTPStatusCode303SeeOther = 303, + k_EHTTPStatusCode304NotModified = 304, + k_EHTTPStatusCode305UseProxy = 305, //k_EHTTPStatusCode306Unused = 306, (used in old HTTP spec, now unused in 1.1) - k_EHTTPStatusCode307TemporaryRedirect = 307, - k_EHTTPStatusCode308PermanentRedirect = 308, + k_EHTTPStatusCode307TemporaryRedirect = 307, + k_EHTTPStatusCode308PermanentRedirect = 308, // Error codes - k_EHTTPStatusCode400BadRequest = 400, - k_EHTTPStatusCode401Unauthorized = 401, // You probably want 403 or something else. 401 implies you're sending a WWW-Authenticate header and the client can sent an Authorization header in response. - k_EHTTPStatusCode402PaymentRequired = 402, // This is reserved for future HTTP specs, not really supported by clients - k_EHTTPStatusCode403Forbidden = 403, - k_EHTTPStatusCode404NotFound = 404, - k_EHTTPStatusCode405MethodNotAllowed = 405, - k_EHTTPStatusCode406NotAcceptable = 406, - k_EHTTPStatusCode407ProxyAuthRequired = 407, - k_EHTTPStatusCode408RequestTimeout = 408, - k_EHTTPStatusCode409Conflict = 409, - k_EHTTPStatusCode410Gone = 410, - k_EHTTPStatusCode411LengthRequired = 411, - k_EHTTPStatusCode412PreconditionFailed = 412, - k_EHTTPStatusCode413RequestEntityTooLarge = 413, - k_EHTTPStatusCode414RequestURITooLong = 414, - k_EHTTPStatusCode415UnsupportedMediaType = 415, + k_EHTTPStatusCode400BadRequest = 400, + k_EHTTPStatusCode401Unauthorized = 401, // You probably want 403 or something else. 401 implies you're sending a WWW-Authenticate header and the client can sent an Authorization header in response. + k_EHTTPStatusCode402PaymentRequired = 402, // This is reserved for future HTTP specs, not really supported by clients + k_EHTTPStatusCode403Forbidden = 403, + k_EHTTPStatusCode404NotFound = 404, + k_EHTTPStatusCode405MethodNotAllowed = 405, + k_EHTTPStatusCode406NotAcceptable = 406, + k_EHTTPStatusCode407ProxyAuthRequired = 407, + k_EHTTPStatusCode408RequestTimeout = 408, + k_EHTTPStatusCode409Conflict = 409, + k_EHTTPStatusCode410Gone = 410, + k_EHTTPStatusCode411LengthRequired = 411, + k_EHTTPStatusCode412PreconditionFailed = 412, + k_EHTTPStatusCode413RequestEntityTooLarge = 413, + k_EHTTPStatusCode414RequestURITooLong = 414, + k_EHTTPStatusCode415UnsupportedMediaType = 415, k_EHTTPStatusCode416RequestedRangeNotSatisfiable = 416, - k_EHTTPStatusCode417ExpectationFailed = 417, - k_EHTTPStatusCode4xxUnknown = 418, // 418 is reserved, so we'll use it to mean unknown - k_EHTTPStatusCode429TooManyRequests = 429, - k_EHTTPStatusCode444ConnectionClosed = 444, // nginx only? + k_EHTTPStatusCode417ExpectationFailed = 417, + k_EHTTPStatusCode4xxUnknown = 418, // 418 is reserved, so we'll use it to mean unknown + k_EHTTPStatusCode421MisdirectedRequest = 421, + k_EHTTPStatusCode422UnprocessableContent = 422, + k_EHTTPStatusCode423Locked = 423, + k_EHTTPStatusCode424FailedDependency = 424, + k_EHTTPStatusCode425TooEarly = 425, + k_EHTTPStatusCode426UpgradeRequired = 426, + k_EHTTPStatusCode428PreconditionRequired = 428, + k_EHTTPStatusCode429TooManyRequests = 429, + k_EHTTPStatusCode431RequestHeaderFieldsTooLarge = 431, + k_EHTTPStatusCode444ConnectionClosed = 444, // nginx only? + k_EHTTPStatusCode451UnavailableForLegalReasons = 451, // Server error codes - k_EHTTPStatusCode500InternalServerError = 500, - k_EHTTPStatusCode501NotImplemented = 501, - k_EHTTPStatusCode502BadGateway = 502, - k_EHTTPStatusCode503ServiceUnavailable = 503, - k_EHTTPStatusCode504GatewayTimeout = 504, + k_EHTTPStatusCode500InternalServerError = 500, + k_EHTTPStatusCode501NotImplemented = 501, + k_EHTTPStatusCode502BadGateway = 502, + k_EHTTPStatusCode503ServiceUnavailable = 503, + k_EHTTPStatusCode504GatewayTimeout = 504, k_EHTTPStatusCode505HTTPVersionNotSupported = 505, + k_EHTTPStatusCode506VariantAlsoNegotiates = 506, + k_EHTTPStatusCode507InsufficientStorage = 507, + k_EHTTPStatusCode508LoopDetected = 508, + k_EHTTPStatusCode510NotExtended = 510, + k_EHTTPStatusCode511NetworkAuthenticationRequired = 511, k_EHTTPStatusCode5xxUnknown = 599, }; From e072a14625aafa5ee1b28f2c97b5594e5d42836b Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:22:41 +0100 Subject: [PATCH 002/159] Closes #394 DisOvlAchProg=true by default --- dll/dll/settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dll/dll/settings.h b/dll/dll/settings.h index 5beccf1e8..4eb0e51ab 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -368,7 +368,7 @@ class Settings { int overlay_renderer_detector_timeout_sec = 15; // "Saints Row (2022)" takes almost ~8 sec to detect renderer (DX12) bool disable_overlay_achievement_notification = false; bool disable_overlay_friend_notification = false; - bool disable_overlay_achievement_progress = false; + bool disable_overlay_achievement_progress = true; unsigned overlay_fps_avg_window = 10; float overlay_stats_pos_x = 0.0f; float overlay_stats_pos_y = 0.0f; From 63f803dc0073c5c8d6f16580582e78eac3c15b8e Mon Sep 17 00:00:00 2001 From: a Date: Fri, 9 Jan 2026 21:15:07 +0200 Subject: [PATCH 003/159] revert p2p manager changes --- dll/dll/p2p_manager.hpp | 121 --- dll/dll/settings.h | 29 - dll/dll/steam_client.h | 1 - dll/dll/steam_networking.h | 23 +- dll/net.proto | 13 +- dll/network.cpp | 69 +- dll/p2p_manager.cpp | 734 ------------------ dll/settings_parser.cpp | 7 - dll/steam_client.cpp | 16 +- dll/steam_gameserver.cpp | 7 +- dll/steam_matchmaking.cpp | 7 +- dll/steam_networking.cpp | 356 +++++++-- .../configs.main.EXAMPLE.ini | 13 - 13 files changed, 365 insertions(+), 1031 deletions(-) delete mode 100644 dll/dll/p2p_manager.hpp delete mode 100644 dll/p2p_manager.cpp diff --git a/dll/dll/p2p_manager.hpp b/dll/dll/p2p_manager.hpp deleted file mode 100644 index 0a04f2bdf..000000000 --- a/dll/dll/p2p_manager.hpp +++ /dev/null @@ -1,121 +0,0 @@ -/* 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 - . */ - -#pragma once - -#include "base.h" -#include - - -class P2p_Manager -{ -private: - struct Peer_Src_t - { - CSteamID remote_id{}; - CSteamID my_dest_id{}; - }; - - struct Packet_t - { - private: - std::chrono::high_resolution_clock::time_point time_created = std::chrono::high_resolution_clock::now(); - - public: - bool is_processed = false; - std::vector data{}; - EP2PSend send_type = EP2PSend::k_EP2PSendUnreliable; - - const std::chrono::high_resolution_clock::time_point& get_time_created() const; - }; - - struct Channel_t - { - std::list packets{}; - }; - - struct Connection_t - { - Peer_Src_t peer_conn{}; - std::chrono::high_resolution_clock::time_point time_added = std::chrono::high_resolution_clock::now(); - bool is_accepted = false; - std::unordered_map channels{}; - }; - - std::recursive_mutex p2p_mtx{}; - std::list connections{}; - - class Settings *settings_client{}; - class Settings *settings_server{}; - class SteamCallBacks *callbacks_client{}; - class SteamCallBacks *callbacks_server{}; - class Networking *network{}; - class RunEveryRunCB *run_every_runcb{}; - - SteamCallBacks* get_my_callbacks(const CSteamID &my_id) const; - bool is_same_peer(const CSteamID &id, const CSteamID &peer_id) const; - void send_peer_session_failure(const Peer_Src_t &peer_conn); - void trigger_session_request(const CSteamID &remote_id, const CSteamID &my_id); - - bool remove_connection(const CSteamID &remote_id, const CSteamID &my_id); - Connection_t* create_connection(const CSteamID &remote_id, const CSteamID &my_id); - Connection_t* get_connection(const CSteamID &remote_id, const CSteamID &my_id); - - // true if the connection was already accepted, - // false otherwise. - bool store_packet( - CSteamID my_id, CSteamID steamIDRemote, - const void *pubData, uint32 cubData, int nChannel, - EP2PSend send_type - ); - - std::optional> get_next_packet(CSteamID my_id, int nChannel); - - void periodic_handle_connections(const std::chrono::high_resolution_clock::time_point &now); - void periodic_handle_channels(const std::chrono::high_resolution_clock::time_point &now); - void periodic_handle_packets(const std::chrono::high_resolution_clock::time_point &now); - void periodic_callback(); - - void network_data_packets(Common_Message *msg); - void network_low_level(Common_Message *msg); - void network_callback(Common_Message *msg); - - static void steam_networking_callback(void *object, Common_Message *msg); - static void steam_run_every_runcb(void *object); - - -public: - P2p_Manager( - class Settings *settings_client, class Settings *settings_server, - class SteamCallBacks *callbacks_client, class SteamCallBacks *callbacks_server, - class Networking *network, class RunEveryRunCB *run_every_runcb - ); - ~P2p_Manager(); - - bool send_packet(CSteamID my_id, CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType, int nChannel); - bool is_packet_available(CSteamID my_id, uint32 *pcubMsgSize, int nChannel); - bool read_packet(CSteamID my_id, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote, int nChannel); - bool close_channel(CSteamID my_id, CSteamID steamIDRemote, int nChannel); - bool close_session(CSteamID my_id, CSteamID steamIDRemote); - bool get_session_state(CSteamID my_id, CSteamID steamIDRemote, P2PSessionState_t *pConnectionState); - bool accept_session(CSteamID my_id, CSteamID steamIDRemote); - -}; diff --git a/dll/dll/settings.h b/dll/dll/settings.h index 4eb0e51ab..ad8d1405b 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -200,32 +200,6 @@ struct Branch_Info { bool active = false; }; -struct OldP2pBehavior { - enum class EPacketShareMode { - // if the sending type is unreliable (UDP), share packets between gameserver and client - // otherwise, don't share packets - DEFAULT, - - // always share packets between gameserver and client - ALWAYS_SHARE, - - // never share packets between gameserver and client - NEVER_SHARE, - - _LAST, - }; - - static EPacketShareMode to_share_mode(int val) { - if (val < 0 || val >= (unsigned)EPacketShareMode::_LAST) { - return EPacketShareMode::DEFAULT; - } - - return (EPacketShareMode)val; - } - - EPacketShareMode mode = EPacketShareMode::DEFAULT; -}; - class Settings { private: CSteamID steam_id{}; // user id @@ -399,9 +373,6 @@ class Settings { // free weekend bool free_weekend = false; - // old P2P (ISteamNetworking) behavior - OldP2pBehavior old_p2p_behavior{}; - // voice chat bool enable_voice_chat = false; diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index fcb61dcfe..e919ff0cd 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -173,7 +173,6 @@ public ISteamClient Steam_AppTicket *steam_app_ticket{}; Steam_Overlay* steam_overlay{}; - P2p_Manager *p2p_manager{}; PlaytimeCounter* playtime_counter{}; diff --git a/dll/dll/steam_networking.h b/dll/dll/steam_networking.h index e12387fbf..b838e7fed 100644 --- a/dll/dll/steam_networking.h +++ b/dll/dll/steam_networking.h @@ -19,7 +19,11 @@ #define __INCLUDED_STEAM_NETWORKING_H__ #include "base.h" -#include "p2p_manager.hpp" + +struct Steam_Networking_Connection { + CSteamID remote{}; + std::set open_channels{}; +}; struct steam_listen_socket { SNetListenSocket_t id{}; @@ -57,25 +61,36 @@ public ISteamNetworking { class Settings *settings{}; class Networking *network{}; - class P2p_Manager *p2p_manager{}; class SteamCallBacks *callbacks{}; class RunEveryRunCB *run_every_runcb{}; + std::recursive_mutex messages_mutex{}; + std::list messages{}; + std::list unprocessed_messages{}; + + std::recursive_mutex connections_edit_mutex{}; + std::vector connections{}; 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; + 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(); static void steam_networking_callback(void *object, Common_Message *msg); - static void steam_run_every_runcb(void *object); + static void steam_networking_run_every_runcp(void *object); public: - Steam_Networking(class Settings *settings, class Networking *network, class P2p_Manager *p2p_manager, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + Steam_Networking(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); ~Steam_Networking(); //////////////////////////////////////////////////////////////////////////////////////////// diff --git a/dll/net.proto b/dll/net.proto index 6eb98e739..052531eb3 100644 --- a/dll/net.proto +++ b/dll/net.proto @@ -81,15 +81,18 @@ message Low_Level { } message Network_pb { + uint32 channel = 1; + bytes data = 2; + enum Types { DATA = 0; - FAILED_CONNECT = 1; + NEW_CONNECTION = 1; } - Types type = 1; - int32 channel = 2; - bytes data = 3; - uint32 send_type = 4; // EP2PSend + Types type = 3; + + bool processed = 128; + uint64 time_processed = 129; } message Network_Old { diff --git a/dll/network.cpp b/dll/network.cpp index 2aeaa6907..053e9469e 100644 --- a/dll/network.cpp +++ b/dll/network.cpp @@ -715,33 +715,34 @@ bool Networking::handle_announce(Common_Message *msg, IP_PORT ip_port) Common_Message msg_ = create_announce(true); size_t size = msg_.ByteSizeLong(); - std::vector buffer(size); - msg_.SerializeToArray(buffer.data(), static_cast(size)); + char *buffer = new char[size]; + msg_.SerializeToArray(buffer, static_cast(size)); IP_PORT ipp; ipp.ip = msg->announce().peers(i).ip(); ipp.port = htons(msg->announce().peers(i).udp_port()); - send_packet_to(udp_socket, ipp, buffer.data(), static_cast(size)); + send_packet_to(udp_socket, ipp, buffer, static_cast(size)); + delete[] buffer; } } conn->last_received = std::chrono::high_resolution_clock::now(); if (msg->announce().type() == Announce::PING) { - { - Common_Message msg = create_announce(false); - size_t size = msg.ByteSizeLong(); - std::vector buffer(size); - msg.SerializeToArray(buffer.data(), static_cast(size)); - send_packet_to(udp_socket, ip_port, buffer.data(), static_cast(size)); - } + Common_Message msg = create_announce(false); + size_t size = msg.ByteSizeLong(); + char *buffer = new char[size]; + msg.SerializeToArray(buffer, static_cast(size)); + send_packet_to(udp_socket, ip_port, buffer, static_cast(size)); + delete[] buffer; //send ping packet if not pinged if (!conn->udp_pinged) { Common_Message msg = create_announce(true); - size_t size = msg.ByteSizeLong(); - std::vector buffer(size); - msg.SerializeToArray(buffer.data(), static_cast(size)); - send_packet_to(udp_socket, ip_port, buffer.data(), static_cast(size)); + size_t size = msg.ByteSizeLong(); + char *buffer = new char[size]; + msg.SerializeToArray(buffer, static_cast(size)); + send_packet_to(udp_socket, ip_port, buffer, static_cast(size)); + delete[] buffer; } } else if (msg->announce().type() == Announce::PONG) { conn->udp_ip_port = ip_port; @@ -1105,15 +1106,13 @@ void Networking::Run() auto i = std::find(c.ids.begin(), c.ids.end(), steam_id); if (i != c.ids.end()) { c.ids.erase(i); - PRINT_DEBUG("REMOVE OLD USER CONNECTION ID [%llu]", steam_id.ConvertToUint64()); run_callback_user(steam_id, false, c.appid); + PRINT_DEBUG("REMOVE OLD CONNECTION ID"); } } } - for (auto &steam_id : conn.ids) { - run_callback_user(steam_id, true, conn.appid); - } + for (auto &steam_id : conn.ids) run_callback_user(steam_id, true, conn.appid); } conn.connected = true; @@ -1150,15 +1149,11 @@ void Networking::Run() auto conn = std::begin(connections); while (conn != std::end(connections)) { if (check_timedout(conn->last_received, USER_TIMEOUT + time_extra)) { - if (conn->connected) { - PRINT_DEBUG("USER TIMEOUT"); - for (auto &steam_id : conn->ids) { - run_callback_user(steam_id, false, conn->appid); - } - } + if (conn->connected) for (auto &steam_id : conn->ids) run_callback_user(steam_id, false, conn->appid); kill_tcp_socket(conn->tcp_socket_outgoing); kill_tcp_socket(conn->tcp_socket_incoming); conn = connections.erase(conn); + PRINT_DEBUG("USER TIMEOUT"); } else { ++conn; } @@ -1167,11 +1162,7 @@ void Networking::Run() for (auto &conn: connections) { if (!(conn.tcp_socket_incoming.received_data || conn.tcp_socket_outgoing.received_data)) { - if (conn.connected) { - for (auto &steam_id : conn.ids) { - run_callback_user(steam_id, false, conn.appid); - } - } + if (conn.connected) for (auto &steam_id : conn.ids) run_callback_user(steam_id, false, conn.appid); conn.connected = false; } } @@ -1321,29 +1312,15 @@ bool Networking::sendToAll(Common_Message *msg, bool reliable) void Networking::run_callbacks(Callback_Ids id, Common_Message *msg) { - const uint64 message_destination_steamid = msg->dest_id(); - - for (const auto &cb : callbacks[id].callbacks) { - const uint64 callback_allowed_steamid = cb.steam_id.ConvertToUint64(); - + for (auto &cb : callbacks[id].callbacks) { + uint64 callback_allowed_steamid = cb.steam_id.ConvertToUint64(); + uint64 message_destination_steamid = msg->dest_id(); if (callback_allowed_steamid == 0 || // callback wants to receive all messages (callback for broadcast) message_destination_steamid == 0 || // message was broadcasted to all (broadcast message) callback_allowed_steamid == message_destination_steamid) { // callback destination is the same as the message destination - - // change broadcast destination ID to a specific one - // this is required since otherwise the CSteamID of the destination would be 0 (invalid ID) - if (message_destination_steamid == 0) { - msg->set_dest_id(callback_allowed_steamid); - } - // invoke the callback cb.message_callback(cb.object, msg); } } - - // restore broadcast destination ID - if (message_destination_steamid == 0) { - msg->set_dest_id(0); - } } void Networking::run_callback_user(CSteamID steam_id, bool online, uint32 appid) diff --git a/dll/p2p_manager.cpp b/dll/p2p_manager.cpp deleted file mode 100644 index 311e51aa3..000000000 --- a/dll/p2p_manager.cpp +++ /dev/null @@ -1,734 +0,0 @@ -/* 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/p2p_manager.hpp" -#include "dll/dll.h" - - -//kingdom 2 crowns doesn't work with a 0.3 delay or lower -// appid 353090 becomes unstable when joining a lobby if the time is too low, -// it takes between ~5-12 seconds to get past the message "Waiting for our local client to connect..." !!! -constexpr static double SESSION_REQUEST_DELAY = 2.0; - -// https://partner.steamgames.com/doc/api/ISteamNetworking#SendP2PPacket -// "if we can't get through to the user after a timeout of 20 seconds, then an error will be posted" -constexpr static double SESSION_REQUEST_TIMEOUT = 20.0; - -constexpr static double PACKET_MAX_TIME_TO_LIVE = 20.0; - - - -const std::chrono::high_resolution_clock::time_point& P2p_Manager::Packet_t::get_time_created() const -{ - return time_created; -} - - -void P2p_Manager::steam_networking_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); - - auto *obj = (P2p_Manager *)object; - obj->network_callback(msg); -} - -void P2p_Manager::steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - auto *obj = (P2p_Manager *)object; - obj->periodic_callback(); -} - - - - -SteamCallBacks* P2p_Manager::get_my_callbacks(const CSteamID &my_id) const -{ - const CSteamID our_id_client = settings_client->get_local_steam_id(); - const CSteamID our_id_server = settings_server->get_local_steam_id(); - - SteamCallBacks *callbacks = nullptr; - if (my_id == our_id_client) { - PRINT_DEBUG(" using our client callbacks"); - return callbacks_client; - } else if (my_id == our_id_server) { - PRINT_DEBUG(" using our server callbacks"); - return callbacks_server; - } - - PRINT_DEBUG("[X] Id=[%llu] is not ours!", my_id.ConvertToUint64()); - return nullptr; -} - -bool P2p_Manager::is_same_peer(const CSteamID &id, const CSteamID &peer_id) const -{ - if (id == peer_id) { - return true; - } - - return false; -} - -void P2p_Manager::send_peer_session_failure(const Peer_Src_t &peer_conn) -{ - Common_Message update_msg{}; - update_msg.set_source_id(peer_conn.my_dest_id.ConvertToUint64()); - update_msg.set_dest_id(peer_conn.remote_id.ConvertToUint64()); - update_msg.set_allocated_network(new Network_pb); - - update_msg.mutable_network()->set_type(Network_pb::FAILED_CONNECT); - - PRINT_DEBUG( - "sent a connection failue packet, src id (our client/gameserver)=[%llu], dest id (peer)=[%llu]", - (uint64)update_msg.source_id(), (uint64)update_msg.dest_id() - ); - network->sendTo(&update_msg, true); -} - -void P2p_Manager::trigger_session_request(const CSteamID &remote_id, const CSteamID &my_id) -{ - PRINT_DEBUG( - "triggering session request callback for source steamid=[%llu], I am=[%llu]", - remote_id.ConvertToUint64(), my_id.ConvertToUint64() - ); - P2PSessionRequest_t data{}; - data.m_steamIDRemote = remote_id; - - get_my_callbacks(my_id)->addCBResult(data.k_iCallback, &data, sizeof(data), SESSION_REQUEST_DELAY); -} - - - - - -bool P2p_Manager::remove_connection(const CSteamID &remote_id, const CSteamID &my_id) -{ - auto rem_it = std::remove_if(connections.begin(), connections.end(), [&remote_id, &my_id, this](const Connection_t &item){ - return is_same_peer(remote_id, item.peer_conn.remote_id) - && is_same_peer(my_id, item.peer_conn.my_dest_id) - ; - }); - - if (connections.end() != rem_it) { - connections.erase(rem_it, connections.end()); - return true; - } - - return false; -} - -P2p_Manager::Connection_t* P2p_Manager::create_connection(const CSteamID &remote_id, const CSteamID &my_id) -{ - auto conn = get_connection(remote_id, my_id); - if (conn) { - return conn; - } - - Connection_t connection{}; - connection.peer_conn.remote_id = remote_id; - connection.peer_conn.my_dest_id = my_id; - - auto &conn_ref = connections.emplace_back(std::move(connection)); - PRINT_DEBUG( - "created for/them=[%llu], from/me=[%llu]", - conn_ref.peer_conn.remote_id.ConvertToUint64(), conn_ref.peer_conn.my_dest_id.ConvertToUint64() - ); - return &conn_ref; -} - -P2p_Manager::Connection_t* P2p_Manager::get_connection(const CSteamID &remote_id, const CSteamID &my_id) -{ - auto conn = std::find_if(connections.begin(), connections.end(), [&remote_id, &my_id, this](const Connection_t &item) { - return is_same_peer(remote_id, item.peer_conn.remote_id) - && is_same_peer(my_id, item.peer_conn.my_dest_id) - ; - }); - - if (connections.end() == conn) { - return nullptr; - } - - return &(*conn); -} - - - - - -P2p_Manager::P2p_Manager( - class Settings *settings_client, class Settings *settings_server, - class SteamCallBacks *callbacks_client, class SteamCallBacks *callbacks_server, - class Networking *network, class RunEveryRunCB *run_every_runcb -) -{ - this->settings_client = settings_client; - this->settings_server = settings_server; - - this->callbacks_client = callbacks_client; - this->callbacks_server = callbacks_server; - - this->network = network; - this->run_every_runcb = run_every_runcb; - - for (auto settings : { settings_client, settings_server }) { - network->setCallback(CALLBACK_ID_NETWORKING, settings->get_local_steam_id(), &P2p_Manager::steam_networking_callback, this); - network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &P2p_Manager::steam_networking_callback, this); - } - - run_every_runcb->add(&P2p_Manager::steam_run_every_runcb, this); -} - -P2p_Manager::~P2p_Manager() -{ - for (auto settings : { settings_client, settings_server }) { - network->rmCallback(CALLBACK_ID_NETWORKING, settings->get_local_steam_id(), &P2p_Manager::steam_networking_callback, this); - network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &P2p_Manager::steam_networking_callback, this); - } - - run_every_runcb->remove(&P2p_Manager::steam_run_every_runcb, this); -} - - -bool P2p_Manager::store_packet( - CSteamID my_id, CSteamID steamIDRemote, - const void *pubData, uint32 cubData, int nChannel, - EP2PSend send_type -) -{ - std::lock_guard lock(p2p_mtx); - - auto conn = create_connection(steamIDRemote, my_id); - - { - Packet_t channel_msg{}; - channel_msg.is_processed = conn->is_accepted; - channel_msg.send_type = send_type; - if (pubData && cubData > 0) { - channel_msg.data.assign((const char *)pubData, (const char *)pubData + cubData); - } - - auto &channel = conn->channels[nChannel]; - channel.packets.emplace_back(std::move(channel_msg)); - } - - PRINT_DEBUG( - "stored msg, size=[%u], from=[%llu] (connection accepted=%i), channel=[%i]", - cubData, steamIDRemote.ConvertToUint64(), (int)conn->is_accepted, nChannel - ); - - return conn->is_accepted; -} - -std::optional> P2p_Manager::get_next_packet(CSteamID my_id, int nChannel) -{ - const auto my_settings = is_same_peer(my_id, settings_client->get_local_steam_id()) - ? settings_client - : settings_server; - - for (auto conn_it = connections.begin(); connections.end() != conn_it; ++conn_it) { - if (!conn_it->is_accepted) { - continue; - } - - auto ch_it = conn_it->channels.find(nChannel); - // channel doesn't exist for this connection - if (conn_it->channels.end() == ch_it) { - continue; - } - - auto &packets = ch_it->second.packets; - auto packet_it = packets.begin(); - // channel has no packets - if (packets.end() == packet_it) { - continue; - } - - // no need to check next packets in this channel if the first one isn't processed yet - // once the connection is accepted, all messages in all channels will be marked as processed - if (!packet_it->is_processed) { - continue; - } - - const bool is_packet_for_me = is_same_peer(my_id, conn_it->peer_conn.my_dest_id); - if (!is_packet_for_me) { - bool can_share_packet = false; - - switch (my_settings->old_p2p_behavior.mode) { - default: - case OldP2pBehavior::EPacketShareMode::DEFAULT: { - // appids (353090, 301300) do this: - // - send packet from client >>> to gameserver - // - use the **client** to check for these packets - // it should use the gameserver instead - // but the client is expected to return these packets to the game - // this seems to be an old behavior - // - // on the contrary appids (701160, 248390) use k_EP2PSendReliable - // and they do not need the gameserver to share its packets with the client - // even multiplayer in appid 248390 won't work if packets were shared - can_share_packet = - packet_it->send_type == EP2PSend::k_EP2PSendUnreliable || - packet_it->send_type == EP2PSend::k_EP2PSendUnreliableNoDelay; - } - break; - - case OldP2pBehavior::EPacketShareMode::ALWAYS_SHARE: { - can_share_packet = true; - } - break; - - case OldP2pBehavior::EPacketShareMode::NEVER_SHARE: { - can_share_packet = false; - } - break; - } - - // avoid sharing packets between client and gameserver - if (!can_share_packet) { - continue; - } - } - - return std::make_tuple(conn_it, ch_it, packet_it); - } - - return {}; -} - - -bool P2p_Manager::send_packet(CSteamID my_id, CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType, int nChannel) -{ - bool reliable = false; - if (eP2PSendType == EP2PSend::k_EP2PSendReliable || eP2PSendType == EP2PSend::k_EP2PSendReliableWithBuffering) { - reliable = true; - } - - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // don't lock 2 or more mutexes at the same time - // to avoid the problem of lock ordering deadlock - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - { - std::lock_guard lock(p2p_mtx); - - auto conn = create_connection(steamIDRemote, my_id); - if (!conn->is_accepted) { - // https://partner.steamgames.com/doc/api/ISteamNetworking#AcceptP2PSessionWithUser - // "If you've called SendP2PPacket on the other user, this implicitly accepts the session request" - conn->is_accepted = true; - PRINT_DEBUG( - "auto-accepting remote connections from=[%llu], I am=[%llu]", - conn->peer_conn.remote_id.ConvertToUint64(), conn->peer_conn.my_dest_id.ConvertToUint64() - ); - } - } - - Common_Message msg{}; - msg.set_source_id(my_id.ConvertToUint64()); - msg.set_dest_id(steamIDRemote.ConvertToUint64()); - msg.set_allocated_network(new Network_pb); - - msg.mutable_network()->set_type(Network_pb::DATA); - msg.mutable_network()->set_channel(nChannel); - msg.mutable_network()->set_data(pubData, cubData); - msg.mutable_network()->set_send_type(eP2PSendType); - - bool ret = false; - { - std::lock_guard lock(global_mutex); - - ret = network->sendTo(&msg, reliable); - PRINT_DEBUG( - "Sent remote message with size=[%zu] from=[%llu] to=[%llu], is_ok=%u", - msg.network().data().size(), (uint64)msg.source_id(), (uint64)msg.dest_id(), ret - ); - } - - return ret; -} - -bool P2p_Manager::is_packet_available(CSteamID my_id, uint32 *pcubMsgSize, int nChannel) -{ - std::lock_guard lock(p2p_mtx); - - const CSteamID our_id_client = settings_client->get_local_steam_id(); - const CSteamID our_id_server = settings_server->get_local_steam_id(); - - if (pcubMsgSize) *pcubMsgSize = 0; - - auto packet_opt = get_next_packet(my_id, nChannel); - if (!packet_opt) { - return false; - } - - auto &[conn_it, ch_it, packet_it] = packet_opt.value(); - uint32 size = static_cast(packet_it->data.size()); - if (pcubMsgSize) { - *pcubMsgSize = size; - } - PRINT_DEBUG( - " available message from=[%llu], size=[%u]", - conn_it->peer_conn.remote_id.ConvertToUint64(), size - ); - - return true; -} - -bool P2p_Manager::read_packet(CSteamID my_id, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote, int nChannel) -{ - std::lock_guard lock(p2p_mtx); - - if (pcubMsgSize) *pcubMsgSize = 0; - if (psteamIDRemote) *psteamIDRemote = k_steamIDNil; - - auto packet_opt = get_next_packet(my_id, nChannel); - if (!packet_opt) { - return false; - } - - auto &[conn_it, ch_it, packet_it] = packet_opt.value(); - if (psteamIDRemote) { - *psteamIDRemote = conn_it->peer_conn.remote_id; - } - - uint32 size = static_cast(packet_it->data.size()); - if (cubDest < size) { - // https://partner.steamgames.com/doc/api/ISteamNetworking#ReadP2PPacket - // "If the cubDest buffer is too small for the packet, then the message will be truncated" - size = cubDest; - } - - if (pcubMsgSize) { - *pcubMsgSize = size; - } - - if (pubDest) { - memcpy(pubDest, packet_it->data.data(), size); - } - - ch_it->second.packets.erase(packet_it); - - PRINT_DEBUG( - " copied message from=[%llu], size=[%u]", - conn_it->peer_conn.remote_id.ConvertToUint64(), size - ); - - return true; -} - -bool P2p_Manager::close_channel(CSteamID my_id, CSteamID steamIDRemote, int nChannel) -{ - std::lock_guard lock(p2p_mtx); - - auto conn = get_connection(steamIDRemote, my_id); - if (!conn) { - PRINT_DEBUG( - "[X] no connection to remote user [%llu] was found, I am [%llu]", - steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64() - ); - return false; - } - - conn->channels.erase(nChannel); - PRINT_DEBUG( - "closed channel [%i] with remote user [%llu], I am [%llu]", - nChannel, steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64() - ); - - if (conn->channels.empty()) { - // https://partner.steamgames.com/doc/api/ISteamNetworking#CloseP2PChannelWithUser - // "Once all 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 new P2PSessionRequest_t callback." - PRINT_DEBUG( - "[?] all channels with remote user [%llu] are closed, removing connection, I am [%llu]", - steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64() - ); - remove_connection(steamIDRemote, my_id); - remove_connection(my_id, steamIDRemote); - } - - return true; -} - -bool P2p_Manager::close_session(CSteamID my_id, CSteamID steamIDRemote) -{ - std::lock_guard lock(p2p_mtx); - - const bool res_1 = remove_connection(steamIDRemote, my_id); - const bool res_2 = remove_connection(my_id, steamIDRemote); - return res_1 || res_2; -} - -bool P2p_Manager::get_session_state(CSteamID my_id, CSteamID steamIDRemote, P2PSessionState_t *pConnectionState) -{ - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // don't lock 2 or more mutexes at the same time - // to avoid the problem of lock ordering deadlock - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - { - std::lock_guard lock(p2p_mtx); - - auto conn = get_connection(steamIDRemote, my_id); - if (!conn) { - if (pConnectionState) { - pConnectionState->m_bConnectionActive = false; - pConnectionState->m_bConnecting = false; - pConnectionState->m_eP2PSessionError = EP2PSessionError::k_EP2PSessionErrorTimeout; - pConnectionState->m_bUsingRelay = false; - pConnectionState->m_nBytesQueuedForSend = 0; - pConnectionState->m_nPacketsQueuedForSend = 0; - pConnectionState->m_nRemoteIP = 0; - pConnectionState->m_nRemotePort = 0; - } - - PRINT_DEBUG( - "no connection to remote user [%llu], I am [%llu]", - steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64() - ); - return false; - } - - if (pConnectionState) { - int32 pending_packets = 0; - int32 pending_bytes = 0; - for (const auto& [ch_idx, channel] : conn->channels) { - pending_packets += (int32)channel.packets.size(); - for (const auto &msg : channel.packets) { - pending_bytes += (int32)msg.data.size(); - } - } - - pConnectionState->m_bConnectionActive = conn->is_accepted; - pConnectionState->m_bConnecting = !conn->is_accepted; - pConnectionState->m_eP2PSessionError = EP2PSessionError::k_EP2PSessionErrorNone; - pConnectionState->m_bUsingRelay = false; // TODO - pConnectionState->m_nPacketsQueuedForSend = pending_packets; - pConnectionState->m_nBytesQueuedForSend = pending_bytes; - } - } - - if (pConnectionState) { - std::lock_guard lock(global_mutex); - - pConnectionState->m_nRemoteIP = network->getIP(steamIDRemote); - pConnectionState->m_nRemotePort = network->getPort(steamIDRemote); - } - - PRINT_DEBUG( - "remote user [%llu] has a session/connection, I am [%llu]", - steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64() - ); - return true; -} - -bool P2p_Manager::accept_session(CSteamID my_id, CSteamID steamIDRemote) -{ - std::lock_guard lock(p2p_mtx); - - auto conn = get_connection(steamIDRemote, my_id); - if (!conn) { - PRINT_DEBUG("[X] no connection from=[%llu], I am=[%llu]", steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64()); - return false; - } - - if (!conn->is_accepted) { - conn->is_accepted = true; - PRINT_DEBUG("accepted new session from=[%llu], I am=[%llu]", steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64()); - } - return true; -} - - - - -void P2p_Manager::periodic_handle_connections(const std::chrono::high_resolution_clock::time_point &now) -{ - auto conn_it = connections.begin(); - while (connections.end() != conn_it) { - bool is_remove = false; - if (!conn_it->is_accepted) { - if (check_timedout(conn_it->time_added, SESSION_REQUEST_TIMEOUT, now)) { - is_remove = true; - send_peer_session_failure(conn_it->peer_conn); - } - } - - if (is_remove) { - conn_it = connections.erase(conn_it); - } else { - ++conn_it; - } - } -} - -void P2p_Manager::periodic_handle_channels(const std::chrono::high_resolution_clock::time_point &now) -{ - for (auto &conn : connections) { - if (!conn.is_accepted) { - continue; - } - - // remove channels with no packets - auto ch_it = conn.channels.begin(); - while (conn.channels.end() != ch_it) { - auto &channel = ch_it->second; - if (channel.packets.empty()) { - ch_it = conn.channels.erase(ch_it); - } else { - ++ch_it; - } - } - } -} - -void P2p_Manager::periodic_handle_packets(const std::chrono::high_resolution_clock::time_point &now) -{ - for (auto &conn : connections) { - if (!conn.is_accepted) { - continue; - } - - for (auto &[ch_num, channel] : conn.channels) { - // remove outadated packets, and mark the rest as processed - auto packet_it = channel.packets.begin(); - while (channel.packets.end() != packet_it) { - bool is_remove = false; - if (check_timedout(packet_it->get_time_created(), PACKET_MAX_TIME_TO_LIVE, now)) { - is_remove = true; - } else { - packet_it->is_processed = true; - } - - if (is_remove) { - packet_it = channel.packets.erase(packet_it); - } else { - ++packet_it; - } - } - } - } -} - -void P2p_Manager::periodic_callback() -{ - std::lock_guard lock(p2p_mtx); - - auto now = std::chrono::high_resolution_clock::now(); - periodic_handle_connections(now); - periodic_handle_channels(now); - periodic_handle_packets(now); -} - - - - -void P2p_Manager::network_data_packets(Common_Message *msg) -{ - const CSteamID src_id = (uint64)msg->source_id(); - const CSteamID my_dest_id = (uint64)msg->dest_id(); // this is us - - PRINT_DEBUG("got network msg from [%llu], I am [%llu], type <%u>", - src_id.ConvertToUint64(), my_dest_id.ConvertToUint64(), msg->network().type() - ); - - switch (msg->network().type()) { - case Network_pb::DATA: { - PRINT_DEBUG("got network data message"); - const bool conn_is_accepted = store_packet( - my_dest_id, src_id, - msg->network().data().c_str(), (uint32)msg->network().data().size(), - (int)msg->network().channel(), - (EP2PSend)msg->network().send_type() - ); - if (!conn_is_accepted) { - trigger_session_request(src_id, my_dest_id); - } - } - break; - - case Network_pb::FAILED_CONNECT: { - PRINT_DEBUG("[X] got connection failure packet"); - P2PSessionConnectFail_t data{}; - data.m_steamIDRemote = src_id; - data.m_eP2PSessionError = EP2PSessionError::k_EP2PSessionErrorTimeout; - - get_my_callbacks(my_dest_id)->addCBResult(data.k_iCallback, &data, sizeof(data)); - - { - std::lock_guard lock(p2p_mtx); - - remove_connection(src_id, my_dest_id); - remove_connection(my_dest_id, src_id); - } - } - break; - } -} - -void P2p_Manager::network_low_level(Common_Message *msg) -{ - const CSteamID src_id = (uint64)msg->source_id(); - const CSteamID my_dest_id = (uint64)msg->dest_id(); // this is us - - switch (msg->low_level().type()) { - case Low_Level::CONNECT: { - - } - break; - - case Low_Level::DISCONNECT: { - bool any_conn_removed = false; - { - std::lock_guard lock(p2p_mtx); - - any_conn_removed |= remove_connection(src_id, my_dest_id); - any_conn_removed |= remove_connection(my_dest_id, src_id); - } - - if (any_conn_removed) { - PRINT_DEBUG( - "[X] remote user [%llu] disconnected, sending P2PSessionConnectFail_t, I am [%llu]", - src_id.ConvertToUint64(), my_dest_id.ConvertToUint64() - ); - P2PSessionConnectFail_t data{}; - data.m_steamIDRemote = src_id; - data.m_eP2PSessionError = k_EP2PSessionErrorDestinationNotLoggedIn; - - get_my_callbacks(my_dest_id)->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - - } - break; - } -} - -void P2p_Manager::network_callback(Common_Message *msg) -{ - if (msg->has_network()) { - network_data_packets(msg); - } - - if (msg->has_low_level()) { - network_low_level(msg); - } -} diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 33e8d13cd..362242961 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -1583,13 +1583,6 @@ static void parse_simple_features(class Settings *settings_client, class Setting settings_client->download_steamhttp_requests = ini.GetBoolValue("main::connectivity", "download_steamhttp_requests", settings_client->download_steamhttp_requests); settings_server->download_steamhttp_requests = ini.GetBoolValue("main::connectivity", "download_steamhttp_requests", settings_server->download_steamhttp_requests); - settings_client->old_p2p_behavior.mode = OldP2pBehavior::to_share_mode( - (int)ini.GetLongValue("main::connectivity", "old_p2p_packet_sharing_mode", (unsigned)settings_client->old_p2p_behavior.mode) - ); - settings_server->old_p2p_behavior.mode = OldP2pBehavior::to_share_mode( - (int)ini.GetLongValue("main::connectivity", "old_p2p_packet_sharing_mode", (unsigned)settings_server->old_p2p_behavior.mode) - ); - // [main::misc] settings_client->achievement_bypass = ini.GetBoolValue("main::misc", "achievements_bypass", settings_client->achievement_bypass); diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index 22c135f3b..fe6170c63 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -105,17 +105,12 @@ Steam_Client::Steam_Client() steam_utils = new Steam_Utils(settings_client, callback_results_client, callbacks_client, steam_overlay); ugc_bridge = new Ugc_Remote_Storage_Bridge(settings_client); - p2p_manager = new P2p_Manager( - settings_client, settings_server, - callbacks_client, callbacks_server, - network, run_every_runcb - ); steam_matchmaking = new Steam_Matchmaking(settings_client, local_storage, network, callback_results_client, callbacks_client, run_every_runcb); steam_matchmaking_servers = new Steam_Matchmaking_Servers(settings_client, local_storage, network); steam_user_stats = new Steam_User_Stats(settings_client, network, local_storage, callback_results_client, callbacks_client, run_every_runcb, steam_overlay); steam_apps = new Steam_Apps(settings_client, callback_results_client, callbacks_client); - steam_networking = new Steam_Networking(settings_client, network, p2p_manager, callbacks_client, run_every_runcb); + steam_networking = new Steam_Networking(settings_client, network, callbacks_client, run_every_runcb); steam_remote_storage = new Steam_Remote_Storage(settings_client, ugc_bridge, local_storage, callback_results_client, callbacks_client, run_every_runcb); steam_screenshots = new Steam_Screenshots(local_storage, callbacks_client); steam_http = new Steam_HTTP(settings_client, network, callback_results_client, callbacks_client); @@ -150,7 +145,7 @@ Steam_Client::Steam_Client() steam_gameserver_user = new Steam_User(settings_server, local_storage, network, callback_results_server, callbacks_server); steam_gameserver_utils = new Steam_Utils(settings_server, callback_results_server, callbacks_server, steam_overlay); steam_gameserverstats = new Steam_GameServerStats(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); - steam_gameserver_networking = new Steam_Networking(settings_server, network, p2p_manager, callbacks_server, run_every_runcb); + steam_gameserver_networking = new Steam_Networking(settings_server, network, callbacks_server, run_every_runcb); steam_gameserver_http = new Steam_HTTP(settings_server, network, callback_results_server, callbacks_server); steam_gameserver_inventory = new Steam_Inventory(settings_server, callback_results_server, callbacks_server, run_every_runcb, local_storage); steam_gameserver_ugc = new Steam_UGC(settings_server, ugc_bridge, local_storage, callback_results_server, callbacks_server); @@ -237,7 +232,6 @@ Steam_Client::~Steam_Client() DEL_INST(steam_app_disable_update); DEL_INST(steam_billing); - DEL_INST(p2p_manager); DEL_INST(ugc_bridge); DEL_INST(steam_utils); @@ -959,6 +953,9 @@ void Steam_Client::RunCallbacks(bool runClientCB, bool runGameserverCB) // PRINT_DEBUG("steam_matchmaking_servers *********"); steam_matchmaking_servers->RunCallbacks(); + + // PRINT_DEBUG("run_every_runcb *********"); + run_every_runcb->run(); // PRINT_DEBUG("steam_gameserver *********"); steam_gameserver->RunCallbacks(); @@ -966,9 +963,6 @@ void Steam_Client::RunCallbacks(bool runClientCB, bool runGameserverCB) // PRINT_DEBUG("steam_user *********"); steam_gameserver_user->RunCallbacks(); - // PRINT_DEBUG("run_every_runcb *********"); - run_every_runcb->run(); - if (runClientCB) { // PRINT_DEBUG("callback_results_client *********"); callback_results_client->runCallResults(); diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index c883b1e70..114b0e04a 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -20,9 +20,6 @@ #define SEND_SERVER_RATE 5.0 -// appid 353090 takes ~1.2-1.4 sec to create the gameserver -#define LOGON_DELAY 1.3 - void Steam_GameServer::set_version(const char *pchVersionString) { @@ -844,14 +841,14 @@ void Steam_GameServer::RunCallbacks() if (temp_call_servers_connected) { PRINT_DEBUG("SteamServersConnected_t"); SteamServersConnected_t data{}; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), LOGON_DELAY); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); } if (logged_in && !policy_response_called) { PRINT_DEBUG("GSPolicyResponse_t"); GSPolicyResponse_t data{}; data.m_bSecure = !!(flags & k_unServerFlagSecure); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), LOGON_DELAY); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.11); policy_response_called = true; } diff --git a/dll/steam_matchmaking.cpp b/dll/steam_matchmaking.cpp index 0dc8689dd..766020639 100644 --- a/dll/steam_matchmaking.cpp +++ b/dll/steam_matchmaking.cpp @@ -23,14 +23,11 @@ #define REQUEST_LOBBY_DATA_TIMEOUT 6.0 #define LOBBY_DELETED_TIMEOUT 2 -// appid 353090 takes ~100ms to create the lobby -#define LOBBY_CREATE_DELAY 0.2 +#define LOBBY_CREATE_DELAY 0.07 //artificial delay for lobby creation #define FILTER_MAX_DEFAULT 4096 -// https://partner.steamgames.com/doc/api/ISteamMatchmaking#RequestLobbyList -// "this call can take from 300ms to 5 seconds to complete, and has a timeout of 20 seconds." -#define LOBBY_SEARCH_TIMEOUT 1.0 +#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) diff --git a/dll/steam_networking.cpp b/dll/steam_networking.cpp index 7ae6abd76..d2185c4e6 100644 --- a/dll/steam_networking.cpp +++ b/dll/steam_networking.cpp @@ -16,13 +16,79 @@ . */ #include "dll/steam_networking.h" -#include "dll/dll.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; @@ -107,7 +173,7 @@ void Steam_Networking::steam_networking_callback(void *object, Common_Message *m steam_networking->Callback(msg); } -void Steam_Networking::steam_run_every_runcb(void *object) +void Steam_Networking::steam_networking_run_every_runcp(void *object) { // PRINT_DEBUG_ENTRY(); @@ -115,26 +181,25 @@ void Steam_Networking::steam_run_every_runcb(void *object) steam_networking->RunCallbacks(); } -Steam_Networking::Steam_Networking(class Settings *settings, class Networking *network, class P2p_Manager *p2p_manager, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) +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->p2p_manager = p2p_manager; 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_run_every_runcb, this); + this->run_every_runcb->add(&Steam_Networking::steam_networking_run_every_runcp, this); - PRINT_DEBUG("user id %llu", settings->get_local_steam_id().ConvertToUint64()); + 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_run_every_runcb, this); + this->run_every_runcb->remove(&Steam_Networking::steam_networking_run_every_runcp, this); } //////////////////////////////////////////////////////////////////////////////////////////// @@ -152,15 +217,31 @@ Steam_Networking::~Steam_Networking() // 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( - "size=[%u] sendtype: <%u> channel: [%u] from=[%llu] to=[%llu]", - cubData, eP2PSendType, nChannel, settings->get_local_steam_id().ConvertToUint64(), steamIDRemote.ConvertToUint64() - ); + 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); + } - return p2p_manager->send_packet( - settings->get_local_steam_id(), steamIDRemote, - pubData, cubData, eP2PSendType, nChannel - ); + 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 ) @@ -172,20 +253,26 @@ bool Steam_Networking::SendP2PPacket( CSteamID steamIDRemote, const void *pubDat // 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], my steam id=%llu (is server=%u)", - nChannel, settings->get_local_steam_id().ConvertToUint64(), settings->get_local_steam_id().BGameServerAccount() - ); - + 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(); - return p2p_manager->is_packet_available( - settings->get_local_steam_id(), - pcubMsgSize, nChannel - ); + 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 = static_cast(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) @@ -200,17 +287,38 @@ bool Steam_Networking::IsP2PPacketAvailable( uint32 *pcubMsgSize) // 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 %p", cubDest, nChannel, pubDest); - + 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(); - return p2p_manager->read_packet( - settings->get_local_steam_id(), - pubDest, cubDest, pcubMsgSize, psteamIDRemote, nChannel - ); + 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 = static_cast(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) @@ -227,15 +335,11 @@ bool Steam_Networking::ReadP2PPacket( void *pubDest, uint32 cubDest, uint32 *pcu // (if you've called SendP2PPacket() on the other user, this implicitly accepts the session request) bool Steam_Networking::AcceptP2PSessionWithUser( CSteamID steamIDRemote ) { - PRINT_DEBUG("from [%llu], I am=[%llu]", steamIDRemote.ConvertToUint64(), settings->get_local_steam_id().ConvertToUint64()); - - if (p2p_manager->accept_session(settings->get_local_steam_id().ConvertToUint64(), steamIDRemote)) { - PRINT_DEBUG(" connection accepted"); - return true; - } - - PRINT_DEBUG(" [X] connection NOT accepted"); - return false; + 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; } @@ -244,8 +348,15 @@ bool Steam_Networking::AcceptP2PSessionWithUser( CSteamID steamIDRemote ) bool Steam_Networking::CloseP2PSessionWithUser( CSteamID steamIDRemote ) { PRINT_DEBUG("%llu", steamIDRemote.ConvertToUint64()); + std::lock_guard lock(global_mutex); + if (!connection_exists(steamIDRemote)) { + + return false; + } - return p2p_manager->close_session(settings->get_local_steam_id().ConvertToUint64(), steamIDRemote); + remove_connection(steamIDRemote); + + return true; } @@ -254,9 +365,20 @@ bool Steam_Networking::CloseP2PSessionWithUser( CSteamID steamIDRemote ) // user will trigger a P2PSessionRequest_t callback bool Steam_Networking::CloseP2PChannelWithUser( CSteamID steamIDRemote, int nChannel ) { - PRINT_DEBUG("%llu", steamIDRemote.ConvertToUint64()); + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!connection_exists(steamIDRemote)) { + return false; + } - return p2p_manager->close_channel(settings->get_local_steam_id().ConvertToUint64(), steamIDRemote, nChannel); + 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; } @@ -266,8 +388,36 @@ bool Steam_Networking::CloseP2PChannelWithUser( CSteamID steamIDRemote, int nCha bool Steam_Networking::GetP2PSessionState( CSteamID steamIDRemote, P2PSessionState_t *pConnectionState ) { PRINT_DEBUG("%llu", steamIDRemote.ConvertToUint64()); - - return p2p_manager->get_session_state(settings->get_local_steam_id().ConvertToUint64(), steamIDRemote, pConnectionState); + 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; } @@ -278,7 +428,6 @@ bool Steam_Networking::GetP2PSessionState( CSteamID steamIDRemote, P2PSessionSta // P2P packet relay is allowed by default bool Steam_Networking::AllowP2PPacketRelay( bool bAllow ) { - PRINT_DEBUG_TODO(); PRINT_DEBUG("%u", bAllow); return true; } @@ -620,18 +769,115 @@ int Steam_Networking::GetMaxPacketSize( SNetSocket_t hSocket ) 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 old network socket msg %u", msg->network_old().type()); + 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()) { @@ -698,9 +944,17 @@ void Steam_Networking::Callback(Common_Message *msg) 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{}; + struct SocketStatusCallback_t data; socket.status = SOCKET_DISCONNECTED; data.m_hSocket = socket.id; data.m_hListenSocket = socket.listen_id; @@ -709,7 +963,9 @@ void Steam_Networking::Callback(Common_Message *msg) callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); } } - } else if (msg->low_level().type() == Low_Level::CONNECT) { + } else + + if (msg->low_level().type() == Low_Level::CONNECT) { } } diff --git a/post_build/steam_settings.EXAMPLE/configs.main.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.main.EXAMPLE.ini index 596861ad7..d92030b1c 100644 --- a/post_build/steam_settings.EXAMPLE/configs.main.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.main.EXAMPLE.ini @@ -125,19 +125,6 @@ disable_lobby_creation=0 # this will **not** work if the app is using native/OS web APIs # default=0 download_steamhttp_requests=0 -# sharing mode of the old P2P networking interface (ISteamNetworking). -# this defines how the gameserver and the client intances can read P2P packets received from other players. -# -# some older games require sharing the received packets, i.e if the gameserver instance received a packet, -# then the client instance should be able to detect/read it as well. -# other games won't work properly if the packets were shared, -# and the original receiver (either gameserver or client) must handle that packet -# -# 0=share packets flagged as "unreliable" (k_EP2PSendUnreliable | k_EP2PSendUnreliableNoDelay), otherwise don't share packets -# 1=always share the packets -# 2=never share the packets -# default=0 -old_p2p_packet_sharing_mode=0 # mostly workarounds for specific problems [main::misc] From d3ef427a36d6c5839231297d207813fa6a856e5d Mon Sep 17 00:00:00 2001 From: GogoVang <46242309+GogoVang@users.noreply.github.com> Date: Thu, 15 Jan 2026 14:27:54 +0300 Subject: [PATCH 004/159] disable_achievement_progress=1 --- post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini index 4831793c0..0126a369f 100644 --- a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini @@ -27,8 +27,8 @@ disable_achievement_notification=0 # default=0 disable_friend_notification=0 # 1=disable showing notifications for achievements progress -# default=0 -disable_achievement_progress=0 +# default=1 +disable_achievement_progress=1 # 1=disable any warning in the overlay # default=0 disable_warning_any=0 From a52b15b236f8e52363eb75eab375d4ea0011ff84 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Tue, 27 Jan 2026 18:07:23 +0100 Subject: [PATCH 005/159] update 3rdparty and win premake buildscript --- build_win_premake.bat | 8 ++++---- third-party/common/linux | 2 +- third-party/common/win | 2 +- third-party/deps/linux | 2 +- third-party/deps/win | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build_win_premake.bat b/build_win_premake.bat index 9968af8b9..d2bf9b545 100644 --- a/build_win_premake.bat +++ b/build_win_premake.bat @@ -41,8 +41,8 @@ set /a "GEN_PROJECT=1" :: build deps if %BUILD_DEPS% equ 1 ( - set "CMAKE_GENERATOR=Visual Studio 17 2022" - call "%PREMAKE_EXE%" --file="premake5-deps.lua" --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=windows vs2022 || ( + set "CMAKE_GENERATOR=Visual Studio 2026" + call "%PREMAKE_EXE%" --file="premake5-deps.lua" --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=windows vs2026 || ( goto :end_script_with_err ) goto :end_script @@ -69,13 +69,13 @@ set /a "GEN_PROJECT=1" if %GEN_PROJECT% equ 0 ( goto :gen_project_end ) - call "%PREMAKE_EXE%" --file="premake5.lua" --genproto --dosstub --winrsrc --winsign --os=windows vs2022 || ( + call "%PREMAKE_EXE%" --file="premake5.lua" --genproto --dosstub --winrsrc --winsign --os=windows vs2026 || ( goto :end_script_with_err ) :gen_project_end :: check .sln - set "SLN_FILE=build\project\vs2022\win\gbe.sln" + set "SLN_FILE=build\project\vs2022\win\gbe.slnx" if not exist "%SLN_FILE%" ( 1>&2 echo:.sln file wasn't found goto :end_script_with_err diff --git a/third-party/common/linux b/third-party/common/linux index 890cb007c..c4f721f26 160000 --- a/third-party/common/linux +++ b/third-party/common/linux @@ -1 +1 @@ -Subproject commit 890cb007c168faf132ed7673a18486c1512f5720 +Subproject commit c4f721f26e8f7c88d136787cf21b5e66ed5f8470 diff --git a/third-party/common/win b/third-party/common/win index c40455760..73e5ef5c6 160000 --- a/third-party/common/win +++ b/third-party/common/win @@ -1 +1 @@ -Subproject commit c40455760bf690550be7240b7482884250215188 +Subproject commit 73e5ef5c67b57c182657b9290141c4e8b5f466db diff --git a/third-party/deps/linux b/third-party/deps/linux index 0f64de912..4f9129b3a 160000 --- a/third-party/deps/linux +++ b/third-party/deps/linux @@ -1 +1 @@ -Subproject commit 0f64de91256ea576a6aa087a71284ec8330d78e3 +Subproject commit 4f9129b3a9df778fdd483af31c252621dc36b272 diff --git a/third-party/deps/win b/third-party/deps/win index 107d27376..4edbf85d2 160000 --- a/third-party/deps/win +++ b/third-party/deps/win @@ -1 +1 @@ -Subproject commit 107d27376e491a1fa1dee2b18b27b9446c725d6f +Subproject commit 4edbf85d277c8b44b2a82d1c88ac0fa9a609c9c6 From 4baaddca9d2e0b7c56bf18d9f838bd489c266a54 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Tue, 27 Jan 2026 18:26:52 +0100 Subject: [PATCH 006/159] Fix error in lobbyconnect Re-add some qol lobby connect stuff --- dll/settings_parser_ufs.cpp | 2 ++ tools/lobby_connect/lobby_connect.cpp | 26 ++++++++++---------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/dll/settings_parser_ufs.cpp b/dll/settings_parser_ufs.cpp index 5d4be09db..37774befa 100644 --- a/dll/settings_parser_ufs.cpp +++ b/dll/settings_parser_ufs.cpp @@ -251,6 +251,7 @@ static std::filesystem::path factory_default_cloud_dir(CSimpleIniA *ini, class S // app::cloud_save void parse_cloud_save(CSimpleIniA *ini, class Settings *settings_client, class Settings *settings_server, class Local_Storage *local_storage) { +#ifndef LOBBY_CONNECT constexpr static bool DEFAULT_CREATE_DEFAULT_DIR = true; constexpr static bool DEFAULT_CREATE_SPECIFIC_DIRS = true; constexpr static const char SPECIFIC_INI_KEY[] = @@ -317,4 +318,5 @@ void parse_cloud_save(CSimpleIniA *ini, class Settings *settings_client, class S PRINT_DEBUG(" [X] cloud save dir has unprocessed identifiers, skipping"); } } +#endif // LOBBY_CONNECT } diff --git a/tools/lobby_connect/lobby_connect.cpp b/tools/lobby_connect/lobby_connect.cpp index 2bc6780e7..f2a631634 100644 --- a/tools/lobby_connect/lobby_connect.cpp +++ b/tools/lobby_connect/lobby_connect.cpp @@ -64,41 +64,35 @@ int main() { std::this_thread::sleep_for(std::chrono::milliseconds(200)); } + title(); + int friend_count = SteamFriends()->GetFriendCount(k_EFriendFlagAll); - /* std::cout << "People on the network: " << friend_count << "\n"; - for (int i = 0; i < friend_count; ++i) { - CSteamID id = SteamFriends()->GetFriendByIndex(i, k_EFriendFlagAll); - const char *name = SteamFriends()->GetFriendPersonaName(id); - - FriendGameInfo_t friend_info = {}; - SteamFriends()->GetFriendGamePlayed(id, &friend_info); - std::cout << name << " is playing: " << friend_info.m_gameID.AppID() << std::endl; - } - */ - - title(); - std::vector> arguments; for (int i = 0; i < friend_count; ++i) { CSteamID id = SteamFriends()->GetFriendByIndex(i, k_EFriendFlagAll); const char *name = SteamFriends()->GetFriendPersonaName(id); const char *connect = SteamFriends()->GetFriendRichPresence( id, "connect"); FriendGameInfo_t friend_info = {}; - SteamFriends()->GetFriendGamePlayed(id, &friend_info); + bool isSuccess = SteamFriends()->GetFriendGamePlayed(id, &friend_info); auto appid = friend_info.m_gameID.AppID(); if (strlen(connect) > 0) { std::cout << arguments.size() << " - " << name << " is playing " << appid << " (" << connect << ").\n"; arguments.emplace_back(connect, appid); - } else { - if (friend_info.m_steamIDLobby != k_steamIDNil) { + } else if (isSuccess && friend_info.m_steamIDLobby != k_steamIDNil) { std::string connect = "+connect_lobby " + std::to_string(friend_info.m_steamIDLobby.ConvertToUint64()); std::cout << arguments.size() << " - " << name << " is playing " << appid << " (" << connect << ").\n"; arguments.emplace_back(connect, appid); } } + else if (isSuccess) { + std::cout << name << " is playing " << appid << std::endl; + } + else { + std::cout << name << " is found in network " << std::endl; + } } std::cout << arguments.size() << " - Refresh.\n\n"; From 2444ed4b78fc943beab4e023a940caf778724fd6 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Tue, 27 Jan 2026 18:33:32 +0100 Subject: [PATCH 007/159] Fix workflows, readme and buildscript --- .github/workflows/emu-build-all-win.yml | 6 +- .github/workflows/emu-deps-win.yml | 6 +- .github/workflows/release.yml | 8 +- README.md | 16 +- build_win_premake.bat | 240 ++++++++++++------------ package_win_debug.bat | 6 +- package_win_release.bat | 6 +- 7 files changed, 144 insertions(+), 144 deletions(-) diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 4cdc3ada9..372a461e3 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -10,7 +10,7 @@ permissions: contents: "write" env: - PREMAKE_ACTION: "vs2022" + PREMAKE_ACTION: "vs2026" DEPS_CACHE_KEY: "emu-deps-win" DEPS_CACHE_DIR: "build/deps/win" @@ -100,12 +100,12 @@ jobs: shell: "cmd" working-directory: "${{ github.workspace }}" run: | - "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5.lua --genproto --emubuild=${{ github.sha }} --dosstub --winrsrc --winsign --os=windows vs2022 + "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5.lua --genproto --emubuild=${{ github.sha }} --dosstub --winrsrc --winsign --os=windows vs2026 # build target - name: "Build target" shell: "cmd" - working-directory: "${{ github.workspace }}/build/project/vs2022/win" + working-directory: "${{ github.workspace }}/build/project/vs2026/win" run: | msbuild /nologo /target:${{ matrix.prj }} /m:2 /v:n /p:Configuration=${{ matrix.cfg }},Platform=${{ matrix.arch }} gbe.sln diff --git a/.github/workflows/emu-deps-win.yml b/.github/workflows/emu-deps-win.yml index 4daec42c8..81b4d9026 100644 --- a/.github/workflows/emu-deps-win.yml +++ b/.github/workflows/emu-deps-win.yml @@ -10,7 +10,7 @@ permissions: contents: "write" env: - PREMAKE_ACTION: "vs2022" + PREMAKE_ACTION: "vs2026" DEPS_CACHE_KEY: "emu-deps-win" DEPS_CACHE_DIR: "build/deps/win" @@ -78,5 +78,5 @@ jobs: shell: "cmd" working-directory: "${{ github.workspace }}" run: | - set "CMAKE_GENERATOR=Visual Studio 17 2022" - "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=windows vs2022 + set "CMAKE_GENERATOR=Visual Studio 2026" + "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=windows vs2026 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 854a0877a..a2beae1e2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,13 +61,13 @@ jobs: - name: "Package build (release)" shell: "cmd" working-directory: "${{ github.workspace }}" - run: package_win.bat vs2022\release + run: package_win.bat vs2026\release # package (debug mode) - name: "Package build (debug)" shell: "cmd" working-directory: "${{ github.workspace }}" - run: package_win.bat vs2022\debug 1 + run: package_win.bat vs2026\debug 1 # release (debug + release modes) if this is a tag push - name: "Release" @@ -82,7 +82,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: "release-emu-win-release-${{ github.sha }}" - path: "build/package/win/vs2022/*release*" + path: "build/package/win/vs2026/*release*" if-no-files-found: "error" compression-level: 0 retention-days: 7 @@ -92,7 +92,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: "release-emu-win-debug-${{ github.sha }}" - path: "build/package/win/vs2022/*debug*" + path: "build/package/win/vs2026/*debug*" if-no-files-found: "error" compression-level: 0 retention-days: 7 diff --git a/README.md b/README.md index bd940ebb5..076754432 100644 --- a/README.md +++ b/README.md @@ -142,8 +142,8 @@ The only times you'll need to rebuild them is either when their separete build f Open CMD in the repo folder, then run the following * To build using `Visual Studio` ```batch - set "CMAKE_GENERATOR=Visual Studio 17 2022" - third-party\common\win\premake\premake5.exe --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --verbose --os=windows vs2022 + set "CMAKE_GENERATOR=Visual Studio 2026" + third-party\common\win\premake\premake5.exe --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --verbose --os=windows vs2026 ``` * To build using `MSYS2` **this is currently experimental and will not work due to ABI differences**
@@ -194,14 +194,14 @@ This will: Open CMD in the repo folder, then run the following * For `Visual Studio 2022` ```batch - third-party\common\win\premake\premake5.exe --file=premake5.lua --genproto --os=windows vs2022 + third-party\common\win\premake\premake5.exe --file=premake5.lua --genproto --os=windows vs2026 ``` - You can then go to the folder `build\project\vs2022\win` and open the produced `.sln` file in Visual Studio. - Or, if you prefer to do it from command line, open the `Developer Command Prompt for VS 2022` inside the above folder, then: + You can then go to the folder `build\project\vs2026\win` and open the produced `.sln` file in Visual Studio. + Or, if you prefer to do it from command line, open the `Developer Command Prompt for VS 2026` inside the above folder, then: ```batch - msbuild /nologo /v:n /p:Configuration=release,Platform=Win32 gbe.sln + msbuild /nologo /v:n /p:Configuration=release,Platform=Win32 gbe.slnx - msbuild /nologo /v:n /p:Configuration=release,Platform=x64 gbe.sln + msbuild /nologo /v:n /p:Configuration=release,Platform=x64 gbe.slnx ``` * For `MSYS2` **this is currently experimental and will not work due to ABI differences** @@ -310,7 +310,7 @@ Open CMD in the repos's directory, then run this script ```batch package_win.bat ``` -`build_folder` is any folder inside `build\win`, for example: `vs2022\release` +`build_folder` is any folder inside `build\win`, for example: `6\release` The above example will create a `.7z` archive inside `build\package\win\` ### On Linux: diff --git a/build_win_premake.bat b/build_win_premake.bat index d2bf9b545..e09945a06 100644 --- a/build_win_premake.bat +++ b/build_win_premake.bat @@ -1,120 +1,120 @@ -@echo off -setlocal EnableDelayedExpansion -cd /d "%~dp0" - -set /a "MAX_THREADS=2" -if defined NUMBER_OF_PROCESSORS ( - :: use 70% - set /a "MAX_THREADS=%NUMBER_OF_PROCESSORS% * 70 / 100" - if %MAX_THREADS% lss 1 ( - set /a "MAX_THREADS=1" - ) -) - -set /a "BUILD_DEPS=0" -set /a "GEN_PROJECT=1" - -:args_loop - if "%~1" equ "" ( - goto :args_loop_end - ) else if "%~1" equ "--deps" ( - set /a "BUILD_DEPS=1" - ) else if "%~1" equ "--nogen" ( - set /a "GEN_PROJECT=0" - ) else if "%~1" equ "--help" ( - goto :help_page - ) else ( - 1>&2 echo:invalid arg %~1 - goto :end_script_with_err - ) - - shift /1 - goto :args_loop - -:args_loop_end - :: check premake - set "PREMAKE_EXE=third-party\common\win\premake\premake5.exe" - if not exist "%PREMAKE_EXE%" ( - 1>&2 echo:premake wasn't found - goto :end_script_with_err - ) - - :: build deps - if %BUILD_DEPS% equ 1 ( - set "CMAKE_GENERATOR=Visual Studio 2026" - call "%PREMAKE_EXE%" --file="premake5-deps.lua" --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=windows vs2026 || ( - goto :end_script_with_err - ) - goto :end_script - ) - - :: check vswhere - set "VSWHERE_EXE=third-party\common\win\vswhere\vswhere.exe" - if not exist "%VSWHERE_EXE%" ( - 1>&2 echo:vswhere wasn't found - goto :end_script_with_err - ) - - :: check msbuild - set "MSBUILD_EXE=" - for /f "tokens=* delims=" %%A in ('"%VSWHERE_EXE%" -prerelease -latest -nocolor -nologo -property installationPath 2^>nul') do ( - set "MSBUILD_EXE=%%~A\MSBuild\Current\Bin\MSBuild.exe" - ) - if not exist "%MSBUILD_EXE%" ( - 1>&2 echo:MSBuild wasn't found - goto :end_script_with_err - ) - - :: create .sln - if %GEN_PROJECT% equ 0 ( - goto :gen_project_end - ) - call "%PREMAKE_EXE%" --file="premake5.lua" --genproto --dosstub --winrsrc --winsign --os=windows vs2026 || ( - goto :end_script_with_err - ) -:gen_project_end - - :: check .sln - set "SLN_FILE=build\project\vs2022\win\gbe.slnx" - if not exist "%SLN_FILE%" ( - 1>&2 echo:.sln file wasn't found - goto :end_script_with_err - ) - - :: build .sln - set "BUILD_TYPES=release debug" - set "BUILD_PLATFORMS=x64 Win32" - set "BUILD_TARGETS=api_regular api_experimental steamclient_experimental_stub steamclient_experimental steamclient_experimental_loader steamclient_experimental_extra lib_game_overlay_renderer tool_lobby_connect tool_generate_interfaces" - - for %%A in (%BUILD_TYPES%) do ( - set "BUILD_TYPE=%%A" - for %%B in (%BUILD_PLATFORMS%) do ( - set "BUILD_PLATFORM=%%B" - for %%C in (%BUILD_TARGETS%) do ( - set "BUILD_TARGET=%%C" - echo. & echo:building !BUILD_TARGET! !BUILD_TYPE! !BUILD_PLATFORM! - call "%MSBUILD_EXE%" /nologo -m:%MAX_THREADS% -v:n /p:Configuration=!BUILD_TYPE!,Platform=!BUILD_PLATFORM! /target:!BUILD_TARGET! "%SLN_FILE%" || ( - goto :end_script_with_err - ) - ) - ) - ) - - goto :end_script - -:end_script - endlocal - exit /b 0 - -:end_script_with_err - endlocal - exit /b 1 - -:: show help page -:help_page - echo:"%~nx0" [switches] - echo:switches: - echo: --deps: rebuild third-party dependencies - echo: --nogen: don't regenerate build files - echo: --help: show this page - goto :end_script +@echo off +setlocal EnableDelayedExpansion +cd /d "%~dp0" + +set /a "MAX_THREADS=2" +if defined NUMBER_OF_PROCESSORS ( + :: use 70% + set /a "MAX_THREADS=%NUMBER_OF_PROCESSORS% * 70 / 100" + if %MAX_THREADS% lss 1 ( + set /a "MAX_THREADS=1" + ) +) + +set /a "BUILD_DEPS=0" +set /a "GEN_PROJECT=1" + +:args_loop + if "%~1" equ "" ( + goto :args_loop_end + ) else if "%~1" equ "--deps" ( + set /a "BUILD_DEPS=1" + ) else if "%~1" equ "--nogen" ( + set /a "GEN_PROJECT=0" + ) else if "%~1" equ "--help" ( + goto :help_page + ) else ( + 1>&2 echo:invalid arg %~1 + goto :end_script_with_err + ) + + shift /1 + goto :args_loop + +:args_loop_end + :: check premake + set "PREMAKE_EXE=third-party\common\win\premake\premake5.exe" + if not exist "%PREMAKE_EXE%" ( + 1>&2 echo:premake wasn't found + goto :end_script_with_err + ) + + :: build deps + if %BUILD_DEPS% equ 1 ( + set "CMAKE_GENERATOR=Visual Studio 2026" + call "%PREMAKE_EXE%" --file="premake5-deps.lua" --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=windows vs2026 || ( + goto :end_script_with_err + ) + goto :end_script + ) + + :: check vswhere + set "VSWHERE_EXE=third-party\common\win\vswhere\vswhere.exe" + if not exist "%VSWHERE_EXE%" ( + 1>&2 echo:vswhere wasn't found + goto :end_script_with_err + ) + + :: check msbuild + set "MSBUILD_EXE=" + for /f "tokens=* delims=" %%A in ('"%VSWHERE_EXE%" -prerelease -latest -nocolor -nologo -property installationPath 2^>nul') do ( + set "MSBUILD_EXE=%%~A\MSBuild\Current\Bin\MSBuild.exe" + ) + if not exist "%MSBUILD_EXE%" ( + 1>&2 echo:MSBuild wasn't found + goto :end_script_with_err + ) + + :: create .sln + if %GEN_PROJECT% equ 0 ( + goto :gen_project_end + ) + call "%PREMAKE_EXE%" --file="premake5.lua" --genproto --dosstub --winrsrc --winsign --os=windows vs2026 || ( + goto :end_script_with_err + ) +:gen_project_end + + :: check .sln + set "SLN_FILE=build\project\vs2026\win\gbe.slnx" + if not exist "%SLN_FILE%" ( + 1>&2 echo:.sln file wasn't found + goto :end_script_with_err + ) + + :: build .sln + set "BUILD_TYPES=release debug" + set "BUILD_PLATFORMS=x64 Win32" + set "BUILD_TARGETS=api_regular api_experimental steamclient_experimental_stub steamclient_experimental steamclient_experimental_loader steamclient_experimental_extra lib_game_overlay_renderer tool_lobby_connect tool_generate_interfaces" + + for %%A in (%BUILD_TYPES%) do ( + set "BUILD_TYPE=%%A" + for %%B in (%BUILD_PLATFORMS%) do ( + set "BUILD_PLATFORM=%%B" + for %%C in (%BUILD_TARGETS%) do ( + set "BUILD_TARGET=%%C" + echo. & echo:building !BUILD_TARGET! !BUILD_TYPE! !BUILD_PLATFORM! + call "%MSBUILD_EXE%" /nologo -m:%MAX_THREADS% -v:n /p:Configuration=!BUILD_TYPE!,Platform=!BUILD_PLATFORM! /target:!BUILD_TARGET! "%SLN_FILE%" || ( + goto :end_script_with_err + ) + ) + ) + ) + + goto :end_script + +:end_script + endlocal + exit /b 0 + +:end_script_with_err + endlocal + exit /b 1 + +:: show help page +:help_page + echo:"%~nx0" [switches] + echo:switches: + echo: --deps: rebuild third-party dependencies + echo: --nogen: don't regenerate build files + echo: --help: show this page + goto :end_script diff --git a/package_win_debug.bat b/package_win_debug.bat index ae172a8d8..536464de1 100644 --- a/package_win_debug.bat +++ b/package_win_debug.bat @@ -1,3 +1,3 @@ -@echo off - -call "package_win.bat" vs2022/debug 1 +@echo off + +call "package_win.bat" vs2026/debug 1 diff --git a/package_win_release.bat b/package_win_release.bat index eb49220b4..5b6175ffd 100644 --- a/package_win_release.bat +++ b/package_win_release.bat @@ -1,3 +1,3 @@ -@echo off - -call "package_win.bat" vs2022/release +@echo off + +call "package_win.bat" vs2026/release From 54203d2b81b8e29da54b176b2f0332f3101c912b Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Tue, 27 Jan 2026 18:58:05 +0100 Subject: [PATCH 008/159] update to latest 3rd party --- build_win_premake.bat | 240 +++++++++++++++++++-------------------- package_win_debug.bat | 6 +- package_win_release.bat | 6 +- third-party/common/linux | 2 +- third-party/common/win | 2 +- third-party/deps/linux | 2 +- third-party/deps/win | 2 +- 7 files changed, 130 insertions(+), 130 deletions(-) diff --git a/build_win_premake.bat b/build_win_premake.bat index e09945a06..568a5a6cf 100644 --- a/build_win_premake.bat +++ b/build_win_premake.bat @@ -1,120 +1,120 @@ -@echo off -setlocal EnableDelayedExpansion -cd /d "%~dp0" - -set /a "MAX_THREADS=2" -if defined NUMBER_OF_PROCESSORS ( - :: use 70% - set /a "MAX_THREADS=%NUMBER_OF_PROCESSORS% * 70 / 100" - if %MAX_THREADS% lss 1 ( - set /a "MAX_THREADS=1" - ) -) - -set /a "BUILD_DEPS=0" -set /a "GEN_PROJECT=1" - -:args_loop - if "%~1" equ "" ( - goto :args_loop_end - ) else if "%~1" equ "--deps" ( - set /a "BUILD_DEPS=1" - ) else if "%~1" equ "--nogen" ( - set /a "GEN_PROJECT=0" - ) else if "%~1" equ "--help" ( - goto :help_page - ) else ( - 1>&2 echo:invalid arg %~1 - goto :end_script_with_err - ) - - shift /1 - goto :args_loop - -:args_loop_end - :: check premake - set "PREMAKE_EXE=third-party\common\win\premake\premake5.exe" - if not exist "%PREMAKE_EXE%" ( - 1>&2 echo:premake wasn't found - goto :end_script_with_err - ) - - :: build deps - if %BUILD_DEPS% equ 1 ( - set "CMAKE_GENERATOR=Visual Studio 2026" - call "%PREMAKE_EXE%" --file="premake5-deps.lua" --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=windows vs2026 || ( - goto :end_script_with_err - ) - goto :end_script - ) - - :: check vswhere - set "VSWHERE_EXE=third-party\common\win\vswhere\vswhere.exe" - if not exist "%VSWHERE_EXE%" ( - 1>&2 echo:vswhere wasn't found - goto :end_script_with_err - ) - - :: check msbuild - set "MSBUILD_EXE=" - for /f "tokens=* delims=" %%A in ('"%VSWHERE_EXE%" -prerelease -latest -nocolor -nologo -property installationPath 2^>nul') do ( - set "MSBUILD_EXE=%%~A\MSBuild\Current\Bin\MSBuild.exe" - ) - if not exist "%MSBUILD_EXE%" ( - 1>&2 echo:MSBuild wasn't found - goto :end_script_with_err - ) - - :: create .sln - if %GEN_PROJECT% equ 0 ( - goto :gen_project_end - ) - call "%PREMAKE_EXE%" --file="premake5.lua" --genproto --dosstub --winrsrc --winsign --os=windows vs2026 || ( - goto :end_script_with_err - ) -:gen_project_end - - :: check .sln - set "SLN_FILE=build\project\vs2026\win\gbe.slnx" - if not exist "%SLN_FILE%" ( - 1>&2 echo:.sln file wasn't found - goto :end_script_with_err - ) - - :: build .sln - set "BUILD_TYPES=release debug" - set "BUILD_PLATFORMS=x64 Win32" - set "BUILD_TARGETS=api_regular api_experimental steamclient_experimental_stub steamclient_experimental steamclient_experimental_loader steamclient_experimental_extra lib_game_overlay_renderer tool_lobby_connect tool_generate_interfaces" - - for %%A in (%BUILD_TYPES%) do ( - set "BUILD_TYPE=%%A" - for %%B in (%BUILD_PLATFORMS%) do ( - set "BUILD_PLATFORM=%%B" - for %%C in (%BUILD_TARGETS%) do ( - set "BUILD_TARGET=%%C" - echo. & echo:building !BUILD_TARGET! !BUILD_TYPE! !BUILD_PLATFORM! - call "%MSBUILD_EXE%" /nologo -m:%MAX_THREADS% -v:n /p:Configuration=!BUILD_TYPE!,Platform=!BUILD_PLATFORM! /target:!BUILD_TARGET! "%SLN_FILE%" || ( - goto :end_script_with_err - ) - ) - ) - ) - - goto :end_script - -:end_script - endlocal - exit /b 0 - -:end_script_with_err - endlocal - exit /b 1 - -:: show help page -:help_page - echo:"%~nx0" [switches] - echo:switches: - echo: --deps: rebuild third-party dependencies - echo: --nogen: don't regenerate build files - echo: --help: show this page - goto :end_script +@echo off +setlocal EnableDelayedExpansion +cd /d "%~dp0" + +set /a "MAX_THREADS=2" +if defined NUMBER_OF_PROCESSORS ( + :: use 70% + set /a "MAX_THREADS=%NUMBER_OF_PROCESSORS% * 70 / 100" + if %MAX_THREADS% lss 1 ( + set /a "MAX_THREADS=1" + ) +) + +set /a "BUILD_DEPS=0" +set /a "GEN_PROJECT=1" + +:args_loop + if "%~1" equ "" ( + goto :args_loop_end + ) else if "%~1" equ "--deps" ( + set /a "BUILD_DEPS=1" + ) else if "%~1" equ "--nogen" ( + set /a "GEN_PROJECT=0" + ) else if "%~1" equ "--help" ( + goto :help_page + ) else ( + 1>&2 echo:invalid arg %~1 + goto :end_script_with_err + ) + + shift /1 + goto :args_loop + +:args_loop_end + :: check premake + set "PREMAKE_EXE=third-party\common\win\premake\premake5.exe" + if not exist "%PREMAKE_EXE%" ( + 1>&2 echo:premake wasn't found + goto :end_script_with_err + ) + + :: build deps + if %BUILD_DEPS% equ 1 ( + set "CMAKE_GENERATOR=Visual Studio 2026" + call "%PREMAKE_EXE%" --file="premake5-deps.lua" --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=windows vs2026 || ( + goto :end_script_with_err + ) + goto :end_script + ) + + :: check vswhere + set "VSWHERE_EXE=third-party\common\win\vswhere\vswhere.exe" + if not exist "%VSWHERE_EXE%" ( + 1>&2 echo:vswhere wasn't found + goto :end_script_with_err + ) + + :: check msbuild + set "MSBUILD_EXE=" + for /f "tokens=* delims=" %%A in ('"%VSWHERE_EXE%" -prerelease -latest -nocolor -nologo -property installationPath 2^>nul') do ( + set "MSBUILD_EXE=%%~A\MSBuild\Current\Bin\MSBuild.exe" + ) + if not exist "%MSBUILD_EXE%" ( + 1>&2 echo:MSBuild wasn't found + goto :end_script_with_err + ) + + :: create .sln + if %GEN_PROJECT% equ 0 ( + goto :gen_project_end + ) + call "%PREMAKE_EXE%" --file="premake5.lua" --genproto --dosstub --winrsrc --winsign --os=windows vs2026 || ( + goto :end_script_with_err + ) +:gen_project_end + + :: check .sln + set "SLN_FILE=build\project\vs2026\win\gbe.slnx" + if not exist "%SLN_FILE%" ( + 1>&2 echo:.sln file wasn't found + goto :end_script_with_err + ) + + :: build .sln + set "BUILD_TYPES=release debug" + set "BUILD_PLATFORMS=x64 Win32" + set "BUILD_TARGETS=api_regular api_experimental steamclient_experimental_stub steamclient_experimental steamclient_experimental_loader steamclient_experimental_extra lib_game_overlay_renderer tool_lobby_connect tool_generate_interfaces" + + for %%A in (%BUILD_TYPES%) do ( + set "BUILD_TYPE=%%A" + for %%B in (%BUILD_PLATFORMS%) do ( + set "BUILD_PLATFORM=%%B" + for %%C in (%BUILD_TARGETS%) do ( + set "BUILD_TARGET=%%C" + echo. & echo:building !BUILD_TARGET! !BUILD_TYPE! !BUILD_PLATFORM! + call "%MSBUILD_EXE%" /nologo -m:%MAX_THREADS% -v:n /p:Configuration=!BUILD_TYPE!,Platform=!BUILD_PLATFORM! /target:!BUILD_TARGET! "%SLN_FILE%" || ( + goto :end_script_with_err + ) + ) + ) + ) + + goto :end_script + +:end_script + endlocal + exit /b 0 + +:end_script_with_err + endlocal + exit /b 1 + +:: show help page +:help_page + echo:"%~nx0" [switches] + echo:switches: + echo: --deps: rebuild third-party dependencies + echo: --nogen: don't regenerate build files + echo: --help: show this page + goto :end_script diff --git a/package_win_debug.bat b/package_win_debug.bat index 536464de1..2f558965c 100644 --- a/package_win_debug.bat +++ b/package_win_debug.bat @@ -1,3 +1,3 @@ -@echo off - -call "package_win.bat" vs2026/debug 1 +@echo off + +call "package_win.bat" vs2026/debug 1 diff --git a/package_win_release.bat b/package_win_release.bat index 5b6175ffd..b6b396a60 100644 --- a/package_win_release.bat +++ b/package_win_release.bat @@ -1,3 +1,3 @@ -@echo off - -call "package_win.bat" vs2026/release +@echo off + +call "package_win.bat" vs2026/release diff --git a/third-party/common/linux b/third-party/common/linux index c4f721f26..47f35b43f 160000 --- a/third-party/common/linux +++ b/third-party/common/linux @@ -1 +1 @@ -Subproject commit c4f721f26e8f7c88d136787cf21b5e66ed5f8470 +Subproject commit 47f35b43f35f149c51deea57cb73c63e1540e08b diff --git a/third-party/common/win b/third-party/common/win index 73e5ef5c6..e1ad295dc 160000 --- a/third-party/common/win +++ b/third-party/common/win @@ -1 +1 @@ -Subproject commit 73e5ef5c67b57c182657b9290141c4e8b5f466db +Subproject commit e1ad295dcbc0f4767bb1ca8c27e467d71a6030a0 diff --git a/third-party/deps/linux b/third-party/deps/linux index 4f9129b3a..b023bd398 160000 --- a/third-party/deps/linux +++ b/third-party/deps/linux @@ -1 +1 @@ -Subproject commit 4f9129b3a9df778fdd483af31c252621dc36b272 +Subproject commit b023bd3987998aaf387e669a9c6c26dba3fea34c diff --git a/third-party/deps/win b/third-party/deps/win index 4edbf85d2..784c86c12 160000 --- a/third-party/deps/win +++ b/third-party/deps/win @@ -1 +1 @@ -Subproject commit 4edbf85d277c8b44b2a82d1c88ac0fa9a609c9c6 +Subproject commit 784c86c12ba4a0cd48a818e90a44a32987e77193 From 3e8687d34fd9a7d16fa8675ecb221a227942b14d Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Tue, 27 Jan 2026 19:03:06 +0100 Subject: [PATCH 009/159] fix lobbyconnect compile --- tools/lobby_connect/lobby_connect.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tools/lobby_connect/lobby_connect.cpp b/tools/lobby_connect/lobby_connect.cpp index f2a631634..1d50bb150 100644 --- a/tools/lobby_connect/lobby_connect.cpp +++ b/tools/lobby_connect/lobby_connect.cpp @@ -85,12 +85,9 @@ int main() { std::string connect = "+connect_lobby " + std::to_string(friend_info.m_steamIDLobby.ConvertToUint64()); std::cout << arguments.size() << " - " << name << " is playing " << appid << " (" << connect << ").\n"; arguments.emplace_back(connect, appid); - } - } - else if (isSuccess) { + } else if (isSuccess) { std::cout << name << " is playing " << appid << std::endl; - } - else { + } else { std::cout << name << " is found in network " << std::endl; } } From 2ed1857275e530b0fc3ad2df849d33aa233f8154 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Tue, 27 Jan 2026 19:26:55 +0100 Subject: [PATCH 010/159] update workflow, deps, readme, buildscript --- .github/workflows/emu-deps-win.yml | 2 +- README.md | 2 +- build_win_premake.bat | 2 +- third-party/deps/linux | 2 +- third-party/deps/win | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/emu-deps-win.yml b/.github/workflows/emu-deps-win.yml index 81b4d9026..98253d462 100644 --- a/.github/workflows/emu-deps-win.yml +++ b/.github/workflows/emu-deps-win.yml @@ -78,5 +78,5 @@ jobs: shell: "cmd" working-directory: "${{ github.workspace }}" run: | - set "CMAKE_GENERATOR=Visual Studio 2026" + set "CMAKE_GENERATOR=Visual Studio 18 2026" "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=windows vs2026 diff --git a/README.md b/README.md index 076754432..79bca1175 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ The only times you'll need to rebuild them is either when their separete build f Open CMD in the repo folder, then run the following * To build using `Visual Studio` ```batch - set "CMAKE_GENERATOR=Visual Studio 2026" + set "CMAKE_GENERATOR=Visual Studio 18 2026" third-party\common\win\premake\premake5.exe --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --verbose --os=windows vs2026 ``` * To build using `MSYS2` **this is currently experimental and will not work due to ABI differences** diff --git a/build_win_premake.bat b/build_win_premake.bat index 568a5a6cf..065cbc002 100644 --- a/build_win_premake.bat +++ b/build_win_premake.bat @@ -41,7 +41,7 @@ set /a "GEN_PROJECT=1" :: build deps if %BUILD_DEPS% equ 1 ( - set "CMAKE_GENERATOR=Visual Studio 2026" + set "CMAKE_GENERATOR=Visual Studio 18 2026" call "%PREMAKE_EXE%" --file="premake5-deps.lua" --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=windows vs2026 || ( goto :end_script_with_err ) diff --git a/third-party/deps/linux b/third-party/deps/linux index b023bd398..702f658c7 160000 --- a/third-party/deps/linux +++ b/third-party/deps/linux @@ -1 +1 @@ -Subproject commit b023bd3987998aaf387e669a9c6c26dba3fea34c +Subproject commit 702f658c7b8ed7cd87ea31d9b2676f370d07d065 diff --git a/third-party/deps/win b/third-party/deps/win index 784c86c12..83308a20b 160000 --- a/third-party/deps/win +++ b/third-party/deps/win @@ -1 +1 @@ -Subproject commit 784c86c12ba4a0cd48a818e90a44a32987e77193 +Subproject commit 83308a20b093ff955fd8119726eb6d531027b2a6 From 51ae84f570c55912cc432c2ca084392686d088fb Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Tue, 27 Jan 2026 19:39:41 +0100 Subject: [PATCH 011/159] update windows runners --- .github/workflows/emu-build-all-win.yml | 2 +- .github/workflows/emu-deps-win.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 372a461e3..aebdb8818 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -25,7 +25,7 @@ jobs: builds-matrix-win: name: "build" needs: ["deps"] - runs-on: "windows-2022" + runs-on: "windows-2025" if: ${{ !cancelled() }} continue-on-error: true diff --git a/.github/workflows/emu-deps-win.yml b/.github/workflows/emu-deps-win.yml index 98253d462..2b7bf143f 100644 --- a/.github/workflows/emu-deps-win.yml +++ b/.github/workflows/emu-deps-win.yml @@ -19,7 +19,7 @@ env: jobs: deps-build: - runs-on: "windows-2022" + runs-on: "windows-2025" if: ${{ !cancelled() }} steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2beae1e2..cce65566a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: emu-win-prep: needs: ["emu-win-all"] - runs-on: "windows-2022" + runs-on: "windows-2025" steps: # on Windows Git will auto change line ending to CRLF, not preferable - name: "Ensure LF line ending" From c60ac33d93d2fac5ef86f813fee368bbc297f298 Mon Sep 17 00:00:00 2001 From: universal963 <36097923+universal963@users.noreply.github.com> Date: Mon, 9 Feb 2026 17:52:24 +0800 Subject: [PATCH 012/159] Restore `account_avatar_default` --- dll/steam_friends.cpp | 7 ++++++- post_build/README.release.md | 4 +++- .../account_avatar_default.EXAMPLE.jpg | Bin 0 -> 3409 bytes 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 post_build/steam_settings.EXAMPLE/account_avatar_default.EXAMPLE.jpg diff --git a/dll/steam_friends.cpp b/dll/steam_friends.cpp index e776b5d8b..27f640f06 100644 --- a/dll/steam_friends.cpp +++ b/dll/steam_friends.cpp @@ -71,6 +71,11 @@ struct Avatar_Numbers Steam_Friends::add_friend_avatars(CSteamID id) "account_avatar.jpg", "account_avatar.jpeg", }; + static const std::initializer_list avatar_icons_default = { + "account_avatar_default.png", + "account_avatar_default.jpg", + "account_avatar_default.jpeg", + }; if (!settings->disable_account_avatar && (id == settings->get_local_steam_id())) { std::string file_path{}; @@ -104,7 +109,7 @@ struct Avatar_Numbers Steam_Friends::add_friend_avatars(CSteamID id) // 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) { + for (const auto &file_name : avatar_icons_default) { file_path = settings_path + file_name; file_size = file_size_(file_path); if (file_size) break; diff --git a/post_build/README.release.md b/post_build/README.release.md index db5b5b370..d7e7e8a4e 100644 --- a/post_build/README.release.md +++ b/post_build/README.release.md @@ -226,9 +226,11 @@ An example that was made for payday 2 can be found in `steam_settings.EXAMPLE` Copy a `png`, or a `jpg`, or a `jpeg` image file to your `GSE Saves/settings` folder and name it `account_avatar`. You can also place this file inside the local `steam_settings` folder of the game. +You can set a default profile picture for users who are missing one by copying a similar file called `account_avatar_default`. + Players avatars are shared over the local network -You can find an example in `steam_settings.EXAMPLE` +You can find the example in `steam_settings.EXAMPLE` --- diff --git a/post_build/steam_settings.EXAMPLE/account_avatar_default.EXAMPLE.jpg b/post_build/steam_settings.EXAMPLE/account_avatar_default.EXAMPLE.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aa490cb4ea49d648b7d333c98c8f2acd01695589 GIT binary patch literal 3409 zcmd^>do+~m9>I(WdrhTpE(&xMjvATkawxg`!@CF1AausY4+u z(#DbN5R(vUmo`btA@@rS#&rxc?|$ulc87ib*=MiTI_s?SyzBR_^{(H$zR&Y{e&65w zKJZ)k1CTgqY-$WpC;(8%1z-XA+Q^z_aLB@XAIZyI!{@k%7b$q>_8lbhsi33&UL@0l zCZvOwG!s(rSG&}U=bg)NG*aw_sNGNf%Jm9463>S`MmHFju_cWfc6k(WhKI2?|EC#)k7 z){$jpWXb>WfFA=%Jg5K_Xp}M#l0=~;QE)v_L?DGxzYOrtff7O^>&4+k2y2iRN+p00 z3XK-RpoN7o8077GdoL&Ve%{04lG3vB3TEY#y84EvjZMwZTH9W?zv+0} z`L4IGe_(KEcw}^pGc`RkJ2%f=;4O2Z0Qy%fJ{fXIAa&pYj&>BS%zK#oh^?W_Sx7z~^; zU|_h19-#qw_dqS|7Ct|Klf7uXD1iK-iH}g6ZPx!|N^9ohduHhx?|aPj1KqZC`qirj zD%~;pcVN&&e=ev}6-1FU(R3JG&7a^0LwtO@L+Jyki9Ro=q6(sX@&VC`_J_iu6&j#; zAXP|0sz9{*llqR|5+`d4`||hOd#5CP9lKF}GEtoFUpK2;tiPdz(Yk3P7j@4-%Ct|{ z*taUyy}|c#Kdz$7z>~*u@>!CfRL{lj-)3U%Kimw1#u4;@I%RBa&ci9N0N3^V0(BglcG>7gih5XdBp7<5zsrgtgB2n427yv? z)Z~52;7Me~sR|6soM$_!GfFfdTfP(>0DUZm!H98Qd%mNNCG%Dn5h>u*BsUm-q5z{ziNp$N{3g35ceF&;@!85U7<;bD{i*v73Dsv9}YXr+{+t^&>PJiKN2(6Z5DPTJ5}V+gGS{G zKj3RIA=1;Db>gvS(vzXJ2{GH@J{xWr~_3;^hjrQxWYU?XkUNP?c8gHi*9Fa*mTU+O_WeXzH}jqFG>b~P5JJOlg*Op z=&l2iC;HoTR7QOJ-T`t}%;zXw>6&cQWm8_~2V3y*R{Gfdvt=EEb1pEbY2(J-@c&dy#rMGew!x!0z<;_rcbUa-!e!ILhZm=S`KJaPe zmbd9Z;iWmv*+}rR8#SfuEIsy0Fh^5d!E!}_T zbs_GeRG%P=GY^Z2}pr{>tb3^h`-tBiR-tEBoNH#BXv6;&tq`;imoODyB(# z=Jj-Zm3sVR zG0Hz6pR{sw&AK-(mrXrWU8}FqEJYB{y$EOj+Zw+lZbz-TLnDnF?G~2T)#&G)_@k`7 z4!F^zc)##2)a$|37F?85TpWQxbhrx*gi+aYeSUKF&XWm{tIv+^1x4@a@P~hW(M|8- z2$~BwtZ2#K+VhJ!a)ugl+$e~nZ4om>9*k|=siu4pH}4)#8Do8Nq6p5XP`P5PUT4)M zQC4x=xy^Qm$FzgzQl87#-c2`<6>=j9)ko|t%%%43nJnjvR;mg_m0{4Ai+)P=p-{}I zT;p@iexhA-)=FQ3)z+hz1}qS|5#M+ru8p%@CGvaGmCygh z2~b{E53ABFnJIEd2jT`j>2}Q+sk3?IbEoE)77oLp2k8O=aw=Hdw Date: Sun, 15 Feb 2026 19:14:13 -0500 Subject: [PATCH 013/159] Add purchased_keys.txt support for proof of purchase keys Read per-app proof of purchase keys from steam_settings/purchased_keys.txt using the format: AppId=KEY (e.g. 480=AAAAA-BBBBB-CCCCC-DDDDD). - Add purchased_keys map, setter and getter to Settings - Add parse_purchased_keys() parser in settings_parser.cpp - Wire key lookup into RequestAppProofOfPurchaseKey, RequestAppProofOfPurchaseKey_OLD, and RequestAllProofOfPurchaseKeys - Owned apps without an entry still get the default placeholder key - Remove TODO comments from FillProofOfPurchaseKey --- dll/dll/settings.h | 5 +++++ dll/settings.cpp | 17 +++++++++++++++ dll/settings_parser.cpp | 36 +++++++++++++++++++++++++++++++ dll/steam_apps.cpp | 48 +++++++++++++++++++++++++++-------------- 4 files changed, 90 insertions(+), 16 deletions(-) diff --git a/dll/dll/settings.h b/dll/dll/settings.h index ad8d1405b..4a38d3a19 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -219,6 +219,7 @@ class Settings { std::set installed_app_ids{}; std::map app_paths{}; + std::map purchased_keys{}; std::vector mods{}; std::map leaderboards{}; std::map stats{}; @@ -429,6 +430,10 @@ class Settings { void setAppInstallPath(AppId_t appID, const std::string &path); bool getAppInstallPath(AppId_t appID, std::string &path); + //Purchased keys + void setPurchasedKey(AppId_t appID, const std::string &key); + bool getPurchasedKey(AppId_t appID, std::string &key) const; + //mod stuff void addMod(PublishedFileId_t id, const std::string &title, const std::string &path); void addModDetails(PublishedFileId_t id, const Mod_entry &details); diff --git a/dll/settings.cpp b/dll/settings.cpp index a28742d06..72dcef9f5 100644 --- a/dll/settings.cpp +++ b/dll/settings.cpp @@ -361,6 +361,23 @@ bool Settings::getAppInstallPath(AppId_t appID, std::string &path) return false; } +void Settings::setPurchasedKey(AppId_t appID, const std::string &key) +{ + purchased_keys[appID] = key; +} + +bool Settings::getPurchasedKey(AppId_t appID, std::string &key) const +{ + auto it = purchased_keys.find(appID); + if (purchased_keys.end() != it) + { + key = it->second; + return true; + } + + return false; +} + void Settings::setLeaderboard(const std::string &leaderboard, enum ELeaderboardSortMethod sort_method, enum ELeaderboardDisplayType display_type) { Leaderboard_config leader{}; diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 362242961..9c175c45c 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -989,6 +989,41 @@ static void parse_subscribed_groups(class Settings *settings_client, class Setti } +// purchased_keys.txt +static void parse_purchased_keys(class Settings *settings_client, class Settings *settings_server) +{ + std::string purchased_keys_path = Local_Storage::get_game_settings_path() + "purchased_keys.txt"; + std::ifstream input( std::filesystem::u8path(purchased_keys_path) ); + if (input.is_open()) { + PRINT_DEBUG("Reading purchased keys"); + common_helpers::consume_bom(input); + for( std::string line; getline( input, line ); ) { + if (!line.empty() && line[line.length()-1] == '\n') { + line.pop_back(); + } + + if (!line.empty() && line[line.length()-1] == '\r') { + line.pop_back(); + } + + // skip empty lines and comments + if (line.empty() || line[0] == '#') continue; + + try { + size_t delimiter = line.find('='); + if (delimiter != std::string::npos) { + AppId_t app_id = std::stoul(line.substr(0, delimiter)); + std::string key = line.substr(delimiter + 1); + settings_client->setPurchasedKey(app_id, key); + settings_server->setPurchasedKey(app_id, key); + PRINT_DEBUG("Added purchased key for app ID %u", app_id); + } + } catch (...) {} + } + } + +} + // installed_app_ids.txt static void parse_installed_app_Ids(class Settings *settings_client, class Settings *settings_server) { @@ -1896,6 +1931,7 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s parse_dlc(settings_client, settings_server); parse_installed_app_Ids(settings_client, settings_server); parse_app_paths(settings_client, settings_server, program_path); + parse_purchased_keys(settings_client, settings_server); parse_leaderboards(settings_client, settings_server); parse_stats(settings_client, settings_server, local_storage); diff --git a/dll/steam_apps.cpp b/dll/steam_apps.cpp index 13e9a161c..8c4f6728d 100644 --- a/dll/steam_apps.cpp +++ b/dll/steam_apps.cpp @@ -23,9 +23,6 @@ void Steam_Apps::FillProofOfPurchaseKey( AppProofOfPurchaseKeyResponse_t& data, { data.m_nAppID = nAppID; if (ok_result) { - // TODO maybe read this from a config file "purchased_keys.txt": - // 480=AAAAA-BBBBB-CCCCC-DDDDD - // 218620=XYZFJ-13370-98765 size_t min_len = key.size() < k_cubAppProofOfPurchaseKeyMax ? key.size() : k_cubAppProofOfPurchaseKeyMax - 1; // -1 because we need space for null @@ -45,9 +42,6 @@ void Steam_Apps::FillProofOfPurchaseKey( AppProofOfPurchaseKeyResponse007_t& dat { data.m_nAppID = nAppID; if (ok_result) { - // TODO maybe read this from a config file "purchased_keys.txt": - // 480=AAAAA-BBBBB-CCCCC-DDDDD - // 218620=XYZFJ-13370-98765 size_t min_len = key.size() < k_cubAppProofOfPurchaseKeyMax ? key.size() : k_cubAppProofOfPurchaseKeyMax - 1; // -1 because we need space for null @@ -272,7 +266,7 @@ void Steam_Apps::UninstallDLC( AppId_t nAppID ) // the key is available (which may be immediately). void Steam_Apps::RequestAppProofOfPurchaseKey( AppId_t nAppID ) { - PRINT_DEBUG_TODO(); + PRINT_DEBUG("%u", nAppID); std::lock_guard lock(global_mutex); AppProofOfPurchaseKeyResponse_t data{}; @@ -282,9 +276,14 @@ void Steam_Apps::RequestAppProofOfPurchaseKey( AppId_t nAppID ) if (nAppID == 0 || nAppID == UINT32_MAX) { FillProofOfPurchaseKey(data, nAppID, false); } else if (nAppID == settings->get_local_game_id().AppID() || settings->hasDLC(nAppID)) { - FillProofOfPurchaseKey(data, nAppID, true); + std::string key{}; + settings->getPurchasedKey(nAppID, key); + if (!key.empty()) { + FillProofOfPurchaseKey(data, nAppID, true, key); + } else { + FillProofOfPurchaseKey(data, nAppID, true); + } } else { - //TODO what to do here? FillProofOfPurchaseKey(data, nAppID, false); } @@ -293,7 +292,7 @@ void Steam_Apps::RequestAppProofOfPurchaseKey( AppId_t nAppID ) void Steam_Apps::RequestAppProofOfPurchaseKey_OLD( AppId_t nAppID ) { - PRINT_DEBUG_TODO(); + PRINT_DEBUG("%u", nAppID); std::lock_guard lock(global_mutex); AppProofOfPurchaseKeyResponse007_t data{}; @@ -303,9 +302,14 @@ void Steam_Apps::RequestAppProofOfPurchaseKey_OLD( AppId_t nAppID ) if (nAppID == 0 || nAppID == UINT32_MAX) { FillProofOfPurchaseKey(data, nAppID, false); } else if (nAppID == settings->get_local_game_id().AppID() || settings->hasDLC(nAppID)) { - FillProofOfPurchaseKey(data, nAppID, true); + std::string key{}; + settings->getPurchasedKey(nAppID, key); + if (!key.empty()) { + FillProofOfPurchaseKey(data, nAppID, true, key); + } else { + FillProofOfPurchaseKey(data, nAppID, true); + } } else { - //TODO what to do here? FillProofOfPurchaseKey(data, nAppID, false); } @@ -457,24 +461,36 @@ int Steam_Apps::GetAppBuildId() // member is k_uAppIdInvalid (zero). void Steam_Apps::RequestAllProofOfPurchaseKeys() { - PRINT_DEBUG_TODO(); + PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); // current app { AppProofOfPurchaseKeyResponse_t data{}; - FillProofOfPurchaseKey(data, settings->get_local_game_id().AppID(), true); + std::string key{}; + AppId_t app_id = settings->get_local_game_id().AppID(); + settings->getPurchasedKey(app_id, key); + if (!key.empty()) { + FillProofOfPurchaseKey(data, app_id, true, key); + } else { + FillProofOfPurchaseKey(data, app_id, true); + } callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); } // DLCs - const auto count = settings->DLCCount(); for (unsigned i = 0; i < settings->DLCCount(); i++) { AppId_t app_id{}; bool available{}; std::string name{}; if (settings->getDLC(i, app_id, available, name)) { AppProofOfPurchaseKeyResponse_t data{}; - FillProofOfPurchaseKey(data, app_id, true); + std::string key{}; + settings->getPurchasedKey(app_id, key); + if (!key.empty()) { + FillProofOfPurchaseKey(data, app_id, true, key); + } else { + FillProofOfPurchaseKey(data, app_id, true); + } callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); } } From 331f44ad5596443e70807908ae6f7d018153a584 Mon Sep 17 00:00:00 2001 From: rust86 Date: Mon, 16 Feb 2026 12:35:42 -0500 Subject: [PATCH 014/159] Add support for purchased keys in README and create example file --- post_build/README.release.md | 10 ++++++++++ .../purchased_keys.EXAMPLE.txt | 12 ++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 post_build/steam_settings.EXAMPLE/purchased_keys.EXAMPLE.txt diff --git a/post_build/README.release.md b/post_build/README.release.md index d7e7e8a4e..0942896e2 100644 --- a/post_build/README.release.md +++ b/post_build/README.release.md @@ -98,6 +98,16 @@ See `steam_settings.EXAMPLE\subscribed_groups_clans.EXAMPLE.txt` for an example. --- +## Purchased Keys: +Some games request CD keys or proof-of-purchase keys for installed apps and DLCs. +You can provide these keys to the game with a `steam_settings\purchased_keys.txt` file. + +The format is `appid=KEY` with one entry per line. Comments start with `#`. + +See `steam_settings.EXAMPLE\purchased_keys.EXAMPLE.txt` for an example. + +--- + ## App paths: Some rare games might need to be provided one or more paths to app ids. For example the path to where a dlc is installed. diff --git a/post_build/steam_settings.EXAMPLE/purchased_keys.EXAMPLE.txt b/post_build/steam_settings.EXAMPLE/purchased_keys.EXAMPLE.txt new file mode 100644 index 000000000..08b791e50 --- /dev/null +++ b/post_build/steam_settings.EXAMPLE/purchased_keys.EXAMPLE.txt @@ -0,0 +1,12 @@ +# Rename this to: purchased_keys.txt +# This file allows you to specify CD keys or proof-of-purchase keys for games and DLCs +# These keys will be returned when the game calls RequestAppProofOfPurchaseKey() or RequestAllProofOfPurchaseKeys() +# +# Format: appid=KEY +# Each line should contain one app ID and its corresponding key, separated by an equals sign +# Lines starting with # are treated as comments +# Empty lines are ignored +# +# Examples: +# 480=AAAAA-BBBBB-CCCCC-DDDDD +# 218620=XYZFJ-13370-98765 From 8b1e1c33d6b480db7abe50b76bc2abeab3d3e9a1 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:09:54 +0100 Subject: [PATCH 015/159] windows runs-on to vs26 --- .github/workflows/emu-build-all-win.yml | 2 +- .github/workflows/emu-deps-win.yml | 2 +- .github/workflows/migrate_gse-build-win.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index aebdb8818..4b22ab7ae 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -25,7 +25,7 @@ jobs: builds-matrix-win: name: "build" needs: ["deps"] - runs-on: "windows-2025" + runs-on: "windows-2025-vs2026" if: ${{ !cancelled() }} continue-on-error: true diff --git a/.github/workflows/emu-deps-win.yml b/.github/workflows/emu-deps-win.yml index 2b7bf143f..b230a5445 100644 --- a/.github/workflows/emu-deps-win.yml +++ b/.github/workflows/emu-deps-win.yml @@ -19,7 +19,7 @@ env: jobs: deps-build: - runs-on: "windows-2025" + runs-on: "windows-2025-vs2026" if: ${{ !cancelled() }} steps: diff --git a/.github/workflows/migrate_gse-build-win.yml b/.github/workflows/migrate_gse-build-win.yml index 34913db8a..ae376f748 100644 --- a/.github/workflows/migrate_gse-build-win.yml +++ b/.github/workflows/migrate_gse-build-win.yml @@ -18,7 +18,7 @@ env: jobs: build: - runs-on: "windows-2022" + runs-on: "windows-2025-vs2026" steps: - name: "Set up Python 3.12" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cce65566a..72f5be20f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: emu-win-prep: needs: ["emu-win-all"] - runs-on: "windows-2025" + runs-on: "windows-2025-vs2026" steps: # on Windows Git will auto change line ending to CRLF, not preferable - name: "Ensure LF line ending" From dfdd3b233eb550e5814f7f63d683455091fd4dd5 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:56:04 +0100 Subject: [PATCH 016/159] Fix premakes --- premake5-deps.lua | 2 +- premake5.lua | 70 +++++++++++++++++++++++------------------------ 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/premake5-deps.lua b/premake5-deps.lua index 2c5c9b888..9d2f8aeb6 100644 --- a/premake5-deps.lua +++ b/premake5-deps.lua @@ -5,7 +5,7 @@ require("premake", ">=5.0.0-beta2") -- common ones -- ============ -- Unix Makefiles --- Visual Studio 17 2022 +-- Visual Studio 18 2026 -- MSYS Makefiles local os_iden = '' -- identifier diff --git a/premake5.lua b/premake5.lua index e70176b58..e3d21d247 100644 --- a/premake5.lua +++ b/premake5.lua @@ -508,7 +508,7 @@ end filter {} -- reset the filter and remove all active keywords configurations { "debug", "release", } -platforms { "x64", "x32", } +platforms { "x64", "x86", } language "C++" cppdialect "C++17" cdialect "C17" @@ -518,11 +518,9 @@ filter {} -- reset the filter and remove all active keywords characterset "Unicode" staticruntime "on" -- /MT or /MTd runtime "Release" -- ensure we never link with /MTd, otherwise deps linking will fail -flags { - "NoPCH", -- no precompiled header on Windows - "MultiProcessorCompile", -- /MP "Enable Visual Studio to use multiple compiler processes when building" - "RelativeLinks", -} +enablepch "Off" -- no precompiled header on Windows +multiprocessorcompile "On" -- /MP "Enable Visual Studio to use multiple compiler processes when building" +userelativelinks "On" targetprefix "" -- prevent adding the prefix libxxx on linux visibility "Hidden" -- hide all symbols by default on GCC (unless they are marked visible) linkgroups "On" -- turn off the awful order dependent linking on gcc/clang, causes the linker to go back and forth to find missing symbols @@ -553,7 +551,7 @@ vpaths { -- just for visual niceness, see: https://premake.github.io/docs/vpaths -- arch --------- -filter { "platforms:x32", } +filter { "platforms:x86", } architecture "x86" filter { "platforms:x64", } architecture "x86_64" @@ -673,7 +671,7 @@ filter { 'options:incexamples', 'system:not windows', } } -- deps -filter { 'options:incdeps', "platforms:x32", } +filter { 'options:incdeps', "platforms:x86", } files { table_postfix_items(x32_deps_include, '/**.h'), table_postfix_items(x32_deps_include, '/**.hxx'), @@ -719,7 +717,7 @@ filter { "system:windows", "options:dosstub", } -- sign filter { "system:windows", "options:winsign", } postbuildcommands { - '"' .. signer_tool .. '" %[%{!cfg.buildtarget.abspath}]', + '"' .. signer_tool .. '" %[%{!cfg.buildtarget.directory}%{!cfg.buildtarget.name}]', } filter {} -- reset the filter and remove all active keywords end @@ -740,7 +738,7 @@ project "api_regular" -- name --------- - filter { "system:windows", "platforms:x32", } + filter { "system:windows", "platforms:x86", } targetname "steam_api" filter { "system:windows", "platforms:x64", } targetname "steam_api64" @@ -749,7 +747,7 @@ project "api_regular" -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, } @@ -776,7 +774,7 @@ project "api_regular" "dll/wrap.cpp" } -- Windows x32 common source files - filter { "system:windows", "platforms:x32", "options:winrsrc", } + filter { "system:windows", "platforms:x86", "options:winrsrc", } files { "resources/win/api/32/resources.rc" } @@ -805,7 +803,7 @@ project "api_regular" -- libs search dir --------- -- x32 libs search dir - filter { "platforms:x32", } + filter { "platforms:x86", } libdirs { x32_deps_libdir, } @@ -827,7 +825,7 @@ project "api_experimental" -- name --------- - filter { "system:windows", "platforms:x32", } + filter { "system:windows", "platforms:x86", } targetname "steam_api" filter { "system:windows", "platforms:x64", } targetname "steam_api64" @@ -847,7 +845,7 @@ project "api_experimental" -- include dir --------- -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, x32_deps_overlay_include, @@ -871,7 +869,7 @@ project "api_experimental" 'libs/detours/uimports.cc', } -- deps - filter { 'options:incdeps', "platforms:x32", } + filter { 'options:incdeps', "platforms:x86", } files { table_postfix_items(x32_deps_overlay_include, '/**.h'), table_postfix_items(x32_deps_overlay_include, '/**.hxx'), @@ -889,7 +887,7 @@ project "api_experimental" "dll/wrap.cpp" } -- Windows x32 common source files - filter { "system:windows", "platforms:x32", "options:winrsrc", } + filter { "system:windows", "platforms:x86", "options:winrsrc", } files { "resources/win/api/32/resources.rc" } @@ -928,7 +926,7 @@ project "api_experimental" -- libs search dir --------- -- x32 libs search dir - filter { "platforms:x32", } + filter { "platforms:x86", } libdirs { x32_deps_libdir, x32_deps_overlay_libdir, @@ -958,7 +956,7 @@ project "steamclient_experimental" -- name --------- - filter { "system:windows", "platforms:x32", } + filter { "system:windows", "platforms:x86", } targetname "steamclient" filter { "system:windows", "platforms:x64", } targetname "steamclient64" @@ -978,7 +976,7 @@ project "steamclient_experimental" -- include dir --------- -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, x32_deps_overlay_include, @@ -1004,7 +1002,7 @@ project "steamclient_experimental" 'dll/flat.cpp', } -- deps - filter { 'options:incdeps', "platforms:x32", } + filter { 'options:incdeps', "platforms:x86", } files { table_postfix_items(x32_deps_overlay_include, '/**.h'), table_postfix_items(x32_deps_overlay_include, '/**.hxx'), @@ -1022,7 +1020,7 @@ project "steamclient_experimental" "dll/wrap.cpp" } -- Windows x32 common source files - filter { "system:windows", "platforms:x32", "options:winrsrc", } + filter { "system:windows", "platforms:x86", "options:winrsrc", } files { "resources/win/client/32/resources.rc" } @@ -1060,7 +1058,7 @@ project "steamclient_experimental" -- libs search dir --------- -- x32 libs search dir - filter { "platforms:x32", } + filter { "platforms:x86", } libdirs { x32_deps_libdir, x32_deps_overlay_libdir, @@ -1098,7 +1096,7 @@ project "tool_lobby_connect" --------- -- common include dir -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, } @@ -1123,7 +1121,7 @@ project "tool_lobby_connect" 'dll/flat.cpp', } -- Windows x32 common source files - filter { "system:windows", "platforms:x32", "options:winrsrc", } + filter { "system:windows", "platforms:x86", "options:winrsrc", } files { "resources/win/launcher/32/resources.rc" } @@ -1153,7 +1151,7 @@ project "tool_lobby_connect" -- libs search dir --------- -- x32 libs search dir - filter { "platforms:x32", } + filter { "platforms:x86", } libdirs { x32_deps_libdir, } @@ -1220,7 +1218,7 @@ project "lib_game_overlay_renderer" -- name --------- - filter { "system:windows", "platforms:x32", } + filter { "system:windows", "platforms:x86", } targetname "GameOverlayRenderer" filter { "system:windows", "platforms:x64", } targetname "GameOverlayRenderer64" @@ -1231,7 +1229,7 @@ project "lib_game_overlay_renderer" -- include dir --------- -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, } @@ -1252,7 +1250,7 @@ project "lib_game_overlay_renderer" "common_helpers/os_detector.h", } -- x32 common source files - filter { "system:windows", "platforms:x32", "options:winrsrc", } + filter { "system:windows", "platforms:x86", "options:winrsrc", } files { "resources/win/game_overlay_renderer/32/resources.rc" } @@ -1280,7 +1278,7 @@ project "steamclient_experimental_stub" -- name --------- - filter { "platforms:x32", } + filter { "platforms:x86", } targetname "steamclient" filter { "platforms:x64", } targetname "steamclient64" @@ -1293,7 +1291,7 @@ project "steamclient_experimental_stub" "steamclient/steamclient.cpp", } -- x32 common source files - filter { "platforms:x32", "options:winrsrc", } + filter { "platforms:x86", "options:winrsrc", } files { "resources/win/client/32/resources.rc" } @@ -1316,7 +1314,7 @@ project "steamclient_experimental_extra" -- include dir --------- -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, } @@ -1342,7 +1340,7 @@ project "steamclient_experimental_extra" 'libs/detours/uimports.cc', } -- x32 common source files - filter { "platforms:x32", "options:winrsrc", } + filter { "platforms:x86", "options:winrsrc", } files { "resources/win/client/32/resources.rc" } @@ -1434,7 +1432,7 @@ project "steamclient_experimental_loader" "libs/simpleini/**", } -- x32 common source files - filter { "platforms:x32", "options:winrsrc", } + filter { "platforms:x86", "options:winrsrc", } files { "resources/win/launcher/32/resources.rc" } @@ -1546,7 +1544,7 @@ project "steamclient_regular" -- include dir --------- -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, } @@ -1579,7 +1577,7 @@ project "steamclient_regular" -- libs search dir --------- -- x32 libs search dir - filter { "platforms:x32", } + filter { "platforms:x86", } libdirs { x32_deps_libdir, } From 6660ebb61e33b7c4dd0d0f7e3673c8a5d04b7887 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:57:41 +0100 Subject: [PATCH 017/159] fix build all win --- .github/workflows/emu-build-all-win.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 4b22ab7ae..b2963b816 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -107,7 +107,7 @@ jobs: shell: "cmd" working-directory: "${{ github.workspace }}/build/project/vs2026/win" run: | - msbuild /nologo /target:${{ matrix.prj }} /m:2 /v:n /p:Configuration=${{ matrix.cfg }},Platform=${{ matrix.arch }} gbe.sln + msbuild /nologo /target:${{ matrix.prj }} /m:2 /v:n /p:Configuration=${{ matrix.cfg }},Platform=${{ matrix.arch }} gbe.slnx # upload artifact/package to github Actions - name: "Upload target package" From 1db0de92e5a0f17515b04c0401a75bc869d45980 Mon Sep 17 00:00:00 2001 From: a Date: Tue, 17 Feb 2026 02:42:53 +0200 Subject: [PATCH 018/159] handle numeric achievement progress --- dll/steam_user_stats.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/dll/steam_user_stats.cpp b/dll/steam_user_stats.cpp index 237dbb5fb..1a35a3e73 100644 --- a/dll/steam_user_stats.cpp +++ b/dll/steam_user_stats.cpp @@ -70,8 +70,16 @@ Steam_User_Stats::Steam_User_Stats(Settings *settings, class Networking *network trig.name = name; trig.value_operation = static_cast(it["progress"]["value"]["operation"]); std::string stat_name = common_helpers::to_lower(static_cast(it["progress"]["value"]["operand1"])); - trig.min_value = static_cast(it["progress"]["min_val"]); - trig.max_value = static_cast(it["progress"]["max_val"]); + const auto &min_val_obj = it["progress"]["min_val"]; + std::string min_val = min_val_obj.is_number() + ? std::to_string(static_cast(min_val_obj)) + : static_cast(min_val_obj); + const auto &max_val_obj = it["progress"]["max_val"]; + std::string max_val = max_val_obj.is_number() + ? std::to_string(static_cast(max_val_obj)) + : static_cast(max_val_obj); + trig.min_value = min_val; + trig.max_value = max_val; achievement_stat_trigger[stat_name].push_back(trig); } catch(...) {} From 7da097027d257b09692dac3d40feaf6e17ec8314 Mon Sep 17 00:00:00 2001 From: universal963 <36097923+universal963@users.noreply.github.com> Date: Thu, 19 Feb 2026 23:07:08 +0800 Subject: [PATCH 019/159] Add more tools in README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bd940ebb5..e8f1d7ca0 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,9 @@ You can find helper guides, scripts, and tools here: **(These guides, scripts, and tools are maintained by their authors.)** -* **[GBE Fork Tools](https://github.com/Detanup01/gbe_fork_tools)** +* **[gbe_fork_tools](https://github.com/Detanup01/gbe_fork_tools)** +* **[gen.emu.sharp](https://github.com/otavepto/gen.emu.sharp)** +* **[gse_fork_tools](https://github.com/alex47exe/gse_fork_tools)** * **[Semuexec](https://gitlab.com/detiam/Semuexec)** * **[Steam Emu Utility](https://github.com/turusudiro/SteamEmuUtility)** * **[How to use Goldberg Emulator](https://rentry.co/goldberg_emulator)** From 315b1b17d45d66f09549e9323221b213c5c9fe02 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Fri, 20 Feb 2026 12:39:23 +0300 Subject: [PATCH 020/159] Fire Steam2 deny callback for denied Steam2 auth requests --- dll/auth.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dll/auth.cpp b/dll/auth.cpp index b6b27cbd7..7f9fdaab1 100644 --- a/dll/auth.cpp +++ b/dll/auth.cpp @@ -691,18 +691,18 @@ void Auth_Manager::launch_callback_gs_steam2(CSteamID steam_id, uint32_t user_id data3.m_SteamID = data3.m_OwnerSteamID = steam_id; callbacks->addCBResult(data3.k_iCallback, &data3, sizeof(data3), STEAM_TICKET_PROCESS_TIME * 2.0); } else { - /* + // Fire Steam2 callback. Only one of these is actually needed to kick the client. GSClientSteam2Deny_t data2{}; data2.m_UserID = user_id; - data2.m_eSteamError = k_EDenyNotLoggedOn; + data2.m_eSteamError = k_EDenyNotLoggedOn; //TODO: other reasons? callbacks->addCBResult(data2.k_iCallback, &data2, sizeof(data2), STEAM_TICKET_PROCESS_TIME); - */ - // Fire Steam3 callback. Only one of these is actually needed to kick the client. + /* GSClientDeny_t data3{}; data3.m_SteamID = steam_id; - data3.m_eDenyReason = k_EDenyNotLoggedOn; //TODO: other reasons? - callbacks->addCBResult(data3.k_iCallback, &data3, sizeof(data3), STEAM_TICKET_PROCESS_TIME); + data3.m_eDenyReason = k_EDenyNotLoggedOn; + callbacks->addCBResult(data3.k_iCallback, &data3, sizeof(data3), STEAM_TICKET_PROCESS_TIME * 2.0); + */ } } From 031abb51bcab18b6dbdeeee1e2b0ca3a924031b5 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 21 Feb 2026 19:25:28 +0300 Subject: [PATCH 021/159] Broadcast to 10 ports for peer discovery --- dll/dll/network.h | 1 + dll/network.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dll/dll/network.h b/dll/dll/network.h index 63c6cbdf9..0731b676e 100644 --- a/dll/dll/network.h +++ b/dll/dll/network.h @@ -22,6 +22,7 @@ #include #define DEFAULT_PORT 47584 +#define NUM_QUERY_PORTS 10 #if defined(STEAM_WIN32) typedef unsigned int sock_t; diff --git a/dll/network.cpp b/dll/network.cpp index 053e9469e..dab8ce726 100644 --- a/dll/network.cpp +++ b/dll/network.cpp @@ -928,8 +928,10 @@ void Networking::send_announce_broadcasts() size_t size = msg.ByteSizeLong(); std::vector buffer(size); msg.SerializeToArray(&buffer[0], static_cast(size)); - send_broadcasts(udp_socket, htons(DEFAULT_PORT), &buffer[0], static_cast(size), &this->custom_broadcasts); - if (udp_port != DEFAULT_PORT) { + for (uint16 i = DEFAULT_PORT; i < DEFAULT_PORT + NUM_QUERY_PORTS; i++) { + send_broadcasts(udp_socket, htons(i), &buffer[0], static_cast(size), &this->custom_broadcasts); + } + if (udp_port < DEFAULT_PORT || udp_port >= DEFAULT_PORT + NUM_QUERY_PORTS) { send_broadcasts(udp_socket, htons(udp_port), &buffer[0], static_cast(size), &this->custom_broadcasts); } From 8174c6f25f82abfc2bc421f7fd0472a392567f14 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 21 Feb 2026 19:26:04 +0300 Subject: [PATCH 022/159] Fixed send_broadcasts not always sending to the correct port --- dll/network.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dll/network.cpp b/dll/network.cpp index dab8ce726..2f8abedcb 100644 --- a/dll/network.cpp +++ b/dll/network.cpp @@ -352,8 +352,9 @@ static bool send_broadcasts(sock_t sock, uint16 port, char *data, unsigned long return false; for (int i = 0; i < number_broadcasts; i++) { - ret = send_packet_to(sock, broadcasts[i], data, length); IP_PORT ip_port = broadcasts[i]; + ip_port.port = port; + ret = send_packet_to(sock, ip_port, data, length); } /** From e2139511eb08051f8f6a07d88cb127ed5d623bbe Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 21 Feb 2026 19:26:18 +0300 Subject: [PATCH 023/159] Use GetAdaptersAddresses to collect broadcast addresses on Windows --- dll/network.cpp | 101 +++++++++++++++--------------------------------- 1 file changed, 31 insertions(+), 70 deletions(-) diff --git a/dll/network.cpp b/dll/network.cpp index 2f8abedcb..90ad69c61 100644 --- a/dll/network.cpp +++ b/dll/network.cpp @@ -32,95 +32,56 @@ static uint32_t upper_range_ips[MAX_BROADCASTS]; #if defined(STEAM_WIN32) -//windows xp support -static int -inet_pton4(const char *src, uint32_t *dst) -{ - static const char digits[] = "0123456789"; - int saw_digit, octets, ch; - u_char tmp[sizeof(uint32_t)], *tp; - - saw_digit = 0; - octets = 0; - *(tp = tmp) = 0; - while ((ch = *src++) != '\0') { - const char *pch; - - if ((pch = strchr(digits, ch)) != NULL) { - size_t nx = *tp * 10 + (pch - digits); - - if (nx > 255) - return (0); - *tp = (u_char) nx; - if (! saw_digit) { - if (++octets > 4) - return (0); - saw_digit = 1; - } - } else if (ch == '.' && saw_digit) { - if (octets == 4) - return (0); - *++tp = 0; - saw_digit = 0; - } else - return (0); - } - if (octets < 4) - return (0); - memcpy(dst, tmp, sizeof(uint32_t)); - return (1); -} - static void get_broadcast_info(uint16 port) { number_broadcasts = 0; - IP_ADAPTER_INFO *pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO)); - unsigned long ulOutBufLen = sizeof(IP_ADAPTER_INFO); - - if (pAdapterInfo == NULL) { + ULONG ulOutBufLen = 16384; + IP_ADAPTER_ADDRESSES *pAdapterInfo = (IP_ADAPTER_ADDRESSES *)malloc(ulOutBufLen); + if (pAdapterInfo == nullptr) { return; } - if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) { - free(pAdapterInfo); - pAdapterInfo = (IP_ADAPTER_INFO *)malloc(ulOutBufLen); + ULONG ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAdapterInfo, &ulOutBufLen); - if (pAdapterInfo == NULL) { + if (ret == ERROR_BUFFER_OVERFLOW) { + free(pAdapterInfo); + pAdapterInfo = (IP_ADAPTER_ADDRESSES *)malloc(ulOutBufLen); + if (pAdapterInfo == nullptr) { return; } - } - int ret; + ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAdapterInfo, &ulOutBufLen); + } - if ((ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) { - IP_ADAPTER_INFO *pAdapter = pAdapterInfo; + if (ret == NO_ERROR) { + for (IP_ADAPTER_ADDRESSES *pAdapter = pAdapterInfo; pAdapter; pAdapter = pAdapter->Next) { + if (pAdapter->OperStatus == IfOperStatusUp && + pAdapter->IfType != IF_TYPE_SOFTWARE_LOOPBACK && + pAdapter->FirstUnicastAddress && + pAdapter->FirstUnicastAddress->OnLinkPrefixLength <= 32) { + sockaddr_in *addr_ptr = (sockaddr_in *)(pAdapter->FirstUnicastAddress->Address.lpSockaddr); + uint32 iface_ip = ntohl(addr_ptr->sin_addr.s_addr); - while (pAdapter) { - uint32_t iface_ip = 0, subnet_mask = 0; + ULONG prefix = pAdapter->FirstUnicastAddress->OnLinkPrefixLength; + uint32 subnet_mask = (prefix == 0) ? 0 : (0xFFFFFFFFu << (32 - prefix)); - if (inet_pton4(pAdapter->IpAddressList.IpMask.String, &subnet_mask) == 1 - && inet_pton4(pAdapter->IpAddressList.IpAddress.String, &iface_ip) == 1) { - IP_PORT *ip_port = &broadcasts[number_broadcasts]; - uint32 broadcast_ip = iface_ip | ~subnet_mask; - ip_port->ip = broadcast_ip; - ip_port->port = port; - lower_range_ips[number_broadcasts] = iface_ip & subnet_mask; - upper_range_ips[number_broadcasts] = broadcast_ip; - number_broadcasts++; + IP_PORT *ip_port = &broadcasts[number_broadcasts]; + uint32 broadcast_ip = htonl(iface_ip | ~subnet_mask); + ip_port->ip = broadcast_ip; + ip_port->port = port; + lower_range_ips[number_broadcasts] = htonl(iface_ip & subnet_mask); + upper_range_ips[number_broadcasts] = broadcast_ip; + number_broadcasts++; - if (number_broadcasts >= MAX_BROADCASTS) { - break; - } + if (number_broadcasts >= MAX_BROADCASTS) { + break; } - - pAdapter = pAdapter->Next; + } } } - if (pAdapterInfo) { - free(pAdapterInfo); - } + free(pAdapterInfo); } #elif defined(__linux__) From 4592ed68438064c6e1b3cb751b673272803f5dae Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Mon, 23 Feb 2026 13:47:22 +0300 Subject: [PATCH 024/159] Organize old SDK functions --- dll/dll.cpp | 20 ++++--- dll/dll/steam_client.h | 50 ++++++++++------ dll/dll/steam_gameserver.h | 16 +++-- dll/dll/steam_user.h | 40 ++++++++----- dll/steam_client.cpp | 111 ++++++++++++++++++----------------- dll/steam_gameserver.cpp | 117 ++++++++++++++++++++----------------- dll/steam_user.cpp | 56 ++++++++++-------- 7 files changed, 234 insertions(+), 176 deletions(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index 04d37bbb7..d3bef7d32 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -1755,13 +1755,7 @@ STEAMCLIENT_API void Steam_TerminateGameConnection( HSteamUser hUser, HSteamPipe } // older sdk ---------------------------------------------------------- -STEAMCLIENT_API HSteamUser Steam3_ConnectToGlobalInstance() -{ - PRINT_DEBUG_ENTRY(); - get_steam_client()->steamclient_version = 4; - return get_steam_client()->ConnectToGlobalInstance(); -} - +// SteamClient002 ----------------------------------------------------- STEAMCLIENT_API HSteamUser Steam3_CreateLocalInstance() { PRINT_DEBUG_ENTRY(); @@ -1890,6 +1884,15 @@ STEAMCLIENT_API void Steam3_Init( HSteamUser hUser, auto [it, _] = capi_cmcallback_map.insert_or_assign(hUser, new_cmcallback); su->Init(&it->second, nullptr); } +// SteamClient002 ----------------------------------------------------- + +// SteamClient004 ----------------------------------------------------- +STEAMCLIENT_API HSteamUser Steam3_ConnectToGlobalInstance() +{ + PRINT_DEBUG_ENTRY(); + get_steam_client()->steamclient_version = 4; + return get_steam_client()->ConnectToGlobalInstance(); +} STEAMCLIENT_API steam_bool Steam3_GSSendUserConnect( HSteamUser hUser, uint64 steamID, uint32 unIPPublic, uint32 unk ) { @@ -1912,7 +1915,7 @@ STEAMCLIENT_API steam_bool Steam3_GSSendUserStatusResponse( HSteamUser hUser, ui return gs->GSSendUserStatusResponse(steamID, nSecondsConnected, nSecondsSinceLast); } -STEAMCLIENT_API steam_bool Steam3_BGetCallback( HSteamUser hUser, int *piCallback, uint8_t **ppubParam, int *unk ) +STEAMCLIENT_API steam_bool Steam3_BGetCallback( HSteamUser hUser, int *piCallback, uint8 **ppubParam, int *unk ) { PRINT_DEBUG_ENTRY(); if (get_steam_client()->steamclient_version != 4) @@ -1947,4 +1950,5 @@ STEAMCLIENT_API void Steam3_SetLocalIPBinding( uint32 unIP, uint16 usLocalPort ) PRINT_DEBUG_ENTRY(); get_steam_client()->SetLocalIPBinding(unIP, usLocalPort); } +// SteamClient004 ----------------------------------------------------- // older sdk ---------------------------------------------------------- diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index e919ff0cd..60ef6818a 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -364,36 +364,52 @@ public ISteamClient // https://github.com/ValveSoftware/Proton/blob/proton_9.0/lsteamclient/steamworks_sdk_099v/isteamclient.h // https://workshop.perforce.com/files/guest/knut_wikstrom/ValveSDKCode/public/steam/isteamclient.h - // creates a global instance of a steam user, so that other processes can share it - // used by the steam UI, to share it's account info/connection with any games it launches - // fails (returns NULL) if an existing instance already exists - HSteamUser CreateGlobalUser( HSteamPipe *phSteamPipe ); - // retrieves the IVac interface associated with the handle - // there is normally only one instance of VAC running, but using this connects it to the right user/account - void *GetIVAC( HSteamUser hSteamUser ); - // returns the name of a universe - const char *GetUniverseName( EUniverse eUniverse ); - void *GetISteamBilling_old( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - + // SteamClient001 ----------------------------------------------------- HSteamUser CreateGlobalInstance(); HSteamUser ConnectToGlobalInstance(); HSteamUser CreateLocalInstance(); void ReleaseInstance( HSteamUser hSteamUser ); ISteamUser *GetISteamUser( HSteamUser hSteamUser, const char *pchVersion ); - ISteamGameServer *GetISteamGameServer( HSteamUser hSteamUser, const char *pchVersion ); - bool BMainLoop( uint64 time ); + // retrieves the IVac interface associated with the handle + // there is normally only one instance of VAC running, but using this connects it to the right user/account + void *GetIVAC( HSteamUser hSteamUser ); bool BMainLoop( uint64 time, bool unk ); - EUniverse GetConnectedUniverse(); - bool BGetCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg, int *unk ); - void FreeLastCallback( HSteamPipe hSteamPipe ); - void SetEUniverse( EUniverse universe ); void Test_SetSpew( const char *unk1, int unk2 ) {} void Test_SetSpewFunc( void *unk ) {} void Test_OverrideIPs( uint32 unIPPublic, uint32 unIPPrivate ) {} void Test_SetServerLoadState( bool unk1, bool unk2 ) {} void Test_SetStressMode( bool unk ) {} int Test_GetStatsVConn() { return 0; } + // SteamClient001 ----------------------------------------------------- + + // SteamClient002 ----------------------------------------------------- void Test_RemoveAllClients() {} + // SteamClient002 ----------------------------------------------------- + + // SteamClient003 ----------------------------------------------------- + bool BMainLoop( uint64 time ); + // SteamClient003 ----------------------------------------------------- + + // SteamClient004 ----------------------------------------------------- + ISteamGameServer *GetISteamGameServer( HSteamUser hSteamUser, const char *pchVersion ); + // SteamClient004 ----------------------------------------------------- + + // SteamClient005 ----------------------------------------------------- + // creates a global instance of a steam user, so that other processes can share it + // used by the steam UI, to share it's account info/connection with any games it launches + // fails (returns NULL) if an existing instance already exists + HSteamUser CreateGlobalUser( HSteamPipe *phSteamPipe ); + EUniverse GetConnectedUniverse(); + // returns the name of a universe + const char *GetUniverseName( EUniverse eUniverse ); + bool BGetCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg, int *unk ); + void FreeLastCallback( HSteamPipe hSteamPipe ); + void SetEUniverse( EUniverse universe ); + // SteamClient005 ----------------------------------------------------- + + // SteamClient006 ----------------------------------------------------- + void *GetISteamBilling_old( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // SteamClient006 ----------------------------------------------------- // older sdk ---------------------------------------------------------- void report_missing_impl(std::string_view itf, std::string_view caller); diff --git a/dll/dll/steam_gameserver.h b/dll/dll/steam_gameserver.h index 7637a0109..85982f738 100644 --- a/dll/dll/steam_gameserver.h +++ b/dll/dll/steam_gameserver.h @@ -377,14 +377,20 @@ public ISteamGameServer // older sdk ----------------------------------------------- + // SteamGameServer001 -------------------------------------- + bool GSSendUserConnect( CSteamID steamID, uint32 unIPPublic, uint32 unk ); + bool GSSendUserDisconnect( CSteamID steamID ); + bool GSSendUserStatusResponse( CSteamID steamID, int nSecondsConnected, int nSecondsSinceLast ); + bool Obsolete_GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ); + // SteamGameServer001 -------------------------------------- + + // SteamGameServer002 -------------------------------------- void GSSetSpawnCount( uint32 ucSpawn ); bool GSGetSteam2GetEncryptionKeyToSendToNewClient( void *pvEncryptionKey, uint32 *pcbEncryptionKey, uint32 cbMaxEncryptionKey ); bool GSSendSteam2UserConnect( uint32 unUserID, const void *pvRawKey, uint32 unKeyLen, uint32 unIPPublic, uint16 usPort, const void *pvCookie, uint32 cubCookie ); bool GSSendSteam3UserConnect( CSteamID steamID, uint32 unIPPublic, const void *pvCookie, uint32 cubCookie ); bool GSRemoveUserConnect( uint32 unUserID ); bool GSSendUserDisconnect( CSteamID steamID, uint32 unUserID ); - bool GSSendUserStatusResponse( CSteamID steamID, int nSecondsConnected, int nSecondsSinceLast ); - bool Obsolete_GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ); bool GSUpdateStatus( int cPlayers, int cPlayersMax, int cBotPlayers, const char *pchServerName, const char *pchMapName ); bool GSSetServerType( int32 nGameAppId, uint32 unServerFlags, uint32 unGameIP, uint32 unGamePort, const char *pchGameDir, const char *pchVersion ); bool GSSetServerType2( int32 nGameAppId, uint32 unServerFlags, uint32 unGameIP, uint16 unGamePort, uint16 unSpectatorPort, uint16 usQueryPort, const char *pchGameDir, const char *pchVersion, bool bLANMode ); @@ -393,14 +399,14 @@ public ISteamGameServer bool GSSetUserData( CSteamID steamID, const char *pPlayerName, uint32 nFrags ); void GSUpdateSpectatorPort( uint16 unSpectatorPort ); void GSSetGameType( const char *pchType ); + // SteamGameServer002 -------------------------------------- + // SteamGameServer003 -------------------------------------- bool GSSendUserConnect( uint32 unUserID, uint32 unIPPublic, uint16 usPort, const void *pvCookie, uint32 cubCookie ); bool GSSetServerType( int32 nGameAppId, uint32 unServerFlags, uint32 unGameIP, uint16 unGamePort, uint16 unSpectatorPort, uint16 usQueryPort, const char *pchGameDir, const char *pchVersion, bool bLANMode ); bool GSUpdateStatus( int cPlayers, int cPlayersMax, int cBotPlayers, const char *pchServerName, const char *pSpectatorServerName, const char *pchMapName ); bool GSGetUserAchievementStatus( CSteamID steamID, const char *pchAchievementName ); - - bool GSSendUserConnect( CSteamID steamID, uint32 unIPPublic, uint32 unk ); - bool GSSendUserDisconnect( CSteamID steamID ); + // SteamGameServer003 -------------------------------------- // older sdk ----------------------------------------------- }; diff --git a/dll/dll/steam_user.h b/dll/dll/steam_user.h index af8eaec08..64845ee8e 100644 --- a/dll/dll/steam_user.h +++ b/dll/dll/steam_user.h @@ -329,8 +329,8 @@ public ISteamUser void RunCallbacks(); // older sdk ----------------------------------------------- + // SteamUser001 -------------------------------------------- void Init( ICMCallback001 *cmcallback, ISteam2Auth *steam2auth ); - void Init( ICMCallback *cmcallback, ISteam2Auth *steam2auth ); int ProcessCall( int unk ); virtual void LogOn( CSteamID *steamID ); virtual int CreateAccount( const char *unk1, void *unk2, void *unk3, const char *unk4, int unk5, void *unk6 ); @@ -338,12 +338,31 @@ public ISteamUser virtual bool GSSendDisconnect( CSteamID *steamID ); virtual bool GSSendStatusResponse( CSteamID *steamID, int nSecondsConnected, int nSecondsSinceLast ); virtual bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax ); + virtual void Test_SuspendActivity() {} + virtual void Test_ResumeActivity() {} + virtual void Test_SendVACResponse( int unk1, void *unk2, int unk3 ) {} + virtual void Test_SetFakePrivateIP( uint32 ip ) {} + virtual void Test_SendBigMessage() {} + virtual bool Test_BBigMessageResponseReceived() { return true; } + virtual void Test_SetPktLossPct( int unk1 ) {} + virtual void Test_SetForceTCP( bool unk1 ) {} + virtual void Test_Heartbeat() {} + virtual void Test_FakeDisconnect() {} + virtual EUniverse Test_GetEUniverse() { return k_EUniversePublic; } + // SteamUser001 -------------------------------------------- + + // SteamUser002 (old) -------------------------------------- virtual bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ); + virtual void Test_SetMaxUDPConnectionAttempts( int unk1 ) {} + // SteamUser002 (old) -------------------------------------- + + // SteamUser002 (new) -------------------------------------- + void Init( ICMCallback *cmcallback, ISteam2Auth *steam2auth ); + // SteamUser002 (new) -------------------------------------- + // SteamUser004 (old) -------------------------------------- bool BGetCallback( int *piCallback, uint8 **ppubParam, int *unk ); void FreeLastCallback(); - int GetSteamTicket( void *pBlob, int cbMaxBlob ); - const char *GetPlayerName(); void SetPlayerName( const char *pchPersonaName ); EPersonaState GetFriendStatus(); @@ -356,19 +375,8 @@ public ISteamUser bool GetFriendGamePlayed( CSteamID steamIDFriend, int32 *pnGameID, uint32 *punGameIP, uint16 *pusGamePort ); const char *GetPlayerName( CSteamID steamIDFriend ); int32 AddFriendByName( const char *pchEmailOrAccountName ); - - virtual void Test_SuspendActivity() {} - virtual void Test_ResumeActivity() {} - virtual void Test_SendVACResponse( int unk1, void *unk2, int unk3 ) {} - virtual void Test_SetFakePrivateIP( uint32 ip ) {} - virtual void Test_SendBigMessage() {} - virtual bool Test_BBigMessageResponseReceived() { return true; } - virtual void Test_SetPktLossPct( int unk1 ) {} - virtual void Test_SetForceTCP( bool unk1 ) {} - virtual void Test_SetMaxUDPConnectionAttempts( int unk1 ) {} - virtual void Test_Heartbeat() {} - virtual void Test_FakeDisconnect() {} - virtual EUniverse Test_GetEUniverse() { return k_EUniversePublic; } + int GetSteamTicket( void *pBlob, int cbMaxBlob ); + // SteamUser004 (old) -------------------------------------- // older sdk ----------------------------------------------- }; diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index fe6170c63..99c9d3db4 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -991,53 +991,7 @@ void Steam_Client::DestroyAllInterfaces() // older sdk ---------------------------------------------------------- -// creates a global instance of a steam user, so that other processes can share it -// used by the steam UI, to share it's account info/connection with any games it launches -// fails (returns NULL) if an existing instance already exists -HSteamUser Steam_Client::CreateGlobalUser( HSteamPipe *phSteamPipe ) -{ - // TODO not sure if this implementation is correct - PRINT_DEBUG_TODO(); - for (const auto& [pipe_handle, pipe_type] : steam_pipes) { - if (pipe_type == Steam_Pipe::CLIENT) { - if (phSteamPipe) *phSteamPipe = pipe_handle; - return 0; - } - } - - HSteamPipe pipe = CreateSteamPipe(); - if (phSteamPipe) *phSteamPipe = pipe; - - steam_pipes[pipe] = Steam_Pipe::CLIENT; - return CLIENT_HSTEAMUSER; -} - -// retrieves the IVac interface associated with the handle -// there is normally only one instance of VAC running, but using this connects it to the right user/account -void *Steam_Client::GetIVAC( HSteamUser hSteamUser ) -{ - PRINT_DEBUG_ENTRY(); - // actual value from steamclient64.dll - return nullptr; -} - -// returns the name of a universe -const char *Steam_Client::GetUniverseName( EUniverse eUniverse ) -{ - PRINT_DEBUG("%i", (int)eUniverse); - // actual values returned by steamclient64.dll - switch (eUniverse) - { - case EUniverse::k_EUniverseInvalid: return "Invalid"; - case EUniverse::k_EUniversePublic: return "Public"; - case EUniverse::k_EUniverseBeta: return "Beta"; - case EUniverse::k_EUniverseInternal: return "Internal"; - case EUniverse::k_EUniverseDev: return "Dev"; - } - - return "Unknown"; -} - +// SteamClient001 ----------------------------------------------------- HSteamUser Steam_Client::CreateGlobalInstance() { PRINT_DEBUG_ENTRY(); @@ -1080,11 +1034,13 @@ ISteamUser *Steam_Client::GetISteamUser( HSteamUser hSteamUser, const char *pchV return GetISteamUser(hSteamUser, pipe, pchVersion); } -ISteamGameServer *Steam_Client::GetISteamGameServer( HSteamUser hSteamUser, const char *pchVersion ) +// retrieves the IVac interface associated with the handle +// there is normally only one instance of VAC running, but using this connects it to the right user/account +void *Steam_Client::GetIVAC( HSteamUser hSteamUser ) { PRINT_DEBUG_ENTRY(); - HSteamPipe pipe = get_pipe_for_user(hSteamUser); - return GetISteamGameServer(hSteamUser, pipe, pchVersion); + // actual value from steamclient64.dll + return nullptr; } bool Steam_Client::BMainLoop( uint64 time, bool unk ) @@ -1093,13 +1049,47 @@ bool Steam_Client::BMainLoop( uint64 time, bool unk ) RunCallbacks(true, true); return true; } +// SteamClient001 ----------------------------------------------------- +// SteamClient003 ----------------------------------------------------- bool Steam_Client::BMainLoop( uint64 time ) { PRINT_DEBUG_ENTRY(); RunCallbacks(true, true); return true; } +// SteamClient003 ----------------------------------------------------- + +// SteamClient004 ----------------------------------------------------- +ISteamGameServer *Steam_Client::GetISteamGameServer( HSteamUser hSteamUser, const char *pchVersion ) +{ + PRINT_DEBUG_ENTRY(); + HSteamPipe pipe = get_pipe_for_user(hSteamUser); + return GetISteamGameServer(hSteamUser, pipe, pchVersion); +} +// SteamClient004 ----------------------------------------------------- + +// SteamClient005 ----------------------------------------------------- +// creates a global instance of a steam user, so that other processes can share it +// used by the steam UI, to share it's account info/connection with any games it launches +// fails (returns NULL) if an existing instance already exists +HSteamUser Steam_Client::CreateGlobalUser( HSteamPipe *phSteamPipe ) +{ + // TODO not sure if this implementation is correct + PRINT_DEBUG_TODO(); + for (const auto &[pipe_handle, pipe_type] : steam_pipes) { + if (pipe_type == Steam_Pipe::CLIENT) { + if (phSteamPipe) *phSteamPipe = pipe_handle; + return 0; + } + } + + HSteamPipe pipe = CreateSteamPipe(); + if (phSteamPipe) *phSteamPipe = pipe; + + steam_pipes[pipe] = Steam_Pipe::CLIENT; + return CLIENT_HSTEAMUSER; +} EUniverse Steam_Client::GetConnectedUniverse() { @@ -1107,9 +1097,20 @@ EUniverse Steam_Client::GetConnectedUniverse() return k_EUniversePublic; } -void Steam_Client::SetEUniverse( EUniverse universe ) +// returns the name of a universe +const char *Steam_Client::GetUniverseName( EUniverse eUniverse ) { - PRINT_DEBUG_TODO(); + PRINT_DEBUG("%i", (int)eUniverse); + // actual values returned by steamclient64.dll + switch (eUniverse) { + case EUniverse::k_EUniverseInvalid: return "Invalid"; + case EUniverse::k_EUniversePublic: return "Public"; + case EUniverse::k_EUniverseBeta: return "Beta"; + case EUniverse::k_EUniverseInternal: return "Internal"; + case EUniverse::k_EUniverseDev: return "Dev"; + } + + return "Unknown"; } bool Steam_Client::BGetCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg, int *unk ) @@ -1124,6 +1125,12 @@ void Steam_Client::FreeLastCallback( HSteamPipe hSteamPipe ) steamclient_free_callback(hSteamPipe); } +void Steam_Client::SetEUniverse( EUniverse universe ) +{ + PRINT_DEBUG_TODO(); +} +// SteamClient005 ----------------------------------------------------- + HSteamPipe Steam_Client::get_pipe_for_user(HSteamUser hUser) { if (hUser == CLIENT_HSTEAMUSER) { diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index 114b0e04a..64b57fafa 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -889,6 +889,65 @@ void Steam_GameServer::RunCallbacks() // older sdk ----------------------------------------------- +// SteamGameServer001 -------------------------------------- +bool Steam_GameServer::GSSendUserConnect( CSteamID steamID, uint32 unIPPublic, uint32 unk ) +{ + PRINT_DEBUG("%llu %u %u", steamID.ConvertToUint64(), unIPPublic, unk); + std::lock_guard lock(global_mutex); + + // At this point, client auth is still primarily done via Steam2 (Steam.dll), Steam3 is supplementary. + // As such, there's not much really needed from us besides firing approve callback. + // Since SteamID passed into this comes from Steam.dll, it won't match the client's Goldberg SteamID. + GSClientApprove_t data{}; + data.m_SteamID = data.m_OwnerSteamID = steamID; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + add_player(steamID); + return true; +} + +bool Steam_GameServer::GSSendUserDisconnect( CSteamID steamID ) +{ + PRINT_DEBUG("%llu", steamID.ConvertToUint64()); + std::lock_guard lock(global_mutex); + + remove_player(steamID); + return true; +} + +bool Steam_GameServer::GSSendUserStatusResponse( CSteamID steamID, int nSecondsConnected, int nSecondsSinceLast ) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + return false; +} + +bool Steam_GameServer::Obsolete_GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + server_data.set_appid(nAppIdServed); + server_data.set_num_players(cPlayers); + server_data.set_max_player_count(cPlayersMax); + server_data.set_bot_player_count(cBotPlayers); + server_data.set_port(unGamePort); + server_data.set_query_port(0xFFFF); + server_data.set_server_name(pchServerName); + server_data.set_spectator_server_name(""); + server_data.set_game_dir(pchGameDir ? pchGameDir : ""); + server_data.set_map_name(pchMapName); + set_version(pchVersion); + server_data.set_offline(false); + + if (!settings->get_local_game_id().AppID()) settings->set_game_id(CGameID(nAppIdServed)); + flags = unServerFlags; + + return true; +} +// SteamGameServer001 -------------------------------------- + +// SteamGameServer002 -------------------------------------- void Steam_GameServer::GSSetSpawnCount( uint32 ucSpawn ) { PRINT_DEBUG_TODO(); @@ -950,38 +1009,6 @@ bool Steam_GameServer::GSSendUserDisconnect( CSteamID steamID, uint32 unUserID ) return true; } -bool Steam_GameServer::GSSendUserStatusResponse( CSteamID steamID, int nSecondsConnected, int nSecondsSinceLast ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return false; -} - -bool Steam_GameServer::Obsolete_GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - server_data.set_appid(nAppIdServed); - server_data.set_num_players(cPlayers); - server_data.set_max_player_count(cPlayersMax); - server_data.set_bot_player_count(cBotPlayers); - server_data.set_port(unGamePort); - server_data.set_query_port(0xFFFF); - server_data.set_server_name(pchServerName); - server_data.set_spectator_server_name(""); - server_data.set_game_dir(pchGameDir ? pchGameDir : ""); - server_data.set_map_name(pchMapName); - set_version(pchVersion); - server_data.set_offline(false); - - if (!settings->get_local_game_id().AppID()) settings->set_game_id(CGameID(nAppIdServed)); - flags = unServerFlags; - - return true; -} - bool Steam_GameServer::GSUpdateStatus( int cPlayers, int cPlayersMax, int cBotPlayers, const char *pchServerName, const char *pchMapName ) { PRINT_DEBUG_ENTRY(); @@ -1054,7 +1081,9 @@ void Steam_GameServer::GSSetGameType( const char *pchType ) SetGameType(pchType); } +// SteamGameServer002 -------------------------------------- +// SteamGameServer003 -------------------------------------- bool Steam_GameServer::GSSendUserConnect( uint32 unUserID, uint32 unIPPublic, uint16 usPort, const void *pvCookie, uint32 cubCookie ) { PRINT_DEBUG_ENTRY(); @@ -1098,27 +1127,5 @@ bool Steam_GameServer::GSGetUserAchievementStatus( CSteamID steamID, const char return BGetUserAchievementStatus(steamID, pchAchievementName); } - -bool Steam_GameServer::GSSendUserConnect( CSteamID steamID, uint32 unIPPublic, uint32 unk ) -{ - PRINT_DEBUG("%llu %u %u", steamID.ConvertToUint64(), unIPPublic, unk); - std::lock_guard lock(global_mutex); - - // Note that SteamID passed into this comes from Steam.dll so it won't match the client's Goldberg SteamID. - // POINTLESS TO USE AUTH MANAGER. - GSClientApprove_t data{}; - data.m_SteamID = data.m_OwnerSteamID = steamID; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - add_player(steamID); - return true; -} - -bool Steam_GameServer::GSSendUserDisconnect( CSteamID steamID ) -{ - PRINT_DEBUG("%llu", steamID.ConvertToUint64()); - std::lock_guard lock(global_mutex); - - remove_player(steamID); - return true; -} +// SteamGameServer003 -------------------------------------- // older sdk ----------------------------------------------- diff --git a/dll/steam_user.cpp b/dll/steam_user.cpp index 36fe56054..acdae4ff9 100644 --- a/dll/steam_user.cpp +++ b/dll/steam_user.cpp @@ -906,18 +906,13 @@ bool Steam_User::BSetDurationControlOnlineState( EDurationControlOnlineState eNe } // older sdk ----------------------------------------------- +// SteamUser001 -------------------------------------------- void Steam_User::Init( ICMCallback001 *cmcallback, ISteam2Auth *steam2auth ) { PRINT_DEBUG_ENTRY(); callbacks_old1 = cmcallback; } -void Steam_User::Init( ICMCallback *cmcallback, ISteam2Auth *steam2auth ) -{ - PRINT_DEBUG_ENTRY(); - callbacks_old2 = cmcallback; -} - int Steam_User::ProcessCall( int unk ) { PRINT_DEBUG_TODO(); @@ -941,7 +936,9 @@ bool Steam_User::GSSendLogonRequest( CSteamID *steamID ) PRINT_DEBUG("%llu", (*steamID).ConvertToUint64()); std::lock_guard lock(global_mutex); - // Note that SteamID passed into this comes from Steam.dll so it won't match the client's Goldberg SteamID. + // At this point, client auth is still primarily done via Steam2 (Steam.dll), Steam3 is supplementary. + // As such, there's not much really needed from us besides firing approve callback. + // Since SteamID passed into this comes from Steam.dll, it won't match the client's Goldberg SteamID. std::pair entry(*steamID, std::chrono::high_resolution_clock::now()); player_auths.push_back(entry); get_steam_client()->steam_gameserver->add_player(*steamID); @@ -968,13 +965,25 @@ bool Steam_User::GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPla PRINT_DEBUG_TODO(); return true; } +// SteamUser001 -------------------------------------------- +// SteamUser002 (old) -------------------------------------- bool Steam_User::GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ) { PRINT_DEBUG_ENTRY(); return get_steam_client()->steam_gameserver->Obsolete_GSSetStatus(nAppIdServed, unServerFlags, cPlayers, cPlayersMax, cBotPlayers, unGamePort, pchServerName, pchGameDir, pchMapName, pchVersion); } +// SteamUser002 (old) -------------------------------------- + +// SteamUser002 (new) -------------------------------------- +void Steam_User::Init( ICMCallback *cmcallback, ISteam2Auth *steam2auth ) +{ + PRINT_DEBUG_ENTRY(); + callbacks_old2 = cmcallback; +} +// SteamUser002 (new) -------------------------------------- +// SteamUser004 (old) -------------------------------------- bool Steam_User::BGetCallback( int *piCallback, uint8 **ppubParam, int *unk ) { PRINT_DEBUG_ENTRY(); @@ -1003,22 +1012,6 @@ void Steam_User::FreeLastCallback() steamclient_free_callback(pipe); } -int Steam_User::GetSteamTicket( void *pBlob, int cbMaxBlob ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - if (cbMaxBlob < STEAM_TICKET_MIN_SIZE) return 0; - if (!pBlob) return 0; - - uint32 out_size = STEAM_AUTH_TICKET_SIZE; - auth_manager->getTicketData(pBlob, cbMaxBlob, &out_size); - - if (out_size > STEAM_AUTH_TICKET_SIZE) - return 0; - return out_size; -} - const char *Steam_User::GetPlayerName() { PRINT_DEBUG_ENTRY(); @@ -1090,6 +1083,23 @@ int32 Steam_User::AddFriendByName( const char *pchEmailOrAccountName ) PRINT_DEBUG_ENTRY(); return get_steam_client()->steam_friends->AddFriendByName(pchEmailOrAccountName); } + +int Steam_User::GetSteamTicket( void *pBlob, int cbMaxBlob ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + if (cbMaxBlob < STEAM_TICKET_MIN_SIZE) return 0; + if (!pBlob) return 0; + + uint32 out_size = STEAM_AUTH_TICKET_SIZE; + auth_manager->getTicketData(pBlob, cbMaxBlob, &out_size); + + if (out_size > STEAM_AUTH_TICKET_SIZE) + return 0; + return out_size; +} +// SteamUser004 (old) -------------------------------------- // older sdk ----------------------------------------------- void Steam_User::RunCallbacks() From 4c5e36f35af608071ab663dfa8681c548c05370d Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Tue, 24 Feb 2026 15:56:52 +0300 Subject: [PATCH 025/159] Fix pending request getting deleted while still in use --- dll/steam_gameserverstats.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dll/steam_gameserverstats.cpp b/dll/steam_gameserverstats.cpp index b96a8bafb..e3078e813 100644 --- a/dll/steam_gameserverstats.cpp +++ b/dll/steam_gameserverstats.cpp @@ -491,9 +491,6 @@ void Steam_GameServerStats::network_callback_initial_stats(Common_Message *msg) PRINT_DEBUG("error got all player stats but pending request timedout/removed (doesn't exist)"); return; } - - // remove this pending request - pending_RequestUserStats.erase(it); // copy new stats auto ¤t_stats = all_users_data[user_steamid].stats; @@ -515,6 +512,9 @@ void Steam_GameServerStats::network_callback_initial_stats(Common_Message *msg) callback_results->addCallResult(it->steamAPICall, data.k_iCallback, &data, sizeof(data)); callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + // remove this pending request + pending_RequestUserStats.erase(it); PRINT_DEBUG("server got all player stats %llu: %zu stats, %zu achievements", user_steamid, all_users_data[user_steamid].stats.size(), all_users_data[user_steamid].achievements.size() From 6c847e0259764a12450a6e6339a5efe573209113 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Tue, 24 Feb 2026 15:57:24 +0300 Subject: [PATCH 026/159] Fully implement Steam_GameServer::BGetUserAchievementStatus --- dll/dll/steam_gameserverstats.h | 12 +++ dll/dll/steam_user_stats.h | 1 + dll/net.proto | 16 +++- dll/steam_gameserver.cpp | 10 +-- dll/steam_gameserverstats.cpp | 130 +++++++++++++++++++++++++++++++- dll/steam_user_stats_stats.cpp | 39 ++++++++++ 6 files changed, 196 insertions(+), 12 deletions(-) diff --git a/dll/dll/steam_gameserverstats.h b/dll/dll/steam_gameserverstats.h index 31cb431ba..f1022396f 100644 --- a/dll/dll/steam_gameserverstats.h +++ b/dll/dll/steam_gameserverstats.h @@ -40,6 +40,14 @@ public ISteamGameServerStats bool timeout = false; }; + struct RequestAchievement { + std::chrono::high_resolution_clock::time_point created{}; + CSteamID steamIDUser{}; + std::string achievement_name; + + bool timeout = false; + }; + struct CachedStat { bool dirty = false; // true means it was changed on the server and should be sent to the user StatInfo stat{}; @@ -55,6 +63,7 @@ public ISteamGameServerStats }; std::vector pending_RequestUserStats{}; + std::vector pending_RequestAchievement{}; std::map all_users_data{}; CachedStat* find_stat(CSteamID steamIDUser, const std::string &key); @@ -67,6 +76,7 @@ public ISteamGameServerStats // reponses from player void network_callback_initial_stats(Common_Message *msg); void network_callback_updated_stats(Common_Message *msg); + void network_callback_achievement_response(Common_Message *msg); void network_callback(Common_Message *msg); // user connect/disconnect @@ -79,6 +89,8 @@ public ISteamGameServerStats public: Steam_GameServerStats(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); ~Steam_GameServerStats(); + + bool request_user_achievement(CSteamID steamIDUser, const char *ach_name); // downloads stats for the user // returns a GSStatsReceived_t callback when completed diff --git a/dll/dll/steam_user_stats.h b/dll/dll/steam_user_stats.h index 55eac3206..9c6592450 100644 --- a/dll/dll/steam_user_stats.h +++ b/dll/dll/steam_user_stats.h @@ -157,6 +157,7 @@ public ISteamUserStats // requests from server void network_stats_initial(Common_Message *msg); void network_stats_updated(Common_Message *msg); + void network_achievement_request(Common_Message *msg); void network_callback_stats(Common_Message *msg); // requests from other users to share leaderboards diff --git a/dll/net.proto b/dll/net.proto index 052531eb3..f5ea2ec94 100644 --- a/dll/net.proto +++ b/dll/net.proto @@ -254,6 +254,15 @@ message GameServerStats_Messages { // optional because the server send doesn't send any data, just steam api call id optional AllStats all_data = 2; } + + message AchievementRequest { + string name = 1; + } + message AchievementResponse { + string name = 1; + AchievementInfo info = 2; + } + // Request_: from Steam_GameServerStats // Response_: from Steam_User_Stats enum Types { @@ -262,12 +271,17 @@ message GameServerStats_Messages { UpdateUserStatsFromServer = 2; // sent by Steam_GameServerStats UpdateUserStatsFromUser = 3; // sent by Steam_User_Stats + + Request_UserAchievement = 4; + Response_UserAchievement = 5; } - Types type = 1; + Types type = 1; oneof data_messages { InitialAllStats initial_user_stats = 2; AllStats update_user_stats = 3; + AchievementRequest achievement_request = 4; + AchievementResponse achievement_response = 5; } } diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index 64b57fafa..bfa86488a 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -17,6 +17,7 @@ #include "dll/steam_gameserver.h" #include "dll/source_query.h" +#include "dll/dll.h" #define SEND_SERVER_RATE 5.0 @@ -568,15 +569,10 @@ void Steam_GameServer::SetGameType( const char *pchGameType ) // Ask if a user has a specific achievement for this game, will get a callback on reply bool Steam_GameServer::BGetUserAchievementStatus( CSteamID steamID, const char *pchAchievementName ) { - PRINT_DEBUG("%llu %s", steamID.ConvertToUint64(), pchAchievementName); + PRINT_DEBUG("%llu '%s'", steamID.ConvertToUint64(), pchAchievementName); std::lock_guard lock(global_mutex); - GSClientAchievementStatus_t data = {}; - data.m_bUnlocked = true; - data.m_SteamID = steamID.ConvertToUint64(); - strncpy(data.m_pchAchievement, pchAchievementName, sizeof(data.m_pchAchievement) - 1); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return true; + return get_steam_client()->steam_gameserverstats->request_user_achievement(steamID, pchAchievementName); } // New auth system APIs - do not mix with the old auth system APIs. diff --git a/dll/steam_gameserverstats.cpp b/dll/steam_gameserverstats.cpp index e3078e813..ec4693a03 100644 --- a/dll/steam_gameserverstats.cpp +++ b/dll/steam_gameserverstats.cpp @@ -106,6 +106,45 @@ Steam_GameServerStats::~Steam_GameServerStats() } } +bool Steam_GameServerStats::request_user_achievement(CSteamID steamIDUser, const char *ach_name) +{ + std::lock_guard lock(global_mutex); + if (settings->disable_sharing_stats_with_gameserver) return false; + if (!ach_name) return false; + + auto ach = find_ach(steamIDUser, ach_name); + if (ach) { + GSClientAchievementStatus_t data{}; + data.m_bUnlocked = ach->ach.achieved(); + data.m_SteamID = steamIDUser.ConvertToUint64(); + strncpy(data.m_pchAchievement, ach_name, sizeof(data.m_pchAchievement) - 1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return true; + } + + struct RequestAchievement new_request{}; + new_request.created = std::chrono::high_resolution_clock::now(); + new_request.steamIDUser = steamIDUser; + new_request.achievement_name = ach_name; + + pending_RequestAchievement.push_back(new_request); + + auto ach_request_msg = new GameServerStats_Messages::AchievementRequest(); + ach_request_msg->set_name(ach_name); + + auto gameserverstats_msg = new GameServerStats_Messages(); + gameserverstats_msg->set_type(GameServerStats_Messages::Request_UserAchievement); + gameserverstats_msg->set_allocated_achievement_request(ach_request_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_stats_messages(gameserverstats_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(steamIDUser.ConvertToUint64()); + network->sendTo(&msg, true); + + return true; +} + // downloads stats for the user // returns a GSStatsReceived_t callback when completed @@ -363,8 +402,6 @@ SteamAPICall_t Steam_GameServerStats::StoreUserStats( CSteamID steamIDUser ) void Steam_GameServerStats::remove_timedout_userstats_requests() { - if (pending_RequestUserStats.empty()) return; - // send all pending RequestUserStats() requests for (auto &pendReq : pending_RequestUserStats) { if (check_timedout(pendReq.created, PENDING_RequestUserStats_TIMEOUT)) { @@ -387,6 +424,28 @@ void Steam_GameServerStats::remove_timedout_userstats_requests() [](const struct RequestAllStats &item) { return item.timeout; } ); pending_RequestUserStats.erase(itrm, pending_RequestUserStats.end()); + + // send all pending BGetUserAchievementStatus requests + for (auto &pendReq : pending_RequestAchievement) { + if (check_timedout(pendReq.created, PENDING_RequestUserStats_TIMEOUT)) { + pendReq.timeout = true; + + GSClientAchievementStatus_t data{}; + data.m_bUnlocked = false; + data.m_SteamID = pendReq.steamIDUser.ConvertToUint64(); + pendReq.achievement_name.copy(data.m_pchAchievement, sizeof(data.m_pchAchievement) - 1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + PRINT_DEBUG("BGetUserAchievementStatus timeout, %llu", pendReq.steamIDUser.ConvertToUint64()); + } + } + + // remove all timedout requests + auto itrm2 = std::remove_if( + pending_RequestAchievement.begin(), pending_RequestAchievement.end(), + [](const struct RequestAchievement &item) { return item.timeout; } + ); + pending_RequestAchievement.erase(itrm2, pending_RequestAchievement.end()); } void Steam_GameServerStats::collect_and_send_updated_user_stats() @@ -519,8 +578,6 @@ void Steam_GameServerStats::network_callback_initial_stats(Common_Message *msg) PRINT_DEBUG("server got all player stats %llu: %zu stats, %zu achievements", user_steamid, all_users_data[user_steamid].stats.size(), all_users_data[user_steamid].achievements.size() ); - - } // user has updated/new stats @@ -556,6 +613,50 @@ void Steam_GameServerStats::network_callback_updated_stats(Common_Message *msg) ); } +// user responded with achievement status +void Steam_GameServerStats::network_callback_achievement_response(Common_Message *msg) +{ + uint64 user_steamid = msg->source_id(); + + PRINT_DEBUG("player returned achievement %llu", user_steamid); + if (!msg->gameserver_stats_messages().has_achievement_response()) { + PRINT_DEBUG("error empty msg"); + return; + } + + const auto &ach_response_msg = msg->gameserver_stats_messages().achievement_response(); + std::string ach_name = ach_response_msg.name(); + const auto &ach_info = ach_response_msg.info(); + + // find this pending request + auto it = std::find_if( + pending_RequestAchievement.begin(), pending_RequestAchievement.end(), + [=](const RequestAchievement &item) { + return item.steamIDUser == user_steamid && + item.achievement_name == ach_name; + } + ); + if (pending_RequestAchievement.end() == it) { // timeout and already removed + PRINT_DEBUG("error got player achievement but pending request timedout/removed (doesn't exist)"); + return; + } + + all_users_data[user_steamid].achievements[ach_name].ach = ach_info; + + GSClientAchievementStatus_t data{}; + data.m_bUnlocked = ach_info.achieved(); + data.m_SteamID = user_steamid; + ach_name.copy(data.m_pchAchievement, sizeof(data.m_pchAchievement) - 1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + // remove this pending request + pending_RequestAchievement.erase(it); + + PRINT_DEBUG("server got player achievement %llu: '%s' %s", + user_steamid, ach_name.c_str(), ach_info.achieved() ? "earned" : "unearned" + ); +} + // only triggered when we have a message void Steam_GameServerStats::network_callback(Common_Message *msg) { @@ -573,6 +674,11 @@ void Steam_GameServerStats::network_callback(Common_Message *msg) case GameServerStats_Messages::UpdateUserStatsFromUser: network_callback_updated_stats(msg); break; + + // user responded with achievement status + case GameServerStats_Messages::Response_UserAchievement: + network_callback_achievement_response(msg); + break; default: PRINT_DEBUG("unhandled type %i", (int)msg->gameserver_stats_messages().type()); @@ -596,6 +702,7 @@ void Steam_GameServerStats::network_callback_low_level(Common_Message *msg) case Low_Level::DISCONNECT: { all_users_data.erase(steamid); + auto it_rm = std::remove_if( pending_RequestUserStats.begin(), pending_RequestUserStats.end(), [=](const RequestAllStats &item) { return item.steamIDUser.ConvertToUint64() == steamid; } @@ -611,6 +718,21 @@ void Steam_GameServerStats::network_callback_low_level(Common_Message *msg) PRINT_DEBUG("RequestUserStats timeout, %llu", it_rm->steamIDUser.ConvertToUint64()); it_rm = pending_RequestUserStats.erase(it_rm); } + + auto it_rm2 = std::remove_if( + pending_RequestAchievement.begin(), pending_RequestAchievement.end(), + [=](const RequestAchievement &item) { return item.steamIDUser.ConvertToUint64() == steamid; } + ); + while (pending_RequestAchievement.end() != it_rm2) { + GSClientAchievementStatus_t data{}; + data.m_bUnlocked = false; + data.m_SteamID = it_rm2->steamIDUser.ConvertToUint64(); + it_rm2->achievement_name.copy(data.m_pchAchievement, sizeof(data.m_pchAchievement) - 1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + PRINT_DEBUG("BGetUserAchievementStatus timeout, %llu", it_rm2->steamIDUser.ConvertToUint64()); + it_rm2 = pending_RequestAchievement.erase(it_rm2); + } // PRINT_DEBUG("removed user %llu", steamid); } diff --git a/dll/steam_user_stats_stats.cpp b/dll/steam_user_stats_stats.cpp index fc1ba63e0..e24079be8 100644 --- a/dll/steam_user_stats_stats.cpp +++ b/dll/steam_user_stats_stats.cpp @@ -899,6 +899,40 @@ void Steam_User_Stats::network_stats_updated(Common_Message *msg) ); } +// server requested achievement status +void Steam_User_Stats::network_achievement_request(Common_Message *msg) +{ + if (!msg->gameserver_stats_messages().has_achievement_request()) { + PRINT_DEBUG("error empty msg"); + return; + } + + uint64 server_steamid = msg->source_id(); + + const auto &ach_request_msg = msg->gameserver_stats_messages().achievement_request(); + std::string ach_name = ach_request_msg.name(); + bool achieved = false; + GetAchievement(ach_name.c_str(), &achieved); + + auto ach_response_msg = new GameServerStats_Messages::AchievementResponse(); + ach_response_msg->set_name(ach_name); + auto ach_info = new GameServerStats_Messages::AchievementInfo(); + ach_info->set_achieved(achieved); + ach_response_msg->set_allocated_info(ach_info); + + auto gameserverstats_msg = new GameServerStats_Messages(); + gameserverstats_msg->set_type(GameServerStats_Messages::Response_UserAchievement); + gameserverstats_msg->set_allocated_achievement_response(ach_response_msg); + + Common_Message new_msg{}; + 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 achievement '%s'", ach_name.c_str()); +} + void Steam_User_Stats::send_pending_user_stats_requests() { if (!pending_user_stats_requests.empty()) { @@ -1007,6 +1041,11 @@ void Steam_User_Stats::network_callback_stats(Common_Message *msg) case GameServerStats_Messages::UpdateUserStatsFromUser: // nothing break; + + // server requested achievement status + case GameServerStats_Messages::Request_UserAchievement: + network_achievement_request(msg); + break; default: PRINT_DEBUG("unhandled type %i", (int)msg->gameserver_stats_messages().type()); From ae49acc163815b42c0e2b23a1422579bb59131e6 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Thu, 26 Feb 2026 17:03:15 +0300 Subject: [PATCH 027/159] Fix pending request cleanup in Steam_GameServerStats --- dll/dll/steam_gameserverstats.h | 2 - dll/steam_gameserverstats.cpp | 100 ++++++++++++++------------------ 2 files changed, 44 insertions(+), 58 deletions(-) diff --git a/dll/dll/steam_gameserverstats.h b/dll/dll/steam_gameserverstats.h index f1022396f..21f894c9b 100644 --- a/dll/dll/steam_gameserverstats.h +++ b/dll/dll/steam_gameserverstats.h @@ -36,8 +36,6 @@ public ISteamGameServerStats std::chrono::high_resolution_clock::time_point created{}; SteamAPICall_t steamAPICall{}; CSteamID steamIDUser{}; - - bool timeout = false; }; struct RequestAchievement { diff --git a/dll/steam_gameserverstats.cpp b/dll/steam_gameserverstats.cpp index ec4693a03..c6243490a 100644 --- a/dll/steam_gameserverstats.cpp +++ b/dll/steam_gameserverstats.cpp @@ -403,49 +403,37 @@ SteamAPICall_t Steam_GameServerStats::StoreUserStats( CSteamID steamIDUser ) void Steam_GameServerStats::remove_timedout_userstats_requests() { // send all pending RequestUserStats() requests - for (auto &pendReq : pending_RequestUserStats) { - if (check_timedout(pendReq.created, PENDING_RequestUserStats_TIMEOUT)) { - pendReq.timeout = true; - + for (auto it = pending_RequestUserStats.begin(); it != pending_RequestUserStats.end();) { + if (check_timedout(it->created, PENDING_RequestUserStats_TIMEOUT)) { GSStatsReceived_t data{}; data.m_eResult = k_EResultTimeout; - data.m_steamIDUser = pendReq.steamIDUser; + data.m_steamIDUser = it->steamIDUser; - callback_results->addCallResult(pendReq.steamAPICall, data.k_iCallback, &data, sizeof(data)); + callback_results->addCallResult(it->steamAPICall, data.k_iCallback, &data, sizeof(data)); callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - - PRINT_DEBUG("RequestUserStats timeout, %llu", pendReq.steamIDUser.ConvertToUint64()); + + PRINT_DEBUG("RequestUserStats timeout, %llu", it->steamIDUser.ConvertToUint64()); + it = pending_RequestUserStats.erase(it); + } else { + it++; } } - // remove all timedout requests - auto itrm = std::remove_if( - pending_RequestUserStats.begin(), pending_RequestUserStats.end(), - [](const struct RequestAllStats &item) { return item.timeout; } - ); - pending_RequestUserStats.erase(itrm, pending_RequestUserStats.end()); - // send all pending BGetUserAchievementStatus requests - for (auto &pendReq : pending_RequestAchievement) { - if (check_timedout(pendReq.created, PENDING_RequestUserStats_TIMEOUT)) { - pendReq.timeout = true; - + for (auto it = pending_RequestAchievement.begin(); it != pending_RequestAchievement.end();) { + if (check_timedout(it->created, PENDING_RequestUserStats_TIMEOUT)) { GSClientAchievementStatus_t data{}; data.m_bUnlocked = false; - data.m_SteamID = pendReq.steamIDUser.ConvertToUint64(); - pendReq.achievement_name.copy(data.m_pchAchievement, sizeof(data.m_pchAchievement) - 1); + data.m_SteamID = it->steamIDUser.ConvertToUint64(); + it->achievement_name.copy(data.m_pchAchievement, sizeof(data.m_pchAchievement) - 1); callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - PRINT_DEBUG("BGetUserAchievementStatus timeout, %llu", pendReq.steamIDUser.ConvertToUint64()); + PRINT_DEBUG("BGetUserAchievementStatus timeout, %llu", it->steamIDUser.ConvertToUint64()); + it = pending_RequestAchievement.erase(it); + } else { + it++; } } - - // remove all timedout requests - auto itrm2 = std::remove_if( - pending_RequestAchievement.begin(), pending_RequestAchievement.end(), - [](const struct RequestAchievement &item) { return item.timeout; } - ); - pending_RequestAchievement.erase(itrm2, pending_RequestAchievement.end()); } void Steam_GameServerStats::collect_and_send_updated_user_stats() @@ -703,35 +691,35 @@ void Steam_GameServerStats::network_callback_low_level(Common_Message *msg) case Low_Level::DISCONNECT: { all_users_data.erase(steamid); - auto it_rm = std::remove_if( - pending_RequestUserStats.begin(), pending_RequestUserStats.end(), - [=](const RequestAllStats &item) { return item.steamIDUser.ConvertToUint64() == steamid; } - ); - while (pending_RequestUserStats.end() != it_rm) { - GSStatsReceived_t data{}; - data.m_eResult = k_EResultTimeout; - data.m_steamIDUser = it_rm->steamIDUser; - - callback_results->addCallResult(it_rm->steamAPICall, data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - - PRINT_DEBUG("RequestUserStats timeout, %llu", it_rm->steamIDUser.ConvertToUint64()); - it_rm = pending_RequestUserStats.erase(it_rm); - } + for (auto it = pending_RequestUserStats.begin(); it != pending_RequestUserStats.end();) { + if (steamid == it->steamIDUser.ConvertToUint64()) { + GSStatsReceived_t data{}; + data.m_eResult = k_EResultTimeout; + data.m_steamIDUser = it->steamIDUser; - auto it_rm2 = std::remove_if( - pending_RequestAchievement.begin(), pending_RequestAchievement.end(), - [=](const RequestAchievement &item) { return item.steamIDUser.ConvertToUint64() == steamid; } - ); - while (pending_RequestAchievement.end() != it_rm2) { - GSClientAchievementStatus_t data{}; - data.m_bUnlocked = false; - data.m_SteamID = it_rm2->steamIDUser.ConvertToUint64(); - it_rm2->achievement_name.copy(data.m_pchAchievement, sizeof(data.m_pchAchievement) - 1); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + callback_results->addCallResult(it->steamAPICall, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + PRINT_DEBUG("RequestUserStats timeout, %llu", it->steamIDUser.ConvertToUint64()); + it = pending_RequestUserStats.erase(it); + } else { + it++; + } + } - PRINT_DEBUG("BGetUserAchievementStatus timeout, %llu", it_rm2->steamIDUser.ConvertToUint64()); - it_rm2 = pending_RequestAchievement.erase(it_rm2); + for (auto it = pending_RequestAchievement.begin(); it != pending_RequestAchievement.end();) { + if (steamid == it->steamIDUser.ConvertToUint64()) { + GSClientAchievementStatus_t data{}; + data.m_bUnlocked = false; + data.m_SteamID = it->steamIDUser.ConvertToUint64(); + it->achievement_name.copy(data.m_pchAchievement, sizeof(data.m_pchAchievement) - 1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + PRINT_DEBUG("BGetUserAchievementStatus timeout, %llu", it->steamIDUser.ConvertToUint64()); + it = pending_RequestAchievement.erase(it); + } else { + it++; + } } // PRINT_DEBUG("removed user %llu", steamid); From f631f477a70791a15175c2fe743d04501f6c2b80 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Thu, 26 Feb 2026 17:03:27 +0300 Subject: [PATCH 028/159] Add "definition" field to items.json parser --- dll/steam_inventory.cpp | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/dll/steam_inventory.cpp b/dll/steam_inventory.cpp index fcdbbe494..b00c389db 100644 --- a/dll/steam_inventory.cpp +++ b/dll/steam_inventory.cpp @@ -160,16 +160,9 @@ bool Steam_Inventory::GetResultItems( SteamInventoryResult_t resultHandle, // 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("quantity", static_cast(0)); - } - catch (...) - { - pOutItemsArray->m_unQuantity = 0; - } + pOutItemsArray->m_itemId = std::stoi(i.key()); + pOutItemsArray->m_iDefinition = i->value("definition", static_cast(pOutItemsArray->m_itemId)); + pOutItemsArray->m_unQuantity = i->value("quantity", static_cast(0)); pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; ++pOutItemsArray; } @@ -178,17 +171,9 @@ bool Steam_Inventory::GetResultItems( SteamInventoryResult_t resultHandle, if (!max_items) break; auto it = user_items.find(std::to_string(itemid)); if (it != user_items.end()) { - pOutItemsArray->m_iDefinition = static_cast(itemid); pOutItemsArray->m_itemId = itemid; - - try - { - pOutItemsArray->m_unQuantity = it->value("quantity", static_cast(0)); - } - catch (...) - { - pOutItemsArray->m_unQuantity = 0; - } + pOutItemsArray->m_iDefinition = it->value("definition", static_cast(itemid)); + pOutItemsArray->m_unQuantity = it->value("quantity", static_cast(0)); pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; ++pOutItemsArray; --max_items; From 0aa25547d37872f9952257454d9a40c51d7387a6 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Thu, 26 Feb 2026 17:43:31 +0300 Subject: [PATCH 029/159] Add legal note where it was missing --- dll/auth.cpp | 17 +++++++++++++++++ dll/client_known_interfaces.cpp | 17 +++++++++++++++++ dll/dll/auth.h | 17 +++++++++++++++++ dll/dll/capicmcallback.h | 17 +++++++++++++++++ dll/dll/client_known_interfaces.h | 17 +++++++++++++++++ dll/dll/steam_app_ids.h | 17 +++++++++++++++++ dll/steam_app_ids.cpp | 17 +++++++++++++++++ dll/ugc_remote_storage_bridge.cpp | 17 +++++++++++++++++ dll/voicechat.cpp | 17 +++++++++++++++++ 9 files changed, 153 insertions(+) diff --git a/dll/auth.cpp b/dll/auth.cpp index 7f9fdaab1..1d7043450 100644 --- a/dll/auth.cpp +++ b/dll/auth.cpp @@ -1,3 +1,20 @@ +/* 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/auth.h" #define STEAM_TICKET_PROCESS_TIME 0.03 diff --git a/dll/client_known_interfaces.cpp b/dll/client_known_interfaces.cpp index f1d92b303..8b06d218f 100644 --- a/dll/client_known_interfaces.cpp +++ b/dll/client_known_interfaces.cpp @@ -1,3 +1,20 @@ +/* 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/client_known_interfaces.h" /* diff --git a/dll/dll/auth.h b/dll/dll/auth.h index 0a15b72b6..663ef4184 100644 --- a/dll/dll/auth.h +++ b/dll/dll/auth.h @@ -1,3 +1,20 @@ +/* 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 + . */ + // source: https://github.com/Detanup01/stmsrv/blob/main/Steam3Server/Others/AppTickets.cs // thanks Detanup01 diff --git a/dll/dll/capicmcallback.h b/dll/dll/capicmcallback.h index 5a8dd5be5..d42355f5a 100644 --- a/dll/dll/capicmcallback.h +++ b/dll/dll/capicmcallback.h @@ -1,3 +1,20 @@ +/* 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_CAPICMCALLBACK_H__ #define __INCLUDED_CAPICMCALLBACK_H__ diff --git a/dll/dll/client_known_interfaces.h b/dll/dll/client_known_interfaces.h index 61605a734..22b330b8d 100644 --- a/dll/dll/client_known_interfaces.h +++ b/dll/dll/client_known_interfaces.h @@ -1,3 +1,20 @@ +/* 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 _CLIENT_KNOWN_INTERFACES_H_ #define _CLIENT_KNOWN_INTERFACES_H_ diff --git a/dll/dll/steam_app_ids.h b/dll/dll/steam_app_ids.h index 244b03cb4..ff0523a6a 100644 --- a/dll/dll/steam_app_ids.h +++ b/dll/dll/steam_app_ids.h @@ -1,3 +1,20 @@ +/* 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 _STEAM_APP_IDS_H_ #define _STEAM_APP_IDS_H_ diff --git a/dll/steam_app_ids.cpp b/dll/steam_app_ids.cpp index 06c6bbf6f..1757243c3 100644 --- a/dll/steam_app_ids.cpp +++ b/dll/steam_app_ids.cpp @@ -1,3 +1,20 @@ +/* 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_app_ids.h" // https://developer.valvesoftware.com/wiki/Steam_Application_IDs diff --git a/dll/ugc_remote_storage_bridge.cpp b/dll/ugc_remote_storage_bridge.cpp index 89a74ea11..7cc3bfde0 100644 --- a/dll/ugc_remote_storage_bridge.cpp +++ b/dll/ugc_remote_storage_bridge.cpp @@ -1,3 +1,20 @@ +/* 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/ugc_remote_storage_bridge.h" diff --git a/dll/voicechat.cpp b/dll/voicechat.cpp index dee4ae124..958d17181 100644 --- a/dll/voicechat.cpp +++ b/dll/voicechat.cpp @@ -1,3 +1,20 @@ +/* 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/voicechat.h" From 67e47ae7a10a83f867a9bef0f136f516407999ad Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Thu, 26 Feb 2026 19:29:00 +0300 Subject: [PATCH 030/159] Implement ISteamUserItems and ISteamGameServerItems --- dll/dll/econ_item.h | 38 ++ dll/dll/network.h | 1 + dll/dll/settings.h | 3 + dll/dll/steam_client.h | 12 +- dll/dll/steam_gameserver_items.h | 89 +++ dll/dll/steam_user_items.h | 72 +++ dll/net.proto | 76 ++- dll/network.cpp | 6 +- dll/settings_parser.cpp | 3 + dll/steam_client.cpp | 4 + dll/steam_client_interface_getter.cpp | 20 + dll/steam_gameserver_items.cpp | 511 ++++++++++++++++++ dll/steam_user_items.cpp | 449 +++++++++++++++ .../configs.main.EXAMPLE.ini | 4 + sdk/steam/isteamgameserveritems.h | 155 ++++++ sdk/steam/isteamgameserveritems001.h | 31 ++ sdk/steam/isteamgameserveritems002.h | 27 + sdk/steam/isteamgameserveritems003.h | 34 ++ sdk/steam/isteamuseritems.h | 113 ++++ sdk/steam/isteamuseritems001.h | 29 + sdk/steam/isteamuseritems002.h | 24 + sdk/steam/isteamuseritems003.h | 24 + sdk/steam/steam_api.h | 8 + 23 files changed, 1713 insertions(+), 20 deletions(-) create mode 100644 dll/dll/econ_item.h create mode 100644 dll/dll/steam_gameserver_items.h create mode 100644 dll/dll/steam_user_items.h create mode 100644 dll/steam_gameserver_items.cpp create mode 100644 dll/steam_user_items.cpp create mode 100644 sdk/steam/isteamgameserveritems.h create mode 100644 sdk/steam/isteamgameserveritems001.h create mode 100644 sdk/steam/isteamgameserveritems002.h create mode 100644 sdk/steam/isteamgameserveritems003.h create mode 100644 sdk/steam/isteamuseritems.h create mode 100644 sdk/steam/isteamuseritems001.h create mode 100644 sdk/steam/isteamuseritems002.h create mode 100644 sdk/steam/isteamuseritems003.h diff --git a/dll/dll/econ_item.h b/dll/dll/econ_item.h new file mode 100644 index 000000000..4970848d6 --- /dev/null +++ b/dll/dll/econ_item.h @@ -0,0 +1,38 @@ +/* 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_ECON_ITEM_H__ +#define __INCLUDED_ECON_ITEM_H__ + +struct Econ_Item_Attribute +{ + uint32 def; + float value; +}; + +struct Econ_Item +{ + uint64 id; + uint32 def; + uint32 level; + EItemQuality quality; + uint32 inv_pos; + uint32 quantity; + std::vector attributes; +}; + +#endif diff --git a/dll/dll/network.h b/dll/dll/network.h index 0731b676e..c62756a88 100644 --- a/dll/dll/network.h +++ b/dll/dll/network.h @@ -68,6 +68,7 @@ enum Callback_Ids { CALLBACK_ID_GAMESERVER_STATS, CALLBACK_ID_LEADERBOARDS_STATS, CALLBACK_ID_USER_STATS, + CALLBACK_ID_GAMESERVER_ITEMS, CALLBACK_IDS_MAX }; diff --git a/dll/dll/settings.h b/dll/dll/settings.h index 4a38d3a19..a79031088 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -377,6 +377,9 @@ class Settings { // voice chat bool enable_voice_chat = false; + // only use 32 bits for inventory item ids + bool use_32bit_inventory_item_ids = false; + #ifdef LOBBY_CONNECT static constexpr const bool is_lobby_connect = true; diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index 60ef6818a..15cf557a5 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -51,14 +51,16 @@ #include "steam_parties.h" #include "steam_remoteplay.h" #include "steam_tv.h" -#include "steam_billing.h" - -#include "steam_gameserver.h" -#include "steam_gameserverstats.h" #include "steam_gamestats.h" #include "steam_timeline.h" #include "steam_app_disable_update.h" +#include "steam_billing.h" +#include "steam_user_items.h" + +#include "steam_gameserver.h" #include "steam_masterserver_updater.h" +#include "steam_gameserverstats.h" +#include "steam_gameserver_items.h" #include "overlay/steam_overlay.h" #include "playtime.h" @@ -153,6 +155,7 @@ public ISteamClient Steam_Timeline *steam_timeline{}; Steam_App_Disable_Update *steam_app_disable_update{}; Steam_Billing *steam_billing{}; + Steam_User_Items *steam_user_items{}; Steam_GameServer *steam_gameserver{}; Steam_User *steam_gameserver_user{}; @@ -169,6 +172,7 @@ public ISteamClient Steam_Game_Coordinator *steam_gameserver_game_coordinator{}; Steam_Masterserver_Updater *steam_masterserver_updater{}; Steam_GameStats *steam_gameserver_gamestats{}; + Steam_GameServer_Items *steam_gameserver_items{}; Steam_AppTicket *steam_app_ticket{}; diff --git a/dll/dll/steam_gameserver_items.h b/dll/dll/steam_gameserver_items.h new file mode 100644 index 000000000..e8368e8b6 --- /dev/null +++ b/dll/dll/steam_gameserver_items.h @@ -0,0 +1,89 @@ +/* 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_GAMESERVER_ITEMS_H__ +#define __INCLUDED_STEAM_GAMESERVER_ITEMS_H__ + +#include "base.h" +#include "econ_item.h" + +class Steam_GameServer_Items : +public ISteamGameServerItems001, +public ISteamGameServerItems002, +public ISteamGameServerItems003, +public ISteamGameServerItems +{ + class Settings *settings{}; + class Networking *network{}; + class SteamCallBacks *callbacks{}; + class SteamCallResults *callback_results{}; + class RunEveryRunCB *run_every_runcb{}; + + struct RequestInventory + { + std::chrono::high_resolution_clock::time_point created{}; + CSteamID steam_id{}; + SteamAPICall_t steam_api_call; + }; + + std::map> all_user_items; + std::vector pending_items_requests{}; + + void network_callback_inventory_response(Common_Message *msg); + void network_callback_inventory_pos_update(Common_Message *msg); + void network_callback_item_deletion(Common_Message *msg); + void network_callback(Common_Message *msg); + void run_callbacks(); + + static void steam_gameserver_items_network_callback(void *object, Common_Message *msg); + static void steam_gameserver_items_run_every_runcb(void *object); + +public: + Steam_GameServer_Items(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks, class SteamCallResults *callback_results, class RunEveryRunCB *run_every_runcb); + ~Steam_GameServer_Items(); + + SteamAPICall_t LoadItems( CSteamID ownerID ); + void LoadItems_old( CSteamID ownerID ); + SteamAPICall_t GetItemCount( CSteamID ownerID ); + void GetItemCount_old( CSteamID ownerID ); + bool GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ); + bool GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ); + bool GetItemByID( uint64 ulItemID, CSteamID *pOwnerID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ); + bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ); + bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ); + HNewItemRequest CreateNewItemRequest( CSteamID steamID ); + HNewItemRequest CreateNewItemRequest( CSteamID steamID, uint32 unItemLevel, EItemQuality eQuality ); + bool AddNewItemLevel( HNewItemRequest handle, uint32 unItemLevel ); + bool AddNewItemQuality( HNewItemRequest handle, EItemQuality eQuality ); + bool SetNewItemInitialInventoryPos( HNewItemRequest handle, uint32 unInventoryPos ); + bool SetNewItemInitialQuantity( HNewItemRequest handle, uint32 unQuantity ); + bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, const char *pchValue, bool bRequired ); + bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, float flValue, bool bRequired ); + SteamAPICall_t SendNewItemRequest( HNewItemRequest handle ); + void SendNewItemRequest_old( HNewItemRequest handle ); + SteamAPICall_t GrantItemToUser( uint64 ulItemID, CSteamID steamIDRecipient ); + void GrantItemToUser_old( uint64 ulItemID, CSteamID steamIDRecipient ); + SteamAPICall_t DeleteTemporaryItem( uint64 ulItemID ); + void DeleteTemporaryItem_old( uint64 ulItemID ); + SteamAPICall_t DeleteAllTemporaryItems(); + void DeleteAllTemporaryItems_old(); + SteamAPICall_t UpdateQuantity( uint64 ulItemID, uint32 unNewQuantity ); + SteamAPICall_t GetItemBlob( uint64 ulItemID ); + SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ); +}; + +#endif // __INCLUDED_STEAM_GAMESERVER_ITEMS_H__ diff --git a/dll/dll/steam_user_items.h b/dll/dll/steam_user_items.h new file mode 100644 index 000000000..a6e4d2fb5 --- /dev/null +++ b/dll/dll/steam_user_items.h @@ -0,0 +1,72 @@ +/* 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_USER_ITEMS_H__ +#define __INCLUDED_STEAM_USER_ITEMS_H__ + +#include "base.h" +#include "econ_item.h" + +class Steam_User_Items : +public ISteamUserItems001, +public ISteamUserItems002, +public ISteamUserItems003, +public ISteamUserItems +{ + static constexpr const auto items_user_file = "items.json"; + + class Settings *settings{}; + class Networking *network{}; + class Local_Storage *local_storage{}; + class SteamCallBacks *callbacks{}; + class SteamCallResults *callback_results{}; + + std::vector items; + bool items_loaded{}; + + void network_callback_inventory_request(Common_Message *msg); + void network_callback(Common_Message *msg); + + static void steam_user_items_network_callback(void *object, Common_Message *msg); + + void load_items_from_file(); + void save_items_to_file(); + +public: + Steam_User_Items(class Settings *settings, class Networking *network, class Local_Storage *local_storage, class SteamCallBacks *callbacks, class SteamCallResults *callback_results); + ~Steam_User_Items(); + + SteamAPICall_t LoadItems(); + void LoadItems_old(); + SteamAPICall_t GetItemCount(); + void GetItemCount_old(); + bool GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ); + bool GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ); + bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ); + bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ); + bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ); + SteamAPICall_t UpdateInventoryPos( uint64 ulItemID, uint32 unNewInventoryPos ); + void UpdateInventoryPos_old( uint64 ulItemID, uint32 unNewInventoryPos ); + SteamAPICall_t DeleteItem( uint64 ulItemID ); + void DropItem_old( uint64 ulItemID ); + SteamAPICall_t DropItem_old2( uint64 ulItemID ); + SteamAPICall_t GetItemBlob( uint64 ulItemID ); + SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ); + virtual SteamAPICall_t DropItem( uint64 ulItemID ); +}; + +#endif // __INCLUDED_STEAM_USER_ITEMS_H__ diff --git a/dll/net.proto b/dll/net.proto index f5ea2ec94..4a1d7959a 100644 --- a/dll/net.proto +++ b/dll/net.proto @@ -206,14 +206,14 @@ message Friend_Messages { } message Steam_Messages { - enum Types { - FRIEND_CHAT = 0; - } - - Types type = 1; - oneof message_data { - bytes message = 2; - } + enum Types { + FRIEND_CHAT = 0; + } + + Types type = 1; + oneof message_data { + bytes message = 2; + } } message StatInfo { @@ -250,7 +250,7 @@ message GameServerStats_Messages { // sent from server as a request, response sent by the user message InitialAllStats { uint64 steam_api_call = 1; - + // optional because the server send doesn't send any data, just steam api call id optional AllStats all_data = 2; } @@ -302,7 +302,7 @@ message Leaderboards_Messages { RequestUserScore = 2; // request score data from a single user } - Types type = 1; + Types type = 1; uint32 appid = 2; LeaderboardInfo leaderboard_info = 3; oneof data_messages { @@ -324,6 +324,51 @@ message Steam_User_Stats_Messages { optional Steam_User_Stats_Data steam_user_stats_data = 2; } +message GameServer_Items_Messages { + message Attribute { + uint32 def = 1; + float value = 2; + } + message Item { + uint64 id = 1; + uint32 def = 2; + uint32 level = 3; + int32 quality = 4; + uint32 inv_pos = 5; + uint32 quantity = 6; + repeated Attribute attributes = 7; + } + message InventoryRequest { + uint64 steam_api_call = 1; + } + message InventoryResponse { + uint64 steam_api_call = 1; + repeated Item items = 2; + } + message InventoryPosUpdate { + uint64 item_id = 1; + uint32 item_inv_pos = 2; + } + message ItemDeletion { + uint64 item_id = 1; + } + + enum Types { + Request_Inventory = 0; + Response_Inventory = 1; + Request_UpdateInventoryPos = 2; + Request_DeleteItem = 3; + } + + Types type = 1; + oneof data_messages { + InventoryRequest inventory_request = 2; + InventoryResponse inventory_response = 3; + InventoryPosUpdate inventory_pos_update = 4; + ItemDeletion item_deletion = 5; + } +} + message Common_Message { uint64 source_id = 1; // SteamID64 of the sender uint64 dest_id = 2; // SteamID64 of the target receiver @@ -344,6 +389,7 @@ message Common_Message { GameServerStats_Messages gameserver_stats_messages = 16; Leaderboards_Messages leaderboards_messages = 17; Steam_User_Stats_Messages steam_user_stats_messages = 18; + GameServer_Items_Messages gameserver_items_messages = 19; } uint32 source_ip = 128; @@ -353,9 +399,9 @@ message Common_Message { //Non networking related protobufs message SteamAppTicket_pb { - uint32 ticket_version_no = 1; - uint32 crc_encryptedticket = 2; - uint32 cb_encrypteduserdata = 3; - uint32 cb_encrypted_appownershipticket = 4; - bytes encrypted_ticket = 5; + uint32 ticket_version_no = 1; + uint32 crc_encryptedticket = 2; + uint32 cb_encrypteduserdata = 3; + uint32 cb_encrypted_appownershipticket = 4; + bytes encrypted_ticket = 5; } diff --git a/dll/network.cpp b/dll/network.cpp index 90ad69c61..0a46e5389 100644 --- a/dll/network.cpp +++ b/dll/network.cpp @@ -575,7 +575,11 @@ void Networking::do_callbacks_message(Common_Message *msg) PRINT_DEBUG("has_steam_user_stats_messages"); run_callbacks(CALLBACK_ID_USER_STATS, msg); } - + + if (msg->has_gameserver_items_messages()) { + PRINT_DEBUG("has_gameserver_items_messages"); + run_callbacks(CALLBACK_ID_GAMESERVER_ITEMS, msg); + } } bool Networking::handle_tcp(Common_Message *msg, struct TCP_Socket &socket) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 9c175c45c..03a737d0f 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -1634,6 +1634,9 @@ static void parse_simple_features(class Settings *settings_client, class Setting settings_client->free_weekend = ini.GetBoolValue("main::misc", "free_weekend", settings_client->free_weekend); settings_server->free_weekend = ini.GetBoolValue("main::misc", "free_weekend", settings_server->free_weekend); + + settings_client->use_32bit_inventory_item_ids = ini.GetBoolValue("main::misc", "use_32bit_inventory_item_ids", settings_client->use_32bit_inventory_item_ids); + settings_server->use_32bit_inventory_item_ids = ini.GetBoolValue("main::misc", "use_32bit_inventory_item_ids", settings_server->use_32bit_inventory_item_ids); } // [main::stats] diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index 99c9d3db4..53013dcff 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -137,6 +137,7 @@ Steam_Client::Steam_Client() steam_timeline = new Steam_Timeline(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_app_disable_update = new Steam_App_Disable_Update(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_billing = new Steam_Billing(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); + steam_user_items = new Steam_User_Items(settings_client, network, local_storage, callbacks_client, callback_results_client); // server PRINT_DEBUG("init gameserver"); @@ -156,6 +157,7 @@ Steam_Client::Steam_Client() steam_gameserver_game_coordinator = new Steam_Game_Coordinator(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); steam_masterserver_updater = new Steam_Masterserver_Updater(settings_server, network, callback_results_server, callbacks_server, run_every_runcb, steam_gameserver); steam_gameserver_gamestats = new Steam_GameStats(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); + steam_gameserver_items = new Steam_GameServer_Items(settings_server, network, callbacks_server, callback_results_server, run_every_runcb); PRINT_DEBUG("init AppTicket"); steam_app_ticket = new Steam_AppTicket(settings_client); @@ -199,6 +201,7 @@ Steam_Client::~Steam_Client() DEL_INST(steam_masterserver_updater); DEL_INST(steam_gameserver); DEL_INST(steam_gameserver_gamestats); + DEL_INST(steam_gameserver_items); DEL_INST(steam_matchmaking); DEL_INST(steam_matchmaking_servers); @@ -231,6 +234,7 @@ Steam_Client::~Steam_Client() DEL_INST(steam_timeline); DEL_INST(steam_app_disable_update); DEL_INST(steam_billing); + DEL_INST(steam_user_items); DEL_INST(ugc_bridge); diff --git a/dll/steam_client_interface_getter.cpp b/dll/steam_client_interface_getter.cpp index 264628a09..adc7df4d3 100644 --- a/dll/steam_client_interface_getter.cpp +++ b/dll/steam_client_interface_getter.cpp @@ -463,6 +463,26 @@ void *Steam_Client::GetISteamGenericInterface( HSteamUser hSteamUser, HSteamPipe if (strcmp(pchVersion, STEAMTV_INTERFACE_VERSION) == 0) { return reinterpret_cast(static_cast(steam_tv)); } + } else if (strstr(pchVersion, "STEAMUSERITEMS_INTERFACE_VERSION") == pchVersion) { + if (strcmp(pchVersion, "STEAMUSERITEMS_INTERFACE_VERSION001") == 0) { + return reinterpret_cast(static_cast(steam_user_items)); + } else if (strcmp(pchVersion, "STEAMUSERITEMS_INTERFACE_VERSION002") == 0) { + return reinterpret_cast(static_cast(steam_user_items)); + } else if (strcmp(pchVersion, "STEAMUSERITEMS_INTERFACE_VERSION003") == 0) { + return reinterpret_cast(static_cast(steam_user_items)); + } else if (strcmp(pchVersion, STEAMUSERITEMS_INTERFACE_VERSION) == 0) { + return reinterpret_cast(static_cast(steam_user_items)); + } + } else if (strstr(pchVersion, "STEAMGAMESERVERITEMS_INTERFACE_VERSION") == pchVersion) { + if (strcmp(pchVersion, "STEAMGAMESERVERITEMS_INTERFACE_VERSION001") == 0) { + return reinterpret_cast(static_cast(steam_gameserver_items)); + } else if (strcmp(pchVersion, "STEAMGAMESERVERITEMS_INTERFACE_VERSION002") == 0) { + return reinterpret_cast(static_cast(steam_gameserver_items)); + } else if (strcmp(pchVersion, "STEAMGAMESERVERITEMS_INTERFACE_VERSION003") == 0) { + return reinterpret_cast(static_cast(steam_gameserver_items)); + } else if (strcmp(pchVersion, STEAMGAMESERVERITEMS_INTERFACE_VERSION) == 0) { + return reinterpret_cast(static_cast(steam_gameserver_items)); + } } else if (strstr(pchVersion, "STEAMREMOTESTORAGE_INTERFACE_VERSION") == pchVersion) { return GetISteamRemoteStorage(hSteamUser, hSteamPipe, pchVersion); } else if (strstr(pchVersion, "SteamGameServerStats") == pchVersion) { diff --git a/dll/steam_gameserver_items.cpp b/dll/steam_gameserver_items.cpp new file mode 100644 index 000000000..99b0f2bac --- /dev/null +++ b/dll/steam_gameserver_items.cpp @@ -0,0 +1,511 @@ +/* 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_gameserver_items.h" + +void Steam_GameServer_Items::steam_gameserver_items_network_callback(void *object, Common_Message *msg) +{ + //PRINT_DEBUG_ENTRY(); + + auto inst = (Steam_GameServer_Items *)object; + inst->network_callback(msg); +} + +void Steam_GameServer_Items::steam_gameserver_items_run_every_runcb(void *object) +{ + //PRINT_DEBUG_ENTRY(); + + auto inst = (Steam_GameServer_Items *)object; + inst->run_callbacks(); +} + +Steam_GameServer_Items::Steam_GameServer_Items(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks, class SteamCallResults *callback_results, class RunEveryRunCB *run_every_runcb) +{ + this->settings = settings; + this->network = network; + this->callbacks = callbacks; + this->callback_results = callback_results; + this->run_every_runcb = run_every_runcb; + + this->network->setCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_GameServer_Items::steam_gameserver_items_network_callback, this); + this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_GameServer_Items::steam_gameserver_items_network_callback, this); +} + +Steam_GameServer_Items::~Steam_GameServer_Items() +{ + this->network->rmCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_GameServer_Items::steam_gameserver_items_network_callback, this); + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_GameServer_Items::steam_gameserver_items_network_callback, this); +} + +SteamAPICall_t Steam_GameServer_Items::LoadItems( CSteamID ownerID ) +{ + PRINT_DEBUG("%llu", ownerID.ConvertToUint64()); + std::lock_guard lock(global_mutex); + + // see if we already have their inventory cached + if (all_user_items.count(ownerID)) { + const auto &items = all_user_items.at(ownerID); + + GSItemCount_t data{}; + data.m_OwnerID = ownerID; + data.m_eResult = k_EItemRequestResultOK; + data.m_unCount = static_cast(items.size()); + SteamAPICall_t ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; + } + + // request inventory from this player + RequestInventory new_request{}; + new_request.created = std::chrono::high_resolution_clock::now(); + new_request.steam_id = ownerID; + new_request.steam_api_call = callback_results->reserveCallResult(); + pending_items_requests.push_back(new_request); + + auto request_msg = new GameServer_Items_Messages::InventoryRequest(); + request_msg->set_steam_api_call(new_request.steam_api_call); + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_Inventory); + gameserver_items_msg->set_allocated_inventory_request(request_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(ownerID.ConvertToUint64()); + network->sendTo(&msg, true); + + return new_request.steam_api_call; +} + +void Steam_GameServer_Items::LoadItems_old( CSteamID ownerID ) +{ + PRINT_DEBUG_ENTRY(); + LoadItems(ownerID); +} + +SteamAPICall_t Steam_GameServer_Items::GetItemCount( CSteamID ownerID ) +{ + PRINT_DEBUG_ENTRY(); + return LoadItems(ownerID); +} + +void Steam_GameServer_Items::GetItemCount_old( CSteamID ownerID ) +{ + PRINT_DEBUG_ENTRY(); + GetItemCount(ownerID); +} + +bool Steam_GameServer_Items::GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) +{ + PRINT_DEBUG("%u", iIndex); + std::lock_guard lock(global_mutex); + + if (!all_user_items.count(ownerID)) + return false; + + const auto &items = all_user_items.at(ownerID); + if (iIndex >= items.size()) + return false; + + const Econ_Item &item = items[iIndex]; + *pulItemID = item.id; + *punItemDefIndex = item.def; + *punItemLevel = item.level; + *peQuality = item.quality; + *punInventoryPos = item.inv_pos; + *punQuantity = item.quantity; + *punAttributeCount = static_cast(item.attributes.size()); + + return true; +} + +bool Steam_GameServer_Items::GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) +{ + PRINT_DEBUG_ENTRY(); + uint32 quantity; + return GetItemIterative(ownerID, iIndex, pulItemID, punItemDefIndex, punItemLevel, peQuality, punInventoryPos, &quantity, punAttributeCount); +} + +bool Steam_GameServer_Items::GetItemByID( uint64 ulItemID, CSteamID *pOwnerID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) +{ + PRINT_DEBUG("%llu", ulItemID); + std::lock_guard lock(global_mutex); + + for (const auto &[steam_id, items] : all_user_items) { + for (const Econ_Item &item : items) { + if (item.id != ulItemID) + continue; + + *pOwnerID = steam_id; + *punItemDefIndex = item.def; + *punItemLevel = item.level; + *peQuality = item.quality; + *punInventoryPos = item.inv_pos; + *punQuantity = item.quantity; + *punAttributeCount = static_cast(item.attributes.size()); + + return true; + } + } + + return false; +} + +bool Steam_GameServer_Items::GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) +{ + PRINT_DEBUG_ENTRY(); + CSteamID steam_id; + uint32 quantity; + return GetItemByID(ulItemID, &steam_id, punItemDefIndex, punItemLevel, peQuality, punInventoryPos, &quantity, punAttributeCount); +} + +bool Steam_GameServer_Items::GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) +{ + PRINT_DEBUG("%llu %u", ulItemID, unAttributeIndex); + std::lock_guard lock(global_mutex); + + for (const auto &[steamID, items] : all_user_items) { + for (const Econ_Item &item : items) { + if (item.id != ulItemID) + continue; + + if (unAttributeIndex >= item.attributes.size()) + return false; + + *punAttributeDefIndex = item.attributes[unAttributeIndex].def; + *pflAttributeValue = item.attributes[unAttributeIndex].value; + + return true; + } + } + + return false; +} + +HNewItemRequest Steam_GameServer_Items::CreateNewItemRequest( CSteamID steamID ) +{ + PRINT_DEBUG_TODO(); + return 0; +} + +HNewItemRequest Steam_GameServer_Items::CreateNewItemRequest( CSteamID steamID, uint32 unItemLevel, EItemQuality eQuality ) +{ + PRINT_DEBUG_TODO(); + return 0; +} + +bool Steam_GameServer_Items::AddNewItemLevel( HNewItemRequest handle, uint32 unItemLevel ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +bool Steam_GameServer_Items::AddNewItemQuality( HNewItemRequest handle, EItemQuality eQuality ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +bool Steam_GameServer_Items::SetNewItemInitialInventoryPos( HNewItemRequest handle, uint32 unInventoryPos ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +bool Steam_GameServer_Items::SetNewItemInitialQuantity( HNewItemRequest handle, uint32 unQuantity ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +bool Steam_GameServer_Items::AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, const char *pchValue, bool bRequired ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +bool Steam_GameServer_Items::AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, float flValue, bool bRequired ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +SteamAPICall_t Steam_GameServer_Items::SendNewItemRequest( HNewItemRequest handle ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +void Steam_GameServer_Items::SendNewItemRequest_old( HNewItemRequest handle ) +{ + PRINT_DEBUG_ENTRY(); + SendNewItemRequest(handle); +} + +SteamAPICall_t Steam_GameServer_Items::GrantItemToUser( uint64 ulItemID, CSteamID steamIDRecipient ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +void Steam_GameServer_Items::GrantItemToUser_old( uint64 ulItemID, CSteamID steamIDRecipient ) +{ + PRINT_DEBUG_ENTRY(); + GrantItemToUser(ulItemID, steamIDRecipient); +} + +SteamAPICall_t Steam_GameServer_Items::DeleteTemporaryItem( uint64 ulItemID ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +void Steam_GameServer_Items::DeleteTemporaryItem_old( uint64 ulItemID ) +{ + PRINT_DEBUG_ENTRY(); + DeleteTemporaryItem(ulItemID); +} + +SteamAPICall_t Steam_GameServer_Items::DeleteAllTemporaryItems() +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +void Steam_GameServer_Items::DeleteAllTemporaryItems_old() +{ + PRINT_DEBUG_ENTRY(); + DeleteAllTemporaryItems(); +} + +SteamAPICall_t Steam_GameServer_Items::UpdateQuantity( uint64 ulItemID, uint32 unNewQuantity ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +SteamAPICall_t Steam_GameServer_Items::GetItemBlob( uint64 ulItemID ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +SteamAPICall_t Steam_GameServer_Items::SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +// user sent their inventory +void Steam_GameServer_Items::network_callback_inventory_response(Common_Message *msg) +{ + uint64 user_steamid = msg->source_id(); + + PRINT_DEBUG("player sent their inventory %llu", user_steamid); + if (!msg->gameserver_items_messages().has_inventory_response()) { + PRINT_DEBUG("error empty msg"); + return; + } + + const auto &response_msg = msg->gameserver_items_messages().inventory_response(); + + // find this pending request + auto it = std::find_if( + pending_items_requests.begin(), pending_items_requests.end(), + [=](const RequestInventory &item) { + return item.steam_api_call == response_msg.steam_api_call() && + item.steam_id == user_steamid; + } + ); + if (pending_items_requests.end() == it) { // timeout and already removed + PRINT_DEBUG("error got player inventory but pending request timedout/removed (doesn't exist)"); + return; + } + + auto &items = all_user_items[user_steamid]; + items.clear(); + + for (const auto &item : response_msg.items()) { + Econ_Item new_item; + new_item.id = item.id(); + new_item.def = item.def(); + new_item.level = item.level(); + new_item.quality = static_cast(item.quality()); + new_item.inv_pos = item.inv_pos(); + new_item.quantity = item.quantity(); + if (new_item.id == 0) + continue; + + for (const auto &attr : item.attributes()) { + Econ_Item_Attribute new_attr; + new_attr.def = attr.def(); + new_attr.value = attr.value(); + if (new_attr.def == 0) + continue; + + new_item.attributes.push_back(new_attr); + } + + items.push_back(new_item); + } + + GSItemCount_t data{}; + data.m_OwnerID = user_steamid; + data.m_eResult = k_EItemRequestResultOK; + data.m_unCount = static_cast(items.size()); + callback_results->addCallResult(it->steam_api_call, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + // remove this pending request + pending_items_requests.erase(it); + + PRINT_DEBUG("server got player inventory: %u items", response_msg.items_size()); +} + +// user updated item inventory position +void Steam_GameServer_Items::network_callback_inventory_pos_update(Common_Message *msg) +{ + uint64 user_steamid = msg->source_id(); + + PRINT_DEBUG("player updated item inventory position %llu", user_steamid); + if (!msg->gameserver_items_messages().has_inventory_pos_update()) { + PRINT_DEBUG("error empty msg"); + return; + } + + if (!all_user_items.count(user_steamid)) { + PRINT_DEBUG("error no inventory for player", user_steamid); + return; + } + + const auto &inventory_msg = msg->gameserver_items_messages().inventory_pos_update(); + uint64 item_id = inventory_msg.item_id(); + uint32 item_inv_pos = inventory_msg.item_inv_pos(); + + auto &items = all_user_items.at(user_steamid); + + for (Econ_Item &item : items) { + if (item.id != item_id) + continue; + + item.inv_pos = item_inv_pos; + + GSItemInventoryPosUpdated_t data{}; + data.m_SteamID = user_steamid; + data.m_ulItemID = item_id; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.15); + + PRINT_DEBUG("server got updated item inventory position: %llu 0x%08x", item_id, item_inv_pos); + return; + } + + PRINT_DEBUG("error item %llu not found", item_id); +} + +// user deleted an item +void Steam_GameServer_Items::network_callback_item_deletion(Common_Message *msg) +{ + uint64 user_steamid = msg->source_id(); + + PRINT_DEBUG("player deleted inventory item %llu", user_steamid); + if (!msg->gameserver_items_messages().has_item_deletion()) { + PRINT_DEBUG("error empty msg"); + return; + } + + if (!all_user_items.count(user_steamid)) { + PRINT_DEBUG("error no inventory for player", user_steamid); + return; + } + + const auto &drop_msg = msg->gameserver_items_messages().item_deletion(); + uint64 item_id = drop_msg.item_id(); + + auto &items = all_user_items.at(user_steamid); + + for (auto it = items.begin(); it != items.end(); it++) { + if (it->id != item_id) + continue; + + items.erase(it); + + GSItemDeleted_t data{}; + data.m_SteamID = user_steamid; + data.m_ulItemID = item_id; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.15); + + PRINT_DEBUG("server deleted inventory item: %llu", item_id); + return; + } + + PRINT_DEBUG("error item %llu not found", item_id); +} + +// only triggered when we have a message +void Steam_GameServer_Items::network_callback(Common_Message *msg) +{ + // this should never happen, but just in case + if (msg->source_id() == settings->get_local_steam_id().ConvertToUint64()) return; + + if (msg->has_gameserver_items_messages()) { + switch (msg->gameserver_items_messages().type()) { + // user sent their inventory + case GameServer_Items_Messages::Response_Inventory: + network_callback_inventory_response(msg); + break; + + // user updated item inventory position + case GameServer_Items_Messages::Request_UpdateInventoryPos: + network_callback_inventory_pos_update(msg); + break; + + // user deleted an item + case GameServer_Items_Messages::Request_DeleteItem: + network_callback_item_deletion(msg); + break; + + default: + PRINT_DEBUG("unhandled type %i", (int)msg->gameserver_items_messages().type()); + break; + } + } else if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::DISCONNECT) { + uint64 user_steamid = msg->source_id(); + all_user_items.erase(user_steamid); + } + } +} + +void Steam_GameServer_Items::run_callbacks() +{ + for (auto it = pending_items_requests.begin(); it != pending_items_requests.end();) { + if (check_timedout(it->created, 7.0)) { + GSItemCount_t data{}; + data.m_OwnerID = it->steam_id; + data.m_eResult = k_EItemRequestResultTimeout; + data.m_unCount = 0; + callback_results->addCallResult(it->steam_api_call, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + PRINT_DEBUG("player inventory request timeout %llu", it->steam_id); + it = pending_items_requests.erase(it); + } else { + it++; + } + } +} diff --git a/dll/steam_user_items.cpp b/dll/steam_user_items.cpp new file mode 100644 index 000000000..04b3d7d2c --- /dev/null +++ b/dll/steam_user_items.cpp @@ -0,0 +1,449 @@ +/* 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_items.h" + +void Steam_User_Items::steam_user_items_network_callback(void *object, Common_Message *msg) +{ + //PRINT_DEBUG_ENTRY(); + + auto inst = (Steam_User_Items *)object; + inst->network_callback(msg); +} + +void Steam_User_Items::load_items_from_file() +{ + items.clear(); + + nlohmann::json items_json; + if (!local_storage->load_json_file("", items_user_file, items_json)) + return; + + for (auto it = items_json.begin(); it != items_json.end(); it++) { + Econ_Item new_item{}; + try { + new_item.id = std::stoull(it.key()); + } catch (...) { + continue; + } + if (new_item.id == 0) + continue; + + new_item.def = it->value("definition", 0u); // 0 is a valid item definition + new_item.level = it->value("level", 1u); + new_item.quality = static_cast(it->value("quality", 0)); + new_item.inv_pos = it->value("inventory_pos", 0u); + new_item.quantity = it->value("quantity", 0u); // TODO: not sure what this is for, TF2 does not seem to use it + + if (it->contains("attributes")) { + for (const auto &attr : it->at("attributes")) { + Econ_Item_Attribute new_attr; + new_attr.def = attr.value("definition", 0u); + new_attr.value = attr.value("value", 0.0f); + if (new_attr.def == 0) // 0 is not a valid attribute definition, however + continue; + + new_item.attributes.push_back(new_attr); + } + } + + // add SteamID to item ID to avoid ID collisions in multiplayer games + uint32 account_id = settings->get_local_steam_id().GetAccountID(); + + if (settings->use_32bit_inventory_item_ids) { + // 32-bit mode + new_item.id <<= 20ull; + new_item.id |= static_cast(account_id) & 0x000FFFFFull; + } else { + // 64-bit mode + new_item.id <<= 32ull; + new_item.id |= static_cast(account_id); + } + + items.push_back(new_item); + } +} + +void Steam_User_Items::save_items_to_file() +{ + nlohmann::json items_json; + + for (const Econ_Item &item : items) { + uint64 item_id = 0; + if (settings->use_32bit_inventory_item_ids) { + // 32-bit mode + item_id = item.id >> 20ull; + } else { + // 64-bit mode + item_id = item.id >> 32ull; + } + + nlohmann::json json_item; + json_item["definition"] = item.def; + json_item["level"] = item.level; + json_item["quality"] = item.quality; + json_item["inventory_pos"] = item.inv_pos; + json_item["quantity"] = item.quantity; + + for (const Econ_Item_Attribute &attr : item.attributes) { + nlohmann::json json_attr; + json_attr["definition"] = attr.def; + json_attr["value"] = attr.value; + json_item["attributes"].push_back(json_attr); + } + + items_json[std::to_string(item_id)] = json_item; + } + + local_storage->write_json_file("", items_user_file, items_json); +} + +Steam_User_Items::Steam_User_Items(class Settings *settings, class Networking *network, class Local_Storage *local_storage, class SteamCallBacks *callbacks, class SteamCallResults *callback_results) +{ + this->settings = settings; + this->network = network; + this->local_storage = local_storage; + this->callbacks = callbacks; + this->callback_results = callback_results; + + this->network->setCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_User_Items::steam_user_items_network_callback, this); +} + +Steam_User_Items::~Steam_User_Items() +{ + this->network->rmCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_User_Items::steam_user_items_network_callback, this); +} + +SteamAPICall_t Steam_User_Items::LoadItems() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + if (!items_loaded) { + load_items_from_file(); + items_loaded = true; + } + + UserItemCount_t data{}; + data.m_gameID = settings->get_local_game_id(); + data.m_eResult = k_EItemRequestResultOK; + data.m_unCount = static_cast(items.size()); + SteamAPICall_t ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; +} + +void Steam_User_Items::LoadItems_old() +{ + PRINT_DEBUG_ENTRY(); + LoadItems(); +} + +SteamAPICall_t Steam_User_Items::GetItemCount() +{ + PRINT_DEBUG_ENTRY(); + return LoadItems(); +} + +void Steam_User_Items::GetItemCount_old() +{ + PRINT_DEBUG_ENTRY(); + GetItemCount(); +} + +bool Steam_User_Items::GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) +{ + PRINT_DEBUG("%u", iIndex); + std::lock_guard lock(global_mutex); + + if (iIndex >= items.size()) + return false; + + const Econ_Item &item = items[iIndex]; + *pulItemID = item.id; + *punItemDefIndex = item.def; + *punItemLevel = item.level; + *peQuality = item.quality; + *punInventoryPos = item.inv_pos; + *punQuantity = item.quantity; + *punAttributeCount = static_cast(item.attributes.size()); + + return true; +} + +bool Steam_User_Items::GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) +{ + PRINT_DEBUG_ENTRY(); + uint32 quantity; + return GetItemIterative(iIndex, pulItemID, punItemDefIndex, punItemLevel, peQuality, punInventoryPos, &quantity, punAttributeCount); +} + +bool Steam_User_Items::GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) +{ + PRINT_DEBUG("%llu", ulItemID); + std::lock_guard lock(global_mutex); + + for (const Econ_Item &item : items) { + if (item.id != ulItemID) + continue; + + *punItemDefIndex = item.def; + *punItemLevel = item.level; + *peQuality = item.quality; + *punInventoryPos = item.inv_pos; + *punQuantity = item.quantity; + *punAttributeCount = static_cast(item.attributes.size()); + + return true; + } + + return false; +} + +bool Steam_User_Items::GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) +{ + PRINT_DEBUG_ENTRY(); + uint32 quantity; + return GetItemByID(ulItemID, punItemDefIndex, punItemLevel, peQuality, punInventoryPos, &quantity, punAttributeCount); +} + +bool Steam_User_Items::GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) +{ + PRINT_DEBUG("%llu %u", ulItemID, unAttributeIndex); + std::lock_guard lock(global_mutex); + + for (const Econ_Item &item : items) { + if (item.id != ulItemID) + continue; + + if (unAttributeIndex >= item.attributes.size()) + return false; + + *punAttributeDefIndex = item.attributes[unAttributeIndex].def; + *pflAttributeValue = item.attributes[unAttributeIndex].value; + + return true; + } + + return false; +} + +//=============================================================================================================== +// POSITION HANDLING +//=============================================================================================================== +// TF Inventory Position cracking + +// REALLY OLD FORMAT (??): +// Bits 17-32 are the bag index (class index + 999, 1 is unequipped). +// Bits 1-16 are the position of the item within the bag. +// 0 means item hasn't been acknowledged by the player yet. +// +// LESS OLD FORMAT (up through July, 2011): +// If Bit 31 is 0: +// Bits 1-16 are the backpack position. +// Bits 17-26 are a bool for whether the item is equipped in the matching class. +// Otherwise, if Bit 31 is 1: +// Item hasn't been acknowledged by the player yet. +// Bits 1-16 are the method by the player found the item (see unacknowledged_item_inventory_positions_t) +// Bit 32 is 1, to note the new format. +// +// CURRENT FORMAT: +// If Bit 31 is 0: +// Bits 1-16 are the backpack position. +// Otherwise, if Bit 31 is 1: +// Item hasn't been acknowledged by the player yet. +// Bits 1-16 are the method by the player found the item (see unacknowledged_item_inventory_positions_t) +// Equipped state is stored elsewhere. +// This is the only format that should exist on clients. +// Note (1/15/2013) For backwards compatibility, if the value is 0 item is considered unacknowledged too + +SteamAPICall_t Steam_User_Items::UpdateInventoryPos( uint64 ulItemID, uint32 unNewInventoryPos ) +{ + PRINT_DEBUG("%llu 0x%08x", ulItemID, unNewInventoryPos); + std::lock_guard lock(global_mutex); + + for (auto &item : items) { + if (item.id != ulItemID) + continue; + + item.inv_pos = unNewInventoryPos; + save_items_to_file(); + + // let the server know, too + auto inventory_msg = new GameServer_Items_Messages::InventoryPosUpdate(); + inventory_msg->set_item_id(ulItemID); + inventory_msg->set_item_inv_pos(unNewInventoryPos); + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_UpdateInventoryPos); + gameserver_items_msg->set_allocated_inventory_pos_update(inventory_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + network->sendToAllGameservers(&msg, true); + + UpdateInventoryPosResponse_t data{}; + data.m_ulItemID = item.id; + data.m_eResult = k_EItemRequestResultOK; + SteamAPICall_t ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; + } + + UpdateInventoryPosResponse_t data{}; + data.m_ulItemID = ulItemID; + data.m_eResult = k_EItemRequestResultNoMatch; + SteamAPICall_t ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; +} + +void Steam_User_Items::UpdateInventoryPos_old( uint64 ulItemID, uint32 unNewInventoryPos ) +{ + PRINT_DEBUG_ENTRY(); + UpdateInventoryPos(ulItemID, unNewInventoryPos); +} + +SteamAPICall_t Steam_User_Items::DeleteItem( uint64 ulItemID ) +{ + PRINT_DEBUG("%llu", ulItemID); + std::lock_guard lock(global_mutex); + + for (auto it = items.begin(); it != items.end(); it++) { + if (it->id != ulItemID) + continue; + + items.erase(it); + save_items_to_file(); + + // let the server know, too + auto drop_msg = new GameServer_Items_Messages::ItemDeletion(); + drop_msg->set_item_id(ulItemID); + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_DeleteItem); + gameserver_items_msg->set_allocated_item_deletion(drop_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + network->sendToAllGameservers(&msg, true); + + DeleteItemResponse_t data{}; + data.m_ulItemID = ulItemID; + data.m_eResult = k_EItemRequestResultOK; + SteamAPICall_t ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; + } + + DeleteItemResponse_t data{}; + data.m_ulItemID = ulItemID; + data.m_eResult = k_EItemRequestResultNoMatch; + SteamAPICall_t ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; +} + +void Steam_User_Items::DropItem_old( uint64 ulItemID ) +{ + PRINT_DEBUG_ENTRY(); + DeleteItem(ulItemID); +} + +SteamAPICall_t Steam_User_Items::DropItem_old2( uint64 ulItemID ) +{ + PRINT_DEBUG_ENTRY(); + return DeleteItem(ulItemID); +} + +SteamAPICall_t Steam_User_Items::GetItemBlob( uint64 ulItemID ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +SteamAPICall_t Steam_User_Items::SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +SteamAPICall_t Steam_User_Items::DropItem( uint64 ulItemID ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +// server requested our inventory +void Steam_User_Items::network_callback_inventory_request(Common_Message *msg) +{ + if (!msg->gameserver_items_messages().has_inventory_request()) { + PRINT_DEBUG("error empty msg"); + return; + } + + uint64 server_steamid = msg->source_id(); + const auto &request_msg = msg->gameserver_items_messages().inventory_request(); + + auto response_msg = new GameServer_Items_Messages::InventoryResponse(); + response_msg->set_steam_api_call(request_msg.steam_api_call()); + + for (const Econ_Item &item : items) { + auto new_item = response_msg->add_items(); + new_item->set_id(item.id); + new_item->set_def(item.def); + new_item->set_level(item.level); + new_item->set_quality(static_cast(item.quality)); + new_item->set_inv_pos(item.inv_pos); + new_item->set_quantity(item.quantity); + + for (const auto &attr : item.attributes) { + auto new_attr = new_item->add_attributes(); + new_attr->set_def(attr.def); + new_attr->set_value(attr.value); + } + } + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Response_Inventory); + gameserver_items_msg->set_allocated_inventory_response(response_msg); + + Common_Message new_msg{}; + new_msg.set_allocated_gameserver_items_messages(gameserver_items_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 inventory, sent %u items", items.size()); +} + +// only triggered when we have a message +void Steam_User_Items::network_callback(Common_Message *msg) +{ + switch (msg->gameserver_items_messages().type()) { + // server requested our inventory + case GameServer_Items_Messages::Request_Inventory: + network_callback_inventory_request(msg); + break; + + default: + PRINT_DEBUG("unhandled type %i", (int)msg->gameserver_items_messages().type()); + break; + } +} diff --git a/post_build/steam_settings.EXAMPLE/configs.main.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.main.EXAMPLE.ini index d92030b1c..fd8acb53d 100644 --- a/post_build/steam_settings.EXAMPLE/configs.main.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.main.EXAMPLE.ini @@ -154,3 +154,7 @@ steam_game_stats_reports_dir=./path/relative/to/dll/ # some games may have extra bonuses/achievements when being or playing with a free-weekend player # default=0 free_weekend=0 +# very old versions of Team Fortress 2 don't properly handle 64-bit item IDs +# this workaround makes Goldberg only generate 32-bit item IDs +# default=0 +use_32bit_inventory_item_ids=0 diff --git a/sdk/steam/isteamgameserveritems.h b/sdk/steam/isteamgameserveritems.h new file mode 100644 index 000000000..60c7ba96f --- /dev/null +++ b/sdk/steam/isteamgameserveritems.h @@ -0,0 +1,155 @@ + +#ifndef ISTEAMGAMESERVERITEMS_H +#define ISTEAMGAMESERVERITEMS_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +#include "isteamuseritems.h" + +enum EItemCriteriaOperator +{ + k_EOperator_String_EQ = 0, + k_EOperator_Not, + k_EOperator_String_Not_EQ = k_EOperator_Not, + k_EOperator_Float_EQ, + k_EOperator_Float_Not_EQ, + k_EOperator_Float_LT, + k_EOperator_Float_Not_LT, + k_EOperator_Float_LTE, + k_EOperator_Float_Not_LTE, + k_EOperator_Float_GT, + k_EOperator_Float_Not_GT, + k_EOperator_Float_GTE, + k_EOperator_Float_Not_GTE, + k_EOperator_Subkey_Contains, + k_EOperator_Subkey_Not_Contains, + k_EItemCriteriaOperator_Count, +}; + +typedef int32 HNewItemRequest; + +class ISteamGameServerItems +{ +public: + virtual SteamAPICall_t LoadItems( CSteamID ownerID ) = 0; + virtual SteamAPICall_t GetItemCount( CSteamID ownerID ) = 0; + virtual bool GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, CSteamID *pOwnerID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual HNewItemRequest CreateNewItemRequest( CSteamID steamID ) = 0; + virtual bool AddNewItemLevel( HNewItemRequest handle, uint32 unItemLevel ) = 0; + virtual bool AddNewItemQuality( HNewItemRequest handle, EItemQuality eQuality ) = 0; + virtual bool SetNewItemInitialInventoryPos( HNewItemRequest handle, uint32 unInventoryPos ) = 0; + virtual bool SetNewItemInitialQuantity( HNewItemRequest handle, uint32 unQuantity ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, const char *pchValue, bool bRequired ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, float flValue, bool bRequired ) = 0; + virtual SteamAPICall_t SendNewItemRequest( HNewItemRequest handle ) = 0; + virtual SteamAPICall_t GrantItemToUser( uint64 ulItemID, CSteamID steamIDRecipient ) = 0; + virtual SteamAPICall_t DeleteTemporaryItem( uint64 ulItemID ) = 0; + virtual SteamAPICall_t DeleteAllTemporaryItems() = 0; + virtual SteamAPICall_t UpdateQuantity( uint64 ulItemID, uint32 unNewQuantity ) = 0; + virtual SteamAPICall_t GetItemBlob( uint64 ulItemID ) = 0; + virtual SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) = 0; +}; + +#define STEAMGAMESERVERITEMS_INTERFACE_VERSION "STEAMGAMESERVERITEMS_INTERFACE_VERSION004" + +struct GSItemCount_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 0 }; + CSteamID m_OwnerID; + EItemRequestResult m_eResult; + uint32 m_unCount; +}; + +struct GSNewItemRequestResponse_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 1 }; + CSteamID m_SteamID; + EItemRequestResult m_eResult; + uint64 m_ulItemID; + HNewItemRequest m_hRequest; +}; + +struct GSItemInventoryPosUpdated_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 2 }; + CSteamID m_SteamID; + uint64 m_ulItemID; +}; + +struct GSItemDeleted_t // renamed from GSItemDropped_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 3 }; + CSteamID m_SteamID; + uint64 m_ulItemID; +}; + +struct GSGrantItemToUser_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 4 }; + EItemRequestResult m_eResult; + uint64 m_ulItemIDOld; + uint64 m_ulItemIDPermanent; + CSteamID m_SteamID; +}; + +struct GSDeleteTemporaryItem_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 5 }; + EItemRequestResult m_eResult; + uint64 m_ulItemID; +}; + +struct GSDeleteAllTemporaryItems_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 6 }; + EItemRequestResult m_eResult; +}; + +struct GSItemAdded_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 7 }; + CSteamID m_SteamID; + uint64 m_ulItemID; +}; + +struct GSGetItemBlobResponse_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 8 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; + uint8 m_pubBlob[1024]; + int32 m_cubBlob; +}; + +struct GSSetItemBlobResponse_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 9 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +struct GSUpdateQuantity_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 10 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +struct GSItemsRefreshed_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 11 }; +}; + +struct GSItemDropped_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 12 }; + CSteamID m_SteamID; + uint64 m_ulItemID; +}; + +#endif // ISTEAMGAMESERVERITEMS_H diff --git a/sdk/steam/isteamgameserveritems001.h b/sdk/steam/isteamgameserveritems001.h new file mode 100644 index 000000000..c727c7ff9 --- /dev/null +++ b/sdk/steam/isteamgameserveritems001.h @@ -0,0 +1,31 @@ + +#ifndef ISTEAMGAMESERVERITEMS001_H +#define ISTEAMGAMESERVERITEMS001_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +class ISteamGameServerItems001 +{ +public: + virtual void LoadItems_old( CSteamID ownerID ) = 0; + virtual void GetItemCount_old( CSteamID ownerID ) = 0; + virtual bool GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual HNewItemRequest CreateNewItemRequest( CSteamID steamID, uint32 unItemLevel, EItemQuality eQuality ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, const char *pchValue, bool bRequired ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, float flValue, bool bRequired ) = 0; + virtual void SendNewItemRequest_old( HNewItemRequest handle ) = 0; +}; + +struct GSItemDropped001_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 3 }; + CSteamID m_SteamID; + uint64 m_ulItemID; +}; + +#endif // ISTEAMGAMESERVERITEMS001_H diff --git a/sdk/steam/isteamgameserveritems002.h b/sdk/steam/isteamgameserveritems002.h new file mode 100644 index 000000000..fcd607963 --- /dev/null +++ b/sdk/steam/isteamgameserveritems002.h @@ -0,0 +1,27 @@ + +#ifndef ISTEAMGAMESERVERITEMS002_H +#define ISTEAMGAMESERVERITEMS002_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +class ISteamGameServerItems002 +{ +public: + virtual void LoadItems_old( CSteamID ownerID ) = 0; + virtual void GetItemCount_old( CSteamID ownerID ) = 0; + virtual bool GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual HNewItemRequest CreateNewItemRequest( CSteamID steamID, uint32 unItemLevel, EItemQuality eQuality ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, const char *pchValue, bool bRequired ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, float flValue, bool bRequired ) = 0; + virtual void SendNewItemRequest_old( HNewItemRequest handle ) = 0; + virtual void GrantItemToUser_old( uint64 ulItemID, CSteamID steamIDRecipient ) = 0; + virtual void DeleteTemporaryItem_old( uint64 ulItemID ) = 0; + virtual void DeleteAllTemporaryItems_old() = 0; +}; + +#endif // ISTEAMGAMESERVERITEMS002_H diff --git a/sdk/steam/isteamgameserveritems003.h b/sdk/steam/isteamgameserveritems003.h new file mode 100644 index 000000000..2cb5523ed --- /dev/null +++ b/sdk/steam/isteamgameserveritems003.h @@ -0,0 +1,34 @@ + +#ifndef ISTEAMGAMESERVERITEMS003_H +#define ISTEAMGAMESERVERITEMS003_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +class ISteamGameServerItems003 +{ +public: + virtual void LoadItems_old( CSteamID ownerID ) = 0; + virtual void GetItemCount_old( CSteamID ownerID ) = 0; + virtual bool GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, CSteamID *pOwnerID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual HNewItemRequest CreateNewItemRequest( CSteamID steamID ) = 0; + virtual bool AddNewItemLevel( HNewItemRequest handle, uint32 unItemLevel ) = 0; + virtual bool AddNewItemQuality( HNewItemRequest handle, EItemQuality eQuality ) = 0; + virtual bool SetNewItemInitialInventoryPos( HNewItemRequest handle, uint32 unInventoryPos ) = 0; + virtual bool SetNewItemInitialQuantity( HNewItemRequest handle, uint32 unQuantity ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, const char *pchValue, bool bRequired ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, float flValue, bool bRequired ) = 0; + virtual void SendNewItemRequest_old( HNewItemRequest handle ) = 0; + virtual void GrantItemToUser_old( uint64 ulItemID, CSteamID steamIDRecipient ) = 0; + virtual void DeleteTemporaryItem_old( uint64 ulItemID ) = 0; + virtual void DeleteAllTemporaryItems_old() = 0; + virtual SteamAPICall_t UpdateQuantity( uint64 ulItemID, uint32 unNewQuantity ) = 0; + virtual SteamAPICall_t GetItemBlob( uint64 ulItemID ) = 0; + virtual SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) = 0; +}; + +#endif // ISTEAMGAMESERVERITEMS003_H diff --git a/sdk/steam/isteamuseritems.h b/sdk/steam/isteamuseritems.h new file mode 100644 index 000000000..a57aa65a2 --- /dev/null +++ b/sdk/steam/isteamuseritems.h @@ -0,0 +1,113 @@ + +#ifndef ISTEAMUSERITEMS_H +#define ISTEAMUSERITEMS_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +enum EItemQuality +{ + k_EItemQuality_Invalid = -2, + k_EItemQuality_Any = -1, + k_EItemQuality_Normal = 0, + k_EItemQuality_Common, + k_EItemQuality_Rare, + k_EItemQuality_Unique, + k_EItemQuality_Count, +}; + +enum EItemRequestResult +{ + k_EItemRequestResultOK = 0, + k_EItemRequestResultDenied, + k_EItemRequestResultServerError, + k_EItemRequestResultTimeout, + k_EItemRequestResultInvalid, + k_EItemRequestResultNoMatch, + k_EItemRequestResultUnknownError, + k_EItemRequestResultNotLoggedOn, +}; + +class ISteamUserItems +{ +public: + virtual SteamAPICall_t LoadItems() = 0; + virtual SteamAPICall_t GetItemCount() = 0; + virtual bool GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual SteamAPICall_t UpdateInventoryPos( uint64 ulItemID, uint32 unNewInventoryPos ) = 0; + virtual SteamAPICall_t DeleteItem( uint64 ulItemID ) = 0; + virtual SteamAPICall_t GetItemBlob( uint64 ulItemID ) = 0; + virtual SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) = 0; + virtual SteamAPICall_t DropItem( uint64 ulItemID ) = 0; +}; + +#define STEAMUSERITEMS_INTERFACE_VERSION "STEAMUSERITEMS_INTERFACE_VERSION004" + +struct UserItemCount_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 0 }; + CGameID m_gameID; + EItemRequestResult m_eResult; + uint32 m_unCount; +}; + +struct UpdateInventoryPosResponse_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 1 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +struct DeleteItemResponse_t // renamed from DropItemResponse_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 2 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +struct ItemGranted_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 3 }; + uint64 m_ulItemID; + CGameID m_gameID; +}; + +struct GetItemBlobResponse_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 4 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; + uint8 m_pubBlob[1024]; + int32 m_cubBlob; +}; + +struct SetItemBlobResponse_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 5 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +struct ItemQuantityUpdated_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 6 }; + uint64 m_ulItemID; +}; + +struct ItemsRefreshed_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 7 }; +}; + +struct DropItemResponse_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 8 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +#endif // ISTEAMUSERITEMS_H diff --git a/sdk/steam/isteamuseritems001.h b/sdk/steam/isteamuseritems001.h new file mode 100644 index 000000000..1e7268d5e --- /dev/null +++ b/sdk/steam/isteamuseritems001.h @@ -0,0 +1,29 @@ + +#ifndef ISTEAMUSERITEMS001_H +#define ISTEAMUSERITEMS001_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +class ISteamUserItems001 +{ +public: + virtual void LoadItems_old() = 0; + virtual void GetItemCount_old() = 0; + virtual bool GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual void UpdateInventoryPos_old( uint64 ulItemID, uint32 unNewInventoryPos ) = 0; + virtual void DropItem_old( uint64 ulItemID ) = 0; +}; + +struct DropItemResponse001_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 2 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +#endif // ISTEAMUSERITEMS001_H diff --git a/sdk/steam/isteamuseritems002.h b/sdk/steam/isteamuseritems002.h new file mode 100644 index 000000000..154a83b0e --- /dev/null +++ b/sdk/steam/isteamuseritems002.h @@ -0,0 +1,24 @@ + +#ifndef ISTEAMUSERITEMS002_H +#define ISTEAMUSERITEMS002_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +class ISteamUserItems002 +{ +public: + virtual void LoadItems_old() = 0; + virtual void GetItemCount_old() = 0; + virtual bool GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual void UpdateInventoryPos_old( uint64 ulItemID, uint32 unNewInventoryPos ) = 0; + virtual void DropItem_old( uint64 ulItemID ) = 0; + virtual SteamAPICall_t GetItemBlob( uint64 ulItemID ) = 0; + virtual SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) = 0; +}; + +#endif // ISTEAMUSERITEMS002_H diff --git a/sdk/steam/isteamuseritems003.h b/sdk/steam/isteamuseritems003.h new file mode 100644 index 000000000..6cc63ef39 --- /dev/null +++ b/sdk/steam/isteamuseritems003.h @@ -0,0 +1,24 @@ + +#ifndef ISTEAMUSERITEMS003_H +#define ISTEAMUSERITEMS003_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +class ISteamUserItems003 +{ +public: + virtual SteamAPICall_t LoadItems() = 0; + virtual SteamAPICall_t GetItemCount() = 0; + virtual bool GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual SteamAPICall_t UpdateInventoryPos( uint64 ulItemID, uint32 unNewInventoryPos ) = 0; + virtual SteamAPICall_t DropItem_old2( uint64 ulItemID ) = 0; + virtual SteamAPICall_t GetItemBlob( uint64 ulItemID ) = 0; + virtual SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) = 0; +}; + +#endif // ISTEAMUSERITEMS003_H diff --git a/sdk/steam/steam_api.h b/sdk/steam/steam_api.h index 7584b249d..fbb674292 100644 --- a/sdk/steam/steam_api.h +++ b/sdk/steam/steam_api.h @@ -248,6 +248,14 @@ #include "isteamgameserver001.h" #include "isteamgameserverstats.h" #include "isteamgamestats.h" +#include "isteamuseritems.h" +#include "isteamuseritems001.h" +#include "isteamuseritems002.h" +#include "isteamuseritems003.h" +#include "isteamgameserveritems.h" +#include "isteamgameserveritems001.h" +#include "isteamgameserveritems002.h" +#include "isteamgameserveritems003.h" //----------------------------------------------------------------------------------------------------------------------------------------------------------// From a00bd0bb6b61bc5c488a7634a5ed0d0bb241da3a Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 28 Feb 2026 22:37:41 +0300 Subject: [PATCH 031/159] Remove unnecessary includes --- sdk/steam/isteamclient001.h | 2 -- sdk/steam/isteamclient002.h | 2 -- sdk/steam/isteamclient003.h | 2 -- sdk/steam/isteamclient004.h | 2 -- sdk/steam/isteamclient005.h | 2 -- 5 files changed, 10 deletions(-) diff --git a/sdk/steam/isteamclient001.h b/sdk/steam/isteamclient001.h index cf421b73f..239e2065b 100644 --- a/sdk/steam/isteamclient001.h +++ b/sdk/steam/isteamclient001.h @@ -7,8 +7,6 @@ // this interface version is not found in public SDK archives, it is based on reversing old Linux binaries -#include "isteammasterserverupdater.h" - class ISteamClient001 { public: diff --git a/sdk/steam/isteamclient002.h b/sdk/steam/isteamclient002.h index a8bc9be46..05dd2573d 100644 --- a/sdk/steam/isteamclient002.h +++ b/sdk/steam/isteamclient002.h @@ -7,8 +7,6 @@ // this interface version is not found in public SDK archives, it is based on reversing old Linux binaries -#include "isteammasterserverupdater.h" - class ISteamClient002 { public: diff --git a/sdk/steam/isteamclient003.h b/sdk/steam/isteamclient003.h index 267072611..2961c911c 100644 --- a/sdk/steam/isteamclient003.h +++ b/sdk/steam/isteamclient003.h @@ -7,8 +7,6 @@ // this interface version is not found in public SDK archives, it is based on reversing old Linux binaries -#include "isteammasterserverupdater.h" - class ISteamClient003 { public: diff --git a/sdk/steam/isteamclient004.h b/sdk/steam/isteamclient004.h index 7a6aafb61..8cdd0b897 100644 --- a/sdk/steam/isteamclient004.h +++ b/sdk/steam/isteamclient004.h @@ -7,8 +7,6 @@ // this interface version is not found in public SDK archives, it is based on reversing old Linux binaries -#include "isteammasterserverupdater.h" - class ISteamClient004 { public: diff --git a/sdk/steam/isteamclient005.h b/sdk/steam/isteamclient005.h index 614537c0d..3a4c90e91 100644 --- a/sdk/steam/isteamclient005.h +++ b/sdk/steam/isteamclient005.h @@ -7,8 +7,6 @@ // this interface version is not found in public SDK archives, it is based on reversing old Linux binaries -#include "isteammasterserverupdater.h" - class ISteamClient005 { public: From bd72a4dfa0891f7cdef404d8c3e88395322dc3c2 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 28 Feb 2026 23:22:05 +0300 Subject: [PATCH 032/159] Use STEAMGAMESERVER_QUERY_PORT_SHARED constant --- dll/steam_gameserver.cpp | 5 +++-- dll/steam_matchmaking_servers.cpp | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index bfa86488a..389ffa5bc 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -928,7 +928,8 @@ bool Steam_GameServer::Obsolete_GSSetStatus( int32 nAppIdServed, uint32 unServer server_data.set_max_player_count(cPlayersMax); server_data.set_bot_player_count(cBotPlayers); server_data.set_port(unGamePort); - server_data.set_query_port(0xFFFF); + server_data.set_query_port(STEAMGAMESERVER_QUERY_PORT_SHARED); + server_data.set_spectator_port(0); server_data.set_server_name(pchServerName); server_data.set_spectator_server_name(""); server_data.set_game_dir(pchGameDir ? pchGameDir : ""); @@ -1019,7 +1020,7 @@ bool Steam_GameServer::GSSetServerType( int32 nGameAppId, uint32 unServerFlags, PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - bool ret = BSetServerType(unServerFlags, unGameIP, unGamePort, 0xFFFF, 0xFFFF, pchGameDir, pchVersion, false); + bool ret = BSetServerType(unServerFlags, unGameIP, unGamePort, 0, STEAMGAMESERVER_QUERY_PORT_SHARED, pchGameDir, pchVersion, false); server_data.set_appid(nGameAppId); return ret; } diff --git a/dll/steam_matchmaking_servers.cpp b/dll/steam_matchmaking_servers.cpp index 1277c1e29..6b99bac93 100644 --- a/dll/steam_matchmaking_servers.cpp +++ b/dll/steam_matchmaking_servers.cpp @@ -606,7 +606,7 @@ void Steam_Matchmaking_Servers::server_details(Gameserver *g, gameserveritem_t * int latency = MIN_LATENCY; uint16 query_port = g->query_port(); - if (g->query_port() == 0xFFFF) { + if (g->query_port() == STEAMGAMESERVER_QUERY_PORT_SHARED) { query_port = g->port(); } @@ -767,7 +767,7 @@ void Steam_Matchmaking_Servers::server_details_players(Gameserver *g, Steam_Matc void Steam_Matchmaking_Servers::server_details_rules(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r) { uint16 query_port = g->query_port(); - if (g->query_port() == 0xFFFF) { + if (g->query_port() == STEAMGAMESERVER_QUERY_PORT_SHARED) { query_port = g->port(); } @@ -915,7 +915,7 @@ void Steam_Matchmaking_Servers::RunCallbacks() for (auto &g : gameservers) { PRINT_DEBUG("%u:%u", g.server.ip(), g.server.query_port()); uint16 query_port = g.server.query_port(); - if (query_port == 0xFFFF) { + if (query_port == STEAMGAMESERVER_QUERY_PORT_SHARED) { query_port = g.server.port(); } From 5fba4c09bfada016a025c61ee908f2969e8ff72e Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 28 Feb 2026 23:25:41 +0300 Subject: [PATCH 033/159] Improve logging --- dll/steam_gameserver.cpp | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index 389ffa5bc..7a6ad0807 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -100,7 +100,7 @@ std::vector>* Steam_GameServer::ge /// This is called by SteamGameServer_Init, and you will usually not need to call it directly bool Steam_GameServer::InitGameServer( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, uint32 unFlags, AppId_t nGameAppId, const char *pchVersionString ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%u %u %u %u %u '%s'", unIP, usGamePort, usQueryPort, unFlags, nGameAppId, pchVersionString); std::lock_guard lock(global_mutex); if (logged_in) return false; // may not be changed after logged in. @@ -332,7 +332,7 @@ void Steam_GameServer::SetPasswordProtected( bool bPasswordProtected ) /// is not used. void Steam_GameServer::SetSpectatorPort( uint16 unSpectatorPort ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%u", unSpectatorPort); std::lock_guard lock(global_mutex); server_data.set_spectator_port(unSpectatorPort); } @@ -455,7 +455,7 @@ CSteamID Steam_GameServer::CreateUnauthenticatedUserConnection() // account being logged into multiple servers, showing who is currently on a server, etc. void Steam_GameServer::SendUserDisconnect( CSteamID steamIDUser ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%llu", steamIDUser.ConvertToUint64()); std::lock_guard lock(global_mutex); remove_player(steamIDUser); @@ -470,7 +470,7 @@ void Steam_GameServer::SendUserDisconnect( CSteamID steamIDUser ) // Return Value: true if successful, false if failure (ie, steamIDUser wasn't for an active player) bool Steam_GameServer::BUpdateUserData( CSteamID steamIDUser, const char *pchPlayerName, uint32 uScore ) { - PRINT_DEBUG("%llu %s %u", steamIDUser.ConvertToUint64(), pchPlayerName, uScore); + PRINT_DEBUG("%llu '%s' %u", steamIDUser.ConvertToUint64(), pchPlayerName, uScore); std::lock_guard lock(global_mutex); auto player_it = std::find_if(players.begin(), players.end(), [&steamIDUser](std::pair& player) @@ -508,7 +508,8 @@ bool Steam_GameServer::BUpdateUserData( CSteamID steamIDUser, const char *pchPla bool Steam_GameServer::BSetServerType( uint32 unServerFlags, uint32 unGameIP, uint16 unGamePort, uint16 unSpectatorPort, uint16 usQueryPort, const char *pchGameDir, const char *pchVersion, bool bLANMode ) { - PRINT_DEBUG("%u %u '%s' '%s'", unGameIP, unGamePort, pchVersion, pchGameDir); + PRINT_DEBUG("%u %u %u %u %u '%s' '%s' %i", unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, + pchGameDir, pchVersion, bLANMode); std::lock_guard lock(global_mutex); // Note: appid 55100 doesn't call InitGameServer(), it immediately calls this function @@ -531,6 +532,8 @@ bool Steam_GameServer::BSetServerType( uint32 unServerFlags, uint32 unGameIP, ui bool Steam_GameServer::BSetServerType( int32 nGameAppId, uint32 unServerFlags, uint32 unGameIP, uint16 unGamePort, uint16 unSpectatorPort, uint16 usQueryPort, const char *pchGameDir, const char *pchVersion, bool bLANMode ) { + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); bool ret = BSetServerType(unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, pchGameDir, pchVersion, bLANMode); server_data.set_appid(nGameAppId); return ret; @@ -541,8 +544,9 @@ void Steam_GameServer::UpdateServerStatus( int cPlayers, int cPlayersMax, int cB const char *pchServerName, const char *pSpectatorServerName, const char *pchMapName ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%i %i %i '%s' '%s' '%s'", cPlayers, cPlayersMax, cBotPlayers, pchServerName, pSpectatorServerName, pchMapName); std::lock_guard lock(global_mutex); + server_data.set_num_players(cPlayers); server_data.set_max_player_count(cPlayersMax); server_data.set_bot_player_count(cBotPlayers); @@ -562,8 +566,7 @@ void Steam_GameServer::UpdateSpectatorPort( uint16 unSpectatorPort ) // it allows users to filter in the matchmaking/server-browser interfaces based on the value void Steam_GameServer::SetGameType( const char *pchGameType ) { - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); + PRINT_DEBUG_TODO(); } // Ask if a user has a specific achievement for this game, will get a callback on reply @@ -636,7 +639,7 @@ void Steam_GameServer::CancelAuthTicket( HAuthTicket hAuthTicket ) // to determine if the user owns downloadable content specified by the provided AppID. EUserHasLicenseForAppResult Steam_GameServer::UserHasLicenseForApp( CSteamID steamID, AppId_t appID ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%llu %u", steamID.ConvertToUint64(), appID); std::lock_guard lock(global_mutex); return k_EUserHasLicenseResultHasLicense; } @@ -646,26 +649,23 @@ EUserHasLicenseForAppResult Steam_GameServer::UserHasLicenseForApp( CSteamID ste // returns false if we're not connected to the steam servers and thus cannot ask bool Steam_GameServer::RequestUserGroupStatus( CSteamID steamIDUser, CSteamID steamIDGroup ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%llu %llu", steamIDUser.ConvertToUint64(), steamIDGroup.ConvertToUint64()); std::lock_guard lock(global_mutex); return true; } - // these two functions s are deprecated, and will not return results // they will be removed in a future version of the SDK void Steam_GameServer::GetGameplayStats( ) { PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); } STEAM_CALL_RESULT( GSReputation_t ) SteamAPICall_t Steam_GameServer::GetServerReputation() { PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); return 0; } @@ -812,7 +812,6 @@ STEAM_CALL_RESULT( AssociateWithClanResult_t ) SteamAPICall_t Steam_GameServer::AssociateWithClan( CSteamID steamIDClan ) { PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); return 0; } @@ -822,7 +821,6 @@ STEAM_CALL_RESULT( ComputeNewPlayerCompatibilityResult_t ) SteamAPICall_t Steam_GameServer::ComputeNewPlayerCompatibility( CSteamID steamIDNewPlayer ) { PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); return 0; } @@ -920,7 +918,9 @@ bool Steam_GameServer::GSSendUserStatusResponse( CSteamID steamID, int nSecondsC bool Steam_GameServer::Obsolete_GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%i %u %i %i %i %i '%s' '%s' '%s' '%s'", + nAppIdServed, unServerFlags, cPlayers, cPlayersMax, cBotPlayers, unGamePort, + pchServerName, pchGameDir, pchMapName, pchVersion); std::lock_guard lock(global_mutex); server_data.set_appid(nAppIdServed); @@ -992,8 +992,6 @@ bool Steam_GameServer::GSSendSteam3UserConnect( CSteamID steamID, uint32 unIPPub bool Steam_GameServer::GSRemoveUserConnect( uint32 unUserID ) { PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; } From 1b3589ce455a332008eab4a96ebd7f3b61564991 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 28 Feb 2026 23:28:44 +0300 Subject: [PATCH 034/159] Use game_dir field for server details --- dll/source_query.cpp | 2 +- dll/steam_gameserver.cpp | 2 -- dll/steam_matchmaking_servers.cpp | 10 +++++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/dll/source_query.cpp b/dll/source_query.cpp index 300c4d5f5..e2eaf4259 100644 --- a/dll/source_query.cpp +++ b/dll/source_query.cpp @@ -155,7 +155,7 @@ std::vector Source_Query::handle_source_query(const void* buffer, size_ serialize_response(output_buffer, static_cast(2)); serialize_response(output_buffer, gs.server_name()); serialize_response(output_buffer, gs.map_name()); - serialize_response(output_buffer, gs.mod_dir()); + serialize_response(output_buffer, !gs.mod_dir().empty() ? gs.mod_dir() : gs.game_dir()); serialize_response(output_buffer, gs.product()); serialize_response(output_buffer, static_cast(gs.appid())); serialize_response(output_buffer, static_cast(players.size())); diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index 7a6ad0807..548701721 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -852,8 +852,6 @@ void Steam_GameServer::RunCallbacks() msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); if (server_data.appid() == 0) server_data.set_appid(settings->get_local_game_id().AppID()); - if (server_data.mod_dir().empty()) - server_data.set_mod_dir(server_data.game_dir()); msg.set_allocated_gameserver(new Gameserver(server_data)); //msg.mutable_gameserver()->set_num_players(auth_manager->countInboundAuth()); network->sendToAllIndividuals(&msg, true); diff --git a/dll/steam_matchmaking_servers.cpp b/dll/steam_matchmaking_servers.cpp index 6b99bac93..cb7d23036 100644 --- a/dll/steam_matchmaking_servers.cpp +++ b/dll/steam_matchmaking_servers.cpp @@ -637,7 +637,7 @@ void Steam_Matchmaking_Servers::server_details(Gameserver *g, gameserveritem_t * PRINT_DEBUG(" ssq server info ok"); if (ssq_info_has_steamid(ssq_a2s_info)) g->set_id(ssq_a2s_info->steamid); g->set_game_description(ssq_a2s_info->game); - g->set_mod_dir(ssq_a2s_info->folder); + g->set_game_dir(ssq_a2s_info->folder); if (ssq_a2s_info->server_type == A2S_SERVER_TYPE_DEDICATED) g->set_dedicated_server(true); else if (ssq_a2s_info->server_type == A2S_SERVER_TYPE_STV_RELAY) g->set_dedicated_server(true); else g->set_dedicated_server(false); @@ -694,7 +694,11 @@ void Steam_Matchmaking_Servers::server_details(Gameserver *g, gameserveritem_t * server->m_steamID = CSteamID((uint64)g->id()); memset(server->m_szGameDir, 0, sizeof(server->m_szGameDir)); - g->mod_dir().copy(server->m_szGameDir, sizeof(server->m_szGameDir) - 1); + if (!g->mod_dir().empty()) { + g->mod_dir().copy(server->m_szGameDir, sizeof(server->m_szGameDir) - 1); + } else { + g->game_dir().copy(server->m_szGameDir, sizeof(server->m_szGameDir) - 1); + } memset(server->m_szMap, 0, sizeof(server->m_szMap)); g->map_name().copy(server->m_szMap, sizeof(server->m_szMap) - 1); @@ -711,7 +715,7 @@ void Steam_Matchmaking_Servers::server_details(Gameserver *g, gameserveritem_t * void Steam_Matchmaking_Servers::server_details_players(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r) { uint16 query_port = g->query_port(); - if (g->query_port() == 0xFFFF) { + if (g->query_port() == STEAMGAMESERVER_QUERY_PORT_SHARED) { query_port = g->port(); } From f911f7a7657b3a7e28da7a91acda85023b565498 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sun, 1 Mar 2026 04:56:06 +0300 Subject: [PATCH 035/159] Add missing run_every_runcb registration --- dll/steam_gameserver_items.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dll/steam_gameserver_items.cpp b/dll/steam_gameserver_items.cpp index 99b0f2bac..d5c800907 100644 --- a/dll/steam_gameserver_items.cpp +++ b/dll/steam_gameserver_items.cpp @@ -43,12 +43,14 @@ Steam_GameServer_Items::Steam_GameServer_Items(class Settings *settings, class N this->network->setCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_GameServer_Items::steam_gameserver_items_network_callback, this); this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_GameServer_Items::steam_gameserver_items_network_callback, this); + this->run_every_runcb->add(Steam_GameServer_Items::steam_gameserver_items_run_every_runcb, this); } Steam_GameServer_Items::~Steam_GameServer_Items() { this->network->rmCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_GameServer_Items::steam_gameserver_items_network_callback, this); this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_GameServer_Items::steam_gameserver_items_network_callback, this); + this->run_every_runcb->remove(Steam_GameServer_Items::steam_gameserver_items_run_every_runcb, this); } SteamAPICall_t Steam_GameServer_Items::LoadItems( CSteamID ownerID ) From e5455cd4e3f01c70485915fdf032850b5d09f493 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sun, 1 Mar 2026 01:11:53 +0300 Subject: [PATCH 036/159] Update server app id handling --- dll/dll/steam_gameserver.h | 1 + dll/steam_gameserver.cpp | 132 +++++++++++++++++++++++++++---------- 2 files changed, 97 insertions(+), 36 deletions(-) diff --git a/dll/dll/steam_gameserver.h b/dll/dll/steam_gameserver.h index 85982f738..4921b666d 100644 --- a/dll/dll/steam_gameserver.h +++ b/dll/dll/steam_gameserver.h @@ -73,6 +73,7 @@ public ISteamGameServer std::vector outgoing_packets{}; + bool set_appid(int32 appid, const char *version_str, bool autodetect); void set_version(const char *pchVersionString); public: diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index 548701721..467393580 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -22,6 +22,59 @@ #define SEND_SERVER_RATE 5.0 +bool Steam_GameServer::set_appid(int32 input_appid, const char *game_dir, bool autodetect) +{ + if (server_data.appid() != 0) { + if (input_appid == 0 || input_appid == server_data.appid()) + return true; + } + + uint32 appid = input_appid; + + // If appid has not been provided by the game code, try reading it from steam_appid.txt. + // Some dedicated servers write it there and expect steamclient.dll to read it. + if (!appid && autodetect) { + char buf[10] = {}; + + // Try game dir. + if (game_dir && game_dir[0]) { + std::string file_path = std::string(game_dir) + PATH_SEPARATOR + "steam_appid.txt"; + + memset(buf, 0, sizeof(buf)); + Local_Storage::get_file_data(file_path, buf, sizeof(buf) - 1); + appid = atoi(buf); + } + + // Try current dir. + if (!appid) { + std::string file_path = "steam_appid.txt"; + + memset(buf, 0, sizeof(buf)); + Local_Storage::get_file_data(file_path, buf, sizeof(buf) - 1); + appid = atoi(buf); + } + + // Finally, fall back to config appid. + if (!appid) { + appid = settings->get_local_game_id().AppID(); + } + + if (!appid) { + PRINT_DEBUG("error cannot determine server appid", appid); + } + } + + if (!appid) + return false; + + server_data.set_appid(appid); + settings->set_game_id(CGameID(appid)); + network->setAppID(appid); + + PRINT_DEBUG("set server appid to %u", appid); + return true; +} + void Steam_GameServer::set_version(const char *pchVersionString) { PRINT_DEBUG("'%s'", pchVersionString); @@ -103,21 +156,25 @@ bool Steam_GameServer::InitGameServer( uint32 unIP, uint16 usGamePort, uint16 us PRINT_DEBUG("%u %u %u %u %u '%s'", unIP, usGamePort, usQueryPort, unFlags, nGameAppId, pchVersionString); std::lock_guard lock(global_mutex); - if (logged_in) return false; // may not be changed after logged in. + if (logged_in) // may not be changed after logged in. + return false; + + if (nGameAppId == 0) + return false; - server_data.set_appid(nGameAppId); - set_version(pchVersionString); server_data.set_ip(unIP); server_data.set_port(usGamePort); server_data.set_query_port(usQueryPort); + set_appid(nGameAppId, "", false); + set_version(pchVersionString); server_data.set_offline(false); + //TODO: flags should be k_unServerFlag + flags = unFlags; + if (!settings->disable_source_query) network->startQuery({ unIP, usQueryPort }); - if (!settings->get_local_game_id().AppID()) settings->set_game_id(CGameID(nGameAppId)); - //TODO: flags should be k_unServerFlag - flags = unFlags; policy_response_called = false; call_servers_connected = false; call_servers_disconnected = false; @@ -512,19 +569,22 @@ bool Steam_GameServer::BSetServerType( uint32 unServerFlags, uint32 unGameIP, ui pchGameDir, pchVersion, bLANMode); std::lock_guard lock(global_mutex); - // Note: appid 55100 doesn't call InitGameServer(), it immediately calls this function - InitGameServer(unGameIP, unGamePort, usQueryPort, unServerFlags, settings->get_local_game_id().AppID(), pchVersion); + if (!set_appid(0, pchGameDir, true)) + return false; server_data.set_ip(unGameIP); server_data.set_port(unGamePort); - server_data.set_query_port(usQueryPort); server_data.set_spectator_port(unSpectatorPort); - set_version(pchVersion); - + server_data.set_query_port(usQueryPort); server_data.set_game_dir(pchGameDir ? pchGameDir : ""); + set_version(pchVersion); + server_data.set_offline(false); flags = unServerFlags; + if (!settings->disable_source_query && !network->isQueryAlive()) + network->startQuery({ unGameIP, usQueryPort }); + //TODO? return true; } @@ -534,9 +594,11 @@ bool Steam_GameServer::BSetServerType( int32 nGameAppId, uint32 unServerFlags, u { PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - bool ret = BSetServerType(unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, pchGameDir, pchVersion, bLANMode); - server_data.set_appid(nGameAppId); - return ret; + + if (!set_appid(nGameAppId, pchGameDir, true)) + return false; + + return BSetServerType(unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, pchGameDir, pchVersion, bLANMode); } // Updates server status values which shows up in the server browser and matchmaking APIs @@ -846,12 +908,12 @@ void Steam_GameServer::RunCallbacks() policy_response_called = true; } - if (logged_in && server_data.port() != 0 && check_timedout(last_sent_server_info, SEND_SERVER_RATE)) { + if (logged_in && + server_data.appid() != 0 && server_data.port() != 0 && + check_timedout(last_sent_server_info, SEND_SERVER_RATE)) { PRINT_DEBUG("Sending Gameserver"); Common_Message msg{}; msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - if (server_data.appid() == 0) - server_data.set_appid(settings->get_local_game_id().AppID()); msg.set_allocated_gameserver(new Gameserver(server_data)); //msg.mutable_gameserver()->set_num_players(auth_manager->countInboundAuth()); network->sendToAllIndividuals(&msg, true); @@ -909,8 +971,6 @@ bool Steam_GameServer::GSSendUserDisconnect( CSteamID steamID ) bool Steam_GameServer::GSSendUserStatusResponse( CSteamID steamID, int nSecondsConnected, int nSecondsSinceLast ) { PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; } @@ -921,7 +981,7 @@ bool Steam_GameServer::Obsolete_GSSetStatus( int32 nAppIdServed, uint32 unServer pchServerName, pchGameDir, pchMapName, pchVersion); std::lock_guard lock(global_mutex); - server_data.set_appid(nAppIdServed); + set_appid(nAppIdServed, pchGameDir, false); server_data.set_num_players(cPlayers); server_data.set_max_player_count(cPlayersMax); server_data.set_bot_player_count(cBotPlayers); @@ -929,13 +989,12 @@ bool Steam_GameServer::Obsolete_GSSetStatus( int32 nAppIdServed, uint32 unServer server_data.set_query_port(STEAMGAMESERVER_QUERY_PORT_SHARED); server_data.set_spectator_port(0); server_data.set_server_name(pchServerName); - server_data.set_spectator_server_name(""); - server_data.set_game_dir(pchGameDir ? pchGameDir : ""); + server_data.set_spectator_server_name(pchServerName); + server_data.set_game_dir(pchGameDir); server_data.set_map_name(pchMapName); set_version(pchVersion); server_data.set_offline(false); - if (!settings->get_local_game_id().AppID()) settings->set_game_id(CGameID(nAppIdServed)); flags = unServerFlags; return true; @@ -1007,7 +1066,7 @@ bool Steam_GameServer::GSUpdateStatus( int cPlayers, int cPlayersMax, int cBotPl PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - UpdateServerStatus(cPlayers, cPlayersMax, cBotPlayers, pchServerName, "", pchMapName); + UpdateServerStatus(cPlayers, cPlayersMax, cBotPlayers, pchServerName, pchServerName, pchMapName); return true; } @@ -1016,9 +1075,10 @@ bool Steam_GameServer::GSSetServerType( int32 nGameAppId, uint32 unServerFlags, PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - bool ret = BSetServerType(unServerFlags, unGameIP, unGamePort, 0, STEAMGAMESERVER_QUERY_PORT_SHARED, pchGameDir, pchVersion, false); - server_data.set_appid(nGameAppId); - return ret; + if (!set_appid(nGameAppId, pchGameDir, true)) + return false; + + return BSetServerType(unServerFlags, unGameIP, unGamePort, 0, STEAMGAMESERVER_QUERY_PORT_SHARED, pchGameDir, pchVersion, false); } bool Steam_GameServer::GSSetServerType2( int32 nGameAppId, uint32 unServerFlags, uint32 unGameIP, uint16 unGamePort, uint16 unSpectatorPort, uint16 usQueryPort, const char *pchGameDir, const char *pchVersion, bool bLANMode ) @@ -1026,10 +1086,10 @@ bool Steam_GameServer::GSSetServerType2( int32 nGameAppId, uint32 unServerFlags, PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - SetSpectatorPort(unSpectatorPort); - bool ret = BSetServerType(unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, pchGameDir, pchVersion, bLANMode); - server_data.set_appid(nGameAppId); - return ret; + if (!set_appid(nGameAppId, pchGameDir, true)) + return false; + + return BSetServerType(unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, pchGameDir, pchVersion, bLANMode); } bool Steam_GameServer::GSUpdateStatus2( int cPlayers, int cPlayersMax, int cBotPlayers, const char *pchServerName, const char *pSpectatorServerName, const char *pchMapName ) @@ -1064,7 +1124,7 @@ void Steam_GameServer::GSUpdateSpectatorPort( uint16 unSpectatorPort ) PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - UpdateSpectatorPort(unSpectatorPort); + SetSpectatorPort(unSpectatorPort); } void Steam_GameServer::GSSetGameType( const char *pchType ) @@ -1098,10 +1158,10 @@ bool Steam_GameServer::GSSetServerType( int32 nGameAppId, uint32 unServerFlags, PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - SetSpectatorPort(unSpectatorPort); - bool ret = BSetServerType(unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, pchGameDir, pchVersion, bLANMode); - server_data.set_appid(nGameAppId); - return ret; + if (!set_appid(nGameAppId, pchGameDir, true)) + return false; + + return BSetServerType(unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, pchGameDir, pchVersion, bLANMode); } bool Steam_GameServer::GSUpdateStatus( int cPlayers, int cPlayersMax, int cBotPlayers, const char *pchServerName, const char *pSpectatorServerName, const char *pchMapName ) From ee2baed270227cefb856d799bd12d40aa3998ef2 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Tue, 3 Mar 2026 19:17:35 +0300 Subject: [PATCH 037/159] Rewrite SteamGameServer_Init to be more accurate --- dll/dll.cpp | 202 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 143 insertions(+), 59 deletions(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index d3bef7d32..4b1f13614 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -957,38 +957,152 @@ STEAMAPI_API HSteamUser S_CALLTYPE SteamGameServer_GetHSteamUser() return SERVER_HSTEAMUSER; } -//See: SteamGameServer_Init -//STEAMAPI_API steam_bool S_CALLTYPE SteamGameServer_InitSafe(uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ) -STEAMAPI_API steam_bool S_CALLTYPE SteamGameServer_InitSafe( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 unknown, EServerMode eServerMode, void *unknown1, void *unknown2, void *unknown3 ) +STEAMAPI_API ISteamClient *SteamGameServerClient(); + +//SteamGameServer002 (very old, not in public SDKs): +//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, EServerMode eServerMode, int nGameAppId, const char *pchGameDir, const char *pchVersionString ); +//SteamGameServer004 and before: +//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usSpectatorPort, uint16 usQueryPort, EServerMode eServerMode, int nGameAppId, const char *pchGameDir, const char *pchVersionString ); +//SteamGameServer010 and before: +//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usSpectatorPort, uint16 usQueryPort, EServerMode eServerMode, const char *pchGameDir, const char *pchVersionString ); +//SteamGameServer014 and before: +//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ); +//SteamGameServer015 and later: +//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ); +STEAMAPI_API steam_bool SteamGameServer_Init( void *a1, void *a2, void *a3, void *a4, void *a5, void *a6, void *a7, void *a8, void *a9 ) { PRINT_DEBUG_ENTRY(); - const char *pchVersionString{}; - EServerMode serverMode{}; - uint16 usQueryPort{}; + + if (!strstr(old_gameserver, "SteamGameServer")) + return false; + + int gameserver_version = atoi(old_gameserver + 15); + if (gameserver_version == 0) + return false; + // call this first since it loads old interfaces Steam_Client* client = get_steam_client(); - bool logon_anon = false; - if (strcmp(old_gameserver, "SteamGameServer010") == 0 || strstr(old_gameserver, "SteamGameServer00") == old_gameserver) { - PRINT_DEBUG("Old game server init safe"); - pchVersionString = (char *)unknown3; - memcpy(&serverMode, &unknown1, sizeof(serverMode)); - memcpy(&usQueryPort, (char *)&eServerMode, sizeof(usQueryPort)); - logon_anon = true; - } else { - pchVersionString = (char *)unknown1; - serverMode = eServerMode; - usQueryPort = unknown; + if (!server_steam_pipe) { + client->CreateLocalUser(&server_steam_pipe, k_EAccountTypeGameServer); + sdk_lifetime_counters.update(true); + //g_pSteamClientGameServer is only used in pre 1.37 (where the interface versions are not provided by the game) + g_pSteamClientGameServer = SteamGameServerClient(); } - bool ret = SteamInternal_GameServer_Init( unIP, usSteamPort, usGamePort, usQueryPort, serverMode, pchVersionString ); - if (logon_anon) { + bool ret = false; + + if (gameserver_version <= 4) { + uint32 unIP; + uint16 usPort; + uint16 usGamePort; + uint16 usSpectatorPort; + uint16 usQueryPort; + EServerMode eServerMode; + int nGameAppId; + const char *pchGameDir; + const char *pchVersionString; + + memcpy(&unIP, &a1, sizeof(unIP)); + memcpy(&usPort, &a2, sizeof(usPort)); + memcpy(&usGamePort, &a3, sizeof(usGamePort)); + memcpy(&usSpectatorPort, &a4, sizeof(usSpectatorPort)); + memcpy(&usQueryPort, &a5, sizeof(usQueryPort)); + memcpy(&eServerMode, &a6, sizeof(eServerMode)); + memcpy(&nGameAppId, &a7, sizeof(nGameAppId)); + memcpy(&pchGameDir, &a8, sizeof(pchGameDir)); + memcpy(&pchVersionString, &a9, sizeof(pchVersionString)); + + uint32 unFlags = 0; + if (eServerMode == eServerModeAuthenticationAndSecure) unFlags = k_unServerFlagSecure; + else if (eServerMode == eServerModeNoAuthentication) unFlags = k_unServerFlagPrivate; + bool bLANMode = (eServerMode == eServerModeNoAuthentication); + client->steam_gameserver->LogOnAnonymous(); + ret = client->steam_gameserver->BSetServerType(nGameAppId, unFlags, unIP, usGamePort, usSpectatorPort, usQueryPort, pchGameDir, pchVersionString, bLANMode); + } else if (gameserver_version <= 10) { + uint32 unIP; + uint16 usPort; + uint16 usGamePort; + uint16 usSpectatorPort; + uint16 usQueryPort; + EServerMode eServerMode; + const char *pchGameDir; + const char *pchVersionString; + + memcpy(&unIP, &a1, sizeof(unIP)); + memcpy(&usPort, &a2, sizeof(usPort)); + memcpy(&usGamePort, &a3, sizeof(usGamePort)); + memcpy(&usSpectatorPort, &a4, sizeof(usSpectatorPort)); + memcpy(&usQueryPort, &a5, sizeof(usQueryPort)); + memcpy(&eServerMode, &a6, sizeof(eServerMode)); + memcpy(&pchGameDir, &a7, sizeof(pchGameDir)); + memcpy(&pchVersionString, &a8, sizeof(pchVersionString)); + + uint32 unFlags = 0; + if (eServerMode == eServerModeAuthenticationAndSecure) unFlags = k_unServerFlagSecure; + else if (eServerMode == eServerModeNoAuthentication) unFlags = k_unServerFlagPrivate; + bool bLANMode = (eServerMode == eServerModeNoAuthentication); + + ret = client->steam_gameserver->BSetServerType(unFlags, unIP, usGamePort, usSpectatorPort, usQueryPort, pchGameDir, pchVersionString, bLANMode); + + if (ret) { + client->steam_gameserver->LogOnAnonymous(); + } + } else if (gameserver_version <= 12) { // export was removed in 1.37 + uint32 unIP; + uint16 usSteamPort; + uint16 usGamePort; + uint16 usQueryPort; + EServerMode eServerMode; + const char *pchVersionString; + + memcpy(&unIP, &a1, sizeof(unIP)); + memcpy(&usSteamPort, &a2, sizeof(usSteamPort)); + memcpy(&usGamePort, &a3, sizeof(usGamePort)); + memcpy(&usQueryPort, &a4, sizeof(usQueryPort)); + memcpy(&eServerMode, &a5, sizeof(eServerMode)); + memcpy(&pchVersionString, &a6, sizeof(pchVersionString)); + + uint32 unFlags = 0; + if (eServerMode == eServerModeAuthenticationAndSecure) unFlags = k_unServerFlagSecure; + else if (eServerMode == eServerModeNoAuthentication) unFlags = k_unServerFlagPrivate; + + int nGameAppId = client->settings_server->get_local_game_id().AppID(); + + if (nGameAppId != 0) { + ret = client->steam_gameserver->InitGameServer(unIP, usGamePort, usQueryPort, unFlags, nGameAppId, pchVersionString); + } else { + PRINT_DEBUG("error appid is not set"); + } } return ret; } -STEAMAPI_API ISteamClient *SteamGameServerClient(); +//See: SteamGameServer_Init +//STEAMAPI_API steam_bool S_CALLTYPE SteamGameServer_InitSafe( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usSpectatorPort, uint16 usQueryPort, EServerMode eServerMode, const char *pchGameDir, const char *pchVersionString ) +//STEAMAPI_API steam_bool S_CALLTYPE SteamGameServer_InitSafe( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ) +STEAMAPI_API steam_bool S_CALLTYPE SteamGameServer_InitSafe( void *a1, void *a2, void *a3, void *a4, void *a5, void *a6, void *a7, void *a8 ) +{ + PRINT_DEBUG_ENTRY(); + + if (!strstr(old_gameserver, "SteamGameServer")) + return false; + + int gameserver_version = atoi(old_gameserver + 15); + if (gameserver_version == 0) + return false; + + bool ret = false; + + if (gameserver_version >= 5 && gameserver_version <= 10) { // export didn't exist before 1.01 + ret = SteamGameServer_Init(a1, a2, a3, a4, a5, a6, a7, a8, 0); + } else if (gameserver_version <= 12) { // export was removed in 1.37 + ret = SteamGameServer_Init(a1, a2, a3, a4, a5, a6, 0, 0, 0); + } + + return ret; +} STEAMAPI_API steam_bool S_CALLTYPE SteamInternal_GameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ) { @@ -998,13 +1112,20 @@ STEAMAPI_API steam_bool S_CALLTYPE SteamInternal_GameServer_Init( uint32 unIP, u if (!server_steam_pipe) { client->CreateLocalUser(&server_steam_pipe, k_EAccountTypeGameServer); sdk_lifetime_counters.update(true); - //g_pSteamClientGameServer is only used in pre 1.37 (where the interface versions are not provided by the game) g_pSteamClientGameServer = SteamGameServerClient(); } uint32 unFlags = 0; if (eServerMode == eServerModeAuthenticationAndSecure) unFlags = k_unServerFlagSecure; - return client->steam_gameserver->InitGameServer(unIP, usGamePort, usQueryPort, unFlags, 0, pchVersionString); + else if (eServerMode == eServerModeNoAuthentication) unFlags = k_unServerFlagPrivate; + + int nGameAppId = client->settings_server->get_local_game_id().AppID(); + if (nGameAppId == 0) { + PRINT_DEBUG("error appid is not set"); + return false; + } + + return client->steam_gameserver->InitGameServer(unIP, usGamePort, usQueryPort, unFlags, nGameAppId, pchVersionString); } STEAMAPI_API ESteamAPIInitResult S_CALLTYPE SteamInternal_GameServer_Init_V2( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString, const char *pszInternalCheckInterfaceVersions, SteamErrMsg *pOutErrMsg ) @@ -1021,43 +1142,6 @@ STEAMAPI_API ESteamAPIInitResult S_CALLTYPE SteamInternal_GameServer_Init_V2( ui return ESteamAPIInitResult::k_ESteamAPIInitResult_FailedGeneric; } -//SteamGameServer004 and before: -//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usSpectatorPort, uint16 usQueryPort, EServerMode eServerMode, int nGameAppId, const char *pchGameDir, const char *pchVersionString ); -//SteamGameServer010 and before: -//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usSpectatorPort, uint16 usQueryPort, EServerMode eServerMode, const char *pchGameDir, const char *pchVersionString ); -//SteamGameServer011 and later: -//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ); -//SteamGameServer015 and later: -//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ); -STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 unknown, EServerMode eServerMode, void *unknown1, void *unknown2, void *unknown3 ) -{ - PRINT_DEBUG_ENTRY(); - const char *pchVersionString{}; - EServerMode serverMode{}; - uint16 usQueryPort{}; - // call this first since it loads old interfaces - Steam_Client* client = get_steam_client(); - bool logon_anon = false; - if (strcmp(old_gameserver, "SteamGameServer010") == 0 || strstr(old_gameserver, "SteamGameServer00") == old_gameserver) { - PRINT_DEBUG("Old game server init"); - pchVersionString = (char *)unknown3; - memcpy(&serverMode, &unknown1, sizeof(serverMode)); - memcpy(&usQueryPort, (char *)&eServerMode, sizeof(usQueryPort)); - logon_anon = true; - } else { - pchVersionString = (char *)unknown1; - serverMode = eServerMode; - usQueryPort = unknown; - } - - bool ret = SteamInternal_GameServer_Init( unIP, usSteamPort, usGamePort, usQueryPort, serverMode, pchVersionString ); - if (logon_anon) { - client->steam_gameserver->LogOnAnonymous(); - } - - return ret; -} - STEAMAPI_API void SteamGameServer_Shutdown() { PRINT_DEBUG_ENTRY(); From 250ead88b7529a57916b8ee8cebc5409d7ca01ef Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:24:46 +0100 Subject: [PATCH 038/159] make cloud save userdata off by default --- dll/settings_parser_ufs.cpp | 4 ++-- post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dll/settings_parser_ufs.cpp b/dll/settings_parser_ufs.cpp index 37774befa..25d3692f9 100644 --- a/dll/settings_parser_ufs.cpp +++ b/dll/settings_parser_ufs.cpp @@ -252,8 +252,8 @@ static std::filesystem::path factory_default_cloud_dir(CSimpleIniA *ini, class S void parse_cloud_save(CSimpleIniA *ini, class Settings *settings_client, class Settings *settings_server, class Local_Storage *local_storage) { #ifndef LOBBY_CONNECT - constexpr static bool DEFAULT_CREATE_DEFAULT_DIR = true; - constexpr static bool DEFAULT_CREATE_SPECIFIC_DIRS = true; + constexpr static bool DEFAULT_CREATE_DEFAULT_DIR = false; + constexpr static bool DEFAULT_CREATE_SPECIFIC_DIRS = false; constexpr static const char SPECIFIC_INI_KEY[] = "app::cloud_save::" // then concat the OS specific part diff --git a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini index 7c21892ff..f71a15ea0 100644 --- a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini @@ -38,11 +38,11 @@ unlock_all=0 [app::cloud_save::general] # should the emu create the default directory for cloud saves on startup: # [Steam Install]/userdata/{Steam3AccountID}/{AppID}/ -# default=1 -create_default_dir=1 +# default=0 +create_default_dir=0 # should the emu create the directories specified in the cloud saves section of the current OS on startup -# default=1 -create_specific_dirs=1 +# default=0 +create_specific_dirs=0 # directories which should be created on startup, this is used for cloud saves # some games refuse to work unless these directories exist # there are reserved identifiers which are replaced at runtime From a52e511873208503a53f71b378cae1573f1708e1 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Wed, 4 Mar 2026 23:30:12 +0300 Subject: [PATCH 039/159] Fix callbacks for pre-1.02x SDKs --- dll/dll.cpp | 7 ++++ dll/dll/callback_wrapper.h | 70 ++++++++++++++++++++++++++++++++++++++ dll/dll/steam_client.h | 14 +++++--- dll/steam_client.cpp | 45 ++++++++++++++++++------ 4 files changed, 120 insertions(+), 16 deletions(-) create mode 100644 dll/dll/callback_wrapper.h diff --git a/dll/dll.cpp b/dll/dll.cpp index 4b1f13614..3329fe85a 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -235,6 +235,13 @@ Steam_Client *get_steam_client() if (!steamclient_instance) { load_old_steam_interfaces(); steamclient_instance = new Steam_Client(); + + if (strstr(old_friends, "SteamFriends")) { + int friends_ver = atoi(old_friends + 12); + if (friends_ver != 0 && friends_ver <= 4) { + steamclient_instance->using_old_callbacks = true; + } + } } } diff --git a/dll/dll/callback_wrapper.h b/dll/dll/callback_wrapper.h new file mode 100644 index 000000000..5b7463507 --- /dev/null +++ b/dll/dll/callback_wrapper.h @@ -0,0 +1,70 @@ +/* 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_CALLBACK_WRAPPER_H__ +#define __INCLUDED_CALLBACK_WRAPPER_H__ + +#include "base.h" + +class CCallbackBase001 +{ +public: + CCallbackBase001() { m_nCallbackFlags = 0; m_iCallback = 0; } + // don't add a virtual destructor because we export this binary interface across dll's + virtual void Run( void *pvParam ) = 0; + int GetICallback() { return m_iCallback; } + +protected: + enum { k_ECallbackFlagsRegistered = 0x01, k_ECallbackFlagsGameServer = 0x02 }; + uint8 m_nCallbackFlags; + int m_iCallback; + friend class CCallbackMgr; + friend class CCallBackWrapper; + +private: + CCallbackBase001( const CCallbackBase001& ); + CCallbackBase001& operator=( const CCallbackBase001& ); +}; + +class CCallBackWrapper : public CCallbackBase +{ +public: + CCallBackWrapper( CCallbackBase *callback ) + { + old_callback = reinterpret_cast( callback ); + m_nCallbackFlags = old_callback->m_nCallbackFlags; + m_iCallback = old_callback->m_iCallback; + } + + void Run( void *pvParam ) + { + old_callback->Run( pvParam ); + } + void Run( void *pvParam, bool bIOFailure, SteamAPICall_t hSteamAPICall ) + { + old_callback->Run( pvParam ); + } + int GetCallbackSizeBytes() + { + return 0; + } + +private: + CCallbackBase001 *old_callback{}; +}; + +#endif // __INCLUDED_CALLBACK_WRAPPER_H__ diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index 15cf557a5..dfece97ea 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -64,6 +64,7 @@ #include "overlay/steam_overlay.h" #include "playtime.h" +#include "callback_wrapper.h" enum Steam_Pipe { NO_USER, @@ -111,6 +112,8 @@ public ISteamClient common_helpers::KillableWorker *background_thread{}; void background_thread_proc(); + std::map old_callbacks_map; + public: Networking *network{}; SteamCallResults *callback_results_server{}, *callback_results_client{}; @@ -180,10 +183,11 @@ public ISteamClient PlaytimeCounter* playtime_counter{}; - bool steamclient_server_inited = false; + bool steamclient_server_inited{}; bool gameserver_has_ipv6_functions{}; int steamclient_version{}; + bool using_old_callbacks{}; unsigned steam_pipe_counter = 1; std::map steam_pipes{}; @@ -347,11 +351,11 @@ public ISteamClient ISteamAppTicket *GetAppTicket( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - void RegisterCallback( class CCallbackBase *pCallback, int iCallback); - void UnregisterCallback( class CCallbackBase *pCallback); + void RegisterCallback( class CCallbackBase *pCallback, int iCallback ); + void UnregisterCallback( class CCallbackBase *pCallback) ; - void RegisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall); - void UnregisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall); + void RegisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall ); + void UnregisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall ); void RunCallbacks(bool runClientCB, bool runGameserverCB); void setAppID(uint32 appid); diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index 53013dcff..84524b864 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -482,11 +482,22 @@ void Steam_Client::Remove_SteamAPI_CPostAPIResultInProcess( SteamAPI_PostAPIResu PRINT_DEBUG_TODO(); } -void Steam_Client::RegisterCallback( class CCallbackBase *pCallback, int iCallback) +void Steam_Client::RegisterCallback( class CCallbackBase *pCallback, int iCallback ) { + CCallbackBase *callback_to_add = pCallback; + + // 1.02x added an overload for CCallbackBase::Run. Because of how MSVC handles virtual function + // overloads, this changed the order of functions in vtable from 1.02, which breaks old games. + // We get around this using a wrapper for the old CCallbackBase variant. + if (using_old_callbacks) { + PRINT_DEBUG("creating a wrapper for old callback %08X %i", pCallback, iCallback); + auto [it, _] = old_callbacks_map.emplace(pCallback, pCallback); + callback_to_add = &(it->second); + } + int base_callback = (iCallback / 100) * 100; int callback_id = iCallback % 100; - bool isGameServer = CCallbackMgr::isServer(pCallback); + bool isGameServer = CCallbackMgr::isServer(callback_to_add); PRINT_DEBUG("isGameServer %u %i %i", isGameServer, iCallback, base_callback); switch (base_callback) { @@ -699,18 +710,26 @@ void Steam_Client::RegisterCallback( class CCallbackBase *pCallback, int iCallba }; if (isGameServer) { - callbacks_server->addCallBack(iCallback, pCallback); + callbacks_server->addCallBack(iCallback, callback_to_add); } else { - callbacks_client->addCallBack(iCallback, pCallback); + callbacks_client->addCallBack(iCallback, callback_to_add); } } -void Steam_Client::UnregisterCallback( class CCallbackBase *pCallback) +void Steam_Client::UnregisterCallback( class CCallbackBase *pCallback ) { - int iCallback = pCallback->GetICallback(); + CCallbackBase *callback_to_rm = pCallback; + if (using_old_callbacks) { + if (!old_callbacks_map.count(pCallback)) + return; + + callback_to_rm = &(old_callbacks_map.at(pCallback)); + } + + int iCallback = callback_to_rm->GetICallback(); int base_callback = (iCallback / 100) * 100; int callback_id = iCallback % 100; - bool isGameServer = CCallbackMgr::isServer(pCallback); + bool isGameServer = CCallbackMgr::isServer(callback_to_rm); PRINT_DEBUG("isGameServer %u %i", isGameServer, base_callback); switch (base_callback) { @@ -923,13 +942,17 @@ void Steam_Client::UnregisterCallback( class CCallbackBase *pCallback) }; if (isGameServer) { - callbacks_server->rmCallBack(iCallback, pCallback); + callbacks_server->rmCallBack(iCallback, callback_to_rm); } else { - callbacks_client->rmCallBack(iCallback, pCallback); + callbacks_client->rmCallBack(iCallback, callback_to_rm); + } + + if (using_old_callbacks) { + old_callbacks_map.erase(pCallback); } } -void Steam_Client::RegisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall) +void Steam_Client::RegisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall ) { PRINT_DEBUG("%llu %i", hAPICall, pCallback->GetICallback()); std::lock_guard lock(global_mutex); @@ -938,7 +961,7 @@ void Steam_Client::RegisterCallResult( class CCallbackBase *pCallback, SteamAPIC } -void Steam_Client::UnregisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall) +void Steam_Client::UnregisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall ) { PRINT_DEBUG("%llu %i", hAPICall, pCallback->GetICallback()); std::lock_guard lock(global_mutex); From 71c5f690819a4247e9a62fa2fb7501ca8f663164 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 7 Mar 2026 04:26:34 +0300 Subject: [PATCH 040/159] Add checks for whether steam client is initialized --- dll/dll.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index 3329fe85a..6e5e0b6a2 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -211,15 +211,6 @@ static void load_old_steam_interfaces() reset_LastError(); } -//steam_api_internal.h -STEAMAPI_API HSteamUser SteamAPI_GetHSteamUser() -{ - PRINT_DEBUG_ENTRY(); - if (!get_steam_client()->IsUserLogIn()) return 0; - return CLIENT_HSTEAMUSER; -} - - // declare "g_pSteamClientGameServer" as an export for API library, then actually define it #if !defined(STEAMCLIENT_DLL) // api STEAMAPI_API ISteamClient *g_pSteamClientGameServer; @@ -688,6 +679,8 @@ STEAMAPI_API void Steam_RegisterInterfaceFuncs( void *hModule ) PRINT_DEBUG_TODO(); } +STEAMAPI_API HSteamUser SteamAPI_GetHSteamUser(); + // returns the HSteamUser of the last user to dispatch a callback STEAMAPI_API HSteamUser Steam_GetHSteamUserCurrent() { @@ -714,6 +707,14 @@ STEAMAPI_API const char *SteamAPI_GetSteamInstallPath() return steam_folder; } +//steam_api_internal.h +STEAMAPI_API HSteamUser SteamAPI_GetHSteamUser() +{ + PRINT_DEBUG_ENTRY(); + if (!steamclient_instance || !get_steam_client()->IsUserLogIn()) return 0; + return CLIENT_HSTEAMUSER; +} + // returns the pipe we are communicating to Steam with STEAMAPI_API HSteamPipe SteamAPI_GetHSteamPipe() { @@ -757,7 +758,7 @@ STEAMAPI_API ISteamClient *SteamClient() return (ISteamClient *)SteamInternal_CreateInterface(old_client); } -#define CACHE_OLDSTEAM_INSTANCE(variable, get_func) { if (variable) return variable; else return variable = (get_func); } +#define CACHE_OLDSTEAM_INSTANCE(variable, get_func) { if (!steamclient_instance) return nullptr; if (variable) return variable; else return variable = (get_func); } STEAMAPI_API ISteamUser *SteamUser() { @@ -960,6 +961,7 @@ STEAMAPI_API HSteamPipe S_CALLTYPE SteamGameServer_GetHSteamPipe() STEAMAPI_API HSteamUser S_CALLTYPE SteamGameServer_GetHSteamUser() { PRINT_DEBUG_ENTRY(); + if (!steamclient_instance) return 0; if (!get_steam_client()->IsServerInit()) return 0; return SERVER_HSTEAMUSER; } @@ -1197,12 +1199,14 @@ STEAMAPI_API void SteamGameServer_RunCallbacks() STEAMAPI_API steam_bool SteamGameServer_BSecure() { PRINT_DEBUG_ENTRY(); + if (!steamclient_instance) return false; return get_steam_client()->steam_gameserver->BSecure(); } STEAMAPI_API uint64 SteamGameServer_GetSteamID() { PRINT_DEBUG_ENTRY(); + if (!steamclient_instance) return 0; return get_steam_client()->steam_gameserver->GetSteamID().ConvertToUint64(); } @@ -1215,6 +1219,7 @@ STEAMAPI_API ISteamClient *SteamGameServerClient() STEAMAPI_API uint32 SteamGameServer_GetIPCCallCount() { + if (!steamclient_instance) return 0; return get_steam_client()->GetIPCCallCount(); } From abd03f5e34a55cb666fa0ca8b2b1b324aa6d7472 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 7 Mar 2026 04:28:33 +0300 Subject: [PATCH 041/159] Move callback code --- dll/dll.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index 6e5e0b6a2..9f8c4b8eb 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -1498,29 +1498,29 @@ SteamMasterServerUpdater */ -STEAMCLIENT_API steam_bool Steam_BGetCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg ) +bool steamclient_get_callback(HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg) { - PRINT_DEBUG("%i", hSteamPipe); SteamAPI_ManualDispatch_Init(); Steam_Client *steam_client = get_steam_client(); steam_client->RunCallbacks(true, true); - return SteamAPI_ManualDispatch_GetNextCallback( hSteamPipe, pCallbackMsg ); + return SteamAPI_ManualDispatch_GetNextCallback(hSteamPipe, pCallbackMsg); } -STEAMCLIENT_API void Steam_FreeLastCallback( HSteamPipe hSteamPipe ) +void steamclient_free_callback(HSteamPipe hSteamPipe) { - //PRINT_DEBUG("%i", hSteamPipe); - SteamAPI_ManualDispatch_FreeLastCallback( hSteamPipe ); + SteamAPI_ManualDispatch_FreeLastCallback(hSteamPipe); } -bool steamclient_get_callback(HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg) +STEAMCLIENT_API steam_bool Steam_BGetCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg ) { - return Steam_BGetCallback(hSteamPipe, pCallbackMsg); + PRINT_DEBUG("%i", hSteamPipe); + return steamclient_get_callback(hSteamPipe, pCallbackMsg); } -void steamclient_free_callback(HSteamPipe hSteamPipe) +STEAMCLIENT_API void Steam_FreeLastCallback( HSteamPipe hSteamPipe ) { - return Steam_FreeLastCallback(hSteamPipe); + //PRINT_DEBUG("%i", hSteamPipe); + steamclient_free_callback(hSteamPipe); } STEAMCLIENT_API steam_bool Steam_GetAPICallResult( HSteamPipe hSteamPipe, SteamAPICall_t hSteamAPICall, void* pCallback, int cubCallback, int iCallbackExpected, bool* pbFailed ) From de2eedb023a29fc938e5f22912747aa15210180c Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 7 Mar 2026 04:29:01 +0300 Subject: [PATCH 042/159] Fix logging --- dll/steam_gameserver_items.cpp | 2 +- dll/steam_user_items.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dll/steam_gameserver_items.cpp b/dll/steam_gameserver_items.cpp index d5c800907..f8c5a1e3f 100644 --- a/dll/steam_gameserver_items.cpp +++ b/dll/steam_gameserver_items.cpp @@ -412,7 +412,7 @@ void Steam_GameServer_Items::network_callback_inventory_pos_update(Common_Messag data.m_ulItemID = item_id; callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.15); - PRINT_DEBUG("server got updated item inventory position: %llu 0x%08x", item_id, item_inv_pos); + PRINT_DEBUG("server got updated item inventory position: %llu 0x%08X", item_id, item_inv_pos); return; } diff --git a/dll/steam_user_items.cpp b/dll/steam_user_items.cpp index 04b3d7d2c..2632f97a3 100644 --- a/dll/steam_user_items.cpp +++ b/dll/steam_user_items.cpp @@ -273,10 +273,10 @@ bool Steam_User_Items::GetItemAttribute( uint64 ulItemID, uint32 unAttributeInde SteamAPICall_t Steam_User_Items::UpdateInventoryPos( uint64 ulItemID, uint32 unNewInventoryPos ) { - PRINT_DEBUG("%llu 0x%08x", ulItemID, unNewInventoryPos); + PRINT_DEBUG("%llu 0x%08X", ulItemID, unNewInventoryPos); std::lock_guard lock(global_mutex); - for (auto &item : items) { + for (Econ_Item &item : items) { if (item.id != ulItemID) continue; @@ -430,7 +430,7 @@ void Steam_User_Items::network_callback_inventory_request(Common_Message *msg) new_msg.set_dest_id(server_steamid); network->sendTo(&new_msg, true); - PRINT_DEBUG("server requested inventory, sent %u items", items.size()); + PRINT_DEBUG("server requested inventory, sent %u items", static_cast(items.size())); } // only triggered when we have a message From 716b0b7459eab7d7ec6a8be586d86d83a3559a43 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 7 Mar 2026 04:29:39 +0300 Subject: [PATCH 043/159] Handle bLanMode parameter in Steam_GameServer::BSetServerType --- dll/steam_gameserver.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index 467393580..f1534ca2f 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -582,6 +582,13 @@ bool Steam_GameServer::BSetServerType( uint32 unServerFlags, uint32 unGameIP, ui flags = unServerFlags; + if (bLANMode) { + flags &= ~k_unServerFlagSecure; + flags |= k_unServerFlagPrivate; + } else { + flags &= ~k_unServerFlagPrivate; + } + if (!settings->disable_source_query && !network->isQueryAlive()) network->startQuery({ unGameIP, usQueryPort }); From e0a4dd8846ea970d0312ef778fb7082ea0657f81 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 7 Mar 2026 04:30:13 +0300 Subject: [PATCH 044/159] Give env vars higher priority when determining appid --- dll/settings_parser.cpp | 58 ++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 03a737d0f..870f5cc65 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -523,40 +523,12 @@ static uint32 parse_steam_app_id(const std::string &program_path) { uint32 appid = 0; - // try steam_settings folder - char array[10] = {}; - array[0] = '0'; - Local_Storage::get_file_data(Local_Storage::get_game_settings_path() + "steam_appid.txt", array, sizeof(array) - 1); - try { - appid = std::stoul(array); - } catch (...) {} - - // try current dir - if (!appid) { - memset(array, 0, sizeof(array)); - array[0] = '0'; - Local_Storage::get_file_data("steam_appid.txt", array, sizeof(array) - 1); - try { - appid = std::stoul(array); - } catch (...) {} - } - - // try exe dir - if (!appid) { - memset(array, 0, sizeof(array)); - array[0] = '0'; - Local_Storage::get_file_data(program_path + "steam_appid.txt", array, sizeof(array) - 1); - try { - appid = std::stoul(array); - } catch (...) {} - } - // try env vars if (!appid) { std::string str_appid = get_env_variable("SteamAppId"); std::string str_gameid = get_env_variable("SteamGameId"); std::string str_overlay_gameid = get_env_variable("SteamOverlayGameId"); - + PRINT_DEBUG("str_appid %s str_gameid: %s str_overlay_gameid: %s", str_appid.c_str(), str_gameid.c_str(), str_overlay_gameid.c_str()); uint32 appid_env = 0; uint32 gameid_env = 0; @@ -600,6 +572,34 @@ static uint32 parse_steam_app_id(const std::string &program_path) } } + // try steam_settings folder + char array[10] = {}; + array[0] = '0'; + Local_Storage::get_file_data(Local_Storage::get_game_settings_path() + "steam_appid.txt", array, sizeof(array) - 1); + try { + appid = std::stoul(array); + } catch (...) {} + + // try current dir + if (!appid) { + memset(array, 0, sizeof(array)); + array[0] = '0'; + Local_Storage::get_file_data("steam_appid.txt", array, sizeof(array) - 1); + try { + appid = std::stoul(array); + } catch (...) {} + } + + // try exe dir + if (!appid) { + memset(array, 0, sizeof(array)); + array[0] = '0'; + Local_Storage::get_file_data(program_path + "steam_appid.txt", array, sizeof(array) - 1); + try { + appid = std::stoul(array); + } catch (...) {} + } + PRINT_DEBUG("final appid = %u", appid); return appid; } From a0bcae748057fb92de1e75927b52c1a40fefae30 Mon Sep 17 00:00:00 2001 From: PaLaS0 <36226165+PaLaS0@users.noreply.github.com> Date: Sat, 7 Mar 2026 10:10:00 +0100 Subject: [PATCH 045/159] fix SetConnectionPollGroup removing connection from wrong poll group Spotted in Stardew Valley where players would get kicked immediately after joining via Steam invite because messages were still being received on the old JoiningGroup after the connection was promoted to FarmhandGroup --- dll/steam_networking_sockets.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dll/steam_networking_sockets.cpp b/dll/steam_networking_sockets.cpp index c2bf3504a..11911258a 100644 --- a/dll/steam_networking_sockets.cpp +++ b/dll/steam_networking_sockets.cpp @@ -1340,9 +1340,9 @@ bool Steam_Networking_Sockets::SetConnectionPollGroup( HSteamNetConnection hConn 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); + auto old_group = sbcs->poll_groups.find(old_poll_group); // bugfix: should be old_poll_group + if (old_group != sbcs->poll_groups.end()) { + old_group->second.remove(hConn); } } From d0bb0725ad7fb0a4ef7491f4bdc4a9d06836f905 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Sun, 8 Mar 2026 15:36:13 +0100 Subject: [PATCH 046/159] Fixes GetConnectionInfo001 not return valid data set_steamnetconnectioninfo not set real port CloseListenSocket not closing socket --- dll/dll/steam_networking_sockets.h | 2 ++ dll/steam_networking_sockets.cpp | 36 +++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/dll/dll/steam_networking_sockets.h b/dll/dll/steam_networking_sockets.h index 036376d33..900b8c378 100644 --- a/dll/dll/steam_networking_sockets.h +++ b/dll/dll/steam_networking_sockets.h @@ -115,6 +115,8 @@ public ISteamNetworkingSockets ESteamNetworkingConnectionState convert_status(enum connect_socket_status old_status); void set_steamnetconnectioninfo(std::map::iterator connect_socket, SteamNetConnectionInfo_t *pInfo); + void set_steamnetconnectioninfo_001(std::map::iterator connect_socket, SteamNetConnectionInfo001_t* pInfo); + void launch_callback(HSteamNetConnection m_hConn, enum connect_socket_status old_status); diff --git a/dll/steam_networking_sockets.cpp b/dll/steam_networking_sockets.cpp index 11911258a..85ffe86ba 100644 --- a/dll/steam_networking_sockets.cpp +++ b/dll/steam_networking_sockets.cpp @@ -187,7 +187,7 @@ void Steam_Networking_Sockets::set_steamnetconnectioninfo(std::mapm_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_addrRemote.SetIPv4(network->getIP(connect_socket->second.remote_identity.GetSteamID()), connect_socket->second.real_port); } pInfo->m_idPOPRemote = 0; @@ -201,6 +201,25 @@ void Steam_Networking_Sockets::set_steamnetconnectioninfo(std::map::iterator connect_socket, SteamNetConnectionInfo001_t* pInfo) +{ + pInfo->m_steamIDRemote = connect_socket->second.remote_identity.GetSteamID(); + pInfo->m_nUserData = connect_socket->second.user_data; + pInfo->m_hListenSocket = connect_socket->second.listen_socket_id; + if (connect_socket->second.real_port != SNS_DISABLED_PORT) { + pInfo->m_unIPRemote = network->getIP(connect_socket->second.remote_identity.GetSteamID()); + pInfo->m_unPortRemote = connect_socket->second.real_port; + } + + 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; + //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); @@ -576,7 +595,7 @@ bool Steam_Networking_Sockets::CloseListenSocket( HSteamListenSocket hSocket, co { PRINT_DEBUG("old"); std::lock_guard lock(global_mutex); - return false; + return CloseListenSocket(hSocket); } /// Destroy a listen socket. All the connections that were accepting on the listen @@ -892,7 +911,7 @@ int Steam_Networking_Sockets::ReceiveMessagesOnListenSocket( HSteamListenSocket /// Returns basic information about the high-level state of the connection. bool Steam_Networking_Sockets::GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo_t *pInfo ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%u %i", hConn, pInfo == NULL); std::lock_guard lock(global_mutex); if (!pInfo) return false; @@ -993,9 +1012,16 @@ int Steam_Networking_Sockets::ReceiveMessagesOnListenSocket( HSteamListenSocket /// Returns information about the specified connection. bool Steam_Networking_Sockets::GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo001_t *pInfo ) { - PRINT_DEBUG_TODO(); + PRINT_DEBUG("001 %u %i", hConn, pInfo == NULL); std::lock_guard lock(global_mutex); - return false; + + if (!pInfo) return false; + + auto connect_socket = sbcs->connect_sockets.find(hConn); + if (connect_socket == sbcs->connect_sockets.end()) return false; + + set_steamnetconnectioninfo_001(connect_socket, pInfo); + return true; } From a10ebbca6af847889f7e0f922146734bd19bf18b Mon Sep 17 00:00:00 2001 From: NotAndreh <39744335+NotAndreh@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:46:58 +0100 Subject: [PATCH 047/159] Implementation of Steam_AppTicket::GetAppOwnershipTicketData --- dll/appticket.cpp | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/dll/appticket.cpp b/dll/appticket.cpp index cccbbbb5b..4f8a760ce 100644 --- a/dll/appticket.cpp +++ b/dll/appticket.cpp @@ -259,8 +259,32 @@ Steam_AppTicket::Steam_AppTicket(class 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); + PRINT_DEBUG("%u, %p, %u, %p, %p, %p, %p", nAppID, pvBuffer, cbBufferLength, piAppId, piSteamId, piSignature, pcbSignature); std::lock_guard lock(global_mutex); + if (pvBuffer != nullptr && cbBufferLength >= 24) { + *reinterpret_cast(pvBuffer) = nAppID; + *reinterpret_cast(reinterpret_cast(pvBuffer) + 8) = settings->get_current_steam_id().ConvertToUint64(); + *reinterpret_cast(reinterpret_cast(pvBuffer) + 16) = 0; + + if (piAppId != nullptr) { + *piAppId = 0; + } + + if (piSteamId != nullptr) { + *piSteamId = 8; + } + + if (piSignature != nullptr) { + *piSignature = 16; + } + + if (pcbSignature != nullptr) { + *pcbSignature = 8; + } + + return 24; + } + return 0; } From 11e11657ee33c6a06fb14541e3ee619a41772db3 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Tue, 10 Mar 2026 04:27:29 +0300 Subject: [PATCH 048/159] Initial work on Game Coordinator --- dll/dll/econ_item.h | 53 + dll/dll/steam_game_coordinator.h | 97 +- dll/dll/steam_gameserver_items.h | 29 +- dll/dll/steam_user_items.h | 19 +- dll/net.proto | 15 +- dll/steam_client.cpp | 11 +- dll/steam_game_coordinator.cpp | 1003 ++++++++++++++++- dll/steam_gameserver.cpp | 14 +- dll/steam_gameserver_items.cpp | 297 +---- dll/steam_user_items.cpp | 245 +--- .../steam_settings.EXAMPLE/gc.EXAMPLE.json | 3 + 11 files changed, 1203 insertions(+), 583 deletions(-) create mode 100644 post_build/steam_settings.EXAMPLE/gc.EXAMPLE.json diff --git a/dll/dll/econ_item.h b/dll/dll/econ_item.h index 4970848d6..14ba72227 100644 --- a/dll/dll/econ_item.h +++ b/dll/dll/econ_item.h @@ -18,6 +18,37 @@ #ifndef __INCLUDED_ECON_ITEM_H__ #define __INCLUDED_ECON_ITEM_H__ +#include "base.h" + +//=============================================================================================================== +// POSITION HANDLING +//=============================================================================================================== +// TF Inventory Position cracking + +// REALLY OLD FORMAT (??): +// Bits 17-32 are the bag index (class index + 999, 1 is unequipped). +// Bits 1-16 are the position of the item within the bag. +// 0 means item hasn't been acknowledged by the player yet. +// +// LESS OLD FORMAT (up through July, 2011): +// If Bit 31 is 0: +// Bits 1-16 are the backpack position. +// Bits 17-26 are a bool for whether the item is equipped in the matching class. +// Otherwise, if Bit 31 is 1: +// Item hasn't been acknowledged by the player yet. +// Bits 1-16 are the method by the player found the item (see unacknowledged_item_inventory_positions_t) +// Bit 32 is 1, to note the new format. +// +// CURRENT FORMAT: +// If Bit 31 is 0: +// Bits 1-16 are the backpack position. +// Otherwise, if Bit 31 is 1: +// Item hasn't been acknowledged by the player yet. +// Bits 1-16 are the method by the player found the item (see unacknowledged_item_inventory_positions_t) +// Equipped state is stored elsewhere. +// This is the only format that should exist on clients. +// Note (1/15/2013) For backwards compatibility, if the value is 0 item is considered unacknowledged too + struct Econ_Item_Attribute { uint32 def; @@ -32,7 +63,29 @@ struct Econ_Item EItemQuality quality; uint32 inv_pos; uint32 quantity; + uint8 flags; + uint8 origin; + std::string custom_name; + std::string custom_desc; + bool in_use; + uint64 original_id; std::vector attributes; }; +inline bool check_econ_item_name(const std::string &name) +{ + if (name.size() > 40) + return false; + + return true; +} + +inline bool check_econ_item_desc(const std::string &desc) +{ + if (desc.size() > 80) + return false; + + return true; +} + #endif diff --git a/dll/dll/steam_game_coordinator.h b/dll/dll/steam_game_coordinator.h index 376343d22..5c52fdb79 100644 --- a/dll/dll/steam_game_coordinator.h +++ b/dll/dll/steam_game_coordinator.h @@ -19,32 +19,109 @@ #define __INCLUDED_STEAM_GAME_COORDINATOR_H__ #include "base.h" +#include "econ_item.h" + +class Steam_User_Items; +class Steam_GameServer_Items; +struct GCMsgHdr_t; +struct GCMsgHdrEx_t; +struct ProtoBufMsgHeader_t; class Steam_Game_Coordinator : public ISteamGameCoordinator { + static constexpr const auto items_user_file = "items.json"; + static constexpr const auto gc_config_file = "gc.json"; constexpr const static uint32 protobuf_mask = 0x80000000; class Settings *settings{}; class Networking *network{}; - class SteamCallResults *callback_results{}; + class Local_Storage *local_storage{}; class SteamCallBacks *callbacks{}; class RunEveryRunCB *run_every_runcb{}; + bool is_server{}; + + struct GC_Message + { + uint32 msg_type{}; + std::string msg_body; + std::chrono::high_resolution_clock::time_point created{}; + double post_in{}; + }; + + std::vector pending_messages; + std::queue incoming_messages; + + int gc_version{}; + bool gc_initialized{}; + + std::vector items; + bool items_loaded{}; + + struct RequestInventory + { + std::chrono::high_resolution_clock::time_point created{}; + CSteamID steam_id; + SteamAPICall_t steam_api_call{}; + bool is_gc{}; + }; + + std::map> all_user_items; + std::vector pending_items_requests; + + bool gc_enabled(); + Steam_User_Items *client_items(); + Steam_GameServer_Items *server_items(); + void parse_gc_config(); + void push_incoming(uint32 msg_type, const std::string &message, double delay = 0.1); + + std::string build_msg_header(JobID_t target_job = k_GIDNil, JobID_t source_job = k_GIDNil); + GCMsgHdrEx_t parse_msg_header(const char *&p); + uint64 item_id_local_to_network(uint64 item_id); + uint64 item_id_network_to_local(uint64 item_id); + + void handle_set_item_pos(const void *input, uint32 input_size); + void handle_delete_item(const void *input, uint32 input_size); + void handle_respawn(const void *input, uint32 input_size); + void handle_motd_request(const void *input, uint32 input_size); + + void network_callback_inventory_request(Common_Message *msg); + void network_callback_inventory_response(Common_Message *msg); + void network_callback_inventory_pos_update(Common_Message *msg); + void network_callback_item_deletion(Common_Message *msg); + void network_callback_respawn_request(Common_Message *msg); + void network_callback(Common_Message *msg); + void RunCallbacks(); - std::queue outgoing_messages{}; - - void push_incoming(std::string message); - - static void steam_callback(void *object, Common_Message *msg); + static void steam_network_callback(void *object, Common_Message *msg); static void steam_run_every_runcb(void *object); - void RunCallbacks(); - void Callback(Common_Message *msg); - public: - Steam_Game_Coordinator(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + Steam_Game_Coordinator(class Settings *settings, class Networking *network, class Local_Storage *local_storage, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, bool is_server); ~Steam_Game_Coordinator(); + void initialize_gc(); + + const std::vector &get_items() { return items; } + const std::map> &get_all_user_items() { return all_user_items; } + const bool has_items_for_user(CSteamID steam_id) { return (all_user_items.count(steam_id) != 0); } + const std::vector &get_items_for_user(CSteamID steam_id) { return all_user_items.at(steam_id); } + + const std::vector &load_items_from_file(); + void save_items_to_file(); + const Econ_Item *set_item_pos(uint64 item_id, uint32 inv_pos, bool is_gc); + bool delete_item(uint64 item_id, bool is_gc); + + void request_user_items(CSteamID steam_id, SteamAPICall_t api_call, bool is_gc); + SteamAPICall_t find_items_request(CSteamID steam_id); + void remove_user_items(CSteamID steam_id); + + void on_client_connected(CSteamID steam_id); + void on_client_disconnected(CSteamID steam_id); + void on_items_received(CSteamID steam_id, const std::vector &items); + void on_item_pos_updated(CSteamID steam_id, const Econ_Item &item); + void on_item_deleted(CSteamID steam_id, uint64 item_id); + // sends a message to the Game Coordinator EGCResults SendMessage_( uint32 unMsgType, const void *pubData, uint32 cubData ); diff --git a/dll/dll/steam_gameserver_items.h b/dll/dll/steam_gameserver_items.h index e8368e8b6..43868d02e 100644 --- a/dll/dll/steam_gameserver_items.h +++ b/dll/dll/steam_gameserver_items.h @@ -21,6 +21,8 @@ #include "base.h" #include "econ_item.h" +class Steam_Game_Coordinator; + class Steam_GameServer_Items : public ISteamGameServerItems001, public ISteamGameServerItems002, @@ -28,34 +30,19 @@ public ISteamGameServerItems003, public ISteamGameServerItems { class Settings *settings{}; - class Networking *network{}; class SteamCallBacks *callbacks{}; class SteamCallResults *callback_results{}; - class RunEveryRunCB *run_every_runcb{}; - - struct RequestInventory - { - std::chrono::high_resolution_clock::time_point created{}; - CSteamID steam_id{}; - SteamAPICall_t steam_api_call; - }; - - std::map> all_user_items; - std::vector pending_items_requests{}; - void network_callback_inventory_response(Common_Message *msg); - void network_callback_inventory_pos_update(Common_Message *msg); - void network_callback_item_deletion(Common_Message *msg); - void network_callback(Common_Message *msg); - void run_callbacks(); - - static void steam_gameserver_items_network_callback(void *object, Common_Message *msg); - static void steam_gameserver_items_run_every_runcb(void *object); + Steam_Game_Coordinator *gc(); public: - Steam_GameServer_Items(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks, class SteamCallResults *callback_results, class RunEveryRunCB *run_every_runcb); + Steam_GameServer_Items(class Settings *settings, class SteamCallBacks *callbacks, class SteamCallResults *callback_results); ~Steam_GameServer_Items(); + void on_items_received(CSteamID steam_id, size_t num_items, SteamAPICall_t api_call, bool success); + void on_item_pos_updated(CSteamID steam_id, uint64 item_id, uint32 inv_pos); + void on_item_deleted(CSteamID steam_id, uint64 item_id); + SteamAPICall_t LoadItems( CSteamID ownerID ); void LoadItems_old( CSteamID ownerID ); SteamAPICall_t GetItemCount( CSteamID ownerID ); diff --git a/dll/dll/steam_user_items.h b/dll/dll/steam_user_items.h index a6e4d2fb5..80e131ab9 100644 --- a/dll/dll/steam_user_items.h +++ b/dll/dll/steam_user_items.h @@ -21,33 +21,22 @@ #include "base.h" #include "econ_item.h" +class Steam_Game_Coordinator; + class Steam_User_Items : public ISteamUserItems001, public ISteamUserItems002, public ISteamUserItems003, public ISteamUserItems { - static constexpr const auto items_user_file = "items.json"; - class Settings *settings{}; - class Networking *network{}; - class Local_Storage *local_storage{}; class SteamCallBacks *callbacks{}; class SteamCallResults *callback_results{}; - std::vector items; - bool items_loaded{}; - - void network_callback_inventory_request(Common_Message *msg); - void network_callback(Common_Message *msg); - - static void steam_user_items_network_callback(void *object, Common_Message *msg); - - void load_items_from_file(); - void save_items_to_file(); + Steam_Game_Coordinator *gc(); public: - Steam_User_Items(class Settings *settings, class Networking *network, class Local_Storage *local_storage, class SteamCallBacks *callbacks, class SteamCallResults *callback_results); + Steam_User_Items(class Settings *settings, class SteamCallBacks *callbacks, class SteamCallResults *callback_results); ~Steam_User_Items(); SteamAPICall_t LoadItems(); diff --git a/dll/net.proto b/dll/net.proto index 4a1d7959a..7e98a7369 100644 --- a/dll/net.proto +++ b/dll/net.proto @@ -337,6 +337,11 @@ message GameServer_Items_Messages { uint32 inv_pos = 5; uint32 quantity = 6; repeated Attribute attributes = 7; + uint32 flags = 8; + uint32 origin = 9; + string custom_name = 10; + string custom_desc = 11; + uint64 original_id = 12; } message InventoryRequest { uint64 steam_api_call = 1; @@ -358,14 +363,16 @@ message GameServer_Items_Messages { Response_Inventory = 1; Request_UpdateInventoryPos = 2; Request_DeleteItem = 3; + Request_Respawn = 4; } Types type = 1; + bool is_gc = 2; oneof data_messages { - InventoryRequest inventory_request = 2; - InventoryResponse inventory_response = 3; - InventoryPosUpdate inventory_pos_update = 4; - ItemDeletion item_deletion = 5; + InventoryRequest inventory_request = 3; + InventoryResponse inventory_response = 4; + InventoryPosUpdate inventory_pos_update = 5; + ItemDeletion item_deletion = 6; } } diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index 84524b864..4263c4db2 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -126,7 +126,7 @@ Steam_Client::Steam_Client() steam_networking_sockets = new Steam_Networking_Sockets(settings_client, network, callback_results_client, callbacks_client, run_every_runcb, NULL); steam_networking_sockets_serialized = new Steam_Networking_Sockets_Serialized(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_networking_messages = new Steam_Networking_Messages(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); - steam_game_coordinator = new Steam_Game_Coordinator(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); + steam_game_coordinator = new Steam_Game_Coordinator(settings_client, network, local_storage, callbacks_client, run_every_runcb, false); steam_networking_utils = new Steam_Networking_Utils(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_unified_messages = new Steam_Unified_Messages(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_game_search = new Steam_Game_Search(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); @@ -137,7 +137,7 @@ Steam_Client::Steam_Client() steam_timeline = new Steam_Timeline(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_app_disable_update = new Steam_App_Disable_Update(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_billing = new Steam_Billing(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); - steam_user_items = new Steam_User_Items(settings_client, network, local_storage, callbacks_client, callback_results_client); + steam_user_items = new Steam_User_Items(settings_client, callbacks_client, callback_results_client); // server PRINT_DEBUG("init gameserver"); @@ -154,10 +154,10 @@ Steam_Client::Steam_Client() steam_gameserver_networking_sockets = new Steam_Networking_Sockets(settings_server, network, callback_results_server, callbacks_server, run_every_runcb, steam_networking_sockets->get_shared_between_client_server()); steam_gameserver_networking_sockets_serialized = new Steam_Networking_Sockets_Serialized(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); steam_gameserver_networking_messages = new Steam_Networking_Messages(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); - steam_gameserver_game_coordinator = new Steam_Game_Coordinator(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); + steam_gameserver_game_coordinator = new Steam_Game_Coordinator(settings_server, network, local_storage, callbacks_server, run_every_runcb, true); steam_masterserver_updater = new Steam_Masterserver_Updater(settings_server, network, callback_results_server, callbacks_server, run_every_runcb, steam_gameserver); steam_gameserver_gamestats = new Steam_GameStats(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); - steam_gameserver_items = new Steam_GameServer_Items(settings_server, network, callbacks_server, callback_results_server, run_every_runcb); + steam_gameserver_items = new Steam_GameServer_Items(settings_server, callbacks_server, callback_results_server); PRINT_DEBUG("init AppTicket"); steam_app_ticket = new Steam_AppTicket(settings_client); @@ -347,6 +347,9 @@ HSteamUser Steam_Client::ConnectToGlobalUser( HSteamPipe hSteamPipe ) } userLogIn(); + + // initialize GC now so that we have user's inventory ready right away + steam_game_coordinator->initialize_gc(); // games like appid 1740720 and 2379780 do not call SteamAPI_RunCallbacks() or SteamAPI_ManualDispatch_RunFrame() or Steam_BGetCallback() // hence all run_callbacks() will never run, which might break the assumption that these callbacks are always run diff --git a/dll/steam_game_coordinator.cpp b/dll/steam_game_coordinator.cpp index 4a9baca32..f69116dcf 100644 --- a/dll/steam_game_coordinator.cpp +++ b/dll/steam_game_coordinator.cpp @@ -16,22 +16,235 @@ . */ #include "dll/steam_game_coordinator.h" +#include "dll/dll.h" -void Steam_Game_Coordinator::push_incoming(std::string message) +constexpr int GC_MIN_VERSION = 20091217; +constexpr int GC_MAX_VERSION = 20110413; + +#pragma pack( push, 1 ) +//----------------------------------------------------------------------------- +// Purpose: Header for messages from a client or gameserver to or from the GC +//----------------------------------------------------------------------------- +struct GCMsgHdr_t +{ + uint32 m_eMsg; // The message type + uint64 m_ulSteamID; // User's SteamID +}; + +//----------------------------------------------------------------------------- +// Purpose: Header for messages from a client or gameserver to or from the GC +// That contains source and destination jobs for the purpose of +// replying messages. +//----------------------------------------------------------------------------- +struct GCMsgHdrEx_t +{ + uint32 m_eMsg; // The message type + uint64 m_ulSteamID; // User's SteamID + uint16 m_nHdrVersion; + JobID_t m_JobIDTarget; + JobID_t m_JobIDSource; +}; + +struct ProtoBufMsgHeader_t { - outgoing_messages.push(message); + uint32 m_EMsgFlagged; // High bit should be set to indicate this message header type is in use. The rest of the bits indicate message type. + uint32 m_cubProtoBufExtHdr; // Size of the extended header which is a serialized protobuf object. Indicates where it ends and the serialized body protobuf begins. +}; +#pragma pack(pop) - struct GCMessageAvailable_t data{}; - data.m_nMessageSize = static_cast(message.size()); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); +template +static void ser_var(std::string &buf, const T &input) +{ + buf.append(reinterpret_cast(&input), sizeof(T)); } -void Steam_Game_Coordinator::steam_callback(void *object, Common_Message *msg) +static void ser_varstring(std::string &buf, const std::string &input) { - // PRINT_DEBUG_ENTRY(); + uint16 len = static_cast(input.size()); + if (len != 0) { + ser_var(buf, len + 1); + buf.append(input); + buf.push_back('\0'); + } else { + ser_var(buf, 0); + } +} - Steam_Game_Coordinator *steam_gamecoordinator = (Steam_Game_Coordinator *)object; - steam_gamecoordinator->Callback(msg); +template +static T deser_var(const char *&p) { + T output; + memcpy(&output, p, sizeof(T)); + p += sizeof(T); + return output; +} + +bool Steam_Game_Coordinator::gc_enabled() +{ + return (gc_version >= GC_MIN_VERSION && gc_version <= GC_MAX_VERSION); +} + +Steam_User_Items *Steam_Game_Coordinator::client_items() +{ + return get_steam_client()->steam_user_items; +} + +Steam_GameServer_Items *Steam_Game_Coordinator::server_items() +{ + return get_steam_client()->steam_gameserver_items;; +} + +void Steam_Game_Coordinator::parse_gc_config() +{ + std::string file_path = Local_Storage::get_game_settings_path() + gc_config_file; + nlohmann::json gc_json; + if (!local_storage->load_json(file_path, gc_json)) + return; + + try { + gc_version = gc_json.value("gc_version", 0); + } catch (std::exception &e) { + const char *errorMessage = e.what(); + PRINT_DEBUG("error parsing GC config: %s", errorMessage); + gc_version = 0; + } +} + +void Steam_Game_Coordinator::push_incoming(uint32 msg_type, const std::string &message, double delay) +{ + PRINT_DEBUG("%u %.2f", msg_type, delay); + + GC_Message new_item; + new_item.msg_type = msg_type; + new_item.msg_body = message; + new_item.created = std::chrono::high_resolution_clock::now(); + new_item.post_in = delay; + pending_messages.push_back(new_item); +} + +std::string Steam_Game_Coordinator::build_msg_header(JobID_t target_job, JobID_t source_job) +{ + std::string message; + GCMsgHdrEx_t hdr{}; + hdr.m_nHdrVersion = 1; + hdr.m_JobIDTarget = target_job; + hdr.m_JobIDSource = source_job; + ser_var(message, hdr); + return message.substr(sizeof(GCMsgHdr_t)); +} + +GCMsgHdrEx_t Steam_Game_Coordinator::parse_msg_header(const char *&p) +{ + size_t write_offset = sizeof(GCMsgHdr_t); + size_t hdr_size = sizeof(GCMsgHdrEx_t) - write_offset; + GCMsgHdrEx_t hdr{}; + memcpy(reinterpret_cast(&hdr) + write_offset, p, hdr_size); + p += hdr_size; + return hdr; +} + +uint64 Steam_Game_Coordinator::item_id_local_to_network(uint64 item_id) +{ + if (item_id == 0) + return 0; + + // Add SteamID to item ID to avoid ID collisions in multiplayer games. + uint32 account_id = settings->get_local_steam_id().GetAccountID(); + + if (settings->use_32bit_inventory_item_ids) { + // 32-bit mode + item_id <<= 20ull; + item_id |= static_cast(account_id) & 0x000FFFFFull; + } else { + // 64-bit mode + item_id <<= 32ull; + item_id |= static_cast(account_id); + } + + return item_id; +} + +uint64 Steam_Game_Coordinator::item_id_network_to_local(uint64 item_id) +{ + if (settings->use_32bit_inventory_item_ids) { + // 32-bit mode + item_id >>= 20ull; + } else { + // 64-bit mode + item_id >>= 32ull; + } + + return item_id; +} + +void Steam_Game_Coordinator::handle_set_item_pos(const void *input, uint32 input_size) +{ + if (is_server || input_size < 30) + return; + + const char *p = reinterpret_cast(input); + GCMsgHdrEx_t hdr = parse_msg_header(p); + uint64 item_id = deser_var(p); + uint32 inv_pos = deser_var(p); + + if (const Econ_Item *item = set_item_pos(item_id, inv_pos, true)) { + on_item_pos_updated(settings->get_local_steam_id(), *item); + } +} + +void Steam_Game_Coordinator::handle_delete_item(const void *input, uint32 input_size) +{ + if (is_server || input_size < 26) + return; + + const char *p = reinterpret_cast(input); + GCMsgHdrEx_t hdr = parse_msg_header(p); + uint64 item_id = deser_var(p); + + if (delete_item(item_id, true)) { + on_item_deleted(settings->get_local_steam_id(), item_id); + } +} + +void Steam_Game_Coordinator::handle_respawn(const void *input, uint32 input_size) +{ + if (is_server || input_size < 19) + return; + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_Respawn); + gameserver_items_msg->set_is_gc(true); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + network->sendToAllGameservers(&msg, true); +} + +void Steam_Game_Coordinator::handle_motd_request(const void *input, uint32 input_size) +{ + if (is_server || input_size < 24) + return; + + const char *p = reinterpret_cast(input); + GCMsgHdrEx_t hdr = parse_msg_header(p); + uint32 last_req_time = deser_var(p); + uint16 language = deser_var(p); + + // k_EMsgGCMOTDRequestResponse + uint32 msg_type = 1013; + std::string message = build_msg_header(); + uint16 num_entries = 0; + ser_var(message, num_entries); + + push_incoming(msg_type, message); +} + +void Steam_Game_Coordinator::steam_network_callback(void *object, Common_Message *msg) +{ + //PRINT_DEBUG_ENTRY(); + + auto inst = (Steam_Game_Coordinator *)object; + inst->network_callback(msg); } void Steam_Game_Coordinator::steam_run_every_runcb(void *object) @@ -42,41 +255,433 @@ void Steam_Game_Coordinator::steam_run_every_runcb(void *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) +Steam_Game_Coordinator::Steam_Game_Coordinator(class Settings *settings, class Networking *network, class Local_Storage *local_storage, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, bool is_server) { this->settings = settings; this->network = network; - this->callback_results = callback_results; + this->local_storage = local_storage; 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->is_server = is_server; + + this->network->setCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_Game_Coordinator::steam_network_callback, this); + this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Game_Coordinator::steam_network_callback, this); this->run_every_runcb->add(&Steam_Game_Coordinator::steam_run_every_runcb, this); + + parse_gc_config(); } 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->network->rmCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_Game_Coordinator::steam_network_callback, this); + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Game_Coordinator::steam_network_callback, this); this->run_every_runcb->remove(&Steam_Game_Coordinator::steam_run_every_runcb, this); } +void Steam_Game_Coordinator::initialize_gc() +{ + if (!gc_enabled() || gc_initialized) + return; + + gc_initialized = true; + + // Servers don't need anything. + if (is_server) + return; + + // Load user's items. + const auto &items = load_items_from_file(); + on_items_received(settings->get_local_steam_id(), items); + + // HACK: Put any messages from the initialization directly into the queue so that the initial + // IsMessageAvailable call reads it. + for (const GC_Message &msg : pending_messages) { + incoming_messages.push(msg); + + GCMessageAvailable_t data{}; + data.m_nMessageSize = static_cast(msg.msg_body.size()); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + pending_messages.clear(); +} + +const std::vector &Steam_Game_Coordinator::load_items_from_file() +{ + if (items_loaded) + return items; + + items_loaded = true; + + nlohmann::json items_json; + if (!local_storage->load_json_file("", items_user_file, items_json)) + return items; + + for (auto it = items_json.begin(); it != items_json.end(); it++) { + Econ_Item new_item{}; + try { + new_item.id = std::stoull(it.key()); + } catch (...) { + continue; + } + if (new_item.id == 0) + continue; + + try { + new_item.def = it->value("definition", 0u); // 0 is a valid item definition + new_item.level = it->value("level", 1u); + new_item.quality = static_cast(it->value("quality", 0)); + new_item.inv_pos = it->value("inventory_pos", 0u); + new_item.quantity = it->value("quantity", 1u); + new_item.flags = it->value("flags", 0u); + new_item.origin = it->value("origin", 0u); + new_item.custom_name = it->value("custom_name", std::string()); + new_item.custom_desc = it->value("custom_desc", std::string()); + new_item.original_id = it->value("original_id", 0ull); + new_item.in_use = false; + + if (it->contains("attributes")) { + for (const auto &attr : it->at("attributes")) { + Econ_Item_Attribute new_attr{}; + new_attr.def = attr.value("definition", 0u); + new_attr.value = attr.value("value", 0.0f); + if (new_attr.def == 0) // 0 is not a valid attribute definition, however + continue; + + new_item.attributes.push_back(new_attr); + } + } + } catch (std::exception &e) { + const char *errorMessage = e.what(); + PRINT_DEBUG("error parsing item %llu: %s", new_item.id, errorMessage); + continue; + } + + new_item.id = item_id_local_to_network(new_item.id); + new_item.original_id = item_id_local_to_network(new_item.original_id); + + // Check custom name and custom description limits. + if (!check_econ_item_name(new_item.custom_name)) { + new_item.custom_name.clear(); + } + + if (!check_econ_item_desc(new_item.custom_desc)) { + new_item.custom_desc.clear(); + } + + items.push_back(new_item); + } + + return items; +} + +void Steam_Game_Coordinator::save_items_to_file() +{ + nlohmann::json items_json; + + for (const Econ_Item &item : items) { + uint64 item_id = item_id_network_to_local(item.id); + + nlohmann::json json_item; + json_item["definition"] = item.def; + json_item["level"] = item.level; + json_item["quality"] = item.quality; + json_item["inventory_pos"] = item.inv_pos; + json_item["quantity"] = item.quantity; + json_item["flags"] = item.flags; + json_item["origin"] = item.origin; + json_item["custom_name"] = item.custom_name; + json_item["custom_desc"] = item.custom_desc; + json_item["original_id"] = item_id_network_to_local(item.original_id); + + for (const Econ_Item_Attribute &attr : item.attributes) { + nlohmann::json json_attr; + json_attr["definition"] = attr.def; + json_attr["value"] = attr.value; + json_item["attributes"].push_back(json_attr); + } + + items_json[std::to_string(item_id)] = json_item; + } + + local_storage->write_json_file("", items_user_file, items_json); +} + +const Econ_Item *Steam_Game_Coordinator::set_item_pos(uint64 item_id, uint32 inv_pos, bool is_gc) +{ + for (Econ_Item &item : items) { + if (item.id != item_id) + continue; + + item.inv_pos = inv_pos; + save_items_to_file(); + + // Let the others know, too. + auto inventory_msg = new GameServer_Items_Messages::InventoryPosUpdate(); + inventory_msg->set_item_id(item_id); + inventory_msg->set_item_inv_pos(inv_pos); + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_UpdateInventoryPos); + gameserver_items_msg->set_is_gc(is_gc); + gameserver_items_msg->set_allocated_inventory_pos_update(inventory_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + network->sendToAll(&msg, true); + + return &item; + } + + return nullptr; +} + +bool Steam_Game_Coordinator::delete_item(uint64 item_id, bool is_gc) +{ + for (auto it = items.begin(); it != items.end(); it++) { + if (it->id != item_id) + continue; + + items.erase(it); + save_items_to_file(); + + // Let the others know, too. + auto drop_msg = new GameServer_Items_Messages::ItemDeletion(); + drop_msg->set_item_id(item_id); + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_DeleteItem); + gameserver_items_msg->set_is_gc(is_gc); + gameserver_items_msg->set_allocated_item_deletion(drop_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + network->sendToAll(&msg, true); + + return true; + } + + return false; +} + +void Steam_Game_Coordinator::request_user_items(CSteamID steam_id, SteamAPICall_t api_call, bool is_gc) +{ + RequestInventory new_request{}; + new_request.created = std::chrono::high_resolution_clock::now(); + new_request.steam_id = steam_id; + new_request.steam_api_call = api_call; + new_request.is_gc = is_gc; + pending_items_requests.push_back(new_request); + + auto request_msg = new GameServer_Items_Messages::InventoryRequest(); + request_msg->set_steam_api_call(new_request.steam_api_call); + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_Inventory); + gameserver_items_msg->set_is_gc(is_gc); + gameserver_items_msg->set_allocated_inventory_request(request_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(steam_id.ConvertToUint64()); + network->sendTo(&msg, true); +} + +SteamAPICall_t Steam_Game_Coordinator::find_items_request(CSteamID steam_id) +{ + auto it = std::find_if( + pending_items_requests.begin(), pending_items_requests.end(), + [=](const RequestInventory &item) { + return item.steam_id == steam_id; + } + ); + + if (it == pending_items_requests.end()) + return k_uAPICallInvalid; + + return it->steam_api_call; +} + +void Steam_Game_Coordinator::remove_user_items(CSteamID steam_id) +{ + all_user_items.erase(steam_id); + + // Clean up any pending requests we have. + for (auto it = pending_items_requests.begin(); it != pending_items_requests.end();) { + if (it->steam_id == steam_id) { + it = pending_items_requests.erase(it); + } else { + it++; + } + } + + if (gc_initialized) { + // k_ESOMsg_CacheUnsubscribed + uint32 msg_type = 25; + std::string message = build_msg_header(); + ser_var(message, steam_id.ConvertToUint64()); + + push_incoming(msg_type, message); + } +} + +void Steam_Game_Coordinator::on_client_connected(CSteamID steam_id) +{ + if (!steam_id.BIndividualAccount()) + return; + + if (gc_initialized) { + request_user_items(steam_id, generate_steam_api_call_id(), true); + } +} + +void Steam_Game_Coordinator::on_client_disconnected(CSteamID steam_id) +{ + if (!steam_id.BIndividualAccount()) + return; + + remove_user_items(steam_id); +} + +void Steam_Game_Coordinator::on_items_received(CSteamID steam_id, const std::vector &items) +{ + if (!gc_initialized) + return; + + // k_ESOMsg_CacheSubscribed + uint32 msg_type = 24; + std::string message = build_msg_header(); + + uint64 owner_id = steam_id.ConvertToUint64(); + uint16 num_types = 1; + + ser_var(message, owner_id); + ser_var(message, num_types); + + // econ items (1) + uint32 object_type = 1; + uint16 num_items = static_cast(items.size()); + + ser_var(message, object_type); + ser_var(message, num_items); + + for (const Econ_Item &item : items) { + ser_var(message, item.id); + ser_var(message, steam_id.GetAccountID()); + ser_var(message, item.def); + ser_var(message, item.level); + ser_var(message, item.quality); + ser_var(message, item.inv_pos); + ser_var(message, item.quantity); + + if (gc_version >= 20100428) { + // Strings are passed as UTF-8 which is good for us since we can just copy std::string as is. + ser_varstring(message, item.custom_name); + + if (gc_version >= 20100930) { + ser_var(message, item.flags); + + if (gc_version >= 20101027) { + ser_var(message, item.origin); + ser_varstring(message, item.custom_desc); + ser_var(message, item.in_use); + } + } + } + + ser_var(message, static_cast(item.attributes.size())); + + for (const Econ_Item_Attribute &attr : item.attributes) { + ser_var(message, attr.def); + ser_var(message, attr.value); + } + + if (gc_version >= 20101217) { + ser_var(message, item.original_id); + } + } + + push_incoming(msg_type, message); +} + +void Steam_Game_Coordinator::on_item_pos_updated(CSteamID steam_id, const Econ_Item &item) +{ + if (!gc_initialized) + return; + + // k_ESOMsg_Update + uint32 msg_type = 22; + std::string message = build_msg_header(); + + uint64 owner_id = steam_id.ConvertToUint64(); + uint32 object_type = 1; + uint8 num_fields = 1; + + ser_var(message, owner_id); + ser_var(message, object_type); + ser_var(message, item.id); + ser_var(message, num_fields); + + uint8 field_idx = 5; + ser_var(message, field_idx); + ser_var(message, item.inv_pos); + + if (gc_version >= 20101027) { + ser_var(message, item.in_use); + } + + push_incoming(msg_type, message); +} + +void Steam_Game_Coordinator::on_item_deleted(CSteamID steam_id, uint64 item_id) +{ + if (!gc_initialized) + return; + + // k_ESOMsg_Destroy + uint32 msg_type = 23; + std::string message = build_msg_header(); + + uint64 owner_id = steam_id.ConvertToUint64(); + uint32 object_type = 1; + + ser_var(message, owner_id); + ser_var(message, object_type); + ser_var(message, item_id); + + push_incoming(msg_type, message); +} // 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); + PRINT_DEBUG("0x%08X %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); - } + + if (!gc_initialized) + return k_EGCResultOK; + + switch (unMsgType) { + case 1001: + PRINT_DEBUG("k_EMsgGCSetSingleItemPosition"); + handle_set_item_pos(pubData, cubData); + break; + case 1004: + PRINT_DEBUG("k_EMsgGCDelete"); + handle_delete_item(pubData, cubData); + break; + case 1012: + PRINT_DEBUG("k_EMsgGCMOTDRequest"); + handle_motd_request(pubData, cubData); + break; + case 1029: + PRINT_DEBUG("k_EMsgGCRespawnPostLoadoutChange"); + handle_respawn(pubData, cubData); + break; + default: + break; } return k_EGCResultOK; @@ -87,12 +692,15 @@ bool Steam_Game_Coordinator::IsMessageAvailable( uint32 *pcubMsgSize ) { PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - if (outgoing_messages.size()) { - if (pcubMsgSize) *pcubMsgSize = static_cast(outgoing_messages.front().size()); - return true; - } else { + + if (!gc_initialized || incoming_messages.empty()) { + *pcubMsgSize = 0; return false; } + + GC_Message &message = incoming_messages.front(); + *pcubMsgSize = static_cast(message.msg_body.size()); + return true; } // fills the provided buffer with the first message in the queue and returns k_EGCResultOK or @@ -103,44 +711,343 @@ EGCResults Steam_Game_Coordinator::RetrieveMessage( uint32 *punMsgType, void *pu { PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - if (outgoing_messages.size()) { - if (outgoing_messages.front().size() > cubDest) { - return k_EGCResultBufferTooSmall; + + if (!gc_initialized || incoming_messages.empty()) { + *pcubMsgSize = 0; + return k_EGCResultNoMessage; + } + + GC_Message &message = incoming_messages.front(); + + uint32 outsize = static_cast(message.msg_body.size()); + if (outsize > cubDest) { + return k_EGCResultBufferTooSmall; + } + + *punMsgType = message.msg_type; + *pcubMsgSize = outsize; + message.msg_body.copy(reinterpret_cast(pubDest), cubDest); + + incoming_messages.pop(); + return k_EGCResultOK; +} + +// server requested our inventory +void Steam_Game_Coordinator::network_callback_inventory_request(Common_Message *msg) +{ + // Server instance should never receive this. + if (is_server) + return; + + uint64 server_steamid = msg->source_id(); + + if (!msg->gameserver_items_messages().has_inventory_request()) { + PRINT_DEBUG("error empty msg"); + return; + } + + bool is_gc = msg->gameserver_items_messages().is_gc(); + const auto &request_msg = msg->gameserver_items_messages().inventory_request(); + auto response_msg = new GameServer_Items_Messages::InventoryResponse(); + response_msg->set_steam_api_call(request_msg.steam_api_call()); + + for (const Econ_Item &item : items) { + auto new_item = response_msg->add_items(); + new_item->set_id(item.id); + new_item->set_def(item.def); + new_item->set_level(item.level); + new_item->set_quality(static_cast(item.quality)); + new_item->set_inv_pos(item.inv_pos); + new_item->set_quantity(item.quantity); + new_item->set_flags(item.flags); + new_item->set_origin(item.origin); + new_item->set_custom_name(item.custom_name); + new_item->set_custom_desc(item.custom_desc); + new_item->set_original_id(item.original_id); + + for (const auto &attr : item.attributes) { + auto new_attr = new_item->add_attributes(); + new_attr->set_def(attr.def); + new_attr->set_value(attr.value); } + } + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Response_Inventory); + gameserver_items_msg->set_is_gc(is_gc); + gameserver_items_msg->set_allocated_inventory_response(response_msg); + + Common_Message new_msg{}; + new_msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + new_msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + new_msg.set_dest_id(server_steamid); + network->sendTo(&new_msg, true); - outgoing_messages.front().copy((char *)pubDest, cubDest); - if (pcubMsgSize) *pcubMsgSize = static_cast(outgoing_messages.front().size()); - if (punMsgType && outgoing_messages.front().size() >= sizeof(uint32)) { - outgoing_messages.front().copy((char *)punMsgType, sizeof(uint32)); - *punMsgType = ntohl(*punMsgType); + PRINT_DEBUG("server requested inventory, sent %u items", static_cast(items.size())); +} + +// user sent their inventory +void Steam_Game_Coordinator::network_callback_inventory_response(Common_Message *msg) +{ + uint64 user_steamid = msg->source_id(); + + PRINT_DEBUG("player sent their inventory %llu", user_steamid); + if (!msg->gameserver_items_messages().has_inventory_response()) { + PRINT_DEBUG("error empty msg"); + return; + } + + bool is_gc = msg->gameserver_items_messages().is_gc(); + const auto &response_msg = msg->gameserver_items_messages().inventory_response(); + SteamAPICall_t api_call = response_msg.steam_api_call(); + + // Find this pending request. + auto it = std::find_if( + pending_items_requests.begin(), pending_items_requests.end(), + [=](const RequestInventory &item) { + return item.steam_api_call == response_msg.steam_api_call() && + item.steam_id == user_steamid; } + ); + if (pending_items_requests.end() == it) { + PRINT_DEBUG("error got player inventory but pending request timedout/removed (doesn't exist)"); + return; + } + pending_items_requests.erase(it); - outgoing_messages.pop(); - return k_EGCResultOK; + auto &items = all_user_items[user_steamid]; + items.clear(); + + for (const auto &item : response_msg.items()) { + Econ_Item new_item; + new_item.id = item.id(); + new_item.def = item.def(); + new_item.level = item.level(); + new_item.quality = static_cast(item.quality()); + new_item.inv_pos = item.inv_pos(); + new_item.quantity = item.quantity(); + new_item.flags = item.flags(); + new_item.origin = item.origin(); + new_item.custom_name = item.custom_name(); + new_item.custom_desc = item.custom_desc(); + new_item.original_id = item.original_id(); + new_item.in_use = false; + if (new_item.id == 0) + continue; + + for (const auto &attr : item.attributes()) { + Econ_Item_Attribute new_attr; + new_attr.def = attr.def(); + new_attr.value = attr.value(); + if (new_attr.def == 0) + continue; + + new_item.attributes.push_back(new_attr); + } + + // Check custom name and custom description limits. + if (!check_econ_item_name(new_item.custom_name)) { + new_item.custom_name.clear(); + } + + if (!check_econ_item_desc(new_item.custom_desc)) { + new_item.custom_desc.clear(); + } + + items.push_back(new_item); + } + + if (is_gc) { + on_items_received(user_steamid, items); } else { - return k_EGCResultNoMessage; + server_items()->on_items_received(user_steamid, items.size(), api_call, true); } + + PRINT_DEBUG("got player inventory: %u items", response_msg.items_size()); } +// user updated item inventory position +void Steam_Game_Coordinator::network_callback_inventory_pos_update(Common_Message *msg) +{ + uint64 user_steamid = msg->source_id(); + PRINT_DEBUG("player updated item inventory position %llu", user_steamid); + if (!msg->gameserver_items_messages().has_inventory_pos_update()) { + PRINT_DEBUG("error empty msg"); + return; + } -void Steam_Game_Coordinator::RunCallbacks() + if (!all_user_items.count(user_steamid)) { + PRINT_DEBUG("error no inventory for player", user_steamid); + return; + } + + bool is_gc = msg->gameserver_items_messages().is_gc(); + const auto &inventory_msg = msg->gameserver_items_messages().inventory_pos_update(); + uint64 item_id = inventory_msg.item_id(); + uint32 item_inv_pos = inventory_msg.item_inv_pos(); + + auto &items = all_user_items.at(user_steamid); + + for (Econ_Item &item : items) { + if (item.id != item_id) + continue; + + item.inv_pos = item_inv_pos; + + if (is_gc) { + on_item_pos_updated(user_steamid, item); + } else { + server_items()->on_item_pos_updated(user_steamid, item_id, item_inv_pos); + } + + PRINT_DEBUG("got updated item inventory position: %llu 0x%08X", item_id, item_inv_pos); + return; + } + + PRINT_DEBUG("error item %llu not found", item_id); +} + +// user deleted an item +void Steam_Game_Coordinator::network_callback_item_deletion(Common_Message *msg) +{ + uint64 user_steamid = msg->source_id(); + + PRINT_DEBUG("player deleted inventory item %llu", user_steamid); + if (!msg->gameserver_items_messages().has_item_deletion()) { + PRINT_DEBUG("error empty msg"); + return; + } + + if (!all_user_items.count(user_steamid)) { + PRINT_DEBUG("error no inventory for player", user_steamid); + return; + } + + bool is_gc = msg->gameserver_items_messages().is_gc(); + const auto &drop_msg = msg->gameserver_items_messages().item_deletion(); + uint64 item_id = drop_msg.item_id(); + + auto &items = all_user_items.at(user_steamid); + + for (auto it = items.begin(); it != items.end(); it++) { + if (it->id != item_id) + continue; + + items.erase(it); + + if (is_gc) { + on_item_deleted(user_steamid, item_id); + } else { + server_items()->on_item_deleted(user_steamid, item_id); + } + + PRINT_DEBUG("deleted player's inventory item: %llu", item_id); + return; + } + + PRINT_DEBUG("error item %llu not found", item_id); +} + +// user wants to respawn after loadout change +void Steam_Game_Coordinator::network_callback_respawn_request(Common_Message *msg) { + if (!gc_initialized || !is_server) + return; + + uint64 user_steamid = msg->source_id(); + if (!all_user_items.count(user_steamid)) + return; + + // k_EMsgGCRespawnPostLoadoutChange + uint32 msg_type = 1029; + std::string message = build_msg_header(); + ser_var(message, user_steamid); + + push_incoming(msg_type, message); } -void Steam_Game_Coordinator::Callback(Common_Message *msg) +// only triggered when we have a message +void Steam_Game_Coordinator::network_callback(Common_Message *msg) { - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - + if (msg->source_id() == settings->get_local_steam_id().ConvertToUint64()) return; + + if (msg->has_gameserver_items_messages()) { + switch (msg->gameserver_items_messages().type()) { + // server requested our inventory + case GameServer_Items_Messages::Request_Inventory: + network_callback_inventory_request(msg); + break; + + // user sent their inventory + case GameServer_Items_Messages::Response_Inventory: + network_callback_inventory_response(msg); + break; + + // user updated item inventory position + case GameServer_Items_Messages::Request_UpdateInventoryPos: + network_callback_inventory_pos_update(msg); + break; + + // user deleted an item + case GameServer_Items_Messages::Request_DeleteItem: + network_callback_item_deletion(msg); + break; + + // user wants to respawn after loadout change + case GameServer_Items_Messages::Request_Respawn: + network_callback_respawn_request(msg); + break; + + default: + PRINT_DEBUG("unhandled type %i", (int)msg->gameserver_items_messages().type()); + break; } + } else if (msg->has_low_level()) { + if (!is_server && gc_initialized) { + uint64 user_steamid = msg->source_id(); - if (msg->low_level().type() == Low_Level::DISCONNECT) { + // Client needs to know other players' inventories as well since the game uses them to + // validate cosmetic items. + switch (msg->low_level().type()) { + case Low_Level::CONNECT: + request_user_items(user_steamid, generate_steam_api_call_id(), true); + break; + case Low_Level::DISCONNECT: + remove_user_items(user_steamid); + break; + } } } +} - if (msg->has_networking_sockets()) { +void Steam_Game_Coordinator::RunCallbacks() +{ + for (auto it = pending_messages.begin(); it != pending_messages.end();) { + if (check_timedout(it->created, it->post_in)) { + incoming_messages.push(*it); + GCMessageAvailable_t data{}; + data.m_nMessageSize = static_cast(it->msg_body.size()); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + it = pending_messages.erase(it); + } else { + it++; + } + } + + for (auto it = pending_items_requests.begin(); it != pending_items_requests.end();) { + if (check_timedout(it->created, 7.0)) { + if (!it->is_gc) { + server_items()->on_items_received(it->steam_id, items.size(), it->steam_api_call, false); + } + + PRINT_DEBUG("player inventory request timeout %llu", it->steam_id); + it = pending_items_requests.erase(it); + } else { + it++; + } } } diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index f1534ca2f..5700f0ade 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -70,6 +70,7 @@ bool Steam_GameServer::set_appid(int32 input_appid, const char *game_dir, bool a server_data.set_appid(appid); settings->set_game_id(CGameID(appid)); network->setAppID(appid); + get_steam_client()->steam_gameserver_game_coordinator->initialize_gc(); PRINT_DEBUG("set server appid to %u", appid); return true; @@ -109,6 +110,8 @@ void Steam_GameServer::add_player(CSteamID steamID) infos.second.score = 0; infos.second.name = "unnamed"; players.emplace_back(std::move(infos)); + + get_steam_client()->steam_gameserver_game_coordinator->on_client_connected(steamID); } void Steam_GameServer::remove_player(CSteamID steamID) @@ -119,6 +122,8 @@ void Steam_GameServer::remove_player(CSteamID steamID) if (player_it != players.end()) { players.erase(player_it); + + get_steam_client()->steam_gameserver_game_coordinator->on_client_disconnected(steamID); } } @@ -678,8 +683,13 @@ EBeginAuthSessionResult Steam_GameServer::BeginAuthSession( const void *pAuthTic PRINT_DEBUG("%i %llu", cbAuthTicket, steamID.ConvertToUint64()); std::lock_guard lock(global_mutex); - add_player(steamID); - return auth_manager->beginAuth(pAuthTicket, cbAuthTicket, steamID ); + EBeginAuthSessionResult res = auth_manager->beginAuth(pAuthTicket, cbAuthTicket, steamID ); + + if (res == k_EBeginAuthSessionResultOK) { + add_player(steamID); + } + + return res; } diff --git a/dll/steam_gameserver_items.cpp b/dll/steam_gameserver_items.cpp index f8c5a1e3f..6a989a4f6 100644 --- a/dll/steam_gameserver_items.cpp +++ b/dll/steam_gameserver_items.cpp @@ -16,41 +16,53 @@ . */ #include "dll/steam_gameserver_items.h" +#include "dll/dll.h" -void Steam_GameServer_Items::steam_gameserver_items_network_callback(void *object, Common_Message *msg) +Steam_Game_Coordinator *Steam_GameServer_Items::gc() { - //PRINT_DEBUG_ENTRY(); - - auto inst = (Steam_GameServer_Items *)object; - inst->network_callback(msg); + return get_steam_client()->steam_gameserver_game_coordinator; } -void Steam_GameServer_Items::steam_gameserver_items_run_every_runcb(void *object) +Steam_GameServer_Items::Steam_GameServer_Items(class Settings *settings, class SteamCallBacks *callbacks, class SteamCallResults *callback_results) { - //PRINT_DEBUG_ENTRY(); + this->settings = settings; + this->callbacks = callbacks; + this->callback_results = callback_results; +} - auto inst = (Steam_GameServer_Items *)object; - inst->run_callbacks(); +Steam_GameServer_Items::~Steam_GameServer_Items() +{ } -Steam_GameServer_Items::Steam_GameServer_Items(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks, class SteamCallResults *callback_results, class RunEveryRunCB *run_every_runcb) +void Steam_GameServer_Items::on_items_received(CSteamID steam_id, size_t num_items, SteamAPICall_t api_call, bool success) { - this->settings = settings; - this->network = network; - this->callbacks = callbacks; - this->callback_results = callback_results; - this->run_every_runcb = run_every_runcb; + GSItemCount_t data{}; + data.m_OwnerID = steam_id; + if (success) { + data.m_eResult = k_EItemRequestResultOK; + data.m_unCount = static_cast(num_items); + } else { + data.m_eResult = k_EItemRequestResultTimeout; + data.m_unCount = 0; + } + callback_results->addCallResult(api_call, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); +} - this->network->setCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_GameServer_Items::steam_gameserver_items_network_callback, this); - this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_GameServer_Items::steam_gameserver_items_network_callback, this); - this->run_every_runcb->add(Steam_GameServer_Items::steam_gameserver_items_run_every_runcb, this); +void Steam_GameServer_Items::on_item_pos_updated(CSteamID steam_id, uint64 item_id, uint32 inv_pos) +{ + GSItemInventoryPosUpdated_t data{}; + data.m_SteamID = steam_id; + data.m_ulItemID = item_id; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.15); } -Steam_GameServer_Items::~Steam_GameServer_Items() +void Steam_GameServer_Items::on_item_deleted(CSteamID steam_id, uint64 item_id) { - this->network->rmCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_GameServer_Items::steam_gameserver_items_network_callback, this); - this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_GameServer_Items::steam_gameserver_items_network_callback, this); - this->run_every_runcb->remove(Steam_GameServer_Items::steam_gameserver_items_run_every_runcb, this); + GSItemDeleted_t data{}; + data.m_SteamID = steam_id; + data.m_ulItemID = item_id; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.15); } SteamAPICall_t Steam_GameServer_Items::LoadItems( CSteamID ownerID ) @@ -58,9 +70,9 @@ SteamAPICall_t Steam_GameServer_Items::LoadItems( CSteamID ownerID ) PRINT_DEBUG("%llu", ownerID.ConvertToUint64()); std::lock_guard lock(global_mutex); - // see if we already have their inventory cached - if (all_user_items.count(ownerID)) { - const auto &items = all_user_items.at(ownerID); + // See if we already have their inventory cached. + if (gc()->has_items_for_user(ownerID)) { + const auto &items = gc()->get_items_for_user(ownerID); GSItemCount_t data{}; data.m_OwnerID = ownerID; @@ -71,27 +83,15 @@ SteamAPICall_t Steam_GameServer_Items::LoadItems( CSteamID ownerID ) return ret; } - // request inventory from this player - RequestInventory new_request{}; - new_request.created = std::chrono::high_resolution_clock::now(); - new_request.steam_id = ownerID; - new_request.steam_api_call = callback_results->reserveCallResult(); - pending_items_requests.push_back(new_request); - - auto request_msg = new GameServer_Items_Messages::InventoryRequest(); - request_msg->set_steam_api_call(new_request.steam_api_call); - - auto gameserver_items_msg = new GameServer_Items_Messages(); - gameserver_items_msg->set_type(GameServer_Items_Messages::Request_Inventory); - gameserver_items_msg->set_allocated_inventory_request(request_msg); - - Common_Message msg{}; - msg.set_allocated_gameserver_items_messages(gameserver_items_msg); - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_dest_id(ownerID.ConvertToUint64()); - network->sendTo(&msg, true); + // See if we've already requested their inventory. + SteamAPICall_t ret = gc()->find_items_request(ownerID); + if (ret != k_uAPICallInvalid) + return ret; - return new_request.steam_api_call; + // Request inventory from this player. + ret = callback_results->reserveCallResult(); + gc()->request_user_items(ownerID, ret, false); + return ret; } void Steam_GameServer_Items::LoadItems_old( CSteamID ownerID ) @@ -117,10 +117,10 @@ bool Steam_GameServer_Items::GetItemIterative( CSteamID ownerID, uint32 iIndex, PRINT_DEBUG("%u", iIndex); std::lock_guard lock(global_mutex); - if (!all_user_items.count(ownerID)) + if (!gc()->has_items_for_user(ownerID)) return false; - const auto &items = all_user_items.at(ownerID); + const auto &items = gc()->get_items_for_user(ownerID); if (iIndex >= items.size()) return false; @@ -148,7 +148,7 @@ bool Steam_GameServer_Items::GetItemByID( uint64 ulItemID, CSteamID *pOwnerID, u PRINT_DEBUG("%llu", ulItemID); std::lock_guard lock(global_mutex); - for (const auto &[steam_id, items] : all_user_items) { + for (const auto &[steam_id, items] : gc()->get_all_user_items()) { for (const Econ_Item &item : items) { if (item.id != ulItemID) continue; @@ -181,7 +181,7 @@ bool Steam_GameServer_Items::GetItemAttribute( uint64 ulItemID, uint32 unAttribu PRINT_DEBUG("%llu %u", ulItemID, unAttributeIndex); std::lock_guard lock(global_mutex); - for (const auto &[steamID, items] : all_user_items) { + for (const auto &[steamID, items] : gc()->get_all_user_items()) { for (const Econ_Item &item : items) { if (item.id != ulItemID) continue; @@ -312,202 +312,3 @@ SteamAPICall_t Steam_GameServer_Items::SetItemBlob( uint64 ulItemID, const void PRINT_DEBUG_TODO(); return k_uAPICallInvalid; } - -// user sent their inventory -void Steam_GameServer_Items::network_callback_inventory_response(Common_Message *msg) -{ - uint64 user_steamid = msg->source_id(); - - PRINT_DEBUG("player sent their inventory %llu", user_steamid); - if (!msg->gameserver_items_messages().has_inventory_response()) { - PRINT_DEBUG("error empty msg"); - return; - } - - const auto &response_msg = msg->gameserver_items_messages().inventory_response(); - - // find this pending request - auto it = std::find_if( - pending_items_requests.begin(), pending_items_requests.end(), - [=](const RequestInventory &item) { - return item.steam_api_call == response_msg.steam_api_call() && - item.steam_id == user_steamid; - } - ); - if (pending_items_requests.end() == it) { // timeout and already removed - PRINT_DEBUG("error got player inventory but pending request timedout/removed (doesn't exist)"); - return; - } - - auto &items = all_user_items[user_steamid]; - items.clear(); - - for (const auto &item : response_msg.items()) { - Econ_Item new_item; - new_item.id = item.id(); - new_item.def = item.def(); - new_item.level = item.level(); - new_item.quality = static_cast(item.quality()); - new_item.inv_pos = item.inv_pos(); - new_item.quantity = item.quantity(); - if (new_item.id == 0) - continue; - - for (const auto &attr : item.attributes()) { - Econ_Item_Attribute new_attr; - new_attr.def = attr.def(); - new_attr.value = attr.value(); - if (new_attr.def == 0) - continue; - - new_item.attributes.push_back(new_attr); - } - - items.push_back(new_item); - } - - GSItemCount_t data{}; - data.m_OwnerID = user_steamid; - data.m_eResult = k_EItemRequestResultOK; - data.m_unCount = static_cast(items.size()); - callback_results->addCallResult(it->steam_api_call, data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - - // remove this pending request - pending_items_requests.erase(it); - - PRINT_DEBUG("server got player inventory: %u items", response_msg.items_size()); -} - -// user updated item inventory position -void Steam_GameServer_Items::network_callback_inventory_pos_update(Common_Message *msg) -{ - uint64 user_steamid = msg->source_id(); - - PRINT_DEBUG("player updated item inventory position %llu", user_steamid); - if (!msg->gameserver_items_messages().has_inventory_pos_update()) { - PRINT_DEBUG("error empty msg"); - return; - } - - if (!all_user_items.count(user_steamid)) { - PRINT_DEBUG("error no inventory for player", user_steamid); - return; - } - - const auto &inventory_msg = msg->gameserver_items_messages().inventory_pos_update(); - uint64 item_id = inventory_msg.item_id(); - uint32 item_inv_pos = inventory_msg.item_inv_pos(); - - auto &items = all_user_items.at(user_steamid); - - for (Econ_Item &item : items) { - if (item.id != item_id) - continue; - - item.inv_pos = item_inv_pos; - - GSItemInventoryPosUpdated_t data{}; - data.m_SteamID = user_steamid; - data.m_ulItemID = item_id; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.15); - - PRINT_DEBUG("server got updated item inventory position: %llu 0x%08X", item_id, item_inv_pos); - return; - } - - PRINT_DEBUG("error item %llu not found", item_id); -} - -// user deleted an item -void Steam_GameServer_Items::network_callback_item_deletion(Common_Message *msg) -{ - uint64 user_steamid = msg->source_id(); - - PRINT_DEBUG("player deleted inventory item %llu", user_steamid); - if (!msg->gameserver_items_messages().has_item_deletion()) { - PRINT_DEBUG("error empty msg"); - return; - } - - if (!all_user_items.count(user_steamid)) { - PRINT_DEBUG("error no inventory for player", user_steamid); - return; - } - - const auto &drop_msg = msg->gameserver_items_messages().item_deletion(); - uint64 item_id = drop_msg.item_id(); - - auto &items = all_user_items.at(user_steamid); - - for (auto it = items.begin(); it != items.end(); it++) { - if (it->id != item_id) - continue; - - items.erase(it); - - GSItemDeleted_t data{}; - data.m_SteamID = user_steamid; - data.m_ulItemID = item_id; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.15); - - PRINT_DEBUG("server deleted inventory item: %llu", item_id); - return; - } - - PRINT_DEBUG("error item %llu not found", item_id); -} - -// only triggered when we have a message -void Steam_GameServer_Items::network_callback(Common_Message *msg) -{ - // this should never happen, but just in case - if (msg->source_id() == settings->get_local_steam_id().ConvertToUint64()) return; - - if (msg->has_gameserver_items_messages()) { - switch (msg->gameserver_items_messages().type()) { - // user sent their inventory - case GameServer_Items_Messages::Response_Inventory: - network_callback_inventory_response(msg); - break; - - // user updated item inventory position - case GameServer_Items_Messages::Request_UpdateInventoryPos: - network_callback_inventory_pos_update(msg); - break; - - // user deleted an item - case GameServer_Items_Messages::Request_DeleteItem: - network_callback_item_deletion(msg); - break; - - default: - PRINT_DEBUG("unhandled type %i", (int)msg->gameserver_items_messages().type()); - break; - } - } else if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::DISCONNECT) { - uint64 user_steamid = msg->source_id(); - all_user_items.erase(user_steamid); - } - } -} - -void Steam_GameServer_Items::run_callbacks() -{ - for (auto it = pending_items_requests.begin(); it != pending_items_requests.end();) { - if (check_timedout(it->created, 7.0)) { - GSItemCount_t data{}; - data.m_OwnerID = it->steam_id; - data.m_eResult = k_EItemRequestResultTimeout; - data.m_unCount = 0; - callback_results->addCallResult(it->steam_api_call, data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - - PRINT_DEBUG("player inventory request timeout %llu", it->steam_id); - it = pending_items_requests.erase(it); - } else { - it++; - } - } -} diff --git a/dll/steam_user_items.cpp b/dll/steam_user_items.cpp index 2632f97a3..64484513c 100644 --- a/dll/steam_user_items.cpp +++ b/dll/steam_user_items.cpp @@ -16,116 +16,22 @@ . */ #include "dll/steam_user_items.h" +#include "dll/dll.h" -void Steam_User_Items::steam_user_items_network_callback(void *object, Common_Message *msg) +Steam_Game_Coordinator *Steam_User_Items::gc() { - //PRINT_DEBUG_ENTRY(); - - auto inst = (Steam_User_Items *)object; - inst->network_callback(msg); -} - -void Steam_User_Items::load_items_from_file() -{ - items.clear(); - - nlohmann::json items_json; - if (!local_storage->load_json_file("", items_user_file, items_json)) - return; - - for (auto it = items_json.begin(); it != items_json.end(); it++) { - Econ_Item new_item{}; - try { - new_item.id = std::stoull(it.key()); - } catch (...) { - continue; - } - if (new_item.id == 0) - continue; - - new_item.def = it->value("definition", 0u); // 0 is a valid item definition - new_item.level = it->value("level", 1u); - new_item.quality = static_cast(it->value("quality", 0)); - new_item.inv_pos = it->value("inventory_pos", 0u); - new_item.quantity = it->value("quantity", 0u); // TODO: not sure what this is for, TF2 does not seem to use it - - if (it->contains("attributes")) { - for (const auto &attr : it->at("attributes")) { - Econ_Item_Attribute new_attr; - new_attr.def = attr.value("definition", 0u); - new_attr.value = attr.value("value", 0.0f); - if (new_attr.def == 0) // 0 is not a valid attribute definition, however - continue; - - new_item.attributes.push_back(new_attr); - } - } - - // add SteamID to item ID to avoid ID collisions in multiplayer games - uint32 account_id = settings->get_local_steam_id().GetAccountID(); - - if (settings->use_32bit_inventory_item_ids) { - // 32-bit mode - new_item.id <<= 20ull; - new_item.id |= static_cast(account_id) & 0x000FFFFFull; - } else { - // 64-bit mode - new_item.id <<= 32ull; - new_item.id |= static_cast(account_id); - } - - items.push_back(new_item); - } + return get_steam_client()->steam_game_coordinator; } -void Steam_User_Items::save_items_to_file() -{ - nlohmann::json items_json; - - for (const Econ_Item &item : items) { - uint64 item_id = 0; - if (settings->use_32bit_inventory_item_ids) { - // 32-bit mode - item_id = item.id >> 20ull; - } else { - // 64-bit mode - item_id = item.id >> 32ull; - } - - nlohmann::json json_item; - json_item["definition"] = item.def; - json_item["level"] = item.level; - json_item["quality"] = item.quality; - json_item["inventory_pos"] = item.inv_pos; - json_item["quantity"] = item.quantity; - - for (const Econ_Item_Attribute &attr : item.attributes) { - nlohmann::json json_attr; - json_attr["definition"] = attr.def; - json_attr["value"] = attr.value; - json_item["attributes"].push_back(json_attr); - } - - items_json[std::to_string(item_id)] = json_item; - } - - local_storage->write_json_file("", items_user_file, items_json); -} - -Steam_User_Items::Steam_User_Items(class Settings *settings, class Networking *network, class Local_Storage *local_storage, class SteamCallBacks *callbacks, class SteamCallResults *callback_results) +Steam_User_Items::Steam_User_Items(class Settings *settings, class SteamCallBacks *callbacks, class SteamCallResults *callback_results) { this->settings = settings; - this->network = network; - this->local_storage = local_storage; this->callbacks = callbacks; this->callback_results = callback_results; - - this->network->setCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_User_Items::steam_user_items_network_callback, this); } Steam_User_Items::~Steam_User_Items() { - this->network->rmCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_User_Items::steam_user_items_network_callback, this); } SteamAPICall_t Steam_User_Items::LoadItems() @@ -133,10 +39,7 @@ SteamAPICall_t Steam_User_Items::LoadItems() PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - if (!items_loaded) { - load_items_from_file(); - items_loaded = true; - } + const auto &items = gc()->load_items_from_file(); UserItemCount_t data{}; data.m_gameID = settings->get_local_game_id(); @@ -170,6 +73,8 @@ bool Steam_User_Items::GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint3 PRINT_DEBUG("%u", iIndex); std::lock_guard lock(global_mutex); + const auto &items = gc()->get_items(); + if (iIndex >= items.size()) return false; @@ -197,6 +102,8 @@ bool Steam_User_Items::GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, ui PRINT_DEBUG("%llu", ulItemID); std::lock_guard lock(global_mutex); + const auto &items = gc()->get_items(); + for (const Econ_Item &item : items) { if (item.id != ulItemID) continue; @@ -226,6 +133,8 @@ bool Steam_User_Items::GetItemAttribute( uint64 ulItemID, uint32 unAttributeInde PRINT_DEBUG("%llu %u", ulItemID, unAttributeIndex); std::lock_guard lock(global_mutex); + const auto &items = gc()->get_items(); + for (const Econ_Item &item : items) { if (item.id != ulItemID) continue; @@ -242,63 +151,14 @@ bool Steam_User_Items::GetItemAttribute( uint64 ulItemID, uint32 unAttributeInde return false; } -//=============================================================================================================== -// POSITION HANDLING -//=============================================================================================================== -// TF Inventory Position cracking - -// REALLY OLD FORMAT (??): -// Bits 17-32 are the bag index (class index + 999, 1 is unequipped). -// Bits 1-16 are the position of the item within the bag. -// 0 means item hasn't been acknowledged by the player yet. -// -// LESS OLD FORMAT (up through July, 2011): -// If Bit 31 is 0: -// Bits 1-16 are the backpack position. -// Bits 17-26 are a bool for whether the item is equipped in the matching class. -// Otherwise, if Bit 31 is 1: -// Item hasn't been acknowledged by the player yet. -// Bits 1-16 are the method by the player found the item (see unacknowledged_item_inventory_positions_t) -// Bit 32 is 1, to note the new format. -// -// CURRENT FORMAT: -// If Bit 31 is 0: -// Bits 1-16 are the backpack position. -// Otherwise, if Bit 31 is 1: -// Item hasn't been acknowledged by the player yet. -// Bits 1-16 are the method by the player found the item (see unacknowledged_item_inventory_positions_t) -// Equipped state is stored elsewhere. -// This is the only format that should exist on clients. -// Note (1/15/2013) For backwards compatibility, if the value is 0 item is considered unacknowledged too - SteamAPICall_t Steam_User_Items::UpdateInventoryPos( uint64 ulItemID, uint32 unNewInventoryPos ) { PRINT_DEBUG("%llu 0x%08X", ulItemID, unNewInventoryPos); std::lock_guard lock(global_mutex); - for (Econ_Item &item : items) { - if (item.id != ulItemID) - continue; - - item.inv_pos = unNewInventoryPos; - save_items_to_file(); - - // let the server know, too - auto inventory_msg = new GameServer_Items_Messages::InventoryPosUpdate(); - inventory_msg->set_item_id(ulItemID); - inventory_msg->set_item_inv_pos(unNewInventoryPos); - - auto gameserver_items_msg = new GameServer_Items_Messages(); - gameserver_items_msg->set_type(GameServer_Items_Messages::Request_UpdateInventoryPos); - gameserver_items_msg->set_allocated_inventory_pos_update(inventory_msg); - - Common_Message msg{}; - msg.set_allocated_gameserver_items_messages(gameserver_items_msg); - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - network->sendToAllGameservers(&msg, true); - + if (gc()->set_item_pos(ulItemID, unNewInventoryPos, false)) { UpdateInventoryPosResponse_t data{}; - data.m_ulItemID = item.id; + data.m_ulItemID = ulItemID; data.m_eResult = k_EItemRequestResultOK; SteamAPICall_t ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); @@ -324,26 +184,7 @@ SteamAPICall_t Steam_User_Items::DeleteItem( uint64 ulItemID ) PRINT_DEBUG("%llu", ulItemID); std::lock_guard lock(global_mutex); - for (auto it = items.begin(); it != items.end(); it++) { - if (it->id != ulItemID) - continue; - - items.erase(it); - save_items_to_file(); - - // let the server know, too - auto drop_msg = new GameServer_Items_Messages::ItemDeletion(); - drop_msg->set_item_id(ulItemID); - - auto gameserver_items_msg = new GameServer_Items_Messages(); - gameserver_items_msg->set_type(GameServer_Items_Messages::Request_DeleteItem); - gameserver_items_msg->set_allocated_item_deletion(drop_msg); - - Common_Message msg{}; - msg.set_allocated_gameserver_items_messages(gameserver_items_msg); - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - network->sendToAllGameservers(&msg, true); - + if (gc()->delete_item(ulItemID, false)) { DeleteItemResponse_t data{}; data.m_ulItemID = ulItemID; data.m_eResult = k_EItemRequestResultOK; @@ -389,61 +230,3 @@ SteamAPICall_t Steam_User_Items::DropItem( uint64 ulItemID ) PRINT_DEBUG_TODO(); return k_uAPICallInvalid; } - -// server requested our inventory -void Steam_User_Items::network_callback_inventory_request(Common_Message *msg) -{ - if (!msg->gameserver_items_messages().has_inventory_request()) { - PRINT_DEBUG("error empty msg"); - return; - } - - uint64 server_steamid = msg->source_id(); - const auto &request_msg = msg->gameserver_items_messages().inventory_request(); - - auto response_msg = new GameServer_Items_Messages::InventoryResponse(); - response_msg->set_steam_api_call(request_msg.steam_api_call()); - - for (const Econ_Item &item : items) { - auto new_item = response_msg->add_items(); - new_item->set_id(item.id); - new_item->set_def(item.def); - new_item->set_level(item.level); - new_item->set_quality(static_cast(item.quality)); - new_item->set_inv_pos(item.inv_pos); - new_item->set_quantity(item.quantity); - - for (const auto &attr : item.attributes) { - auto new_attr = new_item->add_attributes(); - new_attr->set_def(attr.def); - new_attr->set_value(attr.value); - } - } - - auto gameserver_items_msg = new GameServer_Items_Messages(); - gameserver_items_msg->set_type(GameServer_Items_Messages::Response_Inventory); - gameserver_items_msg->set_allocated_inventory_response(response_msg); - - Common_Message new_msg{}; - new_msg.set_allocated_gameserver_items_messages(gameserver_items_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 inventory, sent %u items", static_cast(items.size())); -} - -// only triggered when we have a message -void Steam_User_Items::network_callback(Common_Message *msg) -{ - switch (msg->gameserver_items_messages().type()) { - // server requested our inventory - case GameServer_Items_Messages::Request_Inventory: - network_callback_inventory_request(msg); - break; - - default: - PRINT_DEBUG("unhandled type %i", (int)msg->gameserver_items_messages().type()); - break; - } -} diff --git a/post_build/steam_settings.EXAMPLE/gc.EXAMPLE.json b/post_build/steam_settings.EXAMPLE/gc.EXAMPLE.json new file mode 100644 index 000000000..6d4e9abd8 --- /dev/null +++ b/post_build/steam_settings.EXAMPLE/gc.EXAMPLE.json @@ -0,0 +1,3 @@ +{ + "gc_version": 20091217 +} From 1ccedc0f1f3bfd4cba1301432a9e63ffa17b5d44 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Fri, 13 Mar 2026 17:24:34 +0100 Subject: [PATCH 049/159] bump to SDK 164 --- dll/dll/steam_apps.h | 2 + dll/dll/steam_remoteplay.h | 20 +++++ dll/dll/steam_ugc.h | 11 ++- dll/flat.cpp | 84 +++++++++++++++++++ dll/steam_apps.cpp | 31 ++++++- dll/steam_client_interface_getter.cpp | 4 + dll/steam_remoteplay.cpp | 44 ++++++++++ dll/steam_ugc.cpp | 24 ++++++ sdk/steam/isteamapps.h | 4 +- sdk/steam/isteamapps008.h | 112 ++++++++++++++++++++++++++ sdk/steam/isteamparentalsettings.h | 1 + sdk/steam/isteamremoteplay.h | 38 +++++++-- sdk/steam/isteamremoteplay003.h | 86 ++++++++++++++++++++ sdk/steam/isteamugc.h | 9 +++ sdk/steam/steam_api.h | 2 + sdk/steam/steam_api_flat.h | 10 ++- sdk/steam/steamnetworkingtypes.h | 31 +++++-- 17 files changed, 497 insertions(+), 16 deletions(-) create mode 100644 sdk/steam/isteamapps008.h create mode 100644 sdk/steam/isteamremoteplay003.h diff --git a/dll/dll/steam_apps.h b/dll/dll/steam_apps.h index e44e004a3..784d7da4c 100644 --- a/dll/dll/steam_apps.h +++ b/dll/dll/steam_apps.h @@ -28,6 +28,7 @@ public ISteamApps004, public ISteamApps005, public ISteamApps006, public ISteamApps007, +public ISteamApps008, public ISteamApps { class Settings *settings{}; @@ -144,6 +145,7 @@ public ISteamApps // return beta branch details, name, description, current BuildID and state flags (EBetaBranchFlags) bool GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuildID, char *pchBetaName, int cchBetaName, char *pchDescription, int cchDescription ); // iterate through + bool GetBetaInfo(int iBetaIndex, uint32* punFlags, uint32* punBuildID, char* pchBetaName, int cchBetaName, char* pchDescription, int cchDescription, uint32* punLastUpdated); // iterate through // select this beta branch for this app as active, might need the game to restart so Steam can update to that branch bool SetActiveBeta( const char *pchBetaName ); diff --git a/dll/dll/steam_remoteplay.h b/dll/dll/steam_remoteplay.h index 8cdcc8ea6..750c77443 100644 --- a/dll/dll/steam_remoteplay.h +++ b/dll/dll/steam_remoteplay.h @@ -23,6 +23,7 @@ class Steam_RemotePlay : public ISteamRemotePlay001, public ISteamRemotePlay002, +public ISteamRemotePlay003, public ISteamRemotePlay { class Settings *settings{}; @@ -47,9 +48,28 @@ public ISteamRemotePlay // 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 ); + // Return true if the session has joined using a Remote Play Together invitation + bool BSessionRemotePlayTogether(RemotePlaySessionID_t unSessionID); + // Get the SteamID of the connected user CSteamID GetSessionSteamID( uint32 unSessionID ); + // Get the guest ID of the connected user if they are a Remote Play Together guest + // This returns 0 if the sessionID isn't valid or the session isn't a Remote Play Together guest + uint32 GetSessionGuestID(RemotePlaySessionID_t unSessionID); + + // gets the small (32x32) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid + // returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again + int GetSmallSessionAvatar(RemotePlaySessionID_t unSessionID); + + // gets the medium (64x64) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid + // returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again + int GetMediumSessionAvatar(RemotePlaySessionID_t unSessionID); + + // gets the large (184x184) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid + // returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again + int GetLargeSessionAvatar(RemotePlaySessionID_t unSessionID); + // Get the name of the session client device // This returns NULL if the sessionID is not valid const char *GetSessionClientName( uint32 unSessionID ); diff --git a/dll/dll/steam_ugc.h b/dll/dll/steam_ugc.h index 15e9085e8..4bd96ce42 100644 --- a/dll/dll/steam_ugc.h +++ b/dll/dll/steam_ugc.h @@ -434,7 +434,16 @@ public ISteamUGC // Set the local load order for these items. If there are any items not in the given list, they will sort by the time subscribed. bool SetSubscriptionsLoadOrder( PublishedFileId_t *pvecPublishedFileIDs, uint32 unNumPublishedFileIDs ); - + + // Tells the client to no longer try to keep the item in its local cache, unless it was subscribed to by other users on this machine + bool MarkDownloadedItemAsUnused(PublishedFileId_t nPublishedFileID); + + // Returns the number of items actually downloaded locally + uint32 GetNumDownloadedItems(); + + // Returns the ids of the items downloaded + uint32 GetDownloadedItems(PublishedFileId_t* pvecPublishedFileIDs, uint32 cMaxEntries); + }; #endif // __INCLUDED_STEAM_UGC_H__ diff --git a/dll/flat.cpp b/dll/flat.cpp index 5e77ad29b..317fe275d 100644 --- a/dll/flat.cpp +++ b/dll/flat.cpp @@ -2384,6 +2384,16 @@ STEAMAPI_API steam_bool SteamAPI_ISteamUserStats_GetAchievementProgressLimitsFlo return (get_steam_client()->steam_user_stats)->GetAchievementProgressLimits(pchName, pfMinProgress, pfMaxProgress); } +STEAMAPI_API ISteamApps* SteamAPI_SteamApps_v009() +{ + return get_steam_client()->GetISteamApps(flat_hsteamuser(), flat_hsteampipe(), "STEAMAPPS_INTERFACE_VERSION009"); +} + +STEAMAPI_API ISteamApps* SteamAPI_SteamGameServerApps_v009() +{ + return get_steam_client()->GetISteamApps(flat_gs_hsteamuser(), flat_gs_hsteampipe(), "STEAMAPPS_INTERFACE_VERSION009"); +} + STEAMAPI_API ISteamApps *SteamAPI_SteamApps_v008() { return get_steam_client()->GetISteamApps(flat_hsteamuser(), flat_hsteampipe(), "STEAMAPPS_INTERFACE_VERSION008"); @@ -2551,10 +2561,18 @@ STEAMAPI_API int SteamAPI_ISteamApps_GetNumBetas(ISteamApps* self, int* pnAvaila } // SDK 1.61 removed unAppID param +/* STEAMAPI_API steam_bool SteamAPI_ISteamApps_GetBetaInfo(ISteamApps* self, int iBetaIndex, uint32* punFlags, uint32* punBuildID, char* pchBetaName, int cchBetaName, char* pchDescription, int cchDescription) { return self->GetBetaInfo(iBetaIndex, punFlags, punBuildID, pchBetaName, cchBetaName, pchDescription, cchDescription); } +*/ + +// SDK 1.64 added punLastUpdated. +STEAMAPI_API steam_bool SteamAPI_ISteamApps_GetBetaInfo(ISteamApps* self, int iBetaIndex, uint32* punFlags, uint32* punBuildID, char* pchBetaName, int cchBetaName, char* pchDescription, int cchDescription, uint32* punLastUpdated) +{ + return self->GetBetaInfo(iBetaIndex, punFlags, punBuildID, pchBetaName, cchBetaName, pchDescription, cchDescription, punLastUpdated); +} // SDK 1.61 removed unAppID param STEAMAPI_API steam_bool SteamAPI_ISteamApps_SetActiveBeta(ISteamApps* self, const char* pchBetaName) @@ -4812,6 +4830,42 @@ STEAMAPI_API steam_bool SteamAPI_ISteamUGC_SetSubscriptionsLoadOrder( ISteamUGC return (ptr)->SetSubscriptionsLoadOrder(pvecPublishedFileIDs, unNumPublishedFileIDs); } +STEAMAPI_API steam_bool SteamAPI_ISteamUGC_MarkDownloadedItemAsUnused(ISteamUGC* self, PublishedFileId_t nPublishedFileID) +{ + long long client_vftable_distance = ((char*)self - (char*)get_steam_client()->steam_ugc); + long long server_vftable_distance = ((char*)self - (char*)get_steam_client()->steam_gameserver_ugc); + auto ptr = get_steam_client()->steam_gameserver_ugc; + if (client_vftable_distance >= 0 && (server_vftable_distance < 0 || client_vftable_distance < server_vftable_distance)) { + ptr = get_steam_client()->steam_ugc; + } + + return (ptr)->MarkDownloadedItemAsUnused(nPublishedFileID); +} + +STEAMAPI_API uint32 SteamAPI_ISteamUGC_GetNumDownloadedItems(ISteamUGC* self) +{ + long long client_vftable_distance = ((char*)self - (char*)get_steam_client()->steam_ugc); + long long server_vftable_distance = ((char*)self - (char*)get_steam_client()->steam_gameserver_ugc); + auto ptr = get_steam_client()->steam_gameserver_ugc; + if (client_vftable_distance >= 0 && (server_vftable_distance < 0 || client_vftable_distance < server_vftable_distance)) { + ptr = get_steam_client()->steam_ugc; + } + + return (ptr)->GetNumDownloadedItems(); +} + +STEAMAPI_API uint32 SteamAPI_ISteamUGC_GetDownloadedItems(ISteamUGC* self, PublishedFileId_t* pvecPublishedFileIDs, uint32 cMaxEntries) +{ + long long client_vftable_distance = ((char*)self - (char*)get_steam_client()->steam_ugc); + long long server_vftable_distance = ((char*)self - (char*)get_steam_client()->steam_gameserver_ugc); + auto ptr = get_steam_client()->steam_gameserver_ugc; + if (client_vftable_distance >= 0 && (server_vftable_distance < 0 || client_vftable_distance < server_vftable_distance)) { + ptr = get_steam_client()->steam_ugc; + } + + return (ptr)->GetDownloadedItems(pvecPublishedFileIDs, cMaxEntries); +} + STEAMAPI_API ISteamAppList *SteamAPI_SteamAppList_v001() { return get_steam_client()->GetISteamAppList(flat_hsteamuser(), flat_hsteampipe(), "STEAMAPPLIST_INTERFACE_VERSION001"); @@ -5791,6 +5845,11 @@ STEAMAPI_API ISteamRemotePlay *SteamAPI_SteamRemotePlay_v003() return get_steam_client()->GetISteamRemotePlay(flat_hsteamuser(), flat_hsteampipe(), "STEAMREMOTEPLAY_INTERFACE_VERSION003"); } +STEAMAPI_API ISteamRemotePlay* SteamAPI_SteamRemotePlay_v004() +{ + return get_steam_client()->GetISteamRemotePlay(flat_hsteamuser(), flat_hsteampipe(), "STEAMREMOTEPLAY_INTERFACE_VERSION004"); +} + STEAMAPI_API uint32 SteamAPI_ISteamRemotePlay_GetSessionCount( ISteamRemotePlay* self ) { return (get_steam_client()->steam_remoteplay)->GetSessionCount(); @@ -5801,11 +5860,36 @@ STEAMAPI_API RemotePlaySessionID_t SteamAPI_ISteamRemotePlay_GetSessionID( IStea return (get_steam_client()->steam_remoteplay)->GetSessionID(iSessionIndex); } +STEAMAPI_API bool SteamAPI_ISteamRemotePlay_BSessionRemotePlayTogether(ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID) +{ + return (get_steam_client()->steam_remoteplay)->BSessionRemotePlayTogether(unSessionID); +} + STEAMAPI_API uint64_steamid SteamAPI_ISteamRemotePlay_GetSessionSteamID( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ) { return (get_steam_client()->steam_remoteplay)->GetSessionSteamID(unSessionID).ConvertToUint64(); } +STEAMAPI_API uint32 SteamAPI_ISteamRemotePlay_GetSessionGuestID(ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID) +{ + return (get_steam_client()->steam_remoteplay)->GetSessionGuestID(unSessionID); +} + +STEAMAPI_API int SteamAPI_ISteamRemotePlay_GetSmallSessionAvatar(ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID) +{ + return (get_steam_client()->steam_remoteplay)->GetSmallSessionAvatar(unSessionID); +} + +STEAMAPI_API int SteamAPI_ISteamRemotePlay_GetMediumSessionAvatar(ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID) +{ + return (get_steam_client()->steam_remoteplay)->GetMediumSessionAvatar(unSessionID); +} + +STEAMAPI_API int SteamAPI_ISteamRemotePlay_GetLargeSessionAvatar(ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID) +{ + return (get_steam_client()->steam_remoteplay)->GetLargeSessionAvatar(unSessionID); +} + STEAMAPI_API const char * SteamAPI_ISteamRemotePlay_GetSessionClientName( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ) { return (get_steam_client()->steam_remoteplay)->GetSessionClientName(unSessionID); diff --git a/dll/steam_apps.cpp b/dll/steam_apps.cpp index 8c4f6728d..ccf13e54e 100644 --- a/dll/steam_apps.cpp +++ b/dll/steam_apps.cpp @@ -614,7 +614,8 @@ int Steam_Apps::GetNumBetas( int *pnAvailable, int *pnPrivate ) // TODO no public docs // return beta branch details, name, description, current BuildID and state flags (EBetaBranchFlags) -bool Steam_Apps::GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuildID, char *pchBetaName, int cchBetaName, char *pchDescription, int cchDescription ) // iterate through +// iterate through +bool Steam_Apps::GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuildID, char *pchBetaName, int cchBetaName, char *pchDescription, int cchDescription ) { // I assume this API is like "Steam_User_Stats::GetNextMostAchievedAchievementInfo()", it returns 'ok' until index is out of range PRINT_DEBUG("[%i] %p %p --- %p %i --- %p %i", iBetaIndex, punFlags, punBuildID, pchBetaName, cchBetaName, pchDescription, cchDescription); @@ -641,6 +642,34 @@ bool Steam_Apps::GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuild return true; } +bool Steam_Apps::GetBetaInfo(int iBetaIndex, uint32* punFlags, uint32* punBuildID, char* pchBetaName, int cchBetaName, char* pchDescription, int cchDescription, uint32* punLastUpdated) +{ + // I assume this API is like "Steam_User_Stats::GetNextMostAchievedAchievementInfo()", it returns 'ok' until index is out of range + PRINT_DEBUG("[%i] %p %p --- %p %i --- %p %i --- %p", iBetaIndex, punFlags, punBuildID, pchBetaName, cchBetaName, pchDescription, cchDescription, punLastUpdated); + std::lock_guard lock(global_mutex); + + if (iBetaIndex < 0) return false; + if (static_cast(iBetaIndex) >= settings->branches.size()) return false; + + const auto& branch = settings->branches[iBetaIndex]; + + if (punFlags) *punFlags = branch.flags; + if (punBuildID) *punBuildID = branch.build_id; + if (punLastUpdated) *punLastUpdated = branch.time_updated_epoch; + + if (pchBetaName && cchBetaName > 0 && static_cast(cchBetaName) > branch.name.size()) { + memset(pchBetaName, 0, cchBetaName); + memcpy(pchBetaName, branch.name.c_str(), branch.name.size()); + } + + if (pchDescription && cchDescription > 0 && static_cast(cchDescription) > branch.description.size()) { + memset(pchDescription, 0, cchDescription); + memcpy(pchDescription, branch.description.c_str(), branch.description.size()); + } + + return true; +} + // TODO no public docs // select this beta branch for this app as active, might need the game to restart so Steam can update to that branch bool Steam_Apps::SetActiveBeta( const char *pchBetaName ) diff --git a/dll/steam_client_interface_getter.cpp b/dll/steam_client_interface_getter.cpp index adc7df4d3..4bcc083b2 100644 --- a/dll/steam_client_interface_getter.cpp +++ b/dll/steam_client_interface_getter.cpp @@ -630,6 +630,8 @@ ISteamApps *Steam_Client::GetISteamApps( HSteamUser hSteamUser, HSteamPipe hStea return reinterpret_cast(static_cast(steam_apps_temp)); } else if (strcmp(pchVersion, "STEAMAPPS_INTERFACE_VERSION007") == 0) { return reinterpret_cast(static_cast(steam_apps_temp)); + } else if (strcmp(pchVersion, "STEAMAPPS_INTERFACE_VERSION008") == 0) { + return reinterpret_cast(static_cast(steam_apps_temp)); } else if (strcmp(pchVersion, STEAMAPPS_INTERFACE_VERSION) == 0) { return reinterpret_cast(static_cast(steam_apps_temp)); } @@ -1059,6 +1061,8 @@ ISteamRemotePlay *Steam_Client::GetISteamRemotePlay( HSteamUser hSteamUser, HSte return reinterpret_cast(static_cast(steam_remoteplay)); } else if (strcmp(pchVersion, "STEAMREMOTEPLAY_INTERFACE_VERSION002") == 0) { return reinterpret_cast(static_cast(steam_remoteplay)); + } else if (strcmp(pchVersion, "STEAMREMOTEPLAY_INTERFACE_VERSION003") == 0) { + return reinterpret_cast(static_cast(steam_remoteplay)); } else if (strcmp(pchVersion, STEAMREMOTEPLAY_INTERFACE_VERSION) == 0) { return reinterpret_cast(static_cast(steam_remoteplay)); } diff --git a/dll/steam_remoteplay.cpp b/dll/steam_remoteplay.cpp index 67a34dfb1..949912334 100644 --- a/dll/steam_remoteplay.cpp +++ b/dll/steam_remoteplay.cpp @@ -67,6 +67,14 @@ uint32 Steam_RemotePlay::GetSessionID( int iSessionIndex ) return 0; } +// Return true if the session has joined using a Remote Play Together invitation +bool Steam_RemotePlay::BSessionRemotePlayTogether(RemotePlaySessionID_t unSessionID) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + // Get the SteamID of the connected user CSteamID Steam_RemotePlay::GetSessionSteamID( uint32 unSessionID ) { @@ -75,6 +83,42 @@ CSteamID Steam_RemotePlay::GetSessionSteamID( uint32 unSessionID ) return k_steamIDNil; } +// Get the guest ID of the connected user if they are a Remote Play Together guest +// This returns 0 if the sessionID isn't valid or the session isn't a Remote Play Together guest +uint32 Steam_RemotePlay::GetSessionGuestID(RemotePlaySessionID_t unSessionID) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// gets the small (32x32) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid +// returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again +int Steam_RemotePlay::GetSmallSessionAvatar(RemotePlaySessionID_t unSessionID) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// gets the medium (64x64) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid +// returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again +int Steam_RemotePlay::GetMediumSessionAvatar(RemotePlaySessionID_t unSessionID) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// gets the large (184x184) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid +// returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again +int Steam_RemotePlay::GetLargeSessionAvatar(RemotePlaySessionID_t unSessionID) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + // Get the name of the session client device // This returns NULL if the sessionID is not valid const char* Steam_RemotePlay::GetSessionClientName( uint32 unSessionID ) diff --git a/dll/steam_ugc.cpp b/dll/steam_ugc.cpp index 1b7584ff7..15e3f8d27 100644 --- a/dll/steam_ugc.cpp +++ b/dll/steam_ugc.cpp @@ -1808,3 +1808,27 @@ bool Steam_UGC::SetSubscriptionsLoadOrder( PublishedFileId_t *pvecPublishedFileI return true; } + +// Tells the client to no longer try to keep the item in its local cache, unless it was subscribed to by other users on this machine +bool Steam_UGC::MarkDownloadedItemAsUnused(PublishedFileId_t nPublishedFileID) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + +// Returns the number of items actually downloaded locally +uint32 Steam_UGC::GetNumDownloadedItems() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// Returns the ids of the items downloaded +uint32 Steam_UGC::GetDownloadedItems(PublishedFileId_t* pvecPublishedFileIDs, uint32 cMaxEntries) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} diff --git a/sdk/steam/isteamapps.h b/sdk/steam/isteamapps.h index 820b7775b..618244da9 100644 --- a/sdk/steam/isteamapps.h +++ b/sdk/steam/isteamapps.h @@ -116,13 +116,13 @@ class ISteamApps virtual int GetNumBetas( int *pnAvailable, int *pnPrivate ) = 0; // // return beta branch details, name, description, current BuildID and state flags (EBetaBranchFlags) - virtual bool GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuildID, char *pchBetaName, int cchBetaName, char *pchDescription, int cchDescription ) = 0; // iterate through + virtual bool GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuildID, char *pchBetaName, int cchBetaName, char *pchDescription, int cchDescription, uint32* punLastUpdated) = 0; // iterate through // select this beta branch for this app as active, might need the game to restart so Steam can update to that branch virtual bool SetActiveBeta( const char *pchBetaName ) = 0; }; -#define STEAMAPPS_INTERFACE_VERSION "STEAMAPPS_INTERFACE_VERSION008" +#define STEAMAPPS_INTERFACE_VERSION "STEAMAPPS_INTERFACE_VERSION009" #ifndef STEAM_API_EXPORTS // Global interface accessor diff --git a/sdk/steam/isteamapps008.h b/sdk/steam/isteamapps008.h new file mode 100644 index 000000000..17aa65e8c --- /dev/null +++ b/sdk/steam/isteamapps008.h @@ -0,0 +1,112 @@ + +#ifndef ISTEAMAPPS008_H +#define ISTEAMAPPS008_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +class ISteamApps008 +{ +public: + virtual bool BIsSubscribed() = 0; + virtual bool BIsLowViolence() = 0; + virtual bool BIsCybercafe() = 0; + virtual bool BIsVACBanned() = 0; + virtual const char* GetCurrentGameLanguage() = 0; + virtual const char* GetAvailableGameLanguages() = 0; + + // only use this member if you need to check ownership of another game related to yours, a demo for example + virtual bool BIsSubscribedApp(AppId_t appID) = 0; + + // Takes AppID of DLC and checks if the user owns the DLC & if the DLC is installed + virtual bool BIsDlcInstalled(AppId_t appID) = 0; + + // returns the Unix time of the purchase of the app + virtual uint32 GetEarliestPurchaseUnixTime(AppId_t nAppID) = 0; + + // Checks if the user is subscribed to the current app through a free weekend + // This function will return false for users who have a retail or other type of license + // Before using, please ask your Valve technical contact how to package and secure your free weekend + virtual bool BIsSubscribedFromFreeWeekend() = 0; + + // Returns the number of DLC pieces for the running app + virtual int GetDLCCount() = 0; + + // Returns metadata for DLC by index, of range [0, GetDLCCount()] + virtual bool BGetDLCDataByIndex(int iDLC, AppId_t* pAppID, bool* pbAvailable, char* pchName, int cchNameBufferSize) = 0; + + // Install/Uninstall control for optional DLC + virtual void InstallDLC(AppId_t nAppID) = 0; + virtual void UninstallDLC(AppId_t nAppID) = 0; + + // Request legacy cd-key for yourself or owned DLC. If you are interested in this + // data then make sure you provide us with a list of valid keys to be distributed + // to users when they purchase the game, before the game ships. + // You'll receive an AppProofOfPurchaseKeyResponse_t callback when + // the key is available (which may be immediately). + virtual void RequestAppProofOfPurchaseKey(AppId_t nAppID) = 0; + + virtual bool GetCurrentBetaName(char* pchName, int cchNameBufferSize) = 0; // returns current beta branch name, 'public' is the default branch + virtual bool MarkContentCorrupt(bool bMissingFilesOnly) = 0; // signal Steam that game files seems corrupt or missing + virtual uint32 GetInstalledDepots(AppId_t appID, DepotId_t* pvecDepots, uint32 cMaxDepots) = 0; // return installed depots in mount order + + // returns current app install folder for AppID, returns folder name length + virtual uint32 GetAppInstallDir(AppId_t appID, char* pchFolder, uint32 cchFolderBufferSize) = 0; + virtual bool BIsAppInstalled(AppId_t appID) = 0; // returns true if that app is installed (not necessarily owned) + + // returns the SteamID of the original owner. If this CSteamID is different from ISteamUser::GetSteamID(), + // the user has a temporary license borrowed via Family Sharing + virtual CSteamID GetAppOwner() = 0; + + // Returns the associated launch param if the game is run via steam://run///?param1=value1¶m2=value2¶m3=value3 etc. + // Parameter names starting with the character '@' are reserved for internal use and will always return and empty string. + // Parameter names starting with an underscore '_' are reserved for steam features -- they can be queried by the game, + // but it is advised that you not param names beginning with an underscore for your own features. + // Check for new launch parameters on callback NewUrlLaunchParameters_t + virtual const char* GetLaunchQueryParam(const char* pchKey) = 0; + + // get download progress for optional DLC + virtual bool GetDlcDownloadProgress(AppId_t nAppID, uint64* punBytesDownloaded, uint64* punBytesTotal) = 0; + + // return the buildid of this app, may change at any time based on backend updates to the game + virtual int GetAppBuildId() = 0; + + // Request all proof of purchase keys for the calling appid and associated DLC. + // A series of AppProofOfPurchaseKeyResponse_t callbacks will be sent with + // appropriate appid values, ending with a final callback where the m_nAppId + // member is k_uAppIdInvalid (zero). + virtual void RequestAllProofOfPurchaseKeys() = 0; + + STEAM_CALL_RESULT(FileDetailsResult_t) + virtual SteamAPICall_t GetFileDetails(const char* pszFileName) = 0; + + // Get command line if game was launched via Steam URL, e.g. steam://run////. + // This method of passing a connect string (used when joining via rich presence, accepting an + // invite, etc) is preferable to passing the connect string on the operating system command + // line, which is a security risk. In order for rich presence joins to go through this + // path and not be placed on the OS command line, you must set a value in your app's + // configuration on Steam. Ask Valve for help with this. + // + // If game was already running and launched again, the NewUrlLaunchParameters_t will be fired. + virtual int GetLaunchCommandLine(char* pszCommandLine, int cubCommandLine) = 0; + + // Check if user borrowed this game via Family Sharing, If true, call GetAppOwner() to get the lender SteamID + virtual bool BIsSubscribedFromFamilySharing() = 0; + + // check if game is a timed trial with limited playtime + virtual bool BIsTimedTrial(uint32* punSecondsAllowed, uint32* punSecondsPlayed) = 0; + + // set current DLC AppID being played (or 0 if none). Allows Steam to track usage of major DLC extensions + virtual bool SetDlcContext(AppId_t nAppID) = 0; + + // returns total number of known app beta branches (including default "public" branch ) + virtual int GetNumBetas(int* pnAvailable, int* pnPrivate) = 0; // + + // return beta branch details, name, description, current BuildID and state flags (EBetaBranchFlags) + virtual bool GetBetaInfo(int iBetaIndex, uint32* punFlags, uint32* punBuildID, char* pchBetaName, int cchBetaName, char* pchDescription, int cchDescription) = 0; // iterate through + + // select this beta branch for this app as active, might need the game to restart so Steam can update to that branch + virtual bool SetActiveBeta(const char* pchBetaName) = 0; +}; + +#endif //ISTEAMAPPS008_H diff --git a/sdk/steam/isteamparentalsettings.h b/sdk/steam/isteamparentalsettings.h index edf822bbf..7b385faf3 100644 --- a/sdk/steam/isteamparentalsettings.h +++ b/sdk/steam/isteamparentalsettings.h @@ -31,6 +31,7 @@ enum EParentalFeature k_EFeatureSiteLicense = 13, k_EFeatureKioskMode_Deprecated = 14, k_EFeatureBlockAlways = 15, + k_EFeatureDesktop = 16, k_EFeatureMax }; diff --git a/sdk/steam/isteamremoteplay.h b/sdk/steam/isteamremoteplay.h index 99f67ea18..2811f439c 100644 --- a/sdk/steam/isteamremoteplay.h +++ b/sdk/steam/isteamremoteplay.h @@ -282,9 +282,28 @@ class ISteamRemotePlay // Get the currently connected Steam Remote Play session ID at the specified index. Returns zero if index is out of bounds. virtual RemotePlaySessionID_t GetSessionID( int iSessionIndex ) = 0; + // Return true if the session has joined using a Remote Play Together invitation + virtual bool BSessionRemotePlayTogether( RemotePlaySessionID_t unSessionID ) = 0; + // Get the SteamID of the connected user virtual CSteamID GetSessionSteamID( RemotePlaySessionID_t unSessionID ) = 0; + // Get the guest ID of the connected user if they are a Remote Play Together guest + // This returns 0 if the sessionID isn't valid or the session isn't a Remote Play Together guest + virtual uint32 GetSessionGuestID( RemotePlaySessionID_t unSessionID ) = 0; + + // gets the small (32x32) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid + // returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again + virtual int GetSmallSessionAvatar( RemotePlaySessionID_t unSessionID ) = 0; + + // gets the medium (64x64) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid + // returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again + virtual int GetMediumSessionAvatar( RemotePlaySessionID_t unSessionID ) = 0; + + // gets the large (184x184) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid + // returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again + virtual int GetLargeSessionAvatar( RemotePlaySessionID_t unSessionID ) = 0; + // Get the name of the session client device // This returns NULL if the sessionID is not valid virtual const char *GetSessionClientName( RemotePlaySessionID_t unSessionID ) = 0; @@ -337,8 +356,8 @@ class ISteamRemotePlay // nHeight - The height of the cursor, in pixels // nHotX - The X coordinate of the cursor hot spot in pixels, offset from the left of the cursor // nHotY - The Y coordinate of the cursor hot spot in pixels, offset from the top of the cursor - // pBGRA - A pointer to the cursor pixels, with the color channels in red, green, blue, alpha order - // nPitch - The distance between pixel rows in bytes, defaults to nWidth * 4 + // pBGRA - A pointer to the cursor pixels, with 8-bit channels in red, green, blue, alpha order + // nPitch - The distance between pixel rows in bytes, defaults to nWidth * 4 virtual RemotePlayCursorID_t CreateMouseCursor( int nWidth, int nHeight, int nHotX, int nHotY, const void *pBGRA, int nPitch = 0 ) = 0; // Set the mouse cursor for a remote player @@ -348,7 +367,7 @@ class ISteamRemotePlay virtual void SetMouseCursor( RemotePlaySessionID_t unSessionID, RemotePlayCursorID_t unCursorID ) = 0; }; -#define STEAMREMOTEPLAY_INTERFACE_VERSION "STEAMREMOTEPLAY_INTERFACE_VERSION003" +#define STEAMREMOTEPLAY_INTERFACE_VERSION "STEAMREMOTEPLAY_INTERFACE_VERSION004" // Global interface accessor inline ISteamRemotePlay *SteamRemotePlay(); @@ -366,18 +385,25 @@ STEAM_DEFINE_USER_INTERFACE_ACCESSOR( ISteamRemotePlay *, SteamRemotePlay, STEAM STEAM_CALLBACK_BEGIN( SteamRemotePlaySessionConnected_t, k_iSteamRemotePlayCallbacks + 1 ) STEAM_CALLBACK_MEMBER( 0, RemotePlaySessionID_t, m_unSessionID ) -STEAM_CALLBACK_END( 0 ) +STEAM_CALLBACK_END( 1 ) STEAM_CALLBACK_BEGIN( SteamRemotePlaySessionDisconnected_t, k_iSteamRemotePlayCallbacks + 2 ) STEAM_CALLBACK_MEMBER( 0, RemotePlaySessionID_t, m_unSessionID ) -STEAM_CALLBACK_END( 0 ) +STEAM_CALLBACK_END( 1 ) STEAM_CALLBACK_BEGIN( SteamRemotePlayTogetherGuestInvite_t, k_iSteamRemotePlayCallbacks + 3 ) STEAM_CALLBACK_MEMBER_ARRAY( 0, char, m_szConnectURL, 1024 ) -STEAM_CALLBACK_END( 0 ) +STEAM_CALLBACK_END( 1 ) + +STEAM_CALLBACK_BEGIN(SteamRemotePlaySessionAvatarLoaded_t, k_iSteamRemotePlayCallbacks + 4) + STEAM_CALLBACK_MEMBER(0, RemotePlaySessionID_t, m_unSessionID) // sessionID the avatar has been loaded for + STEAM_CALLBACK_MEMBER(1, int, m_iImage) // the image index of the now loaded image + STEAM_CALLBACK_MEMBER(2, int, m_iWide) // width of the loaded image + STEAM_CALLBACK_MEMBER(3, int, m_iTall) // height of the loaded image +STEAM_CALLBACK_END(4) #pragma pack( pop ) diff --git a/sdk/steam/isteamremoteplay003.h b/sdk/steam/isteamremoteplay003.h new file mode 100644 index 000000000..06e0d8d71 --- /dev/null +++ b/sdk/steam/isteamremoteplay003.h @@ -0,0 +1,86 @@ + +#ifndef ISTEAMREMOTEPLAY003_H +#define ISTEAMREMOTEPLAY003_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Functions to provide information about Steam Remote Play sessions +//----------------------------------------------------------------------------- +class ISteamRemotePlay003 +{ +public: + // Get the number of currently connected Steam Remote Play sessions + virtual uint32 GetSessionCount() = 0; + + // Get the currently connected Steam Remote Play session ID at the specified index. Returns zero if index is out of bounds. + virtual RemotePlaySessionID_t GetSessionID( int iSessionIndex ) = 0; + + // Get the SteamID of the connected user + virtual CSteamID GetSessionSteamID( RemotePlaySessionID_t unSessionID ) = 0; + + // Get the name of the session client device + // This returns NULL if the sessionID is not valid + virtual const char *GetSessionClientName( RemotePlaySessionID_t unSessionID ) = 0; + + // Get the form factor of the session client device + virtual ESteamDeviceFormFactor GetSessionClientFormFactor( RemotePlaySessionID_t unSessionID ) = 0; + + // Get the resolution, in pixels, of the session client device + // This is set to 0x0 if the resolution is not available + virtual bool BGetSessionClientResolution( RemotePlaySessionID_t unSessionID, int *pnResolutionX, int *pnResolutionY ) = 0; + + // Show the Remote Play Together UI in the game overlay + // This returns false if your game is not configured for Remote Play Together + virtual bool ShowRemotePlayTogetherUI() = 0; + + // Invite a friend to Remote Play Together, or create a guest invite if steamIDFriend is CSteamID() + // This returns false if the invite can't be sent or your game is not configured for Remote Play Together + virtual bool BSendRemotePlayTogetherInvite( CSteamID steamIDFriend ) = 0; + + // Make mouse and keyboard input for Remote Play Together sessions available via GetInput() instead of being merged with local input + virtual bool BEnableRemotePlayTogetherDirectInput() = 0; + + // Merge Remote Play Together mouse and keyboard input with local input + virtual void DisableRemotePlayTogetherDirectInput() = 0; + + // Get input events from Remote Play Together sessions + // This is available after calling BEnableRemotePlayTogetherDirectInput() + // + // pInput is an array of input events that will be filled in by this function, up to unMaxEvents. + // This returns the number of events copied to pInput, or the number of events available if pInput is nullptr. + virtual uint32 GetInput( RemotePlayInput_t *pInput, uint32 unMaxEvents ) = 0; + + // Set the mouse cursor visibility for a remote player + // This is available after calling BEnableRemotePlayTogetherDirectInput() + virtual void SetMouseVisibility( RemotePlaySessionID_t unSessionID, bool bVisible ) = 0; + + // Set the mouse cursor position for a remote player + // This is available after calling BEnableRemotePlayTogetherDirectInput() + // + // This is used to warp the cursor to a specific location and isn't needed during normal event processing. + // + // The position is normalized relative to the window, where 0,0 is the upper left, and 1,1 is the lower right. + virtual void SetMousePosition( RemotePlaySessionID_t unSessionID, float flNormalizedX, float flNormalizedY ) = 0; + + // Create a cursor that can be used with SetMouseCursor() + // This is available after calling BEnableRemotePlayTogetherDirectInput() + // + // Parameters: + // nWidth - The width of the cursor, in pixels + // nHeight - The height of the cursor, in pixels + // nHotX - The X coordinate of the cursor hot spot in pixels, offset from the left of the cursor + // nHotY - The Y coordinate of the cursor hot spot in pixels, offset from the top of the cursor + // pBGRA - A pointer to the cursor pixels, with the color channels in red, green, blue, alpha order + // nPitch - The distance between pixel rows in bytes, defaults to nWidth * 4 + virtual RemotePlayCursorID_t CreateMouseCursor( int nWidth, int nHeight, int nHotX, int nHotY, const void *pBGRA, int nPitch = 0 ) = 0; + + // Set the mouse cursor for a remote player + // This is available after calling BEnableRemotePlayTogetherDirectInput() + // + // The cursor ID is a value returned by CreateMouseCursor() + virtual void SetMouseCursor( RemotePlaySessionID_t unSessionID, RemotePlayCursorID_t unCursorID ) = 0; +}; + +#endif // #define ISTEAMREMOTEPLAY003_H diff --git a/sdk/steam/isteamugc.h b/sdk/steam/isteamugc.h index cf2fbf4d5..344a6c125 100644 --- a/sdk/steam/isteamugc.h +++ b/sdk/steam/isteamugc.h @@ -400,6 +400,15 @@ class ISteamUGC // Set the local load order for these items. If there are any items not in the given list, they will sort by the time subscribed. virtual bool SetSubscriptionsLoadOrder( PublishedFileId_t *pvecPublishedFileIDs, uint32 unNumPublishedFileIDs ) = 0; + + // Tells the client to no longer try to keep the item in its local cache, unless it was subscribed to by other users on this machine + virtual bool MarkDownloadedItemAsUnused( PublishedFileId_t nPublishedFileID ) = 0; + + // Returns the number of items actually downloaded locally + virtual uint32 GetNumDownloadedItems() = 0; + + // Returns the ids of the items downloaded + virtual uint32 GetDownloadedItems( PublishedFileId_t *pvecPublishedFileIDs, uint32 cMaxEntries ) = 0; }; #define STEAMUGC_INTERFACE_VERSION "STEAMUGC_INTERFACE_VERSION021" diff --git a/sdk/steam/steam_api.h b/sdk/steam/steam_api.h index fbb674292..46ba98570 100644 --- a/sdk/steam/steam_api.h +++ b/sdk/steam/steam_api.h @@ -121,6 +121,7 @@ #include "isteamuserstats002.h" #include "isteamuserstats001.h" #include "isteamapps.h" +#include "isteamapps008.h" #include "isteamapps007.h" #include "isteamapps006.h" #include "isteamapps005.h" @@ -228,6 +229,7 @@ #include "isteamremoteplay.h" #include "isteamremoteplay001.h" #include "isteamremoteplay002.h" +#include "isteamremoteplay003.h" #include "isteamnetworkingmessages.h" #include "isteamnetworkingsockets.h" #include "isteamnetworkingutils.h" diff --git a/sdk/steam/steam_api_flat.h b/sdk/steam/steam_api_flat.h index a49941a2b..121c28ed4 100644 --- a/sdk/steam/steam_api_flat.h +++ b/sdk/steam/steam_api_flat.h @@ -454,6 +454,8 @@ STEAMAPI_API steam_bool SteamAPI_ISteamUserStats_GetAchievementProgressLimitsInt STEAMAPI_API steam_bool SteamAPI_ISteamUserStats_GetAchievementProgressLimitsFloat( ISteamUserStats* self, const char * pchName, float * pfMinProgress, float * pfMaxProgress ); // ISteamApps +STEAMAPI_API ISteamApps *SteamAPI_SteamApps_v009(); +STEAMAPI_API ISteamApps *SteamAPI_SteamGameServerApps_v009(); STEAMAPI_API ISteamApps *SteamAPI_SteamApps_v008(); STEAMAPI_API ISteamApps *SteamAPI_SteamGameServerApps_v008(); STEAMAPI_API steam_bool SteamAPI_ISteamApps_BIsSubscribed( ISteamApps* self ); @@ -487,7 +489,7 @@ STEAMAPI_API steam_bool SteamAPI_ISteamApps_BIsSubscribedFromFamilySharing( ISte STEAMAPI_API steam_bool SteamAPI_ISteamApps_BIsTimedTrial( ISteamApps* self, uint32 * punSecondsAllowed, uint32 * punSecondsPlayed ); STEAMAPI_API steam_bool SteamAPI_ISteamApps_SetDlcContext( ISteamApps* self, AppId_t nAppID ); STEAMAPI_API int SteamAPI_ISteamApps_GetNumBetas( ISteamApps* self, int * pnAvailable, int * pnPrivate ); -STEAMAPI_API steam_bool SteamAPI_ISteamApps_GetBetaInfo( ISteamApps* self, int iBetaIndex, uint32 * punFlags, uint32 * punBuildID, char * pchBetaName, int cchBetaName, char * pchDescription, int cchDescription ); +STEAMAPI_API steam_bool SteamAPI_ISteamApps_GetBetaInfo( ISteamApps* self, int iBetaIndex, uint32 * punFlags, uint32 * punBuildID, char * pchBetaName, int cchBetaName, char * pchDescription, int cchDescription, uint32 *punLastUpdated ); STEAMAPI_API steam_bool SteamAPI_ISteamApps_SetActiveBeta( ISteamApps* self, const char * pchBetaName ); @@ -963,9 +965,15 @@ STEAMAPI_API steam_bool SteamAPI_ISteamParentalSettings_BIsFeatureInBlockList( I STEAMAPI_API ISteamRemotePlay *SteamAPI_SteamRemotePlay_v001(); STEAMAPI_API ISteamRemotePlay *SteamAPI_SteamRemotePlay_v002(); STEAMAPI_API ISteamRemotePlay *SteamAPI_SteamRemotePlay_v003(); +STEAMAPI_API ISteamRemotePlay *SteamAPI_SteamRemotePlay_v004(); STEAMAPI_API uint32 SteamAPI_ISteamRemotePlay_GetSessionCount( ISteamRemotePlay* self ); STEAMAPI_API RemotePlaySessionID_t SteamAPI_ISteamRemotePlay_GetSessionID( ISteamRemotePlay* self, int iSessionIndex ); +STEAMAPI_API bool SteamAPI_ISteamRemotePlay_BSessionRemotePlayTogether( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); STEAMAPI_API uint64_steamid SteamAPI_ISteamRemotePlay_GetSessionSteamID( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); +STEAMAPI_API uint32 SteamAPI_ISteamRemotePlay_GetSessionGuestID( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); +STEAMAPI_API int SteamAPI_ISteamRemotePlay_GetSmallSessionAvatar( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); +STEAMAPI_API int SteamAPI_ISteamRemotePlay_GetMediumSessionAvatar( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); +STEAMAPI_API int SteamAPI_ISteamRemotePlay_GetLargeSessionAvatar( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); STEAMAPI_API const char * SteamAPI_ISteamRemotePlay_GetSessionClientName( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); STEAMAPI_API ESteamDeviceFormFactor SteamAPI_ISteamRemotePlay_GetSessionClientFormFactor( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); STEAMAPI_API steam_bool SteamAPI_ISteamRemotePlay_BGetSessionClientResolution( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID, int * pnResolutionX, int * pnResolutionY ); diff --git a/sdk/steam/steamnetworkingtypes.h b/sdk/steam/steamnetworkingtypes.h index a1d9f53b4..02f023e53 100644 --- a/sdk/steam/steamnetworkingtypes.h +++ b/sdk/steam/steamnetworkingtypes.h @@ -807,8 +807,17 @@ struct SteamNetConnectionRealTimeStatus_t /// Nagle delay is ignored for the purposes of this calculation. SteamNetworkingMicroseconds m_usecQueueTime; + /// Highest packet jitter experienced, since the last time this information + /// was returned. (The high water mark is cleared each time you fetch the info.) + /// + /// - The units are microseconds, although the measurement precision is usually + /// not nearly this precise. + /// - A negative value means "no data available". + /// - Not all connections are able to measure jitter. + int32 m_usecMaxJitter; + // Internal stuff, room to change API easily - uint32 reserved[16]; + uint32 reserved[15]; }; #define SteamNetworkingQuickConnectionStatus SteamNetConnectionRealTimeStatus_t @@ -1696,12 +1705,24 @@ enum ESteamNetworkingConfigValue k_ESteamNetworkingConfig_LogLevel_P2PRendezvous = 17, // [connection int32] P2P rendezvous messages k_ESteamNetworkingConfig_LogLevel_SDRRelayPings = 18, // [global int32] Ping relays - // Experimental. Set the ECN header field on all outbound UDP packets - // -1 = the default, and means "don't set anything". - // 0..3 = set that value. (Even though 0 is the default UDP ECN value, a 0 here means "explicitly set a 0".) +// +// Experimental values. These are subject to be deleted or change at any time, +// do not set them, except as a result of an explicit and advanced user opt-in, +// and do not write anything that depends on them existing, or have any particular +// behaviour. +// + // [global int32] ECN value to send in every packet. + // -1 = The default, and means "auto". We will set ECN=1, if it appears that the local internet connection appears to understand it and there may be some benefit. + // 0..2 = use that value. k_ESteamNetworkingConfig_ECN = 999, - // Deleted, do not use + // [global int32] If true, send and request different TOS values in probes to relays + // to try to deduce if there is any bleaching or mutating of the TOS field in either direction + k_ESteamNetworkingConfig_SDRClient_EnableTOSProbes = 998, + +// +// Deleted, do not use +// k_ESteamNetworkingConfig_DELETED_EnumerateDevVars = 35, k_ESteamNetworkingConfigValue__Force32Bit = 0x7fffffff From a1215bef9c4a0e97b46a54095c1dab3b09766352 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Fri, 13 Mar 2026 17:33:20 +0100 Subject: [PATCH 050/159] set ISteamApps to ISteamApps008 in steam old --- steam_old_lib/steam_old_lib.cpp | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/steam_old_lib/steam_old_lib.cpp b/steam_old_lib/steam_old_lib.cpp index d0515f0d1..8b1f887e7 100644 --- a/steam_old_lib/steam_old_lib.cpp +++ b/steam_old_lib/steam_old_lib.cpp @@ -40,19 +40,7 @@ constexpr const static char SOLD_ITF_NAME_FRIENDS[] = "SteamFriends017"; static ISteamClient017 *sold_steam_client = nullptr; static ISteamUtils009 *sold_steam_utils = nullptr; static ISteamUser019 *sold_steam_user = nullptr; -static ISteamApps *sold_steam_apps = nullptr; // TODO at that time ISteamApps == ISteamApps008 (update this when the interface vesion changes) - -static_assert( - sizeof(STEAMAPPS_INTERFACE_VERSION) == sizeof(SOLD_ITF_NAME_APPS) - && STEAMAPPS_INTERFACE_VERSION[sizeof(STEAMAPPS_INTERFACE_VERSION) - 1] == SOLD_ITF_NAME_APPS[sizeof(SOLD_ITF_NAME_APPS) - 1] - && STEAMAPPS_INTERFACE_VERSION[sizeof(STEAMAPPS_INTERFACE_VERSION) - 2] == SOLD_ITF_NAME_APPS[sizeof(SOLD_ITF_NAME_APPS) - 2] - && STEAMAPPS_INTERFACE_VERSION[sizeof(STEAMAPPS_INTERFACE_VERSION) - 3] == SOLD_ITF_NAME_APPS[sizeof(SOLD_ITF_NAME_APPS) - 3] - && STEAMAPPS_INTERFACE_VERSION[sizeof(STEAMAPPS_INTERFACE_VERSION) - 4] == SOLD_ITF_NAME_APPS[sizeof(SOLD_ITF_NAME_APPS) - 4] - , - "ISteamApps interface was updated,\n" - "change the variable definition to a specific version,\n" - "ex: ISteamApps008" -); +static ISteamApps008 *sold_steam_apps = nullptr; static sold::steamclient_loader_t *steamclient_loader = nullptr; static CreateInterface_t ptrCreateInterface = nullptr; From 4118e536abf43b5bdba9b735325abc84c42d780d Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 14 Mar 2026 06:53:42 +0300 Subject: [PATCH 051/159] Update default_items.json example --- .../steam_settings.EXAMPLE/default_items.EXAMPLE.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/post_build/steam_settings.EXAMPLE/default_items.EXAMPLE.json b/post_build/steam_settings.EXAMPLE/default_items.EXAMPLE.json index 34db32785..00ef7cd7b 100644 --- a/post_build/steam_settings.EXAMPLE/default_items.EXAMPLE.json +++ b/post_build/steam_settings.EXAMPLE/default_items.EXAMPLE.json @@ -1,4 +1,10 @@ { - "2001": 1, - "2002": 1 + "1": { + "definition": 2001, + "quantity": 1 + }, + "2": { + "definition": 2002, + "quantity": 1 + } } \ No newline at end of file From 2ba3f33172b20cda5adf03c9fcd376c926fac7eb Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 14 Mar 2026 06:54:21 +0300 Subject: [PATCH 052/159] Restore exception handling in items.json parser --- dll/steam_inventory.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/dll/steam_inventory.cpp b/dll/steam_inventory.cpp index b00c389db..e8bd2fd79 100644 --- a/dll/steam_inventory.cpp +++ b/dll/steam_inventory.cpp @@ -161,8 +161,13 @@ bool Steam_Inventory::GetResultItems( SteamInventoryResult_t resultHandle, for( auto i = user_items.begin(); i != user_items.end() && max_items; ++i, --max_items ) { pOutItemsArray->m_itemId = std::stoi(i.key()); - pOutItemsArray->m_iDefinition = i->value("definition", static_cast(pOutItemsArray->m_itemId)); - pOutItemsArray->m_unQuantity = i->value("quantity", static_cast(0)); + try { + pOutItemsArray->m_iDefinition = i->value("definition", static_cast(pOutItemsArray->m_itemId)); + pOutItemsArray->m_unQuantity = i->value("quantity", static_cast(1)); + } catch (...) { + pOutItemsArray->m_iDefinition = pOutItemsArray->m_itemId; + pOutItemsArray->m_unQuantity = 1; + } pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; ++pOutItemsArray; } @@ -172,8 +177,13 @@ bool Steam_Inventory::GetResultItems( SteamInventoryResult_t resultHandle, auto it = user_items.find(std::to_string(itemid)); if (it != user_items.end()) { pOutItemsArray->m_itemId = itemid; - pOutItemsArray->m_iDefinition = it->value("definition", static_cast(itemid)); - pOutItemsArray->m_unQuantity = it->value("quantity", static_cast(0)); + try { + pOutItemsArray->m_iDefinition = it->value("definition", static_cast(pOutItemsArray->m_itemId)); + pOutItemsArray->m_unQuantity = it->value("quantity", static_cast(1)); + } catch (...) { + pOutItemsArray->m_iDefinition = pOutItemsArray->m_itemId; + pOutItemsArray->m_unQuantity = 1; + } pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; ++pOutItemsArray; --max_items; From 587efc029cbc877b8b846e294c3f2b8c261991b5 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:43:44 +0100 Subject: [PATCH 053/159] Add check before trying with steam_settings folder --- dll/settings_parser.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 870f5cc65..6a3424cac 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -573,12 +573,14 @@ static uint32 parse_steam_app_id(const std::string &program_path) } // try steam_settings folder - char array[10] = {}; - array[0] = '0'; - Local_Storage::get_file_data(Local_Storage::get_game_settings_path() + "steam_appid.txt", array, sizeof(array) - 1); - try { - appid = std::stoul(array); - } catch (...) {} + if (!appid) { + char array[10] = {}; + array[0] = '0'; + Local_Storage::get_file_data(Local_Storage::get_game_settings_path() + "steam_appid.txt", array, sizeof(array) - 1); + try { + appid = std::stoul(array); + } catch (...) {} + } // try current dir if (!appid) { From 4f3501b11e621d950107f56165daed5aa8e361a3 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:49:28 +0100 Subject: [PATCH 054/159] Fix array not exists yet --- dll/settings_parser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 6a3424cac..18860603b 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -573,9 +573,9 @@ static uint32 parse_steam_app_id(const std::string &program_path) } // try steam_settings folder + char array[10] = {}; + array[0] = '0'; if (!appid) { - char array[10] = {}; - array[0] = '0'; Local_Storage::get_file_data(Local_Storage::get_game_settings_path() + "steam_appid.txt", array, sizeof(array) - 1); try { appid = std::stoul(array); From bd882d1c85c8370cf37ee72e8c5d4e8f5e41eb51 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Sun, 15 Mar 2026 11:53:58 +0100 Subject: [PATCH 055/159] Remove if check for env var was pointless --- dll/settings_parser.cpp | 70 ++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 18860603b..3489f0ad2 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -524,52 +524,50 @@ static uint32 parse_steam_app_id(const std::string &program_path) uint32 appid = 0; // try env vars - if (!appid) { - std::string str_appid = get_env_variable("SteamAppId"); - std::string str_gameid = get_env_variable("SteamGameId"); - std::string str_overlay_gameid = get_env_variable("SteamOverlayGameId"); + std::string str_appid = get_env_variable("SteamAppId"); + std::string str_gameid = get_env_variable("SteamGameId"); + std::string str_overlay_gameid = get_env_variable("SteamOverlayGameId"); - PRINT_DEBUG("str_appid %s str_gameid: %s str_overlay_gameid: %s", str_appid.c_str(), str_gameid.c_str(), str_overlay_gameid.c_str()); - uint32 appid_env = 0; - uint32 gameid_env = 0; - uint32 overlay_gameid = 0; + PRINT_DEBUG("str_appid %s str_gameid: %s str_overlay_gameid: %s", str_appid.c_str(), str_gameid.c_str(), str_overlay_gameid.c_str()); + uint32 appid_env = 0; + uint32 gameid_env = 0; + uint32 overlay_gameid = 0; - if (str_appid.size() > 0) { - try { - appid_env = std::stoul(str_appid); - } catch (...) { - appid_env = 0; - } + if (str_appid.size() > 0) { + try { + appid_env = std::stoul(str_appid); + } catch (...) { + appid_env = 0; } + } - if (str_gameid.size() > 0) { - try { - gameid_env = std::stoul(str_gameid); - } catch (...) { - gameid_env = 0; - } + if (str_gameid.size() > 0) { + try { + gameid_env = std::stoul(str_gameid); + } catch (...) { + gameid_env = 0; } + } - if (str_overlay_gameid.size() > 0) { - try { - overlay_gameid = std::stoul(str_overlay_gameid); - } catch (...) { - overlay_gameid = 0; - } + if (str_overlay_gameid.size() > 0) { + try { + overlay_gameid = std::stoul(str_overlay_gameid); + } catch (...) { + overlay_gameid = 0; } + } - PRINT_DEBUG("appid_env %u gameid_env: %u overlay_gameid: %u", appid_env, gameid_env, overlay_gameid); - if (appid_env) { - appid = appid_env; - } + PRINT_DEBUG("appid_env %u gameid_env: %u overlay_gameid: %u", appid_env, gameid_env, overlay_gameid); + if (appid_env) { + appid = appid_env; + } - if (gameid_env) { - appid = gameid_env; - } + if (gameid_env) { + appid = gameid_env; + } - if (overlay_gameid) { - appid = overlay_gameid; - } + if (overlay_gameid) { + appid = overlay_gameid; } // try steam_settings folder From 195baa522bac9f5a00e5ac8388947c0441509437 Mon Sep 17 00:00:00 2001 From: Anthony Beaumont Date: Sun, 15 Mar 2026 21:25:23 +0700 Subject: [PATCH 056/159] Trying to add an override for controller type (SteamInput) --- dll/settings_parser.cpp | 9 +++++++++ dll/steam_controller.cpp | 11 +++++++++++ .../steam_settings.EXAMPLE/configs.app.EXAMPLE.ini | 5 +++++ 3 files changed, 25 insertions(+) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 3489f0ad2..6ece01eb3 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -518,6 +518,14 @@ static void load_gamecontroller_settings(Settings *settings) settings->glyphs_directory = path + (PATH_SEPARATOR "glyphs" PATH_SEPARATOR); } +//controller type override +static void parse_controller_type_override(class Settings *settings_client) +{ + std::string type(common_helpers::to_upper(common_helpers::string_strip(ini.GetValue("app::controller", "type", "")))); + settings_client->controller_type_override = type; + PRINT_DEBUG("Setting Controller type override to: '%s'", type.c_str()); +} + // steam_appid.txt static uint32 parse_steam_app_id(const std::string &program_path) { @@ -1945,6 +1953,7 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s parse_mods_folder(settings_client, settings_server, local_storage); load_gamecontroller_settings(settings_client); + parse_controller_type_override(settings_client); parse_auto_accept_invite(settings_client, settings_server); parse_auto_send_invite(settings_client, settings_server); parse_ip_country(local_storage, settings_client, settings_server); diff --git a/dll/steam_controller.cpp b/dll/steam_controller.cpp index 27c15e813..d7dfa388a 100644 --- a/dll/steam_controller.cpp +++ b/dll/steam_controller.cpp @@ -1086,6 +1086,17 @@ ESteamInputType Steam_Controller::GetInputTypeForHandle( ControllerHandle_t cont PRINT_DEBUG("%llu", controllerHandle); auto controller = controllers.find(controllerHandle); if (controller == controllers.end()) return k_ESteamInputType_Unknown; + + // Playstation + if (settings->controller_type_override == "PS3") return k_ESteamInputType_PS3Controller; + if (settings->controller_type_override == "PS4") return k_ESteamInputType_PS4Controller; + if (settings->controller_type_override == "PS5") return k_ESteamInputType_PS5Controller; + // Xbox + if (settings->controller_type_override == "XBOX360") return k_ESteamInputType_XBox360Controller; + if (settings->controller_type_override == "XBOXONE") return k_ESteamInputType_XBoxOneController; + // Nintendo + if (settings->controller_type_override == "SWITCHPRO") return k_ESteamInputType_SwitchProController; + return k_ESteamInputType_XBox360Controller; } diff --git a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini index f71a15ea0..2d7e6fca4 100644 --- a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini @@ -23,6 +23,11 @@ unlock_all=0 1234=DLCNAME 56789=This is another example DLC name +[app::controller] +# Controller type reported to SteamInput (override) +# XBOX360 (default) | XBOXONE | PS3 | PS4 | PS5 | SWITCHPRO +type=XBOX360 + [app::paths] # some rare games might need to be provided one or more paths to appids # for example the path to where a DLC is installed From 6a58b584852089cb476c7b96030755d47be48910 Mon Sep 17 00:00:00 2001 From: Anthony Beaumont Date: Sun, 15 Mar 2026 21:54:05 +0700 Subject: [PATCH 057/159] oops --- dll/dll/settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dll/dll/settings.h b/dll/dll/settings.h index a79031088..13ad7c139 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -322,7 +322,7 @@ class Settings { //controller struct Controller_Settings controller_settings{}; std::string glyphs_directory{}; - + std::string controller_type_override; // allow Steam_User_Stats::FindLeaderboard() to always succeed and create the given unknown leaderboard bool disable_leaderboards_create_unknown = false; From 9178a45c1c22a79b1f0cd38f5cc36bb2331a8dea Mon Sep 17 00:00:00 2001 From: Anthony Beaumont Date: Mon, 16 Mar 2026 09:56:18 +0700 Subject: [PATCH 058/159] Reuse existing settings->controller_settings --- dll/dll/settings.h | 2 +- dll/settings_parser.cpp | 9 ++++----- dll/steam_controller.cpp | 12 ++++++------ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/dll/dll/settings.h b/dll/dll/settings.h index 13ad7c139..5ed9b8a1d 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -109,6 +109,7 @@ struct Controller_Settings { std::map, std::string>>> action_sets{}; std::map action_set_layer_parents{}; std::map, std::string>>> action_set_layers{}; + std::string controller_type_override; }; struct Group_Clans { @@ -322,7 +323,6 @@ class Settings { //controller struct Controller_Settings controller_settings{}; std::string glyphs_directory{}; - std::string controller_type_override; // allow Steam_User_Stats::FindLeaderboard() to always succeed and create the given unknown leaderboard bool disable_leaderboards_create_unknown = false; diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 6ece01eb3..da8ef61aa 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -31,7 +31,6 @@ constexpr const static char config_ini_overlay[] = "configs.overlay.ini"; constexpr const static char config_ini_user[] = "configs.user.ini"; static CSimpleIniA ini{}; - typedef struct IniValue { enum class Type { STR, @@ -518,11 +517,11 @@ static void load_gamecontroller_settings(Settings *settings) settings->glyphs_directory = path + (PATH_SEPARATOR "glyphs" PATH_SEPARATOR); } -//controller type override -static void parse_controller_type_override(class Settings *settings_client) +// controller +static void parse_controller_config(class Settings *settings_client) { std::string type(common_helpers::to_upper(common_helpers::string_strip(ini.GetValue("app::controller", "type", "")))); - settings_client->controller_type_override = type; + settings->controller_settings.controller_type_override = type; PRINT_DEBUG("Setting Controller type override to: '%s'", type.c_str()); } @@ -1953,7 +1952,7 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s parse_mods_folder(settings_client, settings_server, local_storage); load_gamecontroller_settings(settings_client); - parse_controller_type_override(settings_client); + parse_controller_config(settings_client); parse_auto_accept_invite(settings_client, settings_server); parse_auto_send_invite(settings_client, settings_server); parse_ip_country(local_storage, settings_client, settings_server); diff --git a/dll/steam_controller.cpp b/dll/steam_controller.cpp index d7dfa388a..f8f0b3274 100644 --- a/dll/steam_controller.cpp +++ b/dll/steam_controller.cpp @@ -1088,14 +1088,14 @@ ESteamInputType Steam_Controller::GetInputTypeForHandle( ControllerHandle_t cont if (controller == controllers.end()) return k_ESteamInputType_Unknown; // Playstation - if (settings->controller_type_override == "PS3") return k_ESteamInputType_PS3Controller; - if (settings->controller_type_override == "PS4") return k_ESteamInputType_PS4Controller; - if (settings->controller_type_override == "PS5") return k_ESteamInputType_PS5Controller; + if (settings->controller_settings.controller_type_override == "PS3") return k_ESteamInputType_PS3Controller; + if (settings->controller_settings.controller_type_override == "PS4") return k_ESteamInputType_PS4Controller; + if (settings->controller_settings.controller_type_override == "PS5") return k_ESteamInputType_PS5Controller; // Xbox - if (settings->controller_type_override == "XBOX360") return k_ESteamInputType_XBox360Controller; - if (settings->controller_type_override == "XBOXONE") return k_ESteamInputType_XBoxOneController; + if (settings->controller_settings.controller_type_override == "XBOX360") return k_ESteamInputType_XBox360Controller; + if (settings->controller_settings.controller_type_override == "XBOXONE") return k_ESteamInputType_XBoxOneController; // Nintendo - if (settings->controller_type_override == "SWITCHPRO") return k_ESteamInputType_SwitchProController; + if (settings->controller_settings.controller_type_override == "SWITCHPRO") return k_ESteamInputType_SwitchProController; return k_ESteamInputType_XBox360Controller; } From 1dd99c7f03eaa055ab516cf45c66476b1acc5d08 Mon Sep 17 00:00:00 2001 From: Anthony Beaumont Date: Mon, 16 Mar 2026 10:20:48 +0700 Subject: [PATCH 059/159] Add SteamInput enable switch This remove the need of creating dummy controller action files to enable SteamInput --- dll/dll/settings.h | 1 + dll/settings_parser.cpp | 7 ++++++- dll/steam_controller.cpp | 2 +- post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini | 7 ++++++- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/dll/dll/settings.h b/dll/dll/settings.h index 5ed9b8a1d..b3b3f8ef6 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -110,6 +110,7 @@ struct Controller_Settings { std::map action_set_layer_parents{}; std::map, std::string>>> action_set_layers{}; std::string controller_type_override; + bool enabled; }; struct Group_Clans { diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index da8ef61aa..fc5acfca0 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -521,8 +521,13 @@ static void load_gamecontroller_settings(Settings *settings) static void parse_controller_config(class Settings *settings_client) { std::string type(common_helpers::to_upper(common_helpers::string_strip(ini.GetValue("app::controller", "type", "")))); - settings->controller_settings.controller_type_override = type; + settings_client->controller_settings.controller_type_override = type; PRINT_DEBUG("Setting Controller type override to: '%s'", type.c_str()); + + bool enabled = ini.GetBoolValue("app::controller", "steam_input", false); + settings_client->controller_settings.enabled = enabled; + if (enabled) PRINT_DEBUG("Enable SteamInput"); + else PRINT_DEBUG("Disable SteamInput"); } // steam_appid.txt diff --git a/dll/steam_controller.cpp b/dll/steam_controller.cpp index f8f0b3274..ea7b7ae4d 100644 --- a/dll/steam_controller.cpp +++ b/dll/steam_controller.cpp @@ -221,7 +221,7 @@ Steam_Controller::Steam_Controller(class Settings *settings, class SteamCallResu this->run_every_runcb = run_every_runcb; set_handles(settings->controller_settings.action_sets); - disabled = action_handles.empty(); + disabled = !settings->controller_settings.enabled; initialized = false; this->run_every_runcb->add(&Steam_Controller::steam_run_every_runcb, this); diff --git a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini index 2d7e6fca4..4d54f253b 100644 --- a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini @@ -24,7 +24,12 @@ unlock_all=0 56789=This is another example DLC name [app::controller] -# Controller type reported to SteamInput (override) +# 1=Enable SteamInput +# 0=Disable SteamInput +# default=0 +steam_input=0 + +# Controller type reported to SteamInput # XBOX360 (default) | XBOXONE | PS3 | PS4 | PS5 | SWITCHPRO type=XBOX360 From c96a88bc4a7212a823b9543a223e2fe93eef7ca4 Mon Sep 17 00:00:00 2001 From: Anthony Beaumont Date: Mon, 16 Mar 2026 16:03:09 +0700 Subject: [PATCH 060/159] Small changes --- dll/settings_parser.cpp | 8 ++++++-- dll/steam_controller.cpp | 2 +- post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index fc5acfca0..ad4eb7e11 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -526,8 +526,12 @@ static void parse_controller_config(class Settings *settings_client) bool enabled = ini.GetBoolValue("app::controller", "steam_input", false); settings_client->controller_settings.enabled = enabled; - if (enabled) PRINT_DEBUG("Enable SteamInput"); - else PRINT_DEBUG("Disable SteamInput"); + if (enabled) { + PRINT_DEBUG("Enable SteamInput"); + } + else { + PRINT_DEBUG("Disable SteamInput"); + } } // steam_appid.txt diff --git a/dll/steam_controller.cpp b/dll/steam_controller.cpp index ea7b7ae4d..ec1578b81 100644 --- a/dll/steam_controller.cpp +++ b/dll/steam_controller.cpp @@ -1095,7 +1095,7 @@ ESteamInputType Steam_Controller::GetInputTypeForHandle( ControllerHandle_t cont if (settings->controller_settings.controller_type_override == "XBOX360") return k_ESteamInputType_XBox360Controller; if (settings->controller_settings.controller_type_override == "XBOXONE") return k_ESteamInputType_XBoxOneController; // Nintendo - if (settings->controller_settings.controller_type_override == "SWITCHPRO") return k_ESteamInputType_SwitchProController; + if (settings->controller_settings.controller_type_override == "SWITCH") return k_ESteamInputType_SwitchProController; return k_ESteamInputType_XBox360Controller; } diff --git a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini index 4d54f253b..e1b447b46 100644 --- a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini @@ -30,7 +30,7 @@ unlock_all=0 steam_input=0 # Controller type reported to SteamInput -# XBOX360 (default) | XBOXONE | PS3 | PS4 | PS5 | SWITCHPRO +# XBOX360 (default) | XBOXONE | PS3 | PS4 | PS5 | SWITCH type=XBOX360 [app::paths] From dbd09e4d497aff66860c570778dea2e302c9520f Mon Sep 17 00:00:00 2001 From: Anthony Beaumont Date: Wed, 18 Mar 2026 10:29:13 +0700 Subject: [PATCH 061/159] Restore action_handles check for backward compatibility --- dll/dll/settings.h | 4 ++-- dll/steam_controller.cpp | 2 +- post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dll/dll/settings.h b/dll/dll/settings.h index b3b3f8ef6..ea301d334 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -109,8 +109,8 @@ struct Controller_Settings { std::map, std::string>>> action_sets{}; std::map action_set_layer_parents{}; std::map, std::string>>> action_set_layers{}; - std::string controller_type_override; - bool enabled; + std::string controller_type_override{}; + bool enabled{}; }; struct Group_Clans { diff --git a/dll/steam_controller.cpp b/dll/steam_controller.cpp index ec1578b81..b6c40b5b9 100644 --- a/dll/steam_controller.cpp +++ b/dll/steam_controller.cpp @@ -221,7 +221,7 @@ Steam_Controller::Steam_Controller(class Settings *settings, class SteamCallResu this->run_every_runcb = run_every_runcb; set_handles(settings->controller_settings.action_sets); - disabled = !settings->controller_settings.enabled; + disabled = !settings->controller_settings.enabled && action_handles.empty(); initialized = false; this->run_every_runcb->add(&Steam_Controller::steam_run_every_runcb, this); diff --git a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini index e1b447b46..b436d4ec5 100644 --- a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini @@ -27,6 +27,7 @@ unlock_all=0 # 1=Enable SteamInput # 0=Disable SteamInput # default=0 +# SteamInput is automatically enabled when there are action sets in `steam_settings\controller\` steam_input=0 # Controller type reported to SteamInput From 5ca52f34332dbc9e2238fca39756baa459acacd7 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Mon, 16 Mar 2026 11:50:45 +0300 Subject: [PATCH 062/159] Implement proper delay for SteamGameServer login --- dll/dll/steam_gameserver.h | 11 +++-- dll/steam_gameserver.cpp | 84 ++++++++++++++++++-------------------- dll/steam_user.cpp | 10 +++-- 3 files changed, 51 insertions(+), 54 deletions(-) diff --git a/dll/dll/steam_gameserver.h b/dll/dll/steam_gameserver.h index 4921b666d..519ba7399 100644 --- a/dll/dll/steam_gameserver.h +++ b/dll/dll/steam_gameserver.h @@ -57,16 +57,15 @@ public ISteamGameServer class Networking *network{}; class SteamCallBacks *callbacks{}; - CSteamID steam_id{}; - - bool call_servers_connected = false; - bool logged_in = false; - bool call_servers_disconnected = false; + bool call_servers_connected{}; + std::chrono::high_resolution_clock::time_point logon_time{}; + bool call_servers_disconnected{}; + std::chrono::high_resolution_clock::time_point logoff_time{}; + bool logged_in{}; Gameserver server_data{}; std::vector> players{}; uint32 flags{}; - bool policy_response_called{}; std::chrono::high_resolution_clock::time_point last_sent_server_info{}; Auth_Manager *auth_manager{}; diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index 5700f0ade..5dc4a9604 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -180,7 +180,6 @@ bool Steam_GameServer::InitGameServer( uint32 unIP, uint16 usGamePort, uint16 us if (!settings->disable_source_query) network->startQuery({ unIP, usQueryPort }); - policy_response_called = false; call_servers_connected = false; call_servers_disconnected = false; @@ -248,8 +247,7 @@ void Steam_GameServer::LogOn( const char *pszToken ) std::lock_guard lock(global_mutex); call_servers_connected = true; call_servers_disconnected = false; - logged_in = true; - settings->set_offline(false); + logon_time = std::chrono::high_resolution_clock::now(); } void Steam_GameServer::LogOn( @@ -271,8 +269,7 @@ void Steam_GameServer::LogOnAnonymous() std::lock_guard lock(global_mutex); call_servers_connected = true; call_servers_disconnected = false; - logged_in = true; - settings->set_offline(false); + logon_time = std::chrono::high_resolution_clock::now(); } void Steam_GameServer::LogOn() @@ -289,11 +286,16 @@ void Steam_GameServer::LogOff() if (logged_in) { call_servers_connected = false; call_servers_disconnected = true; - } + logoff_time = std::chrono::high_resolution_clock::now(); + + logged_in = false; + settings->set_offline(true); - policy_response_called = false; - logged_in = false; - settings->set_offline(true); + // Shutdown Source Query + network->shutDownQuery(); + // And empty the queue if needed + outgoing_packets.clear(); + } } @@ -309,7 +311,7 @@ bool Steam_GameServer::BSecure() { PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - if (!policy_response_called) { + if (!logged_in) { server_data.set_secure(0); return false; } @@ -322,7 +324,7 @@ CSteamID Steam_GameServer::GetSteamID() { PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - if (!logged_in) return k_steamIDNil; + if (!logged_in) return CSteamID(0, 0, k_EUniversePublic, k_EAccountTypeAnonGameServer); // blank anon server id return settings->get_local_steam_id(); } @@ -597,7 +599,6 @@ bool Steam_GameServer::BSetServerType( uint32 unServerFlags, uint32 unGameIP, ui if (!settings->disable_source_query && !network->isQueryAlive()) network->startQuery({ unGameIP, usQueryPort }); - //TODO? return true; } @@ -907,22 +908,37 @@ SteamAPICall_t Steam_GameServer::ComputeNewPlayerCompatibility( CSteamID steamID void Steam_GameServer::RunCallbacks() { - bool temp_call_servers_connected = call_servers_connected; - bool temp_call_servers_disconnected = call_servers_disconnected; - call_servers_disconnected = call_servers_connected = false; - - if (temp_call_servers_connected) { + if (call_servers_connected && check_timedout(logon_time, 0.1)) { PRINT_DEBUG("SteamServersConnected_t"); + + call_servers_connected = false; + logged_in = true; + settings->set_offline(false); + SteamServersConnected_t data{}; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); - } + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.0); - if (logged_in && !policy_response_called) { PRINT_DEBUG("GSPolicyResponse_t"); - GSPolicyResponse_t data{}; - data.m_bSecure = !!(flags & k_unServerFlagSecure); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.11); - policy_response_called = true; + GSPolicyResponse_t data2{}; + data2.m_bSecure = !!(flags & k_unServerFlagSecure); + callbacks->addCBResult(data2.k_iCallback, &data2, sizeof(data2), 0.1); + } + + if (call_servers_disconnected && check_timedout(logoff_time, 0.1)) { + PRINT_DEBUG("Gameserver is disconnected"); + + call_servers_disconnected = false; + + SteamServersDisconnected_t data{}; + data.m_eResult = k_EResultOK; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.0); + + PRINT_DEBUG("notifying all that Gameserver is not logged in"); + Common_Message msg{}; + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_allocated_gameserver(new Gameserver(server_data)); + msg.mutable_gameserver()->set_offline(true); + network->sendToAllIndividuals(&msg, true); } if (logged_in && @@ -936,26 +952,6 @@ void Steam_GameServer::RunCallbacks() network->sendToAllIndividuals(&msg, true); last_sent_server_info = std::chrono::high_resolution_clock::now(); } - - if (temp_call_servers_disconnected) { - PRINT_DEBUG("Gameserver is disconnected"); - SteamServersDisconnected_t data{}; - data.m_eResult = k_EResultOK; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - - if (!logged_in) { - PRINT_DEBUG("notifying all that Gameserver is not logged in"); - Common_Message msg{}; - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_allocated_gameserver(new Gameserver(server_data)); - msg.mutable_gameserver()->set_offline(true); - network->sendToAllIndividuals(&msg, true); - // Shutdown Source Query - network->shutDownQuery(); - // And empty the queue if needed - outgoing_packets.clear(); - } - } } diff --git a/dll/steam_user.cpp b/dll/steam_user.cpp index acdae4ff9..f94faaf45 100644 --- a/dll/steam_user.cpp +++ b/dll/steam_user.cpp @@ -50,10 +50,9 @@ HSteamUser Steam_User::GetHSteamUser() void Steam_User::LogOn( CSteamID steamID ) { PRINT_DEBUG_ENTRY(); - settings->set_offline(false); - logon_time = std::chrono::high_resolution_clock::now(); call_logged_on = true; call_logged_off = false; + logon_time = std::chrono::high_resolution_clock::now(); if (settings == get_steam_client()->settings_server) { get_steam_client()->steam_gameserver->LogOnAnonymous(); @@ -63,10 +62,11 @@ void Steam_User::LogOn( CSteamID steamID ) void Steam_User::LogOff() { PRINT_DEBUG_ENTRY(); - settings->set_offline(true); - logoff_time = std::chrono::high_resolution_clock::now(); call_logged_on = false; call_logged_off = true; + logoff_time = std::chrono::high_resolution_clock::now(); + + settings->set_offline(true); player_auths.clear(); if (settings == get_steam_client()->settings_server) { @@ -1107,6 +1107,7 @@ void Steam_User::RunCallbacks() if (callbacks_old1) { if (call_logged_on && check_timedout(logon_time, 0.1)) { PRINT_DEBUG("ICMCallback -> OnLogonSuccess"); + settings->set_offline(false); callbacks_old1->OnLogonSuccess(); call_logged_on = false; } @@ -1129,6 +1130,7 @@ void Steam_User::RunCallbacks() } else if (callbacks_old2) { if (call_logged_on && check_timedout(logon_time, 0.1)) { PRINT_DEBUG("ICMCallback -> OnLogonSuccess"); + settings->set_offline(false); callbacks_old2->OnLogonSuccess(); call_logged_on = false; } From 8bdf3e5d3e98a05286fc836f7cce0a264f118720 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Mon, 16 Mar 2026 12:52:53 +0300 Subject: [PATCH 063/159] Send player info with game server info --- dll/net.proto | 8 ++++++++ dll/source_query.cpp | 4 +++- dll/steam_gameserver.cpp | 12 +++++++++++- dll/steam_matchmaking_servers.cpp | 15 +++++++-------- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/dll/net.proto b/dll/net.proto index 7e98a7369..507d09ac0 100644 --- a/dll/net.proto +++ b/dll/net.proto @@ -143,6 +143,12 @@ message Networking_Messages { } message Gameserver { + message PlayerInfo { + string name = 1; + uint32 score = 2; + float playtime = 3; + } + uint64 id = 1; bytes game_description = 2; bytes mod_dir = 3; @@ -171,6 +177,8 @@ message Gameserver { bool offline = 48; uint32 type = 49; + + repeated PlayerInfo players = 50; } message Friend { diff --git a/dll/source_query.cpp b/dll/source_query.cpp index e2eaf4259..37fdd3a43 100644 --- a/dll/source_query.cpp +++ b/dll/source_query.cpp @@ -206,11 +206,13 @@ std::vector Source_Query::handle_source_query(const void* buffer, size_ serialize_response(output_buffer, source_response_header::A2S_PLAYER); serialize_response(output_buffer, static_cast(players.size())); // num_players + auto cur_time = std::chrono::steady_clock::now(); + for (unsigned 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())); + serialize_response(output_buffer, std::chrono::duration(cur_time - players[i].second.join_time).count()); } } } diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index 5dc4a9604..c3d83534c 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -948,7 +948,17 @@ void Steam_GameServer::RunCallbacks() Common_Message msg{}; msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); msg.set_allocated_gameserver(new Gameserver(server_data)); - //msg.mutable_gameserver()->set_num_players(auth_manager->countInboundAuth()); + + // Fill player list and calculate their playtime. + auto cur_time = std::chrono::steady_clock::now(); + + for (const auto &[steam_id, info] : players) { + auto new_player = msg.mutable_gameserver()->add_players(); + new_player->set_name(info.name); + new_player->set_score(info.score); + new_player->set_playtime(std::chrono::duration(cur_time - info.join_time).count()); + } + network->sendToAllIndividuals(&msg, true); last_sent_server_info = std::chrono::high_resolution_clock::now(); } diff --git a/dll/steam_matchmaking_servers.cpp b/dll/steam_matchmaking_servers.cpp index cb7d23036..f6f40f0fe 100644 --- a/dll/steam_matchmaking_servers.cpp +++ b/dll/steam_matchmaking_servers.cpp @@ -15,7 +15,7 @@ License along with the Goldberg Emulator; if not, see . */ -#include "dll/dll.h" +#include "dll/steam_matchmaking_servers.h" #define SERVER_TIMEOUT 10.0 #define DIRECT_IP_DELAY 0.05 @@ -755,13 +755,12 @@ void Steam_Matchmaking_Servers::server_details_players(Gameserver *g, Steam_Matc } else if (!settings->matchmaking_server_details_via_source_query) { // original behavior uint32_t number_players = g->num_players(); PRINT_DEBUG(" players: %u", number_players); - const auto &players = get_steam_client()->steam_gameserver->get_players(); - auto player = players->cbegin(); - for (uint32_t i = 0; i < number_players && player != players->end(); ++i, ++player) { - auto duration = std::chrono::duration_cast(std::chrono::steady_clock::now() - player->second.join_time); - float playtime = static_cast(duration.count()); - PRINT_DEBUG(" PLAYER [%u] '%s' %u %f", i, player->second.name.c_str(), player->second.score, playtime); - r->players_response->AddPlayerToList(player->second.name.c_str(), player->second.score, playtime); + + int player_num = 1; + + for (const auto &player : g->players()) { + PRINT_DEBUG(" PLAYER [%u] '%s' %u %.2f", player_num++, player.name().c_str(), player.score(), player.playtime()); + r->players_response->AddPlayerToList(player.name().c_str(), player.score(), player.playtime()); } } From 949c02a3fa37d7e321aef4c8aa79dcdb7f6e1226 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sat, 21 Mar 2026 09:20:54 +0300 Subject: [PATCH 064/159] Raise auth ticket process time --- dll/auth.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dll/auth.cpp b/dll/auth.cpp index 1d7043450..f38ebe893 100644 --- a/dll/auth.cpp +++ b/dll/auth.cpp @@ -17,7 +17,8 @@ #include "dll/auth.h" -#define STEAM_TICKET_PROCESS_TIME 0.03 +// this should be at least as long as SteamGameServer logon time +#define STEAM_TICKET_PROCESS_TIME 0.15 static inline int generate_random_int() { From 10bca9d8a45dceacc1e170721fa2d5dfd7822c33 Mon Sep 17 00:00:00 2001 From: a Date: Mon, 23 Mar 2026 01:09:06 +0200 Subject: [PATCH 065/159] allow specifying overlay toggle keys via ini --- dll/dll/settings.h | 2 + dll/settings_parser.cpp | 15 ++++ helpers/common_helpers.cpp | 13 ++++ helpers/common_helpers/common_helpers.hpp | 1 + overlay_experimental/overlay/steam_overlay.h | 3 + overlay_experimental/steam_overlay.cpp | 68 +++++++++++++++++-- .../configs.overlay.EXAMPLE.ini | 19 ++++++ 7 files changed, 114 insertions(+), 7 deletions(-) diff --git a/dll/dll/settings.h b/dll/dll/settings.h index a79031088..985b5183d 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -370,6 +370,8 @@ class Settings { bool overlay_always_show_fps = false; bool overlay_always_show_frametime = false; bool overlay_always_show_playtime = false; + // keys used to toggle the overlay, default = Shift + Tab + std::vector overlay_toggle_keys{}; // free weekend bool free_weekend = false; diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 3489f0ad2..7b5f693cc 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -198,6 +198,20 @@ static void load_subscribed_groups_clans(const std::string &base_path, Settings } } +// overlay::hotkeys +static void parse_overlay_hotkeys(class Settings *settings_client, class Settings *settings_server) +{ + // avoid including overlay header here, just save the strings and let the overlay class translate them + auto combo_str = ini.GetValue("overlay::hotkeys", "key_combo"); + if (combo_str && combo_str[0]) { + auto combo = common_helpers::str_split(combo_str, "+"); + std::for_each(combo.begin(), combo.end(), [](std::string &item){ item = common_helpers::str_strip(item); }); + std::erase_if(combo, [](const std::string& item) { return item.empty(); }); + settings_client->overlay_toggle_keys = combo; + settings_server->overlay_toggle_keys = combo; + } +} + // overlay::appearance static void load_overlay_appearance(class Settings *settings_client, class Settings *settings_server, class Local_Storage *local_storage) { @@ -1957,6 +1971,7 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s } parse_overlay_general_config(settings_client, settings_server); + parse_overlay_hotkeys(settings_client, settings_server); load_overlay_appearance(settings_client, settings_server, local_storage); parse_steam_game_stats_reports_dir(settings_client, settings_server); parse_cloud_save(&ini, settings_client, settings_server, local_storage); diff --git a/helpers/common_helpers.cpp b/helpers/common_helpers.cpp index f46cd5be3..0fdbbd8c1 100644 --- a/helpers/common_helpers.cpp +++ b/helpers/common_helpers.cpp @@ -570,3 +570,16 @@ std::vector common_helpers::str_split(std::string_view str, std::st return out; } + +std::string common_helpers::str_strip(std::string_view str) +{ + if (str.empty()) return {}; + + constexpr static const char whitespaces[] = { ' ', '\t', '\r', '\n', 0 }; // null terminator at the end is needed + + const auto start = str.find_first_not_of(whitespaces); + if (std::string::npos == start) return {}; + + const auto end = str.find_last_not_of(whitespaces); + return std::string(str.substr(start, end - start + 1)); +} diff --git a/helpers/common_helpers/common_helpers.hpp b/helpers/common_helpers/common_helpers.hpp index ab0cc433e..15c0992a1 100644 --- a/helpers/common_helpers/common_helpers.hpp +++ b/helpers/common_helpers/common_helpers.hpp @@ -111,5 +111,6 @@ std::string to_str(std::wstring_view wstr); std::string str_replace_all(std::string_view source, std::string_view substr, std::string_view replace, bool case_insensitive = true); size_t str_find(std::string_view str_src, std::string_view str_query, size_t start = 0, bool case_insensitive = true); std::vector str_split(std::string_view str, std::string_view splitter, bool ignore_empty = true, bool case_insensitive = true); +std::string str_strip(std::string_view str); } diff --git a/overlay_experimental/overlay/steam_overlay.h b/overlay_experimental/overlay/steam_overlay.h index 7fcaca8a5..db773caf2 100644 --- a/overlay_experimental/overlay/steam_overlay.h +++ b/overlay_experimental/overlay/steam_overlay.h @@ -167,6 +167,8 @@ class Steam_Overlay common_helpers::KillableWorker renderer_hook_init_thread{}; int renderer_hook_timeout_ctr{}; + std::vector toggle_keys{}; + // font stuff ImFontAtlas fonts_atlas{}; ImFont *font_default{}; @@ -188,6 +190,7 @@ class Steam_Overlay Steam_Overlay& operator=(Steam_Overlay const&) = delete; Steam_Overlay& operator=(Steam_Overlay&&) = delete; + void parse_key_combo(); bool submit_notification( notification_type type, const std::string &msg, diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index f62947f7b..cecc85761 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "InGameOverlay/RendererDetector.h" @@ -32,11 +33,6 @@ static constexpr int base_notif_window_id = 0 * max_window_id; static constexpr int base_friend_window_id = 1 * max_window_id; static constexpr int base_friend_item_id = 2 * max_window_id; -static InGameOverlay::ToggleKey overlay_toggle_keys[] = { - InGameOverlay::ToggleKey::SHIFT, InGameOverlay::ToggleKey::TAB -}; -static const int toggle_keys_count = 2; - // look for the column 'API language code' here: https://partner.steamgames.com/doc/store/localization/languages static constexpr const char* valid_languages[] = { "english", @@ -95,6 +91,63 @@ void Steam_Overlay::overlay_networking_callback(void* object, Common_Message* ms _this->networking_msg_received(msg); } +void Steam_Overlay::parse_key_combo() +{ + std::unordered_set keys_combo{}; + bool use_default = false; + if (settings->overlay_toggle_keys.empty()) { + use_default = true; + } else { + for (const auto &key_name : settings->overlay_toggle_keys) { + if (common_helpers::str_cmp_insensitive("shift", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::SHIFT); + } else if (common_helpers::str_cmp_insensitive("ctrl", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::CTRL); + } else if (common_helpers::str_cmp_insensitive("alt", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::ALT); + } else if (common_helpers::str_cmp_insensitive("tab", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::TAB); + } else if (common_helpers::str_cmp_insensitive("fn1", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::F1); + } else if (common_helpers::str_cmp_insensitive("fn2", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::F2); + } else if (common_helpers::str_cmp_insensitive("fn3", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::F3); + } else if (common_helpers::str_cmp_insensitive("fn4", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::F4); + } else if (common_helpers::str_cmp_insensitive("fn5", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::F5); + } else if (common_helpers::str_cmp_insensitive("fn6", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::F6); + } else if (common_helpers::str_cmp_insensitive("fn7", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::F7); + } else if (common_helpers::str_cmp_insensitive("fn8", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::F8); + } else if (common_helpers::str_cmp_insensitive("fn9", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::F9); + } else if (common_helpers::str_cmp_insensitive("fn10", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::F10); + } else if (common_helpers::str_cmp_insensitive("fn11", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::F11); + } else if (common_helpers::str_cmp_insensitive("fn12", key_name)) { + keys_combo.insert(InGameOverlay::ToggleKey::F12); + } else { + use_default = true; + PRINT_DEBUG("[X] Unknown key '%s', using default key combo Shift + Tab", key_name.c_str()); + break; + } + } + } + + if (use_default) { + toggle_keys = { + InGameOverlay::ToggleKey::SHIFT, InGameOverlay::ToggleKey::TAB + }; + } else { + toggle_keys = std::vector(keys_combo.begin(), keys_combo.end()); + } +} + Steam_Overlay::Steam_Overlay(Settings* settings, Local_Storage *local_storage, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking* network) : settings(settings), local_storage(local_storage), @@ -125,7 +178,8 @@ Steam_Overlay::Steam_Overlay(Settings* settings, Local_Storage *local_storage, S std::chrono::milliseconds(0), [this] { return !setup_overlay_called; } ); - + + parse_key_combo(); strncpy(username_text, settings->get_local_name(), sizeof(username_text)); // we need these copies to show the warning only once, then disable the flag @@ -222,7 +276,7 @@ bool Steam_Overlay::renderer_hook_proc() overlay_state_hook(state == InGameOverlay::OverlayHookState::Ready || state == InGameOverlay::OverlayHookState::Reset); }; - bool started = _renderer->StartHook(overlay_toggle_callback, overlay_toggle_keys, toggle_keys_count, &fonts_atlas); + bool started = _renderer->StartHook(overlay_toggle_callback, toggle_keys.data(), (int)toggle_keys.size(), &fonts_atlas); PRINT_DEBUG("started renderer hook (result=%i)", (int)started); return true; diff --git a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini index 0126a369f..31e03fceb 100644 --- a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini @@ -63,6 +63,25 @@ overlay_always_show_frametime=0 # default=0 overlay_always_show_playtime=0 +[overlay::hotkeys] +# combination of keys used to show/hide the overlay +# available values: +# shift +# ctrl +# alt +# tab +# fn1 +# fn2 +# ... +# ... +# fn12 +# +# you can use the '+' sign to combine multiple keys +# example: ctrl + shift + fn5 +# +# default=shift + tab +key_combo=shift + tab + [overlay::appearance] # load custom TrueType font from a path, it could be absolute, or relative # relative paths will be looked up inside the local folder "steam_settings/fonts" first, From d819b571d192ec63ae24df378087da8a5d1b7bba Mon Sep 17 00:00:00 2001 From: a Date: Mon, 23 Mar 2026 01:25:53 +0200 Subject: [PATCH 066/159] add missing strings to known interfaces --- dll/client_known_interfaces.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dll/client_known_interfaces.cpp b/dll/client_known_interfaces.cpp index 8b06d218f..a67790f46 100644 --- a/dll/client_known_interfaces.cpp +++ b/dll/client_known_interfaces.cpp @@ -42,6 +42,7 @@ extern const std::unordered_set client_known_interfaces = { "STEAMAPPS_INTERFACE_VERSION006", "STEAMAPPS_INTERFACE_VERSION007", "STEAMAPPS_INTERFACE_VERSION008", + "STEAMAPPS_INTERFACE_VERSION009", "STEAMAPPTICKET_INTERFACE_VERSION001", "SteamBilling002", "STEAMCHAT_INTERFACE_VERSION003", @@ -152,6 +153,7 @@ extern const std::unordered_set client_known_interfaces = { "STEAMREMOTEPLAY_INTERFACE_VERSION001", "STEAMREMOTEPLAY_INTERFACE_VERSION002", "STEAMREMOTEPLAY_INTERFACE_VERSION003", + "STEAMREMOTEPLAY_INTERFACE_VERSION004", "STEAMREMOTESTORAGE_INTERFACE_VERSION001", "STEAMREMOTESTORAGE_INTERFACE_VERSION002", "STEAMREMOTESTORAGE_INTERFACE_VERSION003", From a8f9a4328d5939828daea5ef87f1e8da35181c62 Mon Sep 17 00:00:00 2001 From: a Date: Mon, 23 Mar 2026 01:26:35 +0200 Subject: [PATCH 067/159] reduce code duplication in `Steam_Apps::GetBetaInfo()` --- dll/steam_apps.cpp | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/dll/steam_apps.cpp b/dll/steam_apps.cpp index ccf13e54e..30f50206a 100644 --- a/dll/steam_apps.cpp +++ b/dll/steam_apps.cpp @@ -612,36 +612,14 @@ int Steam_Apps::GetNumBetas( int *pnAvailable, int *pnPrivate ) return static_cast(settings->branches.size()); // we always return at least 1 since "public" branch } -// TODO no public docs -// return beta branch details, name, description, current BuildID and state flags (EBetaBranchFlags) -// iterate through bool Steam_Apps::GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuildID, char *pchBetaName, int cchBetaName, char *pchDescription, int cchDescription ) { - // I assume this API is like "Steam_User_Stats::GetNextMostAchievedAchievementInfo()", it returns 'ok' until index is out of range - PRINT_DEBUG("[%i] %p %p --- %p %i --- %p %i", iBetaIndex, punFlags, punBuildID, pchBetaName, cchBetaName, pchDescription, cchDescription); - std::lock_guard lock(global_mutex); - - if (iBetaIndex < 0) return false; - if (static_cast(iBetaIndex) >= settings->branches.size()) return false; - - const auto &branch = settings->branches[iBetaIndex]; - - if (punFlags) *punFlags = branch.flags; - if (punBuildID) *punBuildID = branch.build_id; - - if (pchBetaName && cchBetaName > 0 && static_cast(cchBetaName) > branch.name.size()) { - memset(pchBetaName, 0, cchBetaName); - memcpy(pchBetaName, branch.name.c_str(), branch.name.size()); - } - - if (pchDescription && cchDescription > 0 && static_cast(cchDescription) > branch.description.size()) { - memset(pchDescription, 0, cchDescription); - memcpy(pchDescription, branch.description.c_str(), branch.description.size()); - } - - return true; + PRINT_DEBUG("OLD, before SDK v1.64"); + return GetBetaInfo(iBetaIndex, punFlags, punBuildID, pchBetaName, cchBetaName, pchDescription, cchDescription, nullptr); } +// return beta branch details, name, description, current BuildID and state flags (EBetaBranchFlags) +// iterate through bool Steam_Apps::GetBetaInfo(int iBetaIndex, uint32* punFlags, uint32* punBuildID, char* pchBetaName, int cchBetaName, char* pchDescription, int cchDescription, uint32* punLastUpdated) { // I assume this API is like "Steam_User_Stats::GetNextMostAchievedAchievementInfo()", it returns 'ok' until index is out of range @@ -655,6 +633,7 @@ bool Steam_Apps::GetBetaInfo(int iBetaIndex, uint32* punFlags, uint32* punBuildI if (punFlags) *punFlags = branch.flags; if (punBuildID) *punBuildID = branch.build_id; + // added in SDK 1.64 if (punLastUpdated) *punLastUpdated = branch.time_updated_epoch; if (pchBetaName && cchBetaName > 0 && static_cast(cchBetaName) > branch.name.size()) { From b049e3cd1c960926c62f6c2f96326a0dadebb233 Mon Sep 17 00:00:00 2001 From: a Date: Mon, 23 Mar 2026 01:27:25 +0200 Subject: [PATCH 068/159] dummy implementation for `Steam_UGC::MarkDownloadedItemAsUnused`, `Steam_UGC::GetNumDownloadedItems`, and `Steam_UGC::GetDownloadedItems` --- dll/steam_ugc.cpp | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/dll/steam_ugc.cpp b/dll/steam_ugc.cpp index 15e3f8d27..41f60bf32 100644 --- a/dll/steam_ugc.cpp +++ b/dll/steam_ugc.cpp @@ -16,6 +16,7 @@ . */ #include "dll/steam_ugc.h" +#include "dll/dll.h" UGCQueryHandle_t Steam_UGC::new_ugc_query(EQueryType query_type, bool return_all_subscribed, uint32 page, bool next_cursor, const std::set &return_only) { @@ -1812,9 +1813,12 @@ bool Steam_UGC::SetSubscriptionsLoadOrder( PublishedFileId_t *pvecPublishedFileI // Tells the client to no longer try to keep the item in its local cache, unless it was subscribed to by other users on this machine bool Steam_UGC::MarkDownloadedItemAsUnused(PublishedFileId_t nPublishedFileID) { + PRINT_DEBUG("%llu", nPublishedFileID); + // we don't really have to do anything here, leaving this TODO + // in case we need to keep track of these marked items later PRINT_DEBUG_TODO(); std::lock_guard lock(global_mutex); - return false; + return true; } // Returns the number of items actually downloaded locally @@ -1822,13 +1826,45 @@ uint32 Steam_UGC::GetNumDownloadedItems() { PRINT_DEBUG_TODO(); std::lock_guard lock(global_mutex); - return 0; + + // https://partner.steamgames.com/doc/api/ISteamUGC#GetDownloadedItems + // "Returns 0 if called from a game server" + if (get_steam_client()->settings_server == settings) { + return 0; + } + + return (uint32)settings->modSet().size(); // not sure if returning all mods is correct } // Returns the ids of the items downloaded uint32 Steam_UGC::GetDownloadedItems(PublishedFileId_t* pvecPublishedFileIDs, uint32 cMaxEntries) { + PRINT_DEBUG("%p [%u]", pvecPublishedFileIDs, cMaxEntries); PRINT_DEBUG_TODO(); std::lock_guard lock(global_mutex); - return 0; + + if (!pvecPublishedFileIDs || cMaxEntries == 0) { + return 0; + } + + // https://partner.steamgames.com/doc/api/ISteamUGC#GetDownloadedItems + // "Returns 0 if called from a game server" + if (get_steam_client()->settings_server == settings) { + return 0; + } + + const auto all_mods = settings->modSet(); // not sure if using all mods is correct + uint32 copied = 0; + for (auto mod_it = all_mods.cbegin(); mod_it != all_mods.cend(); ++mod_it) { + if (cMaxEntries == 0) { + break; + } + *pvecPublishedFileIDs = *mod_it; + ++copied; + --cMaxEntries; + ++pvecPublishedFileIDs; + } + + PRINT_DEBUG(" copied count = %u", copied); + return copied; } From db068e45b7acc8d78366b13dae9d2357c208f107 Mon Sep 17 00:00:00 2001 From: a Date: Mon, 23 Mar 2026 01:45:06 +0200 Subject: [PATCH 069/159] thanks c++ --- dll/settings_parser.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 7b5f693cc..85e39b2b4 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -205,8 +205,16 @@ static void parse_overlay_hotkeys(class Settings *settings_client, class Setting auto combo_str = ini.GetValue("overlay::hotkeys", "key_combo"); if (combo_str && combo_str[0]) { auto combo = common_helpers::str_split(combo_str, "+"); - std::for_each(combo.begin(), combo.end(), [](std::string &item){ item = common_helpers::str_strip(item); }); - std::erase_if(combo, [](const std::string& item) { return item.empty(); }); + std::for_each(combo.begin(), combo.end(), + [](std::string &item){ item = common_helpers::str_strip(item); } + ); + combo.erase( + std::remove_if( + combo.begin(), combo.end(), + [](const std::string& item) { return item.empty(); } + ), + combo.end() + ); settings_client->overlay_toggle_keys = combo; settings_server->overlay_toggle_keys = combo; } From dd24848875c3905c635c54c6e2939a5d661ec32b Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Mon, 23 Mar 2026 12:39:22 +0300 Subject: [PATCH 070/159] Always trigger logoff on server shutdown --- dll/dll.cpp | 2 +- dll/steam_client.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index 9f8c4b8eb..ca5b891d7 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -1168,7 +1168,7 @@ STEAMAPI_API void SteamGameServer_Shutdown() return; } - get_steam_client()->serverShutdown(); + get_steam_client()->ReleaseUser(server_steam_pipe, SERVER_HSTEAMUSER); get_steam_client()->BReleaseSteamPipe(server_steam_pipe); get_steam_client()->BShutdownIfAllPipesClosed(); diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index 4263c4db2..05aee2df8 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -399,6 +399,10 @@ void Steam_Client::ReleaseUser( HSteamPipe hSteamPipe, HSteamUser hUser ) { PRINT_DEBUG_ENTRY(); if (hUser == SERVER_HSTEAMUSER && steam_pipes.count(hSteamPipe)) { + if (steam_gameserver->BLoggedOn()) { + steam_gameserver->LogOff(); + } + steamclient_server_inited = false; } } From 564c4034abdd9292f99e018ec23fd04822d4fc28 Mon Sep 17 00:00:00 2001 From: a Date: Mon, 23 Mar 2026 13:43:56 +0200 Subject: [PATCH 071/159] use `std::copy_n` instead of manual loop --- dll/steam_ugc.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/dll/steam_ugc.cpp b/dll/steam_ugc.cpp index 41f60bf32..a0687f429 100644 --- a/dll/steam_ugc.cpp +++ b/dll/steam_ugc.cpp @@ -1854,17 +1854,9 @@ uint32 Steam_UGC::GetDownloadedItems(PublishedFileId_t* pvecPublishedFileIDs, ui } const auto all_mods = settings->modSet(); // not sure if using all mods is correct - uint32 copied = 0; - for (auto mod_it = all_mods.cbegin(); mod_it != all_mods.cend(); ++mod_it) { - if (cMaxEntries == 0) { - break; - } - *pvecPublishedFileIDs = *mod_it; - ++copied; - --cMaxEntries; - ++pvecPublishedFileIDs; - } + uint32 count = std::min((uint32)all_mods.size(), cMaxEntries); + std::copy_n(all_mods.cbegin(), count, pvecPublishedFileIDs); - PRINT_DEBUG(" copied count = %u", copied); - return copied; + PRINT_DEBUG(" copied count = %u", count); + return count; } From 8794d31fcc6b63ff85e2ab8b3c4ec7deaa251320 Mon Sep 17 00:00:00 2001 From: a Date: Mon, 23 Mar 2026 14:25:28 +0200 Subject: [PATCH 072/159] use a map for the keys instead of if-statements --- overlay_experimental/steam_overlay.cpp | 57 +++++++++++--------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index cecc85761..7ccd71d96 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "InGameOverlay/RendererDetector.h" @@ -93,44 +94,36 @@ void Steam_Overlay::overlay_networking_callback(void* object, Common_Message* ms void Steam_Overlay::parse_key_combo() { + static const std::unordered_map KEYS_MAP { + { InGameOverlay::ToggleKey::SHIFT, "shift" }, + { InGameOverlay::ToggleKey::CTRL, "ctrl" }, + { InGameOverlay::ToggleKey::ALT, "alt" }, + { InGameOverlay::ToggleKey::TAB, "tab" }, + { InGameOverlay::ToggleKey::F1, "fn1" }, + { InGameOverlay::ToggleKey::F2, "fn2" }, + { InGameOverlay::ToggleKey::F3, "fn3" }, + { InGameOverlay::ToggleKey::F4, "fn4" }, + { InGameOverlay::ToggleKey::F5, "fn5" }, + { InGameOverlay::ToggleKey::F6, "fn6" }, + { InGameOverlay::ToggleKey::F7, "fn7" }, + { InGameOverlay::ToggleKey::F8, "fn8" }, + { InGameOverlay::ToggleKey::F9, "fn9" }, + { InGameOverlay::ToggleKey::F10, "fn10" }, + { InGameOverlay::ToggleKey::F11, "fn11" }, + { InGameOverlay::ToggleKey::F12, "fn12" }, + }; + std::unordered_set keys_combo{}; bool use_default = false; if (settings->overlay_toggle_keys.empty()) { use_default = true; } else { for (const auto &key_name : settings->overlay_toggle_keys) { - if (common_helpers::str_cmp_insensitive("shift", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::SHIFT); - } else if (common_helpers::str_cmp_insensitive("ctrl", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::CTRL); - } else if (common_helpers::str_cmp_insensitive("alt", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::ALT); - } else if (common_helpers::str_cmp_insensitive("tab", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::TAB); - } else if (common_helpers::str_cmp_insensitive("fn1", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::F1); - } else if (common_helpers::str_cmp_insensitive("fn2", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::F2); - } else if (common_helpers::str_cmp_insensitive("fn3", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::F3); - } else if (common_helpers::str_cmp_insensitive("fn4", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::F4); - } else if (common_helpers::str_cmp_insensitive("fn5", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::F5); - } else if (common_helpers::str_cmp_insensitive("fn6", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::F6); - } else if (common_helpers::str_cmp_insensitive("fn7", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::F7); - } else if (common_helpers::str_cmp_insensitive("fn8", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::F8); - } else if (common_helpers::str_cmp_insensitive("fn9", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::F9); - } else if (common_helpers::str_cmp_insensitive("fn10", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::F10); - } else if (common_helpers::str_cmp_insensitive("fn11", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::F11); - } else if (common_helpers::str_cmp_insensitive("fn12", key_name)) { - keys_combo.insert(InGameOverlay::ToggleKey::F12); + auto key_it = std::find_if(KEYS_MAP.cbegin(), KEYS_MAP.cend(), [&key_name](decltype(*KEYS_MAP.cbegin()) const &item) { + return common_helpers::str_cmp_insensitive(item.second, key_name); + }); + if (KEYS_MAP.cend() != key_it) { + keys_combo.insert(key_it->first); } else { use_default = true; PRINT_DEBUG("[X] Unknown key '%s', using default key combo Shift + Tab", key_name.c_str()); From ca8aa9ed19d9e810e2624857a7811f0b7581ed4a Mon Sep 17 00:00:00 2001 From: a Date: Mon, 23 Mar 2026 15:58:41 +0200 Subject: [PATCH 073/159] allow specifying max parallel build jobs --- build_linux_premake.sh | 14 ++++++++++---- build_win_premake.bat | 26 +++++++++++++++++--------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/build_linux_premake.sh b/build_linux_premake.sh index 4a22bc3af..49a817e6a 100644 --- a/build_linux_premake.sh +++ b/build_linux_premake.sh @@ -5,21 +5,22 @@ function help_page () { echo "switches:" echo " --deps: rebuild third-party dependencies" echo " --nogen: don't regenerate build files" + echo " --j: parallel build jobs" echo " --help: show this page" } -# use 70% -build_threads="$(( $(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 0) * 70 / 100 ))" -[[ $build_threads -lt 2 ]] && build_threads=2 - BUILD_DEPS=0 GEN_PROJECT=1 +BUILD_JOBS=-1 for (( i=1; i<=$#; ++i )); do arg="${!i}" if [[ "$arg" = "--deps" ]]; then BUILD_DEPS=1 elif [[ "$arg" = "--nogen" ]]; then GEN_PROJECT=0 + elif [[ "$arg" = "--j" ]]; then + BUILD_JOBS="$2" + shift 1 elif [[ "$arg" = "--help" ]]; then help_page exit 0 @@ -29,6 +30,11 @@ for (( i=1; i<=$#; ++i )); do fi done +# use 70% +build_threads="$(( $(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 0) * 70 / 100 ))" +[[ $build_threads -lt 2 ]] && build_threads=2 +[[ "$BUILD_JOBS" -ge "1" ]] && build_threads="$BUILD_JOBS" + premake_exe=./"third-party/common/linux/premake/premake5" if [[ ! -f "$premake_exe" ]]; then echo "preamke wasn't found" 1>&2 diff --git a/build_win_premake.bat b/build_win_premake.bat index 9968af8b9..8e8774246 100644 --- a/build_win_premake.bat +++ b/build_win_premake.bat @@ -2,17 +2,9 @@ setlocal EnableDelayedExpansion cd /d "%~dp0" -set /a "MAX_THREADS=2" -if defined NUMBER_OF_PROCESSORS ( - :: use 70% - set /a "MAX_THREADS=%NUMBER_OF_PROCESSORS% * 70 / 100" - if %MAX_THREADS% lss 1 ( - set /a "MAX_THREADS=1" - ) -) - set /a "BUILD_DEPS=0" set /a "GEN_PROJECT=1" +set /a "BUILD_JOBS=-1" :args_loop if "%~1" equ "" ( @@ -21,6 +13,9 @@ set /a "GEN_PROJECT=1" set /a "BUILD_DEPS=1" ) else if "%~1" equ "--nogen" ( set /a "GEN_PROJECT=0" + ) else if "%~1" equ "--j" ( + set /a "BUILD_JOBS=%~2" + shift /1 ) else if "%~1" equ "--help" ( goto :help_page ) else ( @@ -32,6 +27,18 @@ set /a "GEN_PROJECT=1" goto :args_loop :args_loop_end + set /a "MAX_THREADS=2" + if defined NUMBER_OF_PROCESSORS ( + :: use 70% + set /a "MAX_THREADS=%NUMBER_OF_PROCESSORS% * 70 / 100" + if %MAX_THREADS% lss 1 ( + set /a "MAX_THREADS=1" + ) + ) + if %BUILD_JOBS% geq 1 ( + set /a MAX_THREADS=BUILD_JOBS + ) + :: check premake set "PREMAKE_EXE=third-party\common\win\premake\premake5.exe" if not exist "%PREMAKE_EXE%" ( @@ -116,5 +123,6 @@ set /a "GEN_PROJECT=1" echo:switches: echo: --deps: rebuild third-party dependencies echo: --nogen: don't regenerate build files + echo --j: parallel build jobs echo: --help: show this page goto :end_script From 32cbb2f21aaf01a2fa32a1ffa1fa33f506d3057a Mon Sep 17 00:00:00 2001 From: a Date: Mon, 23 Mar 2026 16:06:36 +0200 Subject: [PATCH 074/159] fix build parallelism on windows --- build_win_premake.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_win_premake.bat b/build_win_premake.bat index 8e8774246..a91649175 100644 --- a/build_win_premake.bat +++ b/build_win_premake.bat @@ -100,7 +100,7 @@ set /a "BUILD_JOBS=-1" for %%C in (%BUILD_TARGETS%) do ( set "BUILD_TARGET=%%C" echo. & echo:building !BUILD_TARGET! !BUILD_TYPE! !BUILD_PLATFORM! - call "%MSBUILD_EXE%" /nologo -m:%MAX_THREADS% -v:n /p:Configuration=!BUILD_TYPE!,Platform=!BUILD_PLATFORM! /target:!BUILD_TARGET! "%SLN_FILE%" || ( + call "%MSBUILD_EXE%" /nologo -m:1 -p:CL_MPCount=%MAX_THREADS% -v:n /p:Configuration=!BUILD_TYPE!,Platform=!BUILD_PLATFORM! /target:!BUILD_TARGET! "%SLN_FILE%" || ( goto :end_script_with_err ) ) From f731d2ed8e41c4fcfea8c43ed5d5a58408cf09d7 Mon Sep 17 00:00:00 2001 From: a Date: Mon, 23 Mar 2026 16:06:57 +0200 Subject: [PATCH 075/159] recognize custom msbuild paths via `where` command line tool --- build_win_premake.bat | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build_win_premake.bat b/build_win_premake.bat index a91649175..faa97ab77 100644 --- a/build_win_premake.bat +++ b/build_win_premake.bat @@ -67,6 +67,14 @@ set /a "BUILD_JOBS=-1" for /f "tokens=* delims=" %%A in ('"%VSWHERE_EXE%" -prerelease -latest -nocolor -nologo -property installationPath 2^>nul') do ( set "MSBUILD_EXE=%%~A\MSBuild\Current\Bin\MSBuild.exe" ) + :: in case we are running inside a vcvars cmd from a portable buildtools installation without Visual Studio + if not exist "%MSBUILD_EXE%" ( + for /f "tokens=* delims=" %%A in ('where MSBuild 2^>nul') do ( + set "MSBUILD_EXE=%%~A" + goto :end_find_custom_msbuild + ) + ) +:end_find_custom_msbuild if not exist "%MSBUILD_EXE%" ( 1>&2 echo:MSBuild wasn't found goto :end_script_with_err From 8f02661cd177c3afcc2a12f45b792b120e6f02c1 Mon Sep 17 00:00:00 2001 From: a Date: Mon, 23 Mar 2026 16:25:41 +0200 Subject: [PATCH 076/159] refactor syntax --- build_linux_premake.sh | 2 +- build_win_premake.bat | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build_linux_premake.sh b/build_linux_premake.sh index 49a817e6a..15c6c9273 100644 --- a/build_linux_premake.sh +++ b/build_linux_premake.sh @@ -33,7 +33,7 @@ done # use 70% build_threads="$(( $(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 0) * 70 / 100 ))" [[ $build_threads -lt 2 ]] && build_threads=2 -[[ "$BUILD_JOBS" -ge "1" ]] && build_threads="$BUILD_JOBS" +[[ "$BUILD_JOBS" -ge 1 ]] && build_threads="$BUILD_JOBS" premake_exe=./"third-party/common/linux/premake/premake5" if [[ ! -f "$premake_exe" ]]; then diff --git a/build_win_premake.bat b/build_win_premake.bat index faa97ab77..6bc08c2e4 100644 --- a/build_win_premake.bat +++ b/build_win_premake.bat @@ -131,6 +131,6 @@ set /a "BUILD_JOBS=-1" echo:switches: echo: --deps: rebuild third-party dependencies echo: --nogen: don't regenerate build files - echo --j: parallel build jobs + echo: --j: parallel build jobs echo: --help: show this page goto :end_script From d614485b2998beac6cfddf59e48d870e111dceeb Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Tue, 24 Mar 2026 09:29:40 +0300 Subject: [PATCH 077/159] Fix SteamIDs generated for bots --- dll/auth.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dll/auth.cpp b/dll/auth.cpp index f38ebe893..efcaeb85d 100644 --- a/dll/auth.cpp +++ b/dll/auth.cpp @@ -881,7 +881,7 @@ HAuthTicket Auth_Manager::getWebApiTicket( const char* pchIdentity ) CSteamID Auth_Manager::fakeUser() { Auth_Data data = {}; - data.id = generate_steam_anon_user(); + data.id = generate_steam_id_anonserver(); // not k_EAccountTypeAnonUser inbound.push_back(data); return data.id; } From 279ce6f8a4e45a8ddb418491339a8ec75f07052d Mon Sep 17 00:00:00 2001 From: a Date: Wed, 25 Mar 2026 03:45:26 +0200 Subject: [PATCH 078/159] increase parallel jobs count for ci scripts and fix windows variant --- .github/workflows/emu-build-all-linux.yml | 7 +++++-- .github/workflows/emu-build-all-win.yml | 5 ++++- .github/workflows/emu-deps-linux.yml | 5 ++++- .github/workflows/emu-deps-win.yml | 5 ++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/emu-build-all-linux.yml b/.github/workflows/emu-build-all-linux.yml index d603f2eab..8211253c7 100644 --- a/.github/workflows/emu-build-all-linux.yml +++ b/.github/workflows/emu-build-all-linux.yml @@ -17,6 +17,9 @@ env: PACKAGE_BASE_DIR: "build/package/linux" THIRD_PARTY_BASE_DIR: "third-party" + # https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for-public-repositories + MAX_PARALLEL_JOBS: 4 + jobs: deps: name: "Restore or build deps" @@ -110,9 +113,9 @@ jobs: working-directory: "${{ github.workspace }}/build/project/gmake2/linux" run: | echo "dry run..." - make -n -j 2 config=${{ matrix.cfg }}_${{ matrix.arch }} ${{ matrix.prj }} + make -n -j ${{ env.MAX_PARALLEL_JOBS }} config=${{ matrix.cfg }}_${{ matrix.arch }} ${{ matrix.prj }} echo "actual run..." - make -j 2 config=${{ matrix.cfg }}_${{ matrix.arch }} ${{ matrix.prj }} + make -j ${{ env.MAX_PARALLEL_JOBS }} config=${{ matrix.cfg }}_${{ matrix.arch }} ${{ matrix.prj }} # upload artifact/package to github Actions - name: "Upload target package" diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 4cdc3ada9..bcca23e36 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -16,6 +16,9 @@ env: THIRD_PARTY_BASE_DIR: "third-party" + # https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for-public-repositories + MAX_PARALLEL_JOBS: 4 + jobs: deps: name: "Restore or build deps" @@ -107,7 +110,7 @@ jobs: shell: "cmd" working-directory: "${{ github.workspace }}/build/project/vs2022/win" run: | - msbuild /nologo /target:${{ matrix.prj }} /m:2 /v:n /p:Configuration=${{ matrix.cfg }},Platform=${{ matrix.arch }} gbe.sln + msbuild /nologo /target:${{ matrix.prj }} /m:1 -p:CL_MPCount=${{ env.MAX_PARALLEL_JOBS }} /v:n /p:Configuration=${{ matrix.cfg }},Platform=${{ matrix.arch }} gbe.sln # upload artifact/package to github Actions - name: "Upload target package" diff --git a/.github/workflows/emu-deps-linux.yml b/.github/workflows/emu-deps-linux.yml index 1ef4ec6ae..2bf6e05ba 100644 --- a/.github/workflows/emu-deps-linux.yml +++ b/.github/workflows/emu-deps-linux.yml @@ -17,6 +17,9 @@ env: PACKAGE_BASE_DIR: "build/package/linux" THIRD_PARTY_BASE_DIR: "third-party" + # https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for-public-repositories + MAX_PARALLEL_JOBS: 4 + jobs: deps-build: runs-on: "ubuntu-22.04" @@ -87,4 +90,4 @@ jobs: run: | export CMAKE_GENERATOR="Unix Makefiles" sudo chmod 777 ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 - ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=linux gmake2 + ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --j=${{ env.MAX_PARALLEL_JOBS }} --verbose --clean --os=linux gmake2 diff --git a/.github/workflows/emu-deps-win.yml b/.github/workflows/emu-deps-win.yml index 4daec42c8..0a15991a5 100644 --- a/.github/workflows/emu-deps-win.yml +++ b/.github/workflows/emu-deps-win.yml @@ -17,6 +17,9 @@ env: PACKAGE_BASE_DIR: "build/package/win" THIRD_PARTY_BASE_DIR: "third-party" + # https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for-public-repositories + MAX_PARALLEL_JOBS: 4 + jobs: deps-build: runs-on: "windows-2022" @@ -79,4 +82,4 @@ jobs: working-directory: "${{ github.workspace }}" run: | set "CMAKE_GENERATOR=Visual Studio 17 2022" - "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=windows vs2022 + "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --j=${{ env.MAX_PARALLEL_JOBS }} --verbose --clean --os=windows vs2022 From 9aa1aef12f48dc108ee258dcd934c57f21d975b9 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:38:56 +0100 Subject: [PATCH 079/159] update all workflows --- .github/workflows/emu-build-all-linux.yml | 10 +++--- .github/workflows/emu-build-all-win.yml | 10 +++--- .github/workflows/emu-deps-linux.yml | 10 +++--- .github/workflows/emu-deps-win.yml | 12 +++---- .github/workflows/migrate_gse-build-linux.yml | 4 +-- .github/workflows/migrate_gse-build-win.yml | 6 ++-- .github/workflows/release.yml | 34 +++++++++---------- 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/.github/workflows/emu-build-all-linux.yml b/.github/workflows/emu-build-all-linux.yml index 8211253c7..0c91999b2 100644 --- a/.github/workflows/emu-build-all-linux.yml +++ b/.github/workflows/emu-build-all-linux.yml @@ -53,25 +53,25 @@ jobs: steps: # clone branch - name: "Checkout branch" - uses: actions/checkout@v4 + uses: actions/checkout@v6 # deps - name: "Restore deps" id: "emu-deps-cache-step" - uses: actions/cache@v4 + uses: actions/cache@v5 with: key: "${{ env.DEPS_CACHE_KEY }}-${{ env.PREMAKE_ACTION }}" path: "${{ env.DEPS_CACHE_DIR }}/${{ env.PREMAKE_ACTION }}" # extra helpers/tools, these are not built inside the deps build dir - name: "Clone third-party build helpers (common/linux)" - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/common/linux" path: "${{env.THIRD_PARTY_BASE_DIR}}/common/linux" - name: "Clone third-party build helpers (build/linux)" - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/build/linux" path: "${{env.THIRD_PARTY_BASE_DIR}}/build/linux" @@ -119,7 +119,7 @@ jobs: # upload artifact/package to github Actions - name: "Upload target package" - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: "emu-linux-${{ matrix.prj }}-${{ matrix.cfg }}-${{ matrix.arch }}-${{ github.sha }}" path: "build/linux" diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index bcca23e36..bdffda78c 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -75,25 +75,25 @@ jobs: # clone branch - name: "Checkout branch" - uses: actions/checkout@v4 + uses: actions/checkout@v6 # deps - name: "Restore deps" id: "emu-deps-cache-step" - uses: actions/cache@v4 + uses: actions/cache@v5 with: key: "${{ env.DEPS_CACHE_KEY }}-${{ env.PREMAKE_ACTION }}" path: "${{ env.DEPS_CACHE_DIR }}/${{ env.PREMAKE_ACTION }}" # extra helpers/tools, these are not built inside the deps build dir - name: "Clone third-party build helpers (common/win)" - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/common/win" path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win" - name: "Clone third-party deps (build/win)" - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/build/win" path: "${{env.THIRD_PARTY_BASE_DIR}}/build/win" @@ -114,7 +114,7 @@ jobs: # upload artifact/package to github Actions - name: "Upload target package" - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: "emu-win-${{ matrix.prj }}-${{ matrix.cfg }}-${{ matrix.arch }}-${{ github.sha }}" path: "build/win" diff --git a/.github/workflows/emu-deps-linux.yml b/.github/workflows/emu-deps-linux.yml index 2bf6e05ba..c2bef0630 100644 --- a/.github/workflows/emu-deps-linux.yml +++ b/.github/workflows/emu-deps-linux.yml @@ -28,7 +28,7 @@ jobs: steps: - name: "Lookup cache for deps" id: "emu-deps-cache-step" - uses: actions/cache@v4 + uses: actions/cache@v5 with: key: "${{ env.DEPS_CACHE_KEY }}-${{ env.PREMAKE_ACTION }}" path: "${{ env.DEPS_CACHE_DIR }}/${{ env.PREMAKE_ACTION }}" @@ -36,25 +36,25 @@ jobs: # we need branch because it has build scripts - name: "Checkout branch" if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: "Clone third-party deps (common/linux)" if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/common/linux" path: "${{env.THIRD_PARTY_BASE_DIR}}/common/linux" - name: "Clone third-party deps (deps/linux)" if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/deps/linux" path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/linux" - name: "Clone third-party deps (deps/common)" if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/deps/common" path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/common" diff --git a/.github/workflows/emu-deps-win.yml b/.github/workflows/emu-deps-win.yml index 0a15991a5..9c259338b 100644 --- a/.github/workflows/emu-deps-win.yml +++ b/.github/workflows/emu-deps-win.yml @@ -37,7 +37,7 @@ jobs: - name: "Lookup cache for deps" id: "emu-deps-cache-step" - uses: actions/cache@v4 + uses: actions/cache@v5 with: key: "${{ env.DEPS_CACHE_KEY }}-${{ env.PREMAKE_ACTION }}" path: "${{ env.DEPS_CACHE_DIR }}/${{ env.PREMAKE_ACTION }}" @@ -46,32 +46,32 @@ jobs: # we need branch because it has build scripts - name: "Checkout branch" if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: "Clone third-party deps (common/win)" if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/common/win" path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win" - name: "Clone third-party deps (deps/win)" if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/deps/win" path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/win" - name: "Clone third-party deps (deps/common)" if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/deps/common" path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/common" - name: "Clone third-party deps (common/win)" if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/common/win" path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win" diff --git a/.github/workflows/migrate_gse-build-linux.yml b/.github/workflows/migrate_gse-build-linux.yml index aec43d1c6..a0c0e77f0 100644 --- a/.github/workflows/migrate_gse-build-linux.yml +++ b/.github/workflows/migrate_gse-build-linux.yml @@ -20,7 +20,7 @@ jobs: steps: - name: "Checkout branch" - uses: actions/checkout@v4 + uses: actions/checkout@v6 # fix folder permissions! not sure why this fails # nested subdirs "build/linux/release" cause permission problems @@ -43,7 +43,7 @@ jobs: # upload artifact - name: "Upload build package" - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: "${{ env.ARTIFACT_NAME }}" path: "${{ env.PACKAGE_BASE_DIR }}/" diff --git a/.github/workflows/migrate_gse-build-win.yml b/.github/workflows/migrate_gse-build-win.yml index 34913db8a..bbd536ece 100644 --- a/.github/workflows/migrate_gse-build-win.yml +++ b/.github/workflows/migrate_gse-build-win.yml @@ -36,11 +36,11 @@ jobs: git config --global core.autocrlf false - name: "Checkout branch" - uses: actions/checkout@v4 + uses: actions/checkout@v6 # extra helpers/tools, these are not built inside the deps build dir - name: "Clone third-party deps (build/win)" - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/build/win" path: "${{env.THIRD_PARTY_BASE_DIR}}/build/win" @@ -59,7 +59,7 @@ jobs: # upload artifact - name: "Upload build package" - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: "${{ env.ARTIFACT_NAME }}" path: "${{ env.PACKAGE_BASE_DIR }}/" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 854a0877a..f3c4a54b8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,17 +34,17 @@ jobs: # we need branch because it has package scripts - name: "Checkout branch" - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: "Clone third-party deps (deps/win)" - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/deps/win" path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/win" # download artifacts - name: "Download emu build artifacts (Win)" - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: "build/win" pattern: "emu-win-*-${{ github.sha }}" @@ -79,7 +79,7 @@ jobs: # upload artifacts/packages if this is a manual run - name: "Upload release package" if: ${{ !startsWith(github.ref, 'refs/tags/') }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: "release-emu-win-release-${{ github.sha }}" path: "build/package/win/vs2022/*release*" @@ -89,7 +89,7 @@ jobs: - name: "Upload debug package" if: ${{ !startsWith(github.ref, 'refs/tags/') }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: "release-emu-win-debug-${{ github.sha }}" path: "build/package/win/vs2022/*debug*" @@ -108,17 +108,17 @@ jobs: steps: # we need branch because it has package scripts - name: "Checkout branch" - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: "Clone third-party deps (deps/linux)" - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/deps/linux" path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/linux" # download artifacts - name: "Download emu build artifacts (linux)" - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: "build/linux" pattern: "emu-linux-*-${{ github.sha }}" @@ -168,7 +168,7 @@ jobs: # upload artifacts/packages if this is a manual run - name: "Upload release package" if: ${{ !startsWith(github.ref, 'refs/tags/') }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: "release-emu-linux-release-${{ github.sha }}" path: "build/package/linux/gmake2/*release*" @@ -178,7 +178,7 @@ jobs: - name: "Upload debug package" if: ${{ !startsWith(github.ref, 'refs/tags/') }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: "release-emu-linux-debug-${{ github.sha }}" path: "build/package/linux/gmake2/*debug*" @@ -206,17 +206,17 @@ jobs: # we need branch because it has package scripts - name: "Checkout branch" - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: "Clone third-party deps (deps/win)" - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: "third-party/deps/win" path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/win" # download artifacts - name: "Download script build artifacts (Win)" - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: "tools/migrate_gse/bin/win" pattern: "migrate_gse-win-*" @@ -238,7 +238,7 @@ jobs: # upload artifact/package if this is a manual run - name: "Upload release package" if: ${{ !startsWith(github.ref, 'refs/tags/') }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: "release-migrate_gse-win-${{ github.sha }}" path: "tools/migrate_gse/bin/package/win/**/*" @@ -257,11 +257,11 @@ jobs: steps: # we need branch because it has package scripts - name: "Checkout branch" - uses: actions/checkout@v4 + uses: actions/checkout@v6 # download artifacts - name: "Download script build artifacts (linux)" - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: "tools/migrate_gse/bin/linux" pattern: "migrate_gse-linux-*" @@ -290,7 +290,7 @@ jobs: # upload artifact/package if this is a manual run - name: "Upload release package" if: ${{ !startsWith(github.ref, 'refs/tags/') }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: "release-migrate_gse-linux-${{ github.sha }}" path: "tools/migrate_gse/bin/package/linux/**/*" From 1ce2b43e0eaea22704508262419c89ba516d1704 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:41:45 +0100 Subject: [PATCH 080/159] setup-msbuild update --- .github/workflows/emu-build-all-win.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index bdffda78c..d93e4ba04 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -71,7 +71,7 @@ jobs: # ensure we have msbuild - name: "Add MSBuild to PATH" - uses: microsoft/setup-msbuild@v2 + uses: microsoft/setup-msbuild@v3 # clone branch - name: "Checkout branch" From d60120f12302576a8b6a3117098a40091fb2c9a8 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Thu, 26 Mar 2026 18:45:54 +0300 Subject: [PATCH 081/159] Add missing integer cast --- dll/steam_inventory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dll/steam_inventory.cpp b/dll/steam_inventory.cpp index e8bd2fd79..9e87b58a2 100644 --- a/dll/steam_inventory.cpp +++ b/dll/steam_inventory.cpp @@ -165,7 +165,7 @@ bool Steam_Inventory::GetResultItems( SteamInventoryResult_t resultHandle, pOutItemsArray->m_iDefinition = i->value("definition", static_cast(pOutItemsArray->m_itemId)); pOutItemsArray->m_unQuantity = i->value("quantity", static_cast(1)); } catch (...) { - pOutItemsArray->m_iDefinition = pOutItemsArray->m_itemId; + pOutItemsArray->m_iDefinition = static_cast(pOutItemsArray->m_itemId); pOutItemsArray->m_unQuantity = 1; } pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; @@ -181,7 +181,7 @@ bool Steam_Inventory::GetResultItems( SteamInventoryResult_t resultHandle, pOutItemsArray->m_iDefinition = it->value("definition", static_cast(pOutItemsArray->m_itemId)); pOutItemsArray->m_unQuantity = it->value("quantity", static_cast(1)); } catch (...) { - pOutItemsArray->m_iDefinition = pOutItemsArray->m_itemId; + pOutItemsArray->m_iDefinition = static_cast(pOutItemsArray->m_itemId); pOutItemsArray->m_unQuantity = 1; } pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; From f3ce1175d93500cb2f75f84b9d6771781024a02e Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Thu, 26 Mar 2026 18:47:14 +0300 Subject: [PATCH 082/159] Further work on Game Coordinator --- dll/dll/econ_item.h | 35 +- dll/dll/steam_game_coordinator.h | 42 +- dll/dll/steam_gameserver_items.h | 7 +- dll/dll/steam_user_items.h | 1 - dll/gc_steam/steammessages.proto | 577 ++++++ dll/gc_tf2/base_gcmessages.proto | 598 ++++++ dll/gc_tf2/econ_gcmessages.proto | 370 ++++ dll/gc_tf2/gcsdk_gcmessages.proto | 188 ++ dll/gc_tf2/gcsystemmsgs.proto | 180 ++ dll/gc_tf2/tf_gcmessages.proto | 1650 +++++++++++++++++ dll/net.proto | 21 +- dll/steam_game_coordinator.cpp | 910 ++++++--- dll/steam_gameserver.cpp | 17 +- dll/steam_gameserver_items.cpp | 8 +- .../steam_settings.EXAMPLE/gc.EXAMPLE.json | 1 + premake5.lua | 15 +- 16 files changed, 4370 insertions(+), 250 deletions(-) create mode 100644 dll/gc_steam/steammessages.proto create mode 100644 dll/gc_tf2/base_gcmessages.proto create mode 100644 dll/gc_tf2/econ_gcmessages.proto create mode 100644 dll/gc_tf2/gcsdk_gcmessages.proto create mode 100644 dll/gc_tf2/gcsystemmsgs.proto create mode 100644 dll/gc_tf2/tf_gcmessages.proto diff --git a/dll/dll/econ_item.h b/dll/dll/econ_item.h index 14ba72227..ca12cabcc 100644 --- a/dll/dll/econ_item.h +++ b/dll/dll/econ_item.h @@ -51,24 +51,35 @@ struct Econ_Item_Attribute { - uint32 def; - float value; + enum Attribute_Type + { + ATTR_TYPE_DEFAULT = 0, + ATTR_TYPE_FLOAT, + ATTR_TYPE_INT, + ATTR_TYPE_STRING, + }; + uint32 def{}; + float value{}; // for backwards compatibility + std::string value_bytes; + Attribute_Type type{}; // this is only used on the client side to know how to save attribute into file }; struct Econ_Item { - uint64 id; - uint32 def; - uint32 level; - EItemQuality quality; - uint32 inv_pos; - uint32 quantity; - uint8 flags; - uint8 origin; + uint64 id{}; + uint32 def{}; + uint32 level{}; + EItemQuality quality{}; + uint32 inv_pos{}; + uint32 quantity{}; + uint8 flags{}; + uint8 origin{}; std::string custom_name; std::string custom_desc; - bool in_use; - uint64 original_id; + bool in_use{}; + uint64 original_id{}; + uint8 style{}; + std::map equip_states; std::vector attributes; }; diff --git a/dll/dll/steam_game_coordinator.h b/dll/dll/steam_game_coordinator.h index 5c52fdb79..612437091 100644 --- a/dll/dll/steam_game_coordinator.h +++ b/dll/dll/steam_game_coordinator.h @@ -26,6 +26,7 @@ class Steam_GameServer_Items; struct GCMsgHdr_t; struct GCMsgHdrEx_t; struct ProtoBufMsgHeader_t; +class CMsgProtoBufHeader; class Steam_Game_Coordinator : public ISteamGameCoordinator @@ -52,8 +53,20 @@ public ISteamGameCoordinator std::vector pending_messages; std::queue incoming_messages; + enum GC_Profile + { + GC_PROFILE_INVALID = 0, + GC_PROFILE_TF2, + //GC_PROFILE_PORTAL2, + }; + int gc_version{}; + GC_Profile gc_profile{}; + bool is_portal2{}; bool gc_initialized{}; + bool delay_init{}; + bool welcome_received{}; + std::chrono::high_resolution_clock::time_point welcome_time{}; std::vector items; bool items_loaded{}; @@ -61,7 +74,7 @@ public ISteamGameCoordinator struct RequestInventory { std::chrono::high_resolution_clock::time_point created{}; - CSteamID steam_id; + CSteamID steam_id{}; SteamAPICall_t steam_api_call{}; bool is_gc{}; }; @@ -73,21 +86,38 @@ public ISteamGameCoordinator Steam_User_Items *client_items(); Steam_GameServer_Items *server_items(); void parse_gc_config(); + bool is_welcome_message(const GC_Message &message); void push_incoming(uint32 msg_type, const std::string &message, double delay = 0.1); std::string build_msg_header(JobID_t target_job = k_GIDNil, JobID_t source_job = k_GIDNil); GCMsgHdrEx_t parse_msg_header(const char *&p); + std::string build_protomsg_header(uint32 msg_type, JobID_t target_job = k_GIDNil, JobID_t source_job = k_GIDNil); + template + std::tuple parse_protomsg(const void *input, uint32 input_size); uint64 item_id_local_to_network(uint64 item_id); uint64 item_id_network_to_local(uint64 item_id); + std::string item_to_gcstruct(const Econ_Item &item, CSteamID steam_id); + std::string item_to_gcprotobuf(const Econ_Item &item, CSteamID steam_id); void handle_set_item_pos(const void *input, uint32 input_size); void handle_delete_item(const void *input, uint32 input_size); - void handle_respawn(const void *input, uint32 input_size); void handle_motd_request(const void *input, uint32 input_size); + void handle_respawn(const void *input, uint32 input_size); + void handle_set_item_style(const void *input, uint32 input_size); + void handle_adjust_equip_state(const void *input, uint32 input_size); + void handle_set_multiple_item_pos(const void *input, uint32 input_size); + + void callback_client_welcome(); + void callback_server_welcome(); + void callback_items_received(CSteamID steam_id, const std::vector &items); + void callback_items_removed(CSteamID steam_id); + void callback_item_updated(CSteamID steam_id, const Econ_Item &item); + void callback_item_deleted(CSteamID steam_id, uint64 item_id); + void callback_respawn_request(CSteamID steam_id); void network_callback_inventory_request(Common_Message *msg); void network_callback_inventory_response(Common_Message *msg); - void network_callback_inventory_pos_update(Common_Message *msg); + void network_callback_item_update(Common_Message *msg); void network_callback_item_deletion(Common_Message *msg); void network_callback_respawn_request(Common_Message *msg); void network_callback(Common_Message *msg); @@ -101,6 +131,7 @@ public ISteamGameCoordinator ~Steam_Game_Coordinator(); void initialize_gc(); + void shutdown_gc(); const std::vector &get_items() { return items; } const std::map> &get_all_user_items() { return all_user_items; } @@ -109,7 +140,7 @@ public ISteamGameCoordinator const std::vector &load_items_from_file(); void save_items_to_file(); - const Econ_Item *set_item_pos(uint64 item_id, uint32 inv_pos, bool is_gc); + const Econ_Item *set_item_pos(uint64 item_id, uint32 inv_pos, bool is_gc, bool save = true); bool delete_item(uint64 item_id, bool is_gc); void request_user_items(CSteamID steam_id, SteamAPICall_t api_call, bool is_gc); @@ -118,9 +149,6 @@ public ISteamGameCoordinator void on_client_connected(CSteamID steam_id); void on_client_disconnected(CSteamID steam_id); - void on_items_received(CSteamID steam_id, const std::vector &items); - void on_item_pos_updated(CSteamID steam_id, const Econ_Item &item); - void on_item_deleted(CSteamID steam_id, uint64 item_id); // sends a message to the Game Coordinator EGCResults SendMessage_( uint32 unMsgType, const void *pubData, uint32 cubData ); diff --git a/dll/dll/steam_gameserver_items.h b/dll/dll/steam_gameserver_items.h index 43868d02e..2daaf94fe 100644 --- a/dll/dll/steam_gameserver_items.h +++ b/dll/dll/steam_gameserver_items.h @@ -19,7 +19,6 @@ #define __INCLUDED_STEAM_GAMESERVER_ITEMS_H__ #include "base.h" -#include "econ_item.h" class Steam_Game_Coordinator; @@ -39,9 +38,9 @@ public ISteamGameServerItems Steam_GameServer_Items(class Settings *settings, class SteamCallBacks *callbacks, class SteamCallResults *callback_results); ~Steam_GameServer_Items(); - void on_items_received(CSteamID steam_id, size_t num_items, SteamAPICall_t api_call, bool success); - void on_item_pos_updated(CSteamID steam_id, uint64 item_id, uint32 inv_pos); - void on_item_deleted(CSteamID steam_id, uint64 item_id); + void callback_items_received(CSteamID steam_id, size_t num_items, SteamAPICall_t api_call, bool success); + void callback_item_pos_updated(CSteamID steam_id, uint64 item_id, uint32 inv_pos); + void callback_item_deleted(CSteamID steam_id, uint64 item_id); SteamAPICall_t LoadItems( CSteamID ownerID ); void LoadItems_old( CSteamID ownerID ); diff --git a/dll/dll/steam_user_items.h b/dll/dll/steam_user_items.h index 80e131ab9..eaefa6c36 100644 --- a/dll/dll/steam_user_items.h +++ b/dll/dll/steam_user_items.h @@ -19,7 +19,6 @@ #define __INCLUDED_STEAM_USER_ITEMS_H__ #include "base.h" -#include "econ_item.h" class Steam_Game_Coordinator; diff --git a/dll/gc_steam/steammessages.proto b/dll/gc_steam/steammessages.proto new file mode 100644 index 000000000..f09971df9 --- /dev/null +++ b/dll/gc_steam/steammessages.proto @@ -0,0 +1,577 @@ +import "google/protobuf/descriptor.proto"; + +option optimize_for = SPEED; +option cc_generic_services = false; + +extend google.protobuf.FieldOptions { + optional bool key_field = 60000 [default = false]; +} + +extend google.protobuf.MessageOptions { + optional int32 msgpool_soft_limit = 60000 [default = 32]; + optional int32 msgpool_hard_limit = 60001 [default = 384]; +} + +enum GCProtoBufMsgSrc { + GCProtoBufMsgSrc_Unspecified = 0; + GCProtoBufMsgSrc_FromSystem = 1; + GCProtoBufMsgSrc_FromSteamID = 2; + GCProtoBufMsgSrc_FromGC = 3; + GCProtoBufMsgSrc_ReplySystem = 4; +} + +message CMsgProtoBufHeader { + option (msgpool_soft_limit) = 256; + option (msgpool_hard_limit) = 1024; + + optional fixed64 client_steam_id = 1; + optional int32 client_session_id = 2; + optional uint32 source_app_id = 3; + optional fixed64 job_id_source = 10 [default = 18446744073709551615]; + optional fixed64 job_id_target = 11 [default = 18446744073709551615]; + optional string target_job_name = 12; + optional int32 eresult = 13 [default = 2]; + optional string error_message = 14; + optional GCProtoBufMsgSrc gc_msg_src = 200 [default = GCProtoBufMsgSrc_Unspecified]; + optional uint32 gc_dir_index_source = 201; +} + +message CMsgWebAPIKey { + optional uint32 status = 1 [default = 255]; + optional uint32 account_id = 2 [default = 0]; + optional uint32 publisher_group_id = 3 [default = 0]; + optional uint32 key_id = 4; + optional string domain = 5; +} + +message CMsgHttpRequest { + message RequestHeader { + optional string name = 1; + optional string value = 2; + } + + message QueryParam { + optional string name = 1; + optional bytes value = 2; + } + + optional uint32 request_method = 1; + optional string hostname = 2; + optional string url = 3; + repeated CMsgHttpRequest.RequestHeader headers = 4; + repeated CMsgHttpRequest.QueryParam get_params = 5; + repeated CMsgHttpRequest.QueryParam post_params = 6; + optional bytes body = 7; + optional uint32 absolute_timeout = 8; +} + +message CMsgWebAPIRequest { + optional string UNUSED_job_name = 1; + optional string interface_name = 2; + optional string method_name = 3; + optional uint32 version = 4; + optional CMsgWebAPIKey api_key = 5; + optional CMsgHttpRequest request = 6; + optional uint32 routing_app_id = 7; +} + +message CMsgHttpResponse { + message ResponseHeader { + optional string name = 1; + optional string value = 2; + } + + optional uint32 status_code = 1; + repeated CMsgHttpResponse.ResponseHeader headers = 2; + optional bytes body = 3; +} + +message CMsgAMFindAccounts { + optional uint32 search_type = 1; + optional string search_string = 2; +} + +message CMsgAMFindAccountsResponse { + repeated fixed64 steam_id = 1; +} + +message CMsgNotifyWatchdog { + optional uint32 source = 1; + optional uint32 alert_type = 2; + optional uint32 alert_destination = 3; + optional bool critical = 4; + optional uint32 time = 5; + optional uint32 appid = 6; + optional string text = 7; +} + +message CMsgAMGetLicenses { + optional fixed64 steamid = 1; +} + +message CMsgPackageLicense { + optional uint32 package_id = 1; + optional uint32 time_created = 2; + optional uint32 owner_id = 3; +} + +message CMsgAMGetLicensesResponse { + repeated CMsgPackageLicense license = 1; + optional uint32 result = 2; +} + +message CMsgAMGetUserGameStats { + optional fixed64 steam_id = 1; + optional fixed64 game_id = 2; + repeated uint32 stats = 3; +} + +message CMsgAMGetUserGameStatsResponse { + message Stats { + optional uint32 stat_id = 1; + optional uint32 stat_value = 2; + } + + message Achievement_Blocks { + optional uint32 achievement_id = 1; + optional uint32 achievement_bit_id = 2; + optional fixed32 unlock_time = 3; + } + + optional fixed64 steam_id = 1; + optional fixed64 game_id = 2; + optional int32 eresult = 3 [default = 2]; + repeated CMsgAMGetUserGameStatsResponse.Stats stats = 4; + repeated CMsgAMGetUserGameStatsResponse.Achievement_Blocks achievement_blocks = 5; +} + +message CMsgGCGetCommandList { + optional uint32 app_id = 1; + optional string command_prefix = 2; +} + +message CMsgGCGetCommandListResponse { + repeated string command_name = 1; +} + +message CGCMsgMemCachedGet { + repeated string keys = 1; +} + +message CGCMsgMemCachedGetResponse { + message ValueTag { + optional bool found = 1; + optional bytes value = 2; + } + + repeated CGCMsgMemCachedGetResponse.ValueTag values = 1; +} + +message CGCMsgMemCachedSet { + message KeyPair { + optional string name = 1; + optional bytes value = 2; + } + + repeated CGCMsgMemCachedSet.KeyPair keys = 1; +} + +message CGCMsgMemCachedDelete { + repeated string keys = 1; +} + +message CGCMsgMemCachedStats { +} + +message CGCMsgMemCachedStatsResponse { + optional uint64 curr_connections = 1; + optional uint64 cmd_get = 2; + optional uint64 cmd_set = 3; + optional uint64 cmd_flush = 4; + optional uint64 get_hits = 5; + optional uint64 get_misses = 6; + optional uint64 delete_hits = 7; + optional uint64 delete_misses = 8; + optional uint64 bytes_read = 9; + optional uint64 bytes_written = 10; + optional uint64 limit_maxbytes = 11; + optional uint64 curr_items = 12; + optional uint64 evictions = 13; + optional uint64 bytes = 14; +} + +message CGCMsgSQLStats { + optional uint32 schema_catalog = 1; +} + +message CGCMsgSQLStatsResponse { + optional uint32 threads = 1; + optional uint32 threads_connected = 2; + optional uint32 threads_active = 3; + optional uint32 operations_submitted = 4; + optional uint32 prepared_statements_executed = 5; + optional uint32 non_prepared_statements_executed = 6; + optional uint32 deadlock_retries = 7; + optional uint32 operations_timed_out_in_queue = 8; + optional uint32 errors = 9; +} + +message CMsgAMAddFreeLicense { + optional fixed64 steamid = 1; + optional uint32 ip_public = 2; + optional uint32 packageid = 3; + optional string store_country_code = 4; +} + +message CMsgAMAddFreeLicenseResponse { + optional int32 eresult = 1 [default = 2]; + optional int32 purchase_result_detail = 2; + optional fixed64 transid = 3; +} + +message CGCMsgGetIPLocation { + repeated fixed32 ips = 1; +} + +message CIPLocationInfo { + optional uint32 ip = 1; + optional float latitude = 2; + optional float longitude = 3; + optional string country = 4; + optional string state = 5; + optional string city = 6; +} + +message CGCMsgGetIPLocationResponse { + repeated CIPLocationInfo infos = 1; +} + +message CGCMsgSystemStatsSchema { + optional uint32 gc_app_id = 1; + optional bytes schema_kv = 2; +} + +message CGCMsgGetSystemStats { +} + +message CGCMsgGetSystemStatsResponse { + optional uint32 gc_app_id = 1; + optional bytes stats_kv = 2; + optional uint32 active_jobs = 3; + optional uint32 yielding_jobs = 4; + optional uint32 user_sessions = 5; + optional uint32 game_server_sessions = 6; + optional uint32 socaches = 7; + optional uint32 socaches_to_unload = 8; + optional uint32 socaches_loading = 9; + optional uint32 writeback_queue = 10; + optional uint32 steamid_locks = 11; + optional uint32 logon_queue = 12; + optional uint32 logon_jobs = 13; +} + +message CMsgAMSendEmail { + message ReplacementToken { + optional string token_name = 1; + optional string token_value = 2; + } + + message PersonaNameReplacementToken { + optional fixed64 steamid = 1; + optional string token_name = 2; + } + + optional fixed64 steamid = 1; + optional uint32 email_msg_type = 2; + optional uint32 email_format = 3; + repeated CMsgAMSendEmail.PersonaNameReplacementToken persona_name_tokens = 5; + optional uint32 source_gc = 6; + repeated CMsgAMSendEmail.ReplacementToken tokens = 7; +} + +message CMsgAMSendEmailResponse { + optional uint32 eresult = 1 [default = 2]; +} + +message CMsgGCGetEmailTemplate { + optional uint32 app_id = 1; + optional uint32 email_msg_type = 2; + optional int32 email_lang = 3; + optional int32 email_format = 4; +} + +message CMsgGCGetEmailTemplateResponse { + optional uint32 eresult = 1 [default = 2]; + optional bool template_exists = 2; + optional string template = 3; +} + +message CMsgAMGrantGuestPasses2 { + optional fixed64 steam_id = 1; + optional uint32 package_id = 2; + optional int32 passes_to_grant = 3; + optional int32 days_to_expiration = 4; + optional int32 action = 5; +} + +message CMsgAMGrantGuestPasses2Response { + optional int32 eresult = 1 [default = 2]; + optional int32 passes_granted = 2 [default = 0]; +} + +message CGCSystemMsg_GetAccountDetails { + option (msgpool_soft_limit) = 128; + option (msgpool_hard_limit) = 512; + + optional fixed64 steamid = 1; + optional uint32 appid = 2; +} + +message CGCSystemMsg_GetAccountDetails_Response { + option (msgpool_soft_limit) = 128; + option (msgpool_hard_limit) = 512; + + optional uint32 eresult_deprecated = 1 [default = 2]; + optional string account_name = 2; + optional string persona_name = 3; + optional bool is_profile_public = 4; + optional bool is_inventory_public = 5; + optional bool is_vac_banned = 7; + optional bool is_cyber_cafe = 8; + optional bool is_school_account = 9; + optional bool is_limited = 10; + optional bool is_subscribed = 11; + optional uint32 package = 12; + optional bool is_free_trial_account = 13; + optional uint32 free_trial_expiration = 14; + optional bool is_low_violence = 15; + optional bool is_account_locked_down = 16; + optional bool is_community_banned = 17; + optional bool is_trade_banned = 18; + optional uint32 trade_ban_expiration = 19; + optional uint32 accountid = 20; + optional uint32 suspension_end_time = 21; + optional string currency = 22; + optional uint32 steam_level = 23; + optional uint32 friend_count = 24; + optional uint32 account_creation_time = 25; + optional bool is_steamguard_enabled = 27; + optional bool is_phone_verified = 28; + optional bool is_two_factor_auth_enabled = 29; + optional uint32 two_factor_enabled_time = 30; + optional uint32 phone_verification_time = 31; + optional uint64 phone_id = 33; + optional bool is_phone_identifying = 34; + optional uint32 rt_identity_linked = 35; + optional uint32 rt_birth_date = 36; + optional string txn_country_code = 37; +} + +message CMsgGCCheckClanMembership { + optional fixed64 steamid = 1; + optional uint32 clanid = 2; +} + +message CMsgGCCheckClanMembership_Response { + optional bool ismember = 1; +} + +message CMsgGCGetPersonaNames { + repeated fixed64 steamids = 1; +} + +message CMsgGCGetPersonaNames_Response { + message PersonaName { + optional fixed64 steamid = 1; + optional string persona_name = 2; + } + + repeated CMsgGCGetPersonaNames_Response.PersonaName succeeded_lookups = 1; + repeated fixed64 failed_lookup_steamids = 2; +} + +message CMsgGCCheckFriendship { + optional fixed64 steamid_left = 1; + optional fixed64 steamid_right = 2; +} + +message CMsgGCCheckFriendship_Response { + optional bool success = 1; + optional bool found_friendship = 2; +} + +message CMsgGCMsgMasterSetDirectory { + message SubGC { + optional uint32 dir_index = 1; + optional string name = 2; + optional string box = 3; + optional string command_line = 4; + optional string gc_binary = 5; + } + + optional uint32 master_dir_index = 1; + repeated CMsgGCMsgMasterSetDirectory.SubGC dir = 2; +} + +message CMsgGCMsgMasterSetDirectory_Response { + optional int32 eresult = 1 [default = 2]; +} + +message CMsgGCMsgWebAPIJobRequestForwardResponse { + optional uint32 dir_index = 1; +} + +message CGCSystemMsg_GetPurchaseTrust_Request { + optional fixed64 steamid = 1; +} + +message CGCSystemMsg_GetPurchaseTrust_Response { + optional bool has_prior_purchase_history = 1; + optional bool has_no_recent_password_resets = 2; + optional bool is_wallet_cash_trusted = 3; + optional uint32 time_all_trusted = 4; +} + +message CMsgGCHAccountVacStatusChange { + optional fixed64 steam_id = 1; + optional uint32 app_id = 2; + optional uint32 rtime_vacban_starts = 3; + optional bool is_banned_now = 4; + optional bool is_banned_future = 5; +} + +message CMsgGCHAccountTradeBanStatusChange { + optional fixed64 steamid = 1; + optional uint32 appid = 2; + optional bool is_banned = 3; + optional uint32 time_banned_until = 4; +} + +message CMsgGCHAccountLockStatusChange { + optional fixed64 steamid = 1; + optional uint32 appid = 2; + optional bool is_locked = 3; +} + +message CMsgGCHVacVerificationChange { + optional fixed64 steamid = 1; + optional uint32 appid = 2; + optional bool is_verified = 3; +} + +message CMsgGCHAccountPhoneNumberChange { + optional fixed64 steamid = 1; + optional uint32 appid = 2; + optional uint64 phone_id = 3; + optional bool is_verified = 4; + optional bool is_identifying = 5; +} + +message CMsgGCHAccountTwoFactorChange { + optional fixed64 steamid = 1; + optional uint32 appid = 2; + optional bool twofactor_enabled = 3; +} + +message CMsgGCGetPartnerAccountLink { + optional fixed64 steamid = 1; +} + +message CMsgGCGetPartnerAccountLink_Response { + optional uint32 pwid = 1; + optional uint32 nexonid = 2; +} + +message CMsgGCRoutingInfo { + enum RoutingMethod { + RANDOM = 0; + DISCARD = 1; + CLIENT_STEAMID = 2; + PROTOBUF_FIELD_UINT64 = 3; + WEBAPI_PARAM_UINT64 = 4; + } + + repeated uint32 dir_index = 1; + optional CMsgGCRoutingInfo.RoutingMethod method = 2 [default = RANDOM]; + optional CMsgGCRoutingInfo.RoutingMethod fallback = 3 [default = DISCARD]; + optional uint32 protobuf_field = 4; + optional string webapi_param = 5; +} + +message CMsgGCMsgMasterSetWebAPIRouting { + message Entry { + optional string interface_name = 1; + optional string method_name = 2; + optional CMsgGCRoutingInfo routing = 3; + } + + repeated CMsgGCMsgMasterSetWebAPIRouting.Entry entries = 1; +} + +message CMsgGCMsgMasterSetClientMsgRouting { + message Entry { + optional uint32 msg_type = 1; + optional CMsgGCRoutingInfo routing = 2; + } + + repeated CMsgGCMsgMasterSetClientMsgRouting.Entry entries = 1; +} + +message CMsgGCMsgMasterSetWebAPIRouting_Response { + optional int32 eresult = 1 [default = 2]; +} + +message CMsgGCMsgMasterSetClientMsgRouting_Response { + optional int32 eresult = 1 [default = 2]; +} + +message CMsgGCMsgSetOptions { + message MessageRange { + required uint32 low = 1; + required uint32 high = 2; + } + + enum Option { + NOTIFY_USER_SESSIONS = 0; + NOTIFY_SERVER_SESSIONS = 1; + NOTIFY_ACHIEVEMENTS = 2; + NOTIFY_VAC_ACTION = 3; + } + + enum GCSQLVersion { + GCSQL_VERSION_BASELINE = 1; + GCSQL_VERSION_BOOLTYPE = 2; + } + + repeated CMsgGCMsgSetOptions.Option options = 1; + repeated CMsgGCMsgSetOptions.MessageRange client_msg_ranges = 2; + optional CMsgGCMsgSetOptions.GCSQLVersion gcsql_version = 3 [default = GCSQL_VERSION_BASELINE]; +} + +message CMsgGCHUpdateSession { + message ExtraField { + optional string name = 1; + optional string value = 2; + } + + optional fixed64 steam_id = 1; + optional uint32 app_id = 2; + optional bool online = 3; + optional fixed64 server_steam_id = 4; + optional uint32 server_addr = 5; + optional uint32 server_port = 6; + optional uint32 os_type = 7; + optional uint32 client_addr = 8; + repeated CMsgGCHUpdateSession.ExtraField extra_fields = 9; +} + +message CMsgNotificationOfSuspiciousActivity { + message MultipleGameInstances { + optional uint32 app_instance_count = 1; + repeated fixed64 other_steamids = 2; + } + + optional fixed64 steamid = 1; + optional uint32 appid = 2; + optional CMsgNotificationOfSuspiciousActivity.MultipleGameInstances multiple_instances = 3; +} diff --git a/dll/gc_tf2/base_gcmessages.proto b/dll/gc_tf2/base_gcmessages.proto new file mode 100644 index 000000000..4274f09e8 --- /dev/null +++ b/dll/gc_tf2/base_gcmessages.proto @@ -0,0 +1,598 @@ +import "steammessages.proto"; + +option optimize_for = SPEED; +option cc_generic_services = false; + +package gamecoordinator.tf2; + +enum EGCBaseMsg { + k_EMsgGCSystemMessage = 4001; + k_EMsgGCReplicateConVars = 4002; + k_EMsgGCConVarUpdated = 4003; + k_EMsgGCServerAvailable = 4506; + k_EMsgGCClientConnectToServer = 4507; + k_EMsgGCGameServerInfo = 4508; + k_EMsgGCError = 4509; + k_EMsgGCReplay_UploadedToYouTube = 4510; + k_EMsgGCLANServerAvailable = 4511; +} + +enum EGCBaseProtoObjectTypes { + k_EProtoObjectPartyInvite = 1001; + k_EProtoObjectLobbyInvite = 1002; +} + +enum GCGoodbyeReason { + GCGoodbyeReason_GC_GOING_DOWN = 1; + GCGoodbyeReason_NO_SESSION = 2; +} + +message CGCStorePurchaseInit_LineItem { + optional uint32 item_def_id = 1; + optional uint32 quantity = 2; + optional uint32 cost_in_local_currency = 3; + optional uint32 purchase_type = 4; +} + +message CMsgGCStorePurchaseInit { + optional string country = 1; + optional int32 language = 2; + optional int32 currency = 3; + repeated CGCStorePurchaseInit_LineItem line_items = 4; +} + +message CMsgGCStorePurchaseInitResponse { + optional int32 result = 1; + optional uint64 txn_id = 2; +} + +message CMsgSystemBroadcast { + optional string message = 1; +} + +message CMsgClientHello { + optional uint32 version = 1; +} + +message CMsgServerHello { + optional uint32 version = 1; +} + +message CMsgClientWelcome { + optional uint32 version = 1; + optional bytes game_data = 2; + optional string txn_country_code = 3; +} + +message CMsgServerWelcome { + optional uint32 min_allowed_version = 1; + optional uint32 active_version = 2; +} + +message CMsgClientGoodbye { + optional GCGoodbyeReason reason = 1 [default = GCGoodbyeReason_GC_GOING_DOWN]; +} + +message CMsgServerGoodbye { + optional GCGoodbyeReason reason = 1 [default = GCGoodbyeReason_GC_GOING_DOWN]; +} + +message CMsgServerAvailable { +} + +message CMsgLANServerAvailable { + optional fixed64 lobby_id = 1; +} + +message CSOEconGameAccountClient { + optional uint32 additional_backpack_slots = 1 [default = 0]; + optional bool trial_account = 2 [default = false]; + optional bool need_to_choose_most_helpful_friend = 4; + optional bool in_coaches_list = 5; + optional fixed32 trade_ban_expiration = 6; + optional fixed32 duel_ban_expiration = 7; + optional uint32 preview_item_def = 8 [default = 0]; + optional bool phone_verified = 19 [default = false]; + optional uint32 skill_rating_6v6 = 20; + optional uint32 skill_rating_9v9 = 21; + optional bool competitive_access = 23 [default = false]; + optional uint32 matchmaking_ranked_ban_expiration = 18; + optional uint32 matchmaking_ranked_low_priority_expiration = 24; + optional uint32 matchmaking_ranked_ban_last_duration = 25; + optional uint32 matchmaking_ranked_low_priority_last_duration = 26; + optional uint32 matchmaking_casual_ban_expiration = 27; + optional uint32 matchmaking_casual_low_priority_expiration = 28; + optional uint32 matchmaking_casual_ban_last_duration = 29; + optional uint32 matchmaking_casual_low_priority_last_duration = 30; + optional bool phone_identifying = 31 [default = false]; + optional bool disable_party_quest_progress = 32 [default = false]; + optional uint32 quest_reward_credits = 33; + optional uint32 matchmaking_last_casual_excessive_reports_auto_ban_time = 34; + optional uint32 matchmaking_last_comp_excessive_reports_auto_ban_time = 35; +} + +message CSOItemCriteriaCondition { + optional int32 op = 1; + optional string field = 2; + optional bool required = 3; + optional float float_value = 4; + optional string string_value = 5; +} + +message CSOItemCriteria { + optional uint32 item_level = 1; + optional int32 item_quality = 2; + optional bool item_level_set = 3; + optional bool item_quality_set = 4; + optional uint32 initial_inventory = 5; + optional uint32 initial_quantity = 6; + optional bool ignore_enabled_flag = 8; + repeated CSOItemCriteriaCondition conditions = 9; + optional bool recent_only = 10; + optional string tags = 11; + optional string equip_regions = 12; +} + +message CSOItemRecipe { + optional uint32 def_index = 1; + optional string name = 2; + optional string n_a = 3; + optional string desc_inputs = 4; + optional string desc_outputs = 5; + optional string di_a = 6; + optional string di_b = 7; + optional string di_c = 8; + optional string do_a = 9; + optional string do_b = 10; + optional string do_c = 11; + optional bool requires_all_same_class = 12; + optional bool requires_all_same_slot = 13; + optional int32 class_usage_for_output = 14; + optional int32 slot_usage_for_output = 15; + optional int32 set_for_output = 16; + repeated CSOItemCriteria input_items_criteria = 20; + repeated CSOItemCriteria output_items_criteria = 21; + repeated uint32 input_item_dupe_counts = 22; +} + +message CMsgDevNewItemRequest { + optional fixed64 receiver = 1; + optional CSOItemCriteria criteria = 2; +} + +message CMsgDevDebugRollLootRequest { + optional fixed64 receiver = 1; + optional string loot_list_name = 2; +} + +message CMsgIncrementKillCountAttribute { + optional uint64 killer_steam_id = 1; + optional uint64 victim_steam_id = 2; + optional uint64 item_id = 3; + optional uint32 event_type = 4; + optional uint32 increment_value = 5; +} + +message CMsgIncrementKillCountAttribute_Multiple { + repeated CMsgIncrementKillCountAttribute msgs = 1; +} + +message CMsgTrackUniquePlayerPairEvent { + optional uint64 killer_steam_id = 1; + optional uint64 victim_steam_id = 2; + optional uint64 item_id = 3; + optional uint32 event_type = 4; +} + +message CMsgApplyStrangeCountTransfer { + optional uint64 tool_item_id = 1; + optional uint64 item_src_item_id = 2; + optional uint64 item_dest_item_id = 3; +} + +message CMsgApplyStrangePart { + optional uint64 strange_part_item_id = 1; + optional uint64 item_item_id = 2; +} + +message CMsgApplyStrangeRestriction { + optional uint64 strange_part_item_id = 1; + optional uint64 item_item_id = 2; + optional uint32 strange_attr_index = 3; +} + +message CMsgApplyUpgradeCard { + optional uint64 upgrade_card_item_id = 1; + optional uint64 subject_item_id = 2; +} + +message CSOEconItemAttribute { + optional uint32 def_index = 1; + optional uint32 value = 2; + optional bytes value_bytes = 3; +} + +message CSOEconItemEquipped { + optional uint32 new_class = 1; + optional uint32 new_slot = 2; +} + +message CSOEconItem { + optional uint64 id = 1; + optional uint32 account_id = 2; + optional uint32 inventory = 3; + optional uint32 def_index = 4; + optional uint32 quantity = 5; + optional uint32 level = 6; + optional uint32 quality = 7; + optional uint32 flags = 8 [default = 0]; + optional uint32 origin = 9; + optional string custom_name = 10; + optional string custom_desc = 11; + repeated CSOEconItemAttribute attribute = 12; + optional CSOEconItem interior_item = 13; + optional bool in_use = 14 [default = false]; + optional uint32 style = 15 [default = 0]; + optional uint64 original_id = 16 [default = 0]; + optional bool contains_equipped_state = 17; + repeated CSOEconItemEquipped equipped_state = 18; + optional bool contains_equipped_state_v2 = 19; +} + +message CMsgAdjustItemEquippedState { + optional uint64 item_id = 1; + optional uint32 new_class = 2; + optional uint32 new_slot = 3; +} + +message CMsgSortItems { + optional uint32 sort_type = 1; +} + +message CSOEconClaimCode { + optional uint32 account_id = 1; + optional uint32 code_type = 2; + optional uint32 time_acquired = 3; + optional string code = 4; +} + +message CMsgStoreGetUserData { + optional fixed32 price_sheet_version = 1; +} + +message CMsgStoreGetUserDataResponse { + optional int32 result = 1; + optional int32 currency = 2; + optional string country = 3; + optional fixed32 price_sheet_version = 4; + optional uint64 experiment_data = 5 [default = 0]; + optional int32 featured_item_idx = 6; + optional bool show_hat_descriptions = 7 [default = true]; + optional bytes price_sheet = 8; + optional int32 default_item_sort = 9 [default = 0]; + repeated uint32 popular_items = 10; +} + +message CMsgUpdateItemSchema { + optional bytes items_game = 1; + optional fixed32 item_schema_version = 2; + optional string items_game_url = 3; + optional bytes signature = 4; +} + +message CMsgGCError { + optional string error_text = 1; +} + +message CMsgRequestInventoryRefresh { +} + +message CMsgConVarValue { + optional string name = 1; + optional string value = 2; +} + +message CMsgReplicateConVars { + repeated CMsgConVarValue convars = 1; +} + +message CMsgUseItem { + optional uint64 item_id = 1; + optional fixed64 target_steam_id = 2; + repeated uint32 gift__potential_targets = 3; + optional uint32 duel__class_lock = 4; + optional fixed64 initiator_steam_id = 5; + optional bool itempack__ack_immediately = 6; +} + +message CMsgReplayUploadedToYouTube { + optional string youtube_url = 1; + optional string youtube_account_name = 2; + optional uint64 session_id = 3; +} + +message CMsgConsumableExhausted { + optional int32 item_def_id = 1; +} + +message CMsgItemAcknowledged { + optional uint32 account_id = 1; + optional uint32 inventory = 2; + optional uint32 def_index = 3; + optional uint32 quality = 4; + optional uint32 rarity = 5; + optional uint32 origin = 6; + optional uint32 is_strange = 7; + optional uint32 is_unusual = 8; + optional float wear = 9; +} + +message CMsgSetPresetItemPosition { + optional uint32 class_id = 1; + optional uint32 preset_id = 2; + optional uint32 slot_id = 3; + optional uint64 item_id = 4; +} + +message CMsgSetItemPositions { + message ItemPosition { + optional uint64 item_id = 1; + optional uint32 position = 2; + } + + repeated CMsgSetItemPositions.ItemPosition item_positions = 1; +} + +message CSOEconItemPresetInstance { + optional uint32 class_id = 2 [(key_field) = true]; + optional uint32 preset_id = 3 [(key_field) = true]; + optional uint32 slot_id = 4 [(key_field) = true]; + optional uint64 item_id = 5; +} + +message CMsgSelectPresetForClass { + optional uint32 class_id = 1; + optional uint32 preset_id = 2; +} + +message CSOClassPresetClientData { + optional uint32 account_id = 1; + optional uint32 class_id = 2; + optional uint32 active_preset_id = 3; +} + +message CMsgGCReportAbuse { + optional fixed64 target_steam_id = 1; + optional string description = 4; + optional uint64 gid = 5; + optional uint32 abuse_type = 2; + optional uint32 content_type = 3; + optional fixed32 target_game_server_ip = 6; + optional uint32 target_game_server_port = 7; +} + +message CMsgGCReportAbuseResponse { + optional fixed64 target_steam_id = 1; + optional uint32 result = 2; + optional string error_message = 3; +} + +message CMsgGCNameItemNotification { + optional fixed64 player_steamid = 1; + optional uint32 item_def_index = 2; + optional string item_name_custom = 3; +} + +message CMsgGCClientDisplayNotification { + optional string notification_title_localization_key = 1; + optional string notification_body_localization_key = 2; + repeated string body_substring_keys = 3; + repeated string body_substring_values = 4; +} + +message CMsgGCShowItemsPickedUp { + optional fixed64 player_steamid = 1; +} + +message CMsgUpdatePeriodicEvent { + optional uint32 account_id = 1; + optional uint32 event_type = 2; + optional uint32 amount = 3; +} + +message CMsgGCIncrementKillCountResponse { + optional uint32 killer_account_id = 1 [(key_field) = true]; + optional uint32 num_kills = 2; + optional uint32 item_def = 3; + optional uint32 level_type = 4; +} + +message CMsgGCRemoveStrangePart { + optional uint64 item_id = 1; + optional uint32 strange_part_score_type = 2; +} + +message CMsgGCRemoveUpgradeCard { + optional uint64 item_id = 1; + optional uint32 attribute_index = 2; +} + +message CMsgGCRemoveCustomizationAttributeSimple { + optional uint64 item_id = 1; +} + +message CMsgGCResetStrangeScores { + optional uint64 item_id = 1; +} + +message CMsgGCItemPreviewItemBoughtNotification { + optional uint32 item_def_index = 1; +} + +message CMsgGCStorePurchaseCancel { + optional uint64 txn_id = 1; +} + +message CMsgGCStorePurchaseCancelResponse { + optional uint32 result = 1; +} + +message CMsgGCStorePurchaseFinalize { + optional uint64 txn_id = 1; +} + +message CMsgGCStorePurchaseFinalizeResponse { + optional uint32 result = 1; + repeated uint64 item_ids = 2; +} + +message CMsgGCBannedWordListRequest { + optional uint32 ban_list_group_id = 1; + optional uint32 word_id = 2; +} + +message CMsgGCGiftedItems { + optional uint64 gifter_steam_id = 1; + optional bool was_random_person = 2; + repeated uint32 recipient_account_ids = 3; +} + +message CMsgGCCollectItem { + optional uint64 collection_item_id = 1; + optional uint64 subject_item_id = 2; +} + +message CMsgGCClientMarketDataRequest { + optional uint32 user_currency = 1; +} + +message CMsgGCClientMarketDataEntry { + optional uint32 item_def_index = 1; + optional uint32 item_quality = 2; + optional uint32 item_sell_listings = 3; + optional uint32 price_in_local_currency = 4; +} + +message CMsgGCClientMarketData { + repeated CMsgGCClientMarketDataEntry entries = 1; +} + +message CMsgApplyToolToItem { + optional uint64 tool_item_id = 1; + optional uint64 subject_item_id = 2; +} + +message CMsgApplyToolToBaseItem { + optional uint64 tool_item_id = 1; + optional uint32 baseitem_def_index = 2; +} + +message CMsgRecipeComponent { + optional uint64 subject_item_id = 1; + optional uint64 attribute_index = 2; +} + +message CMsgFulfillDynamicRecipeComponent { + optional uint64 tool_item_id = 1; + repeated CMsgRecipeComponent consumption_components = 2; +} + +message CMsgSetItemEffectVerticalOffset { + optional uint64 item_id = 1; + optional float offset = 2; +} + +message CMsgSetHatEffectUseHeadOrigin { + optional uint64 item_id = 1; + optional bool use_head = 2; +} + +message CMsgDeliverGiftResponseGiver { + optional uint32 response_code = 1; + optional string receiver_account_name = 2; +} + +message CSOEconGameAccountForGameServers { + optional bool disable_party_quest_progress = 6 [default = false]; +} + +message CWorkshop_PopulateItemDescriptions_Request { + message SingleItemDescription { + optional uint32 gameitemid = 1; + optional string item_description = 2; + } + + message ItemDescriptionsLanguageBlock { + optional string language = 1; + repeated CWorkshop_PopulateItemDescriptions_Request.SingleItemDescription descriptions = 2; + } + + optional uint32 appid = 1; + repeated CWorkshop_PopulateItemDescriptions_Request.ItemDescriptionsLanguageBlock languages = 2; +} + +message CWorkshop_GetContributors_Request { + optional uint32 appid = 1; + optional uint32 gameitemid = 2; +} + +message CWorkshop_GetContributors_Response { + repeated fixed64 contributors = 1; +} + +message CWorkshop_SetItemPaymentRules_Request { + message WorkshopItemPaymentRule { + optional uint64 workshop_file_id = 1; + optional float revenue_percentage = 2; + optional string rule_description = 3; + } + + message WorkshopDirectPaymentRule { + optional uint64 workshop_file_id = 1; + optional string rule_description = 2; + } + + message PartnerItemPaymentRule { + optional uint32 account_id = 1; + optional float revenue_percentage = 2; + optional string rule_description = 3; + } + + optional uint32 appid = 1; + optional uint32 gameitemid = 2; + repeated CWorkshop_SetItemPaymentRules_Request.WorkshopItemPaymentRule associated_workshop_files = 3; + repeated CWorkshop_SetItemPaymentRules_Request.PartnerItemPaymentRule partner_accounts = 4; + optional CWorkshop_SetItemPaymentRules_Request.WorkshopDirectPaymentRule associated_workshop_file_for_direct_payments = 7; +} + +message CWorkshop_SetItemPaymentRules_Response { +} + +message CWorkshop_AddSpecialPayment_Request { + optional uint32 appid = 1; + optional uint32 gameitemid = 2; + optional string date = 3; + optional uint64 payment_us_usd = 4; + optional uint64 payment_row_usd = 5; +} + +message CWorkshop_AddSpecialPayment_Response { +} + +message CWorkshop_GetSpecialPayments_Request { + optional uint32 appid = 1; + optional uint32 gameitemid = 2; + optional string date = 3; +} + +message CWorkshop_GetSpecialPayments_Response { + message SpecialPayment { + optional uint32 appid = 1; + optional uint32 gameitemid = 2; + optional string date = 3; + optional uint64 net_payment_us_usd = 4; + optional uint64 net_payment_row_usd = 5; + } + + repeated CWorkshop_GetSpecialPayments_Response.SpecialPayment special_payments = 1; +} diff --git a/dll/gc_tf2/econ_gcmessages.proto b/dll/gc_tf2/econ_gcmessages.proto new file mode 100644 index 000000000..be1cecd9c --- /dev/null +++ b/dll/gc_tf2/econ_gcmessages.proto @@ -0,0 +1,370 @@ +import "steammessages.proto"; + +option optimize_for = SPEED; +option cc_generic_services = false; + +package gamecoordinator.tf2; + +enum EGCItemMsg { + k_EMsgGCBase = 1000; + k_EMsgGCSetSingleItemPosition = 1001; + k_EMsgGCCraft = 1002; + k_EMsgGCCraftResponse = 1003; + k_EMsgGCDelete = 1004; + k_EMsgGCVerifyCacheSubscription = 1005; + k_EMsgGCNameItem = 1006; + k_EMsgGCUnlockCrate = 1007; + k_EMsgGCUnlockCrateResponse = 1008; + k_EMsgGCPaintItem = 1009; + k_EMsgGCPaintItemResponse = 1010; + k_EMsgGCGoldenWrenchBroadcast = 1011; + k_EMsgGCMOTDRequest = 1012; + k_EMsgGCMOTDRequestResponse = 1013; + k_EMsgGCNameBaseItem = 1019; + k_EMsgGCNameBaseItemResponse = 1020; + k_EMsgGCRemoveSocketItem_DEPRECATED = 1021; + k_EMsgGCRemoveSocketItemResponse_DEPRECATED = 1022; + k_EMsgGCCustomizeItemTexture = 1023; + k_EMsgGCCustomizeItemTextureResponse = 1024; + k_EMsgGCUseItemRequest = 1025; + k_EMsgGCUseItemResponse = 1026; + k_EMsgGCRespawnPostLoadoutChange = 1029; + k_EMsgGCRemoveItemName = 1030; + k_EMsgGCRemoveItemPaint = 1031; + k_EMsgGCGiftWrapItem = 1032; + k_EMsgGCGiftWrapItemResponse = 1033; + k_EMsgGCDeliverGift = 1034; + k_EMsgGCDeliverGiftResponseReceiver = 1036; + k_EMsgGCUnwrapGiftRequest = 1037; + k_EMsgGCUnwrapGiftResponse = 1038; + k_EMsgGCSetItemStyle = 1039; + k_EMsgGCUsedClaimCodeItem = 1040; + k_EMsgGCSortItems = 1041; + k_EMsgGC_RevolvingLootList_DEPRECATED = 1042; + k_EMsgGCLookupAccount = 1043; + k_EMsgGCLookupAccountResponse = 1044; + k_EMsgGCLookupAccountName = 1045; + k_EMsgGCLookupAccountNameResponse = 1046; + k_EMsgGCUpdateItemSchema = 1049; + k_EMsgGCRequestInventoryRefresh = 1050; + k_EMsgGCRemoveCustomTexture = 1051; + k_EMsgGCRemoveCustomTextureResponse = 1052; + k_EMsgGCRemoveMakersMark = 1053; + k_EMsgGCRemoveMakersMarkResponse = 1054; + k_EMsgGCRemoveUniqueCraftIndex = 1055; + k_EMsgGCRemoveUniqueCraftIndexResponse = 1056; + k_EMsgGCSaxxyBroadcast = 1057; + k_EMsgGCBackpackSortFinished = 1058; + k_EMsgGCAdjustItemEquippedState = 1059; + k_EMsgGCCollectItem = 1061; + k_EMsgGCItemAcknowledged = 1062; + k_EMsgGCPresets_SelectPresetForClass = 1063; + k_EMsgGCPresets_SetItemPosition = 1064; + k_EMsgGC_ReportAbuse = 1065; + k_EMsgGC_ReportAbuseResponse = 1066; + k_EMsgGCPresets_SelectPresetForClassReply = 1067; + k_EMsgGCNameItemNotification = 1068; + k_EMsgGCClientDisplayNotification = 1069; + k_EMsgGCApplyStrangePart = 1070; + k_EMsgGC_IncrementKillCountAttribute = 1071; + k_EMsgGC_IncrementKillCountResponse = 1072; + k_EMsgGCRemoveStrangePart = 1073; + k_EMsgGCResetStrangeScores = 1074; + k_EMsgGCGiftedItems = 1075; + k_EMsgGCApplyUpgradeCard = 1077; + k_EMsgGCRemoveUpgradeCard = 1078; + k_EMsgGCApplyStrangeRestriction = 1079; + k_EMsgGCClientRequestMarketData = 1080; + k_EMsgGCClientRequestMarketDataResponse = 1081; + k_EMsgGCApplyXifier = 1082; + k_EMsgGCApplyXifierResponse = 1083; + k_EMsgGC_TrackUniquePlayerPairEvent = 1084; + k_EMsgGCFulfillDynamicRecipeComponent = 1085; + k_EMsgGCFulfillDynamicRecipeComponentResponse = 1086; + k_EMsgGCSetItemEffectVerticalOffset = 1087; + k_EMsgGCSetHatEffectUseHeadOrigin = 1088; + k_EMsgGCItemEaterRecharger = 1089; + k_EMsgGCItemEaterRechargerResponse = 1090; + k_EMsgGCApplyBaseItemXifier = 1091; + k_EMsgGCApplyClassTransmogrifier = 1092; + k_EMsgGCApplyHalloweenSpellbookPage = 1093; + k_EMsgGCRemoveKillStreak = 1094; + k_EMsgGCRemoveKillStreakResponse = 1095; + k_EMsgGCTFSpecificItemBroadcast = 1096; + k_EMsgGC_IncrementKillCountAttribute_Multiple = 1097; + k_EMsgGCDeliverGiftResponseGiver = 1098; + k_EMsgGCSetItemPositions = 1100; + k_EMsgGCLookupMultipleAccountNames = 1101; + k_EMsgGCLookupMultipleAccountNamesResponse = 1102; + k_EMsgGCTradingBase = 1500; + k_EMsgGCTrading_InitiateTradeRequest = 1501; + k_EMsgGCTrading_InitiateTradeResponse = 1502; + k_EMsgGCTrading_StartSession = 1503; + k_EMsgGCTrading_SessionClosed = 1509; + k_EMsgGCTrading_CancelSession = 1510; + k_EMsgGCTrading_InitiateTradeRequestResponse = 1514; + k_EMsgGCServerBrowser_FavoriteServer = 1601; + k_EMsgGCServerBrowser_BlacklistServer = 1602; + k_EMsgGCServerRentalsBase = 1700; + k_EMsgGCItemPreviewCheckStatus = 1701; + k_EMsgGCItemPreviewStatusResponse = 1702; + k_EMsgGCItemPreviewRequest = 1703; + k_EMsgGCItemPreviewRequestResponse = 1704; + k_EMsgGCItemPreviewExpire = 1705; + k_EMsgGCItemPreviewExpireNotification = 1706; + k_EMsgGCItemPreviewItemBoughtNotification = 1708; + k_EMsgGCDev_NewItemRequest = 2001; + k_EMsgGCDev_NewItemRequestResponse = 2002; + k_EMsgGCDev_DebugRollLootRequest = 2003; + k_EMsgGCStoreGetUserData = 2500; + k_EMsgGCStoreGetUserDataResponse = 2501; + k_EMsgGCStorePurchaseInit_DEPRECATED = 2502; + k_EMsgGCStorePurchaseInitResponse_DEPRECATED = 2503; + k_EMsgGCStorePurchaseFinalize = 2512; + k_EMsgGCStorePurchaseFinalizeResponse = 2513; + k_EMsgGCStorePurchaseCancel = 2514; + k_EMsgGCStorePurchaseCancelResponse = 2515; + k_EMsgGCStorePurchaseQueryTxn = 2508; + k_EMsgGCStorePurchaseQueryTxnResponse = 2509; + k_EMsgGCStorePurchaseInit = 2510; + k_EMsgGCStorePurchaseInitResponse = 2511; + k_EMsgGCToGCDirtySDOCache = 2516; + k_EMsgGCToGCDirtyMultipleSDOCache = 2517; + k_EMsgGCToGCUpdateSQLKeyValue = 2518; + k_EMsgGCToGCBroadcastConsoleCommand = 2521; + k_EMsgGCServerVersionUpdated = 2522; + k_EMsgGCApplyAutograph = 2523; + k_EMsgGCToGCWebAPIAccountChanged = 2524; + k_EMsgGCRequestAnnouncements = 2525; + k_EMsgGCRequestAnnouncementsResponse = 2526; + k_EMsgGCRequestPassportItemGrant = 2527; + k_EMsgGCClientVersionUpdated = 2528; + k_EMsgGCItemPurgatory_FinalizePurchase = 2531; + k_EMsgGCItemPurgatory_FinalizePurchaseResponse = 2532; + k_EMsgGCItemPurgatory_RefundPurchase = 2533; + k_EMsgGCItemPurgatory_RefundPurchaseResponse = 2534; + k_EMsgGCToGCPlayerStrangeCountAdjustments = 2535; + k_EMsgGCRequestStoreSalesData = 2536; + k_EMsgGCRequestStoreSalesDataResponse = 2537; + k_EMsgGCRequestStoreSalesDataUpToDateResponse = 2538; + k_EMsgGCToGCPingRequest = 2539; + k_EMsgGCToGCPingResponse = 2540; + k_EMsgGCToGCGetUserSessionServer = 2541; + k_EMsgGCToGCGetUserSessionServerResponse = 2542; + k_EMsgGCToGCGetUserServerMembers = 2543; + k_EMsgGCToGCGetUserServerMembersResponse = 2544; + k_EMsgGCToGCGrantSelfMadeItemToAccount = 2555; + k_EMsgGCToGCThankedByNewUser = 2556; + k_EMsgGCShuffleCrateContents = 2557; + k_EMsgGCQuestObjective_Progress = 2558; + k_EMsgGCQuestCompleted = 2559; + k_EMsgGCApplyDuckToken = 2560; + k_EMsgGCQuestObjective_PointsChange = 2562; + k_EMsgGCQuestObjective_RequestLoanerItems = 2564; + k_EMsgGCQuestObjective_RequestLoanerResponse = 2565; + k_EMsgGCApplyStrangeCountTransfer = 2566; + k_EMsgGCCraftCollectionUpgrade = 2567; + k_EMsgGCCraftHalloweenOffering = 2568; + k_EMsgGCQuestDiscard_Request = 2569; + k_EMsgGCRemoveGiftedBy = 2570; + k_EMsgGCRemoveGiftedByResponse = 2571; + k_EMsgGCRemoveFestivizer = 2572; + k_EMsgGCRemoveFestivizerResponse = 2573; + k_EMsgGCCraftCommonStatClock = 2574; +} + +enum EGCMsgResponse { + k_EGCMsgResponseOK = 0; + k_EGCMsgResponseDenied = 1; + k_EGCMsgResponseServerError = 2; + k_EGCMsgResponseTimeout = 3; + k_EGCMsgResponseInvalid = 4; + k_EGCMsgResponseNoMatch = 5; + k_EGCMsgResponseUnknownError = 6; + k_EGCMsgResponseNotLoggedOn = 7; + k_EGCMsgFailedToCreate = 8; +} + +enum EUnlockStyle { + k_UnlockStyle_Succeeded = 0; + k_UnlockStyle_Failed_PreReq = 1; + k_UnlockStyle_Failed_CantAfford = 2; + k_UnlockStyle_Failed_CantCommit = 3; + k_UnlockStyle_Failed_CantLockCache = 4; + k_UnlockStyle_Failed_CantAffordAttrib = 5; + k_UnlockStyle_Failed_CantAffordGem = 6; +} + +enum EItemPurgatoryResponse_Finalize { + k_ItemPurgatoryResponse_Finalize_Succeeded = 0; + k_ItemPurgatoryResponse_Finalize_Failed_Incomplete = 1; + k_ItemPurgatoryResponse_Finalize_Failed_ItemsNotInPurgatory = 2; + k_ItemPurgatoryResponse_Finalize_Failed_CouldNotFindItems = 3; + k_ItemPurgatoryResponse_Finalize_Failed_NoSOCache = 4; + k_ItemPurgatoryResponse_Finalize_BackpackFull = 5; +} + +enum EItemPurgatoryResponse_Refund { + k_ItemPurgatoryResponse_Refund_Succeeded = 0; + k_ItemPurgatoryResponse_Refund_Failed_ItemNotInPurgatory = 1; + k_ItemPurgatoryResponse_Refund_Failed_CouldNotFindItem = 2; + k_ItemPurgatoryResponse_Refund_Failed_NoSOCache = 3; + k_ItemPurgatoryResponse_Refund_Failed_NoDetail = 4; + k_ItemPurgatoryResponse_Refund_Failed_NexonWebAPI = 5; +} + +message CMsgApplyAutograph { + optional uint64 autograph_item_id = 1; + optional uint64 item_item_id = 2; +} + +message CMsgEconPlayerStrangeCountAdjustment { + message CStrangeCountAdjustment { + optional uint32 event_type = 1; + optional uint64 item_id = 2; + optional uint32 adjustment = 3; + } + + optional uint32 account_id = 1; + repeated CMsgEconPlayerStrangeCountAdjustment.CStrangeCountAdjustment strange_count_adjustments = 2; +} + +message CMsgRequestItemPurgatory_FinalizePurchase { + repeated uint64 item_ids = 1; +} + +message CMsgRequestItemPurgatory_FinalizePurchaseResponse { + optional uint32 result = 1; +} + +message CMsgRequestItemPurgatory_RefundPurchase { + optional uint64 item_id = 1; +} + +message CMsgRequestItemPurgatory_RefundPurchaseResponse { + optional uint32 result = 1; +} + +message CMsgCraftingResponse { + repeated uint64 item_ids = 1; +} + +message CMsgGCRequestStoreSalesData { + optional uint32 version = 1; + optional uint32 currency = 2; +} + +message CMsgGCRequestStoreSalesDataResponse { + message Price { + optional uint32 item_def = 1; + optional uint32 price = 2; + } + + repeated CMsgGCRequestStoreSalesDataResponse.Price sale_price = 1; + optional uint32 version = 2; + optional uint32 expiration_time = 3; +} + +message CMsgGCRequestStoreSalesDataUpToDateResponse { + optional uint32 version = 1; + optional uint32 expiration_time = 2; +} + +message CMsgGCToGCPingRequest { +} + +message CMsgGCToGCPingResponse { +} + +message CMsgGCToGCGetUserSessionServer { + optional uint32 account_id = 1; +} + +message CMsgGCToGCGetUserSessionServerResponse { + optional fixed64 server_steam_id = 1; +} + +message CMsgGCToGCGetUserServerMembers { + optional uint32 account_id = 1; + optional uint32 max_spectators = 2; +} + +message CMsgGCToGCGetUserServerMembersResponse { + repeated uint32 member_account_id = 1; +} + +message CMsgLookupMultipleAccountNames { + repeated uint32 accountids = 1 [packed = true]; +} + +message CMsgLookupMultipleAccountNamesResponse { + message Account { + optional uint32 accountid = 1; + optional string persona = 2; + } + + repeated CMsgLookupMultipleAccountNamesResponse.Account accounts = 1; +} + +message CMsgGCToGCGrantSelfMadeItemToAccount { + optional uint32 item_def_index = 1; + optional uint32 accountid = 2; +} + +message CMsgGCToGCThankedByNewUser { + optional uint32 new_user_accountid = 1; + optional uint32 thanked_user_accountid = 2; +} + +message CMsgGCShuffleCrateContents { + optional uint64 crate_item_id = 1; + optional string user_code_string = 2; +} + +message CMsgGCQuestObjective_Progress { + optional uint64 quest_id = 1; + optional uint32 quest_attrib_index = 2; + optional uint32 delta = 3; + optional fixed64 owner_steamid = 4; +} + +message CMsgGCQuestObjective_PointsChange { + optional uint64 quest_id = 1; + optional fixed64 owner_steamid = 4; + optional bool update_base_points = 5 [default = false]; + optional uint32 points_0 = 6; + optional uint32 points_1 = 7; + optional uint32 points_2 = 8; +} + +message CMsgGCQuestComplete_Request { + optional uint64 quest_id = 1; +} + +message CMsgGCQuestCompleted { +} + +message CMsgGCQuestObjective_RequestLoanerItems { + optional uint64 quest_id = 1; +} + +message CMsgGCQuestObjective_RequestLoanerResponse { +} + +message CMsgCraftCollectionUpgrade { + repeated uint64 item_id = 1; +} + +message CMsgCraftHalloweenOffering { + optional uint64 tool_id = 1; + repeated uint64 item_id = 2; +} + +message CMsgCraftCommonStatClock { + optional uint64 tool_id = 1; + repeated uint64 item_id = 2; +} + +message CMsgGCQuestDiscard_Request { + optional uint64 quest_id = 1; +} diff --git a/dll/gc_tf2/gcsdk_gcmessages.proto b/dll/gc_tf2/gcsdk_gcmessages.proto new file mode 100644 index 000000000..9fea4f735 --- /dev/null +++ b/dll/gc_tf2/gcsdk_gcmessages.proto @@ -0,0 +1,188 @@ +import "steammessages.proto"; + +option optimize_for = SPEED; +option cc_generic_services = false; + +package gamecoordinator.tf2; + +enum PartnerAccountType { + PARTNER_NONE = 0; + PARTNER_PERFECT_WORLD = 1; + PARTNER_NEXON = 2; +} + +enum GCConnectionStatus { + GCConnectionStatus_HAVE_SESSION = 0; + GCConnectionStatus_GC_GOING_DOWN = 1; + GCConnectionStatus_NO_SESSION = 2; + GCConnectionStatus_NO_SESSION_IN_LOGON_QUEUE = 3; + GCConnectionStatus_NO_STEAM = 4; + GCConnectionStatus_SUSPENDED = 5; +} + +message CMsgSOIDOwner { + optional uint32 type = 1; + optional uint64 id = 2; +} + +message CMsgSOSingleObject { + optional fixed64 owner = 1; + optional int32 type_id = 2; + optional bytes object_data = 3; + optional fixed64 version = 4; + optional CMsgSOIDOwner owner_soid = 5; + optional uint32 service_id = 6; +} + +message CMsgSOMultipleObjects { + message SingleObject { + optional int32 type_id = 1; + optional bytes object_data = 2; + } + + optional fixed64 owner = 1; + repeated CMsgSOMultipleObjects.SingleObject objects = 2; + optional fixed64 version = 3; + optional CMsgSOIDOwner owner_soid = 6; + optional uint32 service_id = 7; +} + +message CMsgSOCacheSubscribed { + message SubscribedType { + optional int32 type_id = 1; + repeated bytes object_data = 2; + } + + optional fixed64 owner = 1; + repeated CMsgSOCacheSubscribed.SubscribedType objects = 2; + optional fixed64 version = 3; + optional CMsgSOIDOwner owner_soid = 4; + optional uint32 service_id = 5; + repeated uint32 service_list = 6; + optional fixed64 sync_version = 7; +} + +message CMsgSOCacheSubscribedUpToDate { + optional fixed64 version = 1; + optional CMsgSOIDOwner owner_soid = 2; + optional uint32 service_id = 3; + repeated uint32 service_list = 4; + optional fixed64 sync_version = 5; +} + +message CMsgSOCacheUnsubscribed { + optional fixed64 owner = 1; +} + +message CMsgSOCacheSubscriptionCheck { + optional fixed64 owner = 1; + optional fixed64 version = 2; + optional CMsgSOIDOwner owner_soid = 3; + optional uint32 service_id = 4; + repeated uint32 service_list = 5; + optional fixed64 sync_version = 6; +} + +message CMsgSOCacheSubscriptionRefresh { + optional fixed64 owner = 1; + optional CMsgSOIDOwner owner_soid = 2; +} + +message CMsgSOCacheVersion { + optional fixed64 version = 1; +} + +message CMsgGCMultiplexMessage { + optional uint32 msgtype = 1; + optional bytes payload = 2; + repeated fixed64 steamids = 3; +} + +message CGCToGCMsgMasterAck { + optional uint32 dir_index = 1; + optional string machine_name = 3; + optional string process_name = 4; + repeated uint32 type_instances = 5; +} + +message CGCToGCMsgMasterAck_Response { + optional int32 eresult = 1 [default = 2]; +} + +message CGCToGCMsgMasterStartupComplete { + message GCInfo { + optional uint32 dir_index = 1; + optional string machine_name = 2; + } + + repeated CGCToGCMsgMasterStartupComplete.GCInfo gc_info = 1; +} + +message CGCToGCMsgRouted { + optional uint32 msg_type = 1; + optional fixed64 sender_id = 2; + optional bytes net_message = 3; +} + +message CGCToGCMsgRoutedReply { + optional uint32 msg_type = 1; + optional bytes net_message = 2; +} + +message CMsgGCUpdateSubGCSessionInfo { + message CMsgUpdate { + optional fixed64 steamid = 1; + optional fixed32 ip = 2; + optional bool trusted = 3; + } + + repeated CMsgGCUpdateSubGCSessionInfo.CMsgUpdate updates = 1; +} + +message CMsgGCRequestSubGCSessionInfo { + optional fixed64 steamid = 1; +} + +message CMsgGCRequestSubGCSessionInfoResponse { + optional fixed32 ip = 1; + optional bool trusted = 2; +} + +message CMsgGCToGCIncrementRecruitmentLevel { + optional fixed64 steamid = 1; +} + +message CMsgSOCacheHaveVersion { + optional CMsgSOIDOwner soid = 1; + optional fixed64 version = 2; + optional uint32 service_id = 3; +} + +message CMsgConnectionStatus { + optional GCConnectionStatus status = 1 [default = GCConnectionStatus_HAVE_SESSION]; + optional uint32 client_session_need = 2; + optional int32 queue_position = 3; + optional int32 queue_size = 4; + optional int32 wait_seconds = 5; + optional int32 estimated_wait_seconds_remaining = 6; +} + +message CMsgGCToGCSOCacheSubscribe { + message CMsgHaveVersions { + optional uint32 service_id = 1; + optional uint64 version = 2; + } + + optional fixed64 subscriber = 1; + optional fixed64 subscribe_to = 2; + optional fixed64 sync_version = 3; + repeated CMsgGCToGCSOCacheSubscribe.CMsgHaveVersions have_versions = 4; +} + +message CMsgGCToGCSOCacheUnsubscribe { + optional fixed64 subscriber = 1; + optional fixed64 unsubscribe_from = 2; +} + +message CMsgGCClientPing { +} diff --git a/dll/gc_tf2/gcsystemmsgs.proto b/dll/gc_tf2/gcsystemmsgs.proto new file mode 100644 index 000000000..e30f3840e --- /dev/null +++ b/dll/gc_tf2/gcsystemmsgs.proto @@ -0,0 +1,180 @@ +option optimize_for = SPEED; +option cc_generic_services = false; + +package gamecoordinator.tf2; + +enum EGCSystemMsg { + k_EGCMsgInvalid = 0; + k_EGCMsgMulti = 1; + k_EGCMsgGenericReply = 10; + k_EGCMsgSystemBase = 50; + k_EGCMsgAchievementAwarded = 51; + k_EGCMsgConCommand = 52; + k_EGCMsgStartPlaying = 53; + k_EGCMsgStopPlaying = 54; + k_EGCMsgStartGameserver = 55; + k_EGCMsgStopGameserver = 56; + k_EGCMsgWGRequest = 57; + k_EGCMsgWGResponse = 58; + k_EGCMsgGetUserGameStatsSchema = 59; + k_EGCMsgGetUserGameStatsSchemaResponse = 60; + k_EGCMsgGetUserStatsDEPRECATED = 61; + k_EGCMsgGetUserStatsResponse = 62; + k_EGCMsgAppInfoUpdated = 63; + k_EGCMsgValidateSession = 64; + k_EGCMsgValidateSessionResponse = 65; + k_EGCMsgLookupAccountFromInput = 66; + k_EGCMsgSendHTTPRequest = 67; + k_EGCMsgSendHTTPRequestResponse = 68; + k_EGCMsgPreTestSetup = 69; + k_EGCMsgRecordSupportAction = 70; + k_EGCMsgGetAccountDetails_DEPRECATED = 71; + k_EGCMsgReceiveInterAppMessage = 73; + k_EGCMsgFindAccounts = 74; + k_EGCMsgPostAlert = 75; + k_EGCMsgGetLicenses = 76; + k_EGCMsgGetUserStats = 77; + k_EGCMsgGetCommands = 78; + k_EGCMsgGetCommandsResponse = 79; + k_EGCMsgAddFreeLicense = 80; + k_EGCMsgAddFreeLicenseResponse = 81; + k_EGCMsgGetIPLocation = 82; + k_EGCMsgGetIPLocationResponse = 83; + k_EGCMsgSystemStatsSchema = 84; + k_EGCMsgGetSystemStats = 85; + k_EGCMsgGetSystemStatsResponse = 86; + k_EGCMsgSendEmail = 87; + k_EGCMsgSendEmailResponse = 88; + k_EGCMsgGetEmailTemplate = 89; + k_EGCMsgGetEmailTemplateResponse = 90; + k_EGCMsgGrantGuestPass = 91; + k_EGCMsgGrantGuestPassResponse = 92; + k_EGCMsgGetAccountDetails = 93; + k_EGCMsgGetAccountDetailsResponse = 94; + k_EGCMsgGetPersonaNames = 95; + k_EGCMsgGetPersonaNamesResponse = 96; + k_EGCMsgMultiplexMsg = 97; + k_EGCMsgWebAPIRegisterInterfaces = 101; + k_EGCMsgWebAPIJobRequest = 102; + k_EGCMsgWebAPIJobRequestHttpResponse = 104; + k_EGCMsgWebAPIJobRequestForwardResponse = 105; + k_EGCMsgMemCachedGet = 200; + k_EGCMsgMemCachedGetResponse = 201; + k_EGCMsgMemCachedSet = 202; + k_EGCMsgMemCachedDelete = 203; + k_EGCMsgMemCachedStats = 204; + k_EGCMsgMemCachedStatsResponse = 205; + k_EGCMsgSQLStats = 210; + k_EGCMsgSQLStatsResponse = 211; + k_EGCMsgMasterSetDirectory = 220; + k_EGCMsgMasterSetDirectoryResponse = 221; + k_EGCMsgMasterSetWebAPIRouting = 222; + k_EGCMsgMasterSetWebAPIRoutingResponse = 223; + k_EGCMsgMasterSetClientMsgRouting = 224; + k_EGCMsgMasterSetClientMsgRoutingResponse = 225; + k_EGCMsgSetOptions = 226; + k_EGCMsgSetOptionsResponse = 227; + k_EGCMsgSystemBase2 = 500; + k_EGCMsgGetPurchaseTrustStatus = 501; + k_EGCMsgGetPurchaseTrustStatusResponse = 502; + k_EGCMsgUpdateSession = 503; + k_EGCMsgGCAccountVacStatusChange = 504; + k_EGCMsgCheckFriendship = 505; + k_EGCMsgCheckFriendshipResponse = 506; + k_EGCMsgGetPartnerAccountLink = 507; + k_EGCMsgGetPartnerAccountLinkResponse = 508; + k_EGCMsgVSReportedSuspiciousActivity = 509; + k_EGCMsgAccountTradeBanStatusChange = 510; + k_EGCMsgAccountLockStatusChange = 511; + k_EGCMsgDPPartnerMicroTxns = 512; + k_EGCMsgDPPartnerMicroTxnsResponse = 513; + k_EGCMsgGetIPASN = 514; + k_EGCMsgGetIPASNResponse = 515; + k_EGCMsgGetAppFriendsList = 516; + k_EGCMsgGetAppFriendsListResponse = 517; + k_EGCMsgVacVerificationChange = 518; + k_EGCMsgAccountPhoneNumberChange = 519; + k_EGCMsgAccountTwoFactorChange = 520; + k_EGCMsgCheckClanMembership = 521; + k_EGCMsgCheckClanMembershipResponse = 522; + k_EGCMsgInviteUserToLobby = 523; + k_EGCMsgGetGamePersonalDataCategoriesRequest = 524; + k_EGCMsgGetGamePersonalDataCategoriesResponse = 525; + k_EGCMsgGetGamePersonalDataEntriesRequest = 526; + k_EGCMsgGetGamePersonalDataEntriesResponse = 527; + k_EGCMsgTerminateGamePersonalDataEntriesRequest = 528; + k_EGCMsgTerminateGamePersonalDataEntriesResponse = 529; +} + +enum ESOMsg { + k_ESOMsg_Create = 21; + k_ESOMsg_Update = 22; + k_ESOMsg_Destroy = 23; + k_ESOMsg_CacheSubscribed = 24; + k_ESOMsg_CacheUnsubscribed = 25; + k_ESOMsg_UpdateMultiple = 26; + k_ESOMsg_CacheSubscriptionCheck = 27; + k_ESOMsg_CacheSubscriptionRefresh = 28; + k_ESOMsg_CacheSubscribedUpToDate = 29; +} + +enum EGCBaseClientMsg { + k_EMsgGCPingRequest = 3001; + k_EMsgGCPingResponse = 3002; + k_EMsgGCClientWelcome = 4004; + k_EMsgGCServerWelcome = 4005; + k_EMsgGCClientHello = 4006; + k_EMsgGCServerHello = 4007; + k_EMsgGCClientGoodbye = 4008; + k_EMsgGCServerGoodbye = 4009; +} + +enum EGCToGCMsg { + k_EGCToGCMsgMasterAck = 150; + k_EGCToGCMsgMasterAckResponse = 151; + k_EGCToGCMsgRouted = 152; + k_EGCToGCMsgRoutedReply = 153; + k_EMsgGCUpdateSubGCSessionInfo = 154; + k_EMsgGCRequestSubGCSessionInfo = 155; + k_EMsgGCRequestSubGCSessionInfoResponse = 156; + k_EGCToGCMsgMasterStartupComplete = 157; + k_EMsgGCToGCSOCacheSubscribe = 158; + k_EMsgGCToGCSOCacheUnsubscribe = 159; +} + +message CCommunity_GamePersonalDataCategoryInfo { + optional string type = 1; + optional string localization_token = 2; + optional string template_file = 3; +} + +message CCommunity_GetGamePersonalDataCategories_Request { + optional uint32 appid = 1; +} + +message CCommunity_GetGamePersonalDataCategories_Response { + repeated CCommunity_GamePersonalDataCategoryInfo categories = 1; + optional string app_assets_basename = 2; +} + +message CCommunity_GetGamePersonalDataEntries_Request { + optional uint32 appid = 1; + optional uint64 steamid = 2; + optional string type = 3; + optional string continue_token = 4; +} + +message CCommunity_GetGamePersonalDataEntries_Response { + optional uint32 gceresult = 1; + repeated string entries = 2; + optional string continue_token = 3; +} + +message CCommunity_TerminateGamePersonalDataEntries_Request { + optional uint32 appid = 1; + optional uint64 steamid = 2; +} + +message CCommunity_TerminateGamePersonalDataEntries_Response { + optional uint32 gceresult = 1; +} diff --git a/dll/gc_tf2/tf_gcmessages.proto b/dll/gc_tf2/tf_gcmessages.proto new file mode 100644 index 000000000..29bc0e78a --- /dev/null +++ b/dll/gc_tf2/tf_gcmessages.proto @@ -0,0 +1,1650 @@ +import "steammessages.proto"; +import "base_gcmessages.proto"; + +option optimize_for = SPEED; +option cc_generic_services = false; + +package gamecoordinator.tf2; + +enum ETFGCMsg { + k_EMsgGCReportWarKill = 5001; + k_EMsgGCKickPlayer_DEPRECATED = 5020; + k_EMsgGCStartedTraining_DEPRECATED = 5021; + k_EMsgGCFreeTrial_ChooseMostHelpfulFriend = 5022; + k_EMsgGCRequestTF2Friends = 5023; + k_EMsgGCRequestTF2FriendsResponse = 5024; + k_EMsgGCReplay_SubmitContestEntry = 5026; + k_EMsgGCReplay_SubmitContestEntryResponse = 5027; + k_EMsgGCSaxxy_Awarded = 5029; + k_EMsgGCFreeTrial_ThankedBySomeone = 5028; + k_EMsgGCFreeTrial_ThankedSomeone = 5030; + k_EMsgGCFreeTrial_ConvertedToPremium = 5031; + k_EMsgGCMeetThePyroSilliness_BananaCraft_DEPRECATED = 5032; + k_EMsgGCMVMARG_HighFiveSuccessResponse_DEPRECATED = 5033; + k_EMsgGCMVMARG_HighFiveOnClient_DEPRECATED = 5034; + k_EMsgGCCoaching_AddToCoaches = 5200; + k_EMsgGCCoaching_AddToCoachesResponse = 5201; + k_EMsgGCCoaching_RemoveFromCoaches = 5202; + k_EMsgGCCoaching_RemoveFromCoachesResponse = 5203; + k_EMsgGCCoaching_FindCoach = 5204; + k_EMsgGCCoaching_FindCoachResponse = 5205; + k_EMsgGCCoaching_AskCoach = 5206; + k_EMsgGCCoaching_AskCoachResponse = 5207; + k_EMsgGCCoaching_CoachJoinGame = 5208; + k_EMsgGCCoaching_CoachJoining = 5209; + k_EMsgGCCoaching_CoachJoined = 5210; + k_EMsgGCCoaching_LikeCurrentCoach = 5211; + k_EMsgGCCoaching_RemoveCurrentCoach = 5212; + k_EMsgGCCoaching_AlreadyRatedCoach = 5213; + k_EMsgGC_Duel_Request = 5500; + k_EMsgGC_Duel_Response = 5501; + k_EMsgGC_Duel_Results = 5502; + k_EMsgGC_Duel_Status = 5503; + k_EMsgGC_Halloween_ReservedItem_DEPRECATED = 5600; + k_EMsgGC_Halloween_GrantItem_DEPRECATED = 5601; + k_EMsgGC_Halloween_GrantItemResponse_DEPRECATED = 5604; + k_EMsgGC_Halloween_Cheat_QueryResponse_DEPRECATED = 5605; + k_EMsgGC_Halloween_ItemClaimed_DEPRECATED = 5606; + k_EMsgGC_Halloween_ReservedItem = 5607; + k_EMsgGC_Halloween_GrantItem = 5608; + k_EMsgGC_Halloween_GrantItemResponse = 5609; + k_EMsgGC_Halloween_Cheat_QueryResponse_DEPRECATED_2 = 5610; + k_EMsgGC_Halloween_ItemClaimed_DEPRECATED_2 = 5611; + k_EMsgGC_Halloween_ServerBossEvent = 5612; + k_EMsgGC_Halloween_Merasmus2012 = 5613; + k_EMsgGC_Halloween_UpdateMerasmusLootLevel = 5614; + k_EMsgGC_GameServer_LevelInfo = 5700; + k_EMsgGC_GameServer_AuthChallenge = 5701; + k_EMsgGC_GameServer_AuthChallengeResponse = 5702; + k_EMsgGC_GameServer_CreateIdentity = 5703; + k_EMsgGC_GameServer_CreateIdentityResponse = 5704; + k_EMsgGC_GameServer_List = 5705; + k_EMsgGC_GameServer_ListResponse = 5706; + k_EMsgGC_GameServer_AuthResult = 5707; + k_EMsgGC_GameServer_ResetIdentity = 5708; + k_EMsgGC_GameServer_ResetIdentityResponse = 5709; + k_EMsgGC_Client_UseServerModificationItem = 5710; + k_EMsgGC_Client_UseServerModificationItem_Response = 5711; + k_EMsgGC_GameServer_UseServerModificationItem = 5712; + k_EMsgGC_GameServer_UseServerModificationItem_Response = 5713; + k_EMsgGC_GameServer_ServerModificationItemExpired = 5714; + k_EMsgGC_GameServer_ModificationItemState = 5715; + k_EMsgGC_GameServer_AckPolicy = 5716; + k_EMsgGC_GameServer_AckPolicyResponse = 5717; + k_EMsgGC_QP_ScoreServers = 5800; + k_EMsgGC_QP_ScoreServersResponse = 5801; + k_EMsgGC_QP_PlayerJoining = 5802; + k_EMsgGC_PickupItemEligibility_Query_DEPRECATED = 6000; + k_EMsgGC_PickupItemEligibility_Query_DEPRECATED_2 = 6001; + k_EMsgGC_IncrementKillCountAttribute_DEPRECATED = 6100; + k_EMsgGC_IncrementKillCountResponse_DEPRECATED = 6101; + k_EMsgGCAbandonCurrentGame = 6235; + k_EMsgForceSOCacheResend = 6237; + k_EMsgGCRequestChatChannelList = 6260; + k_EMsgGCRequestChatChannelListResponse = 6261; + k_EMsgGCReadyUp = 6270; + k_EMsgGCKickedFromMatchmakingQueue = 6271; + k_EMsgGCLeaverDetected = 6272; + k_EMsgGCLeaverDetectedResponse = 6287; + k_EMsgGCExitMatchmaking = 6289; + k_EMsgGCMatchmakingProgress = 6293; + k_EMsgGCMvMVictoryInfo = 6294; + k_EMsgGCGameServerMatchmakingStatus = 6295; + k_EMsgGCMvMVictory = 6297; + k_EMsgGCMvMVictoryReply = 6298; + k_EMsgGCGameServerKickingLobby = 6299; + k_EMsgGCLeaveGameAndPrepareToJoinParty = 6300; + k_EMsgGC_UpdatePeriodicEvent = 6400; + k_EMsgGC_DuckLeaderboard_IndividualUpdate = 6401; + k_EMsgGC_Client2GCEconPreviewDataBlockRequest = 6402; + k_EMsgGC_Client2GCEconPreviewDataBlockResponse = 6403; + k_EMsgGC_ClientVerificationChallenge = 6500; + k_EMsgGC_ClientVerificationChallengeResponse = 6501; + k_EMsgGC_ClientVerificationVerboseResponse = 6502; + k_EMsgGC_ClientSetItemSlotAttribute = 6503; + k_EMsgGC_War_IndividualUpdate = 6505; + k_EMsgGC_War_JoinWar = 6506; + k_EMsgGC_War_RequestGlobalStats = 6507; + k_EMsgGC_War_GlobalStatsResponse = 6508; + k_EMsgGC_WorldItemPlacement_Attribute = 6510; + k_EMsgGC_WorldItemPlacement_Update = 6511; + k_EMsgGC_Match_Result = 6512; + k_EMsgGCVoteKickPlayerRequest = 6513; + k_EMsgGCVoteKickPlayerRequestResponse = 6514; + k_EMsgGC_DailyCompetitiveStatsRollup = 6516; + k_EMsgGC_DailyCompetitiveStatsRollup_Response = 6517; + k_EMsgGC_WorldStatusBroadcast = 6518; + k_EMsgGC_ReportPlayer = 6519; + k_EMsgGC_Match_ResultResponse = 6520; + k_EMsgGCGameServerKickingLobbyResponse = 6521; + k_EMsgGCPlayerLeftMatch = 6522; + k_EMsgGCPlayerLeftMatchResponse = 6523; + k_EMsgGCRequestMatchMakerStats = 6524; + k_EMsgGCMatchMakerStatsResponse = 6525; + k_EMsgGCMatchHistoryLoad = 6526; + k_EMsgGC_AcknowledgeXP = 6527; + k_EMsgGCDataCenterPing_Update = 6528; + k_EMsgGC_NotificationAcknowledge = 6529; + k_EMsgGC_NotificationAcknowledgeReply = 6530; + k_EMsgGC_KickPlayerFromLobby = 6531; + k_EMsgGC_SurveyQuestionRequest = 6534; + k_EMsgGC_SurveyQuestionResponse = 6535; + k_EMsgGC_TFClientInit = 6536; + k_EMsgGC_NewMatchForLobbyRequest = 6537; + k_EMsgGC_NewMatchForLobbyResponse = 6538; + k_EMsgGC_ChangeMatchPlayerTeamsRequest = 6539; + k_EMsgGC_ChangeMatchPlayerTeamsResponse = 6540; + k_EMsgGC_QuestIdentify = 6541; + k_EMsgGC_QuestDevGive = 6542; + k_EMsgGCQuestComplete_Debug = 6544; + k_EMsgGC_QuestMapDebug = 6545; + k_EMsgGC_QuestMapUnlockNode = 6547; + k_EMsgGC_QuestMapPurchaseReward = 6549; + k_EMsgGC_SetDisablePartyQuestProgress = 6550; + k_EMsgGCQuestProgressReport = 6553; + k_EMsgGCParty_SetOptions = 6554; + k_EMsgGCParty_SetOptionsResponse = 6555; + k_EMsgGCParty_QueueForMatch = 6556; + k_EMsgGCParty_QueueForMatchResponse = 6557; + k_EMsgGCParty_RemoveFromQueue = 6558; + k_EMsgGCParty_RemoveFromQueueResponse = 6559; + k_EMsgGCParty_InvitePlayer = 6560; + k_EMsgGCParty_RequestJoinPlayer = 6561; + k_EMsgGCParty_SendChat = 6562; + k_EMsgGCParty_ChatMsg = 6563; + k_EMsgGCQuestNodeTurnIn = 6564; + k_EMsgGCConsumePaintKit = 6565; + k_EMsgGC_Painkit_DevGrant = 6566; + k_EMsgGCParty_QueueForStandby = 6567; + k_EMsgGCParty_QueueForStandbyResponse = 6568; + k_EMsgGCParty_RemoveFromStandbyQueue = 6569; + k_EMsgGCParty_RemoveFromStandbyQueueResponse = 6570; + k_EMsgGCParty_ClearPendingPlayer = 6571; + k_EMsgGCParty_ClearPendingPlayerResponse = 6572; + k_EMsgGCParty_ClearOtherPartyRequest = 6573; + k_EMsgGCParty_ClearOtherPartyRequestResponse = 6574; + k_EMsgGCParty_PromoteToLeader = 6575; + k_EMsgGCParty_KickMember = 6576; + k_EMsgGCQuestStrangeEvent = 6577; + k_EMsgGC_AcceptLobbyInvite = 6578; + k_EMsgGC_AcceptLobbyInviteReply = 6579; + k_EMsgGC_SDRTicket = 6580; + k_EMsgGC_ProcessMatchVoteKick = 6581; + k_EMsgGC_ProcessMatchVoteKickResponse = 6582; + k_EMsgGCToGC_SendAccountBannedNotifications = 6584; + k_EMsgGCToGC_SendNotification = 6585; + k_EMsgGCParty_MMError = 6586; + k_EMsgGCDev_GrantWarKill = 10001; +} + +enum EServerModificationItemType { + kGameServerModificationItem_Halloween = 1; +} + +enum ETFSyncedMMMenuStep { + k_eTFSyncedMMMenuStep_Invalid = -1; + k_eTFSyncedMMMenuStep_None = 0; + k_eTFSyncedMMMenuStep_Configuring_Mode = 1; + k_eTFSyncedMMMenuStep_MvM_Selecting_Mode = 2; + k_eTFSyncedMMMenuStep_MvM_Selecting_Tour = 3; + k_eTFSyncedMMMenuStep_MvM_Selecting_Missions = 4; +} + +enum ETFMatchGroup { + option allow_alias = true; + k_eTFMatchGroup_Invalid = -1; + k_eTFMatchGroup_MvM_Practice = 0; + k_eTFMatchGroup_MvM_MannUp = 1; + k_eTFMatchGroup_First = 0; + k_eTFMatchGroup_MvM_Default = 0; + k_eTFMatchGroup_MvM_First = 0; + k_eTFMatchGroup_MvM_Last = 1; + k_eTFMatchGroup_Ladder_6v6 = 2; + k_eTFMatchGroup_Ladder_9v9 = 3; + k_eTFMatchGroup_Ladder_12v12 = 4; + k_eTFMatchGroup_Ladder_Default = 2; + k_eTFMatchGroup_Ladder_First = 2; + k_eTFMatchGroup_Ladder_Last = 4; + k_eTFMatchGroup_Casual_6v6 = 5; + k_eTFMatchGroup_Casual_9v9 = 6; + k_eTFMatchGroup_Casual_12v12 = 7; + k_eTFMatchGroup_Casual_Default = 7; + k_eTFMatchGroup_Casual_First = 5; + k_eTFMatchGroup_Casual_Last = 7; + k_eTFMatchGroup_Event_Placeholder = 8; + k_eTFMatchGroup_Event_Default = 8; + k_eTFMatchGroup_Event_First = 8; + k_eTFMatchGroup_Event_Last = 8; +} + +enum ETFPartyChatType { + k_eTFPartyChatType_Invalid = 0; + k_eTFPartyChatType_MemberChat = 1; + k_eTFPartyChatType_Synthetic_MemberJoin = 1000; + k_eTFPartyChatType_Synthetic_MemberLeave = 1001; + k_eTFPartyChatType_Synthetic_SendFailed = 1002; + k_eTFPartyChatType_Synthetic_MemberOnline = 1003; + k_eTFPartyChatType_Synthetic_MemberOffline = 1004; +} + +enum TF_GC_GameState { + TF_GC_GAMESTATE_STATE_INIT = 0; + TF_GC_GAMESTATE_WAIT_FOR_PLAYERS_TO_LOAD = 1; + TF_GC_GAMESTATE_STRATEGY_TIME = 3; + TF_GC_GAMESTATE_GAME_IN_PROGRESS = 5; + TF_GC_GAMESTATE_POST_GAME = 6; + TF_GC_GAMESTATE_DISCONNECT = 7; + TF_GC_GAMESTATE_LAST = 8; +} + +enum TF_GC_TEAM { + TF_GC_TEAM_DEFENDERS = 0; + TF_GC_TEAM_INVADERS = 1; + TF_GC_TEAM_BROADCASTER = 2; + TF_GC_TEAM_SPECTATOR = 3; + TF_GC_TEAM_PLAYER_POOL = 4; + TF_GC_TEAM_NOTEAM = 5; +} + +enum TFMatchLeaveReason { + TFMatchLeaveReason_UNSPECIFIED = 0; + TFMatchLeaveReason_VOTE_KICK = 1; + TFMatchLeaveReason_IDLE = 2; + TFMatchLeaveReason_ADMIN_KICK = 3; + TFMatchLeaveReason_AWOL = 4; + TFMatchLeaveReason_NO_SHOW = 5; + TFMatchLeaveReason_GC_REMOVED = 6; +} + +enum TFVoteKickReason { + TFVoteKickReason_Invalid = -1; + TFVoteKickReason_Other = 0; + TFVoteKickReason_Cheating = 1; + TFVoteKickReason_Idle = 2; + TFVoteKickReason_Scamming = 3; +} + +enum ServerMatchmakingState { + ServerMatchmakingState_INVALID = 0; + ServerMatchmakingState_NOT_PARTICIPATING = 1; + ServerMatchmakingState_EMPTY = 2; + ServerMatchmakingState_ACTIVE_MATCH_REQUESTING_LATE_JOIN = 3; + ServerMatchmakingState_ACTIVE_MATCH = 4; +} + +enum SurveyQuestionType { + QUESTION_MATCH_QUALITY = 0; + QUESTION_MAP_QUALITY = 1; + QUESTION_COMP_INQUIRY = 2; + QUESTION_CASUAL_INQUIRY = 3; + QUESTION_RANDOM_CRIT = 4; +} + +message CMsgTFGoldenWrenchBroadcast { + optional int32 wrench_number = 1; + optional bool deleted = 2; + optional string user_name = 3; +} + +message CMsgTFSaxxyBroadcast { + optional int32 category_number = 1; + optional string user_name = 2; +} + +message CMsgGCTFSpecificItemBroadcast { + optional uint32 item_def_index = 1; + optional bool was_destruction = 2; + optional string user_name = 3; +} + +message CMsgTFWorldStatus { + optional bool beta_stress_test_event_active = 1 [default = false]; + optional ETFMatchGroup event_match_group = 2 [default = k_eTFMatchGroup_Invalid]; + optional fixed32 event_expire_time = 3 [default = 0]; + optional uint32 active_client_version = 4; + repeated ETFMatchGroup disabled_match_groups = 5; +} + +message CSOTFDuelSummary { + optional uint32 account_id = 1 [(key_field) = true]; + optional uint32 duel_wins = 2; + optional uint32 duel_losses = 3; + optional uint32 last_duel_account_id = 4; + optional uint32 last_duel_timestamp = 5; + optional uint32 last_duel_status = 6; +} + +message CSOTFMapContribution { + optional uint32 account_id = 1 [(key_field) = true]; + optional uint32 def_index = 2 [(key_field) = true]; + optional uint32 contribution_level = 3; +} + +message CMsgTFFreeTrialChooseMostHelpfulFriend { + optional uint32 account_id_friend = 1; +} + +message CMsgTFRequestTF2Friends { + repeated uint32 account_ids = 1; +} + +message CMsgTFRequestTF2FriendsResponse { + repeated uint32 account_ids = 1; +} + +message CSOTFPlayerInfo { + optional uint32 num_new_users_helped = 1; +} + +message CMsgTFThankedBySomeone { + optional uint64 thanker_steam_id = 1; +} + +message CMsgTFThankedSomeone { +} + +message CMsgTFFreeTrialConvertedToPremium { +} + +message CMsgSaxxyAwarded { + optional uint32 category = 1; + repeated string winner_names = 2; +} + +message CMsgReplaySubmitContestEntry { + optional string youtube_url = 1; + optional uint32 category = 2; +} + +message CMsgReplaySubmitContestEntryResponse { + optional bool success = 1; +} + +message CReplayCachedContestData { + optional fixed32 timestamp = 1; + optional uint32 num_votes_last_day = 2; + repeated uint32 video_entry_ids = 3; + optional uint32 num_flags_last_day = 4; +} + +message CMsgTFCoaching_AddToCoaches { +} + +message CMsgTFCoaching_RemoveFromCoaches { +} + +message CMsgTFCoaching_FindCoach { + optional uint32 account_id_friend_as_coach = 1; +} + +message CMsgTFCoaching_FindCoachResponse { + optional bool found_coach = 1; + optional uint32 num_likes = 2; + optional string coach_name = 3; +} + +message CMsgTFCoaching_AskCoach { + optional uint32 account_id_student = 1; + optional bool student_is_friend = 2; +} + +message CMsgTFCoaching_AskCoachResponse { + optional bool accept_coaching_assignment = 1; +} + +message CMsgTFCoaching_CoachJoinGame { + optional bool join_game = 1; + optional uint32 server_address = 2; + optional uint32 server_port = 3; + optional uint32 account_id_student = 4; +} + +message CMsgTFCoaching_CoachJoining { + optional uint32 account_id_coach = 1; + optional uint32 account_id_student = 2; +} + +message CMsgTFCoaching_CoachJoined { + optional uint32 account_id_coach = 1; +} + +message CMsgTFCoaching_LikeCurrentCoach { + optional bool like_coach = 1; +} + +message CMsgTFCoaching_RemoveCurrentCoach { + optional uint32 account_id_coach = 1; +} + +message CMsgTFQuickplay_ScoreServers { + message ServerInfo { + optional uint32 server_address = 1; + optional uint32 server_port = 2; + optional uint32 num_users = 3; + optional uint64 steam_id = 4; + optional uint32 max_users = 5; + optional float user_score = 6; + } + + repeated CMsgTFQuickplay_ScoreServers.ServerInfo servers = 1; +} + +message CMsgTFQuickplay_ScoreServersResponse { + message ServerInfo { + optional uint32 server_address = 1; + optional uint32 server_port = 2; + optional float total_score = 3; + optional uint64 steam_id = 4; + optional uint32 options_score = 5; + } + + repeated CMsgTFQuickplay_ScoreServersResponse.ServerInfo servers = 1; +} + +message CMsgTFQuickplay_PlayerJoining { + optional uint32 account_id = 1; +} + +message CMsgGC_GameServer_LevelInfo { + optional bool level_loaded = 1; + optional string level_name = 2; +} + +message CMsgGC_GameServer_AuthChallenge { + optional string challenge_string = 1; +} + +message CMsgGC_GameServer_AuthResult { + optional bool authenticated = 1; + optional int32 game_server_standing = 2; + optional int32 game_server_standing_trend = 3; + optional bool is_valve_server = 4; + optional string message = 5; +} + +message CMsgGC_GameServer_AuthChallengeResponse { + optional uint32 game_server_account_id = 1; + optional bytes hashed_challenge_string = 2; +} + +message CMsgGC_GameServer_CreateIdentity { + optional uint32 account_id = 1; +} + +message CMsgGC_GameServer_CreateIdentityResponse { + enum EStatus { + kStatus_GenericFailure = 0; + kStatus_TooMany = -1; + kStatus_NoPrivs = -2; + kStatus_Created = 1; + } + + optional bool account_created = 1; + optional uint32 game_server_account_id = 2; + optional string game_server_identity_token = 3; + optional CMsgGC_GameServer_CreateIdentityResponse.EStatus status = 4 [default = kStatus_GenericFailure]; +} + +message CMsgGC_GameServer_List { + optional uint32 account_id = 1; +} + +message CMsgGC_GameServer_ListResponse { + message GameServerIdentity { + optional uint32 game_server_account_id = 1; + optional string game_server_identity_token = 2; + optional int32 game_server_standing = 3; + optional int32 game_server_standing_trend = 4; + } + + repeated CMsgGC_GameServer_ListResponse.GameServerIdentity owned_game_servers = 1; +} + +message CMsgGC_GameServer_ResetIdentity { + optional uint32 game_server_account_id = 1; +} + +message CMsgGC_GameServer_ResetIdentityResponse { + optional bool game_server_identity_token_reset = 1; + optional uint32 game_server_account_id = 2; + optional string game_server_identity_token = 3; +} + +message CMsgGC_GameServer_AckPolicy { +} + +message CMsgGC_GameServer_AckPolicyResponse { + optional uint32 result = 1; + optional string message = 2; +} + +message CMsgGC_Client_UseServerModificationItem { + optional uint64 item_id = 1; +} + +message CMsgGC_Client_UseServerModificationItem_Response { + enum EServerModificationItemResponse { + kServerModificationItemResponse_AlreadyInUse = 1; + kServerModificationItemResponse_NotOnAuthenticatedServer = 2; + kServerModificationItemResponse_ServerReject = 3; + kServerModificationItemResponse_InternalError = 4; + kServerModificationItemResponse_EventAlreadyActive = 5; + } + + optional CMsgGC_Client_UseServerModificationItem_Response.EServerModificationItemResponse response_code = 1 [default = kServerModificationItemResponse_AlreadyInUse]; +} + +message CMsgGC_GameServer_UseServerModificationItem { + optional EServerModificationItemType modification_type = 1 [default = kGameServerModificationItem_Halloween]; +} + +message CMsgGC_GameServer_UseServerModificationItem_Response { + enum EServerModificationItemServerResponse { + kServerModificationItemServerResponse_Accepted = 1; + kServerModificationItemServerResponse_NoVoteCalled = 2; + kServerModificationItemServerResponse_VoteFailed = 3; + } + + optional EServerModificationItemType modification_type = 1 [default = kGameServerModificationItem_Halloween]; + optional CMsgGC_GameServer_UseServerModificationItem_Response.EServerModificationItemServerResponse server_response_code = 2 [default = kServerModificationItemServerResponse_Accepted]; +} + +message CMsgGC_GameServer_ServerModificationItemExpired { + optional EServerModificationItemType modification_type = 1 [default = kGameServerModificationItem_Halloween]; +} + +message CMsgGC_GameServer_ServerModificationItem { + optional EServerModificationItemType modification_type = 1 [default = kGameServerModificationItem_Halloween]; + optional bool active = 2; +} + +message CMsgGC_Halloween_ReservedItem { + repeated float x = 1; + repeated float y = 2; + repeated float z = 3; + optional uint32 spawn_meta_info = 7; +} + +message CMsgGC_Halloween_GrantItem { + optional uint32 recipient_account_id = 1; + optional uint32 level_id = 2; + optional bool flagged = 3; +} + +message CMsgGC_Halloween_GrantItemResponse { + optional uint32 recipient_account_id = 1; +} + +message CMsgGC_Halloween_ItemClaimed { +} + +message CMsgGC_PickupItemEligibility_Query { + optional uint32 account_id = 1; + optional uint32 seconds_ago = 2; +} + +message CMsgGC_PickupItemEligibility_QueryResponse { + optional uint32 account_id = 1; + optional bool was_eligible = 2; + optional uint32 level_id = 3; +} + +message CSOTFPartyMember { + message Activity { + optional fixed64 lobby_id = 1; + optional ETFMatchGroup lobby_match_group = 2 [default = k_eTFMatchGroup_Invalid]; + optional bool multiqueue_blocked = 3; + optional bool online = 4; + optional uint32 client_version = 5; + } + + optional bool owns_ticket = 2; + optional uint32 completed_missions = 3; + optional uint32 badge_level = 4; + optional bool competitive_access = 9; + optional uint32 experience = 14; + optional CTFPerPlayerMatchCriteriaProto player_criteria = 16; + optional CSOTFPartyMember.Activity activity = 17; + optional bool casual_banned = 18; + optional bool ranked_banned = 19; + optional bool casual_low_priority = 20; + optional bool ranked_low_priority = 21; + optional bool lobby_standby = 22; +} + +message TFPendingPartyMember { + enum EType { + Invited = 0; + RequestedToJoin = 1; + } + + optional fixed64 steamid = 1; + optional TFPendingPartyMember.EType type = 2 [default = Invited]; + optional fixed64 inviter = 3; +} + +message TFSyncedMMUIState { + optional ETFSyncedMMMenuStep menu_step = 1 [default = k_eTFSyncedMMMenuStep_None]; + optional ETFMatchGroup match_group = 2 [default = k_eTFMatchGroup_Invalid]; +} + +message CTFGroupMatchCriteriaProto { + optional bool late_join_ok = 5; + optional uint32 custom_ping_tolerance = 13 [default = 0]; + optional string mvm_mannup_tour = 10; + repeated string mvm_mannup_missions = 15; + repeated string mvm_bootcamp_missions = 16; + optional CTFCasualMatchCriteria casual_criteria = 12; +} + +message CTFCasualMatchCriteria { + repeated fixed32 selected_maps_bits = 3; +} + +message CTFPerPlayerMatchCriteriaProto { + optional bool mvm_squad_surplus = 1; +} + +message CTFPartyOptions { + optional bool overwrite_existing = 1; + optional CTFGroupMatchCriteriaProto group_criteria = 2; + optional CTFPerPlayerMatchCriteriaProto player_criteria = 3; + optional TFSyncedMMUIState player_uistate = 5; +} + +message CMsgPartySetOptions { + optional fixed64 party_id = 1; + optional CTFPartyOptions options = 2; +} + +message CMsgPartySetOptionsResponse { +} + +message CMsgPartyQueueForMatch { + optional fixed64 party_id = 1; + optional CTFPartyOptions final_options = 2; + optional ETFMatchGroup match_group = 3 [default = k_eTFMatchGroup_Invalid]; +} + +message CMsgPartyQueueForMatchResponse { +} + +message CMsgPartyQueueForStandby { + optional fixed64 party_id = 1; + optional fixed64 party_lobby_id = 2; +} + +message CMsgPartyQueueForStandbyResponse { +} + +message CMsgPartyRemoveFromQueue { + optional fixed64 party_id = 1; + optional ETFMatchGroup match_group = 2 [default = k_eTFMatchGroup_Invalid]; +} + +message CMsgPartyRemoveFromQueueResponse { +} + +message CMsgPartyRemoveFromStandbyQueue { + optional fixed64 party_id = 1; +} + +message CMsgPartyRemoveFromStandbyQueueResponse { +} + +message CMsgPartyInvitePlayer { + optional fixed64 party_id = 1; + optional fixed64 player_id = 2; + optional bool expecting_request_to_join = 3; +} + +message CMsgPartyRequestJoinPlayer { + optional fixed64 current_party_id = 1; + optional fixed64 join_player_id = 2; + optional fixed64 join_party_id = 3; + optional bool expecting_invite = 4; +} + +message CMsgPartyClearPendingPlayer { + optional fixed64 party_id = 1; + optional fixed64 pending_player_id = 2; +} + +message CMsgPartyMMError { + enum Type { + QUEUE_KICK_NO_PING = 1; + QUEUE_KICK_AUTH = 2; + } + + optional CMsgPartyMMError.Type type = 1 [default = QUEUE_KICK_NO_PING]; +} + +message CMsgPartyClearPendingPlayerResponse { +} + +message CMsgPartyClearOtherPartyRequest { + optional fixed64 other_party_id = 1; +} + +message CMsgPartyClearOtherPartyRequestResponse { +} + +message CMsgPartyPromoteToLeader { + optional fixed64 party_id = 1; + optional fixed64 new_leader_id = 2; +} + +message CMsgPartyKickMember { + optional fixed64 party_id = 1; + optional fixed64 target_id = 2; +} + +message CMsgPartySendChat { + optional fixed64 party_id = 1; + optional string msg = 2; +} + +message CMsgPartyChatMsg { + optional ETFPartyChatType type = 1 [default = k_eTFPartyChatType_Invalid]; + optional fixed64 actor_id = 2; + optional string msg = 3; +} + +message CSOTFParty { + message QueueEntry { + optional ETFMatchGroup match_group = 1 [default = k_eTFMatchGroup_Invalid]; + optional fixed32 queued_time = 2; + } + + optional uint64 party_id = 1 [(key_field) = true]; + optional fixed64 leader_id = 2; + repeated fixed64 member_ids = 3; + repeated CSOTFPartyMember members = 13; + optional uint64 associated_lobby_id = 35; + optional ETFMatchGroup associated_lobby_match_group = 40 [default = k_eTFMatchGroup_Invalid]; + repeated CSOTFParty.QueueEntry matchmaking_queues = 43; + optional CTFGroupMatchCriteriaProto group_criteria = 37; + optional uint32 casual_banned_time = 18; + optional uint32 casual_low_priority_time = 20; + optional uint32 ranked_banned_time = 41; + optional uint32 ranked_low_priority_time = 42; + optional TFSyncedMMUIState leader_ui_state = 44; + repeated TFPendingPartyMember pending_members = 39; +} + +message CSOTFPartyInvite { + message PartyMember { + optional fixed64 steamid = 2; + } + + enum Type { + PENDING_INVITE = 1; + PENDING_JOIN_REQUEST = 2; + } + + optional uint64 group_id = 1 [(key_field) = true]; + optional fixed64 inviter = 2; + repeated CSOTFPartyInvite.PartyMember members = 4; + optional CSOTFPartyInvite.Type type = 5 [default = PENDING_INVITE]; +} + +message CTFLobbyPlayerProto { + enum ConnectState { + INVALID = 0; + RESERVATION_PENDING = 1; + RESERVED = 2; + CONNECTED = 3; + DISCONNECTED = 5; + } + + enum Type { + INVALID_PLAYER = 0; + MATCH_PLAYER = 1; + STANDBY_PLAYER = 2; + OBSERVING_PLAYER = 3; + } + + optional fixed64 id = 1 [(key_field) = true]; + optional TF_GC_TEAM team = 3 [default = TF_GC_TEAM_DEFENDERS]; + optional CTFLobbyPlayerProto.ConnectState connect_state = 13 [default = INVALID]; + optional string name = 6; + optional uint64 original_party_id = 12; + optional bool squad_surplus = 14; + optional uint32 badge_level = 15; + optional uint32 last_connect_time = 17; + optional CTFLobbyPlayerProto.Type type = 19 [default = INVALID_PLAYER]; + optional double normalized_rating = 20; + optional double normalized_uncertainty = 22; + optional uint32 rank = 21; + optional bool chat_suspension = 23; +} + +message CTFLobbyInviteProto { + optional fixed64 lobby_id = 1 [(key_field) = true]; + optional ETFMatchGroup match_group = 2 [default = k_eTFMatchGroup_Invalid]; +} + +message CSOTFGameServerLobby { + enum State { + UNKNOWN = 0; + SERVERSETUP = 1; + RUN = 2; + } + + enum WarMatch { + NOPE = 0; + INVADERS_ARE_PYRO = 1; + INVADERS_ARE_HEAVY = 2; + } + + optional uint64 lobby_id = 1 [(key_field) = true]; + repeated CTFLobbyPlayerProto members = 2; + optional fixed64 server_id = 6 [default = 0]; + optional CSOTFGameServerLobby.State state = 4 [default = UNKNOWN]; + optional string connect = 5; + optional TF_GC_GameState game_state = 22 [default = TF_GC_GAMESTATE_STATE_INIT]; + optional double initial_average_mm_rating = 32; + optional string mannup_tour_name = 42; + optional string map_name = 38; + optional string mission_name = 39; + optional uint32 match_group = 41; + optional uint64 match_id = 30 [default = 0]; + optional uint32 formed_time = 36; + optional uint32 flags = 43; + optional bool late_join_eligible = 44; + optional uint32 fixed_match_size = 45; + optional CSOTFGameServerLobby.WarMatch is_war_match = 46 [default = NOPE]; + repeated uint32 next_maps_for_vote = 47; + optional uint32 lobby_mm_version = 48; + repeated CTFLobbyPlayerProto pending_members = 49; +} + +message CMsgExitMatchmaking { + optional bool explicit_abandon = 1; + optional uint64 party_id = 2; + optional uint64 lobby_id = 3; +} + +message CMsgAcceptLobbyInvite { + optional uint64 invited_lobby_id = 1; + optional uint64 abandoning_match_id = 2; + repeated uint64 abandoning_invite_lobby_ids = 3; +} + +message CMsgAcceptLobbyInviteReply { +} + +message CMsgMatchmakingSearchCountRequest { +} + +message CMsgMatchmakingSearchCountResponse { + repeated uint32 searching_players_by_group = 1; +} + +message CMsgKickedFromMatchmakingQueue { +} + +message CMsgGameServerMatchmakingStatus { + message Player { + optional fixed64 steam_id = 1; + optional CMsgGameServerMatchmakingStatus.PlayerConnectState connect_state = 2 [default = INVALID]; + } + + enum PlayerConnectState { + INVALID = 0; + CONNECTED = 1; + RESERVED = 2; + } + + enum Event { + None = 0; + MvMVictory = 1; + MvMDefeat = 2; + AcknowledgePlayers = 3; + } + + optional uint32 server_version = 16 [default = 1225]; + optional ServerMatchmakingState matchmaking_state = 1 [default = ServerMatchmakingState_INVALID]; + optional string map = 3; + optional string tags = 4; + optional uint32 bot_count = 5; + optional uint32 num_spectators = 6; + optional uint32 max_players = 7; + optional uint32 slots_free = 8; + optional uint32 server_region = 9; + optional float server_loadavg = 10; + optional bool server_trusted = 11; + optional bool server_dedicated = 12; + optional uint32 strict = 17; + optional string fake_ip = 25; + repeated CMsgGameServerMatchmakingStatus.Player players = 13; + optional TF_GC_GameState game_state = 14 [default = TF_GC_GAMESTATE_STATE_INIT]; + optional CMsgGameServerMatchmakingStatus.Event event = 15 [default = None]; + optional uint32 mvm_wave = 18; + optional uint32 mvm_credits_acquired = 19; + optional uint32 mvm_credits_dropped = 20; + optional sint32 match_group = 23 [default = -1]; + optional uint32 lobby_mm_version = 24; +} + +message CMsgMatchmakingProgress { + optional uint32 avg_wait_time_new = 4; + optional uint32 avg_wait_time_join_late = 5; + optional uint32 your_wait_time = 6; + optional uint32 matching_worldwide_searching_players = 8; + optional uint32 matching_near_you_searching_players = 9; + optional uint32 total_worldwide_searching_players = 13; + optional uint32 total_near_you_searching_players = 14; + optional uint32 matching_worldwide_active_players = 15; + optional uint32 matching_near_you_active_players = 16; + optional uint32 total_worldwide_active_players = 17; + optional uint32 total_near_you_active_players = 18; + optional uint32 matching_worldwide_empty_gameservers = 19; + optional uint32 matching_near_you_empty_gameservers = 20; + optional uint32 total_worldwide_empty_gameservers = 21; + optional uint32 total_near_you_empty_gameservers = 22; + optional uint32 urgency_pct = 1; +} + +message CMsgMvMVictoryInfo { + message Item { + optional CMsgMvMVictoryInfo.GrantReason grant_reason = 1 [default = INVALID]; + optional bytes item_data = 2; + optional fixed64 squad_surplus_claimer_steam_id = 3; + } + + message Player { + optional fixed64 steam_id = 1; + optional bool badge_granted = 3; + optional bool badge_progress_updated = 4; + optional bool badge_leveled = 5; + optional uint32 badge_level = 6; + optional uint32 badge_progress_bits = 7; + repeated CMsgMvMVictoryInfo.Item items = 8; + optional bool voucher_missing = 9; + optional uint32 badge_points = 10; + } + + enum GrantReason { + INVALID = 0; + BADGE_LEVELED = 1; + SQUAD_SURPLUS = 2; + MANN_UP = 3; + HELP_A_NOOB = 4; + } + + repeated CMsgMvMVictoryInfo.Player players = 1; + optional string tour_name = 2; + optional string mission_name = 3; +} + +message CGCMsgTFHelloResponse { + optional uint32 version_check = 1; + repeated uint64 version_checksum = 2; + optional uint32 version_verbose = 3; +} + +message CGCMsgTFSync { + optional bytes version_checksum = 1; + optional uint32 version_check = 2; + optional uint32 version_check_ex = 3; + optional uint32 version_check_ex2 = 4; + optional bytes version_checksum_ex = 5; +} + +message CGCMsgTFSyncEx { + optional string version_checksum = 1; + optional bytes version_checksum_ex = 2; + optional uint32 version_check = 3; +} + +message CMsgMvMVictory { + message Player { + optional fixed64 steam_id = 1; + optional bool squad_surplus = 2; + } + + optional uint32 legacy_mission_index = 1; + optional string tour_name_mannup = 5; + optional string mission_name = 6; + repeated CMsgMvMVictory.Player players = 2; + optional uint64 lobby_id = 3; + optional fixed32 event_time = 4; +} + +message CMsgMvMMannUpVictoryReply { +} + +message CMsgGameServerKickingLobby { + optional uint64 lobby_id = 3; + optional uint64 match_id = 4; +} + +message CMsgGameServerKickingLobbyResponse { +} + +message CMsgLeaveGameAndPrepareToJoinParty { + optional fixed64 party_id = 1; +} + +message CMsgPlayerLeftMatch { + optional fixed64 steam_id = 1; + optional TFMatchLeaveReason leave_reason = 2 [default = TFMatchLeaveReason_UNSPECIFIED]; + optional bool was_abandon = 3; + optional uint64 lobby_id = 4; + optional uint64 match_id = 5; + repeated CMsgTFXPSource xp_breakdown = 6; +} + +message CMsgPlayerLeftMatchResponse { +} + +message CMsgProcessMatchVoteKick { + message Vote { + optional fixed64 steam_id = 1; + optional bool vote_yay = 2; + } + + optional fixed64 match_id = 1; + optional fixed64 initiator_steam_id = 2; + optional fixed64 target_steam_id = 3; + optional TFVoteKickReason reason = 4 [default = TFVoteKickReason_Invalid]; + repeated CMsgProcessMatchVoteKick.Vote votes = 5; + optional bool default_pass = 6; +} + +message CMsgProcessMatchVoteKickResponse { + optional bool rip = 1; +} + +message CMsgHalloween_ServerBossEvent { + optional uint32 event_counter = 1; + optional uint32 timestamp = 2; + optional uint32 boss_type = 3; + optional uint32 boss_level = 4; + optional uint32 event_type = 5; + optional uint32 players_involved = 6; + optional float elapsed_time = 7; +} + +message CMsgHalloween_Merasmus2012 { + optional uint32 event_counter = 1; + optional fixed32 time_submitted = 2; + optional bool is_valve_server = 3; + optional uint32 boss_level = 4; + optional uint32 spawned_health = 5; + optional uint32 remaining_health = 6; + optional uint32 life_time = 7; + optional uint32 bomb_kills = 8; + optional uint32 staff_kills = 9; + optional uint32 pvp_kills = 10; + optional uint32 prophunt_time1 = 11; + optional uint32 prophunt_time2 = 12; + optional uint32 dmg_scout = 13; + optional uint32 dmg_sniper = 14; + optional uint32 dmg_soldier = 15; + optional uint32 dmg_demo = 16; + optional uint32 dmg_medic = 17; + optional uint32 dmg_heavy = 18; + optional uint32 dmg_pyro = 19; + optional uint32 dmg_spy = 20; + optional uint32 dmg_engineer = 21; + optional uint32 scout_count = 22; + optional uint32 sniper_count = 23; + optional uint32 solider_count = 24; + optional uint32 demo_count = 25; + optional uint32 medic_count = 26; + optional uint32 heavy_count = 27; + optional uint32 pyro_count = 28; + optional uint32 spy_count = 29; + optional uint32 engineer_count = 30; +} + +message CMsgUpdateHalloweenMerasmusLootLevel { + message Player { + optional fixed64 steam_id = 1; + } + + repeated CMsgUpdateHalloweenMerasmusLootLevel.Player players = 1; + optional uint32 merasmus_level = 2; +} + +message CAttribute_String { + optional string value = 1; +} + +message CAttribute_DynamicRecipeComponent { + optional uint32 def_index = 1; + optional uint32 item_quality = 2; + optional uint32 component_flags = 3; + optional string attributes_string = 4; + optional uint32 num_required = 5; + optional uint32 num_fulfilled = 6; +} + +message CAttribute_DynamicRecipeComponent_COMPAT_NEVER_SERIALIZE_THIS_OUT { + optional uint32 def_index = 1; + optional uint32 item_def = 2; + optional uint32 item_quality = 3; + optional uint32 component_flags = 4; + optional uint32 item_flags = 5; + optional string attributes_string = 6; + optional uint32 num_required = 7; + optional uint32 item_count = 8; + optional uint32 num_fulfilled = 9; + optional uint32 items_fulfilled = 10; +} + +message CAttribute_ItemSlotCriteria { + optional string tags = 1; +} + +message CMsgSetItemSlotAttribute { + optional uint64 item_id = 1; + optional uint64 slot_item_original_id = 2; + optional uint32 slot_index = 3; +} + +message CSOWarData { + optional uint32 account_id = 1 [(key_field) = true]; + optional uint32 war_id = 2 [(key_field) = true]; + optional uint32 affiliation = 3; + optional uint32 points_scored = 4; +} + +message CGCMsgGC_War_IndividualUpdate { + optional fixed64 steam_id = 1; + optional uint32 war_id = 2; + optional uint32 score = 3; +} + +message CGCMsgGC_War_JoinWar { + optional uint32 affiliation = 1; + optional uint32 war_id = 2; +} + +message CGCMsgGC_War_RequestGlobalStats { + optional uint32 war_id = 1; +} + +message CGCMsgGC_War_GlobalStatsResponse { + message SideScore { + optional uint32 side = 1; + optional uint64 score = 2; + } + + repeated CGCMsgGC_War_GlobalStatsResponse.SideScore side_scores = 1; + optional uint32 war_id = 2; +} + +message CGCMsgGC_PlayerDuckLeaderboard_IndividualUpdate { + optional uint32 score = 2; + optional uint32 type = 3; + optional bytes score_id = 4; + optional uint32 score_check = 5; +} + +message CAttribute_WorldItemPlacement { + optional uint64 original_item_id = 1; + optional float pos_x = 2; + optional float pos_y = 3; + optional float pos_z = 4; + optional float ang_x = 5; + optional float ang_y = 6; + optional float ang_z = 7; +} + +message CGCMsg_WorldItemPlacement_Update { + optional uint64 original_item_id = 1; + optional float pos_x = 2; + optional float pos_y = 3; + optional float pos_z = 4; + optional float ang_x = 5; + optional float ang_y = 6; + optional float ang_z = 7; + optional bool force_remove_all = 8; + optional string attrib_name = 9; +} + +message CMsgAcknowledgeXP { + optional int32 match_group = 1; + optional uint32 predicted_experience = 2; +} + +message CMsgTFXPSource { + enum XPSourceType { + SOURCE_SCORE = 0; + SOURCE_OBJECTIVE_BONUS = 1; + SOURCE_COMPLETED_MATCH = 2; + SOURCE_COMPETITIVE_ABANDON = 3; + SOURCE_COMPETITIVE_WIN = 4; + SOURCE_COMPETITIVE_LOSS = 5; + SOURCE_AUTOBALANCE_BONUS = 6; + SOURCE_PRESTIGE_BONUS = 7; + } + + optional CMsgTFXPSource.XPSourceType type = 1 [default = SOURCE_SCORE, (key_field) = true]; + optional int32 amount = 2; + optional int32 match_group = 3; + optional uint32 account_id = 4 [(key_field) = true]; + optional uint64 match_id = 5 [(key_field) = true]; +} + +message CMsgTFXPSourceBreakdown { + repeated CMsgTFXPSource sources = 1; +} + +message CMsgTFClientInit { + optional uint32 client_version = 1; + optional int32 language = 2; +} + +message CMsgGCNotification { + enum NotificationType { + NOTIFICATION_REPORTED_PLAYER_BANNED = 0; + NOTIFICATION_CUSTOM_STRING = 1; + NOTIFICATION_MM_BAN_DUE_TO_EXCESSIVE_REPORTS = 2; + NOTIFICATION_REPORTED_PLAYER_WAS_BANNED = 3; + NOTIFICATION_SUPPORT_MESSAGE = 4; + NOTIFICATION_NUM_TYPES = 5; + } + + optional uint64 notification_id = 1 [(key_field) = true]; + optional uint32 account_id = 2; + optional fixed32 expiration_time = 3; + optional CMsgGCNotification.NotificationType type = 4 [default = NOTIFICATION_CUSTOM_STRING]; + optional string notification_string = 5; +} + +message CMsgGCNotificationQueue { + repeated CMsgGCNotification notifications = 1; +} + +message CMsgNotificationAcknowledge { + optional uint32 account_id = 1; + optional uint64 notification_id = 2; +} + +message CMsgNotificationAcknowledgeReply { +} + +message CMsgGC_Match_Result { + message Player { + optional fixed64 steam_id = 1; + optional uint64 original_party_id = 2; + optional uint32 team = 3; + optional uint32 score = 4; + optional uint32 ping = 5; + optional uint32 flags = 6; + optional uint32 rank = 9; + optional uint32 classes_played = 10; + optional uint32 kills = 11; + optional uint32 deaths = 12; + optional uint32 damage = 13; + optional uint32 healing = 14; + optional uint32 support = 15; + optional uint32 score_medal = 16; + optional uint32 kills_medal = 17; + optional uint32 damage_medal = 18; + optional uint32 healing_medal = 19; + optional uint32 support_medal = 20; + repeated CMsgTFXPSource xp_breakdown = 21; + optional uint32 leave_time = 22; + optional TFMatchLeaveReason leave_reason = 23 [default = TFMatchLeaveReason_UNSPECIFIED]; + optional uint32 connect_time = 24; + } + + enum Status { + MATCH_SUCCEEDED = 0; + MATCH_FAILED_GC = 1; + MATCH_FAILED_TRUSTED = 2; + MATCH_FAILED_ABANDON = 3; + MATCH_FAILED_UNKNOWN = 5; + MATCH_FAILED_TIMEOUT = 6; + MATCH_FINISHED_ABANDON = 7; + } + + optional uint64 match_id = 1 [(key_field) = true]; + optional int32 match_group = 2; + optional CMsgGC_Match_Result.Status status = 3 [default = MATCH_SUCCEEDED]; + optional uint32 duration = 4; + optional uint32 red_score = 5; + optional uint32 blue_score = 6; + optional uint32 winning_team = 7; + optional uint32 map_index = 8; + optional uint32 game_type = 9 [default = 0]; + repeated CMsgGC_Match_Result.Player players = 10; + optional uint32 win_reason = 13; + optional uint32 flags = 14; + optional uint32 bots = 16; +} + +message CMsgGC_Match_ResultResponse { +} + +message CEconItemPreviewDataBlock { + optional CSOEconItem econitem = 1; +} + +message CMsgGC_Client2GCEconPreviewDataBlockRequest { + optional uint64 param_s = 1; + optional uint64 param_a = 2; + optional uint64 param_d = 3; + optional uint64 param_m = 4; +} + +message CMsgGC_Client2GCEconPreviewDataBlockResponse { + optional CEconItemPreviewDataBlock iteminfo = 1; +} + +message CSOTFLadderPlayerStats { + optional uint32 account_id = 1 [(key_field) = true]; + optional int32 match_group = 2 [(key_field) = true]; + optional uint32 season_id = 3 [(key_field) = true]; + optional uint32 games = 9; + optional uint32 score = 10; + optional uint32 kills = 11; + optional uint32 deaths = 12; + optional uint32 damage = 13; + optional uint32 healing = 14; + optional uint32 support = 15; + optional uint32 score_bronze = 16; + optional uint32 score_silver = 17; + optional uint32 score_gold = 18; + optional uint32 kills_bronze = 19; + optional uint32 kills_silver = 20; + optional uint32 kills_gold = 21; + optional uint32 damage_bronze = 22; + optional uint32 damage_silver = 23; + optional uint32 damage_gold = 24; + optional uint32 healing_bronze = 25; + optional uint32 healing_silver = 26; + optional uint32 healing_gold = 27; + optional uint32 support_bronze = 28; + optional uint32 support_silver = 29; + optional uint32 support_gold = 30; +} + +message CSOTFRatingData { + optional uint32 account_id = 1 [(key_field) = true]; + optional int32 rating_type = 2 [(key_field) = true]; + optional uint32 rating_primary = 3; + optional uint32 rating_secondary = 4; + optional uint32 rating_tertiary = 5; +} + +message CMsgGC_TFVoteKickPlayerRequest { + optional uint64 target_id = 2; + optional uint64 voter_id = 3; + optional TFVoteKickReason reason = 4 [default = TFVoteKickReason_Invalid]; + optional uint64 match_id = 5; +} + +message CMsgGC_VoteKickPlayerRequestResponse { + optional bool allowed = 1; + optional bool voter_inhibit = 3; + optional bool target_inhibit = 4; +} + +message CMsgGC_DailyCompetitiveStatsRollup { +} + +message CMsgGC_DailyCompetitiveStatsRollup_Response { + message RankBucketEntry { + optional uint32 rank = 1; + optional uint32 records = 2; + optional uint32 avg_score = 3; + optional uint32 stdev_score = 4; + optional uint32 avg_kills = 5; + optional uint32 stdev_kills = 6; + optional uint32 avg_damage = 7; + optional uint32 stdev_damage = 8; + optional uint32 avg_healing = 9; + optional uint32 stdev_healing = 10; + optional uint32 avg_support = 11; + optional uint32 stdev_support = 12; + } + + repeated CMsgGC_DailyCompetitiveStatsRollup_Response.RankBucketEntry rankdata = 1; +} + +message CMsgGC_ReportPlayer { + enum EReason { + kReason_INVALID = 0; + kReason_CHEATING = 1; + kReason_IDLE = 2; + kReason_HARASSMENT = 3; + kReason_GRIEFING = 4; + kReason_COUNT = 5; + } + + optional uint32 account_id_target = 1; + optional CMsgGC_ReportPlayer.EReason reason = 2 [default = kReason_INVALID]; +} + +message CSOTFMatchResultPlayerStats { + optional uint64 match_id = 1 [(key_field) = true]; + optional uint32 account_id = 2 [(key_field) = true]; + optional int32 match_group = 3 [(key_field) = true]; + optional uint32 endtime = 4; + optional uint32 season_id = 5; + optional uint32 status = 6; + optional uint32 original_party_id = 7; + optional uint32 team = 8; + optional uint32 score = 9; + optional uint32 ping = 10; + optional uint32 flags = 11; + optional uint32 display_rating = 12; + optional int32 display_rating_change = 13; + optional uint32 rank = 14; + optional uint32 classes_played = 15; + optional uint32 kills = 16; + optional uint32 deaths = 17; + optional uint32 damage = 18; + optional uint32 healing = 19; + optional uint32 support = 20; + optional uint32 score_medal = 21; + optional uint32 kills_medal = 22; + optional uint32 damage_medal = 23; + optional uint32 healing_medal = 24; + optional uint32 support_medal = 25; + optional uint32 map_index = 26; + optional uint32 winning_team = 27; +} + +message CMsgGCRequestMatchMakerStats { +} + +message CMsgGCDataCenterPopulation { + optional string name = 1; + optional float health_ratio = 2; +} + +message CMsgGCMatchGroupDataCenterPopulation { + repeated CMsgGCDataCenterPopulation data_center_population = 1; +} + +message CMsgGCMatchMakerStatsResponse { + repeated uint32 map_count = 1; + repeated CMsgGCMatchGroupDataCenterPopulation matchgroup_data_center_population = 2; +} + +message CMsgGCMatchHistoryLoad { + optional ETFMatchGroup match_group = 1 [default = k_eTFMatchGroup_Invalid]; +} + +message CMsgGCDataCenterPing_Update { + message PingEntry { + optional string name = 1; + optional uint32 ping = 2; + optional CMsgGCDataCenterPing_Update.Status ping_status = 3 [default = Normal]; + } + + enum Status { + Invalid = 0; + Normal = 1; + Unreachable = 2; + FallbackToDCPing = 3; + } + + repeated CMsgGCDataCenterPing_Update.PingEntry pingdata = 1; +} + +message CMsgGC_KickPlayerFromLobby { + optional uint64 targetID = 1; +} + +message CMsgGCSurveyRequest { + optional SurveyQuestionType question_type = 1 [default = QUESTION_MATCH_QUALITY]; + optional uint64 match_id = 2; +} + +message CMsgGCSurveyResponse { + optional SurveyQuestionType question_type = 1 [default = QUESTION_MATCH_QUALITY]; + optional uint64 match_id = 2; + optional int32 response = 3; +} + +message CSOQuestMapNode { + optional uint32 account_id = 1 [(key_field) = true]; + optional uint32 defindex = 3 [(key_field) = true]; + optional uint32 node_id = 4; + optional bool star_0_earned = 6 [default = false]; + optional bool star_1_earned = 7 [default = false]; + optional bool star_2_earned = 8 [default = false]; + optional bool loot_claimed = 9 [default = false]; + optional uint32 selected_quest_def = 10 [default = 0]; + optional uint32 map_cycle = 11; +} + +message CSOQuest { + optional uint32 account_id = 1; + optional uint64 quest_id = 2 [(key_field) = true]; + optional uint32 defindex = 3; + optional bool active = 4 [default = false]; + optional uint32 points_0 = 5; + optional uint32 points_1 = 6; + optional uint32 points_2 = 7; + optional uint32 quest_map_node_source_id = 8; + optional uint32 map_cycle = 9; +} + +message CSOQuestMapRewardPurchase { + optional uint32 account_id = 1; + optional uint32 defindex = 2 [(key_field) = true]; + optional uint32 count = 3; + optional uint32 map_cycle = 4; + optional uint32 purchase_id = 5; +} + +message CMsgGCQuestIdentify { + optional uint64 quest_id = 1; +} + +message CMsgGCQuestDevGive { + optional uint32 quest_def_index = 1; +} + +message CMsgGCQuestNodeTurnIn { + optional uint32 node_defindex = 1; +} + +message CMsgGCQuestMapUnlockNode { + optional uint32 node_defindex = 1; + optional uint32 quest_defindex = 2; +} + +message CMsgGCNewMatchForLobbyRequest { + optional uint64 current_match_id = 1; + optional uint32 next_map_id = 2; + optional uint64 lobby_id = 3; +} + +message CMsgGCNewMatchForLobbyResponse { + optional bool success = 1; +} + +message CMsgGCChangeMatchPlayerTeamsRequest { + message Member { + optional uint64 member_id = 1; + optional TF_GC_TEAM new_team = 2 [default = TF_GC_TEAM_NOTEAM]; + } + + optional uint64 match_id = 1; + optional uint64 lobby_id = 2; + repeated CMsgGCChangeMatchPlayerTeamsRequest.Member member = 3; +} + +message CMsgGCChangeMatchPlayerTeamsResponse { + optional bool success = 1; +} + +message CMsgGCQuestComplete_Debug { + optional uint64 quest_id = 1; + optional uint32 points_type = 2; +} + +message CMsgGCQuestMap_Debug { + optional uint32 reset_operation = 1; + optional uint32 give_credit = 2; + optional CMsgGCQuestMapUnlockNode unlock_node = 3; +} + +message CMsgGCQuestMapPurchaseReward { + optional uint32 store_item_defindex = 1; +} + +message CMsgGCQuestResponse { + optional bool success = 1 [default = false]; +} + +message CMsgGCSetDisablePartyQuestProgress { + optional bool state = 1; +} + +message CMsgQuestProgressReport { + optional uint64 quest_id = 1; + optional bool star_0_earned = 2; + optional bool star_1_earned = 3; + optional bool star_2_earned = 4; + repeated uint64 items_earned = 5; + optional uint32 reward_credits_earned = 6; + optional bool contract_completed = 7; +} + +message CMsgConsumePaintkit { + optional fixed64 source_id = 1; + optional uint32 target_defindex = 2; +} + +message CMsgPainkitDevGrant { + optional uint32 paintkit_defindex = 1; + optional float wear = 2; + optional uint32 item_defindex = 3; +} + +message GCQuestStrangeEvent { + optional uint32 owner_account_id = 1; + optional uint32 scorer_account_id = 2; + optional uint64 quest_id = 3; + optional uint32 strange_event_id = 4; + optional uint32 score = 5; +} + +message CMsgSDRTicket { + optional bytes serialized_ticket = 1; +} + +message CMsgAuthorizeServerItemRetrieval { + repeated uint64 item_id = 1; +} + +message CMsgGCToGCSendAccountBannedNotifications { + optional uint32 banned_accountid = 1; + optional uint32 report_period_begin = 2; + optional uint32 report_period_end = 3; +} + +message CMsgGCToGCSendNotification { + optional CMsgGCNotification notification = 1; +} diff --git a/dll/net.proto b/dll/net.proto index 507d09ac0..a867b128b 100644 --- a/dll/net.proto +++ b/dll/net.proto @@ -336,6 +336,11 @@ message GameServer_Items_Messages { message Attribute { uint32 def = 1; float value = 2; + bytes value_bytes = 3; + } + message EquipState { + uint32 class_id = 1; + uint32 slot_id = 2; } message Item { uint64 id = 1; @@ -350,6 +355,9 @@ message GameServer_Items_Messages { string custom_name = 10; string custom_desc = 11; uint64 original_id = 12; + bool in_use = 13; + uint32 style = 14; + repeated EquipState equip_states = 15; } message InventoryRequest { uint64 steam_api_call = 1; @@ -358,9 +366,12 @@ message GameServer_Items_Messages { uint64 steam_api_call = 1; repeated Item items = 2; } - message InventoryPosUpdate { - uint64 item_id = 1; - uint32 item_inv_pos = 2; + message ItemUpdate { + uint64 id = 1; + optional uint32 inv_pos = 2; + optional uint32 style = 3; + optional bool has_equip_states = 4; + repeated EquipState equip_states = 5; } message ItemDeletion { uint64 item_id = 1; @@ -369,7 +380,7 @@ message GameServer_Items_Messages { enum Types { Request_Inventory = 0; Response_Inventory = 1; - Request_UpdateInventoryPos = 2; + Request_UpdateItem = 2; Request_DeleteItem = 3; Request_Respawn = 4; } @@ -379,7 +390,7 @@ message GameServer_Items_Messages { oneof data_messages { InventoryRequest inventory_request = 3; InventoryResponse inventory_response = 4; - InventoryPosUpdate inventory_pos_update = 5; + ItemUpdate item_update = 5; ItemDeletion item_deletion = 6; } } diff --git a/dll/steam_game_coordinator.cpp b/dll/steam_game_coordinator.cpp index f69116dcf..f62d38c17 100644 --- a/dll/steam_game_coordinator.cpp +++ b/dll/steam_game_coordinator.cpp @@ -17,9 +17,16 @@ #include "dll/steam_game_coordinator.h" #include "dll/dll.h" +#include +#include +#include +#include +#include +#include + +using namespace gamecoordinator::tf2; constexpr int GC_MIN_VERSION = 20091217; -constexpr int GC_MAX_VERSION = 20110413; #pragma pack( push, 1 ) //----------------------------------------------------------------------------- @@ -63,15 +70,15 @@ static void ser_varstring(std::string &buf, const std::string &input) uint16 len = static_cast(input.size()); if (len != 0) { ser_var(buf, len + 1); - buf.append(input); - buf.push_back('\0'); + buf.append(input + '\0'); } else { ser_var(buf, 0); } } template -static T deser_var(const char *&p) { +static T deser_var(const char *&p) +{ T output; memcpy(&output, p, sizeof(T)); p += sizeof(T); @@ -80,7 +87,7 @@ static T deser_var(const char *&p) { bool Steam_Game_Coordinator::gc_enabled() { - return (gc_version >= GC_MIN_VERSION && gc_version <= GC_MAX_VERSION); + return (gc_version >= GC_MIN_VERSION && gc_profile != GC_PROFILE_INVALID); } Steam_User_Items *Steam_Game_Coordinator::client_items() @@ -90,7 +97,7 @@ Steam_User_Items *Steam_Game_Coordinator::client_items() Steam_GameServer_Items *Steam_Game_Coordinator::server_items() { - return get_steam_client()->steam_gameserver_items;; + return get_steam_client()->steam_gameserver_items; } void Steam_Game_Coordinator::parse_gc_config() @@ -101,14 +108,37 @@ void Steam_Game_Coordinator::parse_gc_config() return; try { + std::string gc_profile_name = gc_json.value("gc_profile", std::string()); + std::transform(gc_profile_name.begin(), gc_profile_name.end(), gc_profile_name.begin(), + [](auto c) { return std::tolower(c); }); + if (gc_profile_name == "tf2") { + gc_profile = GC_PROFILE_TF2; + } else if (gc_profile_name == "portal2") { + // Portal 2 is pretty much entirely compatible with TF2 protobuf structs so we can just + // make it an alias for TF2 profile. + //gc_profile = GC_PROFILE_PORTAL2; + gc_profile = GC_PROFILE_TF2; + is_portal2 = true; + } else { + gc_profile = GC_PROFILE_INVALID; + } + gc_version = gc_json.value("gc_version", 0); } catch (std::exception &e) { const char *errorMessage = e.what(); PRINT_DEBUG("error parsing GC config: %s", errorMessage); gc_version = 0; + gc_profile = GC_PROFILE_INVALID; } } +bool Steam_Game_Coordinator::is_welcome_message(const GC_Message &message) +{ + uint32 msg_type = message.msg_type & (~protobuf_mask); + return (msg_type == 4004 || + msg_type == 4005); +} + void Steam_Game_Coordinator::push_incoming(uint32 msg_type, const std::string &message, double delay) { PRINT_DEBUG("%u %.2f", msg_type, delay); @@ -142,6 +172,53 @@ GCMsgHdrEx_t Steam_Game_Coordinator::parse_msg_header(const char *&p) return hdr; } +std::string Steam_Game_Coordinator::build_protomsg_header(uint32 msg_type, JobID_t target_job, JobID_t source_job) +{ + std::string message; + ProtoBufMsgHeader_t hdr{}; + hdr.m_EMsgFlagged = msg_type; + + CMsgProtoBufHeader protohdr; + protohdr.set_client_steam_id(settings->get_local_steam_id().ConvertToUint64()); + protohdr.set_client_session_id(1); + protohdr.set_source_app_id(settings->get_local_game_id().AppID()); + protohdr.set_job_id_source(source_job); + protohdr.set_job_id_target(target_job); + hdr.m_cubProtoBufExtHdr = static_cast(protohdr.ByteSizeLong()); + + ser_var(message, hdr); + protohdr.AppendToString(&message); + + return message; +} + +template +std::tuple Steam_Game_Coordinator::parse_protomsg(const void *input, uint32 input_size) +{ + const char *p = reinterpret_cast(input); + const char *end = p + input_size; + + ProtoBufMsgHeader_t hdr{}; + CMsgProtoBufHeader protohdr; + T protomsg; + + if (input_size < sizeof(ProtoBufMsgHeader_t)) + return { hdr, protohdr, protomsg, false }; + + hdr = deser_var(p); + + if (!protohdr.ParseFromArray(p, hdr.m_cubProtoBufExtHdr)) + return { hdr, protohdr, protomsg, false }; + + p += hdr.m_cubProtoBufExtHdr; + + int protomsg_size = static_cast(end - p); + if (!protomsg.ParseFromArray(p, protomsg_size)) + return { hdr, protohdr, protomsg, false }; + + return { hdr, protohdr, protomsg, true }; +} + uint64 Steam_Game_Coordinator::item_id_local_to_network(uint64 item_id) { if (item_id == 0) @@ -176,6 +253,95 @@ uint64 Steam_Game_Coordinator::item_id_network_to_local(uint64 item_id) return item_id; } +std::string Steam_Game_Coordinator::item_to_gcstruct(const Econ_Item &item, CSteamID steam_id) +{ + std::string message; + + ser_var(message, item.id); + ser_var(message, steam_id.GetAccountID()); + ser_var(message, item.def); + ser_var(message, item.level); + ser_var(message, item.quality); + ser_var(message, item.inv_pos); + ser_var(message, item.quantity); + + if (gc_version >= 20100428) { + // Strings are passed as UTF-8 which is good for us since we can just copy std::string as is. + ser_varstring(message, item.custom_name); + + if (gc_version >= 20100930) { + ser_var(message, item.flags); + + if (gc_version >= 20101027) { + ser_var(message, item.origin); + ser_varstring(message, item.custom_desc); + ser_var(message, item.in_use); + } + } + } + + ser_var(message, static_cast(item.attributes.size())); + + for (const Econ_Item_Attribute &attr : item.attributes) { + ser_var(message, attr.def); + ser_var(message, attr.value); + } + + if (gc_version >= 20101217) { + ser_var(message, item.original_id); + } + + return message; +} + +std::string Steam_Game_Coordinator::item_to_gcprotobuf(const Econ_Item &item, CSteamID steam_id) +{ + CSOEconItem proto_item; + proto_item.set_id(item.id); + proto_item.set_account_id(steam_id.GetAccountID()); + proto_item.set_inventory(item.inv_pos); + proto_item.set_def_index(item.def); + proto_item.set_quantity(item.quantity); + proto_item.set_level(item.level); + proto_item.set_quality(item.quality); + proto_item.set_flags(item.flags); + proto_item.set_origin(item.origin); + + if (!item.custom_name.empty()) + proto_item.set_custom_name(item.custom_name); + + if (!item.custom_desc.empty()) + proto_item.set_custom_desc(item.custom_desc); + + proto_item.set_in_use(item.in_use); + proto_item.set_style(item.style); + proto_item.set_original_id(item.original_id); + + proto_item.set_contains_equipped_state(true); + proto_item.set_contains_equipped_state_v2(true); + + for (const auto &[class_id, slot_id] : item.equip_states) { + auto proto_equip = proto_item.add_equipped_state(); + proto_equip->set_new_class(class_id); + proto_equip->set_new_slot(slot_id); + } + + for (const Econ_Item_Attribute &attr : item.attributes) { + auto proto_attr = proto_item.add_attribute(); + proto_attr->set_def_index(attr.def); + if (gc_version < 20130319 || is_portal2) { + // Derp. + uint32 value; + memcpy(&value, &attr.value, sizeof(uint32)); + proto_attr->set_value(value); + } else { + proto_attr->set_value_bytes(attr.value_bytes); + } + } + + return proto_item.SerializeAsString(); +} + void Steam_Game_Coordinator::handle_set_item_pos(const void *input, uint32 input_size) { if (is_server || input_size < 30) @@ -185,9 +351,10 @@ void Steam_Game_Coordinator::handle_set_item_pos(const void *input, uint32 input GCMsgHdrEx_t hdr = parse_msg_header(p); uint64 item_id = deser_var(p); uint32 inv_pos = deser_var(p); + PRINT_DEBUG("%llu %u", item_id, inv_pos); if (const Econ_Item *item = set_item_pos(item_id, inv_pos, true)) { - on_item_pos_updated(settings->get_local_steam_id(), *item); + callback_item_updated(settings->get_local_steam_id(), *item); } } @@ -199,12 +366,32 @@ void Steam_Game_Coordinator::handle_delete_item(const void *input, uint32 input_ const char *p = reinterpret_cast(input); GCMsgHdrEx_t hdr = parse_msg_header(p); uint64 item_id = deser_var(p); + PRINT_DEBUG("%llu", item_id); if (delete_item(item_id, true)) { - on_item_deleted(settings->get_local_steam_id(), item_id); + callback_item_deleted(settings->get_local_steam_id(), item_id); } } +void Steam_Game_Coordinator::handle_motd_request(const void *input, uint32 input_size) +{ + if (is_server || input_size < 24) + return; + + const char *p = reinterpret_cast(input); + GCMsgHdrEx_t hdr = parse_msg_header(p); + uint32 last_req_time = deser_var(p); + uint16 language = deser_var(p); + PRINT_DEBUG("%u %u", last_req_time, language); + + uint32 msg_type = EGCItemMsg::k_EMsgGCMOTDRequestResponse; + std::string message = build_msg_header(); + uint16 num_entries = 0; + ser_var(message, num_entries); + + push_incoming(msg_type, message); +} + void Steam_Game_Coordinator::handle_respawn(const void *input, uint32 input_size) { if (is_server || input_size < 19) @@ -220,21 +407,300 @@ void Steam_Game_Coordinator::handle_respawn(const void *input, uint32 input_size network->sendToAllGameservers(&msg, true); } -void Steam_Game_Coordinator::handle_motd_request(const void *input, uint32 input_size) +void Steam_Game_Coordinator::handle_set_item_style(const void *input, uint32 input_size) { - if (is_server || input_size < 24) + if (is_server || input_size < 27) return; const char *p = reinterpret_cast(input); GCMsgHdrEx_t hdr = parse_msg_header(p); - uint32 last_req_time = deser_var(p); - uint16 language = deser_var(p); + uint64 item_id = deser_var(p); + uint8 style = deser_var(p); + PRINT_DEBUG("%llu %u", item_id, style); + + for (Econ_Item &item : items) { + if (item.id != item_id) + continue; + + item.style = style; + save_items_to_file(); + + // Let the others know, too. + auto inventory_msg = new GameServer_Items_Messages::ItemUpdate(); + inventory_msg->set_id(item_id); + inventory_msg->set_style(style); + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_UpdateItem); + gameserver_items_msg->set_is_gc(true); + gameserver_items_msg->set_allocated_item_update(inventory_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + network->sendToAll(&msg, true); + + callback_item_updated(settings->get_local_steam_id(), item); + break; + } +} + +void Steam_Game_Coordinator::handle_adjust_equip_state(const void *input, uint32 input_size) +{ + if (is_server) + return; + + auto [hdr, protohdr, protomsg, success] = parse_protomsg(input, input_size); + if (!success) + return; + + uint64 item_id = protomsg.item_id(); + uint32 new_class_id = protomsg.new_class(); + uint32 new_slot_id = protomsg.new_slot(); + PRINT_DEBUG("%llu %u %u", item_id, new_class_id, new_slot_id); + + for (Econ_Item &item : items) { + if (item_id != UINT64_MAX && item.id == item_id) { + // Equip the item into this slot. + item.equip_states.insert_or_assign(new_class_id, new_slot_id); + } else { + // Unequip whatever else we had in this slot. + auto it = item.equip_states.find(new_class_id); + if (it == item.equip_states.end() || it->second != new_slot_id) + continue; + + item.equip_states.erase(it); + } + + // Let the others know, too. + auto inventory_msg = new GameServer_Items_Messages::ItemUpdate(); + inventory_msg->set_id(item.id); + inventory_msg->set_has_equip_states(true); + for (const auto &[class_id, slot_id] : item.equip_states) { + auto new_state = inventory_msg->add_equip_states(); + new_state->set_class_id(class_id); + new_state->set_slot_id(slot_id); + } + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_UpdateItem); + gameserver_items_msg->set_is_gc(true); + gameserver_items_msg->set_allocated_item_update(inventory_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + network->sendToAll(&msg, true); + + callback_item_updated(settings->get_local_steam_id(), item); + } + + save_items_to_file(); +} + +void Steam_Game_Coordinator::handle_set_multiple_item_pos(const void *input, uint32 input_size) +{ + if (is_server) + return; + + auto [hdr, protohdr, protomsg, success] = parse_protomsg(input, input_size); + if (!success) + return; + + for (auto &entry : protomsg.item_positions()) { + uint64 item_id = entry.item_id(); + uint32 inv_pos = entry.position(); + + if (const Econ_Item *item = set_item_pos(item_id, inv_pos, true, false)) { + callback_item_updated(settings->get_local_steam_id(), *item); + } + } + + save_items_to_file(); +} + +void Steam_Game_Coordinator::callback_client_welcome() +{ + if (!gc_initialized) + return; + + uint32 msg_type = EGCBaseClientMsg::k_EMsgGCClientWelcome | protobuf_mask; + std::string message = build_protomsg_header(msg_type); + + CMsgClientWelcome protomsg; + protomsg.set_version(0); + + protomsg.AppendToString(&message); + push_incoming(msg_type, message); +} + +void Steam_Game_Coordinator::callback_server_welcome() +{ + if (!gc_initialized) + return; + + uint32 msg_type = EGCBaseClientMsg::k_EMsgGCServerWelcome | protobuf_mask; + std::string message = build_protomsg_header(msg_type); + + CMsgServerWelcome protomsg; + protomsg.set_min_allowed_version(0); + protomsg.set_active_version(0); + + protomsg.AppendToString(&message); + push_incoming(msg_type, message); +} + +void Steam_Game_Coordinator::callback_items_received(CSteamID steam_id, const std::vector &items) +{ + if (!gc_initialized) + return; + + if (gc_version < 20110414) { + uint32 msg_type = ESOMsg::k_ESOMsg_CacheSubscribed; + std::string message = build_msg_header(); + + uint64 owner_id = steam_id.ConvertToUint64(); + uint16 num_types = 1; + + ser_var(message, owner_id); + ser_var(message, num_types); + + // econ items (1) + uint32 object_type = 1; + uint16 num_objects = static_cast(items.size()); + + ser_var(message, object_type); + ser_var(message, num_objects); + + for (const Econ_Item &item : items) { + message.append(item_to_gcstruct(item, steam_id)); + } + + push_incoming(msg_type, message); + } else { + uint32 msg_type = ESOMsg::k_ESOMsg_CacheSubscribed | protobuf_mask; + std::string message = build_protomsg_header(msg_type); + + CMsgSOCacheSubscribed protomsg; + protomsg.set_owner(steam_id.ConvertToUint64()); + auto objects = protomsg.add_objects(); + objects->set_type_id(1); + + for (const Econ_Item &item : items) { + objects->add_object_data(item_to_gcprotobuf(item, steam_id)); + } + + protomsg.AppendToString(&message); + push_incoming(msg_type, message); + } +} + +void Steam_Game_Coordinator::callback_items_removed(CSteamID steam_id) +{ + if (!gc_initialized) + return; + + if (gc_version < 20110414) { + uint32 msg_type = ESOMsg::k_ESOMsg_CacheUnsubscribed; + std::string message = build_msg_header(); + ser_var(message, steam_id.ConvertToUint64()); + + push_incoming(msg_type, message); + } else { + uint32 msg_type = ESOMsg::k_ESOMsg_CacheUnsubscribed | protobuf_mask; + std::string message = build_protomsg_header(msg_type); + + CMsgSOCacheUnsubscribed protomsg; + protomsg.set_owner(steam_id.ConvertToUint64()); + + protomsg.AppendToString(&message); + push_incoming(msg_type, message); + } +} + +void Steam_Game_Coordinator::callback_item_updated(CSteamID steam_id, const Econ_Item &item) +{ + if (!gc_initialized) + return; + + if (gc_version < 20110414) { + uint32 msg_type = ESOMsg::k_ESOMsg_Update; + std::string message = build_msg_header(); + + uint64 owner_id = steam_id.ConvertToUint64(); + uint32 object_type = 1; + uint8 num_fields = 1; + + ser_var(message, owner_id); + ser_var(message, object_type); + ser_var(message, item.id); + ser_var(message, num_fields); + + uint8 field_idx = 5; + ser_var(message, field_idx); + ser_var(message, item.inv_pos); + + if (gc_version >= 20101027) { + ser_var(message, item.in_use); + } + + push_incoming(msg_type, message); + } else { + uint32 msg_type = ESOMsg::k_ESOMsg_Update | protobuf_mask; + std::string message = build_protomsg_header(msg_type); + + CMsgSOSingleObject protomsg; + protomsg.set_owner(steam_id.ConvertToUint64()); + protomsg.set_type_id(1); + protomsg.set_object_data(item_to_gcprotobuf(item, steam_id)); + + protomsg.AppendToString(&message); + push_incoming(msg_type, message); + } +} + +void Steam_Game_Coordinator::callback_item_deleted(CSteamID steam_id, uint64 item_id) +{ + if (!gc_initialized) + return; - // k_EMsgGCMOTDRequestResponse - uint32 msg_type = 1013; + if (gc_version < 20110414) { + uint32 msg_type = ESOMsg::k_ESOMsg_Destroy; + std::string message = build_msg_header(); + + uint64 owner_id = steam_id.ConvertToUint64(); + uint32 object_type = 1; + + ser_var(message, owner_id); + ser_var(message, object_type); + ser_var(message, item_id); + + push_incoming(msg_type, message); + } else { + uint32 msg_type = ESOMsg::k_ESOMsg_Destroy | protobuf_mask; + std::string message = build_protomsg_header(msg_type); + + CMsgSOSingleObject protomsg; + protomsg.set_owner(steam_id.ConvertToUint64()); + protomsg.set_type_id(1); + + CSOEconItem proto_item; + proto_item.set_id(item_id); + protomsg.set_object_data(proto_item.SerializeAsString()); + + protomsg.AppendToString(&message); + push_incoming(msg_type, message); + } +} + +void Steam_Game_Coordinator::callback_respawn_request(CSteamID steam_id) +{ + if (!gc_initialized) + return; + + uint32 msg_type = EGCItemMsg::k_EMsgGCRespawnPostLoadoutChange; std::string message = build_msg_header(); - uint16 num_entries = 0; - ser_var(message, num_entries); + ser_var(message, steam_id.ConvertToUint64()); push_incoming(msg_type, message); } @@ -285,24 +751,41 @@ void Steam_Game_Coordinator::initialize_gc() gc_initialized = true; - // Servers don't need anything. - if (is_server) - return; - - // Load user's items. - const auto &items = load_items_from_file(); - on_items_received(settings->get_local_steam_id(), items); + if (is_server) { + callback_server_welcome(); + } else { + callback_client_welcome(); - // HACK: Put any messages from the initialization directly into the queue so that the initial - // IsMessageAvailable call reads it. - for (const GC_Message &msg : pending_messages) { - incoming_messages.push(msg); + // Load user's items. + const auto &items = load_items_from_file(); + callback_items_received(settings->get_local_steam_id(), items); + } - GCMessageAvailable_t data{}; - data.m_nMessageSize = static_cast(msg.msg_body.size()); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + // Wait a bit until after the game has received welcome message from us before posting anything else. + // This avoids a race condition that can cause the game to receive inventory before parsing item schema. + // For old versions of TF2, we can't do that because they instead have a different bug where receiving + // the inventory late causes erroneous "new items" notifications. + if (gc_version >= 20110414) { + delay_init = true; } +} + +void Steam_Game_Coordinator::shutdown_gc() +{ + if (!gc_initialized) + return; + + items_loaded = false; + items.clear(); + all_user_items.clear(); + pending_items_requests.clear(); pending_messages.clear(); + while (incoming_messages.size()) + incoming_messages.pop(); + + welcome_received = false; + delay_init = false; + gc_initialized = false; } const std::vector &Steam_Game_Coordinator::load_items_from_file() @@ -337,16 +820,49 @@ const std::vector &Steam_Game_Coordinator::load_items_from_file() new_item.custom_name = it->value("custom_name", std::string()); new_item.custom_desc = it->value("custom_desc", std::string()); new_item.original_id = it->value("original_id", 0ull); + new_item.style = it->value("style", 0u); new_item.in_use = false; + if (it->contains("equip_states")) { + for (const auto &equip : it->at("equip_states")) { + uint32 class_id = equip.value("class", 0u); + uint32 slot_id = equip.value("slot", 0u); + + new_item.equip_states.insert({ class_id, slot_id }); + } + } + if (it->contains("attributes")) { for (const auto &attr : it->at("attributes")) { Econ_Item_Attribute new_attr{}; new_attr.def = attr.value("definition", 0u); - new_attr.value = attr.value("value", 0.0f); if (new_attr.def == 0) // 0 is not a valid attribute definition, however continue; + if (attr.contains("value")) { + new_attr.type = Econ_Item_Attribute::ATTR_TYPE_DEFAULT; + float value = attr.value("value", 0.0f); + ser_var(new_attr.value_bytes, value); + new_attr.value = value; + } else if (attr.contains("value_float")) { + new_attr.type = Econ_Item_Attribute::ATTR_TYPE_FLOAT; + float value = attr.value("value_float", 0.0f); + ser_var(new_attr.value_bytes, value); + new_attr.value = value; + } else if (attr.contains("value_int")) { + new_attr.type = Econ_Item_Attribute::ATTR_TYPE_INT; + uint32 value = attr.value("value_int", 0u); + ser_var(new_attr.value_bytes, value); + memcpy(&new_attr.value, &value, sizeof(float)); + } else if (attr.contains("value_string")) { + new_attr.type = Econ_Item_Attribute::ATTR_TYPE_STRING; + std::string value = attr.value("value_string", std::string()); + new_attr.value_bytes = value + '\0'; + new_attr.value = 0.0f; + } else { + continue; + } + new_item.attributes.push_back(new_attr); } } @@ -392,11 +908,45 @@ void Steam_Game_Coordinator::save_items_to_file() json_item["custom_name"] = item.custom_name; json_item["custom_desc"] = item.custom_desc; json_item["original_id"] = item_id_network_to_local(item.original_id); + json_item["style"] = item.style; + + for (auto &[class_id, slot_id] : item.equip_states) { + nlohmann::json json_equip; + json_equip["class"] = class_id; + json_equip["slot"] = slot_id; + json_item["equip_states"].push_back(json_equip); + } for (const Econ_Item_Attribute &attr : item.attributes) { nlohmann::json json_attr; json_attr["definition"] = attr.def; - json_attr["value"] = attr.value; + + switch (attr.type) { + case Econ_Item_Attribute::ATTR_TYPE_DEFAULT: { + float value; + attr.value_bytes.copy(reinterpret_cast(&value), sizeof(float)); + json_attr["value"] = value; + break; + } + case Econ_Item_Attribute::ATTR_TYPE_FLOAT: { + float value; + attr.value_bytes.copy(reinterpret_cast(&value), sizeof(float)); + json_attr["value_float"] = value; + break; + } + case Econ_Item_Attribute::ATTR_TYPE_INT: { + uint32 value; + attr.value_bytes.copy(reinterpret_cast(&value), sizeof(uint32)); + json_attr["value_int"] = value; + break; + } + case Econ_Item_Attribute::ATTR_TYPE_STRING: { + const char *value = attr.value_bytes.c_str(); + json_attr["value_string"] = value; + break; + } + } + json_item["attributes"].push_back(json_attr); } @@ -406,24 +956,26 @@ void Steam_Game_Coordinator::save_items_to_file() local_storage->write_json_file("", items_user_file, items_json); } -const Econ_Item *Steam_Game_Coordinator::set_item_pos(uint64 item_id, uint32 inv_pos, bool is_gc) +const Econ_Item *Steam_Game_Coordinator::set_item_pos(uint64 item_id, uint32 inv_pos, bool is_gc, bool save) { for (Econ_Item &item : items) { if (item.id != item_id) continue; item.inv_pos = inv_pos; - save_items_to_file(); + if (save) { + save_items_to_file(); + } // Let the others know, too. - auto inventory_msg = new GameServer_Items_Messages::InventoryPosUpdate(); - inventory_msg->set_item_id(item_id); - inventory_msg->set_item_inv_pos(inv_pos); + auto inventory_msg = new GameServer_Items_Messages::ItemUpdate(); + inventory_msg->set_id(item_id); + inventory_msg->set_inv_pos(inv_pos); auto gameserver_items_msg = new GameServer_Items_Messages(); - gameserver_items_msg->set_type(GameServer_Items_Messages::Request_UpdateInventoryPos); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_UpdateItem); gameserver_items_msg->set_is_gc(is_gc); - gameserver_items_msg->set_allocated_inventory_pos_update(inventory_msg); + gameserver_items_msg->set_allocated_item_update(inventory_msg); Common_Message msg{}; msg.set_allocated_gameserver_items_messages(gameserver_items_msg); @@ -446,13 +998,13 @@ bool Steam_Game_Coordinator::delete_item(uint64 item_id, bool is_gc) save_items_to_file(); // Let the others know, too. - auto drop_msg = new GameServer_Items_Messages::ItemDeletion(); - drop_msg->set_item_id(item_id); + auto delete_msg = new GameServer_Items_Messages::ItemDeletion(); + delete_msg->set_item_id(item_id); auto gameserver_items_msg = new GameServer_Items_Messages(); gameserver_items_msg->set_type(GameServer_Items_Messages::Request_DeleteItem); gameserver_items_msg->set_is_gc(is_gc); - gameserver_items_msg->set_allocated_item_deletion(drop_msg); + gameserver_items_msg->set_allocated_item_deletion(delete_msg); Common_Message msg{}; msg.set_allocated_gameserver_items_messages(gameserver_items_msg); @@ -517,14 +1069,7 @@ void Steam_Game_Coordinator::remove_user_items(CSteamID steam_id) } } - if (gc_initialized) { - // k_ESOMsg_CacheUnsubscribed - uint32 msg_type = 25; - std::string message = build_msg_header(); - ser_var(message, steam_id.ConvertToUint64()); - - push_incoming(msg_type, message); - } + callback_items_removed(steam_id); } void Steam_Game_Coordinator::on_client_connected(CSteamID steam_id) @@ -545,115 +1090,6 @@ void Steam_Game_Coordinator::on_client_disconnected(CSteamID steam_id) remove_user_items(steam_id); } -void Steam_Game_Coordinator::on_items_received(CSteamID steam_id, const std::vector &items) -{ - if (!gc_initialized) - return; - - // k_ESOMsg_CacheSubscribed - uint32 msg_type = 24; - std::string message = build_msg_header(); - - uint64 owner_id = steam_id.ConvertToUint64(); - uint16 num_types = 1; - - ser_var(message, owner_id); - ser_var(message, num_types); - - // econ items (1) - uint32 object_type = 1; - uint16 num_items = static_cast(items.size()); - - ser_var(message, object_type); - ser_var(message, num_items); - - for (const Econ_Item &item : items) { - ser_var(message, item.id); - ser_var(message, steam_id.GetAccountID()); - ser_var(message, item.def); - ser_var(message, item.level); - ser_var(message, item.quality); - ser_var(message, item.inv_pos); - ser_var(message, item.quantity); - - if (gc_version >= 20100428) { - // Strings are passed as UTF-8 which is good for us since we can just copy std::string as is. - ser_varstring(message, item.custom_name); - - if (gc_version >= 20100930) { - ser_var(message, item.flags); - - if (gc_version >= 20101027) { - ser_var(message, item.origin); - ser_varstring(message, item.custom_desc); - ser_var(message, item.in_use); - } - } - } - - ser_var(message, static_cast(item.attributes.size())); - - for (const Econ_Item_Attribute &attr : item.attributes) { - ser_var(message, attr.def); - ser_var(message, attr.value); - } - - if (gc_version >= 20101217) { - ser_var(message, item.original_id); - } - } - - push_incoming(msg_type, message); -} - -void Steam_Game_Coordinator::on_item_pos_updated(CSteamID steam_id, const Econ_Item &item) -{ - if (!gc_initialized) - return; - - // k_ESOMsg_Update - uint32 msg_type = 22; - std::string message = build_msg_header(); - - uint64 owner_id = steam_id.ConvertToUint64(); - uint32 object_type = 1; - uint8 num_fields = 1; - - ser_var(message, owner_id); - ser_var(message, object_type); - ser_var(message, item.id); - ser_var(message, num_fields); - - uint8 field_idx = 5; - ser_var(message, field_idx); - ser_var(message, item.inv_pos); - - if (gc_version >= 20101027) { - ser_var(message, item.in_use); - } - - push_incoming(msg_type, message); -} - -void Steam_Game_Coordinator::on_item_deleted(CSteamID steam_id, uint64 item_id) -{ - if (!gc_initialized) - return; - - // k_ESOMsg_Destroy - uint32 msg_type = 23; - std::string message = build_msg_header(); - - uint64 owner_id = steam_id.ConvertToUint64(); - uint32 object_type = 1; - - ser_var(message, owner_id); - ser_var(message, object_type); - ser_var(message, item_id); - - push_incoming(msg_type, message); -} - // sends a message to the Game Coordinator EGCResults Steam_Game_Coordinator::SendMessage_( uint32 unMsgType, const void *pubData, uint32 cubData ) { @@ -664,22 +1100,34 @@ EGCResults Steam_Game_Coordinator::SendMessage_( uint32 unMsgType, const void *p return k_EGCResultOK; switch (unMsgType) { - case 1001: + case EGCItemMsg::k_EMsgGCSetSingleItemPosition: PRINT_DEBUG("k_EMsgGCSetSingleItemPosition"); handle_set_item_pos(pubData, cubData); break; - case 1004: + case EGCItemMsg::k_EMsgGCDelete: PRINT_DEBUG("k_EMsgGCDelete"); handle_delete_item(pubData, cubData); break; - case 1012: + case EGCItemMsg::k_EMsgGCMOTDRequest: PRINT_DEBUG("k_EMsgGCMOTDRequest"); handle_motd_request(pubData, cubData); break; - case 1029: + case EGCItemMsg::k_EMsgGCRespawnPostLoadoutChange: PRINT_DEBUG("k_EMsgGCRespawnPostLoadoutChange"); handle_respawn(pubData, cubData); break; + case EGCItemMsg::k_EMsgGCSetItemStyle: + PRINT_DEBUG("k_EMsgGCSetItemStyle"); + handle_set_item_style(pubData, cubData); + break; + case EGCItemMsg::k_EMsgGCAdjustItemEquippedState | protobuf_mask: + PRINT_DEBUG("k_EMsgGCAdjustItemEquippedState"); + handle_adjust_equip_state(pubData, cubData); + break; + case EGCItemMsg::k_EMsgGCSetItemPositions | protobuf_mask: + PRINT_DEBUG("k_EMsgGCSetItemPositions"); + handle_set_multiple_item_pos(pubData, cubData); + break; default: break; } @@ -700,6 +1148,7 @@ bool Steam_Game_Coordinator::IsMessageAvailable( uint32 *pcubMsgSize ) GC_Message &message = incoming_messages.front(); *pcubMsgSize = static_cast(message.msg_body.size()); + return true; } @@ -724,11 +1173,16 @@ EGCResults Steam_Game_Coordinator::RetrieveMessage( uint32 *punMsgType, void *pu return k_EGCResultBufferTooSmall; } + if (is_welcome_message(message)) { + welcome_received = true; + welcome_time = std::chrono::high_resolution_clock::now(); + } + *punMsgType = message.msg_type; *pcubMsgSize = outsize; message.msg_body.copy(reinterpret_cast(pubDest), cubDest); - incoming_messages.pop(); + return k_EGCResultOK; } @@ -764,11 +1218,20 @@ void Steam_Game_Coordinator::network_callback_inventory_request(Common_Message * new_item->set_custom_name(item.custom_name); new_item->set_custom_desc(item.custom_desc); new_item->set_original_id(item.original_id); + new_item->set_in_use(item.in_use); + new_item->set_style(item.style); - for (const auto &attr : item.attributes) { + for (const auto &[class_id, slot_id] : item.equip_states) { + auto new_state = new_item->add_equip_states(); + new_state->set_class_id(class_id); + new_state->set_slot_id(slot_id); + } + + for (const Econ_Item_Attribute &attr : item.attributes) { auto new_attr = new_item->add_attributes(); new_attr->set_def(attr.def); new_attr->set_value(attr.value); + new_attr->set_value_bytes(attr.value_bytes); } } @@ -819,7 +1282,7 @@ void Steam_Game_Coordinator::network_callback_inventory_response(Common_Message items.clear(); for (const auto &item : response_msg.items()) { - Econ_Item new_item; + Econ_Item new_item{}; new_item.id = item.id(); new_item.def = item.def(); new_item.level = item.level(); @@ -831,14 +1294,20 @@ void Steam_Game_Coordinator::network_callback_inventory_response(Common_Message new_item.custom_name = item.custom_name(); new_item.custom_desc = item.custom_desc(); new_item.original_id = item.original_id(); - new_item.in_use = false; + new_item.in_use = item.in_use(); + new_item.style = item.style(); if (new_item.id == 0) continue; + for (const auto &state : item.equip_states()) { + new_item.equip_states.insert({ state.class_id(), state.slot_id() }); + } + for (const auto &attr : item.attributes()) { - Econ_Item_Attribute new_attr; + Econ_Item_Attribute new_attr{}; new_attr.def = attr.def(); new_attr.value = attr.value(); + new_attr.value_bytes = attr.value_bytes(); if (new_attr.def == 0) continue; @@ -858,21 +1327,21 @@ void Steam_Game_Coordinator::network_callback_inventory_response(Common_Message } if (is_gc) { - on_items_received(user_steamid, items); + callback_items_received(user_steamid, items); } else { - server_items()->on_items_received(user_steamid, items.size(), api_call, true); + server_items()->callback_items_received(user_steamid, items.size(), api_call, true); } PRINT_DEBUG("got player inventory: %u items", response_msg.items_size()); } -// user updated item inventory position -void Steam_Game_Coordinator::network_callback_inventory_pos_update(Common_Message *msg) +// user updated an item +void Steam_Game_Coordinator::network_callback_item_update(Common_Message *msg) { uint64 user_steamid = msg->source_id(); - PRINT_DEBUG("player updated item inventory position %llu", user_steamid); - if (!msg->gameserver_items_messages().has_inventory_pos_update()) { + PRINT_DEBUG("player updated an item %llu", user_steamid); + if (!msg->gameserver_items_messages().has_item_update()) { PRINT_DEBUG("error empty msg"); return; } @@ -883,9 +1352,8 @@ void Steam_Game_Coordinator::network_callback_inventory_pos_update(Common_Messag } bool is_gc = msg->gameserver_items_messages().is_gc(); - const auto &inventory_msg = msg->gameserver_items_messages().inventory_pos_update(); - uint64 item_id = inventory_msg.item_id(); - uint32 item_inv_pos = inventory_msg.item_inv_pos(); + const auto &inventory_msg = msg->gameserver_items_messages().item_update(); + uint64 item_id = inventory_msg.id(); auto &items = all_user_items.at(user_steamid); @@ -893,15 +1361,30 @@ void Steam_Game_Coordinator::network_callback_inventory_pos_update(Common_Messag if (item.id != item_id) continue; - item.inv_pos = item_inv_pos; + if (inventory_msg.has_inv_pos()) { + item.inv_pos = inventory_msg.inv_pos(); + PRINT_DEBUG("got updated item inventory pos: %llu 0x%08X", item_id, inventory_msg.inv_pos()); + } + + if (inventory_msg.has_style()) { + item.style = inventory_msg.style(); + PRINT_DEBUG("got updated item style: %llu %u", item_id, inventory_msg.style()); + } + + if (inventory_msg.has_equip_states()) { + item.equip_states.clear(); + for (const auto &state : inventory_msg.equip_states()) { + item.equip_states.insert({ state.class_id(), state.slot_id() }); + } + PRINT_DEBUG("got updated item equip states: %llu", item_id); + } if (is_gc) { - on_item_pos_updated(user_steamid, item); - } else { - server_items()->on_item_pos_updated(user_steamid, item_id, item_inv_pos); + callback_item_updated(user_steamid, item); + } else if (inventory_msg.has_inv_pos()) { + server_items()->callback_item_pos_updated(user_steamid, item_id, inventory_msg.inv_pos()); } - PRINT_DEBUG("got updated item inventory position: %llu 0x%08X", item_id, item_inv_pos); return; } @@ -925,8 +1408,8 @@ void Steam_Game_Coordinator::network_callback_item_deletion(Common_Message *msg) } bool is_gc = msg->gameserver_items_messages().is_gc(); - const auto &drop_msg = msg->gameserver_items_messages().item_deletion(); - uint64 item_id = drop_msg.item_id(); + const auto &delete_msg = msg->gameserver_items_messages().item_deletion(); + uint64 item_id = delete_msg.item_id(); auto &items = all_user_items.at(user_steamid); @@ -937,9 +1420,9 @@ void Steam_Game_Coordinator::network_callback_item_deletion(Common_Message *msg) items.erase(it); if (is_gc) { - on_item_deleted(user_steamid, item_id); + callback_item_deleted(user_steamid, item_id); } else { - server_items()->on_item_deleted(user_steamid, item_id); + server_items()->callback_item_deleted(user_steamid, item_id); } PRINT_DEBUG("deleted player's inventory item: %llu", item_id); @@ -952,19 +1435,14 @@ void Steam_Game_Coordinator::network_callback_item_deletion(Common_Message *msg) // user wants to respawn after loadout change void Steam_Game_Coordinator::network_callback_respawn_request(Common_Message *msg) { - if (!gc_initialized || !is_server) + if (!is_server) return; uint64 user_steamid = msg->source_id(); if (!all_user_items.count(user_steamid)) return; - // k_EMsgGCRespawnPostLoadoutChange - uint32 msg_type = 1029; - std::string message = build_msg_header(); - ser_var(message, user_steamid); - - push_incoming(msg_type, message); + callback_respawn_request(user_steamid); } // only triggered when we have a message @@ -984,9 +1462,9 @@ void Steam_Game_Coordinator::network_callback(Common_Message *msg) network_callback_inventory_response(msg); break; - // user updated item inventory position - case GameServer_Items_Messages::Request_UpdateInventoryPos: - network_callback_inventory_pos_update(msg); + // user updated an item + case GameServer_Items_Messages::Request_UpdateItem: + network_callback_item_update(msg); break; // user deleted an item @@ -1005,18 +1483,21 @@ void Steam_Game_Coordinator::network_callback(Common_Message *msg) } } else if (msg->has_low_level()) { if (!is_server && gc_initialized) { - uint64 user_steamid = msg->source_id(); - - // Client needs to know other players' inventories as well since the game uses them to - // validate cosmetic items. - switch (msg->low_level().type()) { - case Low_Level::CONNECT: - request_user_items(user_steamid, generate_steam_api_call_id(), true); - break; - - case Low_Level::DISCONNECT: - remove_user_items(user_steamid); - break; + CSteamID user_steamid; + user_steamid.SetFromUint64(msg->source_id()); + + if (user_steamid.BIndividualAccount()) { + // Client needs to know other players' inventories as well since the game uses them to + // validate cosmetic items. + switch (msg->low_level().type()) { + case Low_Level::CONNECT: + request_user_items(user_steamid, generate_steam_api_call_id(), true); + break; + + case Low_Level::DISCONNECT: + remove_user_items(user_steamid); + break; + } } } } @@ -1024,13 +1505,22 @@ void Steam_Game_Coordinator::network_callback(Common_Message *msg) void Steam_Game_Coordinator::RunCallbacks() { + if (delay_init && welcome_received && check_timedout(welcome_time, 0.2)) { + delay_init = false; + } + for (auto it = pending_messages.begin(); it != pending_messages.end();) { + if (delay_init && !is_welcome_message(*it)) { + it++; + continue; + } + if (check_timedout(it->created, it->post_in)) { incoming_messages.push(*it); GCMessageAvailable_t data{}; data.m_nMessageSize = static_cast(it->msg_body.size()); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.0); it = pending_messages.erase(it); } else { @@ -1041,7 +1531,7 @@ void Steam_Game_Coordinator::RunCallbacks() for (auto it = pending_items_requests.begin(); it != pending_items_requests.end();) { if (check_timedout(it->created, 7.0)) { if (!it->is_gc) { - server_items()->on_items_received(it->steam_id, items.size(), it->steam_api_call, false); + server_items()->callback_items_received(it->steam_id, items.size(), it->steam_api_call, false); } PRINT_DEBUG("player inventory request timeout %llu", it->steam_id); diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index c3d83534c..a2e4ed130 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -70,7 +70,6 @@ bool Steam_GameServer::set_appid(int32 input_appid, const char *game_dir, bool a server_data.set_appid(appid); settings->set_game_id(CGameID(appid)); network->setAppID(appid); - get_steam_client()->steam_gameserver_game_coordinator->initialize_gc(); PRINT_DEBUG("set server appid to %u", appid); return true; @@ -180,6 +179,8 @@ bool Steam_GameServer::InitGameServer( uint32 unIP, uint16 usGamePort, uint16 us if (!settings->disable_source_query) network->startQuery({ unIP, usQueryPort }); + get_steam_client()->steam_gameserver_game_coordinator->initialize_gc(); + call_servers_connected = false; call_servers_disconnected = false; @@ -290,12 +291,14 @@ void Steam_GameServer::LogOff() logged_in = false; settings->set_offline(true); - - // Shutdown Source Query - network->shutDownQuery(); - // And empty the queue if needed - outgoing_packets.clear(); } + + // Shutdown Source Query + network->shutDownQuery(); + // And empty the queue if needed + outgoing_packets.clear(); + + get_steam_client()->steam_gameserver_game_coordinator->shutdown_gc(); } @@ -599,6 +602,8 @@ bool Steam_GameServer::BSetServerType( uint32 unServerFlags, uint32 unGameIP, ui if (!settings->disable_source_query && !network->isQueryAlive()) network->startQuery({ unGameIP, usQueryPort }); + get_steam_client()->steam_gameserver_game_coordinator->initialize_gc(); + return true; } diff --git a/dll/steam_gameserver_items.cpp b/dll/steam_gameserver_items.cpp index 6a989a4f6..c95a0b1dd 100644 --- a/dll/steam_gameserver_items.cpp +++ b/dll/steam_gameserver_items.cpp @@ -34,7 +34,7 @@ Steam_GameServer_Items::~Steam_GameServer_Items() { } -void Steam_GameServer_Items::on_items_received(CSteamID steam_id, size_t num_items, SteamAPICall_t api_call, bool success) +void Steam_GameServer_Items::callback_items_received(CSteamID steam_id, size_t num_items, SteamAPICall_t api_call, bool success) { GSItemCount_t data{}; data.m_OwnerID = steam_id; @@ -49,7 +49,7 @@ void Steam_GameServer_Items::on_items_received(CSteamID steam_id, size_t num_ite callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); } -void Steam_GameServer_Items::on_item_pos_updated(CSteamID steam_id, uint64 item_id, uint32 inv_pos) +void Steam_GameServer_Items::callback_item_pos_updated(CSteamID steam_id, uint64 item_id, uint32 inv_pos) { GSItemInventoryPosUpdated_t data{}; data.m_SteamID = steam_id; @@ -57,7 +57,7 @@ void Steam_GameServer_Items::on_item_pos_updated(CSteamID steam_id, uint64 item_ callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.15); } -void Steam_GameServer_Items::on_item_deleted(CSteamID steam_id, uint64 item_id) +void Steam_GameServer_Items::callback_item_deleted(CSteamID steam_id, uint64 item_id) { GSItemDeleted_t data{}; data.m_SteamID = steam_id; @@ -181,7 +181,7 @@ bool Steam_GameServer_Items::GetItemAttribute( uint64 ulItemID, uint32 unAttribu PRINT_DEBUG("%llu %u", ulItemID, unAttributeIndex); std::lock_guard lock(global_mutex); - for (const auto &[steamID, items] : gc()->get_all_user_items()) { + for (const auto &[steam_id, items] : gc()->get_all_user_items()) { for (const Econ_Item &item : items) { if (item.id != ulItemID) continue; diff --git a/post_build/steam_settings.EXAMPLE/gc.EXAMPLE.json b/post_build/steam_settings.EXAMPLE/gc.EXAMPLE.json index 6d4e9abd8..47dd9ae3c 100644 --- a/post_build/steam_settings.EXAMPLE/gc.EXAMPLE.json +++ b/post_build/steam_settings.EXAMPLE/gc.EXAMPLE.json @@ -1,3 +1,4 @@ { + "gc_profile": "tf2", "gc_version": 20091217 } diff --git a/premake5.lua b/premake5.lua index e70176b58..be36d38fa 100644 --- a/premake5.lua +++ b/premake5.lua @@ -161,6 +161,7 @@ local function genproto() local protoc_exe = path.join(deps_dir, 'protobuf', deps_install_prefix, 'bin', 'protoc') local out_dir = 'proto_gen/' .. os_iden + local out_dir_tf2 = out_dir .. "/tf2" if os.target() == "windows" then protoc_exe = protoc_exe .. '.exe' @@ -178,6 +179,12 @@ local function genproto() return end + local ok_mk, err_mk = os.mkdir(out_dir_tf2) + if not ok_mk then + error("Error: " .. err_mk) + return + end + if os.target() == "linux" then local ok_chmod, err_chmod = os.chmod(protoc_exe, "777") if not ok_chmod then @@ -190,6 +197,12 @@ local function genproto() protoc_exe = 'wine ' .. protoc_exe end + if not os.execute(protoc_exe .. ' dll/gc_steam/steammessages.proto -I./dll/gc_steam --cpp_out=' .. out_dir ) then + return + end + if not os.execute(protoc_exe .. ' dll/gc_tf2/*.proto -I./dll/gc_steam -I./dll/gc_tf2 --cpp_out=' .. out_dir_tf2 ) then + return + end return os.execute(protoc_exe .. ' dll/net.proto -I./dll/ --cpp_out=' .. out_dir) end @@ -302,7 +315,7 @@ local deps_link = { } -- add protobuf libs table_append(deps_link, { - lib_prefix .. "protobuf-lite" .. static_postfix, + lib_prefix .. "protobuf" .. static_postfix, "absl_bad_any_cast_impl" .. static_postfix, "absl_bad_optional_access" .. static_postfix, "absl_bad_variant_access" .. static_postfix, From 95449c58368478e296d36e0b1adfba9277221cb7 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Mon, 30 Mar 2026 23:27:23 +0300 Subject: [PATCH 083/159] Add mutex locks to Steam_User functions --- dll/steam_user.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dll/steam_user.cpp b/dll/steam_user.cpp index f94faaf45..e3bb20e3d 100644 --- a/dll/steam_user.cpp +++ b/dll/steam_user.cpp @@ -50,6 +50,8 @@ HSteamUser Steam_User::GetHSteamUser() void Steam_User::LogOn( CSteamID steamID ) { PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + call_logged_on = true; call_logged_off = false; logon_time = std::chrono::high_resolution_clock::now(); @@ -62,6 +64,8 @@ void Steam_User::LogOn( CSteamID steamID ) void Steam_User::LogOff() { PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + call_logged_on = false; call_logged_off = true; logoff_time = std::chrono::high_resolution_clock::now(); @@ -80,12 +84,16 @@ void Steam_User::LogOff() bool Steam_User::BLoggedOn() { PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + return !settings->is_offline(); } ELogonState Steam_User::GetLogonState() { PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if(settings->is_offline()) return (ELogonState)0; else @@ -95,6 +103,8 @@ ELogonState Steam_User::GetLogonState() bool Steam_User::BConnected() { PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + return !settings->is_offline(); } @@ -103,6 +113,8 @@ bool Steam_User::BConnected() CSteamID Steam_User::GetSteamID() { PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + CSteamID id = settings->get_current_steam_id(); PRINT_DEBUG("GetSteamID() call #%u, returning %llu", settings->global_steamid_call_count, id.ConvertToUint64()); From 6386c759b27b7fc5d457db559ebdf15f7e2cd02c Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Mon, 30 Mar 2026 23:28:06 +0300 Subject: [PATCH 084/159] Update old interface function signatures --- dll/dll.cpp | 8 +++--- dll/dll/capicmcallback.h | 18 +++++++------- dll/dll/steam_user.h | 46 +++++++++++++++++------------------ dll/steam_user.cpp | 38 ++++++++++++++++------------- sdk/steam/isteamuser001.h | 38 ++++++++++++++++++----------- sdk/steam/isteamuser002.h | 33 +++++++++++++------------ sdk/steam/isteamuser002_old.h | 18 +++++++------- sdk/steam/isteamuser004.h | 17 ++++++++----- sdk/steam/isteamuser005.h | 8 +++--- sdk/steam/isteamuser006.h | 8 +++--- sdk/steam/isteamuser007.h | 8 +++--- 11 files changed, 131 insertions(+), 109 deletions(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index ca5b891d7..80b6a97a7 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -1880,7 +1880,7 @@ STEAMCLIENT_API void Steam3_LogOn( HSteamUser hUser, uint64 steamID ) ISteamUser002 *su = reinterpret_cast(get_steam_client()->GetISteamUser(hUser, "SteamUser002")); CSteamID steam_id_struct(steamID); - su->LogOn(&steam_id_struct); + su->LogOn_old(steam_id_struct); } STEAMCLIENT_API void Steam3_LogOff( HSteamUser hUser ) @@ -1921,7 +1921,7 @@ STEAMCLIENT_API steam_bool Steam3_GSSendLogonRequest( HSteamUser hUser, uint64 s ISteamUser002 *su = reinterpret_cast(get_steam_client()->GetISteamUser(hUser, "SteamUser002")); CSteamID steam_id_struct(steamID); - return su->GSSendLogonRequest(&steam_id_struct); + return su->GSSendLogonRequest(steam_id_struct); } STEAMCLIENT_API steam_bool Steam3_GSSendDisconnect( HSteamUser hUser, uint64 steamID ) @@ -1932,7 +1932,7 @@ STEAMCLIENT_API steam_bool Steam3_GSSendDisconnect( HSteamUser hUser, uint64 ste ISteamUser002 *su = reinterpret_cast(get_steam_client()->GetISteamUser(hUser, "SteamUser002")); CSteamID steam_id_struct(steamID); - return su->GSSendDisconnect(&steam_id_struct); + return su->GSSendDisconnect(steam_id_struct); } STEAMCLIENT_API steam_bool Steam3_GSSendStatusResponse( HSteamUser hUser, uint64 steamID, int nSecondsConnected, int nSecondsSinceLast ) @@ -1943,7 +1943,7 @@ STEAMCLIENT_API steam_bool Steam3_GSSendStatusResponse( HSteamUser hUser, uint64 ISteamUser002 *su = reinterpret_cast(get_steam_client()->GetISteamUser(hUser, "SteamUser002")); CSteamID steam_id_struct(steamID); - return su->GSSendStatusResponse(&steam_id_struct, nSecondsConnected, nSecondsSinceLast); + return su->GSSendStatusResponse(steam_id_struct, nSecondsConnected, nSecondsSinceLast); } STEAMCLIENT_API steam_bool Steam3_GSSetStatus( HSteamUser hUser, int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ) diff --git a/dll/dll/capicmcallback.h b/dll/dll/capicmcallback.h index d42355f5a..3448b58cd 100644 --- a/dll/dll/capicmcallback.h +++ b/dll/dll/capicmcallback.h @@ -24,10 +24,10 @@ typedef void (*OnLogonSuccessFunc)(); typedef void (*OnLogonFailureFunc)(EResult); typedef void (*OnLoggedOffFunc)(); typedef void (*OnBeginLogonRetryFunc)(); -typedef void (*HandleVACChallengeFunc)(int, void *, int); -typedef void (*GSHandleClientApproveFunc)(CSteamID *); -typedef void (*GSHandleClientDenyFunc)(CSteamID *, EDenyReason); -typedef void (*GSHandleClientKickFunc)(CSteamID *, EDenyReason); +typedef void (*HandleVACChallengeFunc)(int, uint8 *, int); +typedef void (*GSHandleClientApproveFunc)(CSteamID &); +typedef void (*GSHandleClientDenyFunc)(CSteamID &, EDenyReason); +typedef void (*GSHandleClientKickFunc)(CSteamID &, EDenyReason); class CCAPICMCallBack : public ICMCallback { @@ -53,13 +53,13 @@ class CCAPICMCallBack : public ICMCallback ~CCAPICMCallBack() {} void OnLogonSuccess() { OnLogonSuccess_ptr(); } - void OnLogonFailure(EResult result) { OnLogonFailure_ptr(result); } + void OnLogonFailure(EResult eResult) { OnLogonFailure_ptr(eResult); } void OnLoggedOff() { OnLoggedOff_ptr(); } void OnBeginLogonRetry() { OnBeginLogonRetry_ptr(); } - void HandleVACChallenge(int unk1, void *unk2, int unk3) { HandleVACChallenge_ptr(unk1, unk2, unk3); } - void GSHandleClientApprove(CSteamID *steamID) { GSHandleClientApprove_ptr(steamID); } - void GSHandleClientDeny(CSteamID *steamID, EDenyReason reason) { GSHandleClientDeny_ptr(steamID, reason); } - void GSHandleClientKick(CSteamID *steamID, EDenyReason reason) { GSHandleClientKick_ptr(steamID, reason); } + void HandleVACChallenge(int nClientGameID, uint8 *pubChallenge, int cubChallenge) { HandleVACChallenge_ptr(nClientGameID, pubChallenge, cubChallenge); } + void GSHandleClientApprove(CSteamID &steamID) { GSHandleClientApprove_ptr(steamID); } + void GSHandleClientDeny(CSteamID &steamID, EDenyReason eDenyReason) { GSHandleClientDeny_ptr(steamID, eDenyReason); } + void GSHandleClientKick(CSteamID &steamID, EDenyReason eDenyReason) { GSHandleClientKick_ptr(steamID, eDenyReason); } private: OnLogonSuccessFunc OnLogonSuccess_ptr{}; diff --git a/dll/dll/steam_user.h b/dll/dll/steam_user.h index 64845ee8e..8c0e87708 100644 --- a/dll/dll/steam_user.h +++ b/dll/dll/steam_user.h @@ -106,10 +106,10 @@ public ISteamUser // logon cookie - this is obsolete and never used int GetSteamGameConnectToken( void *pBlob, int cbMaxBlob ); - bool SetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, const char *pchValue ); - bool GetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ); - bool SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int iValue ); - bool GetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int *piValue ); + bool SetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, const char *pchValue ); + bool GetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ); + bool SetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int iValue ); + bool GetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int *piValue ); // Multiplayer Authentication functions @@ -332,28 +332,28 @@ public ISteamUser // SteamUser001 -------------------------------------------- void Init( ICMCallback001 *cmcallback, ISteam2Auth *steam2auth ); int ProcessCall( int unk ); - virtual void LogOn( CSteamID *steamID ); - virtual int CreateAccount( const char *unk1, void *unk2, void *unk3, const char *unk4, int unk5, void *unk6 ); - virtual bool GSSendLogonRequest( CSteamID *steamID ); - virtual bool GSSendDisconnect( CSteamID *steamID ); - virtual bool GSSendStatusResponse( CSteamID *steamID, int nSecondsConnected, int nSecondsSinceLast ); - virtual bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax ); - virtual void Test_SuspendActivity() {} - virtual void Test_ResumeActivity() {} - virtual void Test_SendVACResponse( int unk1, void *unk2, int unk3 ) {} - virtual void Test_SetFakePrivateIP( uint32 ip ) {} - virtual void Test_SendBigMessage() {} - virtual bool Test_BBigMessageResponseReceived() { return true; } - virtual void Test_SetPktLossPct( int unk1 ) {} - virtual void Test_SetForceTCP( bool unk1 ) {} - virtual void Test_Heartbeat() {} - virtual void Test_FakeDisconnect() {} - virtual EUniverse Test_GetEUniverse() { return k_EUniversePublic; } + void LogOn_old( CSteamID &steamID ); + int CreateAccount( const char *unk1, uint8 *unk2, uint8 *unk3, const char *unk4, int unk5, uint8 *unk6 ); + bool GSSendLogonRequest( CSteamID &steamID ); + bool GSSendDisconnect( CSteamID &steamID ); + bool GSSendStatusResponse( CSteamID &steamID, int nSecondsConnected, int nSecondsSinceLast ); + bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax ); + void Test_SuspendActivity() {} + void Test_ResumeActivity() {} + void Test_SendVACResponse( int nClientGameID, uint8 *pubResponse, int cubResponse ) {} + void Test_SetFakePrivateIP( uint32 unIPPrivate ) {} + void Test_SendBigMessage() {} + bool Test_BBigMessageResponseReceived() { return true; } + void Test_SetPktLossPct( int nPct ) {} + void Test_SetForceTCP( bool bForceTCP ) {} + void Test_Heartbeat() {} + void Test_FakeDisconnect() {} + EUniverse Test_GetEUniverse() { return k_EUniversePublic; } // SteamUser001 -------------------------------------------- // SteamUser002 (old) -------------------------------------- - virtual bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ); - virtual void Test_SetMaxUDPConnectionAttempts( int unk1 ) {} + bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ); + void Test_SetMaxUDPConnectionAttempts( int unk1 ) {} // SteamUser002 (old) -------------------------------------- // SteamUser002 (new) -------------------------------------- diff --git a/dll/steam_user.cpp b/dll/steam_user.cpp index e3bb20e3d..05c0f846b 100644 --- a/dll/steam_user.cpp +++ b/dll/steam_user.cpp @@ -125,6 +125,10 @@ CSteamID Steam_User::GetSteamID() bool Steam_User::IsVACBanned( int nGameID ) { PRINT_DEBUG_ENTRY(); + // EVACBan to appid map: + // k_EVACBanGoldsrc -> 10 + // k_EVACBanSource -> 240 + // k_EVACBanDayOfDefeatSource -> 300 return false; } @@ -189,7 +193,7 @@ int Steam_User::GetSteamGameConnectToken( void *pBlob, int cbMaxBlob ) return out_size; } -bool Steam_User::SetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) +bool Steam_User::SetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) { PRINT_DEBUG_TODO(); if (!pchValue) @@ -209,7 +213,7 @@ bool Steam_User::SetRegistryString( EConfigSubTree eRegistrySubTree, const char return true; } -bool Steam_User::GetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) +bool Steam_User::GetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) { PRINT_DEBUG_TODO(); // TODO: read data on disk, because real steam can get the string when app restarts @@ -235,7 +239,7 @@ bool Steam_User::GetRegistryString( EConfigSubTree eRegistrySubTree, const char return true; } -bool Steam_User::SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int iValue ) +bool Steam_User::SetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int iValue ) { PRINT_DEBUG_TODO(); if (!pchKey) // tested on real steam @@ -252,7 +256,7 @@ bool Steam_User::SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pc return true; } -bool Steam_User::GetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int *piValue ) +bool Steam_User::GetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int *piValue ) { PRINT_DEBUG_TODO(); // TODO: read data on disk, because real steam can get the string when app restarts @@ -931,42 +935,42 @@ int Steam_User::ProcessCall( int unk ) return 0; } -void Steam_User::LogOn( CSteamID *steamID ) +void Steam_User::LogOn_old( CSteamID &steamID ) { PRINT_DEBUG_ENTRY(); - LogOn(*steamID); + LogOn(steamID); } -int Steam_User::CreateAccount( const char *unk1, void *unk2, void *unk3, const char *unk4, int unk5, void *unk6 ) +int Steam_User::CreateAccount( const char *unk1, uint8 *unk2, uint8 *unk3, const char *unk4, int unk5, uint8 *unk6 ) { PRINT_DEBUG_TODO(); return 0; } -bool Steam_User::GSSendLogonRequest( CSteamID *steamID ) +bool Steam_User::GSSendLogonRequest( CSteamID &steamID ) { - PRINT_DEBUG("%llu", (*steamID).ConvertToUint64()); + PRINT_DEBUG("%llu", steamID.ConvertToUint64()); std::lock_guard lock(global_mutex); // At this point, client auth is still primarily done via Steam2 (Steam.dll), Steam3 is supplementary. // As such, there's not much really needed from us besides firing approve callback. // Since SteamID passed into this comes from Steam.dll, it won't match the client's Goldberg SteamID. - std::pair entry(*steamID, std::chrono::high_resolution_clock::now()); + std::pair entry(steamID, std::chrono::high_resolution_clock::now()); player_auths.push_back(entry); - get_steam_client()->steam_gameserver->add_player(*steamID); + get_steam_client()->steam_gameserver->add_player(steamID); return true; } -bool Steam_User::GSSendDisconnect( CSteamID *steamID ) +bool Steam_User::GSSendDisconnect( CSteamID &steamID ) { - PRINT_DEBUG("%llu", (*steamID).ConvertToUint64()); + PRINT_DEBUG("%llu", steamID.ConvertToUint64()); std::lock_guard lock(global_mutex); - get_steam_client()->steam_gameserver->remove_player(*steamID); + get_steam_client()->steam_gameserver->remove_player(steamID); return true; } -bool Steam_User::GSSendStatusResponse( CSteamID *steamID, int nSecondsConnected, int nSecondsSinceLast ) +bool Steam_User::GSSendStatusResponse( CSteamID &steamID, int nSecondsConnected, int nSecondsSinceLast ) { PRINT_DEBUG_TODO(); return false; @@ -1133,7 +1137,7 @@ void Steam_User::RunCallbacks() for (auto it = player_auths.begin(); it != player_auths.end();) { if (check_timedout(it->second, 0.1)) { PRINT_DEBUG("ICMCallback -> GSHandleClientApprove %llu", it->first.ConvertToUint64()); - callbacks_old1->GSHandleClientApprove(&(it->first)); + callbacks_old1->GSHandleClientApprove(it->first); it = player_auths.erase(it); } else { it++; @@ -1156,7 +1160,7 @@ void Steam_User::RunCallbacks() for (auto it = player_auths.begin(); it != player_auths.end();) { if (check_timedout(it->second, 0.1)) { PRINT_DEBUG("ICMCallback -> GSHandleClientApprove %llu", it->first.ConvertToUint64()); - callbacks_old2->GSHandleClientApprove(&(it->first)); + callbacks_old2->GSHandleClientApprove(it->first); it = player_auths.erase(it); } else { it++; diff --git a/sdk/steam/isteamuser001.h b/sdk/steam/isteamuser001.h index 9c44c1dde..4dbdc1a0f 100644 --- a/sdk/steam/isteamuser001.h +++ b/sdk/steam/isteamuser001.h @@ -7,18 +7,28 @@ // this interface version is not found in public SDK archives, it is based on reversing old Linux binaries +//----------------------------------------------------------------------------- +// Purpose: types of VAC bans +//----------------------------------------------------------------------------- +enum EVACBan +{ + k_EVACBanGoldsrc, + k_EVACBanSource, + k_EVACBanDayOfDefeatSource, +}; + class ICMCallback001 { public: virtual ~ICMCallback001() {} virtual void OnLogonSuccess() = 0; - virtual void OnLogonFailure( EResult result ) = 0; + virtual void OnLogonFailure( EResult eResult ) = 0; virtual void OnLoggedOff() = 0; - virtual void HandleVACChallenge( int unk1, void *unk2, int unk3 ) = 0; - virtual void GSHandleClientApprove( CSteamID *steamID ) = 0; - virtual void GSHandleClientDeny( CSteamID *steamID, EDenyReason reason ) = 0; - virtual void GSHandleClientKick( CSteamID *steamID, EDenyReason reason ) = 0; + virtual void HandleVACChallenge( int nClientGameID, uint8 *pubChallenge, int cubChallenge ) = 0; + virtual void GSHandleClientApprove( CSteamID &steamID ) = 0; + virtual void GSHandleClientDeny( CSteamID &steamID, EDenyReason eDenyReason ) = 0; + virtual void GSHandleClientKick( CSteamID &steamID, EDenyReason eDenyReason ) = 0; }; class ISteam2Auth @@ -33,29 +43,29 @@ class ISteamUser001 public: virtual void Init( ICMCallback001 *cmcallback, ISteam2Auth *steam2auth ) = 0; virtual int ProcessCall( int unk ) = 0; - virtual void LogOn( CSteamID *steamID ) = 0; + virtual void LogOn_old( CSteamID &steamID ) = 0; virtual void LogOff() = 0; virtual bool BLoggedOn() = 0; virtual bool BConnected() = 0; - virtual int CreateAccount( const char *unk1, void *unk2, void *unk3, const char *unk4, int unk5, void *unk6 ) = 0; + virtual int CreateAccount( const char *unk1, uint8 *unk2, uint8 *unk3, const char *unk4, int unk5, uint8 *unk6 ) = 0; virtual bool IsVACBanned( int nGameID ) = 0; virtual bool RequireShowVACBannedMessage( int nGameID ) = 0; virtual void AcknowledgeVACBanning( int nGameID ) = 0; - virtual bool GSSendLogonRequest( CSteamID *steamID ) = 0; - virtual bool GSSendDisconnect( CSteamID *steamID ) = 0; - virtual bool GSSendStatusResponse( CSteamID *steamID, int nSecondsConnected, int nSecondsSinceLast ) = 0; + virtual bool GSSendLogonRequest( CSteamID &steamID ) = 0; + virtual bool GSSendDisconnect( CSteamID &steamID ) = 0; + virtual bool GSSendStatusResponse( CSteamID &steamID, int nSecondsConnected, int nSecondsSinceLast ) = 0; virtual bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax ) = 0; virtual int NClientGameIDAdd( int nGameID ) = 0; virtual void RemoveClientGame( int nClientGameID ) = 0; virtual void SetClientGameServer( int nClientGameID, uint32 unIPServer, uint16 usPortServer ) = 0; virtual void Test_SuspendActivity() = 0; virtual void Test_ResumeActivity() = 0; - virtual void Test_SendVACResponse( int unk1, void *unk2, int unk3 ) = 0; - virtual void Test_SetFakePrivateIP( uint32 ip ) = 0; + virtual void Test_SendVACResponse( int nClientGameID, uint8 *pubResponse, int cubResponse ) = 0; + virtual void Test_SetFakePrivateIP( uint32 unIPPrivate ) = 0; virtual void Test_SendBigMessage() = 0; virtual bool Test_BBigMessageResponseReceived() = 0; - virtual void Test_SetPktLossPct( int unk1 ) = 0; - virtual void Test_SetForceTCP( bool unk1 ) = 0; + virtual void Test_SetPktLossPct( int nPct ) = 0; + virtual void Test_SetForceTCP( bool bForceTCP ) = 0; virtual void Test_Heartbeat() = 0; virtual void Test_FakeDisconnect() = 0; virtual EUniverse Test_GetEUniverse() = 0; diff --git a/sdk/steam/isteamuser002.h b/sdk/steam/isteamuser002.h index abdf6e002..924752996 100644 --- a/sdk/steam/isteamuser002.h +++ b/sdk/steam/isteamuser002.h @@ -9,7 +9,10 @@ enum ELogonState { - ELogonState_stub + k_ELogonStateNotLoggedOn = 0, + k_ELogonStateLoggingOn = 1, + k_ELogonStateLoggingOff = 2, + k_ELogonStateLoggedOn = 3 }; class ICMCallback @@ -18,13 +21,13 @@ class ICMCallback virtual ~ICMCallback() {} virtual void OnLogonSuccess() = 0; - virtual void OnLogonFailure( EResult result ) = 0; + virtual void OnLogonFailure( EResult eResult ) = 0; virtual void OnLoggedOff() = 0; virtual void OnBeginLogonRetry() = 0; - virtual void HandleVACChallenge( int unk1, void *unk2, int unk3 ) = 0; - virtual void GSHandleClientApprove( CSteamID *steamID ) = 0; - virtual void GSHandleClientDeny( CSteamID *steamID, EDenyReason reason ) = 0; - virtual void GSHandleClientKick( CSteamID *steamID, EDenyReason reason ) = 0; + virtual void HandleVACChallenge( int nClientGameID, uint8 *pubChallenge, int cubChallenge ) = 0; + virtual void GSHandleClientApprove( CSteamID &steamID ) = 0; + virtual void GSHandleClientDeny( CSteamID &steamID, EDenyReason eDenyReason ) = 0; + virtual void GSHandleClientKick( CSteamID &steamID, EDenyReason eDenyReason ) = 0; }; class ISteamUser002 @@ -32,30 +35,30 @@ class ISteamUser002 public: virtual void Init( ICMCallback *cmcallback, ISteam2Auth *steam2auth ) = 0; virtual int ProcessCall( int unk ) = 0; - virtual void LogOn( CSteamID *steamID ) = 0; + virtual void LogOn_old( CSteamID &steamID ) = 0; virtual void LogOff() = 0; virtual bool BLoggedOn() = 0; virtual ELogonState GetLogonState() = 0; virtual bool BConnected() = 0; - virtual int CreateAccount( const char *unk1, void *unk2, void *unk3, const char *unk4, int unk5, void *unk6 ) = 0; + virtual int CreateAccount( const char *unk1, uint8 *unk2, uint8 *unk3, const char *unk4, int unk5, uint8 *unk6 ) = 0; virtual bool IsVACBanned( int nGameID ) = 0; virtual bool RequireShowVACBannedMessage( int nGameID ) = 0; virtual void AcknowledgeVACBanning( int nGameID ) = 0; - virtual bool GSSendLogonRequest( CSteamID *steamID ) = 0; - virtual bool GSSendDisconnect( CSteamID *steamID ) = 0; - virtual bool GSSendStatusResponse( CSteamID *steamID, int nSecondsConnected, int nSecondsSinceLast ) = 0; + virtual bool GSSendLogonRequest( CSteamID &steamID ) = 0; + virtual bool GSSendDisconnect( CSteamID &steamID ) = 0; + virtual bool GSSendStatusResponse( CSteamID &steamID, int nSecondsConnected, int nSecondsSinceLast ) = 0; virtual bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ) = 0; virtual int NClientGameIDAdd( int nGameID ) = 0; virtual void RemoveClientGame( int nClientGameID ) = 0; virtual void SetClientGameServer( int nClientGameID, uint32 unIPServer, uint16 usPortServer ) = 0; virtual void Test_SuspendActivity() = 0; virtual void Test_ResumeActivity() = 0; - virtual void Test_SendVACResponse( int unk1, void *unk2, int unk3 ) = 0; - virtual void Test_SetFakePrivateIP( uint32 ip ) = 0; + virtual void Test_SendVACResponse( int nClientGameID, uint8 *pubResponse, int cubResponse ) = 0; + virtual void Test_SetFakePrivateIP( uint32 unIPPrivate ) = 0; virtual void Test_SendBigMessage() = 0; virtual bool Test_BBigMessageResponseReceived() = 0; - virtual void Test_SetPktLossPct( int unk1 ) = 0; - virtual void Test_SetForceTCP( bool unk1 ) = 0; + virtual void Test_SetPktLossPct( int nPct ) = 0; + virtual void Test_SetForceTCP( bool bForceTCP ) = 0; virtual void Test_SetMaxUDPConnectionAttempts( int unk1 ) = 0; virtual void Test_Heartbeat() = 0; virtual void Test_FakeDisconnect() = 0; diff --git a/sdk/steam/isteamuser002_old.h b/sdk/steam/isteamuser002_old.h index 23f1cee49..1cbab9433 100644 --- a/sdk/steam/isteamuser002_old.h +++ b/sdk/steam/isteamuser002_old.h @@ -12,29 +12,29 @@ class ISteamUser002_old public: virtual void Init( ICMCallback001 *cmcallback, ISteam2Auth *steam2auth ) = 0; virtual int ProcessCall( int unk ) = 0; - virtual void LogOn( CSteamID *steamID ) = 0; + virtual void LogOn_old( CSteamID &steamID ) = 0; virtual void LogOff() = 0; virtual bool BLoggedOn() = 0; virtual bool BConnected() = 0; - virtual int CreateAccount( const char *unk1, void *unk2, void *unk3, const char *unk4, int unk5, void *unk6 ) = 0; + virtual int CreateAccount( const char *unk1, uint8 *unk2, uint8 *unk3, const char *unk4, int unk5, uint8 *unk6 ) = 0; virtual bool IsVACBanned( int nGameID ) = 0; virtual bool RequireShowVACBannedMessage( int nGameID ) = 0; virtual void AcknowledgeVACBanning( int nGameID ) = 0; - virtual bool GSSendLogonRequest( CSteamID *steamID ) = 0; - virtual bool GSSendDisconnect( CSteamID *steamID ) = 0; - virtual bool GSSendStatusResponse( CSteamID *steamID, int nSecondsConnected, int nSecondsSinceLast ) = 0; + virtual bool GSSendLogonRequest( CSteamID &steamID ) = 0; + virtual bool GSSendDisconnect( CSteamID &steamID ) = 0; + virtual bool GSSendStatusResponse( CSteamID &steamID, int nSecondsConnected, int nSecondsSinceLast ) = 0; virtual bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ) = 0; virtual int NClientGameIDAdd( int nGameID ) = 0; virtual void RemoveClientGame( int nClientGameID ) = 0; virtual void SetClientGameServer( int nClientGameID, uint32 unIPServer, uint16 usPortServer ) = 0; virtual void Test_SuspendActivity() = 0; virtual void Test_ResumeActivity() = 0; - virtual void Test_SendVACResponse( int unk1, void *unk2, int unk3 ) = 0; - virtual void Test_SetFakePrivateIP( uint32 ip ) = 0; + virtual void Test_SendVACResponse( int nClientGameID, uint8 *pubResponse, int cubResponse ) = 0; + virtual void Test_SetFakePrivateIP( uint32 unIPPrivate ) = 0; virtual void Test_SendBigMessage() = 0; virtual bool Test_BBigMessageResponseReceived() = 0; - virtual void Test_SetPktLossPct( int unk1 ) = 0; - virtual void Test_SetForceTCP( bool unk1 ) = 0; + virtual void Test_SetPktLossPct( int nPct ) = 0; + virtual void Test_SetForceTCP( bool bForceTCP ) = 0; virtual void Test_SetMaxUDPConnectionAttempts( int unk1 ) = 0; virtual void Test_Heartbeat() = 0; virtual void Test_FakeDisconnect() = 0; diff --git a/sdk/steam/isteamuser004.h b/sdk/steam/isteamuser004.h index e40cddcab..9bceb8ae5 100644 --- a/sdk/steam/isteamuser004.h +++ b/sdk/steam/isteamuser004.h @@ -5,9 +5,14 @@ #pragma once #endif -enum EConfigSubTree +enum ERegistrySubTree { - EConfigSubTree_stub + k_ERegistrySubTreeNews = 0, + k_ERegistrySubTreeApps = 1, + k_ERegistrySubTreeSubscriptions = 2, + k_ERegistrySubTreeGameServers = 3, + k_ERegistrySubTreeFriends = 4, + k_ERegistrySubTreeSystem = 5, }; class ISteamUser004 @@ -35,10 +40,10 @@ class ISteamUser004 // logon cookie - this is obsolete and never used virtual int GetSteamGameConnectToken( void *pBlob, int cbMaxBlob ) = 0; - virtual bool SetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; - virtual bool GetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; - virtual bool SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; - virtual bool GetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; + virtual bool SetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; + virtual bool GetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; + virtual bool SetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; + virtual bool GetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; virtual int InitiateGameConnection( void *pBlob, int cbMaxBlob, CSteamID steamID, int nGameAppID, uint32 unIPServer, uint16 usPortServer, bool bSecure ) = 0; virtual void TerminateGameConnection( uint32 unIPServer, uint16 usPortServer ) = 0; virtual void SetSelfAsPrimaryChatDestination() = 0; diff --git a/sdk/steam/isteamuser005.h b/sdk/steam/isteamuser005.h index 6d1b0359d..b4220497b 100644 --- a/sdk/steam/isteamuser005.h +++ b/sdk/steam/isteamuser005.h @@ -25,10 +25,10 @@ class ISteamUser005 virtual void SetSteam2Ticket( uint8 *pubTicket, int cubTicket ) = 0; virtual void AddServerNetAddress( uint32 unIP, uint16 unPort ) = 0; virtual bool SetEmail( const char *pchEmail ) = 0; - virtual bool SetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; - virtual bool GetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; - virtual bool SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; - virtual bool GetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; + virtual bool SetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; + virtual bool GetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; + virtual bool SetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; + virtual bool GetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; virtual int InitiateGameConnection( void *pBlob, int cbMaxBlob, CSteamID steamID, CGameID gameID, uint32 unIPServer, uint16 usPortServer, bool bSecure ) = 0; virtual void TerminateGameConnection( uint32 unIPServer, uint16 usPortServer ) = 0; virtual void SetSelfAsPrimaryChatDestination() = 0; diff --git a/sdk/steam/isteamuser006.h b/sdk/steam/isteamuser006.h index ed9be0965..5a0955ffd 100644 --- a/sdk/steam/isteamuser006.h +++ b/sdk/steam/isteamuser006.h @@ -28,10 +28,10 @@ class ISteamUser006 // a CSteamID is a unique identifier for an account, and used to differentiate users in all parts of the Steamworks API virtual CSteamID GetSteamID() = 0; - virtual bool SetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; - virtual bool GetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; - virtual bool SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; - virtual bool GetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; + virtual bool SetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; + virtual bool GetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; + virtual bool SetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; + virtual bool GetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; // Multiplayer Authentication functions diff --git a/sdk/steam/isteamuser007.h b/sdk/steam/isteamuser007.h index b7e0d5db8..c728105e4 100644 --- a/sdk/steam/isteamuser007.h +++ b/sdk/steam/isteamuser007.h @@ -28,10 +28,10 @@ class ISteamUser007 // a CSteamID is a unique identifier for an account, and used to differentiate users in all parts of the Steamworks API virtual CSteamID GetSteamID() = 0; - virtual bool SetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; - virtual bool GetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; - virtual bool SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; - virtual bool GetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; + virtual bool SetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; + virtual bool GetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; + virtual bool SetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; + virtual bool GetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; // Multiplayer Authentication functions From 206ceb9959560d9c02e26d87663bf2d24a0ba24c Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Fri, 3 Apr 2026 19:36:44 +0300 Subject: [PATCH 085/159] Fix Steam_User::GetLogonState --- dll/steam_user.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/dll/steam_user.cpp b/dll/steam_user.cpp index 05c0f846b..7a3fa0e53 100644 --- a/dll/steam_user.cpp +++ b/dll/steam_user.cpp @@ -94,10 +94,17 @@ ELogonState Steam_User::GetLogonState() PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - if(settings->is_offline()) - return (ELogonState)0; - else - return (ELogonState)4; // tested on real steam, undocumented return value + if (settings->is_offline()) { + if (call_logged_on) { + return k_ELogonStateLoggingOn; + } else if (call_logged_off) { + return k_ELogonStateLoggingOff; + } else { + return k_ELogonStateNotLoggedOn; + } + } else { + return k_ELogonStateLoggedOn; + } } bool Steam_User::BConnected() From 6a2dc89c7cbf601bab3ae3a46214c685864354cf Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Fri, 3 Apr 2026 19:38:49 +0300 Subject: [PATCH 086/159] Use a var in Steam_User to distinguish client and server instances --- dll/dll/steam_user.h | 3 ++- dll/steam_client.cpp | 4 ++-- dll/steam_user.cpp | 13 +++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/dll/dll/steam_user.h b/dll/dll/steam_user.h index 8c0e87708..e21c87d75 100644 --- a/dll/dll/steam_user.h +++ b/dll/dll/steam_user.h @@ -53,6 +53,7 @@ public ISteamUser class SteamCallBacks *callbacks{}; class SteamCallResults *callback_results{}; Local_Storage *local_storage{}; + bool is_server{}; std::string encrypted_app_ticket{}; Auth_Manager *auth_manager{}; @@ -69,7 +70,7 @@ public ISteamUser std::vector> player_auths{}; public: - Steam_User(Settings *settings, Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks); + Steam_User(Settings *settings, Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, bool is_server); ~Steam_User(); // returns the HSteamUser this interface represents diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index 05aee2df8..b9d070ba8 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -100,7 +100,7 @@ Steam_Client::Steam_Client() PRINT_DEBUG("init client"); steam_overlay = new Steam_Overlay(settings_client, local_storage, callback_results_client, callbacks_client, run_every_runcb, network); - steam_user = new Steam_User(settings_client, local_storage, network, callback_results_client, callbacks_client); + steam_user = new Steam_User(settings_client, local_storage, network, callback_results_client, callbacks_client, false); steam_friends = new Steam_Friends(settings_client, local_storage, network, callback_results_client, callbacks_client, run_every_runcb, steam_overlay); steam_utils = new Steam_Utils(settings_client, callback_results_client, callbacks_client, steam_overlay); @@ -143,7 +143,7 @@ Steam_Client::Steam_Client() PRINT_DEBUG("init gameserver"); steam_gameserver = new Steam_GameServer(settings_server, network, callbacks_server); - steam_gameserver_user = new Steam_User(settings_server, local_storage, network, callback_results_server, callbacks_server); + steam_gameserver_user = new Steam_User(settings_server, local_storage, network, callback_results_server, callbacks_server, true); steam_gameserver_utils = new Steam_Utils(settings_server, callback_results_server, callbacks_server, steam_overlay); steam_gameserverstats = new Steam_GameServerStats(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); steam_gameserver_networking = new Steam_Networking(settings_server, network, callbacks_server, run_every_runcb); diff --git a/dll/steam_user.cpp b/dll/steam_user.cpp index 7a3fa0e53..d78136d76 100644 --- a/dll/steam_user.cpp +++ b/dll/steam_user.cpp @@ -21,13 +21,14 @@ #include "dll/base64.h" #include "dll/dll.h" -Steam_User::Steam_User(Settings *settings, Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks) +Steam_User::Steam_User(Settings *settings, Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, bool is_server) { this->settings = settings; this->local_storage = local_storage; this->network = network; this->callbacks = callbacks; this->callback_results = callback_results; + this->is_server = is_server; auth_manager = new Auth_Manager(settings, network, callbacks); voicechat = new VoiceChat(); @@ -44,7 +45,7 @@ Steam_User::~Steam_User() HSteamUser Steam_User::GetHSteamUser() { PRINT_DEBUG_ENTRY(); - return (settings == get_steam_client()->settings_server) ? SERVER_HSTEAMUSER : CLIENT_HSTEAMUSER; + return is_server ? SERVER_HSTEAMUSER : CLIENT_HSTEAMUSER; } void Steam_User::LogOn( CSteamID steamID ) @@ -56,7 +57,7 @@ void Steam_User::LogOn( CSteamID steamID ) call_logged_off = false; logon_time = std::chrono::high_resolution_clock::now(); - if (settings == get_steam_client()->settings_server) { + if (is_server) { get_steam_client()->steam_gameserver->LogOnAnonymous(); } } @@ -73,7 +74,7 @@ void Steam_User::LogOff() settings->set_offline(true); player_auths.clear(); - if (settings == get_steam_client()->settings_server) { + if (is_server) { get_steam_client()->steam_gameserver->LogOff(); } } @@ -1010,7 +1011,7 @@ void Steam_User::Init( ICMCallback *cmcallback, ISteam2Auth *steam2auth ) bool Steam_User::BGetCallback( int *piCallback, uint8 **ppubParam, int *unk ) { PRINT_DEBUG_ENTRY(); - HSteamUser user = (settings == get_steam_client()->settings_server) ? SERVER_HSTEAMUSER : CLIENT_HSTEAMUSER; + HSteamUser user = is_server ? SERVER_HSTEAMUSER : CLIENT_HSTEAMUSER; HSteamPipe pipe = get_steam_client()->get_pipe_for_user(user); if (!pipe) return false; @@ -1027,7 +1028,7 @@ bool Steam_User::BGetCallback( int *piCallback, uint8 **ppubParam, int *unk ) void Steam_User::FreeLastCallback() { PRINT_DEBUG_ENTRY(); - HSteamUser user = (settings == get_steam_client()->settings_server) ? SERVER_HSTEAMUSER : CLIENT_HSTEAMUSER; + HSteamUser user = is_server ? SERVER_HSTEAMUSER : CLIENT_HSTEAMUSER; HSteamPipe pipe = get_steam_client()->get_pipe_for_user(user); if (!pipe) return; From 271b702c10e736e0318526a2809ba6970bbaa3db Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Fri, 3 Apr 2026 19:39:22 +0300 Subject: [PATCH 087/159] Fix Steam_GameServer::GSSendSteam3UserConnect --- dll/steam_gameserver.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index a2e4ed130..5977c0391 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -1071,7 +1071,17 @@ bool Steam_GameServer::GSSendSteam3UserConnect( CSteamID steamID, uint32 unIPPub std::lock_guard lock(global_mutex); CSteamID ticket_steam_id; - return SendUserConnectAndAuthenticate(unIPPublic, pvCookie, cubCookie, &ticket_steam_id); + Auth_Data ticket_data = auth_manager->validateTicket(pvCookie, cubCookie, steamID, &ticket_steam_id); + if (!ticket_data.id.IsValid()) + return false; + + // If this function gets called then SteamID was likely obtained from Steam.dll and won't match + // the client's Goldberg SteamID. So just ignore SteamID we got from auth ticket. + GSClientApprove_t data{}; + data.m_SteamID = data.m_OwnerSteamID = steamID; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + add_player(steamID); + return true; } bool Steam_GameServer::GSRemoveUserConnect( uint32 unUserID ) From c0cd8f8a42b72df53eab7621cadc256baa241441 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Fri, 3 Apr 2026 19:40:19 +0300 Subject: [PATCH 088/159] Add SteamApps to supported strings in interface list generator --- dll/settings_parser.cpp | 1 + tools/generate_interfaces/generate_interfaces.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index bc5bdfae3..235b7b84b 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -1743,6 +1743,7 @@ static bool try_parse_old_steam_interfaces_file(std::string interfaces_path) OLD_ITF_LINE("SteamUtils", SettingsItf::UTILS); OLD_ITF_LINE("STEAMUSERSTATS_INTERFACE_VERSION", SettingsItf::USER_STATS); OLD_ITF_LINE("STEAMAPPS_INTERFACE_VERSION", SettingsItf::APPS); + OLD_ITF_LINE("SteamApps", SettingsItf::APPS); OLD_ITF_LINE("SteamNetworking", SettingsItf::NETWORKING); OLD_ITF_LINE("STEAMREMOTESTORAGE_INTERFACE_VERSION", SettingsItf::REMOTE_STORAGE); OLD_ITF_LINE("STEAMSCREENSHOTS_INTERFACE_VERSION", SettingsItf::SCREENSHOTS); diff --git a/tools/generate_interfaces/generate_interfaces.cpp b/tools/generate_interfaces/generate_interfaces.cpp index 1cc091dc5..765015e14 100644 --- a/tools/generate_interfaces/generate_interfaces.cpp +++ b/tools/generate_interfaces/generate_interfaces.cpp @@ -8,6 +8,7 @@ // static char old_xxx[128] = ... const static std::vector interface_patterns = { R"(STEAMAPPS_INTERFACE_VERSION\d+)", + R"(SteamApps\d+)", R"(STEAMAPPLIST_INTERFACE_VERSION\d+)", R"(STEAMAPPTICKET_INTERFACE_VERSION\d+)", R"(SteamClient\d+)", From 3f6efe688548f29c0c025698f91888b74a47b26d Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Fri, 3 Apr 2026 19:43:33 +0300 Subject: [PATCH 089/159] Refactoring --- dll/dll.cpp | 4 ++-- dll/dll/steam_client.h | 2 -- dll/steam_client.cpp | 10 ++++++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index 80b6a97a7..99bb569c9 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -454,7 +454,7 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_Shutdown() return; } - get_steam_client()->clientShutdown(); + get_steam_client()->ReleaseUser(user_steam_pipe, CLIENT_HSTEAMUSER); get_steam_client()->BReleaseSteamPipe(user_steam_pipe); get_steam_client()->BShutdownIfAllPipesClosed(); @@ -1297,7 +1297,7 @@ STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSte { PRINT_DEBUG("%i %p", hSteamPipe, pCallbackMsg); Steam_Client *steam_client = get_steam_client(); - if (!steam_client->steamclient_server_inited) { + if (!steam_client->IsServerInit()) { while(!server_cb.empty()) server_cb.pop(); } diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index dfece97ea..09099b3c6 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -183,8 +183,6 @@ public ISteamClient PlaytimeCounter* playtime_counter{}; - bool steamclient_server_inited{}; - bool gameserver_has_ipv6_functions{}; int steamclient_version{}; bool using_old_callbacks{}; diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index b9d070ba8..e5d4d0c33 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -398,12 +398,18 @@ HSteamUser Steam_Client::CreateLocalUser( HSteamPipe *phSteamPipe ) void Steam_Client::ReleaseUser( HSteamPipe hSteamPipe, HSteamUser hUser ) { PRINT_DEBUG_ENTRY(); - if (hUser == SERVER_HSTEAMUSER && steam_pipes.count(hSteamPipe)) { + + if (!steam_pipes.count(hSteamPipe)) + return; + + if (hUser == SERVER_HSTEAMUSER) { if (steam_gameserver->BLoggedOn()) { steam_gameserver->LogOff(); } - steamclient_server_inited = false; + serverShutdown(); + } else if (hUser == CLIENT_HSTEAMUSER) { + clientShutdown(); } } From e4b2ecc604c43f37cf8cd034edaeaccf451a2a46 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Fri, 3 Apr 2026 19:46:29 +0300 Subject: [PATCH 090/159] Implement old version of SteamGameServer_Init --- dll/dll.cpp | 104 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 27 deletions(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index 99bb569c9..eda148488 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -211,6 +211,15 @@ static void load_old_steam_interfaces() reset_LastError(); } +inline int get_old_interface_ver(const char *interface_name) +{ + size_t len = strlen(interface_name); + if (len < 3) + return 0; + + return atoi(interface_name + (len - 3)); +} + // declare "g_pSteamClientGameServer" as an export for API library, then actually define it #if !defined(STEAMCLIENT_DLL) // api STEAMAPI_API ISteamClient *g_pSteamClientGameServer; @@ -227,11 +236,9 @@ Steam_Client *get_steam_client() load_old_steam_interfaces(); steamclient_instance = new Steam_Client(); - if (strstr(old_friends, "SteamFriends")) { - int friends_ver = atoi(old_friends + 12); - if (friends_ver != 0 && friends_ver <= 4) { - steamclient_instance->using_old_callbacks = true; - } + int friends_ver = get_old_interface_ver(old_friends); + if (friends_ver != 0 && friends_ver <= 4) { + steamclient_instance->using_old_callbacks = true; } } } @@ -966,31 +973,26 @@ STEAMAPI_API HSteamUser S_CALLTYPE SteamGameServer_GetHSteamUser() return SERVER_HSTEAMUSER; } +static bool is_nosteam_server{}; + STEAMAPI_API ISteamClient *SteamGameServerClient(); -//SteamGameServer002 (very old, not in public SDKs): +// 0.99u: //STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, EServerMode eServerMode, int nGameAppId, const char *pchGameDir, const char *pchVersionString ); -//SteamGameServer004 and before: +// 0.99v-1.00: //STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usSpectatorPort, uint16 usQueryPort, EServerMode eServerMode, int nGameAppId, const char *pchGameDir, const char *pchVersionString ); -//SteamGameServer010 and before: +// 1.01-1.16: //STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usSpectatorPort, uint16 usQueryPort, EServerMode eServerMode, const char *pchGameDir, const char *pchVersionString ); -//SteamGameServer014 and before: +// 1.16x-1.50: //STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ); -//SteamGameServer015 and later: +// 1.51 and later: //STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ); STEAMAPI_API steam_bool SteamGameServer_Init( void *a1, void *a2, void *a3, void *a4, void *a5, void *a6, void *a7, void *a8, void *a9 ) { PRINT_DEBUG_ENTRY(); - if (!strstr(old_gameserver, "SteamGameServer")) - return false; - - int gameserver_version = atoi(old_gameserver + 15); - if (gameserver_version == 0) - return false; - // call this first since it loads old interfaces - Steam_Client* client = get_steam_client(); + Steam_Client *client = get_steam_client(); if (!server_steam_pipe) { client->CreateLocalUser(&server_steam_pipe, k_EAccountTypeGameServer); sdk_lifetime_counters.update(true); @@ -998,9 +1000,46 @@ STEAMAPI_API steam_bool SteamGameServer_Init( void *a1, void *a2, void *a3, void g_pSteamClientGameServer = SteamGameServerClient(); } + is_nosteam_server = false; + + int gameserver_version = get_old_interface_ver(old_gameserver); + int user_version = get_old_interface_ver(old_user); + bool has_ms_updater = (settings_old_interfaces().count(SettingsItf::MASTERSERVER_UPDATER) != 0); + + if (gameserver_version < 2 || user_version < 4) + return false; + bool ret = false; - if (gameserver_version <= 4) { + if (gameserver_version == 2 && !has_ms_updater) { + uint32 unIP; + uint16 usPort; + uint16 usGamePort; + EServerMode eServerMode; + int nGameAppId; + const char *pchGameDir; + const char *pchVersionString; + + memcpy(&unIP, &a1, sizeof(unIP)); + memcpy(&usPort, &a2, sizeof(usPort)); + memcpy(&usGamePort, &a3, sizeof(usGamePort)); + memcpy(&eServerMode, &a4, sizeof(eServerMode)); + memcpy(&nGameAppId, &a5, sizeof(nGameAppId)); + memcpy(&pchGameDir, &a6, sizeof(pchGameDir)); + memcpy(&pchVersionString, &a7, sizeof(pchVersionString)); + + uint32 unFlags = 0; + if (eServerMode == eServerModeAuthenticationAndSecure) unFlags = k_unServerFlagSecure; + bool bLANMode = (eServerMode == eServerModeNoAuthentication); + + if (!bLANMode) { + client->steam_gameserver->LogOnAnonymous(); + ret = client->steam_gameserver->GSSetServerType(nGameAppId, unFlags, unIP, usGamePort, pchGameDir, pchVersionString); + } else { + is_nosteam_server = true; + ret = true; + } + } else if (gameserver_version <= 4) { uint32 unIP; uint16 usPort; uint16 usGamePort; @@ -1026,7 +1065,12 @@ STEAMAPI_API steam_bool SteamGameServer_Init( void *a1, void *a2, void *a3, void else if (eServerMode == eServerModeNoAuthentication) unFlags = k_unServerFlagPrivate; bool bLANMode = (eServerMode == eServerModeNoAuthentication); - client->steam_gameserver->LogOnAnonymous(); + if (user_version >= 6 || !bLANMode) { + client->steam_gameserver->LogOnAnonymous(); + } else { + is_nosteam_server = true; + } + ret = client->steam_gameserver->BSetServerType(nGameAppId, unFlags, unIP, usGamePort, usSpectatorPort, usQueryPort, pchGameDir, pchVersionString, bLANMode); } else if (gameserver_version <= 10) { uint32 unIP; @@ -1088,23 +1132,29 @@ STEAMAPI_API steam_bool SteamGameServer_Init( void *a1, void *a2, void *a3, void return ret; } -//See: SteamGameServer_Init +// See SteamGameServer_Init //STEAMAPI_API steam_bool S_CALLTYPE SteamGameServer_InitSafe( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usSpectatorPort, uint16 usQueryPort, EServerMode eServerMode, const char *pchGameDir, const char *pchVersionString ) //STEAMAPI_API steam_bool S_CALLTYPE SteamGameServer_InitSafe( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ) STEAMAPI_API steam_bool S_CALLTYPE SteamGameServer_InitSafe( void *a1, void *a2, void *a3, void *a4, void *a5, void *a6, void *a7, void *a8 ) { PRINT_DEBUG_ENTRY(); - if (!strstr(old_gameserver, "SteamGameServer")) - return false; + // call this first since it loads old interfaces + Steam_Client *client = get_steam_client(); + if (!server_steam_pipe) { + client->CreateLocalUser(&server_steam_pipe, k_EAccountTypeGameServer); + sdk_lifetime_counters.update(true); + //g_pSteamClientGameServer is only used in pre 1.37 (where the interface versions are not provided by the game) + g_pSteamClientGameServer = SteamGameServerClient(); + } - int gameserver_version = atoi(old_gameserver + 15); - if (gameserver_version == 0) + int gameserver_version = get_old_interface_ver(old_gameserver); + if (gameserver_version < 5) // export didn't exist before 1.01 return false; bool ret = false; - if (gameserver_version >= 5 && gameserver_version <= 10) { // export didn't exist before 1.01 + if (gameserver_version <= 10) { ret = SteamGameServer_Init(a1, a2, a3, a4, a5, a6, a7, a8, 0); } else if (gameserver_version <= 12) { // export was removed in 1.37 ret = SteamGameServer_Init(a1, a2, a3, a4, a5, a6, 0, 0, 0); @@ -1193,7 +1243,7 @@ STEAMAPI_API void SteamGameServer_Shutdown() STEAMAPI_API void SteamGameServer_RunCallbacks() { // PRINT_DEBUG_ENTRY(); - get_steam_client()->RunCallbacks(false, true); + get_steam_client()->RunCallbacks(false, !is_nosteam_server); } STEAMAPI_API steam_bool SteamGameServer_BSecure() From 346cca8c7f509a41ca7a6178183cc2ac30cb4a97 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Fri, 3 Apr 2026 20:09:45 +0300 Subject: [PATCH 091/159] Don't run callback system if the user is not active --- dll/callsystem.cpp | 5 +++++ dll/dll/callsystem.h | 2 ++ dll/steam_client.cpp | 9 ++++++--- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/dll/callsystem.cpp b/dll/callsystem.cpp index 78478259e..1b7d50ea8 100644 --- a/dll/callsystem.cpp +++ b/dll/callsystem.cpp @@ -220,6 +220,11 @@ void SteamCallResults::setCbAll(void (*cb_all)(std::vector result, int cal this->cb_all = cb_all; } +void SteamCallResults::clear() +{ + callresults.clear(); +} + void SteamCallResults::runCallResults() { unsigned long current_size = static_cast(callresults.size()); diff --git a/dll/dll/callsystem.h b/dll/dll/callsystem.h index 955867c1d..095d19e38 100644 --- a/dll/dll/callsystem.h +++ b/dll/dll/callsystem.h @@ -87,6 +87,8 @@ class SteamCallResults { void setCbAll(void (*cb_all)(std::vector result, int callback)); + void clear(); + void runCallResults(); }; diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index e5d4d0c33..aa2f0d51e 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -257,12 +257,14 @@ Steam_Client::~Steam_Client() void Steam_Client::userLogIn() { + callback_results_client->clear(); network->addListenId(settings_client->get_local_steam_id()); user_logged_in = true; } void Steam_Client::serverInit() { + callback_results_server->clear(); server_init = true; } @@ -278,11 +280,13 @@ bool Steam_Client::IsUserLogIn() void Steam_Client::serverShutdown() { + callback_results_server->clear(); server_init = false; } void Steam_Client::clientShutdown() { + callback_results_client->clear(); user_logged_in = false; } @@ -383,7 +387,6 @@ HSteamUser Steam_Client::CreateLocalUser( HSteamPipe *phSteamPipe, EAccountType HSteamPipe pipe = CreateSteamPipe(); if (phSteamPipe) *phSteamPipe = pipe; steam_pipes[pipe] = Steam_Pipe::SERVER; - steamclient_server_inited = true; return SERVER_HSTEAMUSER; //} } @@ -1003,12 +1006,12 @@ void Steam_Client::RunCallbacks(bool runClientCB, bool runGameserverCB) // PRINT_DEBUG("steam_user *********"); steam_gameserver_user->RunCallbacks(); - if (runClientCB) { + if (runClientCB && IsUserLogIn()) { // PRINT_DEBUG("callback_results_client *********"); callback_results_client->runCallResults(); } - if (runGameserverCB) { + if (runGameserverCB && IsServerInit()) { // PRINT_DEBUG("callback_results_server *********"); callback_results_server->runCallResults(); } From ed6a3fd15e1dbbde6b0514a26502592b82fb753e Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Fri, 17 Apr 2026 01:28:37 +0300 Subject: [PATCH 092/159] Merge mod_dir and game_dir fields in game server info --- dll/source_query.cpp | 2 +- dll/steam_gameserver.cpp | 4 +- dll/steam_matchmaking_servers.cpp | 83 +++++++++++++++---------------- 3 files changed, 43 insertions(+), 46 deletions(-) diff --git a/dll/source_query.cpp b/dll/source_query.cpp index 37fdd3a43..b7ff5b4db 100644 --- a/dll/source_query.cpp +++ b/dll/source_query.cpp @@ -155,7 +155,7 @@ std::vector Source_Query::handle_source_query(const void* buffer, size_ serialize_response(output_buffer, static_cast(2)); serialize_response(output_buffer, gs.server_name()); serialize_response(output_buffer, gs.map_name()); - serialize_response(output_buffer, !gs.mod_dir().empty() ? gs.mod_dir() : gs.game_dir()); + serialize_response(output_buffer, gs.mod_dir()); serialize_response(output_buffer, gs.product()); serialize_response(output_buffer, static_cast(gs.appid())); serialize_response(output_buffer, static_cast(players.size())); diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index 5977c0391..d536800a7 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -586,7 +586,7 @@ bool Steam_GameServer::BSetServerType( uint32 unServerFlags, uint32 unGameIP, ui server_data.set_port(unGamePort); server_data.set_spectator_port(unSpectatorPort); server_data.set_query_port(usQueryPort); - server_data.set_game_dir(pchGameDir ? pchGameDir : ""); + server_data.set_mod_dir(pchGameDir ? pchGameDir : ""); set_version(pchVersion); server_data.set_offline(false); @@ -1018,7 +1018,7 @@ bool Steam_GameServer::Obsolete_GSSetStatus( int32 nAppIdServed, uint32 unServer server_data.set_spectator_port(0); server_data.set_server_name(pchServerName); server_data.set_spectator_server_name(pchServerName); - server_data.set_game_dir(pchGameDir); + server_data.set_mod_dir(pchGameDir); server_data.set_map_name(pchMapName); set_version(pchVersion); server_data.set_offline(false); diff --git a/dll/steam_matchmaking_servers.cpp b/dll/steam_matchmaking_servers.cpp index f6f40f0fe..76401bb98 100644 --- a/dll/steam_matchmaking_servers.cpp +++ b/dll/steam_matchmaking_servers.cpp @@ -637,7 +637,7 @@ void Steam_Matchmaking_Servers::server_details(Gameserver *g, gameserveritem_t * PRINT_DEBUG(" ssq server info ok"); if (ssq_info_has_steamid(ssq_a2s_info)) g->set_id(ssq_a2s_info->steamid); g->set_game_description(ssq_a2s_info->game); - g->set_game_dir(ssq_a2s_info->folder); + g->set_mod_dir(ssq_a2s_info->folder); if (ssq_a2s_info->server_type == A2S_SERVER_TYPE_DEDICATED) g->set_dedicated_server(true); else if (ssq_a2s_info->server_type == A2S_SERVER_TYPE_STV_RELAY) g->set_dedicated_server(true); else g->set_dedicated_server(false); @@ -694,11 +694,7 @@ void Steam_Matchmaking_Servers::server_details(Gameserver *g, gameserveritem_t * server->m_steamID = CSteamID((uint64)g->id()); memset(server->m_szGameDir, 0, sizeof(server->m_szGameDir)); - if (!g->mod_dir().empty()) { - g->mod_dir().copy(server->m_szGameDir, sizeof(server->m_szGameDir) - 1); - } else { - g->game_dir().copy(server->m_szGameDir, sizeof(server->m_szGameDir) - 1); - } + g->mod_dir().copy(server->m_szGameDir, sizeof(server->m_szGameDir) - 1); memset(server->m_szMap, 0, sizeof(server->m_szMap)); g->map_name().copy(server->m_szMap, sizeof(server->m_szMap) - 1); @@ -956,56 +952,57 @@ void Steam_Matchmaking_Servers::Callback(Common_Message *msg) { if (msg->has_gameserver() && msg->gameserver().type() != eFriendsServer) { PRINT_DEBUG("got SERVER " "%" PRIu64 ", offline:%u", msg->gameserver().id(), msg->gameserver().offline()); + + auto it = std::find_if(gameservers.begin(), gameservers.end(), + [msg](const Steam_Matchmaking_Servers_Gameserver &item) { + return (msg->gameserver().id() == item.server.id()); + } + ); + if (msg->gameserver().offline()) { - for (auto &g : gameservers) { - if (g.server.id() == msg->gameserver().id()) { - g.last_recv = std::chrono::high_resolution_clock::time_point(); - g.type = eLANServer; - } + if (it != gameservers.end()) { + it->last_recv = std::chrono::high_resolution_clock::time_point(); + it->type = eLANServer; } } else { - bool already = false; - for (auto &g : gameservers) { - if (g.server.id() == msg->gameserver().id()) { - g.last_recv = std::chrono::high_resolution_clock::now(); - g.server = msg->gameserver(); - g.server.set_ip(msg->source_ip()); - g.type = eLANServer; - already = true; - } + if (it == gameservers.end()) { + it = gameservers.emplace(it); + PRINT_DEBUG(" eLANServer SERVER ADDED"); } - if (!already) { - struct Steam_Matchmaking_Servers_Gameserver g{}; - g.last_recv = std::chrono::high_resolution_clock::now(); - g.server = msg->gameserver(); - g.server.set_ip(msg->source_ip()); - g.type = eLANServer; - gameservers.push_back(g); - PRINT_DEBUG(" eLANServer SERVER ADDED"); + it->last_recv = std::chrono::high_resolution_clock::now(); + it->server = msg->gameserver(); + it->server.set_ip(msg->source_ip()); + + // TODO: This is obsolete, remove game_dir field eventually. + if (it->server.mod_dir().empty()) { + it->server.set_mod_dir(it->server.game_dir()); } + + it->type = eLANServer; } + + return; } if (msg->has_gameserver() && msg->gameserver().type() == eFriendsServer) { PRINT_DEBUG("got eFriendsServer SERVER " "%" PRIu64 "", msg->gameserver().id()); - bool addserver = true; - for (auto &g : gameservers_friends) { - if (g.source_id == msg->source_id()) { - g.ip = msg->gameserver().ip(); - g.port = msg->gameserver().port(); - g.last_recv = std::chrono::high_resolution_clock::now(); - addserver = false; + + auto it = std::find_if(gameservers_friends.begin(), gameservers_friends.end(), + [msg](const Steam_Matchmaking_Servers_Gameserver_Friends &item) { + return (msg->source_id() == item.source_id); } - } + ); - if (addserver) { - struct Steam_Matchmaking_Servers_Gameserver_Friends gameserver_friend; - gameserver_friend.source_id = msg->source_id(); - gameserver_friend.ip = msg->gameserver().ip(); - gameserver_friend.port = msg->gameserver().port(); - gameserver_friend.last_recv = std::chrono::high_resolution_clock::now(); - gameservers_friends.push_back(gameserver_friend); + if (it == gameservers_friends.end()) { + it = gameservers_friends.emplace(it); + it->source_id = msg->source_id(); } + + it->ip = msg->gameserver().ip(); + it->port = msg->gameserver().port(); + it->last_recv = std::chrono::high_resolution_clock::now(); + + return; } } From 88d11a88809a1e368f7ca83b3efdc40ad558fa20 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Fri, 17 Apr 2026 01:36:57 +0300 Subject: [PATCH 093/159] Store version name and protocol version in game server info --- dll/dll/steam_gameserver.h | 1 + dll/net.proto | 2 ++ dll/steam_gameserver.cpp | 13 ++++++++++++- dll/steam_masterserver_updater.cpp | 3 ++- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/dll/dll/steam_gameserver.h b/dll/dll/steam_gameserver.h index 519ba7399..411dfe068 100644 --- a/dll/dll/steam_gameserver.h +++ b/dll/dll/steam_gameserver.h @@ -79,6 +79,7 @@ public ISteamGameServer Steam_GameServer(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks); ~Steam_GameServer(); + void set_protocol_version(unsigned short nProtocolVersion); std::vector>* get_players(); void add_player(CSteamID steamID); void remove_player(CSteamID steamID); diff --git a/dll/net.proto b/dll/net.proto index a867b128b..6b590768d 100644 --- a/dll/net.proto +++ b/dll/net.proto @@ -174,6 +174,8 @@ message Gameserver { uint32 port = 33; uint32 query_port = 34; uint32 appid = 35; + bytes version_name = 36; + uint32 protocol_version = 37; bool offline = 48; uint32 type = 49; diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index d536800a7..d045a03d1 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -93,12 +93,13 @@ void Steam_GameServer::set_version(const char *pchVersionString) try { auto ver = std::stoul(version); server_data.set_version(ver); + server_data.set_version_name(pchVersionString); PRINT_DEBUG("set version to %lu", ver); } catch (...) { server_data.set_version(0); + server_data.set_version_name(""); PRINT_DEBUG("not a number '%s'", pchVersionString); } - } void Steam_GameServer::add_player(CSteamID steamID) @@ -143,6 +144,10 @@ Steam_GameServer::~Steam_GameServer() auth_manager = nullptr; } +void Steam_GameServer::set_protocol_version(unsigned short nProtocolVersion) +{ + server_data.set_protocol_version(nProtocolVersion); +} std::vector>* Steam_GameServer::get_players() { @@ -172,6 +177,12 @@ bool Steam_GameServer::InitGameServer( uint32 unIP, uint16 usGamePort, uint16 us set_appid(nGameAppId, "", false); set_version(pchVersionString); server_data.set_offline(false); + // Fixed versions sent by SteamGameServer011+. Actual steamclient.dll behavior. + if (nGameAppId < 200) { + server_data.set_protocol_version(48); + } else { + server_data.set_protocol_version(17); + } //TODO: flags should be k_unServerFlag flags = unFlags; diff --git a/dll/steam_masterserver_updater.cpp b/dll/steam_masterserver_updater.cpp index b63e77d4c..1ffddb5f7 100644 --- a/dll/steam_masterserver_updater.cpp +++ b/dll/steam_masterserver_updater.cpp @@ -136,7 +136,8 @@ void Steam_Masterserver_Updater::SetBasicServerData( { PRINT_DEBUG_TODO(); std::lock_guard lock(global_mutex); - + + gameserver->set_protocol_version(nProtocolVersion); gameserver->SetDedicatedServer(bDedicatedServer); gameserver->SetRegion(pRegionName); gameserver->SetProduct(pProductName); From 768f9e01fbc2ced51fdcabc2bfb6194fd0090507 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Fri, 17 Apr 2026 01:37:20 +0300 Subject: [PATCH 094/159] Fix some fields in Source query handler --- dll/source_query.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dll/source_query.cpp b/dll/source_query.cpp index b7ff5b4db..c1517be79 100644 --- a/dll/source_query.cpp +++ b/dll/source_query.cpp @@ -152,11 +152,11 @@ std::vector Source_Query::handle_source_query(const void* buffer, size_ serialize_response(output_buffer, source_query_magic::simple); serialize_response(output_buffer, source_response_header::A2S_INFO); - serialize_response(output_buffer, static_cast(2)); + serialize_response(output_buffer, static_cast(gs.protocol_version())); serialize_response(output_buffer, gs.server_name()); serialize_response(output_buffer, gs.map_name()); serialize_response(output_buffer, gs.mod_dir()); - serialize_response(output_buffer, gs.product()); + serialize_response(output_buffer, gs.game_description()); serialize_response(output_buffer, static_cast(gs.appid())); serialize_response(output_buffer, static_cast(players.size())); serialize_response(output_buffer, static_cast(gs.max_player_count())); @@ -165,7 +165,7 @@ std::vector Source_Query::handle_source_query(const void* buffer, size_ serialize_response(output_buffer, my_server_env); serialize_response(output_buffer, (gs.password_protected() ? source_server_visibility::_private : source_server_visibility::_public)); serialize_response(output_buffer, (gs.secure() ? source_server_vac::secured : source_server_vac::unsecured)); - serialize_response(output_buffer, std::to_string(gs.version())); + serialize_response(output_buffer, gs.version_name()); uint8_t flags = source_server_extra_flag::none; From dca557869048d49d8d87e8ae0e85d88009fc0bc6 Mon Sep 17 00:00:00 2001 From: universal963 <36097923+universal963@users.noreply.github.com> Date: Sat, 18 Apr 2026 21:20:40 +0800 Subject: [PATCH 095/159] Fail if `SteamAPI_ManualDispatch_FreeLastCallback()` is not called before `SteamAPI_ManualDispatch_GetNextCallback()` --- dll/dll.cpp | 17 +++++++++++++++++ dll/dll/common_includes.h | 1 + dll/settings_parser_ufs.cpp | 1 - dll/steam_gamestats.cpp | 1 - 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index eda148488..f00f67653 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -1309,6 +1309,8 @@ static void cb_add_queue_client(std::vector result, int callback) client_cb.push(cb); } +static std::unordered_map got_last_callback; + /// Inform the API that you wish to use manual event dispatch. This must be called after SteamAPI_Init, but before /// you use any of the other manual dispatch functions below. STEAMAPI_API void S_CALLTYPE SteamAPI_ManualDispatch_Init() @@ -1346,6 +1348,15 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_ManualDispatch_RunFrame( HSteamPipe hSteam STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg ) { PRINT_DEBUG("%i %p", hSteamPipe, pCallbackMsg); + + auto it_glcb = got_last_callback.find(hSteamPipe); + if (it_glcb != got_last_callback.end()) { + if (it_glcb->second) { + PRINT_DEBUG("last cb not freed on pipe %d!", hSteamPipe); + return false; + } + } + Steam_Client *steam_client = get_steam_client(); if (!steam_client->IsServerInit()) { while(!server_cb.empty()) server_cb.pop(); @@ -1381,6 +1392,7 @@ STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSte pCallbackMsg->m_pubParam = (uint8 *)&(q->front().result[0]); pCallbackMsg->m_cubParam = static_cast(q->front().result.size()); PRINT_DEBUG("cb number %i", q->front().cb_id); + got_last_callback.insert_or_assign(hSteamPipe, true); return true; } @@ -1408,6 +1420,11 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_ManualDispatch_FreeLastCallback( HSteamPip } if (!q->empty()) q->pop(); + + auto it_glcb = got_last_callback.find(hSteamPipe); + if (it_glcb != got_last_callback.end()) { + it_glcb->second = false; + } } /// Return the call result for the specified call on the specified pipe. You really should diff --git a/dll/dll/common_includes.h b/dll/dll/common_includes.h index 1457155ee..b8716556b 100644 --- a/dll/dll/common_includes.h +++ b/dll/dll/common_includes.h @@ -48,6 +48,7 @@ #include #include #include +#include #include #include diff --git a/dll/settings_parser_ufs.cpp b/dll/settings_parser_ufs.cpp index 25d3692f9..5d1f29d71 100644 --- a/dll/settings_parser_ufs.cpp +++ b/dll/settings_parser_ufs.cpp @@ -16,7 +16,6 @@ . */ #include "dll/settings_parser_ufs.h" -#include #include diff --git a/dll/steam_gamestats.cpp b/dll/steam_gamestats.cpp index eb796e4ca..55d047ba7 100644 --- a/dll/steam_gamestats.cpp +++ b/dll/steam_gamestats.cpp @@ -22,7 +22,6 @@ */ #include "dll/steam_gamestats.h" -#include Steam_GameStats::Attribute_t::Attribute_t(AttributeType_t type) From 92b55d1d82cd4c00278963567c5989fbe1592577 Mon Sep 17 00:00:00 2001 From: universal963 <36097923+universal963@users.noreply.github.com> Date: Sun, 19 Apr 2026 11:09:27 +0800 Subject: [PATCH 096/159] Bind the flag on pipes --- dll/dll.cpp | 40 +++++++++++---------------- dll/dll/steam_client.h | 9 ++++-- dll/steam_client.cpp | 20 +++++++------- dll/steam_client_interface_getter.cpp | 18 ++++++------ 4 files changed, 42 insertions(+), 45 deletions(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index f00f67653..56a1de44e 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -1309,8 +1309,6 @@ static void cb_add_queue_client(std::vector result, int callback) client_cb.push(cb); } -static std::unordered_map got_last_callback; - /// Inform the API that you wish to use manual event dispatch. This must be called after SteamAPI_Init, but before /// you use any of the other manual dispatch functions below. STEAMAPI_API void S_CALLTYPE SteamAPI_ManualDispatch_Init() @@ -1335,9 +1333,9 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_ManualDispatch_RunFrame( HSteamPipe hSteam return; } - if (it->second == Steam_Pipe::SERVER) { + if (it->second.type == Steam_Pipe_Type::SERVER) { steam_client->RunCallbacks(false, true); - } else if (it->second == Steam_Pipe::CLIENT) { + } else if (it->second.type == Steam_Pipe_Type::CLIENT) { steam_client->RunCallbacks(true, false); } } @@ -1348,15 +1346,6 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_ManualDispatch_RunFrame( HSteamPipe hSteam STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg ) { PRINT_DEBUG("%i %p", hSteamPipe, pCallbackMsg); - - auto it_glcb = got_last_callback.find(hSteamPipe); - if (it_glcb != got_last_callback.end()) { - if (it_glcb->second) { - PRINT_DEBUG("last cb not freed on pipe %d!", hSteamPipe); - return false; - } - } - Steam_Client *steam_client = get_steam_client(); if (!steam_client->IsServerInit()) { while(!server_cb.empty()) server_cb.pop(); @@ -1370,10 +1359,16 @@ STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSte std::queue *q = NULL; HSteamUser m_hSteamUser = 0; - if (it->second == Steam_Pipe::SERVER) { + + if (it->second.got_last_cb) { + PRINT_DEBUG("last cb not freed on pipe %d!", hSteamPipe); + return false; + } + + if (it->second.type == Steam_Pipe_Type::SERVER) { q = &server_cb; m_hSteamUser = SERVER_HSTEAMUSER; - } else if (it->second == Steam_Pipe::CLIENT) { + } else if (it->second.type == Steam_Pipe_Type::CLIENT) { q = &client_cb; m_hSteamUser = CLIENT_HSTEAMUSER; } else { @@ -1392,7 +1387,7 @@ STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSte pCallbackMsg->m_pubParam = (uint8 *)&(q->front().result[0]); pCallbackMsg->m_cubParam = static_cast(q->front().result.size()); PRINT_DEBUG("cb number %i", q->front().cb_id); - got_last_callback.insert_or_assign(hSteamPipe, true); + it->second.got_last_cb = true; return true; } @@ -1411,9 +1406,9 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_ManualDispatch_FreeLastCallback( HSteamPip return; } - if (it->second == Steam_Pipe::SERVER) { + if (it->second.type == Steam_Pipe_Type::SERVER) { q = &server_cb; - } else if (it->second == Steam_Pipe::CLIENT) { + } else if (it->second.type == Steam_Pipe_Type::CLIENT) { q = &client_cb; } else { return; @@ -1421,10 +1416,7 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_ManualDispatch_FreeLastCallback( HSteamPip if (!q->empty()) q->pop(); - auto it_glcb = got_last_callback.find(hSteamPipe); - if (it_glcb != got_last_callback.end()) { - it_glcb->second = false; - } + it->second.got_last_cb = false; } /// Return the call result for the specified call on the specified pipe. You really should @@ -1437,9 +1429,9 @@ STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetAPICallResult( HSt return false; } - if (steam_client->steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_client->steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { return steam_client->steam_gameserver_utils->GetAPICallResult(hSteamAPICall, pCallback, cubCallback, iCallbackExpected, pbFailed); - } else if (steam_client->steam_pipes[hSteamPipe] == Steam_Pipe::CLIENT) { + } else if (steam_client->steam_pipes[hSteamPipe].type == Steam_Pipe_Type::CLIENT) { return steam_client->steam_utils->GetAPICallResult(hSteamAPICall, pCallback, cubCallback, iCallbackExpected, pbFailed); } else { return false; diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index 09099b3c6..35731aa0b 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -66,12 +66,17 @@ #include "playtime.h" #include "callback_wrapper.h" -enum Steam_Pipe { +enum Steam_Pipe_Type { NO_USER, CLIENT, SERVER }; +struct Steam_Pipe { + Steam_Pipe_Type type{}; + bool got_last_cb{}; +}; + class Steam_Client : public ISteamClient001, public ISteamClient002, @@ -188,7 +193,7 @@ public ISteamClient bool using_old_callbacks{}; unsigned steam_pipe_counter = 1; - std::map steam_pipes{}; + std::map steam_pipes{}; Steam_Client(); diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index aa2f0d51e..198c615a2 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -321,7 +321,7 @@ HSteamPipe Steam_Client::CreateSteamPipe() ++steam_pipe_counter; PRINT_DEBUG(" returned pipe handle %i", pipe); - steam_pipes[pipe] = Steam_Pipe::NO_USER; + steam_pipes[pipe] = {Steam_Pipe_Type::NO_USER, false}; return pipe; } @@ -364,7 +364,7 @@ HSteamUser Steam_Client::ConnectToGlobalUser( HSteamPipe hSteamPipe ) steam_overlay->SetupOverlay(); - steam_pipes[hSteamPipe] = Steam_Pipe::CLIENT; + steam_pipes[hSteamPipe] = {Steam_Pipe_Type::CLIENT, false}; return CLIENT_HSTEAMUSER; } @@ -386,7 +386,7 @@ HSteamUser Steam_Client::CreateLocalUser( HSteamPipe *phSteamPipe, EAccountType HSteamPipe pipe = CreateSteamPipe(); if (phSteamPipe) *phSteamPipe = pipe; - steam_pipes[pipe] = Steam_Pipe::SERVER; + steam_pipes[pipe] = {Steam_Pipe_Type::SERVER, false}; return SERVER_HSTEAMUSER; //} } @@ -1120,8 +1120,8 @@ HSteamUser Steam_Client::CreateGlobalUser( HSteamPipe *phSteamPipe ) { // TODO not sure if this implementation is correct PRINT_DEBUG_TODO(); - for (const auto &[pipe_handle, pipe_type] : steam_pipes) { - if (pipe_type == Steam_Pipe::CLIENT) { + for (const auto &[pipe_handle, pipe_struct] : steam_pipes) { + if (pipe_struct.type == Steam_Pipe_Type::CLIENT) { if (phSteamPipe) *phSteamPipe = pipe_handle; return 0; } @@ -1130,7 +1130,7 @@ HSteamUser Steam_Client::CreateGlobalUser( HSteamPipe *phSteamPipe ) HSteamPipe pipe = CreateSteamPipe(); if (phSteamPipe) *phSteamPipe = pipe; - steam_pipes[pipe] = Steam_Pipe::CLIENT; + steam_pipes[pipe] = {Steam_Pipe_Type::CLIENT, false}; return CLIENT_HSTEAMUSER; } @@ -1177,14 +1177,14 @@ void Steam_Client::SetEUniverse( EUniverse universe ) HSteamPipe Steam_Client::get_pipe_for_user(HSteamUser hUser) { if (hUser == CLIENT_HSTEAMUSER) { - for (const auto &[pipe_handle, pipe_type] : steam_pipes) { - if (pipe_type == Steam_Pipe::CLIENT) { + for (const auto &[pipe_handle, pipe_struct] : steam_pipes) { + if (pipe_struct.type == Steam_Pipe_Type::CLIENT) { return pipe_handle; } } } else if (hUser == SERVER_HSTEAMUSER) { - for (const auto &[pipe_handle, pipe_type] : steam_pipes) { - if (pipe_type == Steam_Pipe::SERVER) { + for (const auto &[pipe_handle, pipe_struct] : steam_pipes) { + if (pipe_struct.type == Steam_Pipe_Type::SERVER) { return pipe_handle; } } diff --git a/dll/steam_client_interface_getter.cpp b/dll/steam_client_interface_getter.cpp index 4bcc083b2..fb1d7f265 100644 --- a/dll/steam_client_interface_getter.cpp +++ b/dll/steam_client_interface_getter.cpp @@ -79,7 +79,7 @@ ISteamGameStats *Steam_Client::GetISteamGameStats( HSteamUser hSteamUser, HSteam Steam_GameStats *steam_gamestats_tmp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_gamestats_tmp = steam_gameserver_gamestats; } else { steam_gamestats_tmp = steam_gamestats; @@ -111,7 +111,7 @@ ISteamUser *Steam_Client::GetISteamUser( HSteamUser hSteamUser, HSteamPipe hStea Steam_User *steam_user_tmp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_user_tmp = steam_gameserver_user; } else { steam_user_tmp = steam_user; @@ -276,7 +276,7 @@ ISteamUtils *Steam_Client::GetISteamUtils( HSteamPipe hSteamPipe, const char *pc Steam_Utils *steam_utils_temp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_utils_temp = steam_gameserver_utils; } else { steam_utils_temp = steam_utils; @@ -359,7 +359,7 @@ void *Steam_Client::GetISteamGenericInterface( HSteamUser hSteamUser, HSteamPipe if (!steam_pipes.count(hSteamPipe)) return NULL; bool server = false; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { // PRINT_DEBUG("requesting interface with server pipe"); server = true; } else { @@ -611,7 +611,7 @@ ISteamApps *Steam_Client::GetISteamApps( HSteamUser hSteamUser, HSteamPipe hStea Steam_Apps *steam_apps_temp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_apps_temp = steam_gameserver_apps; } else { steam_apps_temp = steam_apps; @@ -647,7 +647,7 @@ ISteamNetworking *Steam_Client::GetISteamNetworking( HSteamUser hSteamUser, HSte Steam_Networking *steam_networking_temp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_networking_temp = steam_gameserver_networking; } else { steam_networking_temp = steam_networking; @@ -738,7 +738,7 @@ ISteamHTTP *Steam_Client::GetISteamHTTP( HSteamUser hSteamuser, HSteamPipe hStea if (!steam_pipes.count(hSteamPipe) || !hSteamuser) return NULL; Steam_HTTP *steam_http_temp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_http_temp = steam_gameserver_http; } else { steam_http_temp = steam_http; @@ -816,7 +816,7 @@ ISteamUGC *Steam_Client::GetISteamUGC( HSteamUser hSteamUser, HSteamPipe hSteamP if (!steam_pipes.count(hSteamPipe) || !hSteamUser) return NULL; Steam_UGC *steam_ugc_temp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_ugc_temp = steam_gameserver_ugc; } else { steam_ugc_temp = steam_ugc; @@ -939,7 +939,7 @@ ISteamInventory *Steam_Client::GetISteamInventory( HSteamUser hSteamuser, HSteam if (!steam_pipes.count(hSteamPipe) || !hSteamuser) return NULL; Steam_Inventory *steam_inventory_temp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_inventory_temp = steam_gameserver_inventory; } else { steam_inventory_temp = steam_inventory; From a9f83367b5d9a6da2335119ea3f6fdfcc96578bb Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sun, 19 Apr 2026 18:18:49 +0300 Subject: [PATCH 097/159] Fix interface generator to properly handle SteamClient in newer dlls --- .../generate_interfaces.cpp | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/tools/generate_interfaces/generate_interfaces.cpp b/tools/generate_interfaces/generate_interfaces.cpp index 765015e14..f85aa5238 100644 --- a/tools/generate_interfaces/generate_interfaces.cpp +++ b/tools/generate_interfaces/generate_interfaces.cpp @@ -62,7 +62,7 @@ const static std::vector interface_patterns = { R"(SteamMasterServerUpdater\d+)", }; -unsigned int findinterface( +size_t findinterface( std::ofstream &out_file, const std::string &file_contents, const std::string &interface_patt) @@ -72,15 +72,32 @@ unsigned int findinterface( auto begin = std::sregex_iterator(file_contents.cbegin(), file_contents.cend(), interface_regex); auto end = std::sregex_iterator(); - unsigned int matches = 0; - + std::vector matches; for (std::sregex_iterator i = begin; i != end; ++i) { - out_file << i->str() << std::endl; - ++matches; + matches.push_back(i->str()); + } + + if (interface_patt == R"(SteamClient\d+)" && + matches.size() > 1 && + std::find(matches.begin(), matches.end(), "SteamClient017") != matches.end()) + { + // In newer SDKs, legacy steam_api.dll interface exports were removed except for SteamClient(), + // which still returns SteamClient017. + auto rm = std::remove_if(matches.begin(), matches.end(), [](const std::string &item) + { + return (item != "SteamClient017"); + } + ); + matches.erase(rm, matches.end()); + } + + for (const std::string &match : matches) + { + out_file << match << std::endl; } - return matches; + return matches.size(); } int main(int argc, char *argv[]) @@ -115,7 +132,7 @@ int main(int argc, char *argv[]) return 1; } - unsigned int total_matches = 0; + size_t total_matches = 0; for (const auto &patt : interface_patterns) { From ecf0b426ff1ff300e141747e955ba65b1d5f0d39 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sun, 19 Apr 2026 18:20:07 +0300 Subject: [PATCH 098/159] Fix indentation --- dll/dll/steam_client.h | 182 ++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index 35731aa0b..054c35974 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -200,141 +200,141 @@ public ISteamClient ~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(); + // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling + HSteamPipe CreateSteamPipe(); - // Releases a previously created communications pipe - // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling - bool BReleaseSteamPipe( HSteamPipe hSteamPipe ); + // Releases a previously created communications pipe + // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling + bool BReleaseSteamPipe( HSteamPipe hSteamPipe ); - // connects to an existing global user, failing if none exists - // used by the game to coordinate with the steamUI - // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling - HSteamUser ConnectToGlobalUser( HSteamPipe hSteamPipe ); + // connects to an existing global user, failing if none exists + // used by the game to coordinate with the steamUI + // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling + HSteamUser ConnectToGlobalUser( HSteamPipe hSteamPipe ); - // used by game servers, create a steam user that won't be shared with anyone else - // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling - HSteamUser CreateLocalUser( HSteamPipe *phSteamPipe, EAccountType eAccountType ); + // used by game servers, create a steam user that won't be shared with anyone else + // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling + HSteamUser CreateLocalUser( HSteamPipe *phSteamPipe, EAccountType eAccountType ); HSteamUser CreateLocalUser( HSteamPipe *phSteamPipe ); - // removes an allocated user - // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling - void ReleaseUser( HSteamPipe hSteamPipe, HSteamUser hUser ); + // removes an allocated user + // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling + void ReleaseUser( HSteamPipe hSteamPipe, HSteamUser hUser ); - // retrieves the ISteamUser interface associated with the handle - ISteamUser *GetISteamUser( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // retrieves the ISteamUser interface associated with the handle + ISteamUser *GetISteamUser( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // retrieves the ISteamGameServer interface associated with the handle - ISteamGameServer *GetISteamGameServer( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // retrieves the ISteamGameServer interface associated with the handle + ISteamGameServer *GetISteamGameServer( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // set the local IP and Port to bind to - // this must be set before CreateLocalUser() - void SetLocalIPBinding( uint32 unIP, uint16 usPort ); + // set the local IP and Port to bind to + // this must be set before CreateLocalUser() + void SetLocalIPBinding( uint32 unIP, uint16 usPort ); void SetLocalIPBinding( const SteamIPAddress_t &unIP, uint16 usPort ); - // returns the ISteamFriends interface - ISteamFriends *GetISteamFriends( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the ISteamFriends interface + ISteamFriends *GetISteamFriends( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns the ISteamUtils interface - ISteamUtils *GetISteamUtils( HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the ISteamUtils interface + ISteamUtils *GetISteamUtils( HSteamPipe hSteamPipe, const char *pchVersion ); - // returns the ISteamMatchmaking interface - ISteamMatchmaking *GetISteamMatchmaking( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the ISteamMatchmaking interface + ISteamMatchmaking *GetISteamMatchmaking( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns the ISteamMatchmakingServers interface - ISteamMatchmakingServers *GetISteamMatchmakingServers( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the ISteamMatchmakingServers interface + ISteamMatchmakingServers *GetISteamMatchmakingServers( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns the a generic interface - void *GetISteamGenericInterface( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the a generic interface + void *GetISteamGenericInterface( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns the ISteamUserStats interface - ISteamUserStats *GetISteamUserStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the ISteamUserStats interface + ISteamUserStats *GetISteamUserStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns the ISteamGameServerStats interface - ISteamGameServerStats *GetISteamGameServerStats( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the ISteamGameServerStats interface + ISteamGameServerStats *GetISteamGameServerStats( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns apps interface - ISteamApps *GetISteamApps( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns apps interface + ISteamApps *GetISteamApps( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // networking - ISteamNetworking *GetISteamNetworking( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // networking + ISteamNetworking *GetISteamNetworking( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // remote storage - ISteamRemoteStorage *GetISteamRemoteStorage( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // remote storage + ISteamRemoteStorage *GetISteamRemoteStorage( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // user screenshots - ISteamScreenshots *GetISteamScreenshots( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // user screenshots + ISteamScreenshots *GetISteamScreenshots( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // game stats - ISteamGameStats *GetISteamGameStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // game stats + ISteamGameStats *GetISteamGameStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // steam timeline - ISteamTimeline *GetISteamTimeline( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // steam timeline + ISteamTimeline *GetISteamTimeline( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // steam app disable update - ISteamAppDisableUpdate *GetISteamAppDisableUpdate( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // steam app disable update + ISteamAppDisableUpdate *GetISteamAppDisableUpdate( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); // steam billing ISteamBilling *GetISteamBilling( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Deprecated. Applications should use SteamAPI_RunCallbacks() or SteamGameServer_RunCallbacks() instead. - STEAM_PRIVATE_API( void RunFrame() ); + // Deprecated. Applications should use SteamAPI_RunCallbacks() or SteamGameServer_RunCallbacks() instead. + STEAM_PRIVATE_API( void RunFrame() ); - // returns the number of IPC calls made since the last time this function was called - // Used for perf debugging so you can understand how many IPC calls your game makes per frame - // Every IPC call is at minimum a thread context switch if not a process one so you want to rate - // control how often you do them. - uint32 GetIPCCallCount(); + // returns the number of IPC calls made since the last time this function was called + // Used for perf debugging so you can understand how many IPC calls your game makes per frame + // Every IPC call is at minimum a thread context switch if not a process one so you want to rate + // control how often you do them. + uint32 GetIPCCallCount(); - // API warning handling - // 'int' is the severity; 0 for msg, 1 for warning - // 'const char *' is the text of the message - // callbacks will occur directly after the API function is called that generated the warning or message. - void SetWarningMessageHook( SteamAPIWarningMessageHook_t pFunction ); + // API warning handling + // 'int' is the severity; 0 for msg, 1 for warning + // 'const char *' is the text of the message + // callbacks will occur directly after the API function is called that generated the warning or message. + void SetWarningMessageHook( SteamAPIWarningMessageHook_t pFunction ); - // Trigger global shutdown for the DLL - bool BShutdownIfAllPipesClosed(); + // Trigger global shutdown for the DLL + bool BShutdownIfAllPipesClosed(); - // Expose HTTP interface - ISteamHTTP *GetISteamHTTP( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // Expose HTTP interface + ISteamHTTP *GetISteamHTTP( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Deprecated - the ISteamUnifiedMessages interface is no longer intended for public consumption. - STEAM_PRIVATE_API( void *DEPRECATED_GetISteamUnifiedMessages( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) ; ) + // Deprecated - the ISteamUnifiedMessages interface is no longer intended for public consumption. + STEAM_PRIVATE_API( void *DEPRECATED_GetISteamUnifiedMessages( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) ; ) ISteamUnifiedMessages *GetISteamUnifiedMessages( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Exposes the ISteamController interface - ISteamController *GetISteamController( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // Exposes the ISteamController interface + ISteamController *GetISteamController( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Exposes the ISteamUGC interface - ISteamUGC *GetISteamUGC( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // Exposes the ISteamUGC interface + ISteamUGC *GetISteamUGC( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns app list interface, only available on specially registered apps - ISteamAppList *GetISteamAppList( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - - // Music Player - ISteamMusic *GetISteamMusic( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns app list interface, only available on specially registered apps + ISteamAppList *GetISteamAppList( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + + // Music Player + ISteamMusic *GetISteamMusic( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Music Player Remote - ISteamMusicRemote *GetISteamMusicRemote(HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion); + // Music Player Remote + ISteamMusicRemote *GetISteamMusicRemote(HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion); - // html page display - ISteamHTMLSurface *GetISteamHTMLSurface(HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion); + // html page display + ISteamHTMLSurface *GetISteamHTMLSurface(HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion); - // Helper functions for internal Steam usage - STEAM_PRIVATE_API( void DEPRECATED_Set_SteamAPI_CPostAPIResultInProcess( void (*)() ); ) - STEAM_PRIVATE_API( void DEPRECATED_Remove_SteamAPI_CPostAPIResultInProcess( void (*)() ); ) - STEAM_PRIVATE_API( void Set_SteamAPI_CCheckCallbackRegisteredInProcess( SteamAPI_CheckCallbackRegistered_t func ); ) + // Helper functions for internal Steam usage + STEAM_PRIVATE_API( void DEPRECATED_Set_SteamAPI_CPostAPIResultInProcess( void (*)() ); ) + STEAM_PRIVATE_API( void DEPRECATED_Remove_SteamAPI_CPostAPIResultInProcess( void (*)() ); ) + STEAM_PRIVATE_API( void Set_SteamAPI_CCheckCallbackRegisteredInProcess( SteamAPI_CheckCallbackRegistered_t func ); ) void Set_SteamAPI_CPostAPIResultInProcess( SteamAPI_PostAPIResultInProcess_t func ); void Remove_SteamAPI_CPostAPIResultInProcess( SteamAPI_PostAPIResultInProcess_t func ); - // inventory - ISteamInventory *GetISteamInventory( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // inventory + ISteamInventory *GetISteamInventory( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Video - ISteamVideo *GetISteamVideo( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // Video + ISteamVideo *GetISteamVideo( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Parental controls - ISteamParentalSettings *GetISteamParentalSettings( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // Parental controls + ISteamParentalSettings *GetISteamParentalSettings( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); // ISteamMasterServerUpdater *GetISteamMasterServerUpdater( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); From e0e0d767a2b6e95f47a4466e7ea138ae67b0b1fb Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Sun, 19 Apr 2026 18:21:36 +0300 Subject: [PATCH 099/159] Update CreateSteamPipe to return the first free number as pipe handle --- dll/dll/steam_client.h | 3 ++- dll/steam_client.cpp | 20 ++++++++++++++------ dll/steam_client_interface_getter.cpp | 13 +------------ 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index 054c35974..9719ebc6d 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -192,7 +192,8 @@ public ISteamClient int steamclient_version{}; bool using_old_callbacks{}; - unsigned steam_pipe_counter = 1; + uint32 steam_pipe_counter = 1; + std::priority_queue, std::greater<>> freed_steam_pipes{}; std::map steam_pipes{}; diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index 198c615a2..8eed632e0 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -311,18 +311,25 @@ void Steam_Client::setAppID(uint32 appid) } - // 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 Steam_Client::CreateSteamPipe() { PRINT_DEBUG_ENTRY(); - if (!steam_pipe_counter) ++steam_pipe_counter; - HSteamPipe pipe = steam_pipe_counter; - ++steam_pipe_counter; + + HSteamPipe pipe{}; + if (!freed_steam_pipes.empty()) { + pipe = freed_steam_pipes.top(); + freed_steam_pipes.pop(); + } else { + if (!steam_pipe_counter) ++steam_pipe_counter; + pipe = steam_pipe_counter; + ++steam_pipe_counter; + } + PRINT_DEBUG(" returned pipe handle %i", pipe); + steam_pipes[pipe] = { Steam_Pipe_Type::NO_USER, false }; - steam_pipes[pipe] = {Steam_Pipe_Type::NO_USER, false}; - return pipe; } @@ -334,6 +341,7 @@ bool Steam_Client::BReleaseSteamPipe( HSteamPipe hSteamPipe ) { PRINT_DEBUG("%i", hSteamPipe); if (steam_pipes.count(hSteamPipe)) { + freed_steam_pipes.push(hSteamPipe); return steam_pipes.erase(hSteamPipe) > 0; } diff --git a/dll/steam_client_interface_getter.cpp b/dll/steam_client_interface_getter.cpp index fb1d7f265..cdde57ebb 100644 --- a/dll/steam_client_interface_getter.cpp +++ b/dll/steam_client_interface_getter.cpp @@ -96,18 +96,7 @@ ISteamGameStats *Steam_Client::GetISteamGameStats( HSteamUser hSteamUser, HSteam ISteamUser *Steam_Client::GetISteamUser( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) { PRINT_DEBUG("%s", pchVersion); - - if (!hSteamUser) { - return NULL; - } - if (!steam_pipes.count(hSteamPipe)) { - // Fallback for steamclient_experimental build: if pipe 1 is requested but not found, - // and we have other valid pipes, continue execution instead of returning NULL - if (hSteamPipe !=1 || steam_pipes.empty()) { - return NULL; - } - } - + if (!steam_pipes.count(hSteamPipe) || !hSteamUser) return nullptr; Steam_User *steam_user_tmp{}; From 595fc27ccc6fac144aaeb8443eead04de7862c7c Mon Sep 17 00:00:00 2001 From: rust86 Date: Wed, 22 Apr 2026 13:47:12 -0400 Subject: [PATCH 100/159] Fix SteamNetworkingSockets lane handling and SendMessages for Enshrouded P2P --- dll/net.proto | 1 + dll/steam_networking_sockets.cpp | 41 +++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/dll/net.proto b/dll/net.proto index 6b590768d..08834c9dc 100644 --- a/dll/net.proto +++ b/dll/net.proto @@ -126,6 +126,7 @@ message Networking_Sockets { uint64 connection_id_from = 4; bytes data = 5; uint64 message_number = 7; + uint32 lane = 8; } message Networking_Messages { diff --git a/dll/steam_networking_sockets.cpp b/dll/steam_networking_sockets.cpp index 85ffe86ba..e12e0ce18 100644 --- a/dll/steam_networking_sockets.cpp +++ b/dll/steam_networking_sockets.cpp @@ -54,6 +54,7 @@ SteamNetworkingMessage_t* Steam_Networking_Sockets::get_steam_message_connection pMsg->m_pfnFreeData = &free_steam_message_data; pMsg->m_pfnRelease = &delete_steam_message; pMsg->m_nChannel = 0; + pMsg->m_idxLane = static_cast(connect_socket->second.data.top().lane()); connect_socket->second.data.pop(); PRINT_DEBUG("get_steam_message_connection %u %lu, %llu", hConn, size, pMsg->m_nMessageNumber); return pMsg; @@ -758,6 +759,7 @@ EResult Steam_Networking_Sockets::SendMessageToConnection( HSteamNetConnection h 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); + msg.mutable_networking_sockets()->set_lane(0); 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; @@ -816,7 +818,44 @@ void Steam_Networking_Sockets::SendMessages( int nMessages, SteamNetworkingMessa 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); + int result = k_EResultInvalidParam; + + if (pMessages[i]) { + auto connect_socket = sbcs->connect_sockets.find(pMessages[i]->m_conn); + if (connect_socket == sbcs->connect_sockets.end()) { + result = k_EResultInvalidParam; + } else if (connect_socket->second.status == CONNECT_SOCKET_CLOSED || connect_socket->second.status == CONNECT_SOCKET_TIMEDOUT) { + result = k_EResultNoConnection; + } else if (connect_socket->second.status != CONNECT_SOCKET_CONNECTED && connect_socket->second.status != CONNECT_SOCKET_CONNECTING) { + result = k_EResultInvalidState; + } else { + 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(pMessages[i]->m_pData, pMessages[i]->m_cbSize); + msg.mutable_networking_sockets()->set_lane(pMessages[i]->m_idxLane); + + 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 (pMessages[i]->m_nFlags & k_nSteamNetworkingSend_Reliable) reliable = true; + if (network->sendTo(&msg, reliable)) { + out_number = message_number; + result = k_EResultOK; + } else { + result = k_EResultFail; + } + } + } + if (pOutMessageNumberOrResult) { if (result == k_EResultOK) { pOutMessageNumberOrResult[i] = out_number; From 24c07cb5ddf4cd17fc3ddc4f53aa487624a92735 Mon Sep 17 00:00:00 2001 From: onthebed <1136664562@qq.com> Date: Tue, 28 Apr 2026 19:09:54 +0800 Subject: [PATCH 101/159] fix(gamepad): stop polling HID devices every callback --- .github/workflows/emu-build-all-linux.yml | 2 + libs/gamepad/gamepad.c | 87 ++++++++++++++++++++--- libs/gamepad/gamepad.h | 5 ++ premake5.lua | 40 +++++++++++ tests/gamepad/test_gamepad_linux.cpp | 43 +++++++++++ 5 files changed, 168 insertions(+), 9 deletions(-) create mode 100644 tests/gamepad/test_gamepad_linux.cpp diff --git a/.github/workflows/emu-build-all-linux.yml b/.github/workflows/emu-build-all-linux.yml index 0c91999b2..bbc518ba8 100644 --- a/.github/workflows/emu-build-all-linux.yml +++ b/.github/workflows/emu-build-all-linux.yml @@ -46,6 +46,8 @@ jobs: # tools "tool_lobby_connect", "tool_generate_interfaces", + # tests + "test_gamepad_linux", ] arch: ["x64", "x32"] cfg: ["debug", "release"] diff --git a/libs/gamepad/gamepad.c b/libs/gamepad/gamepad.c index e7a325d22..369bbec03 100644 --- a/libs/gamepad/gamepad.c +++ b/libs/gamepad/gamepad.c @@ -27,7 +27,7 @@ # include # include # include -# include +# include #else # error "Unknown platform in gamepad.c" #endif @@ -73,6 +73,13 @@ static GAMEPAD_STATE STATE[4]; /* Note whether a gamepad is currently connected */ #define FLAG_CONNECTED (1<<0) #define FLAG_RUMBLE (1<<1) +#if defined(__linux__) +static int GAMEPAD_WATCH_FD = -1; +static GAMEPAD_BOOL GAMEPAD_DEVICE_SCAN_PENDING = GAMEPAD_FALSE; +#ifdef GAMEPAD_TESTING +static unsigned int GAMEPAD_TEST_DETECT_COUNT = 0; +#endif +#endif /* Prototypes for utility functions */ static void GamepadResetState (GAMEPAD_DEVICE gamepad); @@ -80,6 +87,11 @@ static void GamepadUpdateCommon (void); static void GamepadUpdateDevice (GAMEPAD_DEVICE gamepad); static void GamepadUpdateStick (GAMEPAD_AXIS* axis, float deadzone); static void GamepadUpdateTrigger (GAMEPAD_TRIGINFO* trig); +#if defined(__linux__) +static void GamepadPollDeviceChanges(void); +static void GamepadRequestDeviceScan(void); +static void GamepadInitDeviceWatch(void); +#endif /* Various values of PI */ #define PI_1_4 0.78539816339744f @@ -209,6 +221,9 @@ static void GamepadRemoveDevice(const char* devPath); static void GamepadDetect() { +#ifdef GAMEPAD_TESTING + ++GAMEPAD_TEST_DETECT_COUNT; +#endif DIR *folder; struct dirent *dent; @@ -237,6 +252,41 @@ static void GamepadDetect() } } +#if defined(__linux__) +static void GamepadRequestDeviceScan(void) +{ + GAMEPAD_DEVICE_SCAN_PENDING = GAMEPAD_TRUE; +} + +static void GamepadInitDeviceWatch(void) +{ + GAMEPAD_WATCH_FD = inotify_init(); + if (GAMEPAD_WATCH_FD < 0) { + return; + } + + fcntl(GAMEPAD_WATCH_FD, F_SETFL, O_NONBLOCK); + fcntl(GAMEPAD_WATCH_FD, F_SETFD, FD_CLOEXEC); + + if (inotify_add_watch(GAMEPAD_WATCH_FD, "/dev/input", + IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_ATTRIB | IN_DELETE_SELF | IN_MOVE_SELF) < 0) { + close(GAMEPAD_WATCH_FD); + GAMEPAD_WATCH_FD = -1; + } +} + +static void GamepadPollDeviceChanges(void) +{ + if (GAMEPAD_WATCH_FD < 0) { + return; + } + + char buffer[sizeof(struct inotify_event) * 16]; + while (read(GAMEPAD_WATCH_FD, buffer, sizeof(buffer)) > 0) { + GamepadRequestDeviceScan(); + } +} +#endif /* Helper to add a new device */ @@ -377,9 +427,6 @@ static void GamepadRemoveDevice(const char* devPath) { } void GamepadInit(void) { - struct udev_list_entry* devices; - struct udev_list_entry* item; - struct udev_enumerate* enu; int i; /* initialize connection state */ @@ -388,17 +435,22 @@ void GamepadInit(void) { STATE[i].fd = STATE[i].effect = -1; } +#if defined(__linux__) + GAMEPAD_DEVICE_SCAN_PENDING = GAMEPAD_FALSE; + GamepadInitDeviceWatch(); +#endif + GamepadDetect(); } void GamepadUpdate(void) { - static unsigned long last = 0; - unsigned long cur = time(NULL); - - if (last + 2 < cur) { +#if defined(__linux__) + GamepadPollDeviceChanges(); + if (GAMEPAD_DEVICE_SCAN_PENDING) { + GAMEPAD_DEVICE_SCAN_PENDING = GAMEPAD_FALSE; GamepadDetect(); - last = cur; } +#endif GamepadUpdateCommon(); } @@ -544,8 +596,25 @@ void GamepadShutdown(void) { close(STATE[i].fd); } } + +#if defined(__linux__) + if (GAMEPAD_WATCH_FD != -1) { + close(GAMEPAD_WATCH_FD); + GAMEPAD_WATCH_FD = -1; + } +#endif } +#if defined(__linux__) && defined(GAMEPAD_TESTING) +unsigned int GamepadTestDetectCount(void) { + return GAMEPAD_TEST_DETECT_COUNT; +} + +void GamepadTestSignalDeviceChange(void) { + GamepadRequestDeviceScan(); +} +#endif + void GamepadSetRumble(GAMEPAD_DEVICE gamepad, float left, float right, unsigned int rumble_length_ms) { if (STATE[gamepad].fd != -1) { struct input_event play; diff --git a/libs/gamepad/gamepad.h b/libs/gamepad/gamepad.h index 0b101432b..16a862319 100644 --- a/libs/gamepad/gamepad.h +++ b/libs/gamepad/gamepad.h @@ -318,6 +318,11 @@ GAMEPAD_API GAMEPAD_STICKDIR GamepadStickDir(GAMEPAD_DEVICE device, GAMEPAD_STIC */ GAMEPAD_API GAMEPAD_BOOL GamepadStickDirTriggered(GAMEPAD_DEVICE device, GAMEPAD_STICK stick, GAMEPAD_STICKDIR dir); +#if defined(GAMEPAD_TESTING) && defined(__linux__) +GAMEPAD_API unsigned int GamepadTestDetectCount(void); +GAMEPAD_API void GamepadTestSignalDeviceChange(void); +#endif + #if defined(__cplusplus) } /* extern "C" */ #endif diff --git a/premake5.lua b/premake5.lua index be36d38fa..347fb4831 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1674,6 +1674,46 @@ project "test_crash_printer_sa_sigaction" } -- End test_crash_printer_sa_sigaction + +-- Project test_gamepad_linux +--------- +project "test_gamepad_linux" + kind "ConsoleApp" + location "%{wks.location}/%{prj.name}" + targetdir(path.join(build_dir, os_iden, _ACTION, "%{cfg.buildcfg}/tests/gamepad")) + targetname "test_gamepad_linux_%{cfg.platform}" + + + -- defines + --------- + filter {} -- reset the filter and remove all active keywords + defines { + "GAMEPAD_TESTING", + } + + + -- common source & header files + --------- + filter {} -- reset the filter and remove all active keywords + files { + 'libs/gamepad/gamepad.c', + 'libs/gamepad/gamepad.h', + 'tests/gamepad/test_gamepad_linux.cpp', + } + removefiles { + 'post_build/**', + 'build/deps/**', + } + + + -- post build + --------- + filter {} -- reset the filter and remove all active keywords + postbuildcommands { + '%[%{!cfg.buildtarget.abspath}]', + } +-- End test_gamepad_linux + end -- End LINUX ONLY TARGETS diff --git a/tests/gamepad/test_gamepad_linux.cpp b/tests/gamepad/test_gamepad_linux.cpp new file mode 100644 index 000000000..66893acb1 --- /dev/null +++ b/tests/gamepad/test_gamepad_linux.cpp @@ -0,0 +1,43 @@ +#include "gamepad/gamepad.h" + +#include + +#ifdef GAMEPAD_TESTING +extern "C" unsigned int GamepadTestDetectCount(void); +extern "C" void GamepadTestSignalDeviceChange(void); +#endif + +int main() +{ +#ifndef GAMEPAD_TESTING + std::cerr << "GAMEPAD_TESTING must be enabled for this test" << std::endl; + return 1; +#else + GamepadInit(); + + if (GamepadTestDetectCount() != 1) { + std::cerr << "expected exactly one initial device scan" << std::endl; + return 1; + } + + for (int i = 0; i < 10; ++i) { + GamepadUpdate(); + } + + if (GamepadTestDetectCount() != 1) { + std::cerr << "unexpected rescans without hotplug" << std::endl; + return 1; + } + + GamepadTestSignalDeviceChange(); + GamepadUpdate(); + + if (GamepadTestDetectCount() != 2) { + std::cerr << "expected a rescan after a hotplug signal" << std::endl; + return 1; + } + + GamepadShutdown(); + return 0; +#endif +} From 1748fe793c145c711963e0b18f6ad7580ac429fb Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 4 May 2026 17:30:17 +0200 Subject: [PATCH 102/159] update everything to latest --- .gitmodules | 6 ++-- overlay_experimental/steam_overlay.cpp | 12 ++++--- premake5-deps.lua | 44 +++++++++++++++++++++++--- premake5.lua | 16 ++++++---- third-party/common/linux | 2 +- third-party/common/win | 2 +- third-party/deps/common | 2 +- 7 files changed, 61 insertions(+), 23 deletions(-) diff --git a/.gitmodules b/.gitmodules index 101d0a063..81dbe4161 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,12 +11,12 @@ [submodule "third-party/common/win"] path = third-party/common/win url = ./ - branch = third-party/common/win + branch = thirdparty-common-win-premake [submodule "third-party/common/linux"] path = third-party/common/linux url = ./ - branch = third-party/common/linux + branch = thirdparty-common-linux-premake [submodule "third-party/deps/win"] path = third-party/deps/win @@ -31,4 +31,4 @@ [submodule "third-party/deps/common"] path = third-party/deps/common url = ./ - branch = third-party/deps/common + branch = thirdparty-deps-update diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 7ccd71d96..e3e4306df 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -292,8 +292,10 @@ void Steam_Overlay::create_fonts() font_cfg.OversampleV = 1; font_cfg.SizePixels = font_size; // non-latin characters look ugly and squeezed without this horizontal spacing - font_cfg.GlyphExtraSpacing.x = settings->overlay_appearance.font_glyph_extra_spacing_x; - font_cfg.GlyphExtraSpacing.y = settings->overlay_appearance.font_glyph_extra_spacing_y; + + font_cfg.GlyphExtraAdvanceX = settings->overlay_appearance.font_glyph_extra_spacing_x; + // font_cfg.GlyphExtraSpacing.x = settings->overlay_appearance.font_glyph_extra_spacing_x; + // font_cfg.GlyphExtraSpacing.y = settings->overlay_appearance.font_glyph_extra_spacing_y; for (const auto &ach : achievements) { font_builder.AddText(ach.title.c_str()); @@ -358,8 +360,8 @@ void Steam_Overlay::create_fonts() font_notif = font_default = font; stats.font = font; - bool res = fonts_atlas.Build(); - PRINT_DEBUG("created fonts atlas (result=%i)", (int)res); + bool res = fonts_atlas.IsBuilt(); + PRINT_DEBUG("isbuilt fonts atlas (result=%i)", (int)res); reset_LastError(); } @@ -1297,7 +1299,7 @@ bool Steam_Overlay::try_load_ach_icon(Overlay_Achievement &ach, bool achieved, b auto image_info = settings->get_image(icon_handle); if (image_info) { int icon_size = static_cast(settings->overlay_appearance.icon_size); - icon_rsrc->SetAutoLoad(InGameOverlay::ResourceAutoLoad_t::OnUse); + //icon_rsrc->SetAutoLoad(InGameOverlay::ResourceAutoLoad_t::OnUse); icon_rsrc->AttachResource((void*)image_info->data.c_str(), icon_size, icon_size); PRINT_DEBUG("'%s' (result=%i)", ach.name.c_str(), (int)icon_rsrc->GetResourceId() != 0); diff --git a/premake5-deps.lua b/premake5-deps.lua index 9d2f8aeb6..43b4e89a1 100644 --- a/premake5-deps.lua +++ b/premake5-deps.lua @@ -1,4 +1,4 @@ -require("premake", ">=5.0.0-beta2") +require("premake", ">=5.0.0-beta8") -- don't forget to set env var CMAKE_GENERATOR to one of these values: -- https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html#manual:cmake-generators(7) @@ -104,6 +104,12 @@ newoption { description = "Extract portaudio", } +newoption { + category = "extract", + trigger = "ext-sdl", + description = "Extract sdl", +} + -- build newoption { category = "build", @@ -174,6 +180,12 @@ newoption { description = "Build portaudio", } +newoption { + category = "build", + trigger = "build-sdl", + description = "Build sdl", +} + local function merge_list(src, dest) local src_count = #src local res = {} @@ -340,7 +352,7 @@ local function cmake_build(dep_folder, is_32, extra_cmd_defs, c_flags_init, cxx_ -- write toolchain file local toolchain_file_content = '' if _OPTIONS["cmake-toolchain"] then - toolchain_file_content='include(' .. _OPTIONS["cmake-toolchain"] .. ')\n\n' + toolchain_file_content = 'include(' .. _OPTIONS["cmake-toolchain"] .. ')\n\n' end if #cflags_init_str > 0 then toolchain_file_content = toolchain_file_content .. 'set(CMAKE_C_FLAGS_INIT "' .. cflags_init_str .. '" )\n' @@ -465,6 +477,9 @@ end if _OPTIONS["ext-portaudio"] or _OPTIONS["all-ext"] then table.insert(deps_to_extract, { 'portaudio/portaudio.tar.gz', 'portaudio' }) end +if _OPTIONS["ext-sdl"] or _OPTIONS["all-ext"] then + table.insert(deps_to_extract, { 'sdl/sdl.tar.gz', 'sdl' }) +end -- start extraction for _, dep in pairs(deps_to_extract) do @@ -574,12 +589,12 @@ local mbedx509_name = '' -- name if _ACTION and os.target() == 'windows' then if string.match(_ACTION, 'vs.+') then - zlib_name = 'zlibstatic' + zlib_name = 'zs' mbedtls_name = 'mbedtls' mbedcrypto_name = 'mbedcrypto' mbedx509_name = 'mbedx509' elseif string.match(_ACTION, 'gmake.*') then - zlib_name = 'libzlibstatic' + zlib_name = 'libzs' mbedtls_name = 'libmbedtls' mbedcrypto_name = 'libmbedcrypto' mbedx509_name = 'libmbedx509' @@ -666,7 +681,7 @@ if _OPTIONS["build-curl"] or _OPTIONS["all-build"] then "CURL_USE_MBEDTLS=ON", -- "CURL_USE_SCHANNEL=ON", - "CURL_CA_FALLBACK=ON", + "CURL_CA_FALLBACK=OFF", -- fix building on Arch Linux "CURL_USE_LIBSSH2=OFF", @@ -824,3 +839,22 @@ if _OPTIONS["build-portaudio"] or _OPTIONS["all-build"] then cmake_build('portaudio', false, portaudio_common_defs) end end + +if _OPTIONS["build-sdl"] or _OPTIONS["all-build"] then + local sdl_common_defs = { + -- enable / disable SDL subsystems + "SDL_AUDIO=OFF", + "SDL_VIDEO=OFF", + "SDL_HIDAPI=OFF", + "SDL_SENSOR=OFF", + "SDL_DIALOG=OFF", + "SDL_TRAY=OFF", + } + + if _OPTIONS["32-build"] then + cmake_build('sdl', true, sdl_common_defs) + end + if _OPTIONS["64-build"] then + cmake_build('sdl', false, sdl_common_defs) + end +end \ No newline at end of file diff --git a/premake5.lua b/premake5.lua index 4f844c91d..5bc11fa34 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1,4 +1,4 @@ -require("premake", ">=5.0.0-beta2") +require("premake", ">=5.0.0-beta8") -- add "-Wl,--whole-archive -Wl,-Bstatic -lmylib -Wl,-Bdynamic -Wl,--no-whole-archive" @@ -234,6 +234,7 @@ local x32_deps_include = { path.join(deps_dir, "mbedtls/install32/include"), path.join(deps_dir, "opus/install32/include"), path.join(deps_dir, "portaudio/install32/include"), + path.join(deps_dir, "sdl/install32/include"), } local x32_deps_overlay_include = { @@ -250,6 +251,7 @@ local x64_deps_include = { path.join(deps_dir, "mbedtls/install64/include"), path.join(deps_dir, "opus/install64/include"), path.join(deps_dir, "portaudio/install64/include"), + path.join(deps_dir, "sdl/install64/include"), } local x64_deps_overlay_include = { @@ -300,7 +302,7 @@ end local zlib_archive_name = 'z' if os.target() == 'windows' then - zlib_archive_name = 'zlibstatic' -- even on MinGw we need this name + zlib_archive_name = 'zs' -- even on MinGw we need this name end local deps_link = { @@ -312,13 +314,11 @@ local deps_link = { "mbedx509" .. static_postfix, "opus" .. static_postfix, "portaudio" .. static_postfix, + "SDL3-static" .. static_postfix, } -- add protobuf libs table_append(deps_link, { lib_prefix .. "protobuf" .. static_postfix, - "absl_bad_any_cast_impl" .. static_postfix, - "absl_bad_optional_access" .. static_postfix, - "absl_bad_variant_access" .. static_postfix, "absl_base" .. static_postfix, "absl_city" .. static_postfix, "absl_civil_time" .. static_postfix, @@ -357,7 +357,6 @@ table_append(deps_link, { "absl_int128" .. static_postfix, "absl_kernel_timeout_internal" .. static_postfix, "absl_leak_check" .. static_postfix, - "absl_log_entry" .. static_postfix, "absl_log_flags" .. static_postfix, "absl_log_globals" .. static_postfix, "absl_log_initialize" .. static_postfix, @@ -380,7 +379,7 @@ table_append(deps_link, { "absl_random_distributions" .. static_postfix, "absl_random_internal_distribution_test_util" .. static_postfix, "absl_random_internal_platform" .. static_postfix, - "absl_random_internal_pool_urbg" .. static_postfix, + "absl_random_internal_entropy_pool" .. static_postfix, "absl_random_internal_randen" .. static_postfix, "absl_random_internal_randen_hwaes" .. static_postfix, "absl_random_internal_randen_hwaes_impl" .. static_postfix, @@ -420,6 +419,7 @@ local common_link_win = { "Winmm" .. static_postfix, "Bcrypt" .. static_postfix, "Dbghelp" .. static_postfix, + "ntdll" .. static_postfix, -- gamepad "Xinput" .. static_postfix, -- imgui / overlay @@ -465,6 +465,7 @@ local x32_deps_libdir = { path.join(deps_dir, "mbedtls/install32/lib"), path.join(deps_dir, "opus/install32/lib"), path.join(deps_dir, "portaudio/install32/lib"), + path.join(deps_dir, "sdl/install32/lib"), } local x32_deps_overlay_libdir = { @@ -482,6 +483,7 @@ local x64_deps_libdir = { path.join(deps_dir, "ingame_overlay/install64/lib"), path.join(deps_dir, "opus/install64/lib"), path.join(deps_dir, "portaudio/install64/lib"), + path.join(deps_dir, "sdl/install64/lib"), } local x64_deps_overlay_libdir = { diff --git a/third-party/common/linux b/third-party/common/linux index 47f35b43f..bb30ed11b 160000 --- a/third-party/common/linux +++ b/third-party/common/linux @@ -1 +1 @@ -Subproject commit 47f35b43f35f149c51deea57cb73c63e1540e08b +Subproject commit bb30ed11b3fbbea11c0a9447c587582dfb7da885 diff --git a/third-party/common/win b/third-party/common/win index e1ad295dc..d21ecf803 160000 --- a/third-party/common/win +++ b/third-party/common/win @@ -1 +1 @@ -Subproject commit e1ad295dcbc0f4767bb1ca8c27e467d71a6030a0 +Subproject commit d21ecf803366bfa5194afe3f8b799770aa9117a1 diff --git a/third-party/deps/common b/third-party/deps/common index 127738c5f..df55371e4 160000 --- a/third-party/deps/common +++ b/third-party/deps/common @@ -1 +1 @@ -Subproject commit 127738c5f02ae0ecb9835055bc986dffa389ef3e +Subproject commit df55371e43208ae4149d9f307ef2d4a46f4469a5 From ea306fa4fc68d48fdc0e968c4cd4f89841e8443c Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 4 May 2026 17:40:14 +0200 Subject: [PATCH 103/159] revert this once all built --- .github/workflows/emu-deps-linux.yml | 4 ++-- .github/workflows/emu-deps-win.yml | 11 ++--------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/emu-deps-linux.yml b/.github/workflows/emu-deps-linux.yml index c2bef0630..372a009ab 100644 --- a/.github/workflows/emu-deps-linux.yml +++ b/.github/workflows/emu-deps-linux.yml @@ -42,7 +42,7 @@ jobs: if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' uses: actions/checkout@v6 with: - ref: "third-party/common/linux" + ref: "thirdparty-common-linux-premake" path: "${{env.THIRD_PARTY_BASE_DIR}}/common/linux" - name: "Clone third-party deps (deps/linux)" @@ -56,7 +56,7 @@ jobs: if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' uses: actions/checkout@v6 with: - ref: "third-party/deps/common" + ref: "thirdparty-deps-update" path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/common" # fix folder permissions! not sure why this fails diff --git a/.github/workflows/emu-deps-win.yml b/.github/workflows/emu-deps-win.yml index 3f9578852..e3e32e46b 100644 --- a/.github/workflows/emu-deps-win.yml +++ b/.github/workflows/emu-deps-win.yml @@ -52,7 +52,7 @@ jobs: if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' uses: actions/checkout@v6 with: - ref: "third-party/common/win" + ref: "thirdparty-common-win-premake" path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win" - name: "Clone third-party deps (deps/win)" @@ -66,16 +66,9 @@ jobs: if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' uses: actions/checkout@v6 with: - ref: "third-party/deps/common" + ref: "thirdparty-deps-update" path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/common" - - name: "Clone third-party deps (common/win)" - if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' - uses: actions/checkout@v6 - with: - ref: "third-party/common/win" - path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win" - - name: "Build deps" if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' shell: "cmd" From bd2845a74b9743d51026d32cd3d20bef3adc8d4d Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 4 May 2026 17:49:08 +0200 Subject: [PATCH 104/159] update all worklow to newest ubuntu + win25-vs26 --- .github/workflows/emu-build-all-linux.yml | 2 +- .github/workflows/emu-deps-linux.yml | 2 +- .github/workflows/migrate_gse-build-linux.yml | 2 +- .github/workflows/release.yml | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/emu-build-all-linux.yml b/.github/workflows/emu-build-all-linux.yml index 0c91999b2..27892e320 100644 --- a/.github/workflows/emu-build-all-linux.yml +++ b/.github/workflows/emu-build-all-linux.yml @@ -29,7 +29,7 @@ jobs: builds-matrix-linux: name: "build" needs: ["deps"] - runs-on: "ubuntu-22.04" + runs-on: "ubuntu-24.04" if: ${{ !cancelled() }} continue-on-error: true diff --git a/.github/workflows/emu-deps-linux.yml b/.github/workflows/emu-deps-linux.yml index 372a009ab..d061098e1 100644 --- a/.github/workflows/emu-deps-linux.yml +++ b/.github/workflows/emu-deps-linux.yml @@ -22,7 +22,7 @@ env: jobs: deps-build: - runs-on: "ubuntu-22.04" + runs-on: "ubuntu-24.04" if: ${{ !cancelled() }} steps: diff --git a/.github/workflows/migrate_gse-build-linux.yml b/.github/workflows/migrate_gse-build-linux.yml index a0c0e77f0..8bfec3d6c 100644 --- a/.github/workflows/migrate_gse-build-linux.yml +++ b/.github/workflows/migrate_gse-build-linux.yml @@ -16,7 +16,7 @@ env: jobs: build: - runs-on: "ubuntu-22.04" + runs-on: "ubuntu-24.04" steps: - name: "Checkout branch" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43f3f5aab..9a92ff5ca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -104,7 +104,7 @@ jobs: emu-linux-prep: needs: ["emu-linux-all"] - runs-on: "ubuntu-22.04" + runs-on: "ubuntu-24.04" steps: # we need branch because it has package scripts - name: "Checkout branch" @@ -193,7 +193,7 @@ jobs: migrate_gse_script-win-prep: needs: ["migrate_gse_script-win"] - runs-on: "windows-2022" + runs-on: "windows-2025-vs2026" steps: # on Windows Git will auto change line ending to CRLF, not preferable - name: "Ensure LF line ending" @@ -253,7 +253,7 @@ jobs: migrate_gse_script-linux-prep: needs: ["migrate_gse_script-linux"] - runs-on: "ubuntu-22.04" + runs-on: "ubuntu-24.04" steps: # we need branch because it has package scripts - name: "Checkout branch" From dd3d853d826769070376f646371c57656b7585a0 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 4 May 2026 18:29:21 +0200 Subject: [PATCH 105/159] update workflows and deps --- .github/workflows/emu-build-all-linux.yml | 4 ++-- .github/workflows/emu-build-all-win.yml | 2 +- .github/workflows/emu-deps-linux.yml | 2 +- third-party/deps/common | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/emu-build-all-linux.yml b/.github/workflows/emu-build-all-linux.yml index 27892e320..5ecc7a441 100644 --- a/.github/workflows/emu-build-all-linux.yml +++ b/.github/workflows/emu-build-all-linux.yml @@ -67,7 +67,7 @@ jobs: - name: "Clone third-party build helpers (common/linux)" uses: actions/checkout@v6 with: - ref: "third-party/common/linux" + ref: "thirdparty-common-linux-premake" path: "${{env.THIRD_PARTY_BASE_DIR}}/common/linux" - name: "Clone third-party build helpers (build/linux)" @@ -89,7 +89,7 @@ jobs: working-directory: "${{ github.workspace }}" run: | sudo chmod 777 ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 - ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 --file=premake5.lua --genproto --emubuild=${{ github.sha }} --os=linux gmake2 + ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 --file=premake5.lua --genproto --emubuild=${{ github.sha }} --os=linux gmake # mandatory Linux packages - name: "Install required packages" diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 62eaa86f0..09a73f7dc 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -89,7 +89,7 @@ jobs: - name: "Clone third-party build helpers (common/win)" uses: actions/checkout@v6 with: - ref: "third-party/common/win" + ref: "thirdparty-common-win-premake" path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win" - name: "Clone third-party deps (build/win)" diff --git a/.github/workflows/emu-deps-linux.yml b/.github/workflows/emu-deps-linux.yml index d061098e1..44df72a0b 100644 --- a/.github/workflows/emu-deps-linux.yml +++ b/.github/workflows/emu-deps-linux.yml @@ -90,4 +90,4 @@ jobs: run: | export CMAKE_GENERATOR="Unix Makefiles" sudo chmod 777 ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 - ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --j=${{ env.MAX_PARALLEL_JOBS }} --verbose --clean --os=linux gmake2 + ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --j=${{ env.MAX_PARALLEL_JOBS }} --verbose --clean --os=linux gmake diff --git a/third-party/deps/common b/third-party/deps/common index df55371e4..0e50c84aa 160000 --- a/third-party/deps/common +++ b/third-party/deps/common @@ -1 +1 @@ -Subproject commit df55371e43208ae4149d9f307ef2d4a46f4469a5 +Subproject commit 0e50c84aa138880560f99842d404bc569162f11b From c29ea8295d812703811f734142ad0e8c19784d19 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 4 May 2026 18:56:27 +0200 Subject: [PATCH 106/159] fix premake script --- premake5-deps.lua | 1 + premake5.lua | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/premake5-deps.lua b/premake5-deps.lua index 43b4e89a1..2c9a04631 100644 --- a/premake5-deps.lua +++ b/premake5-deps.lua @@ -849,6 +849,7 @@ if _OPTIONS["build-sdl"] or _OPTIONS["all-build"] then "SDL_SENSOR=OFF", "SDL_DIALOG=OFF", "SDL_TRAY=OFF", + "SDL_UNIX_CONSOLE_BUILD=ON", } if _OPTIONS["32-build"] then diff --git a/premake5.lua b/premake5.lua index 5bc11fa34..894968809 100644 --- a/premake5.lua +++ b/premake5.lua @@ -305,6 +305,13 @@ if os.target() == 'windows' then zlib_archive_name = 'zs' -- even on MinGw we need this name end +local sdl_name = 'SDL3' +if os.target() == 'windows' then + sdl_name = 'SDL3-static' +else + sdl_name = 'libSDL3' +end + local deps_link = { "ssq" .. static_postfix, zlib_archive_name .. static_postfix, @@ -314,7 +321,7 @@ local deps_link = { "mbedx509" .. static_postfix, "opus" .. static_postfix, "portaudio" .. static_postfix, - "SDL3-static" .. static_postfix, + sdl_name .. static_postfix, } -- add protobuf libs table_append(deps_link, { From 6e5956f52024ee452a9ac999553af1e8b93c0144 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 4 May 2026 20:57:37 +0200 Subject: [PATCH 107/159] fix deps linux --- .github/workflows/emu-deps-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/emu-deps-linux.yml b/.github/workflows/emu-deps-linux.yml index 44df72a0b..145b1f6c1 100644 --- a/.github/workflows/emu-deps-linux.yml +++ b/.github/workflows/emu-deps-linux.yml @@ -10,7 +10,7 @@ permissions: contents: "write" env: - PREMAKE_ACTION: "gmake2" + PREMAKE_ACTION: "gmake" DEPS_CACHE_KEY: "emu-deps-linux" DEPS_CACHE_DIR: "build/deps/linux" From b411630155292b31ff4339006f2bea40292b947f Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 4 May 2026 21:40:59 +0200 Subject: [PATCH 108/159] fix gmake --- .github/workflows/emu-build-all-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/emu-build-all-linux.yml b/.github/workflows/emu-build-all-linux.yml index 5ecc7a441..23c14a973 100644 --- a/.github/workflows/emu-build-all-linux.yml +++ b/.github/workflows/emu-build-all-linux.yml @@ -10,7 +10,7 @@ permissions: contents: "write" env: - PREMAKE_ACTION: "gmake2" + PREMAKE_ACTION: "gmake" DEPS_CACHE_KEY: "emu-deps-linux" DEPS_CACHE_DIR: "build/deps/linux" From b3c72e08f29571f5b3486c3694dc0f8ec577361c Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 4 May 2026 21:55:39 +0200 Subject: [PATCH 109/159] update --- .github/workflows/emu-build-all-linux.yml | 2 +- .github/workflows/release.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/emu-build-all-linux.yml b/.github/workflows/emu-build-all-linux.yml index 23c14a973..86ac691f3 100644 --- a/.github/workflows/emu-build-all-linux.yml +++ b/.github/workflows/emu-build-all-linux.yml @@ -110,7 +110,7 @@ jobs: # build target - name: "Build target" shell: "bash" - working-directory: "${{ github.workspace }}/build/project/gmake2/linux" + working-directory: "${{ github.workspace }}/build/project/gmake/linux" run: | echo "dry run..." make -n -j ${{ env.MAX_PARALLEL_JOBS }} config=${{ matrix.cfg }}_${{ matrix.arch }} ${{ matrix.prj }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a92ff5ca..25003ae4c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -150,13 +150,13 @@ jobs: - name: "Package build (release)" shell: "bash" working-directory: "${{ github.workspace }}" - run: ./package_linux.sh gmake2/release + run: ./package_linux.sh gmake/release # package (debug mode) - name: "Package build (debug)" shell: "bash" working-directory: "${{ github.workspace }}" - run: ./package_linux.sh gmake2/debug 1 + run: ./package_linux.sh gmake/debug 1 # release (debug + release modes) if this is a tag push - name: "Release" @@ -171,7 +171,7 @@ jobs: uses: actions/upload-artifact@v7 with: name: "release-emu-linux-release-${{ github.sha }}" - path: "build/package/linux/gmake2/*release*" + path: "build/package/linux/gmake/*release*" if-no-files-found: "error" compression-level: 0 retention-days: 7 @@ -181,7 +181,7 @@ jobs: uses: actions/upload-artifact@v7 with: name: "release-emu-linux-debug-${{ github.sha }}" - path: "build/package/linux/gmake2/*debug*" + path: "build/package/linux/gmake/*debug*" if-no-files-found: "error" compression-level: 0 retention-days: 7 From a20038f2434e7005e164a4b33148af539d436832 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Tue, 5 May 2026 13:05:15 +0300 Subject: [PATCH 110/159] Fix double init/double shutdown in Steam_Client --- dll/dll/steam_client.h | 2 ++ dll/steam_client.cpp | 37 +++++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index 9719ebc6d..4446a4906 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -191,6 +191,8 @@ public ISteamClient bool gameserver_has_ipv6_functions{}; int steamclient_version{}; bool using_old_callbacks{}; + + int client_user_ref_count{}; uint32 steam_pipe_counter = 1; std::priority_queue, std::greater<>> freed_steam_pipes{}; diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index 8eed632e0..17a16cc4f 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -358,20 +358,23 @@ HSteamUser Steam_Client::ConnectToGlobalUser( HSteamPipe hSteamPipe ) return 0; } - userLogIn(); + if (client_user_ref_count == 0) { + userLogIn(); - // initialize GC now so that we have user's inventory ready right away - steam_game_coordinator->initialize_gc(); - - // games like appid 1740720 and 2379780 do not call SteamAPI_RunCallbacks() or SteamAPI_ManualDispatch_RunFrame() or Steam_BGetCallback() - // hence all run_callbacks() will never run, which might break the assumption that these callbacks are always run - // also networking callbacks won't run - // hence we spawn the background thread here which trigger all run_callbacks() and run networking callbacks - PRINT_DEBUG("started background thread"); - background_thread->start(this); + // initialize GC now so that we have user's inventory ready right away + steam_game_coordinator->initialize_gc(); - steam_overlay->SetupOverlay(); - + // games like appid 1740720 and 2379780 do not call SteamAPI_RunCallbacks() or SteamAPI_ManualDispatch_RunFrame() or Steam_BGetCallback() + // hence all run_callbacks() will never run, which might break the assumption that these callbacks are always run + // also networking callbacks won't run + // hence we spawn the background thread here which trigger all run_callbacks() and run networking callbacks + PRINT_DEBUG("started background thread"); + background_thread->start(this); + + steam_overlay->SetupOverlay(); + } + + client_user_ref_count++; steam_pipes[hSteamPipe] = {Steam_Pipe_Type::CLIENT, false}; return CLIENT_HSTEAMUSER; } @@ -408,7 +411,7 @@ HSteamUser Steam_Client::CreateLocalUser( HSteamPipe *phSteamPipe ) // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling void Steam_Client::ReleaseUser( HSteamPipe hSteamPipe, HSteamUser hUser ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%i %i", hSteamPipe, hUser); if (!steam_pipes.count(hSteamPipe)) return; @@ -420,7 +423,13 @@ void Steam_Client::ReleaseUser( HSteamPipe hSteamPipe, HSteamUser hUser ) serverShutdown(); } else if (hUser == CLIENT_HSTEAMUSER) { - clientShutdown(); + if (client_user_ref_count > 0) { + client_user_ref_count--; + + if (client_user_ref_count == 0) { + clientShutdown(); + } + } } } From 9bc1db51438e6d3d189edccb6e252671ccbbfe78 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Tue, 5 May 2026 15:57:00 +0200 Subject: [PATCH 111/159] linux update --- premake5.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/premake5.lua b/premake5.lua index 894968809..39ab847ff 100644 --- a/premake5.lua +++ b/premake5.lua @@ -308,8 +308,6 @@ end local sdl_name = 'SDL3' if os.target() == 'windows' then sdl_name = 'SDL3-static' -else - sdl_name = 'libSDL3' end local deps_link = { From 2dfc49b4f545003f95499faa9b2d4c6e0c0377e5 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Tue, 5 May 2026 16:03:47 +0200 Subject: [PATCH 112/159] fix linux 32 -> 86 --- .github/workflows/emu-build-all-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/emu-build-all-linux.yml b/.github/workflows/emu-build-all-linux.yml index 86ac691f3..e38617a3c 100644 --- a/.github/workflows/emu-build-all-linux.yml +++ b/.github/workflows/emu-build-all-linux.yml @@ -47,7 +47,7 @@ jobs: "tool_lobby_connect", "tool_generate_interfaces", ] - arch: ["x64", "x32"] + arch: ["x64", "x86"] cfg: ["debug", "release"] steps: From b5e4390308ccc32e352e086af2a965555dd5b485 Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Tue, 5 May 2026 18:23:59 +0300 Subject: [PATCH 113/159] More accurate HSteamUser behavior for SteamClient004 --- dll/dll/steam_client.h | 39 +++++++++++++++++++++++++-- dll/steam_client.cpp | 61 +++++++++++++++++++++++++----------------- dll/steam_user.cpp | 1 + 3 files changed, 74 insertions(+), 27 deletions(-) diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index 4446a4906..0582cf59d 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -77,6 +77,11 @@ struct Steam_Pipe { bool got_last_cb{}; }; +struct User_Ref { + HSteamUser user{}; + HSteamPipe pipe{}; +}; + class Steam_Client : public ISteamClient001, public ISteamClient002, @@ -193,11 +198,40 @@ public ISteamClient bool using_old_callbacks{}; int client_user_ref_count{}; + + template + class Reusable_Numbers { + T counter{}; + std::priority_queue, std::greater<>> freed_numbers{}; + + public: + T get_number() + { + T retval{}; + if (!freed_numbers.empty()) { + retval = freed_numbers.top(); + freed_numbers.pop(); + } else { + if (!counter) ++counter; + retval = counter; + ++counter; + } + + return retval; + } + + void free_number(T number) + { + freed_numbers.push(number); + } + }; - uint32 steam_pipe_counter = 1; - std::priority_queue, std::greater<>> freed_steam_pipes{}; + Reusable_Numbers steam_pipe_numbers{}; std::map steam_pipes{}; + Reusable_Numbers old_user_ref_numbers{}; + std::map old_user_refs{}; + Steam_Client(); ~Steam_Client(); @@ -429,6 +463,7 @@ public ISteamClient void report_missing_impl(std::string_view itf, std::string_view caller); [[noreturn]] void report_missing_impl_and_exit(std::string_view itf, std::string_view caller); + HSteamUser create_old_user_ref(HSteamUser hUser, HSteamPipe hSteamPipe); HSteamPipe get_pipe_for_user(HSteamUser hUser); }; diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index 17a16cc4f..8ea3b91cb 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -317,15 +317,7 @@ HSteamPipe Steam_Client::CreateSteamPipe() { PRINT_DEBUG_ENTRY(); - HSteamPipe pipe{}; - if (!freed_steam_pipes.empty()) { - pipe = freed_steam_pipes.top(); - freed_steam_pipes.pop(); - } else { - if (!steam_pipe_counter) ++steam_pipe_counter; - pipe = steam_pipe_counter; - ++steam_pipe_counter; - } + HSteamPipe pipe = steam_pipe_numbers.get_number(); PRINT_DEBUG(" returned pipe handle %i", pipe); steam_pipes[pipe] = { Steam_Pipe_Type::NO_USER, false }; @@ -341,7 +333,7 @@ bool Steam_Client::BReleaseSteamPipe( HSteamPipe hSteamPipe ) { PRINT_DEBUG("%i", hSteamPipe); if (steam_pipes.count(hSteamPipe)) { - freed_steam_pipes.push(hSteamPipe); + steam_pipe_numbers.free_number(hSteamPipe); return steam_pipes.erase(hSteamPipe) > 0; } @@ -1056,42 +1048,53 @@ HSteamUser Steam_Client::CreateGlobalInstance() { PRINT_DEBUG_ENTRY(); HSteamPipe pipe = 0; - return CreateGlobalUser(&pipe); + HSteamUser user = CreateGlobalUser(&pipe); + return create_old_user_ref(user, pipe); } HSteamUser Steam_Client::ConnectToGlobalInstance() { PRINT_DEBUG_ENTRY(); - HSteamPipe pipe = get_pipe_for_user(CLIENT_HSTEAMUSER); - if (!pipe) { - pipe = CreateSteamPipe(); + if (steamclient_version < 4) { + for (auto &[key, val] : old_user_refs) { + if (val.user == CLIENT_HSTEAMUSER) { + return key; + } + } } - return ConnectToGlobalUser(pipe); + HSteamPipe pipe = CreateSteamPipe(); + HSteamUser user = ConnectToGlobalUser(pipe); + return create_old_user_ref(user, pipe); } HSteamUser Steam_Client::CreateLocalInstance() { PRINT_DEBUG_ENTRY(); HSteamPipe pipe = 0; - return CreateLocalUser(&pipe); + HSteamUser user = CreateLocalUser(&pipe); + return create_old_user_ref(user, pipe); } void Steam_Client::ReleaseInstance( HSteamUser hSteamUser ) { PRINT_DEBUG_ENTRY(); - HSteamPipe pipe = get_pipe_for_user(hSteamUser); - if (pipe) { - ReleaseUser(pipe, hSteamUser); - BReleaseSteamPipe(pipe); - } + auto it = old_user_refs.find(hSteamUser); + if (it == old_user_refs.end()) return; + + ReleaseUser(it->second.pipe, it->second.user); + BReleaseSteamPipe(it->second.pipe); + + old_user_ref_numbers.free_number(hSteamUser); + old_user_refs.erase(it); } ISteamUser *Steam_Client::GetISteamUser( HSteamUser hSteamUser, const char *pchVersion ) { PRINT_DEBUG_ENTRY(); - HSteamPipe pipe = get_pipe_for_user(hSteamUser); - return GetISteamUser(hSteamUser, pipe, pchVersion); + if (!old_user_refs.count(hSteamUser)) return nullptr; + User_Ref &user_ref = old_user_refs[hSteamUser]; + return GetISteamUser(user_ref.user, user_ref.pipe, pchVersion); } // retrieves the IVac interface associated with the handle @@ -1124,8 +1127,9 @@ bool Steam_Client::BMainLoop( uint64 time ) ISteamGameServer *Steam_Client::GetISteamGameServer( HSteamUser hSteamUser, const char *pchVersion ) { PRINT_DEBUG_ENTRY(); - HSteamPipe pipe = get_pipe_for_user(hSteamUser); - return GetISteamGameServer(hSteamUser, pipe, pchVersion); + if (!old_user_refs.count(hSteamUser)) return nullptr; + User_Ref &user_ref = old_user_refs[hSteamUser]; + return GetISteamGameServer(user_ref.user, user_ref.pipe, pchVersion); } // SteamClient004 ----------------------------------------------------- @@ -1191,6 +1195,13 @@ void Steam_Client::SetEUniverse( EUniverse universe ) } // SteamClient005 ----------------------------------------------------- +HSteamUser Steam_Client::create_old_user_ref(HSteamUser hUser, HSteamPipe hSteamPipe) +{ + HSteamUser ref = old_user_ref_numbers.get_number(); + old_user_refs[ref] = { hUser, hSteamPipe }; + return ref; +} + HSteamPipe Steam_Client::get_pipe_for_user(HSteamUser hUser) { if (hUser == CLIENT_HSTEAMUSER) { diff --git a/dll/steam_user.cpp b/dll/steam_user.cpp index d78136d76..aab3386ad 100644 --- a/dll/steam_user.cpp +++ b/dll/steam_user.cpp @@ -1011,6 +1011,7 @@ void Steam_User::Init( ICMCallback *cmcallback, ISteam2Auth *steam2auth ) bool Steam_User::BGetCallback( int *piCallback, uint8 **ppubParam, int *unk ) { PRINT_DEBUG_ENTRY(); + // HACK: Use the first available pipe. HSteamUser user = is_server ? SERVER_HSTEAMUSER : CLIENT_HSTEAMUSER; HSteamPipe pipe = get_steam_client()->get_pipe_for_user(user); if (!pipe) From 8548cb038ed7421dbb74ee92d160700a32e4948d Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Tue, 5 May 2026 20:45:37 +0300 Subject: [PATCH 114/159] Fix ICMCallback and ICMCallback C wrapper --- dll/dll.cpp | 14 ++++----- dll/dll/capicmcallback.h | 62 ++++++++++++++++++++------------------- dll/steam_user.cpp | 4 +-- sdk/steam/isteamuser001.h | 2 +- sdk/steam/isteamuser002.h | 2 +- 5 files changed, 43 insertions(+), 41 deletions(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index 56a1de44e..c60e77d9d 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -2020,24 +2020,24 @@ STEAMCLIENT_API steam_bool Steam3_GSSetStatus( HSteamUser hUser, int32 nAppIdSer return gs->Obsolete_GSSetStatus(nAppIdServed, unServerFlags, cPlayers, cPlayersMax, cBotPlayers, unGamePort, pchServerName, pchGameDir, pchMapName, pchVersion); } -static std::map capi_cmcallback_map; +static std::map capi_cmcallback_map; STEAMCLIENT_API void Steam3_Init( HSteamUser hUser, OnLogonSuccessFunc func1, OnLogonFailureFunc func2, OnLoggedOffFunc func3, OnBeginLogonRetryFunc func4, - HandleVACChallengeFunc func5, - GSHandleClientApproveFunc func6, - GSHandleClientDenyFunc func7, - GSHandleClientKickFunc func8 ) + GSHandleClientApproveFunc func5, + GSHandleClientDenyFunc func6, + GSHandleClientKickFunc func7, + Steam2GetValueFunc func8 ) { PRINT_DEBUG_ENTRY(); get_steam_client()->steamclient_version = 2; ISteamUser002 *su = reinterpret_cast(get_steam_client()->GetISteamUser(hUser, "SteamUser002")); - CCAPICMCallBack new_cmcallback(func1, func2, func3, func4, func5, func6, func7, func8); + CAPI_CMCallback new_cmcallback(func1, func2, func3, func4, func5, func6, func7, func8); auto [it, _] = capi_cmcallback_map.insert_or_assign(hUser, new_cmcallback); - su->Init(&it->second, nullptr); + su->Init(static_cast(&it->second), static_cast(&it->second)); } // SteamClient002 ----------------------------------------------------- diff --git a/dll/dll/capicmcallback.h b/dll/dll/capicmcallback.h index 3448b58cd..5a9d43b46 100644 --- a/dll/dll/capicmcallback.h +++ b/dll/dll/capicmcallback.h @@ -22,54 +22,56 @@ typedef void (*OnLogonSuccessFunc)(); typedef void (*OnLogonFailureFunc)(EResult); -typedef void (*OnLoggedOffFunc)(); +typedef void (*OnLoggedOffFunc)(EResult); typedef void (*OnBeginLogonRetryFunc)(); -typedef void (*HandleVACChallengeFunc)(int, uint8 *, int); -typedef void (*GSHandleClientApproveFunc)(CSteamID &); -typedef void (*GSHandleClientDenyFunc)(CSteamID &, EDenyReason); -typedef void (*GSHandleClientKickFunc)(CSteamID &, EDenyReason); +typedef void (*GSHandleClientApproveFunc)(uint64); +typedef void (*GSHandleClientDenyFunc)(uint64, EDenyReason); +typedef void (*GSHandleClientKickFunc)(uint64, EDenyReason); +typedef int (*Steam2GetValueFunc)(const char *, char *, int); -class CCAPICMCallBack : public ICMCallback +class CAPI_CMCallback : public ICMCallback, public ISteam2Auth { + OnLogonSuccessFunc OnLogonSuccess_ptr{}; + OnLogonFailureFunc OnLogonFailure_ptr{}; + OnLoggedOffFunc OnLoggedOff_ptr{}; + OnBeginLogonRetryFunc OnBeginLogonRetry_ptr{}; + GSHandleClientApproveFunc GSHandleClientApprove_ptr{}; + GSHandleClientDenyFunc GSHandleClientDeny_ptr{}; + GSHandleClientKickFunc GSHandleClientKick_ptr{}; + Steam2GetValueFunc GetValue_ptr{}; + public: - CCAPICMCallBack(OnLogonSuccessFunc func1, + CAPI_CMCallback(OnLogonSuccessFunc func1, OnLogonFailureFunc func2, OnLoggedOffFunc func3, OnBeginLogonRetryFunc func4, - HandleVACChallengeFunc func5, - GSHandleClientApproveFunc func6, - GSHandleClientDenyFunc func7, - GSHandleClientKickFunc func8) + GSHandleClientApproveFunc func5, + GSHandleClientDenyFunc func6, + GSHandleClientKickFunc func7, + Steam2GetValueFunc func8) { OnLogonSuccess_ptr = func1; OnLogonFailure_ptr = func2; OnLoggedOff_ptr = func3; OnBeginLogonRetry_ptr = func4; - HandleVACChallenge_ptr = func5; - GSHandleClientApprove_ptr = func6; - GSHandleClientDeny_ptr = func7; - GSHandleClientKick_ptr = func8; + GSHandleClientApprove_ptr = func5; + GSHandleClientDeny_ptr = func6; + GSHandleClientKick_ptr = func7; + GetValue_ptr = func8; } - ~CCAPICMCallBack() {} + ~CAPI_CMCallback() {} void OnLogonSuccess() { OnLogonSuccess_ptr(); } void OnLogonFailure(EResult eResult) { OnLogonFailure_ptr(eResult); } - void OnLoggedOff() { OnLoggedOff_ptr(); } + void OnLoggedOff(EResult eResult) { OnLoggedOff_ptr(eResult); } void OnBeginLogonRetry() { OnBeginLogonRetry_ptr(); } - void HandleVACChallenge(int nClientGameID, uint8 *pubChallenge, int cubChallenge) { HandleVACChallenge_ptr(nClientGameID, pubChallenge, cubChallenge); } - void GSHandleClientApprove(CSteamID &steamID) { GSHandleClientApprove_ptr(steamID); } - void GSHandleClientDeny(CSteamID &steamID, EDenyReason eDenyReason) { GSHandleClientDeny_ptr(steamID, eDenyReason); } - void GSHandleClientKick(CSteamID &steamID, EDenyReason eDenyReason) { GSHandleClientKick_ptr(steamID, eDenyReason); } + void HandleVACChallenge(int nClientGameID, uint8 *pubChallenge, int cubChallenge) {} + void GSHandleClientApprove(CSteamID &steamID) { GSHandleClientApprove_ptr(steamID.ConvertToUint64()); } + void GSHandleClientDeny(CSteamID &steamID, EDenyReason eDenyReason) { GSHandleClientDeny_ptr(steamID.ConvertToUint64(), eDenyReason); } + void GSHandleClientKick(CSteamID &steamID, EDenyReason eDenyReason) { GSHandleClientKick_ptr(steamID.ConvertToUint64(), eDenyReason); } -private: - OnLogonSuccessFunc OnLogonSuccess_ptr{}; - OnLogonFailureFunc OnLogonFailure_ptr{}; - OnLoggedOffFunc OnLoggedOff_ptr{}; - OnBeginLogonRetryFunc OnBeginLogonRetry_ptr{}; - HandleVACChallengeFunc HandleVACChallenge_ptr{}; - GSHandleClientApproveFunc GSHandleClientApprove_ptr{}; - GSHandleClientDenyFunc GSHandleClientDeny_ptr{}; - GSHandleClientKickFunc GSHandleClientKick_ptr{}; + int GetValue(const char *var, char *buf, int bufsize) { return GetValue_ptr(var, buf, bufsize); } + int GetServerReadableTicket(uint32 unk1, uint32 unk2, void *unk3, uint32 unk4, uint32 *unk5) { return 0; } }; #endif // __INCLUDED_CAPICMCALLBACK_H__ diff --git a/dll/steam_user.cpp b/dll/steam_user.cpp index aab3386ad..acafc732a 100644 --- a/dll/steam_user.cpp +++ b/dll/steam_user.cpp @@ -1139,7 +1139,7 @@ void Steam_User::RunCallbacks() if (call_logged_off && check_timedout(logoff_time, 0.1)) { PRINT_DEBUG("ICMCallback -> OnLoggedOff"); - callbacks_old1->OnLoggedOff(); + callbacks_old1->OnLoggedOff(k_EResultOK); call_logged_off = false; } @@ -1162,7 +1162,7 @@ void Steam_User::RunCallbacks() if (call_logged_off && check_timedout(logoff_time, 0.1)) { PRINT_DEBUG("ICMCallback -> OnLoggedOff"); - callbacks_old2->OnLoggedOff(); + callbacks_old2->OnLoggedOff(k_EResultOK); call_logged_off = false; } diff --git a/sdk/steam/isteamuser001.h b/sdk/steam/isteamuser001.h index 4dbdc1a0f..b0c164509 100644 --- a/sdk/steam/isteamuser001.h +++ b/sdk/steam/isteamuser001.h @@ -24,7 +24,7 @@ class ICMCallback001 virtual void OnLogonSuccess() = 0; virtual void OnLogonFailure( EResult eResult ) = 0; - virtual void OnLoggedOff() = 0; + virtual void OnLoggedOff( EResult eResult ) = 0; virtual void HandleVACChallenge( int nClientGameID, uint8 *pubChallenge, int cubChallenge ) = 0; virtual void GSHandleClientApprove( CSteamID &steamID ) = 0; virtual void GSHandleClientDeny( CSteamID &steamID, EDenyReason eDenyReason ) = 0; diff --git a/sdk/steam/isteamuser002.h b/sdk/steam/isteamuser002.h index 924752996..d1ad0bcdd 100644 --- a/sdk/steam/isteamuser002.h +++ b/sdk/steam/isteamuser002.h @@ -22,7 +22,7 @@ class ICMCallback virtual void OnLogonSuccess() = 0; virtual void OnLogonFailure( EResult eResult ) = 0; - virtual void OnLoggedOff() = 0; + virtual void OnLoggedOff( EResult eResult ) = 0; virtual void OnBeginLogonRetry() = 0; virtual void HandleVACChallenge( int nClientGameID, uint8 *pubChallenge, int cubChallenge ) = 0; virtual void GSHandleClientApprove( CSteamID &steamID ) = 0; From c8bcf4c3bca8147b9c010d90a3eb20de8b4b410f Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Fri, 15 May 2026 16:03:39 +0200 Subject: [PATCH 115/159] update submodules update workflows --- .github/workflows/emu-build-all-linux.yml | 2 +- .github/workflows/emu-build-all-win.yml | 4 ++-- .github/workflows/emu-deps-linux.yml | 4 ++-- .github/workflows/emu-deps-win.yml | 6 +++--- .github/workflows/migrate_gse-build-win.yml | 2 +- .github/workflows/release.yml | 2 +- .gitmodules | 6 +++--- README.md | 2 +- third-party/common/linux | 2 +- third-party/common/win | 2 +- third-party/deps/common | 2 +- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/emu-build-all-linux.yml b/.github/workflows/emu-build-all-linux.yml index e38617a3c..823bdf0da 100644 --- a/.github/workflows/emu-build-all-linux.yml +++ b/.github/workflows/emu-build-all-linux.yml @@ -67,7 +67,7 @@ jobs: - name: "Clone third-party build helpers (common/linux)" uses: actions/checkout@v6 with: - ref: "thirdparty-common-linux-premake" + ref: "third-party/common/linux" path: "${{env.THIRD_PARTY_BASE_DIR}}/common/linux" - name: "Clone third-party build helpers (build/linux)" diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 09a73f7dc..1430891e3 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -28,7 +28,7 @@ jobs: builds-matrix-win: name: "build" needs: ["deps"] - runs-on: "windows-2025-vs2026" + runs-on: "windows-2025" if: ${{ !cancelled() }} continue-on-error: true @@ -89,7 +89,7 @@ jobs: - name: "Clone third-party build helpers (common/win)" uses: actions/checkout@v6 with: - ref: "thirdparty-common-win-premake" + ref: "third-party/common/win" path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win" - name: "Clone third-party deps (build/win)" diff --git a/.github/workflows/emu-deps-linux.yml b/.github/workflows/emu-deps-linux.yml index 145b1f6c1..4380257c9 100644 --- a/.github/workflows/emu-deps-linux.yml +++ b/.github/workflows/emu-deps-linux.yml @@ -42,7 +42,7 @@ jobs: if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' uses: actions/checkout@v6 with: - ref: "thirdparty-common-linux-premake" + ref: "third-party/common/linux" path: "${{env.THIRD_PARTY_BASE_DIR}}/common/linux" - name: "Clone third-party deps (deps/linux)" @@ -56,7 +56,7 @@ jobs: if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' uses: actions/checkout@v6 with: - ref: "thirdparty-deps-update" + ref: "third-party/deps/common" path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/common" # fix folder permissions! not sure why this fails diff --git a/.github/workflows/emu-deps-win.yml b/.github/workflows/emu-deps-win.yml index e3e32e46b..da88832ab 100644 --- a/.github/workflows/emu-deps-win.yml +++ b/.github/workflows/emu-deps-win.yml @@ -22,7 +22,7 @@ env: jobs: deps-build: - runs-on: "windows-2025-vs2026" + runs-on: "windows-2025" if: ${{ !cancelled() }} steps: @@ -52,7 +52,7 @@ jobs: if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' uses: actions/checkout@v6 with: - ref: "thirdparty-common-win-premake" + ref: "third-party/common/win" path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win" - name: "Clone third-party deps (deps/win)" @@ -66,7 +66,7 @@ jobs: if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' uses: actions/checkout@v6 with: - ref: "thirdparty-deps-update" + ref: "third-party/deps/common" path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/common" - name: "Build deps" diff --git a/.github/workflows/migrate_gse-build-win.yml b/.github/workflows/migrate_gse-build-win.yml index 4f24d775d..295ecbf83 100644 --- a/.github/workflows/migrate_gse-build-win.yml +++ b/.github/workflows/migrate_gse-build-win.yml @@ -18,7 +18,7 @@ env: jobs: build: - runs-on: "windows-2025-vs2026" + runs-on: "windows-2025" steps: - name: "Set up Python 3.12" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25003ae4c..284fa8221 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: emu-win-prep: needs: ["emu-win-all"] - runs-on: "windows-2025-vs2026" + runs-on: "windows-2025" steps: # on Windows Git will auto change line ending to CRLF, not preferable - name: "Ensure LF line ending" diff --git a/.gitmodules b/.gitmodules index 81dbe4161..bc66e2d26 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,12 +11,12 @@ [submodule "third-party/common/win"] path = third-party/common/win url = ./ - branch = thirdparty-common-win-premake + branch = third-party/common/win [submodule "third-party/common/linux"] path = third-party/common/linux url = ./ - branch = thirdparty-common-linux-premake + branch = third-party/common/linux [submodule "third-party/deps/win"] path = third-party/deps/win @@ -31,4 +31,4 @@ [submodule "third-party/deps/common"] path = third-party/deps/common url = ./ - branch = thirdparty-deps-update + branch = third-party/deps/common diff --git a/README.md b/README.md index ec394ba5c..4a3a050dc 100644 --- a/README.md +++ b/README.md @@ -312,7 +312,7 @@ Open CMD in the repos's directory, then run this script ```batch package_win.bat ``` -`build_folder` is any folder inside `build\win`, for example: `6\release` +`build_folder` is any folder inside `build\win`, for example: `vs2026\release` The above example will create a `.7z` archive inside `build\package\win\` ### On Linux: diff --git a/third-party/common/linux b/third-party/common/linux index bb30ed11b..1f34b918c 160000 --- a/third-party/common/linux +++ b/third-party/common/linux @@ -1 +1 @@ -Subproject commit bb30ed11b3fbbea11c0a9447c587582dfb7da885 +Subproject commit 1f34b918c37d58dffd5a9fe9fe1cf47edd5f0186 diff --git a/third-party/common/win b/third-party/common/win index d21ecf803..d431ffcea 160000 --- a/third-party/common/win +++ b/third-party/common/win @@ -1 +1 @@ -Subproject commit d21ecf803366bfa5194afe3f8b799770aa9117a1 +Subproject commit d431ffceaadf4e279e70bc4adea3ae42be6820ac diff --git a/third-party/deps/common b/third-party/deps/common index 0e50c84aa..92a4a1302 160000 --- a/third-party/deps/common +++ b/third-party/deps/common @@ -1 +1 @@ -Subproject commit 0e50c84aa138880560f99842d404bc569162f11b +Subproject commit 92a4a130262083c4e887155cbe6bcab99baf36ea From 5d76e046662087d69421b1d5969cb59aeff61ec7 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Fri, 15 May 2026 16:08:03 +0200 Subject: [PATCH 116/159] still not fucking merged to vs26!!!! --- .github/workflows/emu-build-all-win.yml | 2 +- .github/workflows/emu-deps-win.yml | 2 +- .github/workflows/migrate_gse-build-win.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 1430891e3..62eaa86f0 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -28,7 +28,7 @@ jobs: builds-matrix-win: name: "build" needs: ["deps"] - runs-on: "windows-2025" + runs-on: "windows-2025-vs2026" if: ${{ !cancelled() }} continue-on-error: true diff --git a/.github/workflows/emu-deps-win.yml b/.github/workflows/emu-deps-win.yml index da88832ab..df7929d64 100644 --- a/.github/workflows/emu-deps-win.yml +++ b/.github/workflows/emu-deps-win.yml @@ -22,7 +22,7 @@ env: jobs: deps-build: - runs-on: "windows-2025" + runs-on: "windows-2025-vs2026" if: ${{ !cancelled() }} steps: diff --git a/.github/workflows/migrate_gse-build-win.yml b/.github/workflows/migrate_gse-build-win.yml index 295ecbf83..4f24d775d 100644 --- a/.github/workflows/migrate_gse-build-win.yml +++ b/.github/workflows/migrate_gse-build-win.yml @@ -18,7 +18,7 @@ env: jobs: build: - runs-on: "windows-2025" + runs-on: "windows-2025-vs2026" steps: - name: "Set up Python 3.12" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 284fa8221..25003ae4c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: emu-win-prep: needs: ["emu-win-all"] - runs-on: "windows-2025" + runs-on: "windows-2025-vs2026" steps: # on Windows Git will auto change line ending to CRLF, not preferable - name: "Ensure LF line ending" From 3b12ef52c3432266b7bbf771a4ed8ec54d704bca Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Sat, 16 May 2026 18:11:04 +0200 Subject: [PATCH 117/159] fix appid detection --- dll/settings_parser.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 235b7b84b..09aaec871 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -596,15 +596,17 @@ static uint32 parse_steam_app_id(const std::string &program_path) } PRINT_DEBUG("appid_env %u gameid_env: %u overlay_gameid: %u", appid_env, gameid_env, overlay_gameid); - if (appid_env) { + + // some games set env-vars fake value as '1' in case of 2780710 + if (appid_env != 0 && appid_env != 1) { appid = appid_env; } - if (gameid_env) { + if (gameid_env != 0 && gameid_env != 1) { appid = gameid_env; } - if (overlay_gameid) { + if (overlay_gameid != 0 && overlay_gameid != 1) { appid = overlay_gameid; } From 3f2fcb0da597c144483954bc1e1b88fb4b915176 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 18 May 2026 15:34:12 +0200 Subject: [PATCH 118/159] add support for vs22 --- .github/workflows/emu-build-all-win-vs22.yml | 123 +++++++++++++++ .github/workflows/emu-deps-win-vs22.yml | 78 ++++++++++ .github/workflows/emu-pull-request.yml | 5 + .../workflows/migrate_gse-build-win-vs22.yml | 68 +++++++++ .../workflows/migrate_gse-pull-request.yml | 6 + .github/workflows/release.yml | 143 ++++++++++++++++++ 6 files changed, 423 insertions(+) create mode 100644 .github/workflows/emu-build-all-win-vs22.yml create mode 100644 .github/workflows/emu-deps-win-vs22.yml create mode 100644 .github/workflows/migrate_gse-build-win-vs22.yml diff --git a/.github/workflows/emu-build-all-win-vs22.yml b/.github/workflows/emu-build-all-win-vs22.yml new file mode 100644 index 000000000..16f9fb115 --- /dev/null +++ b/.github/workflows/emu-build-all-win-vs22.yml @@ -0,0 +1,123 @@ +name: "Build emu (Windows)" + +on: + workflow_call: + # needed since it allows this to become a reusable workflow + workflow_dispatch: + # allows manual trigger + +permissions: + contents: "write" + +env: + PREMAKE_ACTION: "vs2022" + DEPS_CACHE_KEY: "emu-deps-win" + DEPS_CACHE_DIR: "build/deps/win" + + THIRD_PARTY_BASE_DIR: "third-party" + + # https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for-public-repositories + MAX_PARALLEL_JOBS: 4 + +jobs: + deps: + name: "Restore or build deps" + if: ${{ !cancelled() }} + uses: "./.github/workflows/emu-deps-win-vs22.yml" + + builds-matrix-win: + name: "build" + needs: ["deps"] + runs-on: "windows-2022" + if: ${{ !cancelled() }} + continue-on-error: true + + strategy: + fail-fast: false + matrix: + prj: [ + # regular api + "api_regular", + # (experimental) api + client + "api_experimental", + "steamclient_experimental_stub", + # client (experimental) + loader + extra dll + gameoverlaylib + "steamclient_experimental", + "steamclient_experimental_loader", + "steamclient_experimental_extra", + "lib_game_overlay_renderer", + # steam old lib + "lib_steam_old", + # tools + "tool_lobby_connect", + "tool_generate_interfaces", + ] + arch: ["x64", "Win32"] + cfg: ["debug", "release"] + + exclude: + - prj: lib_steam_old + arch: x64 + + steps: + # on Windows Git will auto change line ending to CRLF, not preferable + - name: "Ensure LF line ending" + shell: "cmd" + working-directory: "${{ github.workspace }}" + run: | + git config --local core.autocrlf false + git config --system core.autocrlf false + git config --global core.autocrlf false + + # ensure we have msbuild + - name: "Add MSBuild to PATH" + uses: microsoft/setup-msbuild@v3 + + # clone branch + - name: "Checkout branch" + uses: actions/checkout@v6 + + # deps + - name: "Restore deps" + id: "emu-deps-cache-step" + uses: actions/cache@v5 + with: + key: "${{ env.DEPS_CACHE_KEY }}-${{ env.PREMAKE_ACTION }}" + path: "${{ env.DEPS_CACHE_DIR }}/${{ env.PREMAKE_ACTION }}" + + # extra helpers/tools, these are not built inside the deps build dir + - name: "Clone third-party build helpers (common/win)" + uses: actions/checkout@v6 + with: + ref: "third-party/common/win" + path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win" + + - name: "Clone third-party deps (build/win)" + uses: actions/checkout@v6 + with: + ref: "third-party/build/win" + path: "${{env.THIRD_PARTY_BASE_DIR}}/build/win" + + # generate project files + - name: "Generate project files" + shell: "cmd" + working-directory: "${{ github.workspace }}" + run: | + "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5.lua --genproto --emubuild=${{ github.sha }} --dosstub --winrsrc --winsign --os=windows vs2022 + + # build target + - name: "Build target" + shell: "cmd" + working-directory: "${{ github.workspace }}/build/project/vs2026/win" + run: | + msbuild /nologo /target:${{ matrix.prj }} /m:1 -p:CL_MPCount=${{ env.MAX_PARALLEL_JOBS }} /v:n /p:Configuration=${{ matrix.cfg }},Platform=${{ matrix.arch }} gbe.sln + + # upload artifact/package to github Actions + - name: "Upload target package" + uses: actions/upload-artifact@v7 + with: + name: "emu-win-${{ matrix.prj }}-${{ matrix.cfg }}-${{ matrix.arch }}-${{ github.sha }}" + path: "build/win" + if-no-files-found: "error" + compression-level: 9 + retention-days: 1 diff --git a/.github/workflows/emu-deps-win-vs22.yml b/.github/workflows/emu-deps-win-vs22.yml new file mode 100644 index 000000000..174374fe1 --- /dev/null +++ b/.github/workflows/emu-deps-win-vs22.yml @@ -0,0 +1,78 @@ +name: "Emu third-party dependencies (Windows vs22)" + +on: + workflow_call: + # needed since it allows this to become a reusable workflow + workflow_dispatch: + # allows manual trigger + +permissions: + contents: "write" + +env: + PREMAKE_ACTION: "vs2022" + DEPS_CACHE_KEY: "emu-deps-win" + DEPS_CACHE_DIR: "build/deps/win" + + PACKAGE_BASE_DIR: "build/package/win" + THIRD_PARTY_BASE_DIR: "third-party" + + # https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for-public-repositories + MAX_PARALLEL_JOBS: 4 + +jobs: + deps-build: + runs-on: "windows-2022" + if: ${{ !cancelled() }} + + steps: + # on Windows Git will auto change line ending to CRLF, not preferable + - name: "Ensure LF line ending" + shell: "cmd" + working-directory: ${{ github.workspace }} + run: | + git config --local core.autocrlf false + git config --system core.autocrlf false + git config --global core.autocrlf false + + - name: "Lookup cache for deps" + id: "emu-deps-cache-step" + uses: actions/cache@v5 + with: + key: "${{ env.DEPS_CACHE_KEY }}-${{ env.PREMAKE_ACTION }}" + path: "${{ env.DEPS_CACHE_DIR }}/${{ env.PREMAKE_ACTION }}" + lookup-only: true # don't restore cache if found + + # we need branch because it has build scripts + - name: "Checkout branch" + if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' + uses: actions/checkout@v6 + + - name: "Clone third-party deps (common/win)" + if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' + uses: actions/checkout@v6 + with: + ref: "third-party/common/win" + path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win" + + - name: "Clone third-party deps (deps/win)" + if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' + uses: actions/checkout@v6 + with: + ref: "third-party/deps/win" + path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/win" + + - name: "Clone third-party deps (deps/common)" + if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' + uses: actions/checkout@v6 + with: + ref: "third-party/deps/common" + path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/common" + + - name: "Build deps" + if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' + shell: "cmd" + working-directory: "${{ github.workspace }}" + run: | + set "CMAKE_GENERATOR=Visual Studio 17 2022" + "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --j=${{ env.MAX_PARALLEL_JOBS }} --verbose --clean --os=windows vs2022 diff --git a/.github/workflows/emu-pull-request.yml b/.github/workflows/emu-pull-request.yml index eb25a2c59..8652b90f5 100644 --- a/.github/workflows/emu-pull-request.yml +++ b/.github/workflows/emu-pull-request.yml @@ -24,6 +24,11 @@ jobs: if: ${{ !cancelled() }} uses: "./.github/workflows/emu-build-all-win.yml" + emu-win-all-vs22: + name: "win vs22" + if: ${{ !cancelled() }} + uses: "./.github/workflows/emu-build-all-win-vs22.yml" + emu-linux-all: name: "linux" if: ${{ !cancelled() }} diff --git a/.github/workflows/migrate_gse-build-win-vs22.yml b/.github/workflows/migrate_gse-build-win-vs22.yml new file mode 100644 index 000000000..1638e4957 --- /dev/null +++ b/.github/workflows/migrate_gse-build-win-vs22.yml @@ -0,0 +1,68 @@ +name: "Build migrate_gse script (Windows)" + +on: + workflow_call: + # needed since it allows this to become a reusable workflow + workflow_dispatch: + # allows manual trigger + +permissions: + contents: "write" + +env: + ARTIFACT_NAME: "migrate_gse-win-vs22-${{ github.sha }}" + SCRIPT_BASE_DIR: "tools/migrate_gse" + PACKAGE_BASE_DIR: "tools/migrate_gse/bin/win" + + THIRD_PARTY_BASE_DIR: "third-party" + +jobs: + build: + runs-on: "windows-2022" + + steps: + - name: "Set up Python 3.12" + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + # on Windows Git will auto change line ending to CRLF, not preferable + - name: "Ensure LF line ending" + shell: "cmd" + working-directory: "${{ github.workspace }}" + run: | + git config --local core.autocrlf false + git config --system core.autocrlf false + git config --global core.autocrlf false + + - name: "Checkout branch" + uses: actions/checkout@v6 + + # extra helpers/tools, these are not built inside the deps build dir + - name: "Clone third-party deps (build/win)" + uses: actions/checkout@v6 + with: + ref: "third-party/build/win" + path: "${{env.THIRD_PARTY_BASE_DIR}}/build/win" + + # env + - name: "Install env" + shell: cmd + working-directory: "${{ env.SCRIPT_BASE_DIR }}" + run: recreate_venv_win.bat + + # build + - name: "Rebuild" + shell: cmd + working-directory: "${{ env.SCRIPT_BASE_DIR }}" + run: rebuild_win.bat + + # upload artifact + - name: "Upload build package" + uses: actions/upload-artifact@v7 + with: + name: "${{ env.ARTIFACT_NAME }}" + path: "${{ env.PACKAGE_BASE_DIR }}/" + if-no-files-found: "error" + compression-level: 9 + retention-days: 1 diff --git a/.github/workflows/migrate_gse-pull-request.yml b/.github/workflows/migrate_gse-pull-request.yml index f4f113e74..68f555d80 100644 --- a/.github/workflows/migrate_gse-pull-request.yml +++ b/.github/workflows/migrate_gse-pull-request.yml @@ -16,6 +16,12 @@ jobs: if: ${{ !cancelled() }} uses: "./.github/workflows/migrate_gse-build-win.yml" + script-win-vs22: + name: "Migrate GSE win vs22" + if: ${{ !cancelled() }} + uses: "./.github/workflows/migrate_gse-build-win-vs22.yml" + + script-linux: name: "Migrate GSE linux" if: ${{ !cancelled() }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25003ae4c..2a649b34e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -297,3 +297,146 @@ jobs: if-no-files-found: "error" compression-level: 9 retention-days: 7 + + emu-win-all-vs22: + name: "Emu win all vs22" + if: ${{ !cancelled() }} + uses: "./.github/workflows/emu-build-all-win-vs22.yml" + + emu-win-prep-vs22: + needs: ["emu-win-all-vs22"] + runs-on: "windows-2022" + steps: + # on Windows Git will auto change line ending to CRLF, not preferable + - name: "Ensure LF line ending" + shell: "cmd" + working-directory: "${{ github.workspace }}" + run: | + git config --local core.autocrlf false + git config --system core.autocrlf false + git config --global core.autocrlf false + + # we need branch because it has package scripts + - name: "Checkout branch" + uses: actions/checkout@v6 + + - name: "Clone third-party deps (deps/win)" + uses: actions/checkout@v6 + with: + ref: "third-party/deps/win" + path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/win" + + # download artifacts + - name: "Download emu build artifacts (Win)" + uses: actions/download-artifact@v8 + with: + path: "build/win" + pattern: "emu-win-*-${{ github.sha }}" + merge-multiple: true + + # print files + - name: "Print files" + shell: "cmd" + working-directory: "${{ github.workspace }}" + run: | + dir /s /b /a:-d build\win + + # package (release mode) + - name: "Package build (release)" + shell: "cmd" + working-directory: "${{ github.workspace }}" + run: package_win.bat vs2022\release + + # package (debug mode) + - name: "Package build (debug)" + shell: "cmd" + working-directory: "${{ github.workspace }}" + run: package_win.bat vs2022\debug 1 + + # release (debug + release modes) if this is a tag push + - name: "Release" + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v2 + with: + files: "build/package/win/**/*" + + # upload artifacts/packages if this is a manual run + - name: "Upload release package" + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + uses: actions/upload-artifact@v7 + with: + name: "release-emu-win-release-${{ github.sha }}" + path: "build/package/win/vs2022/*release*" + if-no-files-found: "error" + compression-level: 0 + retention-days: 7 + + - name: "Upload debug package" + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + uses: actions/upload-artifact@v7 + with: + name: "release-emu-win-debug-${{ github.sha }}" + path: "build/package/win/vs2022/*debug*" + if-no-files-found: "error" + compression-level: 0 + retention-days: 7 + + migrate_gse_script-win-vs22: + name: "Migrate GSE win vs22" + if: ${{ !cancelled() }} + uses: "./.github/workflows/migrate_gse-build-win-vs22.yml" + + migrate_gse_script-win-prep-vs22: + needs: ["migrate_gse_script-win-vs22"] + runs-on: "windows-2022" + steps: + # on Windows Git will auto change line ending to CRLF, not preferable + - name: "Ensure LF line ending" + shell: "cmd" + working-directory: "${{ github.workspace }}" + run: | + git config --local core.autocrlf false + git config --system core.autocrlf false + git config --global core.autocrlf false + + # we need branch because it has package scripts + - name: "Checkout branch" + uses: actions/checkout@v6 + + - name: "Clone third-party deps (deps/win)" + uses: actions/checkout@v6 + with: + ref: "third-party/deps/win" + path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/win" + + # download artifacts + - name: "Download script build artifacts (Win)" + uses: actions/download-artifact@v8 + with: + path: "tools/migrate_gse/bin/win" + pattern: "migrate_gse-win-vs22-*" + merge-multiple: true + + # package + - name: "Package script" + shell: "cmd" + working-directory: "tools/migrate_gse" + run: package_win.bat + + # release tag + - name: "Release" + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v2 + with: + files: "tools/migrate_gse/bin/package/win/**/*" + + # upload artifact/package if this is a manual run + - name: "Upload release package" + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + uses: actions/upload-artifact@v7 + with: + name: "release-migrate_gse-win-${{ github.sha }}" + path: "tools/migrate_gse/bin/package/win/**/*" + if-no-files-found: "error" + compression-level: 9 + retention-days: 7 From 2e8bb25b8a6b7a26e907cc5c84dafa87f514cbca Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 18 May 2026 15:36:45 +0200 Subject: [PATCH 119/159] fix vs22 build bugs and artifacts --- .github/workflows/emu-build-all-win-vs22.yml | 4 ++-- .github/workflows/release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/emu-build-all-win-vs22.yml b/.github/workflows/emu-build-all-win-vs22.yml index 16f9fb115..da850e69a 100644 --- a/.github/workflows/emu-build-all-win-vs22.yml +++ b/.github/workflows/emu-build-all-win-vs22.yml @@ -108,7 +108,7 @@ jobs: # build target - name: "Build target" shell: "cmd" - working-directory: "${{ github.workspace }}/build/project/vs2026/win" + working-directory: "${{ github.workspace }}/build/project/vs2022/win" run: | msbuild /nologo /target:${{ matrix.prj }} /m:1 -p:CL_MPCount=${{ env.MAX_PARALLEL_JOBS }} /v:n /p:Configuration=${{ matrix.cfg }},Platform=${{ matrix.arch }} gbe.sln @@ -116,7 +116,7 @@ jobs: - name: "Upload target package" uses: actions/upload-artifact@v7 with: - name: "emu-win-${{ matrix.prj }}-${{ matrix.cfg }}-${{ matrix.arch }}-${{ github.sha }}" + name: "emu-win-vs22-${{ matrix.prj }}-${{ matrix.cfg }}-${{ matrix.arch }}-${{ github.sha }}" path: "build/win" if-no-files-found: "error" compression-level: 9 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a649b34e..cccec8539 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -331,7 +331,7 @@ jobs: uses: actions/download-artifact@v8 with: path: "build/win" - pattern: "emu-win-*-${{ github.sha }}" + pattern: "emu-win-vs22-*-${{ github.sha }}" merge-multiple: true # print files From 42961a80fd6d5c2914f81790521bed6b1a02be2b Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 18 May 2026 16:12:48 +0200 Subject: [PATCH 120/159] swap places --- .github/workflows/emu-build-all-win-vs22.yml | 16 ++++++++-------- .github/workflows/emu-build-all-win.yml | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/emu-build-all-win-vs22.yml b/.github/workflows/emu-build-all-win-vs22.yml index da850e69a..c08d8a559 100644 --- a/.github/workflows/emu-build-all-win-vs22.yml +++ b/.github/workflows/emu-build-all-win-vs22.yml @@ -60,6 +60,14 @@ jobs: arch: x64 steps: + # clone branch + - name: "Checkout branch" + uses: actions/checkout@v6 + + # ensure we have msbuild + - name: "Add MSBuild to PATH" + uses: microsoft/setup-msbuild@v3 + # on Windows Git will auto change line ending to CRLF, not preferable - name: "Ensure LF line ending" shell: "cmd" @@ -69,14 +77,6 @@ jobs: git config --system core.autocrlf false git config --global core.autocrlf false - # ensure we have msbuild - - name: "Add MSBuild to PATH" - uses: microsoft/setup-msbuild@v3 - - # clone branch - - name: "Checkout branch" - uses: actions/checkout@v6 - # deps - name: "Restore deps" id: "emu-deps-cache-step" diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 62eaa86f0..746397078 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -60,6 +60,14 @@ jobs: arch: x64 steps: + # clone branch + - name: "Checkout branch" + uses: actions/checkout@v6 + + # ensure we have msbuild + - name: "Add MSBuild to PATH" + uses: microsoft/setup-msbuild@v3 + # on Windows Git will auto change line ending to CRLF, not preferable - name: "Ensure LF line ending" shell: "cmd" @@ -69,14 +77,6 @@ jobs: git config --system core.autocrlf false git config --global core.autocrlf false - # ensure we have msbuild - - name: "Add MSBuild to PATH" - uses: microsoft/setup-msbuild@v3 - - # clone branch - - name: "Checkout branch" - uses: actions/checkout@v6 - # deps - name: "Restore deps" id: "emu-deps-cache-step" From a8a803e0c45d647e73d96e6a84a2ff790bba1b61 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 18 May 2026 16:27:47 +0200 Subject: [PATCH 121/159] fix release --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cccec8539..2ecd4b8e2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -365,7 +365,7 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/tags/') }} uses: actions/upload-artifact@v7 with: - name: "release-emu-win-release-${{ github.sha }}" + name: "release-emu-win-vs22-release-${{ github.sha }}" path: "build/package/win/vs2022/*release*" if-no-files-found: "error" compression-level: 0 @@ -375,7 +375,7 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/tags/') }} uses: actions/upload-artifact@v7 with: - name: "release-emu-win-debug-${{ github.sha }}" + name: "release-emu-win-vs22-debug-${{ github.sha }}" path: "build/package/win/vs2022/*debug*" if-no-files-found: "error" compression-level: 0 @@ -435,7 +435,7 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/tags/') }} uses: actions/upload-artifact@v7 with: - name: "release-migrate_gse-win-${{ github.sha }}" + name: "release-migrate_gse-win-vs22-${{ github.sha }}" path: "tools/migrate_gse/bin/package/win/**/*" if-no-files-found: "error" compression-level: 9 From 13a88fb19ba0308ddb70d9775ee743f4c70cdd35 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Mon, 18 May 2026 17:37:34 +0200 Subject: [PATCH 122/159] update python action --- .github/workflows/migrate_gse-build-win-vs22.yml | 2 +- .github/workflows/migrate_gse-build-win.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/migrate_gse-build-win-vs22.yml b/.github/workflows/migrate_gse-build-win-vs22.yml index 1638e4957..b54fa7e6a 100644 --- a/.github/workflows/migrate_gse-build-win-vs22.yml +++ b/.github/workflows/migrate_gse-build-win-vs22.yml @@ -22,7 +22,7 @@ jobs: steps: - name: "Set up Python 3.12" - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.12" diff --git a/.github/workflows/migrate_gse-build-win.yml b/.github/workflows/migrate_gse-build-win.yml index 4f24d775d..9a2544e99 100644 --- a/.github/workflows/migrate_gse-build-win.yml +++ b/.github/workflows/migrate_gse-build-win.yml @@ -22,7 +22,7 @@ jobs: steps: - name: "Set up Python 3.12" - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.12" From f947ddd73512b8f4238c20b6abc02ae50851a326 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Tue, 19 May 2026 16:37:41 +0200 Subject: [PATCH 123/159] Update release.yml Update gh-release version add rename --- .github/workflows/release.yml | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ecd4b8e2..fe1737e4f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,7 +72,7 @@ jobs: # release (debug + release modes) if this is a tag push - name: "Release" if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: files: "build/package/win/**/*" @@ -161,7 +161,7 @@ jobs: # release (debug + release modes) if this is a tag push - name: "Release" if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: files: "build/package/linux/**/*" @@ -231,7 +231,7 @@ jobs: # release tag - name: "Release" if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: files: "tools/migrate_gse/bin/package/win/**/*" @@ -283,7 +283,7 @@ jobs: # release tag - name: "Release" if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: files: "tools/migrate_gse/bin/package/linux/**/*" @@ -353,10 +353,19 @@ jobs: working-directory: "${{ github.workspace }}" run: package_win.bat vs2022\debug 1 + # Rename + - name: "Rename" + if: startsWith(github.ref, 'refs/tags/') + shell: "cmd" + working-directory: "build/package/win" + run: | + ren emu-win-debug.7z emu-win-debug-vs22.7z + ren emu-win-release.7z emu-win-release-vs22.7z + # release (debug + release modes) if this is a tag push - name: "Release" if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: files: "build/package/win/**/*" @@ -423,10 +432,17 @@ jobs: working-directory: "tools/migrate_gse" run: package_win.bat + # Rename + - name: "Rename" + if: startsWith(github.ref, 'refs/tags/') + shell: "cmd" + working-directory: "tools/migrate_gse/bin/package/win" + run: ren migrate_gse-win.7z migrate_gse-win-vs22.7z + # release tag - name: "Release" if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: files: "tools/migrate_gse/bin/package/win/**/*" From 6bd8e821ed559fc442485bdea25aadc73d2c7fa7 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Tue, 19 May 2026 17:19:46 +0200 Subject: [PATCH 124/159] Fix rename working dir --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe1737e4f..f7eeb5bb7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -357,7 +357,7 @@ jobs: - name: "Rename" if: startsWith(github.ref, 'refs/tags/') shell: "cmd" - working-directory: "build/package/win" + working-directory: "build/package/win/vs2022" run: | ren emu-win-debug.7z emu-win-debug-vs22.7z ren emu-win-release.7z emu-win-release-vs22.7z From 8fab84dbcbbb43acc9d6bdf5dddb7d21361b18c7 Mon Sep 17 00:00:00 2001 From: dasafe <48550353+dasafe@users.noreply.github.com> Date: Mon, 18 May 2026 18:23:34 +0200 Subject: [PATCH 125/159] Add independent font size support for FPS, achievement title and description --- overlay_experimental/overlay/steam_overlay.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/overlay_experimental/overlay/steam_overlay.h b/overlay_experimental/overlay/steam_overlay.h index db773caf2..bff318d78 100644 --- a/overlay_experimental/overlay/steam_overlay.h +++ b/overlay_experimental/overlay/steam_overlay.h @@ -169,10 +169,13 @@ class Steam_Overlay std::vector toggle_keys{}; - // font stuff + // font stuff - now supporting independent font sizes ImFontAtlas fonts_atlas{}; ImFont *font_default{}; ImFont *font_notif{}; + ImFont *font_fps{}; // separate font for FPS display + ImFont *font_ach_title{}; // separate font for achievement title + ImFont *font_ach_desc{}; // separate font for achievement description ImFontConfig font_cfg{}; ImFontGlyphRangesBuilder font_builder{}; ImVector ranges{}; From f38c6c998ba6d520ca6d39f739b93f404baab32a Mon Sep 17 00:00:00 2001 From: dasafe <48550353+dasafe@users.noreply.github.com> Date: Mon, 18 May 2026 18:25:07 +0200 Subject: [PATCH 126/159] Update create_fonts() to support independent font sizes for FPS, achievement title, and description --- overlay_experimental/steam_overlay.cpp | 2150 +----------------------- 1 file changed, 16 insertions(+), 2134 deletions(-) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index e3e4306df..6d9b75d73 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -1,280 +1,3 @@ -#ifdef EMU_OVERLAY - -// if you're wondering about text like: ##PopupAcceptInvite -// these are unique labels (keys) for each button/label/text,etc... -// ImGui uses the labels as keys, adding a suffic like "My Text##SomeKey" -// avoids confusing ImGui when another label has the same text "MyText" - -#include "overlay/steam_overlay.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "InGameOverlay/RendererDetector.h" - -#include "dll/dll.h" -#include "dll/settings_parser.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" - -static constexpr int max_window_id = 10000; -static constexpr int base_notif_window_id = 0 * max_window_id; -static constexpr int base_friend_window_id = 1 * max_window_id; -static constexpr int base_friend_item_id = 2 * max_window_id; - -// look for the column 'API language code' here: https://partner.steamgames.com/doc/store/localization/languages -static constexpr const char* valid_languages[] = { - "english", - "arabic", - "bulgarian", - "schinese", - "tchinese", - "czech", - "danish", - "dutch", - "finnish", - "french", - "german", - "greek", - "hungarian", - "italian", - "japanese", - "koreana", - "norwegian", - "polish", - "portuguese", - "brazilian", - "romanian", - "russian", - "spanish", - "latam", - "swedish", - "thai", - "turkish", - "ukrainian", - "vietnamese", - "croatian", - "indonesian", -}; - - -// ListBoxHeader() is deprecated and inlined inside -// Helper to calculate size from items_count and height_in_items -static inline bool ImGuiHelper_BeginListBox(const char* label, int items_count) { - int min_items = items_count < 7 ? items_count : 7; - float height = ImGui::GetTextLineHeightWithSpacing() * (min_items + 0.25f) + ImGui::GetStyle().FramePadding.y * 2.0f; - return ImGui::BeginListBox(label, ImVec2(0.0f, height)); -} - - -void Steam_Overlay::overlay_run_callback(void* object) -{ - // PRINT_DEBUG_ENTRY(); - Steam_Overlay* _this = reinterpret_cast(object); - _this->steam_run_callback(); -} - -void Steam_Overlay::overlay_networking_callback(void* object, Common_Message* msg) -{ - Steam_Overlay* _this = reinterpret_cast(object); - _this->networking_msg_received(msg); -} - -void Steam_Overlay::parse_key_combo() -{ - static const std::unordered_map KEYS_MAP { - { InGameOverlay::ToggleKey::SHIFT, "shift" }, - { InGameOverlay::ToggleKey::CTRL, "ctrl" }, - { InGameOverlay::ToggleKey::ALT, "alt" }, - { InGameOverlay::ToggleKey::TAB, "tab" }, - { InGameOverlay::ToggleKey::F1, "fn1" }, - { InGameOverlay::ToggleKey::F2, "fn2" }, - { InGameOverlay::ToggleKey::F3, "fn3" }, - { InGameOverlay::ToggleKey::F4, "fn4" }, - { InGameOverlay::ToggleKey::F5, "fn5" }, - { InGameOverlay::ToggleKey::F6, "fn6" }, - { InGameOverlay::ToggleKey::F7, "fn7" }, - { InGameOverlay::ToggleKey::F8, "fn8" }, - { InGameOverlay::ToggleKey::F9, "fn9" }, - { InGameOverlay::ToggleKey::F10, "fn10" }, - { InGameOverlay::ToggleKey::F11, "fn11" }, - { InGameOverlay::ToggleKey::F12, "fn12" }, - }; - - std::unordered_set keys_combo{}; - bool use_default = false; - if (settings->overlay_toggle_keys.empty()) { - use_default = true; - } else { - for (const auto &key_name : settings->overlay_toggle_keys) { - auto key_it = std::find_if(KEYS_MAP.cbegin(), KEYS_MAP.cend(), [&key_name](decltype(*KEYS_MAP.cbegin()) const &item) { - return common_helpers::str_cmp_insensitive(item.second, key_name); - }); - if (KEYS_MAP.cend() != key_it) { - keys_combo.insert(key_it->first); - } else { - use_default = true; - PRINT_DEBUG("[X] Unknown key '%s', using default key combo Shift + Tab", key_name.c_str()); - break; - } - } - } - - if (use_default) { - toggle_keys = { - InGameOverlay::ToggleKey::SHIFT, InGameOverlay::ToggleKey::TAB - }; - } else { - toggle_keys = std::vector(keys_combo.begin(), keys_combo.end()); - } -} - -Steam_Overlay::Steam_Overlay(Settings* settings, Local_Storage *local_storage, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking* network) : - settings(settings), - local_storage(local_storage), - callback_results(callback_results), - callbacks(callbacks), - run_every_runcb(run_every_runcb), - network(network), - stats(Steam_Overlay_Stats(settings)) -{ - // don't even bother initializing the overlay - if (settings->disable_overlay) return; - - renderer_hook_init_thread = common_helpers::KillableWorker( - [this](void *){ return renderer_hook_proc(); }, - std::chrono::milliseconds(0), - std::chrono::milliseconds(renderer_detector_polling_ms), - [this] { return !setup_overlay_called; } - ); - - renderer_detector_delay_thread = common_helpers::KillableWorker( - [this](void *){ - request_renderer_detector(); - set_renderer_hook_timeout(); - renderer_hook_init_thread.start(); - return true; - }, - std::chrono::milliseconds(settings->overlay_hook_delay_sec * 1000), - std::chrono::milliseconds(0), - [this] { return !setup_overlay_called; } - ); - - parse_key_combo(); - strncpy(username_text, settings->get_local_name(), sizeof(username_text)); - - // we need these copies to show the warning only once, then disable the flag - // avoid manipulating settings->xxx - this->warn_local_save = - !settings->disable_overlay_warning_any && !settings->disable_overlay_warning_local_save && settings->overlay_warn_local_save; - this->warn_bad_appid = - !settings->disable_overlay_warning_any && !settings->disable_overlay_warning_bad_appid && settings->get_local_game_id().AppID() == 0; - - current_language = 0; - const char *language = settings->get_language(); - - show_user_info = settings->overlay_always_show_user_info; - - int i = 0; - for (auto &lang : valid_languages) { - if (common_helpers::str_cmp_insensitive(lang, language)) { - current_language = i; - break; - } - - ++i; - } - - this->network->setCallback(CALLBACK_ID_STEAM_MESSAGES, settings->get_local_steam_id(), &Steam_Overlay::overlay_networking_callback, this); - this->run_every_runcb->add(&Steam_Overlay::overlay_run_callback, this); -} - -Steam_Overlay::~Steam_Overlay() -{ - if (settings->disable_overlay) return; - - UnSetupOverlay(); - - this->network->rmCallback(CALLBACK_ID_STEAM_MESSAGES, settings->get_local_steam_id(), &Steam_Overlay::overlay_networking_callback, this); - this->run_every_runcb->remove(&Steam_Overlay::overlay_run_callback, this); -} - -void Steam_Overlay::request_renderer_detector() -{ - PRINT_DEBUG_ENTRY(); - // request renderer detection - future_renderer = InGameOverlay::DetectRenderer(); -} - -void Steam_Overlay::set_renderer_hook_timeout() -{ - renderer_hook_timeout_ctr = settings->overlay_renderer_detector_timeout_sec /*seconds*/ * 1000 /*milli per second*/ / renderer_detector_polling_ms; -} - -void Steam_Overlay::cleanup_renderer_hook() -{ - InGameOverlay::StopRendererDetection(); - InGameOverlay::FreeDetector(); -} - -bool Steam_Overlay::renderer_hook_proc() -{ - if (renderer_hook_timeout_ctr > 0 && future_renderer.wait_for(std::chrono::milliseconds(renderer_detector_polling_ms)) != std::future_status::ready) { - return false; - } - - // free detector resources and check for failure - cleanup_renderer_hook(); - // exit on failure - bool final_chance = future_renderer.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready; - // again check for 'setup_overlay_called' to be extra sure that the overlay wasn't deinitialized - if (!setup_overlay_called || !final_chance || renderer_hook_timeout_ctr <= 0) { - PRINT_DEBUG("failed to detect renderer, ctr=%i, overlay was set up=%i", - renderer_hook_timeout_ctr, (int)setup_overlay_called - ); - return true; - } - - // do a one time initialization - // std::lock_guard lock(overlay_mutex); - _renderer = future_renderer.get(); - if (!_renderer) { // is this even possible? - PRINT_DEBUG("renderer hook was null!"); - return true; - } - PRINT_DEBUG("got renderer hook %p for '%s'", _renderer, _renderer->GetLibraryName()); - - // note: make sure to load all relevant strings before creating the font(s), otherwise some glyphs ranges will be missing - load_achievements_data(); - load_audio(); - create_fonts(); - - // setup renderer callbacks - auto overlay_toggle_callback = [this]() { open_overlay_hook(true); }; - _renderer->OverlayProc = [this]() { overlay_render_proc(); }; - _renderer->OverlayHookReady = [this](InGameOverlay::OverlayHookState state) { - PRINT_DEBUG("hook state changed to <%i>", (int)state); - overlay_state_hook(state == InGameOverlay::OverlayHookState::Ready || state == InGameOverlay::OverlayHookState::Reset); - }; - - bool started = _renderer->StartHook(overlay_toggle_callback, toggle_keys.data(), (int)toggle_keys.size(), &fonts_atlas); - PRINT_DEBUG("started renderer hook (result=%i)", (int)started); - - return true; -} - // note: make sure to load all relevant strings before creating the font(s), otherwise some glyphs ranges will be missing void Steam_Overlay::create_fonts() { @@ -285,17 +8,17 @@ void Steam_Overlay::create_fonts() fonts_atlas.Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight; float font_size = settings->overlay_appearance.font_size; + // Get independent font sizes, fallback to base font_size if not configured + float font_size_fps = settings->overlay_appearance.font_size; // can be customized + float font_size_ach_title = settings->overlay_appearance.font_size; // can be customized + float font_size_ach_desc = settings->overlay_appearance.font_size; // can be customized font_cfg.FontDataOwnedByAtlas = false; // https://github.com/ocornut/imgui/blob/master/docs/FONTS.md#loading-font-data-from-memory font_cfg.PixelSnapH = true; font_cfg.OversampleH = 1; font_cfg.OversampleV = 1; - font_cfg.SizePixels = font_size; // non-latin characters look ugly and squeezed without this horizontal spacing - font_cfg.GlyphExtraAdvanceX = settings->overlay_appearance.font_glyph_extra_spacing_x; - // font_cfg.GlyphExtraSpacing.x = settings->overlay_appearance.font_glyph_extra_spacing_x; - // font_cfg.GlyphExtraSpacing.y = settings->overlay_appearance.font_glyph_extra_spacing_y; for (const auto &ach : achievements) { font_builder.AddText(ach.title.c_str()); @@ -359,1862 +82,21 @@ void Steam_Overlay::create_fonts() ImFont *font = fonts_atlas.AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, font_size, &font_cfg); font_notif = font_default = font; stats.font = font; - - bool res = fonts_atlas.IsBuilt(); - PRINT_DEBUG("isbuilt fonts atlas (result=%i)", (int)res); - - reset_LastError(); -} - -void Steam_Overlay::load_audio() -{ - PRINT_DEBUG_ENTRY(); - - for (auto &kv : wav_files) { - std::string file_path{}; - unsigned int 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() }) { - file_path = settings_path + Steam_Overlay::ACH_SOUNDS_FOLDER + PATH_SEPARATOR + kv.first; - file_size = file_size_(file_path); - if (file_size) break; - } - - kv.second.clear(); - if (file_size) { - kv.second.assign(file_size + 1, 0); // +1 because this will be treated as a null-terminated string later - int read = Local_Storage::get_file_data(file_path, (char *)&kv.second[0], file_size); - if (read <= 0) kv.second.clear(); - PRINT_DEBUG("loaded '%s' (read %i/%u bytes)", file_path.c_str(), read, file_size); - } - } -} - -void Steam_Overlay::load_achievements_data() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - Steam_User_Stats* steamUserStats = get_steam_client()->steam_user_stats; - uint32 achievements_num = steamUserStats->GetNumAchievements(); - for (uint32 i = 0; i < achievements_num; ++i) { - Overlay_Achievement ach{}; - ach.name = steamUserStats->GetAchievementName(i); - ach.title = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "name"); - ach.description = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "desc"); - - const char *hidden = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "hidden"); - ach.hidden = hidden && hidden[0] == '1'; - - bool achieved = false; - uint32 unlock_time = 0; - if (steamUserStats->GetAchievementAndUnlockTime(ach.name.c_str(), &achieved, &unlock_time)) { - ach.achieved = achieved; - ach.unlock_time = unlock_time; - } else { - ach.achieved = false; - ach.unlock_time = 0; - } - - float pnMinProgress = 0, pnMaxProgress = 0; - if (steamUserStats->GetAchievementProgressLimits(ach.name.c_str(), &pnMinProgress, &pnMaxProgress)) { - ach.progress = (uint32)pnMinProgress; - ach.max_progress = (uint32)pnMaxProgress; - } - - if (ach.icon == nullptr) { - ach.icon = _renderer->CreateResource(); - } - if (ach.icon_gray == nullptr) { - ach.icon_gray = _renderer->CreateResource(); - } - - achievements.emplace_back(ach); - - if (!setup_overlay_called) return; - } - - PRINT_DEBUG("count=%u, loaded=%zu", achievements_num, achievements.size()); - -} - -// called initially and when window size is updated -void Steam_Overlay::overlay_state_hook(bool ready) -{ - PRINT_DEBUG("%i", (int)ready); - - // NOTE usage of local objects here cause an exception when this is called with false state - // the reason is that by the time this hook is called, the object may have been already destructed - // this is why we use global mutex - // TODO this also doesn't seem right, no idea why it happens though - // NOTE after initializing the renderer detector on another thread this was solved - std::lock_guard lock(overlay_mutex); - if (!setup_overlay_called) return; - - is_ready = ready; - - if (ready) { - // Antichamber may crash here because ImGui Context is null!, no idea why - bool not_yet = false; - if (ImGui::GetCurrentContext() && late_init_imgui.compare_exchange_weak(not_yet, true)) { - PRINT_DEBUG("late init ImGui"); - - ImGuiIO &io = ImGui::GetIO(); - // disable loading the default ini file - io.IniFilename = NULL; - - ImGuiStyle &style = ImGui::GetStyle(); - // Disable round window - style.WindowRounding = 0.0; - } - } -} - -// called when the user presses SHIFT + TAB -bool Steam_Overlay::open_overlay_hook(bool toggle) -{ - if (toggle) { - ShowOverlay(!show_overlay); - } - - return show_overlay; -} - -void Steam_Overlay::allow_renderer_frame_processing(bool state, bool cleaning_up_overlay) -{ - // this is very important internally it calls the necessary fuctions - // to properly update ImGui window size on the next overlay_render_proc() call - - if (state) { - auto new_val = ++renderer_frame_processing_requests; - if (new_val == 1) { // only take an action on first request - // allow internal frmae processing - _renderer->HideOverlayInputs(false); - PRINT_DEBUG("enabled frame processing (count=%u)", new_val); - } - } else { - if (renderer_frame_processing_requests > 0) { - auto new_val = --renderer_frame_processing_requests; - if (!new_val || cleaning_up_overlay) { // only take an action when the requests reach 0 or by force - _renderer->HideOverlayInputs(true); - PRINT_DEBUG("disabled frame processing (count=%u, force=%i)", new_val, (int)cleaning_up_overlay); - } - } - } -} - -void Steam_Overlay::obscure_game_input(bool state) { - if (state) { - auto new_val = ++obscure_cursor_requests; - if (new_val == 1) { // only take an action on first request - ImGuiIO &io = ImGui::GetIO(); - // force draw the cursor, otherwise games like Truberbrook will not have an overlay cursor - io.MouseDrawCursor = state; - // not necessary, just to be sure - io.WantCaptureMouse = state; - // not necessary, just to be sure - io.WantCaptureKeyboard = state; - - // clip the cursor - _renderer->HideAppInputs(true); - PRINT_DEBUG("obscured app input (count=%u)", new_val); - } - } else { - if (obscure_cursor_requests > 0) { - auto new_val = --obscure_cursor_requests; - if (!new_val) { // only take an action when the requests reach 0 - ImGuiIO &io = ImGui::GetIO(); - // force draw the cursor, otherwise games like Truberbrook will not have an overlay cursor - io.MouseDrawCursor = state; - // not necessary, just to be sure - io.WantCaptureMouse = state; - // not necessary, just to be sure - io.WantCaptureKeyboard = state; - - // restore the old cursor - _renderer->HideAppInputs(false); - PRINT_DEBUG("restored app input (count=%u)", new_val); - } - } - } -} - -void Steam_Overlay::notify_sound_user_invite(friend_window_state& friend_state) -{ - if (settings->disable_overlay_friend_notification) return; - - if (!(friend_state.window_state & window_state_show)) { - friend_state.window_state |= window_state_need_attention; -#ifdef __WINDOWS__ - auto wav_data = wav_files.find("overlay_friend_notification.wav"); - if (wav_files.end() != wav_data && wav_data->second.size()) { - PlaySoundA((LPCSTR)&wav_data->second[0], NULL, SND_ASYNC | SND_MEMORY); - } else { - PlaySoundA((LPCSTR)notif_invite_wav, NULL, SND_ASYNC | SND_MEMORY); - } -#endif - } -} - -void Steam_Overlay::notify_sound_user_achievement() -{ - if (settings->disable_overlay_achievement_notification) return; - -#ifdef __WINDOWS__ - auto wav_data = wav_files.find("overlay_achievement_notification.wav"); - if (wav_files.end() != wav_data && wav_data->second.size()) { - PlaySoundA((LPCSTR)&wav_data->second[0], NULL, SND_ASYNC | SND_MEMORY); - } -#endif -} - -void Steam_Overlay::notify_sound_auto_accept_friend_invite() -{ -#ifdef __WINDOWS__ - auto wav_data = wav_files.find("overlay_friend_notification.wav"); - if (wav_files.end() != wav_data && wav_data->second.size()) { - PlaySoundA((LPCSTR)&wav_data->second[0], NULL, SND_ASYNC | SND_MEMORY); - } else { - PlaySoundA((LPCSTR)notif_invite_wav, NULL, SND_ASYNC | SND_MEMORY); - } -#endif -} - -int find_free_id(std::vector &ids, int base) -{ - std::sort(ids.begin(), ids.end()); - - int id = base; - for (auto i : ids) - { - if (id < i) - break; - id = i + 1; - } - - return id > (base+max_window_id) ? 0 : id; -} - -int find_free_friend_id(const std::map &friend_windows) -{ - std::vector ids{}; - ids.reserve(friend_windows.size()); - - std::for_each(friend_windows.begin(), friend_windows.end(), [&ids](std::pair const& i) - { - ids.emplace_back(i.second.id); - }); - - return find_free_id(ids, base_friend_window_id); -} - -int find_free_notification_id(std::vector const& notifications) -{ - std::vector ids{}; - ids.reserve(notifications.size()); - - std::for_each(notifications.begin(), notifications.end(), [&ids](Notification const& i) - { - ids.emplace_back(i.id); - }); - - - return find_free_id(ids, base_friend_window_id); -} - -bool Steam_Overlay::submit_notification( - notification_type type, - const std::string &msg, - std::pair *frd, - Overlay_Achievement *ach) -{ - PRINT_DEBUG("%i", (int)type); - std::lock_guard lock(overlay_mutex); - if (!Ready()) return false; - - int id = find_free_notification_id(notifications); - if (id == 0) { - PRINT_DEBUG("error no free id to create a notification window"); - return false; - } - - Notification notif{}; - notif.start_time = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); - notif.id = id; - notif.type = (uint8)type; - notif.message = msg; - notif.frd = frd; - if (ach) notif.ach = *ach; - - notifications.emplace_back(notif); - allow_renderer_frame_processing(true); - // uncomment this block to obscure cursor input and steal focus for these specific notifications - switch (type) { - // we want to steal focus for these ones - case notification_type::invite: - obscure_game_input(true); - break; - - // not effective - case notification_type::achievement_progress: - case notification_type::achievement: - case notification_type::auto_accept_invite: - case notification_type::message: - // nothing - break; - - default: - PRINT_DEBUG("error unhandled type %i", (int)type); - break; - } - - return true; -} - -void Steam_Overlay::add_chat_message_notification(std::string const &message) -{ - if (settings->disable_overlay_friend_notification) return; - - PRINT_DEBUG("'%s'", message.c_str()); - std::lock_guard lock(overlay_mutex); - - submit_notification(notification_type::message, message); -} - -void Steam_Overlay::show_test_achievement() -{ - PRINT_DEBUG_ENTRY(); - Overlay_Achievement ach{}; - ach.title = translationTestAchievement[current_language]; - ach.description = "~~~ " + ach.title + " ~~~"; - ach.achieved = true; - - // random add icon - if (achievements.size()) { - size_t rand_idx = common_helpers::rand_number(achievements.size() - 1); - auto &rand_ach = achievements[rand_idx]; - bool achieved = rand_idx < (achievements.size() / 2); - // force upload to GPU if the pagination is request-based - try_load_ach_icon(rand_ach, achieved, settings->paginated_achievements_icons == 0); - ach.icon = rand_ach.icon; - ach.icon_gray = rand_ach.icon_gray; - } - - // randomly add progress - bool for_progress = false; - if (common_helpers::rand_number(1000) % 2) { - for_progress = true; - uint32 progress = (uint32)(common_helpers::rand_number(500) / 10 + 50); // [50, 100] - ach.max_progress = 100; - ach.progress = progress; - ach.achieved = false; - } - - post_achievement_notification(ach, for_progress); - // here we always play the sound for testing - notify_sound_user_achievement(); -} - -void Steam_Overlay::build_friend_context_menu(Friend const& frd, friend_window_state& state) -{ - if (ImGui::BeginPopupContextItem("Friends_ContextMenu", 1)) { - // this is set to true if any button was clicked - // otherwise, after clicking any button, the menu will be persistent - bool close_popup = false; - - // user clicked on "chat" - if (ImGui::Button(translationChat[current_language])) { - close_popup = true; - state.window_state |= window_state_show; - } - // user clicked on "copy id" on a friend - if (ImGui::Button(translationCopyId[current_language])) { - close_popup = true; - auto friend_id_str = std::to_string(frd.id()); - ImGui::SetClipboardText(friend_id_str.c_str()); - } - // If we have the same appid, activate the invite/join buttons - if (settings->get_local_game_id().AppID() == frd.appid()) { - // user clicked on "invite to game" - std::string translationInvite_tmp(translationInvite[current_language]); - translationInvite_tmp.append("##PopupInviteToGame"); - if (i_have_lobby && ImGui::Button(translationInvite_tmp.c_str())) { - close_popup = true; - state.window_state |= window_state_invite; - has_friend_action.push(frd); - } - - // user clicked on "accept game invite" - std::string translationJoin_tmp(translationJoin[current_language]); - translationJoin_tmp.append("##PopupAcceptInvite"); - if (state.joinable && ImGui::Button(translationJoin_tmp.c_str())) { - close_popup = true; - // don't bother adding this friend if the button "invite all" was clicked - // we will send them the invitation later in Steam_Overlay::steam_run_callback() - if (!invite_all_friends_clicked) { - state.window_state |= window_state_join; - has_friend_action.push(frd); - } - } - } - - if (close_popup || invite_all_friends_clicked) { - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } -} - -void Steam_Overlay::build_friend_window(Friend const& frd, friend_window_state& state) -{ - if (!(state.window_state & window_state_show)) - return; - - bool show = true; - bool send_chat_msg = false; - - float width = ImGui::CalcTextSize("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").x; - - if (state.window_state & window_state_need_attention && ImGui::IsWindowFocused()) { - state.window_state &= ~window_state_need_attention; - } - ImGui::SetNextWindowSizeConstraints(ImVec2{ width, ImGui::GetFontSize()*8 + ImGui::GetFrameHeightWithSpacing()*4 }, - ImVec2{ std::numeric_limits::max() , std::numeric_limits::max() }); - - ImGui::SetNextWindowBgAlpha(1.0f); - // Window id is after the ###, the window title is the friend name - std::string friend_window_id = std::move("###" + std::to_string(state.id)); - if (ImGui::Begin((state.window_title + friend_window_id).c_str(), &show)) { - if (state.window_state & window_state_need_attention && ImGui::IsWindowFocused()) { - state.window_state &= ~window_state_need_attention; - } - - // Fill this with the chat box and maybe the invitation - if (state.window_state & (window_state_lobby_invite | window_state_rich_invite)) { - ImGui::LabelText("##label", translationInvitedYouToJoinTheGame[current_language], frd.name().c_str(), frd.appid()); - ImGui::SameLine(); - if (ImGui::Button(translationAccept[current_language])) { - state.window_state |= window_state_join; - this->has_friend_action.push(frd); - } - - ImGui::SameLine(); - if (ImGui::Button(translationRefuse[current_language])) { - state.window_state &= ~(window_state_lobby_invite | window_state_rich_invite); - } - } - - ImGui::InputTextMultiline("##chat_history", &state.chat_history[0], state.chat_history.length(), { -1.0f, -2.0f * ImGui::GetFontSize() }, ImGuiInputTextFlags_ReadOnly); - // TODO: Fix the layout of the chat line + send button. - // It should be like this: chat input should fill the window size minus send button size (button size is fixed) - // |------------------------------| - // | /--------------------------\ | - // | | | | - // | | chat history | | - // | | | | - // | \--------------------------/ | - // | [____chat line______] [send] | - // |------------------------------| - // - // And it is like this - // |------------------------------| - // | /--------------------------\ | - // | | | | - // | | chat history | | - // | | | | - // | \--------------------------/ | - // | [__chat line__] [send] | - // |------------------------------| - float wnd_width = ImGui::GetContentRegionAvail().x; - ImGuiStyle &style = ImGui::GetStyle(); - wnd_width -= ImGui::CalcTextSize(translationSend[current_language]).x + style.FramePadding.x * 2 + style.ItemSpacing.x + 1; - - uint64_t frd_id = frd.id(); - ImGui::PushID((const char *)&frd_id, (const char *)&frd_id + sizeof(frd_id)); - ImGui::PushItemWidth(wnd_width); - - if (ImGui::InputText("##chat_line", state.chat_input, max_chat_len, ImGuiInputTextFlags_EnterReturnsTrue)) { - send_chat_msg = true; - ImGui::SetKeyboardFocusHere(-1); - } - - ImGui::PopItemWidth(); - ImGui::PopID(); - ImGui::SameLine(); - - if (ImGui::Button(translationSend[current_language])) { - send_chat_msg = true; - } - - if (send_chat_msg) { - if (!(state.window_state & window_state_send_message)) { - has_friend_action.push(frd); - state.window_state |= window_state_send_message; - } - } - } - - // User closed the friend window - if (!show) { - state.window_state &= ~window_state_show; - } - - ImGui::End(); -} - - -std::chrono::milliseconds Steam_Overlay::get_notification_duration(notification_type type) -{ - switch (type) - { - case notification_type::message: - return std::chrono::milliseconds(settings->overlay_appearance.notification_duration_chat); - - case notification_type::invite: - return std::chrono::milliseconds(settings->overlay_appearance.notification_duration_invitation); - - case notification_type::achievement: - return std::chrono::milliseconds(settings->overlay_appearance.notification_duration_achievement); + // Create independent fonts for FPS display + font_cfg.SizePixels = font_size_fps; + font_fps = fonts_atlas.AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, font_size_fps, &font_cfg); - case notification_type::achievement_progress: - return std::chrono::milliseconds(settings->overlay_appearance.notification_duration_progress); - - case notification_type::auto_accept_invite: - return Notification::default_show_time; - } - - PRINT_DEBUG("ERROR unhandled type %i", (int)type); - return Notification::default_show_time; -} - -// set the position of the next notification -void Steam_Overlay::set_next_notification_pos(std::pair scrn_size, std::chrono::milliseconds elapsed, std::chrono::milliseconds duration, const Notification ¬i, struct NotificationsCoords &coords) -{ - const float scrn_width = scrn_size.first; - const float scrn_height = scrn_size.second; - - auto &global_style = ImGui::GetStyle(); - const float padding_all_sides = 2 * (global_style.WindowPadding.y + global_style.WindowPadding.x); - - const float noti_width = scrn_width * Notification::width_percent; - const float msg_height = ImGui::CalcTextSize( - noti.message.c_str(), - noti.message.c_str() + noti.message.size(), - false, - noti_width - padding_all_sides - global_style.ItemSpacing.x - ).y; - float noti_height = msg_height; + // Create independent fonts for achievement title + font_cfg.SizePixels = font_size_ach_title; + font_ach_title = fonts_atlas.AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, font_size_ach_title, &font_cfg); - // get the required position - Overlay_Appearance::NotificationPosition pos = Overlay_Appearance::default_pos; - switch ((notification_type)noti.type) { - case notification_type::achievement_progress: - case notification_type::achievement: { - pos = settings->overlay_appearance.ach_earned_pos; - - const float new_msg_height = ImGui::CalcTextSize( - noti.message.c_str(), - noti.message.c_str() + noti.message.size(), - false, - noti_width - padding_all_sides - global_style.ItemSpacing.x - settings->overlay_appearance.icon_size - ).y; - const float new_noti_height = new_msg_height; - - float biggest_noti_height = settings->overlay_appearance.icon_size; - if (biggest_noti_height < new_noti_height) biggest_noti_height = new_noti_height; - - noti_height = biggest_noti_height; - - if ((notification_type)noti.type == notification_type::achievement_progress) { - if (!noti.ach.value().achieved && noti.ach.value().max_progress > 0) { - noti_height += settings->overlay_appearance.font_size + global_style.WindowPadding.y; - } - } - } - break; - - // case notification_type::invite: pos = settings->overlay_appearance.invite_pos; break; - case notification_type::invite: { - pos = settings->overlay_appearance.invite_pos; - const float msg_height = ImGui::CalcTextSize( - noti.message.c_str(), - noti.message.c_str() + noti.message.size(), - false, - noti_width - padding_all_sides - global_style.ItemSpacing.x - ).y; - noti_height = msg_height + settings->overlay_appearance.font_size + global_style.WindowPadding.y; - } - break; - case notification_type::message: pos = settings->overlay_appearance.chat_msg_pos; break; - default: PRINT_DEBUG("ERROR: unhandled notification type %i", (int)noti.type); break; - } - // add some y padding for niceness - noti_height += 2 * global_style.WindowPadding.y; - - // 0 on the y-axis is top, 0 on the x-axis is left - float x = 0.0f; - float y = 0.0f; - float animate_size = 0.0f; - const float margin_y = settings->overlay_appearance.notification_margin_y; - const float margin_x = settings->overlay_appearance.notification_margin_x; - - switch (pos) { - // top - case Overlay_Appearance::NotificationPosition::top_left: - animate_size = animate_factor(elapsed, duration) * noti_width; - x = margin_x - animate_size; - y = coords.top_left.second + margin_y; - coords.top_left.second = y + noti_height; - break; - case Overlay_Appearance::NotificationPosition::top_center: - animate_size = animate_factor(elapsed, duration) * noti_height; - x = (scrn_width / 2) - (noti_width / 2); - y = coords.top_center.second + margin_y - animate_size; - coords.top_center.second = y + noti_height; - break; - case Overlay_Appearance::NotificationPosition::top_right: - animate_size = animate_factor(elapsed, duration) * noti_width; - x = (scrn_width - noti_width - margin_x) + animate_size; - y = coords.top_right.second + margin_y; - coords.top_right.second = y + noti_height; - break; - - // bot - case Overlay_Appearance::NotificationPosition::bot_left: - animate_size = animate_factor(elapsed, duration) * noti_width; - x = margin_x - animate_size; - y = scrn_height - coords.bot_left.second - margin_y - noti_height; - coords.bot_left.second = scrn_height - y; - break; - case Overlay_Appearance::NotificationPosition::bot_center: - animate_size = animate_factor(elapsed, duration) * noti_height; - x = (scrn_width / 2) - (noti_width / 2); - y = scrn_height - coords.bot_center.second - margin_y - noti_height + animate_size; - coords.bot_center.second = scrn_height - y; - break; - case Overlay_Appearance::NotificationPosition::bot_right: - animate_size = animate_factor(elapsed, duration) * noti_width; - x = (scrn_width - noti_width - margin_x) + animate_size; - y = scrn_height - coords.bot_right.second - margin_y - noti_height; - coords.bot_right.second = scrn_height - y; - break; - - default: /* satisfy compiler warning */ break; - } - - ImGui::SetNextWindowPos(ImVec2( x, y )); - ImGui::SetNextWindowSize(ImVec2(noti_width, noti_height)); -} - -float Steam_Overlay::animate_factor(std::chrono::milliseconds elapsed, std::chrono::milliseconds duration) -{ - if (settings->overlay_appearance.notification_animation <= 0) return 0.0f; // no animation - - std::chrono::milliseconds animation_duration(settings->overlay_appearance.notification_animation); - // PRINT_DEBUG("ELAPSED %u/%u/%u", (uint32)elapsed.count(), (uint32)duration.count(), (uint32)animation_duration.count()); - - float factor = 0.0f; - if (elapsed < animation_duration) { // sliding in - factor = 1.0f - (static_cast(elapsed.count()) / animation_duration.count()); - // PRINT_DEBUG("SHOW FACTOR %f", factor); - } else { - // time between sliding in/out animation - // here we add the animation duration because we want to count after the animation - // if we have 1 sec animation & 2 sec show time: - // the duration will start at < 1 sec during the initial animation - // after the animation (1 sec), the duration will be >= 1 sec - // but since we want 2 sec show time, the duration must last 3 sec - auto steady_time = animation_duration + duration; - if (elapsed > steady_time) { - factor = static_cast((elapsed - steady_time).count()) / animation_duration.count(); - // PRINT_DEBUG("HIDE FACTOR %f", factor); - } - } + // Create independent fonts for achievement description + font_cfg.SizePixels = font_size_ach_desc; + font_ach_desc = fonts_atlas.AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, font_size_ach_desc, &font_cfg); - return factor; -} - -void Steam_Overlay::add_ach_progressbar(const Overlay_Achievement &ach) -{ - if (!ach.achieved && ach.max_progress > 0) { - char buf[32]{}; - sprintf(buf, "%u/%u", ach.progress, ach.max_progress); - ImGui::ProgressBar((float)ach.progress / ach.max_progress, { -1 , settings->overlay_appearance.font_size }, buf); - } -} + bool res = fonts_atlas.IsBuilt(); + PRINT_DEBUG("isbuilt fonts atlas (result=%i)", (int)res); -ImVec4 Steam_Overlay::get_notification_bg_rgba_safe() -{ - if (settings->overlay_appearance.notification_r >= 0 && - settings->overlay_appearance.notification_g >= 0 && - settings->overlay_appearance.notification_b >= 0 && - settings->overlay_appearance.notification_a >= 0) - { - return ImVec4( - settings->overlay_appearance.notification_r, - settings->overlay_appearance.notification_g, - settings->overlay_appearance.notification_b, - settings->overlay_appearance.notification_a - ); - } - - // fallback to dark-gray background - return ImVec4( - 0.12f, - 0.14f, - 0.21f, - 1.0f - ); + reset_LastError(); } - -void Steam_Overlay::build_notifications(float width, float height) -{ - auto now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); - std::queue friend_actions_temp{}; - - ImGui::PushFont(font_notif); - // Add window rounding - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, settings->overlay_appearance.notification_rounding); - - NotificationsCoords coords{}; - for (auto it = notifications.begin(); it != notifications.end(); ++it) { - auto noti_duration = get_notification_duration((notification_type)it->type); - if (noti_duration.count() <= 0) { - it->expired = true; - continue; - } - - // *2 for sliding in & out animation - auto total_allowed_duration = noti_duration + std::chrono::milliseconds(settings->overlay_appearance.notification_animation * 2); - auto elapsed_notif = now - it->start_time; - if (elapsed_notif > total_allowed_duration) { - it->expired = true; - continue; - } - - float settings_noti_alpha = settings->overlay_appearance.notification_a >= 0.0f && settings->overlay_appearance.notification_a <= 1.0f - ? settings->overlay_appearance.notification_a - : 1.0f; - - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, settings_noti_alpha)); - ImGui::PushStyleColor(ImGuiCol_WindowBg, get_notification_bg_rgba_safe()); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 255, 255, settings_noti_alpha * 2)); - - // some extra window flags for each notification type - ImGuiWindowFlags extra_flags = ImGuiWindowFlags_NoFocusOnAppearing; - switch ((notification_type)it->type) { - // games like "Mafia Definitive Edition" will pause the entire game/scene if focus was stolen - // be less intrusive for notifications that do not require interaction - case notification_type::achievement_progress: - case notification_type::achievement: - case notification_type::auto_accept_invite: - case notification_type::message: - extra_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoInputs; - break; - - case notification_type::invite: - // nothing - break; - - default: - PRINT_DEBUG("error unhandled flags for type %i", (int)it->type); - break; - } - - std::string wnd_name = "NotiPopupShow" + std::to_string(it->id); - - set_next_notification_pos({width, height}, elapsed_notif, noti_duration, *it, coords); - if (ImGui::Begin(wnd_name.c_str(), nullptr, - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize | extra_flags)) { - switch ((notification_type)it->type) { - case notification_type::achievement_progress: - case notification_type::achievement: { - const auto &ach = it->ach.value(); - auto &icon_rsrc = (notification_type)it->type == notification_type::achievement - ? ach.icon - : ach.icon_gray; - if (icon_rsrc->GetResourceId() != 0 && ImGui::BeginTable("imgui_table", 2)) { - ImGui::TableSetupColumn("imgui_table_image", ImGuiTableColumnFlags_WidthFixed, settings->overlay_appearance.icon_size); - ImGui::TableSetupColumn("imgui_table_text"); - ImGui::TableNextRow(ImGuiTableRowFlags_None, settings->overlay_appearance.icon_size); - - ImGui::TableSetColumnIndex(0); - ImGui::Image(icon_rsrc->GetResourceId(), ImVec2(settings->overlay_appearance.icon_size, settings->overlay_appearance.icon_size)); - - ImGui::TableSetColumnIndex(1); - ImGui::TextWrapped("%s", it->message.c_str()); - - ImGui::EndTable(); - } else { - ImGui::TextWrapped("%s", it->message.c_str()); - } - - if ((notification_type)it->type == notification_type::achievement_progress) { - add_ach_progressbar(ach); - } - } - break; - - case notification_type::invite: { - ImGui::TextWrapped("%s", it->message.c_str()); - if (ImGui::Button(translationJoin[current_language])) { - it->frd->second.window_state |= window_state_join; - 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 = {}; - } - } - break; - - case notification_type::message: - ImGui::TextWrapped("%s", it->message.c_str()); - break; - - case notification_type::auto_accept_invite: - ImGui::TextWrapped("%s", it->message.c_str()); - break; - - default: - PRINT_DEBUG("error unhandled notification for type %i", (int)it->type); - break; - } - - } - - ImGui::End(); - - ImGui::PopStyleColor(3); - } - - ImGui::PopStyleVar(); - ImGui::PopFont(); - - // erase all notifications whose visible time exceeded the max - notifications.erase(std::remove_if(notifications.begin(), notifications.end(), [this](const Notification &item) { - if (item.expired) { - PRINT_DEBUG("removing a notification"); - allow_renderer_frame_processing(false); - // uncomment this block to restore app input focus - switch ((notification_type)item.type) { - // we want to restore focus for these ones - case notification_type::invite: - obscure_game_input(false); - break; - - // not effective - case notification_type::achievement_progress: - case notification_type::achievement: - case notification_type::auto_accept_invite: - case notification_type::message: - // nothing - break; - - default: - PRINT_DEBUG("error unhandled remove for type %i", (int)item.type); - break; - } - - return true; - } - - return false; - }), notifications.end()); - - if (!friend_actions_temp.empty()) { - while (!friend_actions_temp.empty()) { - has_friend_action.push(friend_actions_temp.front()); - friend_actions_temp.pop(); - } - } -} - -void Steam_Overlay::add_auto_accept_invite_notification() -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(overlay_mutex); - if (!Ready()) return; - - char tmp[TRANSLATION_BUFFER_SIZE]{}; - snprintf(tmp, sizeof(tmp), "%s", translationAutoAcceptFriendInvite[current_language]); - - submit_notification(notification_type::auto_accept_invite, tmp); - notify_sound_auto_accept_friend_invite(); -} - -void Steam_Overlay::add_invite_notification(std::pair& wnd_state) -{ - if (settings->disable_overlay_friend_notification) return; - - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(overlay_mutex); - if (!Ready()) return; - - char tmp[TRANSLATION_BUFFER_SIZE]{}; - auto &first_friend = wnd_state.first; - auto &name = first_friend.name(); - snprintf(tmp, sizeof(tmp), translationInvitedYouToJoinTheGame[current_language], name.c_str(), (uint64)first_friend.appid()); - - submit_notification(notification_type::invite, tmp, &wnd_state); -} - -void Steam_Overlay::post_achievement_notification(Overlay_Achievement &ach, bool for_progress) -{ - if (settings->disable_overlay_achievement_notification) return; - - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(overlay_mutex); - if (!Ready()) return; - - bool achieved = !for_progress; // for progress notifications we want to load the gray icon - // force upload to GPU if the pagination is request-based - try_load_ach_icon(ach, achieved, settings->paginated_achievements_icons == 0); - submit_notification( - for_progress ? notification_type::achievement_progress : notification_type::achievement, - ach.title + "\n" + ach.description, - {}, - &ach - ); -} - -bool Steam_Overlay::try_load_ach_icon(Overlay_Achievement &ach, bool achieved, bool upload_new_icon_to_gpu) -{ - if (!_renderer) return false; - if (settings->paginated_achievements_icons < 0) return false; // no icons are loaded anyway - if (!settings->overlay_upload_achs_icons_to_gpu) return false; // don't upload anything to the GPU - - auto &icon_rsrc = achieved ? ach.icon : ach.icon_gray; - if (icon_rsrc->GetResourceId() != 0) return true; - - // icons needs to be loaded, but we're not allowed - if (!upload_new_icon_to_gpu) return false; - - int &icon_handle = achieved ? ach.icon_handle : ach.icon_gray_handle; - if (Settings::UNLOADED_IMAGE_HANDLE == icon_handle) { // not loaded yet - icon_handle = get_steam_client()->steam_user_stats->get_achievement_icon_handle(ach.name, achieved); - } - auto image_info = settings->get_image(icon_handle); - if (image_info) { - int icon_size = static_cast(settings->overlay_appearance.icon_size); - //icon_rsrc->SetAutoLoad(InGameOverlay::ResourceAutoLoad_t::OnUse); - icon_rsrc->AttachResource((void*)image_info->data.c_str(), icon_size, icon_size); - - PRINT_DEBUG("'%s' (result=%i)", ach.name.c_str(), (int)icon_rsrc->GetResourceId() != 0); - } - - return icon_rsrc->GetResourceId() != 0; -} - -// Try to make this function as short as possible or it might affect game's fps. -void Steam_Overlay::overlay_render_proc() -{ - std::lock_guard lock(overlay_mutex); - - if (!Ready()) return; - - if (show_overlay) { - render_main_window(); - } - - if (notifications.size()) { - ImGuiIO &io = ImGui::GetIO(); - build_notifications(io.DisplaySize.x, io.DisplaySize.y); - } - - if (stats.show_any_stats()) { - stats.render_stats(current_language); - } - - load_next_ach_icon(); -} - -uint32 Steam_Overlay::apply_global_style_color() -{ - uint32 style_color_stack = 0; - if ((settings->overlay_appearance.background_r >= 0) && - (settings->overlay_appearance.background_g >= 0) && - (settings->overlay_appearance.background_b >= 0) && - (settings->overlay_appearance.background_a >= 0)) { - ImVec4 colorSet = ImVec4( - settings->overlay_appearance.background_r, - settings->overlay_appearance.background_g, - settings->overlay_appearance.background_b, - settings->overlay_appearance.background_a - ); - ImGui::PushStyleColor(ImGuiCol_WindowBg, colorSet); - style_color_stack += 1; - } - - if ((settings->overlay_appearance.element_r >= 0) && - (settings->overlay_appearance.element_g >= 0) && - (settings->overlay_appearance.element_b >= 0) && - (settings->overlay_appearance.element_a >= 0)) { - ImVec4 colorSet = ImVec4( - settings->overlay_appearance.element_r, - settings->overlay_appearance.element_g, - settings->overlay_appearance.element_b, - settings->overlay_appearance.element_a - ); - ImGui::PushStyleColor(ImGuiCol_TitleBgActive, colorSet); - ImGui::PushStyleColor(ImGuiCol_Button, colorSet); - ImGui::PushStyleColor(ImGuiCol_FrameBg, colorSet); - ImGui::PushStyleColor(ImGuiCol_ResizeGrip, colorSet); - style_color_stack += 4; - } - - if ((settings->overlay_appearance.element_hovered_r >= 0) && - (settings->overlay_appearance.element_hovered_g >= 0) && - (settings->overlay_appearance.element_hovered_b >= 0) && - (settings->overlay_appearance.element_hovered_a >= 0)) { - ImVec4 colorSet = ImVec4( - settings->overlay_appearance.element_hovered_r, - settings->overlay_appearance.element_hovered_g, - settings->overlay_appearance.element_hovered_b, - settings->overlay_appearance.element_hovered_a - ); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colorSet); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, colorSet); - ImGui::PushStyleColor(ImGuiCol_ResizeGripHovered, colorSet); - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, colorSet); - style_color_stack += 4; - } - - if ((settings->overlay_appearance.element_active_r >= 0) && - (settings->overlay_appearance.element_active_g >= 0) && - (settings->overlay_appearance.element_active_b >= 0) && - (settings->overlay_appearance.element_active_a >= 0)) { - ImVec4 colorSet = ImVec4( - settings->overlay_appearance.element_active_r, - settings->overlay_appearance.element_active_g, - settings->overlay_appearance.element_active_b, - settings->overlay_appearance.element_active_a - ); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, colorSet); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, colorSet); - ImGui::PushStyleColor(ImGuiCol_ResizeGripActive, colorSet); - ImGui::PushStyleColor(ImGuiCol_Header, colorSet); - ImGui::PushStyleColor(ImGuiCol_HeaderActive, colorSet); - style_color_stack += 5; - } - - return style_color_stack; -} - -// Try to make this function as short as possible or it might affect game's fps. -void Steam_Overlay::render_main_window() -{ - char tmp[TRANSLATION_BUFFER_SIZE]{}; - snprintf(tmp, sizeof(tmp), translationRenderer[current_language], (_renderer == nullptr ? "Unknown" : _renderer->GetLibraryName())); - std::string windowTitle{}; - // Note: don't translate this, project and author names are nouns, they must be kept intact for proper referral - // think of it as translating "Protobuf - Google" - windowTitle.append("Ingame Overlay project - Nemirtingas (").append(tmp).append(")"); - - bool show = true; - - ImGuiIO &io = ImGui::GetIO(); - - ImGui::PushFont(font_default); - uint32 style_color_stack = apply_global_style_color(); - - ImGui::SetNextWindowPos({ 0, 0 }); - ImGui::SetNextWindowSize({ io.DisplaySize.x, io.DisplaySize.y }); - if (ImGui::Begin(windowTitle.c_str(), &show, - ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoBringToFrontOnFocus)) { - if (show_user_info) { - ImGui::LabelText("##playinglabel", translationUserPlaying[current_language], - settings->get_local_name(), - settings->get_local_steam_id().ConvertToUint64(), - settings->get_local_game_id().AppID()); - } - - ImGui::Spacing(); - - ImGui::SameLine(); - // user clicked on "toggle user info" - if (ImGui::Button(translationToggleUserInfo[current_language])) { - show_user_info = !show_user_info; - } - - ImGui::SameLine(); - // user clicked on "show achievements" - if (ImGui::Button(translationShowAchievements[current_language])) { - show_achievements = !show_achievements; - } - - ImGui::SameLine(); - // user clicked on "test achievement" - if (ImGui::Button(translationTestAchievement[current_language])) { - show_test_achievement(); - } - - ImGui::SameLine(); - // user clicked on "copy id" on themselves - if (ImGui::Button(translationCopyId[current_language])) { - auto friend_id_str = std::to_string(settings->get_local_steam_id().ConvertToUint64()); - ImGui::SetClipboardText(friend_id_str.c_str()); - } - - ImGui::SameLine(); - // user clicked on "settings" - if (ImGui::Button(translationSettings[current_language])) { - show_settings = !show_settings; - } - - ImGui::Spacing(); - ImGui::Spacing(); - // user clicked on "FPS" - ImGui::SameLine(); - if (ImGui::Checkbox(translationFpsCheckbox[current_language], &stats.show_fps)) { - allow_renderer_frame_processing(stats.show_fps); - } - // user clicked on "Frametime" - ImGui::SameLine(); - if (ImGui::Checkbox(translationFrametimeCheckbox[current_language], &stats.show_frametime)) { - allow_renderer_frame_processing(stats.show_frametime); - } - // user clicked on "Playtime" - ImGui::SameLine(); - if (ImGui::Checkbox(translationPlaytimeCheckbox[current_language], &stats.show_playtime)) { - allow_renderer_frame_processing(stats.show_playtime); - } - - ImGui::Spacing(); - ImGui::Spacing(); - ImGui::LabelText("##label", "%s", translationFriends[current_language]); - - if (!friends.empty()) { - if (i_have_lobby) { - std::string inviteAll(translationInviteAll[current_language]); - inviteAll.append("##PopupInviteAllFriends"); - if (ImGui::Button(inviteAll.c_str())) { // if btn clicked - invite_all_friends_clicked = true; - } - } - - if (ImGuiHelper_BeginListBox("##label", static_cast(friends.size()))) { - std::for_each(friends.begin(), friends.end(), [this](std::pair &i) { - ImGui::PushID(i.second.id-base_friend_window_id+base_friend_item_id); - - ImGui::Selectable(i.second.window_title.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick); - build_friend_context_menu(i.first, i.second); - if (ImGui::IsItemClicked() && ImGui::IsMouseDoubleClicked(0)) { - i.second.window_state |= window_state_show; - } - - ImGui::PopID(); - - build_friend_window(i.first, i.second); - }); - ImGui::EndListBox(); - } - } - - // user clicked on "show achievements" button - if (show_achievements && achievements.size()) { - ImGui::SetNextWindowSizeConstraints(ImVec2(ImGui::GetFontSize() * 32, ImGui::GetFontSize() * 32), ImVec2(8192, 8192)); - ImGui::SetNextWindowBgAlpha(1.0f); - if (ImGui::Begin(translationAchievementWindow[current_language], &show_achievements)) { - ImGui::Text("%s", translationListOfAchievements[current_language]); - ImGui::BeginChild(translationAchievements[current_language]); - for (auto & x : achievements) { - bool achieved = x.achieved; - bool hidden = x.hidden && !achieved; - - // force upload to GPU if the pagination is request-based - try_load_ach_icon(x, true, settings->paginated_achievements_icons == 0); - try_load_ach_icon(x, false, settings->paginated_achievements_icons == 0); - - ImGui::Separator(); - - bool could_create_ach_table_entry = false; - if (x.icon->GetResourceId() != 0 || x.icon_gray->GetResourceId() != 0) { - if (ImGui::BeginTable(x.title.c_str(), 2)) { - could_create_ach_table_entry = true; - - ImGui::TableSetupColumn("imgui_table_image", ImGuiTableColumnFlags_WidthFixed, settings->overlay_appearance.icon_size); - ImGui::TableSetupColumn("imgui_table_text"); - ImGui::TableNextRow(ImGuiTableRowFlags_None, settings->overlay_appearance.icon_size); - - ImGui::TableSetColumnIndex(0); - auto &icon_rsrc = achieved ? x.icon : x.icon_gray; - if (icon_rsrc->GetResourceId() != 0) { - ImGui::Image( - icon_rsrc->GetResourceId(), - ImVec2(settings->overlay_appearance.icon_size, settings->overlay_appearance.icon_size) - ); - } - - ImGui::TableSetColumnIndex(1); - // the next column is the achievement text below - } - } - - // we want to display the ach text regardless the icons were displayed or not - ImGui::Text("%s", x.title.c_str()); - - if (hidden) { - ImGui::Text("%s", translationHiddenAchievement[current_language]); - } else { - ImGui::TextWrapped("%s", x.description.c_str()); - } - - if (achieved) { - char buffer[80]{}; - time_t unlock_time = (time_t)x.unlock_time; - size_t written = std::strftime(buffer, sizeof(buffer), settings->overlay_appearance.ach_unlock_datetime_format.c_str(), std::localtime(&unlock_time)); - if (!written) { // count was reached before the entire string could be stored, keep it safe - std::strftime(buffer, sizeof(buffer), "%Y/%m/%d - %H:%M:%S", std::localtime(&unlock_time)); - } - - ImGui::TextColored(ImVec4(0, 255, 0, 255), translationAchievedOn[current_language], buffer); - } else { - ImGui::TextColored(ImVec4(255, 0, 0, 255), "%s", translationNotAchieved[current_language]); - } - add_ach_progressbar(x); - - if (could_create_ach_table_entry) ImGui::EndTable(); - - ImGui::Separator(); - } - ImGui::EndChild(); - } - - ImGui::End(); - } - - // user clicked on "settings" button - if (show_settings) { - ImGui::SetNextWindowBgAlpha(1.0f); - if (ImGui::Begin(translationGlobalSettingsWindow[current_language], &show_settings)) { - ImGui::Text("%s", translationGlobalSettingsWindowDescription[current_language]); - - ImGui::Separator(); - - ImGui::Text("%s", translationUsername[current_language]); - ImGui::SameLine(); - ImGui::InputText("##username", username_text, sizeof(username_text), 0); - - ImGui::Separator(); - - ImGui::Text("%s", translationLanguage[current_language]); - ImGui::ListBox("##language", ¤t_language, valid_languages, sizeof(valid_languages) / sizeof(valid_languages[0]), 7); - ImGui::Text(translationSelectedLanguage[current_language], valid_languages[current_language]); - - ImGui::Separator(); - - ImGui::Text("%s", translationRestartTheGameToApply[current_language]); - if (ImGui::Button(translationSave[current_language])) { - save_settings = true; - show_settings = false; - } - } - - ImGui::End(); - } - - // we have a url to open/display - if (show_url.size()) { - std::string url = show_url; - bool show = true; - ImGui::SetNextWindowBgAlpha(1.0f); - if (ImGui::Begin(URL_WINDOW_NAME, &show)) { - ImGui::Text("%s", translationSteamOverlayURL[current_language]); - ImGui::Spacing(); - - ImGui::PushItemWidth(ImGui::CalcTextSize(url.c_str()).x + 20); - ImGui::InputText("##url_copy", (char *)url.data(), url.size(), ImGuiInputTextFlags_ReadOnly); - ImGui::PopItemWidth(); - - ImGui::Spacing(); - if (ImGui::Button(translationClose[current_language]) || !show) - show_url = ""; - // ImGui::SetWindowSize(ImVec2(ImGui::CalcTextSize(url.c_str()).x + 10, 0)); - } - - ImGui::End(); - } - - bool show_warning = warn_local_save || warn_bad_appid; - if (show_warning) { - ImGui::SetNextWindowSizeConstraints(ImVec2(ImGui::GetFontSize() * 32, ImGui::GetFontSize() * 32), ImVec2(8192, 8192)); - ImGui::SetNextWindowFocus(); - ImGui::SetNextWindowBgAlpha(1.0f); - if (ImGui::Begin(translationWarning[current_language], &show_warning)) { - if (warn_bad_appid) { - ImGui::TextColored(ImVec4(255, 0, 0, 255), - "%s %s %s", - translationWarning[current_language], translationWarning[current_language], translationWarning[current_language]); - ImGui::TextWrapped("%s", translationWarningDescription_badAppid[current_language]); - ImGui::TextColored(ImVec4(255, 0, 0, 255), - "%s %s %s", - translationWarning[current_language], translationWarning[current_language], translationWarning[current_language]); - } - if (warn_local_save) { - ImGui::TextColored(ImVec4(255, 0, 0, 255), - "%s %s %s", - translationWarning[current_language], translationWarning[current_language], translationWarning[current_language]); - ImGui::TextWrapped("%s", translationWarningDescription_localSave[current_language]); - ImGui::TextColored(ImVec4(255, 0, 0, 255), - "%s %s %s", - translationWarning[current_language], translationWarning[current_language], translationWarning[current_language]); - } - } - - ImGui::End(); - // if button closed, don't show the warning again - if (!show_warning) { - warn_local_save = false; - warn_bad_appid = false; - } - } - } - - ImGui::End(); - - if (style_color_stack) ImGui::PopStyleColor(style_color_stack); - ImGui::PopFont(); - - if (!show) { - ShowOverlay(false); - } - -} - -void Steam_Overlay::load_next_ach_icon() -{ - // this function only works when icons pagination is active, request-based loading is not supported too (pagination=0) - if (!settings->overlay_upload_achs_icons_to_gpu || settings->paginated_achievements_icons <= 0 || achievements.empty()) return; - - size_t linear_idx = last_loaded_ach_icon / 2; // 2 icons per achievement, 1 achieved, 1 unachieved - if (linear_idx >= achievements.size()) { - last_loaded_ach_icon = 0; - linear_idx = 0; - } - -#ifndef EMU_RELEASE_BUILD - auto now1 = std::chrono::high_resolution_clock::now(); -#endif - - auto &ach = achievements.at(linear_idx); - ++last_loaded_ach_icon; - - bool achieved = last_loaded_ach_icon % 2 != 0; - auto &icon_rsrc = achieved ? ach.icon : ach.icon_gray; - // always force upload to GPU in background-loading mode (pagination > 0) - bool loaded = try_load_ach_icon(ach, achieved, true); - -#ifndef EMU_RELEASE_BUILD - if (loaded) { - auto now2 = std::chrono::high_resolution_clock::now(); - auto dd = (unsigned)std::chrono::duration_cast(now2 - now1).count(); - PRINT_DEBUG("uploaded an achievement icon to GPU in %u ms", dd); - } -#endif - -} - -void Steam_Overlay::SetupOverlay() -{ - if (settings->disable_overlay) return; - - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(overlay_mutex); - - bool not_called_yet = false; - if (setup_overlay_called.compare_exchange_weak(not_called_yet, true)) { - if (settings->overlay_hook_delay_sec > 0) { - PRINT_DEBUG("waiting %i seconds", settings->overlay_hook_delay_sec); - renderer_detector_delay_thread.start(); - } else { - // "HITMAN 3" fails if the detector was started later (after a delay) - // so request the renderer detector immediately (the old behavior) - request_renderer_detector(); - set_renderer_hook_timeout(); - renderer_hook_init_thread.start(); - } - } -} - -void Steam_Overlay::UnSetupOverlay() -{ - if (settings->disable_overlay) return; - - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(overlay_mutex); - - bool already_called = true; - if (setup_overlay_called.compare_exchange_weak(already_called, false)) { - is_ready = false; - - renderer_hook_init_thread.kill(); - renderer_detector_delay_thread.kill(); - - // stop internal frame processing & restore cursor - if (_renderer) { - // for some reason this gets triggered after the overlay instance has been destroyed - // I assume because the game de-initializes DX later after closing Steam APIs - // this hacky solution just sets it to an empty function - _renderer->OverlayHookReady = [](InGameOverlay::OverlayHookState){}; - _renderer->OverlayProc = [](){}; - - allow_renderer_frame_processing(false, true); - obscure_game_input(false); - - PRINT_DEBUG("releasing any images resources"); - for (auto &ach : achievements) { - if (ach.icon->GetResourceId() != 0) { - ach.icon->Unload(); - } - - if (ach.icon_gray->GetResourceId() != 0) { - ach.icon_gray->Unload(); - } - } - - // manually calling this dtor looks bad, but it actually prevents a lot of crashes on exit, don't remove it! - // many DX12 games will crash on exit if the hook wasn't manually removed (ex appid 2933080, 1583230) - _renderer->~RendererHook_t(); - _renderer = nullptr; - } - - cleanup_renderer_hook(); - } - - PRINT_DEBUG("done *********"); -} - -bool Steam_Overlay::Ready() const -{ - return !settings->disable_overlay && is_ready && late_init_imgui; -} - -bool Steam_Overlay::NeedPresent() const -{ - PRINT_DEBUG_ENTRY(); - return !settings->disable_overlay; -} - -void Steam_Overlay::SetNotificationPosition(ENotificationPosition eNotificationPosition) -{ - if (settings->disable_overlay) return; - - PRINT_DEBUG("TODO %i", (int)eNotificationPosition); - std::lock_guard lock(overlay_mutex); - - notif_position = eNotificationPosition; -} - -void Steam_Overlay::SetNotificationInset(int nHorizontalInset, int nVerticalInset) -{ - if (settings->disable_overlay) return; - - PRINT_DEBUG("TODO x=%i y=%i", nHorizontalInset, nVerticalInset); - std::lock_guard lock(overlay_mutex); - - h_inset = nHorizontalInset; - v_inset = nVerticalInset; -} - -void Steam_Overlay::OpenOverlayInvite(CSteamID lobbyId) -{ - PRINT_DEBUG("TODO %llu", lobbyId.ConvertToUint64()); - std::lock_guard lock(overlay_mutex); - if (!Ready()) return; - - ShowOverlay(true); -} - -void Steam_Overlay::OpenOverlay(const char* pchDialog) -{ - PRINT_DEBUG("TODO '%s'", pchDialog); - std::lock_guard lock(overlay_mutex); - if (!Ready()) return; - - // TODO: Show pages depending on pchDialog - if ((strncmp(pchDialog, "Friends", sizeof("Friends") - 1) == 0) && (settings->overlayAutoAcceptInvitesCount() > 0)) { - PRINT_DEBUG("won't open overlay's friends list because some friends are defined in the auto accept list"); - add_auto_accept_invite_notification(); - } else { - ShowOverlay(true); - } -} - -void Steam_Overlay::OpenOverlayWebpage(const char* pchURL) -{ - PRINT_DEBUG("TODO '%s'", pchURL); - std::lock_guard lock(overlay_mutex); - if (!Ready()) return; - - show_url = pchURL; - ShowOverlay(true); -} - -bool Steam_Overlay::ShowOverlay() const -{ - return show_overlay; -} - -void Steam_Overlay::ShowOverlay(bool state) -{ - std::lock_guard lock(overlay_mutex); - if (!Ready() || show_overlay == state) return; - - show_overlay = state; - overlay_state_changed = true; - - PRINT_DEBUG("%i", (int)state); - - Steam_Overlay::allow_renderer_frame_processing(state); - Steam_Overlay::obscure_game_input(state); - -} - -void Steam_Overlay::SetLobbyInvite(Friend friendId, uint64 lobbyId) -{ - PRINT_DEBUG("%" PRIu64 " %llu", friendId.id(), lobbyId); - std::lock_guard lock(overlay_mutex); - if (!Ready()) return; - - auto i = friends.find(friendId); - if (i != friends.end()) - { - auto& frd = i->second; - frd.lobbyId = lobbyId; - frd.window_state |= window_state_lobby_invite; - // Make sure don't have rich presence invite and a lobby invite (it should not happen but who knows) - frd.window_state &= ~window_state_rich_invite; - add_invite_notification(*i); - notify_sound_user_invite(i->second); - } -} - -void Steam_Overlay::SetRichInvite(Friend friendId, const char* connect_str) -{ - PRINT_DEBUG("%" PRIu64 " '%s'", friendId.id(), connect_str); - std::lock_guard lock(overlay_mutex); - if (!Ready()) return; - - auto i = friends.find(friendId); - if (i != friends.end()) - { - auto& frd = i->second; - strncpy(frd.connect, connect_str, k_cchMaxRichPresenceValueLength - 1); - frd.window_state |= window_state_rich_invite; - // Make sure don't have rich presence invite and a lobby invite (it should not happen but who knows) - frd.window_state &= ~window_state_lobby_invite; - add_invite_notification(*i); - notify_sound_user_invite(i->second); - } -} - -void Steam_Overlay::FriendConnect(Friend _friend) -{ - if (settings->disable_overlay) return; - - PRINT_DEBUG("%" PRIu64 "", _friend.id()); - std::lock_guard lock(overlay_mutex); - - // players connections might happen earlier before the overlay is ready - // we don't want to miss them - //if (!Ready()) return; - - int id = find_free_friend_id(friends); - if (id != 0) { - auto& item = friends[_friend]; - item.window_title = std::move(_friend.name() + " " + translationPlaying[current_language] + " " + std::to_string(_friend.appid())); - item.window_state = window_state_none; - item.id = id; - memset(item.chat_input, 0, max_chat_len); - item.joinable = false; - } else { - PRINT_DEBUG("error no free id to create a friend window"); - } -} - -void Steam_Overlay::FriendDisconnect(Friend _friend) -{ - if (settings->disable_overlay) return; - - PRINT_DEBUG("%" PRIu64 "", _friend.id()); - std::lock_guard lock(overlay_mutex); - - // players connections might happen earlier before the overlay is ready - // we don't want to miss them - //if (!Ready()) return; - - auto it = friends.find(_friend); - if (it != friends.end()) - friends.erase(it); -} - -// show a notification when the user unlocks an achievement -void Steam_Overlay::AddAchievementNotification(const std::string &ach_name, nlohmann::json const &ach, bool for_progress) -{ - if (settings->disable_overlay) return; - - PRINT_DEBUG("'%s' %i", ach_name.c_str(), (int)for_progress); - std::lock_guard lock(overlay_mutex); - if (!Ready()) return; - - // don't return early when disable_overlay_achievement_notification is true - // otherwise when you open the achievements list/menu you won't see the new unlock status - - for (auto &a : achievements) { - if (a.name == ach_name) { - try { - // lock to prevent modifications to this json object - std::lock_guard lock2(global_mutex); - - a.achieved = ach.value("earned", false); - a.unlock_time = ach.value("earned_time", static_cast(0)); - a.progress = ach.value("progress", static_cast(0)); - a.max_progress = ach.value("max_progress", static_cast(0)); - } catch(...) {} - - if (a.achieved && !for_progress) { // here we don't show the progress indications - post_achievement_notification(a, for_progress); - notify_sound_user_achievement(); - } else if (for_progress && !settings->disable_overlay_achievement_progress) { // progress indication is shown for locked achievements only - // post notification if this isn't a progress, or a progress and the user didn't disable these notifications - post_achievement_notification(a, for_progress); - // don't play sound - } - break; - } - } -} - - - -// -- steam run callbacks -- -void Steam_Overlay::steam_run_callback_update_my_lobby() -{ - std::lock_guard lock(global_mutex); - Steam_Friends* steamFriends = get_steam_client()->steam_friends; - if (std::string(steamFriends->get_friend_rich_presence_silent(settings->get_local_steam_id(), "connect")).length() > 0) { - i_have_lobby = true; - } else if (settings->get_lobby().IsValid()) { - i_have_lobby = true; - } else { - i_have_lobby = false; - } -} - -bool Steam_Overlay::is_friend_joinable(std::pair &f) -{ - PRINT_DEBUG("%" PRIu64 "", f.first.id()); - std::lock_guard lock(global_mutex); - Steam_Friends* steamFriends = get_steam_client()->steam_friends; - - if (std::string(steamFriends->get_friend_rich_presence_silent((uint64)f.first.id(), "connect")).length() > 0 ) { - PRINT_DEBUG("%" PRIu64 " true (connect string)", f.first.id()); - return true; - } - - FriendGameInfo_t friend_game_info{}; - steamFriends->GetFriendGamePlayed((uint64)f.first.id(), &friend_game_info); - if (friend_game_info.m_steamIDLobby.IsValid() && (f.second.window_state & window_state_lobby_invite)) { - PRINT_DEBUG("%" PRIu64 " true (friend in a game)", f.first.id()); - return true; - } - - PRINT_DEBUG("%" PRIu64 " false", f.first.id()); - return false; -} - -void Steam_Overlay::invite_friend(uint64 friend_id, class Steam_Friends* steamFriends, class Steam_Matchmaking* steamMatchmaking) -{ - std::string connect_str = steamFriends->get_friend_rich_presence_silent(settings->get_local_steam_id(), "connect"); - if (connect_str.length() > 0) { - steamFriends->InviteUserToGame(friend_id, connect_str.c_str()); - PRINT_DEBUG("sent game invitation to friend with id = %llu", friend_id); - } else if (settings->get_lobby().IsValid()) { - steamMatchmaking->InviteUserToLobby(settings->get_lobby(), friend_id); - PRINT_DEBUG("sent lobby invitation to friend with id = %llu", friend_id); - } -} - -void Steam_Overlay::steam_run_callback_friends_actions() -{ - Steam_Friends* steamFriends = get_steam_client()->steam_friends; - Steam_Matchmaking* steamMatchmaking = get_steam_client()->steam_matchmaking; - - std::for_each(friends.begin(), friends.end(), [this](std::pair &i) { - i.second.joinable = is_friend_joinable(i); - }); - - while (!has_friend_action.empty()) { - auto friend_info = friends.find(has_friend_action.front()); - if (friend_info != friends.end()) { - uint64 friend_id = (uint64)friend_info->first.id(); - // The user clicked on "Send" - if (friend_info->second.window_state & window_state_send_message) { - char* input = friend_info->second.chat_input; - char* end_input = input + strlen(input); - char* printable_char = std::find_if(input, end_input, [](char c) { return std::isgraph(c); }); - - // Check if the message contains something else than blanks - if (printable_char != end_input) { - // Handle chat send - Common_Message msg; - Steam_Messages* steam_messages = new Steam_Messages; - steam_messages->set_type(Steam_Messages::FRIEND_CHAT); - steam_messages->set_message(friend_info->second.chat_input); - msg.set_allocated_steam_messages(steam_messages); - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_dest_id(friend_id); - network->sendTo(&msg, true); - - friend_info->second.chat_history.append(get_steam_client()->settings_client->get_local_name()).append(": ").append(input).append("\n", 1); - } - *input = 0; // Reset the input field - - friend_info->second.window_state &= ~window_state_send_message; - } - // The user clicked on "Invite" (but invite all wasn't clicked) - if (friend_info->second.window_state & window_state_invite) { - invite_friend(friend_id, steamFriends, steamMatchmaking); - - friend_info->second.window_state &= ~window_state_invite; - } - // The user clicked on "Join" - if (friend_info->second.window_state & window_state_join) { - std::string connect = steamFriends->get_friend_rich_presence_silent(friend_id, "connect"); - // The user got a lobby invite and accepted it - if (friend_info->second.window_state & window_state_lobby_invite) { - GameLobbyJoinRequested_t data; - data.m_steamIDLobby.SetFromUint64(friend_info->second.lobbyId); - data.m_steamIDFriend.SetFromUint64(friend_id); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - - friend_info->second.window_state &= ~window_state_lobby_invite; - } else { - // The user got a rich presence invite and accepted it - if (friend_info->second.window_state & window_state_rich_invite) { - GameRichPresenceJoinRequested_t data = {}; - data.m_steamIDFriend.SetFromUint64(friend_id); - strncpy(data.m_rgchConnect, friend_info->second.connect, k_cchMaxRichPresenceValueLength - 1); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - - friend_info->second.window_state &= ~window_state_rich_invite; - } else if (connect.length() > 0) { - GameRichPresenceJoinRequested_t data = {}; - data.m_steamIDFriend.SetFromUint64(friend_id); - strncpy(data.m_rgchConnect, connect.c_str(), k_cchMaxRichPresenceValueLength - 1); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - - //Not sure about this but it fixes sonic racing transformed invites - FriendGameInfo_t friend_game_info = {}; - steamFriends->GetFriendGamePlayed(friend_id, &friend_game_info); - uint64 lobby_id = friend_game_info.m_steamIDLobby.ConvertToUint64(); - if (lobby_id) { - GameLobbyJoinRequested_t data; - data.m_steamIDLobby.SetFromUint64(lobby_id); - data.m_steamIDFriend.SetFromUint64(friend_id); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - } - - friend_info->second.window_state &= ~window_state_join; - } - } - has_friend_action.pop(); - } - -} - -void Steam_Overlay::steam_run_callback() -{ - if (!Ready()) return; - - if (overlay_state_changed) { - overlay_state_changed = false; - - GameOverlayActivated_t data{}; - data.m_bActive = show_overlay; - data.m_bUserInitiated = true; - data.m_dwOverlayPID = 123; - data.m_nAppID = settings->get_local_game_id().AppID(); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - - Steam_Friends* steamFriends = get_steam_client()->steam_friends; - Steam_Matchmaking* steamMatchmaking = get_steam_client()->steam_matchmaking; - - if (save_settings) { - save_settings = false; - - const char *language_text = valid_languages[current_language]; - save_global_settings(get_steam_client()->local_storage, username_text, language_text); - get_steam_client()->settings_client->set_local_name(username_text); - get_steam_client()->settings_server->set_local_name(username_text); - get_steam_client()->settings_client->set_language(language_text); - get_steam_client()->settings_server->set_language(language_text); - steamFriends->resend_friend_data(); - } - - steam_run_callback_update_my_lobby(); - - // if variable == true, then set it to false and return true (because state was changed) in that case - bool yes_clicked = true; - if (invite_all_friends_clicked.compare_exchange_weak(yes_clicked, false)) { - PRINT_DEBUG("Steam_Overlay will send invitations to [%zu] friends if they're using the same app", friends.size()); - uint32 current_appid = settings->get_local_game_id().AppID(); - for (auto &fr : friends) { - if (fr.first.appid() == current_appid) { // friend is playing the same game - uint64 friend_id = (uint64)fr.first.id(); - invite_friend(friend_id, steamFriends, steamMatchmaking); - } - } - } - - // don't wait to lock the overlay mutex - // * the overlay proc might be active and holding the overlay mutex - // * this steam callback will be blocked, but it has the global mutex locked - // * the overlay proc tries to lock the global mutex, but since we have it, it will be blocked forever - if (overlay_mutex.try_lock()) { - if (Ready()) { - // ============================================================== - // call steam callbacks that has to change the overlay state here - // ============================================================== - - steam_run_callback_friends_actions(); - } - - overlay_mutex.unlock(); - } -} - - - -// -- steam networking callbacks -- -void Steam_Overlay::networking_msg_received(Common_Message *msg) -{ - if (msg->has_steam_messages()) { - std::lock_guard lock(overlay_mutex); - - Friend frd; - frd.set_id(msg->source_id()); - auto friend_info = friends.find(frd); - if (friend_info != friends.end()) { - Steam_Messages const& steam_message = msg->steam_messages(); - // Change color to cyan for friend - friend_info->second.chat_history.append(friend_info->first.name() + ": " + steam_message.message()).append("\n", 1); - if (!(friend_info->second.window_state & window_state_show)) { - friend_info->second.window_state |= window_state_need_attention; - } - - add_chat_message_notification(friend_info->first.name() + ": " + steam_message.message()); - notify_sound_user_invite(friend_info->second); - } - } -} - - -#endif From 1b5f26afa92d913828ded413f6b28fdd11549f2f Mon Sep 17 00:00:00 2001 From: dasafe <48550353+dasafe@users.noreply.github.com> Date: Mon, 18 May 2026 18:26:12 +0200 Subject: [PATCH 127/159] Update render_stats() to properly use FPS font with PushFont/PopFont --- overlay_experimental/steam_overlay_stats.cpp | 65 +------------------- 1 file changed, 2 insertions(+), 63 deletions(-) diff --git a/overlay_experimental/steam_overlay_stats.cpp b/overlay_experimental/steam_overlay_stats.cpp index f69a3dbf1..018f64858 100644 --- a/overlay_experimental/steam_overlay_stats.cpp +++ b/overlay_experimental/steam_overlay_stats.cpp @@ -1,63 +1,3 @@ -#include "overlay/steam_overlay_stats.h" -// translation -#include "overlay/steam_overlay_translations.h" -#include - - -Steam_Overlay_Stats::Steam_Overlay_Stats(class Settings* settings): - settings(settings) -{ - show_fps = settings->overlay_always_show_fps; - show_frametime = settings->overlay_always_show_frametime; - show_playtime = settings->overlay_always_show_playtime; -} - -bool Steam_Overlay_Stats::show_any_stats() const -{ - return show_fps || show_frametime || show_playtime; -} - -void Steam_Overlay_Stats::update_frametime(const std::chrono::high_resolution_clock::time_point &now) -{ - running_frametime_ms += static_cast( - std::chrono::duration_cast(now - last_frame_timepoint).count() - ); - last_frame_timepoint = now; - if (last_frametime_idx >= (settings->overlay_fps_avg_window - 1)) { - last_frametime_idx = 0; - active_frametime_ms = static_cast(running_frametime_ms) / settings->overlay_fps_avg_window; - if (running_frametime_ms > 0) { - active_fps = static_cast((1000 * settings->overlay_fps_avg_window) / running_frametime_ms); - } else { // happens when avg window =1, no idea why! - active_fps = 999; - } - running_frametime_ms = 0; - } else { - ++last_frametime_idx; - } -} - -void Steam_Overlay_Stats::update_playtime(const std::chrono::high_resolution_clock::time_point &now) -{ - const auto update_duration_sec = std::chrono::duration_cast( - now - last_playtime - ).count(); - if (update_duration_sec < 1) return; - - last_playtime = now; - - const auto time_duration_sec = (unsigned long long)std::chrono::duration_cast( - now - initial_time - ).count(); - active_playtime_sec = static_cast(time_duration_sec % 60); - - const auto time_duration_min = time_duration_sec / 60; - active_playtime_min = static_cast(time_duration_min % 60); - - const auto time_duration_hr = time_duration_min / 60; - active_playtime_hr = static_cast(time_duration_hr % 24); -} - void Steam_Overlay_Stats::render_stats(int current_language) { auto now = std::chrono::high_resolution_clock::now(); @@ -68,8 +8,6 @@ void Steam_Overlay_Stats::render_stats(int current_language) update_playtime(now); } - ImGui::PushFont(font); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, settings->overlay_appearance.notification_rounding); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); @@ -141,11 +79,12 @@ void Steam_Overlay_Stats::render_stats(int current_language) ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollWithMouse)) { + ImGui::PushFont(font); ImGui::TextWrapped("%s", stats_txt.c_str()); + ImGui::PopFont(); } ImGui::End(); ImGui::PopStyleColor(2); ImGui::PopStyleVar(2); - ImGui::PopFont(); } From ae312cacc6e9ca6f3df2bf0d1c4f8974768694b9 Mon Sep 17 00:00:00 2001 From: dasafe <48550353+dasafe@users.noreply.github.com> Date: Mon, 18 May 2026 18:49:36 +0200 Subject: [PATCH 128/159] Add independent font size settings for overlay (FPS, achievement title, description) --- dll/dll/settings.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dll/dll/settings.h b/dll/dll/settings.h index 9550aba6b..b2003e7c4 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -129,6 +129,10 @@ struct Overlay_Appearance { std::string font_override{}; // path to a custom user-provided TTF font float font_size = 16.0f; + // independent font sizes + float font_size_fps = 16.0f; + float font_size_ach_title = 18.0f; + float font_size_ach_desc = 14.0f; float icon_size = 64.0f; @@ -280,7 +284,7 @@ class Settings { // allow stats not defined by the user? bool allow_unknown_stats = false; - // whether to enable the functionality which reports an achievement progress for stats that are tied to achievements + //whether to enable the functionality which reports an achievement progress for stats that are tied to achievements // only used internally for a stat that's tied to an achievement, the normal achievement progress requests made by the game are not impacted bool stat_achievement_progress_functionality = true; // when a stat that's tied to an achievement gets a new value, should the emu save that progress only if it's higher? From 33a56f77c17526d07b20e3c395293801da871d72 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 18 May 2026 22:45:34 +0200 Subject: [PATCH 129/159] Restore overlay font sizing and achievement notification text --- dll/dll/settings.h | 7 +- dll/settings_parser.cpp | 12 + overlay_experimental/steam_overlay.cpp | 2220 ++++++++++++++++- overlay_experimental/steam_overlay_stats.cpp | 67 +- .../configs.overlay.EXAMPLE.ini | 4 + 5 files changed, 2277 insertions(+), 33 deletions(-) diff --git a/dll/dll/settings.h b/dll/dll/settings.h index b2003e7c4..4ec72eda0 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -129,10 +129,9 @@ struct Overlay_Appearance { std::string font_override{}; // path to a custom user-provided TTF font float font_size = 16.0f; - // independent font sizes - float font_size_fps = 16.0f; - float font_size_ach_title = 18.0f; - float font_size_ach_desc = 14.0f; + float font_size_fps = 0.0f; + float font_size_ach_title = 0.0f; + float font_size_ach_desc = 0.0f; float icon_size = 64.0f; diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 09aaec871..481423c2b 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -260,6 +260,18 @@ static void load_overlay_appearance(class Settings *settings_client, class Setti float nfont_size = std::stof(value, NULL); settings_client->overlay_appearance.font_size = nfont_size; settings_server->overlay_appearance.font_size = nfont_size; + } else if (name.compare("Font_Size_FPS") == 0) { + float nfont_size = std::stof(value, NULL); + settings_client->overlay_appearance.font_size_fps = nfont_size; + settings_server->overlay_appearance.font_size_fps = nfont_size; + } else if (name.compare("Font_Size_Achievement_Title") == 0) { + float nfont_size = std::stof(value, NULL); + settings_client->overlay_appearance.font_size_ach_title = nfont_size; + settings_server->overlay_appearance.font_size_ach_title = nfont_size; + } else if (name.compare("Font_Size_Achievement_Description") == 0) { + float nfont_size = std::stof(value, NULL); + settings_client->overlay_appearance.font_size_ach_desc = nfont_size; + settings_server->overlay_appearance.font_size_ach_desc = nfont_size; } else if (name.compare("Icon_Size") == 0) { float nicon_size = std::stof(value, NULL); settings_client->overlay_appearance.icon_size = nicon_size; diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 6d9b75d73..484db932d 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -1,3 +1,280 @@ +#ifdef EMU_OVERLAY + +// if you're wondering about text like: ##PopupAcceptInvite +// these are unique labels (keys) for each button/label/text,etc... +// ImGui uses the labels as keys, adding a suffic like "My Text##SomeKey" +// avoids confusing ImGui when another label has the same text "MyText" + +#include "overlay/steam_overlay.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "InGameOverlay/RendererDetector.h" + +#include "dll/dll.h" +#include "dll/settings_parser.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" + +static constexpr int max_window_id = 10000; +static constexpr int base_notif_window_id = 0 * max_window_id; +static constexpr int base_friend_window_id = 1 * max_window_id; +static constexpr int base_friend_item_id = 2 * max_window_id; + +// look for the column 'API language code' here: https://partner.steamgames.com/doc/store/localization/languages +static constexpr const char* valid_languages[] = { + "english", + "arabic", + "bulgarian", + "schinese", + "tchinese", + "czech", + "danish", + "dutch", + "finnish", + "french", + "german", + "greek", + "hungarian", + "italian", + "japanese", + "koreana", + "norwegian", + "polish", + "portuguese", + "brazilian", + "romanian", + "russian", + "spanish", + "latam", + "swedish", + "thai", + "turkish", + "ukrainian", + "vietnamese", + "croatian", + "indonesian", +}; + + +// ListBoxHeader() is deprecated and inlined inside +// Helper to calculate size from items_count and height_in_items +static inline bool ImGuiHelper_BeginListBox(const char* label, int items_count) { + int min_items = items_count < 7 ? items_count : 7; + float height = ImGui::GetTextLineHeightWithSpacing() * (min_items + 0.25f) + ImGui::GetStyle().FramePadding.y * 2.0f; + return ImGui::BeginListBox(label, ImVec2(0.0f, height)); +} + + +void Steam_Overlay::overlay_run_callback(void* object) +{ + // PRINT_DEBUG_ENTRY(); + Steam_Overlay* _this = reinterpret_cast(object); + _this->steam_run_callback(); +} + +void Steam_Overlay::overlay_networking_callback(void* object, Common_Message* msg) +{ + Steam_Overlay* _this = reinterpret_cast(object); + _this->networking_msg_received(msg); +} + +void Steam_Overlay::parse_key_combo() +{ + static const std::unordered_map KEYS_MAP { + { InGameOverlay::ToggleKey::SHIFT, "shift" }, + { InGameOverlay::ToggleKey::CTRL, "ctrl" }, + { InGameOverlay::ToggleKey::ALT, "alt" }, + { InGameOverlay::ToggleKey::TAB, "tab" }, + { InGameOverlay::ToggleKey::F1, "fn1" }, + { InGameOverlay::ToggleKey::F2, "fn2" }, + { InGameOverlay::ToggleKey::F3, "fn3" }, + { InGameOverlay::ToggleKey::F4, "fn4" }, + { InGameOverlay::ToggleKey::F5, "fn5" }, + { InGameOverlay::ToggleKey::F6, "fn6" }, + { InGameOverlay::ToggleKey::F7, "fn7" }, + { InGameOverlay::ToggleKey::F8, "fn8" }, + { InGameOverlay::ToggleKey::F9, "fn9" }, + { InGameOverlay::ToggleKey::F10, "fn10" }, + { InGameOverlay::ToggleKey::F11, "fn11" }, + { InGameOverlay::ToggleKey::F12, "fn12" }, + }; + + std::unordered_set keys_combo{}; + bool use_default = false; + if (settings->overlay_toggle_keys.empty()) { + use_default = true; + } else { + for (const auto &key_name : settings->overlay_toggle_keys) { + auto key_it = std::find_if(KEYS_MAP.cbegin(), KEYS_MAP.cend(), [&key_name](decltype(*KEYS_MAP.cbegin()) const &item) { + return common_helpers::str_cmp_insensitive(item.second, key_name); + }); + if (KEYS_MAP.cend() != key_it) { + keys_combo.insert(key_it->first); + } else { + use_default = true; + PRINT_DEBUG("[X] Unknown key '%s', using default key combo Shift + Tab", key_name.c_str()); + break; + } + } + } + + if (use_default) { + toggle_keys = { + InGameOverlay::ToggleKey::SHIFT, InGameOverlay::ToggleKey::TAB + }; + } else { + toggle_keys = std::vector(keys_combo.begin(), keys_combo.end()); + } +} + +Steam_Overlay::Steam_Overlay(Settings* settings, Local_Storage *local_storage, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking* network) : + settings(settings), + local_storage(local_storage), + callback_results(callback_results), + callbacks(callbacks), + run_every_runcb(run_every_runcb), + network(network), + stats(Steam_Overlay_Stats(settings)) +{ + // don't even bother initializing the overlay + if (settings->disable_overlay) return; + + renderer_hook_init_thread = common_helpers::KillableWorker( + [this](void *){ return renderer_hook_proc(); }, + std::chrono::milliseconds(0), + std::chrono::milliseconds(renderer_detector_polling_ms), + [this] { return !setup_overlay_called; } + ); + + renderer_detector_delay_thread = common_helpers::KillableWorker( + [this](void *){ + request_renderer_detector(); + set_renderer_hook_timeout(); + renderer_hook_init_thread.start(); + return true; + }, + std::chrono::milliseconds(settings->overlay_hook_delay_sec * 1000), + std::chrono::milliseconds(0), + [this] { return !setup_overlay_called; } + ); + + parse_key_combo(); + strncpy(username_text, settings->get_local_name(), sizeof(username_text)); + + // we need these copies to show the warning only once, then disable the flag + // avoid manipulating settings->xxx + this->warn_local_save = + !settings->disable_overlay_warning_any && !settings->disable_overlay_warning_local_save && settings->overlay_warn_local_save; + this->warn_bad_appid = + !settings->disable_overlay_warning_any && !settings->disable_overlay_warning_bad_appid && settings->get_local_game_id().AppID() == 0; + + current_language = 0; + const char *language = settings->get_language(); + + show_user_info = settings->overlay_always_show_user_info; + + int i = 0; + for (auto &lang : valid_languages) { + if (common_helpers::str_cmp_insensitive(lang, language)) { + current_language = i; + break; + } + + ++i; + } + + this->network->setCallback(CALLBACK_ID_STEAM_MESSAGES, settings->get_local_steam_id(), &Steam_Overlay::overlay_networking_callback, this); + this->run_every_runcb->add(&Steam_Overlay::overlay_run_callback, this); +} + +Steam_Overlay::~Steam_Overlay() +{ + if (settings->disable_overlay) return; + + UnSetupOverlay(); + + this->network->rmCallback(CALLBACK_ID_STEAM_MESSAGES, settings->get_local_steam_id(), &Steam_Overlay::overlay_networking_callback, this); + this->run_every_runcb->remove(&Steam_Overlay::overlay_run_callback, this); +} + +void Steam_Overlay::request_renderer_detector() +{ + PRINT_DEBUG_ENTRY(); + // request renderer detection + future_renderer = InGameOverlay::DetectRenderer(); +} + +void Steam_Overlay::set_renderer_hook_timeout() +{ + renderer_hook_timeout_ctr = settings->overlay_renderer_detector_timeout_sec /*seconds*/ * 1000 /*milli per second*/ / renderer_detector_polling_ms; +} + +void Steam_Overlay::cleanup_renderer_hook() +{ + InGameOverlay::StopRendererDetection(); + InGameOverlay::FreeDetector(); +} + +bool Steam_Overlay::renderer_hook_proc() +{ + if (renderer_hook_timeout_ctr > 0 && future_renderer.wait_for(std::chrono::milliseconds(renderer_detector_polling_ms)) != std::future_status::ready) { + return false; + } + + // free detector resources and check for failure + cleanup_renderer_hook(); + // exit on failure + bool final_chance = future_renderer.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready; + // again check for 'setup_overlay_called' to be extra sure that the overlay wasn't deinitialized + if (!setup_overlay_called || !final_chance || renderer_hook_timeout_ctr <= 0) { + PRINT_DEBUG("failed to detect renderer, ctr=%i, overlay was set up=%i", + renderer_hook_timeout_ctr, (int)setup_overlay_called + ); + return true; + } + + // do a one time initialization + // std::lock_guard lock(overlay_mutex); + _renderer = future_renderer.get(); + if (!_renderer) { // is this even possible? + PRINT_DEBUG("renderer hook was null!"); + return true; + } + PRINT_DEBUG("got renderer hook %p for '%s'", _renderer, _renderer->GetLibraryName()); + + // note: make sure to load all relevant strings before creating the font(s), otherwise some glyphs ranges will be missing + load_achievements_data(); + load_audio(); + create_fonts(); + + // setup renderer callbacks + auto overlay_toggle_callback = [this]() { open_overlay_hook(true); }; + _renderer->OverlayProc = [this]() { overlay_render_proc(); }; + _renderer->OverlayHookReady = [this](InGameOverlay::OverlayHookState state) { + PRINT_DEBUG("hook state changed to <%i>", (int)state); + overlay_state_hook(state == InGameOverlay::OverlayHookState::Ready || state == InGameOverlay::OverlayHookState::Reset); + }; + + bool started = _renderer->StartHook(overlay_toggle_callback, toggle_keys.data(), (int)toggle_keys.size(), &fonts_atlas); + PRINT_DEBUG("started renderer hook (result=%i)", (int)started); + + return true; +} + // note: make sure to load all relevant strings before creating the font(s), otherwise some glyphs ranges will be missing void Steam_Overlay::create_fonts() { @@ -8,17 +285,26 @@ void Steam_Overlay::create_fonts() fonts_atlas.Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight; float font_size = settings->overlay_appearance.font_size; - // Get independent font sizes, fallback to base font_size if not configured - float font_size_fps = settings->overlay_appearance.font_size; // can be customized - float font_size_ach_title = settings->overlay_appearance.font_size; // can be customized - float font_size_ach_desc = settings->overlay_appearance.font_size; // can be customized + float font_size_fps = settings->overlay_appearance.font_size_fps > 0.0f + ? settings->overlay_appearance.font_size_fps + : font_size; + float font_size_ach_title = settings->overlay_appearance.font_size_ach_title > 0.0f + ? settings->overlay_appearance.font_size_ach_title + : font_size; + float font_size_ach_desc = settings->overlay_appearance.font_size_ach_desc > 0.0f + ? settings->overlay_appearance.font_size_ach_desc + : font_size; font_cfg.FontDataOwnedByAtlas = false; // https://github.com/ocornut/imgui/blob/master/docs/FONTS.md#loading-font-data-from-memory font_cfg.PixelSnapH = true; font_cfg.OversampleH = 1; font_cfg.OversampleV = 1; + font_cfg.SizePixels = font_size; // non-latin characters look ugly and squeezed without this horizontal spacing + font_cfg.GlyphExtraAdvanceX = settings->overlay_appearance.font_glyph_extra_spacing_x; + // font_cfg.GlyphExtraSpacing.x = settings->overlay_appearance.font_glyph_extra_spacing_x; + // font_cfg.GlyphExtraSpacing.y = settings->overlay_appearance.font_glyph_extra_spacing_y; for (const auto &ach : achievements) { font_builder.AddText(ach.title.c_str()); @@ -73,30 +359,1912 @@ void Steam_Overlay::create_fonts() font_builder.BuildRanges(&ranges); font_cfg.GlyphRanges = ranges.Data; - if (settings->overlay_appearance.font_override.size()) { - fonts_atlas.AddFontFromFileTTF(settings->overlay_appearance.font_override.c_str(), font_size, &font_cfg); - font_cfg.MergeMode = true; // merge next fonts into the first one, as if they were all just 1 font file - } - - // note: base85 compressed arrays caused a compiler heap allocation error, regular compression is more guaranteed - ImFont *font = fonts_atlas.AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, font_size, &font_cfg); - font_notif = font_default = font; - stats.font = font; - - // Create independent fonts for FPS display - font_cfg.SizePixels = font_size_fps; - font_fps = fonts_atlas.AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, font_size_fps, &font_cfg); - - // Create independent fonts for achievement title - font_cfg.SizePixels = font_size_ach_title; - font_ach_title = fonts_atlas.AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, font_size_ach_title, &font_cfg); - - // Create independent fonts for achievement description - font_cfg.SizePixels = font_size_ach_desc; - font_ach_desc = fonts_atlas.AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, font_size_ach_desc, &font_cfg); - + auto add_overlay_font = [this](float size) { + font_cfg.SizePixels = size; + font_cfg.MergeMode = false; + + ImFont *font = nullptr; + if (settings->overlay_appearance.font_override.size()) { + font = fonts_atlas.AddFontFromFileTTF(settings->overlay_appearance.font_override.c_str(), size, &font_cfg); + if (font) { + font_cfg.MergeMode = true; // merge next font into the custom font + } + } + + // note: base85 compressed arrays caused a compiler heap allocation error, regular compression is more guaranteed + ImFont *fallback_font = fonts_atlas.AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, size, &font_cfg); + return font ? font : fallback_font; + }; + + font_notif = font_default = add_overlay_font(font_size); + font_fps = add_overlay_font(font_size_fps); + font_ach_title = add_overlay_font(font_size_ach_title); + font_ach_desc = add_overlay_font(font_size_ach_desc); + stats.font = font_fps; + bool res = fonts_atlas.IsBuilt(); PRINT_DEBUG("isbuilt fonts atlas (result=%i)", (int)res); reset_LastError(); } + +void Steam_Overlay::load_audio() +{ + PRINT_DEBUG_ENTRY(); + + for (auto &kv : wav_files) { + std::string file_path{}; + unsigned int 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() }) { + file_path = settings_path + Steam_Overlay::ACH_SOUNDS_FOLDER + PATH_SEPARATOR + kv.first; + file_size = file_size_(file_path); + if (file_size) break; + } + + kv.second.clear(); + if (file_size) { + kv.second.assign(file_size + 1, 0); // +1 because this will be treated as a null-terminated string later + int read = Local_Storage::get_file_data(file_path, (char *)&kv.second[0], file_size); + if (read <= 0) kv.second.clear(); + PRINT_DEBUG("loaded '%s' (read %i/%u bytes)", file_path.c_str(), read, file_size); + } + } +} + +void Steam_Overlay::load_achievements_data() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + Steam_User_Stats* steamUserStats = get_steam_client()->steam_user_stats; + uint32 achievements_num = steamUserStats->GetNumAchievements(); + for (uint32 i = 0; i < achievements_num; ++i) { + Overlay_Achievement ach{}; + ach.name = steamUserStats->GetAchievementName(i); + ach.title = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "name"); + ach.description = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "desc"); + + const char *hidden = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "hidden"); + ach.hidden = hidden && hidden[0] == '1'; + + bool achieved = false; + uint32 unlock_time = 0; + if (steamUserStats->GetAchievementAndUnlockTime(ach.name.c_str(), &achieved, &unlock_time)) { + ach.achieved = achieved; + ach.unlock_time = unlock_time; + } else { + ach.achieved = false; + ach.unlock_time = 0; + } + + float pnMinProgress = 0, pnMaxProgress = 0; + if (steamUserStats->GetAchievementProgressLimits(ach.name.c_str(), &pnMinProgress, &pnMaxProgress)) { + ach.progress = (uint32)pnMinProgress; + ach.max_progress = (uint32)pnMaxProgress; + } + + if (ach.icon == nullptr) { + ach.icon = _renderer->CreateResource(); + } + if (ach.icon_gray == nullptr) { + ach.icon_gray = _renderer->CreateResource(); + } + + achievements.emplace_back(ach); + + if (!setup_overlay_called) return; + } + + PRINT_DEBUG("count=%u, loaded=%zu", achievements_num, achievements.size()); + +} + +// called initially and when window size is updated +void Steam_Overlay::overlay_state_hook(bool ready) +{ + PRINT_DEBUG("%i", (int)ready); + + // NOTE usage of local objects here cause an exception when this is called with false state + // the reason is that by the time this hook is called, the object may have been already destructed + // this is why we use global mutex + // TODO this also doesn't seem right, no idea why it happens though + // NOTE after initializing the renderer detector on another thread this was solved + std::lock_guard lock(overlay_mutex); + if (!setup_overlay_called) return; + + is_ready = ready; + + if (ready) { + // Antichamber may crash here because ImGui Context is null!, no idea why + bool not_yet = false; + if (ImGui::GetCurrentContext() && late_init_imgui.compare_exchange_weak(not_yet, true)) { + PRINT_DEBUG("late init ImGui"); + + ImGuiIO &io = ImGui::GetIO(); + // disable loading the default ini file + io.IniFilename = NULL; + + ImGuiStyle &style = ImGui::GetStyle(); + // Disable round window + style.WindowRounding = 0.0; + } + } +} + +// called when the user presses SHIFT + TAB +bool Steam_Overlay::open_overlay_hook(bool toggle) +{ + if (toggle) { + ShowOverlay(!show_overlay); + } + + return show_overlay; +} + +void Steam_Overlay::allow_renderer_frame_processing(bool state, bool cleaning_up_overlay) +{ + // this is very important internally it calls the necessary fuctions + // to properly update ImGui window size on the next overlay_render_proc() call + + if (state) { + auto new_val = ++renderer_frame_processing_requests; + if (new_val == 1) { // only take an action on first request + // allow internal frmae processing + _renderer->HideOverlayInputs(false); + PRINT_DEBUG("enabled frame processing (count=%u)", new_val); + } + } else { + if (renderer_frame_processing_requests > 0) { + auto new_val = --renderer_frame_processing_requests; + if (!new_val || cleaning_up_overlay) { // only take an action when the requests reach 0 or by force + _renderer->HideOverlayInputs(true); + PRINT_DEBUG("disabled frame processing (count=%u, force=%i)", new_val, (int)cleaning_up_overlay); + } + } + } +} + +void Steam_Overlay::obscure_game_input(bool state) { + if (state) { + auto new_val = ++obscure_cursor_requests; + if (new_val == 1) { // only take an action on first request + ImGuiIO &io = ImGui::GetIO(); + // force draw the cursor, otherwise games like Truberbrook will not have an overlay cursor + io.MouseDrawCursor = state; + // not necessary, just to be sure + io.WantCaptureMouse = state; + // not necessary, just to be sure + io.WantCaptureKeyboard = state; + + // clip the cursor + _renderer->HideAppInputs(true); + PRINT_DEBUG("obscured app input (count=%u)", new_val); + } + } else { + if (obscure_cursor_requests > 0) { + auto new_val = --obscure_cursor_requests; + if (!new_val) { // only take an action when the requests reach 0 + ImGuiIO &io = ImGui::GetIO(); + // force draw the cursor, otherwise games like Truberbrook will not have an overlay cursor + io.MouseDrawCursor = state; + // not necessary, just to be sure + io.WantCaptureMouse = state; + // not necessary, just to be sure + io.WantCaptureKeyboard = state; + + // restore the old cursor + _renderer->HideAppInputs(false); + PRINT_DEBUG("restored app input (count=%u)", new_val); + } + } + } +} + +void Steam_Overlay::notify_sound_user_invite(friend_window_state& friend_state) +{ + if (settings->disable_overlay_friend_notification) return; + + if (!(friend_state.window_state & window_state_show)) { + friend_state.window_state |= window_state_need_attention; +#ifdef __WINDOWS__ + auto wav_data = wav_files.find("overlay_friend_notification.wav"); + if (wav_files.end() != wav_data && wav_data->second.size()) { + PlaySoundA((LPCSTR)&wav_data->second[0], NULL, SND_ASYNC | SND_MEMORY); + } else { + PlaySoundA((LPCSTR)notif_invite_wav, NULL, SND_ASYNC | SND_MEMORY); + } +#endif + } +} + +void Steam_Overlay::notify_sound_user_achievement() +{ + if (settings->disable_overlay_achievement_notification) return; + +#ifdef __WINDOWS__ + auto wav_data = wav_files.find("overlay_achievement_notification.wav"); + if (wav_files.end() != wav_data && wav_data->second.size()) { + PlaySoundA((LPCSTR)&wav_data->second[0], NULL, SND_ASYNC | SND_MEMORY); + } +#endif +} + +void Steam_Overlay::notify_sound_auto_accept_friend_invite() +{ +#ifdef __WINDOWS__ + auto wav_data = wav_files.find("overlay_friend_notification.wav"); + if (wav_files.end() != wav_data && wav_data->second.size()) { + PlaySoundA((LPCSTR)&wav_data->second[0], NULL, SND_ASYNC | SND_MEMORY); + } else { + PlaySoundA((LPCSTR)notif_invite_wav, NULL, SND_ASYNC | SND_MEMORY); + } +#endif +} + +int find_free_id(std::vector &ids, int base) +{ + std::sort(ids.begin(), ids.end()); + + int id = base; + for (auto i : ids) + { + if (id < i) + break; + id = i + 1; + } + + return id > (base+max_window_id) ? 0 : id; +} + +int find_free_friend_id(const std::map &friend_windows) +{ + std::vector ids{}; + ids.reserve(friend_windows.size()); + + std::for_each(friend_windows.begin(), friend_windows.end(), [&ids](std::pair const& i) + { + ids.emplace_back(i.second.id); + }); + + return find_free_id(ids, base_friend_window_id); +} + +int find_free_notification_id(std::vector const& notifications) +{ + std::vector ids{}; + ids.reserve(notifications.size()); + + std::for_each(notifications.begin(), notifications.end(), [&ids](Notification const& i) + { + ids.emplace_back(i.id); + }); + + + return find_free_id(ids, base_friend_window_id); +} + +bool Steam_Overlay::submit_notification( + notification_type type, + const std::string &msg, + std::pair *frd, + Overlay_Achievement *ach) +{ + PRINT_DEBUG("%i", (int)type); + std::lock_guard lock(overlay_mutex); + if (!Ready()) return false; + + int id = find_free_notification_id(notifications); + if (id == 0) { + PRINT_DEBUG("error no free id to create a notification window"); + return false; + } + + Notification notif{}; + notif.start_time = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + notif.id = id; + notif.type = (uint8)type; + notif.message = msg; + notif.frd = frd; + if (ach) notif.ach = *ach; + + notifications.emplace_back(notif); + allow_renderer_frame_processing(true); + // uncomment this block to obscure cursor input and steal focus for these specific notifications + switch (type) { + // we want to steal focus for these ones + case notification_type::invite: + obscure_game_input(true); + break; + + // not effective + case notification_type::achievement_progress: + case notification_type::achievement: + case notification_type::auto_accept_invite: + case notification_type::message: + // nothing + break; + + default: + PRINT_DEBUG("error unhandled type %i", (int)type); + break; + } + + return true; +} + +void Steam_Overlay::add_chat_message_notification(std::string const &message) +{ + if (settings->disable_overlay_friend_notification) return; + + PRINT_DEBUG("'%s'", message.c_str()); + std::lock_guard lock(overlay_mutex); + + submit_notification(notification_type::message, message); +} + +void Steam_Overlay::show_test_achievement() +{ + PRINT_DEBUG_ENTRY(); + Overlay_Achievement ach{}; + ach.title = translationTestAchievement[current_language]; + ach.description = "~~~ " + ach.title + " ~~~"; + ach.achieved = true; + + // random add icon + if (achievements.size()) { + size_t rand_idx = common_helpers::rand_number(achievements.size() - 1); + auto &rand_ach = achievements[rand_idx]; + bool achieved = rand_idx < (achievements.size() / 2); + // force upload to GPU if the pagination is request-based + try_load_ach_icon(rand_ach, achieved, settings->paginated_achievements_icons == 0); + ach.icon = rand_ach.icon; + ach.icon_gray = rand_ach.icon_gray; + } + + // randomly add progress + bool for_progress = false; + if (common_helpers::rand_number(1000) % 2) { + for_progress = true; + uint32 progress = (uint32)(common_helpers::rand_number(500) / 10 + 50); // [50, 100] + ach.max_progress = 100; + ach.progress = progress; + ach.achieved = false; + } + + post_achievement_notification(ach, for_progress); + // here we always play the sound for testing + notify_sound_user_achievement(); +} + +void Steam_Overlay::build_friend_context_menu(Friend const& frd, friend_window_state& state) +{ + if (ImGui::BeginPopupContextItem("Friends_ContextMenu", 1)) { + // this is set to true if any button was clicked + // otherwise, after clicking any button, the menu will be persistent + bool close_popup = false; + + // user clicked on "chat" + if (ImGui::Button(translationChat[current_language])) { + close_popup = true; + state.window_state |= window_state_show; + } + // user clicked on "copy id" on a friend + if (ImGui::Button(translationCopyId[current_language])) { + close_popup = true; + auto friend_id_str = std::to_string(frd.id()); + ImGui::SetClipboardText(friend_id_str.c_str()); + } + // If we have the same appid, activate the invite/join buttons + if (settings->get_local_game_id().AppID() == frd.appid()) { + // user clicked on "invite to game" + std::string translationInvite_tmp(translationInvite[current_language]); + translationInvite_tmp.append("##PopupInviteToGame"); + if (i_have_lobby && ImGui::Button(translationInvite_tmp.c_str())) { + close_popup = true; + state.window_state |= window_state_invite; + has_friend_action.push(frd); + } + + // user clicked on "accept game invite" + std::string translationJoin_tmp(translationJoin[current_language]); + translationJoin_tmp.append("##PopupAcceptInvite"); + if (state.joinable && ImGui::Button(translationJoin_tmp.c_str())) { + close_popup = true; + // don't bother adding this friend if the button "invite all" was clicked + // we will send them the invitation later in Steam_Overlay::steam_run_callback() + if (!invite_all_friends_clicked) { + state.window_state |= window_state_join; + has_friend_action.push(frd); + } + } + } + + if (close_popup || invite_all_friends_clicked) { + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } +} + +void Steam_Overlay::build_friend_window(Friend const& frd, friend_window_state& state) +{ + if (!(state.window_state & window_state_show)) + return; + + bool show = true; + bool send_chat_msg = false; + + float width = ImGui::CalcTextSize("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").x; + + if (state.window_state & window_state_need_attention && ImGui::IsWindowFocused()) { + state.window_state &= ~window_state_need_attention; + } + ImGui::SetNextWindowSizeConstraints(ImVec2{ width, ImGui::GetFontSize()*8 + ImGui::GetFrameHeightWithSpacing()*4 }, + ImVec2{ std::numeric_limits::max() , std::numeric_limits::max() }); + + ImGui::SetNextWindowBgAlpha(1.0f); + // Window id is after the ###, the window title is the friend name + std::string friend_window_id = std::move("###" + std::to_string(state.id)); + if (ImGui::Begin((state.window_title + friend_window_id).c_str(), &show)) { + if (state.window_state & window_state_need_attention && ImGui::IsWindowFocused()) { + state.window_state &= ~window_state_need_attention; + } + + // Fill this with the chat box and maybe the invitation + if (state.window_state & (window_state_lobby_invite | window_state_rich_invite)) { + ImGui::LabelText("##label", translationInvitedYouToJoinTheGame[current_language], frd.name().c_str(), frd.appid()); + ImGui::SameLine(); + if (ImGui::Button(translationAccept[current_language])) { + state.window_state |= window_state_join; + this->has_friend_action.push(frd); + } + + ImGui::SameLine(); + if (ImGui::Button(translationRefuse[current_language])) { + state.window_state &= ~(window_state_lobby_invite | window_state_rich_invite); + } + } + + ImGui::InputTextMultiline("##chat_history", &state.chat_history[0], state.chat_history.length(), { -1.0f, -2.0f * ImGui::GetFontSize() }, ImGuiInputTextFlags_ReadOnly); + // TODO: Fix the layout of the chat line + send button. + // It should be like this: chat input should fill the window size minus send button size (button size is fixed) + // |------------------------------| + // | /--------------------------\ | + // | | | | + // | | chat history | | + // | | | | + // | \--------------------------/ | + // | [____chat line______] [send] | + // |------------------------------| + // + // And it is like this + // |------------------------------| + // | /--------------------------\ | + // | | | | + // | | chat history | | + // | | | | + // | \--------------------------/ | + // | [__chat line__] [send] | + // |------------------------------| + float wnd_width = ImGui::GetContentRegionAvail().x; + ImGuiStyle &style = ImGui::GetStyle(); + wnd_width -= ImGui::CalcTextSize(translationSend[current_language]).x + style.FramePadding.x * 2 + style.ItemSpacing.x + 1; + + uint64_t frd_id = frd.id(); + ImGui::PushID((const char *)&frd_id, (const char *)&frd_id + sizeof(frd_id)); + ImGui::PushItemWidth(wnd_width); + + if (ImGui::InputText("##chat_line", state.chat_input, max_chat_len, ImGuiInputTextFlags_EnterReturnsTrue)) { + send_chat_msg = true; + ImGui::SetKeyboardFocusHere(-1); + } + + ImGui::PopItemWidth(); + ImGui::PopID(); + + ImGui::SameLine(); + + if (ImGui::Button(translationSend[current_language])) { + send_chat_msg = true; + } + + if (send_chat_msg) { + if (!(state.window_state & window_state_send_message)) { + has_friend_action.push(frd); + state.window_state |= window_state_send_message; + } + } + } + + // User closed the friend window + if (!show) { + state.window_state &= ~window_state_show; + } + + ImGui::End(); +} + + +std::chrono::milliseconds Steam_Overlay::get_notification_duration(notification_type type) +{ + switch (type) + { + case notification_type::message: + return std::chrono::milliseconds(settings->overlay_appearance.notification_duration_chat); + + case notification_type::invite: + return std::chrono::milliseconds(settings->overlay_appearance.notification_duration_invitation); + + case notification_type::achievement: + return std::chrono::milliseconds(settings->overlay_appearance.notification_duration_achievement); + + case notification_type::achievement_progress: + return std::chrono::milliseconds(settings->overlay_appearance.notification_duration_progress); + + case notification_type::auto_accept_invite: + return Notification::default_show_time; + } + + PRINT_DEBUG("ERROR unhandled type %i", (int)type); + return Notification::default_show_time; +} + +// set the position of the next notification +void Steam_Overlay::set_next_notification_pos(std::pair scrn_size, std::chrono::milliseconds elapsed, std::chrono::milliseconds duration, const Notification ¬i, struct NotificationsCoords &coords) +{ + const float scrn_width = scrn_size.first; + const float scrn_height = scrn_size.second; + + auto &global_style = ImGui::GetStyle(); + const float padding_all_sides = 2 * (global_style.WindowPadding.y + global_style.WindowPadding.x); + + const float noti_width = scrn_width * Notification::width_percent; + const float msg_height = ImGui::CalcTextSize( + noti.message.c_str(), + noti.message.c_str() + noti.message.size(), + false, + noti_width - padding_all_sides - global_style.ItemSpacing.x + ).y; + float noti_height = msg_height; + + // get the required position + Overlay_Appearance::NotificationPosition pos = Overlay_Appearance::default_pos; + switch ((notification_type)noti.type) { + case notification_type::achievement_progress: + case notification_type::achievement: { + pos = settings->overlay_appearance.ach_earned_pos; + + const auto &ach = noti.ach.value(); + const float ach_text_width = noti_width - padding_all_sides - global_style.ItemSpacing.x - settings->overlay_appearance.icon_size; + ImGui::PushFont(font_ach_title); + float new_msg_height = ImGui::CalcTextSize( + ach.title.c_str(), + ach.title.c_str() + ach.title.size(), + false, + ach_text_width + ).y; + ImGui::PopFont(); + if (ach.description.size()) { + ImGui::PushFont(font_ach_desc); + new_msg_height += global_style.ItemSpacing.y + ImGui::CalcTextSize( + ach.description.c_str(), + ach.description.c_str() + ach.description.size(), + false, + ach_text_width + ).y; + ImGui::PopFont(); + } + const float new_noti_height = new_msg_height; + + float biggest_noti_height = settings->overlay_appearance.icon_size; + if (biggest_noti_height < new_noti_height) biggest_noti_height = new_noti_height; + + noti_height = biggest_noti_height; + + if ((notification_type)noti.type == notification_type::achievement_progress) { + if (!noti.ach.value().achieved && noti.ach.value().max_progress > 0) { + noti_height += settings->overlay_appearance.font_size + global_style.WindowPadding.y; + } + } + } + break; + + // case notification_type::invite: pos = settings->overlay_appearance.invite_pos; break; + case notification_type::invite: { + pos = settings->overlay_appearance.invite_pos; + const float msg_height = ImGui::CalcTextSize( + noti.message.c_str(), + noti.message.c_str() + noti.message.size(), + false, + noti_width - padding_all_sides - global_style.ItemSpacing.x + ).y; + noti_height = msg_height + settings->overlay_appearance.font_size + global_style.WindowPadding.y; + } + break; + case notification_type::message: pos = settings->overlay_appearance.chat_msg_pos; break; + default: PRINT_DEBUG("ERROR: unhandled notification type %i", (int)noti.type); break; + } + // add some y padding for niceness + noti_height += 2 * global_style.WindowPadding.y; + + // 0 on the y-axis is top, 0 on the x-axis is left + float x = 0.0f; + float y = 0.0f; + float animate_size = 0.0f; + const float margin_y = settings->overlay_appearance.notification_margin_y; + const float margin_x = settings->overlay_appearance.notification_margin_x; + + switch (pos) { + // top + case Overlay_Appearance::NotificationPosition::top_left: + animate_size = animate_factor(elapsed, duration) * noti_width; + x = margin_x - animate_size; + y = coords.top_left.second + margin_y; + coords.top_left.second = y + noti_height; + break; + case Overlay_Appearance::NotificationPosition::top_center: + animate_size = animate_factor(elapsed, duration) * noti_height; + x = (scrn_width / 2) - (noti_width / 2); + y = coords.top_center.second + margin_y - animate_size; + coords.top_center.second = y + noti_height; + break; + case Overlay_Appearance::NotificationPosition::top_right: + animate_size = animate_factor(elapsed, duration) * noti_width; + x = (scrn_width - noti_width - margin_x) + animate_size; + y = coords.top_right.second + margin_y; + coords.top_right.second = y + noti_height; + break; + + // bot + case Overlay_Appearance::NotificationPosition::bot_left: + animate_size = animate_factor(elapsed, duration) * noti_width; + x = margin_x - animate_size; + y = scrn_height - coords.bot_left.second - margin_y - noti_height; + coords.bot_left.second = scrn_height - y; + break; + case Overlay_Appearance::NotificationPosition::bot_center: + animate_size = animate_factor(elapsed, duration) * noti_height; + x = (scrn_width / 2) - (noti_width / 2); + y = scrn_height - coords.bot_center.second - margin_y - noti_height + animate_size; + coords.bot_center.second = scrn_height - y; + break; + case Overlay_Appearance::NotificationPosition::bot_right: + animate_size = animate_factor(elapsed, duration) * noti_width; + x = (scrn_width - noti_width - margin_x) + animate_size; + y = scrn_height - coords.bot_right.second - margin_y - noti_height; + coords.bot_right.second = scrn_height - y; + break; + + default: /* satisfy compiler warning */ break; + } + + ImGui::SetNextWindowPos(ImVec2( x, y )); + ImGui::SetNextWindowSize(ImVec2(noti_width, noti_height)); +} + +float Steam_Overlay::animate_factor(std::chrono::milliseconds elapsed, std::chrono::milliseconds duration) +{ + if (settings->overlay_appearance.notification_animation <= 0) return 0.0f; // no animation + + std::chrono::milliseconds animation_duration(settings->overlay_appearance.notification_animation); + // PRINT_DEBUG("ELAPSED %u/%u/%u", (uint32)elapsed.count(), (uint32)duration.count(), (uint32)animation_duration.count()); + + float factor = 0.0f; + if (elapsed < animation_duration) { // sliding in + factor = 1.0f - (static_cast(elapsed.count()) / animation_duration.count()); + // PRINT_DEBUG("SHOW FACTOR %f", factor); + } else { + // time between sliding in/out animation + // here we add the animation duration because we want to count after the animation + // if we have 1 sec animation & 2 sec show time: + // the duration will start at < 1 sec during the initial animation + // after the animation (1 sec), the duration will be >= 1 sec + // but since we want 2 sec show time, the duration must last 3 sec + auto steady_time = animation_duration + duration; + if (elapsed > steady_time) { + factor = static_cast((elapsed - steady_time).count()) / animation_duration.count(); + // PRINT_DEBUG("HIDE FACTOR %f", factor); + } + } + + return factor; +} + +void Steam_Overlay::add_ach_progressbar(const Overlay_Achievement &ach) +{ + if (!ach.achieved && ach.max_progress > 0) { + char buf[32]{}; + sprintf(buf, "%u/%u", ach.progress, ach.max_progress); + ImGui::ProgressBar((float)ach.progress / ach.max_progress, { -1 , settings->overlay_appearance.font_size }, buf); + } +} + +ImVec4 Steam_Overlay::get_notification_bg_rgba_safe() +{ + if (settings->overlay_appearance.notification_r >= 0 && + settings->overlay_appearance.notification_g >= 0 && + settings->overlay_appearance.notification_b >= 0 && + settings->overlay_appearance.notification_a >= 0) + { + return ImVec4( + settings->overlay_appearance.notification_r, + settings->overlay_appearance.notification_g, + settings->overlay_appearance.notification_b, + settings->overlay_appearance.notification_a + ); + } + + // fallback to dark-gray background + return ImVec4( + 0.12f, + 0.14f, + 0.21f, + 1.0f + ); +} + +void Steam_Overlay::build_notifications(float width, float height) +{ + auto now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + std::queue friend_actions_temp{}; + + ImGui::PushFont(font_notif); + // Add window rounding + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, settings->overlay_appearance.notification_rounding); + + NotificationsCoords coords{}; + for (auto it = notifications.begin(); it != notifications.end(); ++it) { + auto noti_duration = get_notification_duration((notification_type)it->type); + if (noti_duration.count() <= 0) { + it->expired = true; + continue; + } + + // *2 for sliding in & out animation + auto total_allowed_duration = noti_duration + std::chrono::milliseconds(settings->overlay_appearance.notification_animation * 2); + auto elapsed_notif = now - it->start_time; + if (elapsed_notif > total_allowed_duration) { + it->expired = true; + continue; + } + + float settings_noti_alpha = settings->overlay_appearance.notification_a >= 0.0f && settings->overlay_appearance.notification_a <= 1.0f + ? settings->overlay_appearance.notification_a + : 1.0f; + + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, settings_noti_alpha)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, get_notification_bg_rgba_safe()); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 255, 255, settings_noti_alpha * 2)); + + // some extra window flags for each notification type + ImGuiWindowFlags extra_flags = ImGuiWindowFlags_NoFocusOnAppearing; + switch ((notification_type)it->type) { + // games like "Mafia Definitive Edition" will pause the entire game/scene if focus was stolen + // be less intrusive for notifications that do not require interaction + case notification_type::achievement_progress: + case notification_type::achievement: + case notification_type::auto_accept_invite: + case notification_type::message: + extra_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoInputs; + break; + + case notification_type::invite: + // nothing + break; + + default: + PRINT_DEBUG("error unhandled flags for type %i", (int)it->type); + break; + } + + std::string wnd_name = "NotiPopupShow" + std::to_string(it->id); + + set_next_notification_pos({width, height}, elapsed_notif, noti_duration, *it, coords); + if (ImGui::Begin(wnd_name.c_str(), nullptr, + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize | extra_flags)) { + switch ((notification_type)it->type) { + case notification_type::achievement_progress: + case notification_type::achievement: { + const auto &ach = it->ach.value(); + auto &icon_rsrc = (notification_type)it->type == notification_type::achievement + ? ach.icon + : ach.icon_gray; + if (icon_rsrc->GetResourceId() != 0 && ImGui::BeginTable("imgui_table", 2)) { + ImGui::TableSetupColumn("imgui_table_image", ImGuiTableColumnFlags_WidthFixed, settings->overlay_appearance.icon_size); + ImGui::TableSetupColumn("imgui_table_text"); + ImGui::TableNextRow(ImGuiTableRowFlags_None, settings->overlay_appearance.icon_size); + + ImGui::TableSetColumnIndex(0); + ImGui::Image(icon_rsrc->GetResourceId(), ImVec2(settings->overlay_appearance.icon_size, settings->overlay_appearance.icon_size)); + + ImGui::TableSetColumnIndex(1); + ImGui::PushFont(font_ach_title); + ImGui::TextWrapped("%s", ach.title.c_str()); + ImGui::PopFont(); + if (ach.description.size()) { + ImGui::PushFont(font_ach_desc); + ImGui::TextWrapped("%s", ach.description.c_str()); + ImGui::PopFont(); + } + + ImGui::EndTable(); + } else { + ImGui::PushFont(font_ach_title); + ImGui::TextWrapped("%s", ach.title.c_str()); + ImGui::PopFont(); + if (ach.description.size()) { + ImGui::PushFont(font_ach_desc); + ImGui::TextWrapped("%s", ach.description.c_str()); + ImGui::PopFont(); + } + } + + if ((notification_type)it->type == notification_type::achievement_progress) { + add_ach_progressbar(ach); + } + } + break; + + case notification_type::invite: { + ImGui::TextWrapped("%s", it->message.c_str()); + if (ImGui::Button(translationJoin[current_language])) { + it->frd->second.window_state |= window_state_join; + 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 = {}; + } + } + break; + + case notification_type::message: + ImGui::TextWrapped("%s", it->message.c_str()); + break; + + case notification_type::auto_accept_invite: + ImGui::TextWrapped("%s", it->message.c_str()); + break; + + default: + PRINT_DEBUG("error unhandled notification for type %i", (int)it->type); + break; + } + + } + + ImGui::End(); + + ImGui::PopStyleColor(3); + } + + ImGui::PopStyleVar(); + ImGui::PopFont(); + + // erase all notifications whose visible time exceeded the max + notifications.erase(std::remove_if(notifications.begin(), notifications.end(), [this](const Notification &item) { + if (item.expired) { + PRINT_DEBUG("removing a notification"); + allow_renderer_frame_processing(false); + // uncomment this block to restore app input focus + switch ((notification_type)item.type) { + // we want to restore focus for these ones + case notification_type::invite: + obscure_game_input(false); + break; + + // not effective + case notification_type::achievement_progress: + case notification_type::achievement: + case notification_type::auto_accept_invite: + case notification_type::message: + // nothing + break; + + default: + PRINT_DEBUG("error unhandled remove for type %i", (int)item.type); + break; + } + + return true; + } + + return false; + }), notifications.end()); + + if (!friend_actions_temp.empty()) { + while (!friend_actions_temp.empty()) { + has_friend_action.push(friend_actions_temp.front()); + friend_actions_temp.pop(); + } + } +} + +void Steam_Overlay::add_auto_accept_invite_notification() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(overlay_mutex); + if (!Ready()) return; + + char tmp[TRANSLATION_BUFFER_SIZE]{}; + snprintf(tmp, sizeof(tmp), "%s", translationAutoAcceptFriendInvite[current_language]); + + submit_notification(notification_type::auto_accept_invite, tmp); + notify_sound_auto_accept_friend_invite(); +} + +void Steam_Overlay::add_invite_notification(std::pair& wnd_state) +{ + if (settings->disable_overlay_friend_notification) return; + + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(overlay_mutex); + if (!Ready()) return; + + char tmp[TRANSLATION_BUFFER_SIZE]{}; + auto &first_friend = wnd_state.first; + auto &name = first_friend.name(); + snprintf(tmp, sizeof(tmp), translationInvitedYouToJoinTheGame[current_language], name.c_str(), (uint64)first_friend.appid()); + + submit_notification(notification_type::invite, tmp, &wnd_state); +} + +void Steam_Overlay::post_achievement_notification(Overlay_Achievement &ach, bool for_progress) +{ + if (settings->disable_overlay_achievement_notification) return; + + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(overlay_mutex); + if (!Ready()) return; + + bool achieved = !for_progress; // for progress notifications we want to load the gray icon + // force upload to GPU if the pagination is request-based + try_load_ach_icon(ach, achieved, settings->paginated_achievements_icons == 0); + submit_notification( + for_progress ? notification_type::achievement_progress : notification_type::achievement, + ach.title + "\n" + ach.description, + {}, + &ach + ); +} + +bool Steam_Overlay::try_load_ach_icon(Overlay_Achievement &ach, bool achieved, bool upload_new_icon_to_gpu) +{ + if (!_renderer) return false; + if (settings->paginated_achievements_icons < 0) return false; // no icons are loaded anyway + if (!settings->overlay_upload_achs_icons_to_gpu) return false; // don't upload anything to the GPU + + auto &icon_rsrc = achieved ? ach.icon : ach.icon_gray; + if (icon_rsrc->GetResourceId() != 0) return true; + + // icons needs to be loaded, but we're not allowed + if (!upload_new_icon_to_gpu) return false; + + int &icon_handle = achieved ? ach.icon_handle : ach.icon_gray_handle; + if (Settings::UNLOADED_IMAGE_HANDLE == icon_handle) { // not loaded yet + icon_handle = get_steam_client()->steam_user_stats->get_achievement_icon_handle(ach.name, achieved); + } + auto image_info = settings->get_image(icon_handle); + if (image_info) { + int icon_size = static_cast(settings->overlay_appearance.icon_size); + //icon_rsrc->SetAutoLoad(InGameOverlay::ResourceAutoLoad_t::OnUse); + icon_rsrc->AttachResource((void*)image_info->data.c_str(), icon_size, icon_size); + + PRINT_DEBUG("'%s' (result=%i)", ach.name.c_str(), (int)icon_rsrc->GetResourceId() != 0); + } + + return icon_rsrc->GetResourceId() != 0; +} + +// Try to make this function as short as possible or it might affect game's fps. +void Steam_Overlay::overlay_render_proc() +{ + std::lock_guard lock(overlay_mutex); + + if (!Ready()) return; + + if (show_overlay) { + render_main_window(); + } + + if (notifications.size()) { + ImGuiIO &io = ImGui::GetIO(); + build_notifications(io.DisplaySize.x, io.DisplaySize.y); + } + + if (stats.show_any_stats()) { + stats.render_stats(current_language); + } + + load_next_ach_icon(); +} + +uint32 Steam_Overlay::apply_global_style_color() +{ + uint32 style_color_stack = 0; + if ((settings->overlay_appearance.background_r >= 0) && + (settings->overlay_appearance.background_g >= 0) && + (settings->overlay_appearance.background_b >= 0) && + (settings->overlay_appearance.background_a >= 0)) { + ImVec4 colorSet = ImVec4( + settings->overlay_appearance.background_r, + settings->overlay_appearance.background_g, + settings->overlay_appearance.background_b, + settings->overlay_appearance.background_a + ); + ImGui::PushStyleColor(ImGuiCol_WindowBg, colorSet); + style_color_stack += 1; + } + + if ((settings->overlay_appearance.element_r >= 0) && + (settings->overlay_appearance.element_g >= 0) && + (settings->overlay_appearance.element_b >= 0) && + (settings->overlay_appearance.element_a >= 0)) { + ImVec4 colorSet = ImVec4( + settings->overlay_appearance.element_r, + settings->overlay_appearance.element_g, + settings->overlay_appearance.element_b, + settings->overlay_appearance.element_a + ); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, colorSet); + ImGui::PushStyleColor(ImGuiCol_Button, colorSet); + ImGui::PushStyleColor(ImGuiCol_FrameBg, colorSet); + ImGui::PushStyleColor(ImGuiCol_ResizeGrip, colorSet); + style_color_stack += 4; + } + + if ((settings->overlay_appearance.element_hovered_r >= 0) && + (settings->overlay_appearance.element_hovered_g >= 0) && + (settings->overlay_appearance.element_hovered_b >= 0) && + (settings->overlay_appearance.element_hovered_a >= 0)) { + ImVec4 colorSet = ImVec4( + settings->overlay_appearance.element_hovered_r, + settings->overlay_appearance.element_hovered_g, + settings->overlay_appearance.element_hovered_b, + settings->overlay_appearance.element_hovered_a + ); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colorSet); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, colorSet); + ImGui::PushStyleColor(ImGuiCol_ResizeGripHovered, colorSet); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, colorSet); + style_color_stack += 4; + } + + if ((settings->overlay_appearance.element_active_r >= 0) && + (settings->overlay_appearance.element_active_g >= 0) && + (settings->overlay_appearance.element_active_b >= 0) && + (settings->overlay_appearance.element_active_a >= 0)) { + ImVec4 colorSet = ImVec4( + settings->overlay_appearance.element_active_r, + settings->overlay_appearance.element_active_g, + settings->overlay_appearance.element_active_b, + settings->overlay_appearance.element_active_a + ); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, colorSet); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, colorSet); + ImGui::PushStyleColor(ImGuiCol_ResizeGripActive, colorSet); + ImGui::PushStyleColor(ImGuiCol_Header, colorSet); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, colorSet); + style_color_stack += 5; + } + + return style_color_stack; +} + +// Try to make this function as short as possible or it might affect game's fps. +void Steam_Overlay::render_main_window() +{ + char tmp[TRANSLATION_BUFFER_SIZE]{}; + snprintf(tmp, sizeof(tmp), translationRenderer[current_language], (_renderer == nullptr ? "Unknown" : _renderer->GetLibraryName())); + std::string windowTitle{}; + // Note: don't translate this, project and author names are nouns, they must be kept intact for proper referral + // think of it as translating "Protobuf - Google" + windowTitle.append("Ingame Overlay project - Nemirtingas (").append(tmp).append(")"); + + bool show = true; + + ImGuiIO &io = ImGui::GetIO(); + + ImGui::PushFont(font_default); + uint32 style_color_stack = apply_global_style_color(); + + ImGui::SetNextWindowPos({ 0, 0 }); + ImGui::SetNextWindowSize({ io.DisplaySize.x, io.DisplaySize.y }); + if (ImGui::Begin(windowTitle.c_str(), &show, + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoBringToFrontOnFocus)) { + if (show_user_info) { + ImGui::LabelText("##playinglabel", translationUserPlaying[current_language], + settings->get_local_name(), + settings->get_local_steam_id().ConvertToUint64(), + settings->get_local_game_id().AppID()); + } + + ImGui::Spacing(); + + ImGui::SameLine(); + // user clicked on "toggle user info" + if (ImGui::Button(translationToggleUserInfo[current_language])) { + show_user_info = !show_user_info; + } + + ImGui::SameLine(); + // user clicked on "show achievements" + if (ImGui::Button(translationShowAchievements[current_language])) { + show_achievements = !show_achievements; + } + + ImGui::SameLine(); + // user clicked on "test achievement" + if (ImGui::Button(translationTestAchievement[current_language])) { + show_test_achievement(); + } + + ImGui::SameLine(); + // user clicked on "copy id" on themselves + if (ImGui::Button(translationCopyId[current_language])) { + auto friend_id_str = std::to_string(settings->get_local_steam_id().ConvertToUint64()); + ImGui::SetClipboardText(friend_id_str.c_str()); + } + + ImGui::SameLine(); + // user clicked on "settings" + if (ImGui::Button(translationSettings[current_language])) { + show_settings = !show_settings; + } + + ImGui::Spacing(); + ImGui::Spacing(); + // user clicked on "FPS" + ImGui::SameLine(); + if (ImGui::Checkbox(translationFpsCheckbox[current_language], &stats.show_fps)) { + allow_renderer_frame_processing(stats.show_fps); + } + // user clicked on "Frametime" + ImGui::SameLine(); + if (ImGui::Checkbox(translationFrametimeCheckbox[current_language], &stats.show_frametime)) { + allow_renderer_frame_processing(stats.show_frametime); + } + // user clicked on "Playtime" + ImGui::SameLine(); + if (ImGui::Checkbox(translationPlaytimeCheckbox[current_language], &stats.show_playtime)) { + allow_renderer_frame_processing(stats.show_playtime); + } + + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::LabelText("##label", "%s", translationFriends[current_language]); + + if (!friends.empty()) { + if (i_have_lobby) { + std::string inviteAll(translationInviteAll[current_language]); + inviteAll.append("##PopupInviteAllFriends"); + if (ImGui::Button(inviteAll.c_str())) { // if btn clicked + invite_all_friends_clicked = true; + } + } + + if (ImGuiHelper_BeginListBox("##label", static_cast(friends.size()))) { + std::for_each(friends.begin(), friends.end(), [this](std::pair &i) { + ImGui::PushID(i.second.id-base_friend_window_id+base_friend_item_id); + + ImGui::Selectable(i.second.window_title.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick); + build_friend_context_menu(i.first, i.second); + if (ImGui::IsItemClicked() && ImGui::IsMouseDoubleClicked(0)) { + i.second.window_state |= window_state_show; + } + + ImGui::PopID(); + + build_friend_window(i.first, i.second); + }); + ImGui::EndListBox(); + } + } + + // user clicked on "show achievements" button + if (show_achievements && achievements.size()) { + ImGui::SetNextWindowSizeConstraints(ImVec2(ImGui::GetFontSize() * 32, ImGui::GetFontSize() * 32), ImVec2(8192, 8192)); + ImGui::SetNextWindowBgAlpha(1.0f); + if (ImGui::Begin(translationAchievementWindow[current_language], &show_achievements)) { + ImGui::Text("%s", translationListOfAchievements[current_language]); + ImGui::BeginChild(translationAchievements[current_language]); + for (auto & x : achievements) { + bool achieved = x.achieved; + bool hidden = x.hidden && !achieved; + + // force upload to GPU if the pagination is request-based + try_load_ach_icon(x, true, settings->paginated_achievements_icons == 0); + try_load_ach_icon(x, false, settings->paginated_achievements_icons == 0); + + ImGui::Separator(); + + bool could_create_ach_table_entry = false; + if (x.icon->GetResourceId() != 0 || x.icon_gray->GetResourceId() != 0) { + if (ImGui::BeginTable(x.title.c_str(), 2)) { + could_create_ach_table_entry = true; + + ImGui::TableSetupColumn("imgui_table_image", ImGuiTableColumnFlags_WidthFixed, settings->overlay_appearance.icon_size); + ImGui::TableSetupColumn("imgui_table_text"); + ImGui::TableNextRow(ImGuiTableRowFlags_None, settings->overlay_appearance.icon_size); + + ImGui::TableSetColumnIndex(0); + auto &icon_rsrc = achieved ? x.icon : x.icon_gray; + if (icon_rsrc->GetResourceId() != 0) { + ImGui::Image( + icon_rsrc->GetResourceId(), + ImVec2(settings->overlay_appearance.icon_size, settings->overlay_appearance.icon_size) + ); + } + + ImGui::TableSetColumnIndex(1); + // the next column is the achievement text below + } + } + + // we want to display the ach text regardless the icons were displayed or not + ImGui::Text("%s", x.title.c_str()); + + if (hidden) { + ImGui::Text("%s", translationHiddenAchievement[current_language]); + } else { + ImGui::TextWrapped("%s", x.description.c_str()); + } + + if (achieved) { + char buffer[80]{}; + time_t unlock_time = (time_t)x.unlock_time; + size_t written = std::strftime(buffer, sizeof(buffer), settings->overlay_appearance.ach_unlock_datetime_format.c_str(), std::localtime(&unlock_time)); + if (!written) { // count was reached before the entire string could be stored, keep it safe + std::strftime(buffer, sizeof(buffer), "%Y/%m/%d - %H:%M:%S", std::localtime(&unlock_time)); + } + + ImGui::TextColored(ImVec4(0, 255, 0, 255), translationAchievedOn[current_language], buffer); + } else { + ImGui::TextColored(ImVec4(255, 0, 0, 255), "%s", translationNotAchieved[current_language]); + } + add_ach_progressbar(x); + + if (could_create_ach_table_entry) ImGui::EndTable(); + + ImGui::Separator(); + } + ImGui::EndChild(); + } + + ImGui::End(); + } + + // user clicked on "settings" button + if (show_settings) { + ImGui::SetNextWindowBgAlpha(1.0f); + if (ImGui::Begin(translationGlobalSettingsWindow[current_language], &show_settings)) { + ImGui::Text("%s", translationGlobalSettingsWindowDescription[current_language]); + + ImGui::Separator(); + + ImGui::Text("%s", translationUsername[current_language]); + ImGui::SameLine(); + ImGui::InputText("##username", username_text, sizeof(username_text), 0); + + ImGui::Separator(); + + ImGui::Text("%s", translationLanguage[current_language]); + ImGui::ListBox("##language", ¤t_language, valid_languages, sizeof(valid_languages) / sizeof(valid_languages[0]), 7); + ImGui::Text(translationSelectedLanguage[current_language], valid_languages[current_language]); + + ImGui::Separator(); + + ImGui::Text("%s", translationRestartTheGameToApply[current_language]); + if (ImGui::Button(translationSave[current_language])) { + save_settings = true; + show_settings = false; + } + } + + ImGui::End(); + } + + // we have a url to open/display + if (show_url.size()) { + std::string url = show_url; + bool show = true; + ImGui::SetNextWindowBgAlpha(1.0f); + if (ImGui::Begin(URL_WINDOW_NAME, &show)) { + ImGui::Text("%s", translationSteamOverlayURL[current_language]); + ImGui::Spacing(); + + ImGui::PushItemWidth(ImGui::CalcTextSize(url.c_str()).x + 20); + ImGui::InputText("##url_copy", (char *)url.data(), url.size(), ImGuiInputTextFlags_ReadOnly); + ImGui::PopItemWidth(); + + ImGui::Spacing(); + if (ImGui::Button(translationClose[current_language]) || !show) + show_url = ""; + // ImGui::SetWindowSize(ImVec2(ImGui::CalcTextSize(url.c_str()).x + 10, 0)); + } + + ImGui::End(); + } + + bool show_warning = warn_local_save || warn_bad_appid; + if (show_warning) { + ImGui::SetNextWindowSizeConstraints(ImVec2(ImGui::GetFontSize() * 32, ImGui::GetFontSize() * 32), ImVec2(8192, 8192)); + ImGui::SetNextWindowFocus(); + ImGui::SetNextWindowBgAlpha(1.0f); + if (ImGui::Begin(translationWarning[current_language], &show_warning)) { + if (warn_bad_appid) { + ImGui::TextColored(ImVec4(255, 0, 0, 255), + "%s %s %s", + translationWarning[current_language], translationWarning[current_language], translationWarning[current_language]); + ImGui::TextWrapped("%s", translationWarningDescription_badAppid[current_language]); + ImGui::TextColored(ImVec4(255, 0, 0, 255), + "%s %s %s", + translationWarning[current_language], translationWarning[current_language], translationWarning[current_language]); + } + if (warn_local_save) { + ImGui::TextColored(ImVec4(255, 0, 0, 255), + "%s %s %s", + translationWarning[current_language], translationWarning[current_language], translationWarning[current_language]); + ImGui::TextWrapped("%s", translationWarningDescription_localSave[current_language]); + ImGui::TextColored(ImVec4(255, 0, 0, 255), + "%s %s %s", + translationWarning[current_language], translationWarning[current_language], translationWarning[current_language]); + } + } + + ImGui::End(); + // if button closed, don't show the warning again + if (!show_warning) { + warn_local_save = false; + warn_bad_appid = false; + } + } + } + + ImGui::End(); + + if (style_color_stack) ImGui::PopStyleColor(style_color_stack); + ImGui::PopFont(); + + if (!show) { + ShowOverlay(false); + } + +} + +void Steam_Overlay::load_next_ach_icon() +{ + // this function only works when icons pagination is active, request-based loading is not supported too (pagination=0) + if (!settings->overlay_upload_achs_icons_to_gpu || settings->paginated_achievements_icons <= 0 || achievements.empty()) return; + + size_t linear_idx = last_loaded_ach_icon / 2; // 2 icons per achievement, 1 achieved, 1 unachieved + if (linear_idx >= achievements.size()) { + last_loaded_ach_icon = 0; + linear_idx = 0; + } + +#ifndef EMU_RELEASE_BUILD + auto now1 = std::chrono::high_resolution_clock::now(); +#endif + + auto &ach = achievements.at(linear_idx); + ++last_loaded_ach_icon; + + bool achieved = last_loaded_ach_icon % 2 != 0; + auto &icon_rsrc = achieved ? ach.icon : ach.icon_gray; + // always force upload to GPU in background-loading mode (pagination > 0) + bool loaded = try_load_ach_icon(ach, achieved, true); + +#ifndef EMU_RELEASE_BUILD + if (loaded) { + auto now2 = std::chrono::high_resolution_clock::now(); + auto dd = (unsigned)std::chrono::duration_cast(now2 - now1).count(); + PRINT_DEBUG("uploaded an achievement icon to GPU in %u ms", dd); + } +#endif + +} + +void Steam_Overlay::SetupOverlay() +{ + if (settings->disable_overlay) return; + + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(overlay_mutex); + + bool not_called_yet = false; + if (setup_overlay_called.compare_exchange_weak(not_called_yet, true)) { + if (settings->overlay_hook_delay_sec > 0) { + PRINT_DEBUG("waiting %i seconds", settings->overlay_hook_delay_sec); + renderer_detector_delay_thread.start(); + } else { + // "HITMAN 3" fails if the detector was started later (after a delay) + // so request the renderer detector immediately (the old behavior) + request_renderer_detector(); + set_renderer_hook_timeout(); + renderer_hook_init_thread.start(); + } + } +} + +void Steam_Overlay::UnSetupOverlay() +{ + if (settings->disable_overlay) return; + + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(overlay_mutex); + + bool already_called = true; + if (setup_overlay_called.compare_exchange_weak(already_called, false)) { + is_ready = false; + + renderer_hook_init_thread.kill(); + renderer_detector_delay_thread.kill(); + + // stop internal frame processing & restore cursor + if (_renderer) { + // for some reason this gets triggered after the overlay instance has been destroyed + // I assume because the game de-initializes DX later after closing Steam APIs + // this hacky solution just sets it to an empty function + _renderer->OverlayHookReady = [](InGameOverlay::OverlayHookState){}; + _renderer->OverlayProc = [](){}; + + allow_renderer_frame_processing(false, true); + obscure_game_input(false); + + PRINT_DEBUG("releasing any images resources"); + for (auto &ach : achievements) { + if (ach.icon->GetResourceId() != 0) { + ach.icon->Unload(); + } + + if (ach.icon_gray->GetResourceId() != 0) { + ach.icon_gray->Unload(); + } + } + + // manually calling this dtor looks bad, but it actually prevents a lot of crashes on exit, don't remove it! + // many DX12 games will crash on exit if the hook wasn't manually removed (ex appid 2933080, 1583230) + _renderer->~RendererHook_t(); + _renderer = nullptr; + } + + cleanup_renderer_hook(); + } + + PRINT_DEBUG("done *********"); +} + +bool Steam_Overlay::Ready() const +{ + return !settings->disable_overlay && is_ready && late_init_imgui; +} + +bool Steam_Overlay::NeedPresent() const +{ + PRINT_DEBUG_ENTRY(); + return !settings->disable_overlay; +} + +void Steam_Overlay::SetNotificationPosition(ENotificationPosition eNotificationPosition) +{ + if (settings->disable_overlay) return; + + PRINT_DEBUG("TODO %i", (int)eNotificationPosition); + std::lock_guard lock(overlay_mutex); + + notif_position = eNotificationPosition; +} + +void Steam_Overlay::SetNotificationInset(int nHorizontalInset, int nVerticalInset) +{ + if (settings->disable_overlay) return; + + PRINT_DEBUG("TODO x=%i y=%i", nHorizontalInset, nVerticalInset); + std::lock_guard lock(overlay_mutex); + + h_inset = nHorizontalInset; + v_inset = nVerticalInset; +} + +void Steam_Overlay::OpenOverlayInvite(CSteamID lobbyId) +{ + PRINT_DEBUG("TODO %llu", lobbyId.ConvertToUint64()); + std::lock_guard lock(overlay_mutex); + if (!Ready()) return; + + ShowOverlay(true); +} + +void Steam_Overlay::OpenOverlay(const char* pchDialog) +{ + PRINT_DEBUG("TODO '%s'", pchDialog); + std::lock_guard lock(overlay_mutex); + if (!Ready()) return; + + // TODO: Show pages depending on pchDialog + if ((strncmp(pchDialog, "Friends", sizeof("Friends") - 1) == 0) && (settings->overlayAutoAcceptInvitesCount() > 0)) { + PRINT_DEBUG("won't open overlay's friends list because some friends are defined in the auto accept list"); + add_auto_accept_invite_notification(); + } else { + ShowOverlay(true); + } +} + +void Steam_Overlay::OpenOverlayWebpage(const char* pchURL) +{ + PRINT_DEBUG("TODO '%s'", pchURL); + std::lock_guard lock(overlay_mutex); + if (!Ready()) return; + + show_url = pchURL; + ShowOverlay(true); +} + +bool Steam_Overlay::ShowOverlay() const +{ + return show_overlay; +} + +void Steam_Overlay::ShowOverlay(bool state) +{ + std::lock_guard lock(overlay_mutex); + if (!Ready() || show_overlay == state) return; + + show_overlay = state; + overlay_state_changed = true; + + PRINT_DEBUG("%i", (int)state); + + Steam_Overlay::allow_renderer_frame_processing(state); + Steam_Overlay::obscure_game_input(state); + +} + +void Steam_Overlay::SetLobbyInvite(Friend friendId, uint64 lobbyId) +{ + PRINT_DEBUG("%" PRIu64 " %llu", friendId.id(), lobbyId); + std::lock_guard lock(overlay_mutex); + if (!Ready()) return; + + auto i = friends.find(friendId); + if (i != friends.end()) + { + auto& frd = i->second; + frd.lobbyId = lobbyId; + frd.window_state |= window_state_lobby_invite; + // Make sure don't have rich presence invite and a lobby invite (it should not happen but who knows) + frd.window_state &= ~window_state_rich_invite; + add_invite_notification(*i); + notify_sound_user_invite(i->second); + } +} + +void Steam_Overlay::SetRichInvite(Friend friendId, const char* connect_str) +{ + PRINT_DEBUG("%" PRIu64 " '%s'", friendId.id(), connect_str); + std::lock_guard lock(overlay_mutex); + if (!Ready()) return; + + auto i = friends.find(friendId); + if (i != friends.end()) + { + auto& frd = i->second; + strncpy(frd.connect, connect_str, k_cchMaxRichPresenceValueLength - 1); + frd.window_state |= window_state_rich_invite; + // Make sure don't have rich presence invite and a lobby invite (it should not happen but who knows) + frd.window_state &= ~window_state_lobby_invite; + add_invite_notification(*i); + notify_sound_user_invite(i->second); + } +} + +void Steam_Overlay::FriendConnect(Friend _friend) +{ + if (settings->disable_overlay) return; + + PRINT_DEBUG("%" PRIu64 "", _friend.id()); + std::lock_guard lock(overlay_mutex); + + // players connections might happen earlier before the overlay is ready + // we don't want to miss them + //if (!Ready()) return; + + int id = find_free_friend_id(friends); + if (id != 0) { + auto& item = friends[_friend]; + item.window_title = std::move(_friend.name() + " " + translationPlaying[current_language] + " " + std::to_string(_friend.appid())); + item.window_state = window_state_none; + item.id = id; + memset(item.chat_input, 0, max_chat_len); + item.joinable = false; + } else { + PRINT_DEBUG("error no free id to create a friend window"); + } +} + +void Steam_Overlay::FriendDisconnect(Friend _friend) +{ + if (settings->disable_overlay) return; + + PRINT_DEBUG("%" PRIu64 "", _friend.id()); + std::lock_guard lock(overlay_mutex); + + // players connections might happen earlier before the overlay is ready + // we don't want to miss them + //if (!Ready()) return; + + auto it = friends.find(_friend); + if (it != friends.end()) + friends.erase(it); +} + +// show a notification when the user unlocks an achievement +void Steam_Overlay::AddAchievementNotification(const std::string &ach_name, nlohmann::json const &ach, bool for_progress) +{ + if (settings->disable_overlay) return; + + PRINT_DEBUG("'%s' %i", ach_name.c_str(), (int)for_progress); + std::lock_guard lock(overlay_mutex); + if (!Ready()) return; + + // don't return early when disable_overlay_achievement_notification is true + // otherwise when you open the achievements list/menu you won't see the new unlock status + + for (auto &a : achievements) { + if (a.name == ach_name) { + try { + // lock to prevent modifications to this json object + std::lock_guard lock2(global_mutex); + + a.achieved = ach.value("earned", false); + a.unlock_time = ach.value("earned_time", static_cast(0)); + a.progress = ach.value("progress", static_cast(0)); + a.max_progress = ach.value("max_progress", static_cast(0)); + } catch(...) {} + + if (a.achieved && !for_progress) { // here we don't show the progress indications + post_achievement_notification(a, for_progress); + notify_sound_user_achievement(); + } else if (for_progress && !settings->disable_overlay_achievement_progress) { // progress indication is shown for locked achievements only + // post notification if this isn't a progress, or a progress and the user didn't disable these notifications + post_achievement_notification(a, for_progress); + // don't play sound + } + break; + } + } +} + + + +// -- steam run callbacks -- +void Steam_Overlay::steam_run_callback_update_my_lobby() +{ + std::lock_guard lock(global_mutex); + Steam_Friends* steamFriends = get_steam_client()->steam_friends; + if (std::string(steamFriends->get_friend_rich_presence_silent(settings->get_local_steam_id(), "connect")).length() > 0) { + i_have_lobby = true; + } else if (settings->get_lobby().IsValid()) { + i_have_lobby = true; + } else { + i_have_lobby = false; + } +} + +bool Steam_Overlay::is_friend_joinable(std::pair &f) +{ + PRINT_DEBUG("%" PRIu64 "", f.first.id()); + std::lock_guard lock(global_mutex); + Steam_Friends* steamFriends = get_steam_client()->steam_friends; + + if (std::string(steamFriends->get_friend_rich_presence_silent((uint64)f.first.id(), "connect")).length() > 0 ) { + PRINT_DEBUG("%" PRIu64 " true (connect string)", f.first.id()); + return true; + } + + FriendGameInfo_t friend_game_info{}; + steamFriends->GetFriendGamePlayed((uint64)f.first.id(), &friend_game_info); + if (friend_game_info.m_steamIDLobby.IsValid() && (f.second.window_state & window_state_lobby_invite)) { + PRINT_DEBUG("%" PRIu64 " true (friend in a game)", f.first.id()); + return true; + } + + PRINT_DEBUG("%" PRIu64 " false", f.first.id()); + return false; +} + +void Steam_Overlay::invite_friend(uint64 friend_id, class Steam_Friends* steamFriends, class Steam_Matchmaking* steamMatchmaking) +{ + std::string connect_str = steamFriends->get_friend_rich_presence_silent(settings->get_local_steam_id(), "connect"); + if (connect_str.length() > 0) { + steamFriends->InviteUserToGame(friend_id, connect_str.c_str()); + PRINT_DEBUG("sent game invitation to friend with id = %llu", friend_id); + } else if (settings->get_lobby().IsValid()) { + steamMatchmaking->InviteUserToLobby(settings->get_lobby(), friend_id); + PRINT_DEBUG("sent lobby invitation to friend with id = %llu", friend_id); + } +} + +void Steam_Overlay::steam_run_callback_friends_actions() +{ + Steam_Friends* steamFriends = get_steam_client()->steam_friends; + Steam_Matchmaking* steamMatchmaking = get_steam_client()->steam_matchmaking; + + std::for_each(friends.begin(), friends.end(), [this](std::pair &i) { + i.second.joinable = is_friend_joinable(i); + }); + + while (!has_friend_action.empty()) { + auto friend_info = friends.find(has_friend_action.front()); + if (friend_info != friends.end()) { + uint64 friend_id = (uint64)friend_info->first.id(); + // The user clicked on "Send" + if (friend_info->second.window_state & window_state_send_message) { + char* input = friend_info->second.chat_input; + char* end_input = input + strlen(input); + char* printable_char = std::find_if(input, end_input, [](char c) { return std::isgraph(c); }); + + // Check if the message contains something else than blanks + if (printable_char != end_input) { + // Handle chat send + Common_Message msg; + Steam_Messages* steam_messages = new Steam_Messages; + steam_messages->set_type(Steam_Messages::FRIEND_CHAT); + steam_messages->set_message(friend_info->second.chat_input); + msg.set_allocated_steam_messages(steam_messages); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(friend_id); + network->sendTo(&msg, true); + + friend_info->second.chat_history.append(get_steam_client()->settings_client->get_local_name()).append(": ").append(input).append("\n", 1); + } + *input = 0; // Reset the input field + + friend_info->second.window_state &= ~window_state_send_message; + } + // The user clicked on "Invite" (but invite all wasn't clicked) + if (friend_info->second.window_state & window_state_invite) { + invite_friend(friend_id, steamFriends, steamMatchmaking); + + friend_info->second.window_state &= ~window_state_invite; + } + // The user clicked on "Join" + if (friend_info->second.window_state & window_state_join) { + std::string connect = steamFriends->get_friend_rich_presence_silent(friend_id, "connect"); + // The user got a lobby invite and accepted it + if (friend_info->second.window_state & window_state_lobby_invite) { + GameLobbyJoinRequested_t data; + data.m_steamIDLobby.SetFromUint64(friend_info->second.lobbyId); + data.m_steamIDFriend.SetFromUint64(friend_id); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + friend_info->second.window_state &= ~window_state_lobby_invite; + } else { + // The user got a rich presence invite and accepted it + if (friend_info->second.window_state & window_state_rich_invite) { + GameRichPresenceJoinRequested_t data = {}; + data.m_steamIDFriend.SetFromUint64(friend_id); + strncpy(data.m_rgchConnect, friend_info->second.connect, k_cchMaxRichPresenceValueLength - 1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + friend_info->second.window_state &= ~window_state_rich_invite; + } else if (connect.length() > 0) { + GameRichPresenceJoinRequested_t data = {}; + data.m_steamIDFriend.SetFromUint64(friend_id); + strncpy(data.m_rgchConnect, connect.c_str(), k_cchMaxRichPresenceValueLength - 1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + + //Not sure about this but it fixes sonic racing transformed invites + FriendGameInfo_t friend_game_info = {}; + steamFriends->GetFriendGamePlayed(friend_id, &friend_game_info); + uint64 lobby_id = friend_game_info.m_steamIDLobby.ConvertToUint64(); + if (lobby_id) { + GameLobbyJoinRequested_t data; + data.m_steamIDLobby.SetFromUint64(lobby_id); + data.m_steamIDFriend.SetFromUint64(friend_id); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + } + + friend_info->second.window_state &= ~window_state_join; + } + } + has_friend_action.pop(); + } + +} + +void Steam_Overlay::steam_run_callback() +{ + if (!Ready()) return; + + if (overlay_state_changed) { + overlay_state_changed = false; + + GameOverlayActivated_t data{}; + data.m_bActive = show_overlay; + data.m_bUserInitiated = true; + data.m_dwOverlayPID = 123; + data.m_nAppID = settings->get_local_game_id().AppID(); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + + Steam_Friends* steamFriends = get_steam_client()->steam_friends; + Steam_Matchmaking* steamMatchmaking = get_steam_client()->steam_matchmaking; + + if (save_settings) { + save_settings = false; + + const char *language_text = valid_languages[current_language]; + save_global_settings(get_steam_client()->local_storage, username_text, language_text); + get_steam_client()->settings_client->set_local_name(username_text); + get_steam_client()->settings_server->set_local_name(username_text); + get_steam_client()->settings_client->set_language(language_text); + get_steam_client()->settings_server->set_language(language_text); + steamFriends->resend_friend_data(); + } + + steam_run_callback_update_my_lobby(); + + // if variable == true, then set it to false and return true (because state was changed) in that case + bool yes_clicked = true; + if (invite_all_friends_clicked.compare_exchange_weak(yes_clicked, false)) { + PRINT_DEBUG("Steam_Overlay will send invitations to [%zu] friends if they're using the same app", friends.size()); + uint32 current_appid = settings->get_local_game_id().AppID(); + for (auto &fr : friends) { + if (fr.first.appid() == current_appid) { // friend is playing the same game + uint64 friend_id = (uint64)fr.first.id(); + invite_friend(friend_id, steamFriends, steamMatchmaking); + } + } + } + + // don't wait to lock the overlay mutex + // * the overlay proc might be active and holding the overlay mutex + // * this steam callback will be blocked, but it has the global mutex locked + // * the overlay proc tries to lock the global mutex, but since we have it, it will be blocked forever + if (overlay_mutex.try_lock()) { + if (Ready()) { + // ============================================================== + // call steam callbacks that has to change the overlay state here + // ============================================================== + + steam_run_callback_friends_actions(); + } + + overlay_mutex.unlock(); + } +} + + + +// -- steam networking callbacks -- +void Steam_Overlay::networking_msg_received(Common_Message *msg) +{ + if (msg->has_steam_messages()) { + std::lock_guard lock(overlay_mutex); + + Friend frd; + frd.set_id(msg->source_id()); + auto friend_info = friends.find(frd); + if (friend_info != friends.end()) { + Steam_Messages const& steam_message = msg->steam_messages(); + // Change color to cyan for friend + friend_info->second.chat_history.append(friend_info->first.name() + ": " + steam_message.message()).append("\n", 1); + if (!(friend_info->second.window_state & window_state_show)) { + friend_info->second.window_state |= window_state_need_attention; + } + + add_chat_message_notification(friend_info->first.name() + ": " + steam_message.message()); + notify_sound_user_invite(friend_info->second); + } + } +} + + +#endif diff --git a/overlay_experimental/steam_overlay_stats.cpp b/overlay_experimental/steam_overlay_stats.cpp index 018f64858..34b79c359 100644 --- a/overlay_experimental/steam_overlay_stats.cpp +++ b/overlay_experimental/steam_overlay_stats.cpp @@ -1,3 +1,63 @@ +#include "overlay/steam_overlay_stats.h" +// translation +#include "overlay/steam_overlay_translations.h" +#include + + +Steam_Overlay_Stats::Steam_Overlay_Stats(class Settings* settings): + settings(settings) +{ + show_fps = settings->overlay_always_show_fps; + show_frametime = settings->overlay_always_show_frametime; + show_playtime = settings->overlay_always_show_playtime; +} + +bool Steam_Overlay_Stats::show_any_stats() const +{ + return show_fps || show_frametime || show_playtime; +} + +void Steam_Overlay_Stats::update_frametime(const std::chrono::high_resolution_clock::time_point &now) +{ + running_frametime_ms += static_cast( + std::chrono::duration_cast(now - last_frame_timepoint).count() + ); + last_frame_timepoint = now; + if (last_frametime_idx >= (settings->overlay_fps_avg_window - 1)) { + last_frametime_idx = 0; + active_frametime_ms = static_cast(running_frametime_ms) / settings->overlay_fps_avg_window; + if (running_frametime_ms > 0) { + active_fps = static_cast((1000 * settings->overlay_fps_avg_window) / running_frametime_ms); + } else { // happens when avg window =1, no idea why! + active_fps = 999; + } + running_frametime_ms = 0; + } else { + ++last_frametime_idx; + } +} + +void Steam_Overlay_Stats::update_playtime(const std::chrono::high_resolution_clock::time_point &now) +{ + const auto update_duration_sec = std::chrono::duration_cast( + now - last_playtime + ).count(); + if (update_duration_sec < 1) return; + + last_playtime = now; + + const auto time_duration_sec = (unsigned long long)std::chrono::duration_cast( + now - initial_time + ).count(); + active_playtime_sec = static_cast(time_duration_sec % 60); + + const auto time_duration_min = time_duration_sec / 60; + active_playtime_min = static_cast(time_duration_min % 60); + + const auto time_duration_hr = time_duration_min / 60; + active_playtime_hr = static_cast(time_duration_hr % 24); +} + void Steam_Overlay_Stats::render_stats(int current_language) { auto now = std::chrono::high_resolution_clock::now(); @@ -8,6 +68,8 @@ void Steam_Overlay_Stats::render_stats(int current_language) update_playtime(now); } + ImGui::PushFont(font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, settings->overlay_appearance.notification_rounding); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); @@ -23,7 +85,7 @@ void Steam_Overlay_Stats::render_stats(int current_language) settings->overlay_appearance.stats_text_b, settings->overlay_appearance.stats_text_a )); - + std::stringstream stats_txt_buff{}; if (show_fps) { stats_txt_buff << translationFpsDisplay[current_language] @@ -79,12 +141,11 @@ void Steam_Overlay_Stats::render_stats(int current_language) ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollWithMouse)) { - ImGui::PushFont(font); ImGui::TextWrapped("%s", stats_txt.c_str()); - ImGui::PopFont(); } ImGui::End(); ImGui::PopStyleColor(2); ImGui::PopStyleVar(2); + ImGui::PopFont(); } diff --git a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini index 31e03fceb..f9eba333f 100644 --- a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini @@ -92,6 +92,10 @@ Font_Override=Roboto-Medium.ttf # for built-in font, multiple of 16 is recommended. e.g. 16 32... # default=16.0 Font_Size=20.0 +# optional independent font sizes. <= 0 uses Font_Size +Font_Size_FPS=0.0 +Font_Size_Achievement_Title=0.0 +Font_Size_Achievement_Description=0.0 # achievement icon size Icon_Size=64.0 From 23f1f406461ab389ae4755096d74b85058a936df Mon Sep 17 00:00:00 2001 From: dasafe <48550353+dasafe@users.noreply.github.com> Date: Tue, 19 May 2026 13:22:13 +0200 Subject: [PATCH 130/159] Add font override options for achievement title and description --- dll/dll/settings.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dll/dll/settings.h b/dll/dll/settings.h index 4ec72eda0..5954c2a7a 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -128,10 +128,13 @@ struct Overlay_Appearance { constexpr const static NotificationPosition default_pos = NotificationPosition::top_right; std::string font_override{}; // path to a custom user-provided TTF font + std::string font_override_ach_title{}; // path to custom font for achievement title + std::string font_override_ach_desc{}; // path to custom font for achievement description float font_size = 16.0f; float font_size_fps = 0.0f; float font_size_ach_title = 0.0f; float font_size_ach_desc = 0.0f; + bool font_ach_title_bold = false; float icon_size = 64.0f; From 1330d54d7844a25d678e69a763fecbde8980e1d0 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 19 May 2026 15:26:16 +0200 Subject: [PATCH 131/159] Add parser for Font_Override_Achievement_Title and Font_Override_Achievement_Description settings --- dll/settings_parser.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 481423c2b..93f9b18e6 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -256,6 +256,44 @@ static void load_overlay_appearance(class Settings *settings_client, class Setti } else { PRINT_DEBUG(" ERROR font file '%s' doesn't exist and will be ignored", value.c_str()); } + } else if (name.compare("Font_Override_Achievement_Title") == 0) { + value = common_helpers::string_strip(value); + std::string nfont_override(common_helpers::to_absolute(value, Local_Storage::get_game_settings_path() + "fonts")); + if (!common_helpers::file_exist(nfont_override)) { + nfont_override.clear(); + } + if (nfont_override.empty()) { + nfont_override = common_helpers::to_absolute(value, local_storage->get_global_settings_path() + "fonts"); + if (!common_helpers::file_exist(nfont_override)) { + nfont_override.clear(); + } + } + if (nfont_override.size()) { + settings_client->overlay_appearance.font_override_ach_title = nfont_override; + settings_server->overlay_appearance.font_override_ach_title = nfont_override; + PRINT_DEBUG(" loaded font '%s'", nfont_override.c_str()); + } else { + PRINT_DEBUG(" ERROR font file '%s' doesn't exist and will be ignored", value.c_str()); + } + } else if (name.compare("Font_Override_Achievement_Description") == 0) { + value = common_helpers::string_strip(value); + std::string nfont_override(common_helpers::to_absolute(value, Local_Storage::get_game_settings_path() + "fonts")); + if (!common_helpers::file_exist(nfont_override)) { + nfont_override.clear(); + } + if (nfont_override.empty()) { + nfont_override = common_helpers::to_absolute(value, local_storage->get_global_settings_path() + "fonts"); + if (!common_helpers::file_exist(nfont_override)) { + nfont_override.clear(); + } + } + if (nfont_override.size()) { + settings_client->overlay_appearance.font_override_ach_desc = nfont_override; + settings_server->overlay_appearance.font_override_ach_desc = nfont_override; + PRINT_DEBUG(" loaded font '%s'", nfont_override.c_str()); + } else { + PRINT_DEBUG(" ERROR font file '%s' doesn't exist and will be ignored", value.c_str()); + } } else if (name.compare("Font_Size") == 0) { float nfont_size = std::stof(value, NULL); settings_client->overlay_appearance.font_size = nfont_size; From 011181f63c4eb9b4f43ba685c240e6878a0cb320 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 19 May 2026 15:41:56 +0200 Subject: [PATCH 132/159] Fix achievement title/description to use their specific font override settings --- overlay_experimental/steam_overlay.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 484db932d..27f12e3f4 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -359,13 +359,14 @@ void Steam_Overlay::create_fonts() font_builder.BuildRanges(&ranges); font_cfg.GlyphRanges = ranges.Data; - auto add_overlay_font = [this](float size) { + auto add_overlay_font = [this](float size, const std::string &custom_font = "") { font_cfg.SizePixels = size; font_cfg.MergeMode = false; + const std::string &font_path = custom_font.empty() ? settings->overlay_appearance.font_override : custom_font; ImFont *font = nullptr; - if (settings->overlay_appearance.font_override.size()) { - font = fonts_atlas.AddFontFromFileTTF(settings->overlay_appearance.font_override.c_str(), size, &font_cfg); + if (font_path.size()) { + font = fonts_atlas.AddFontFromFileTTF(font_path.c_str(), size, &font_cfg); if (font) { font_cfg.MergeMode = true; // merge next font into the custom font } @@ -378,8 +379,8 @@ void Steam_Overlay::create_fonts() font_notif = font_default = add_overlay_font(font_size); font_fps = add_overlay_font(font_size_fps); - font_ach_title = add_overlay_font(font_size_ach_title); - font_ach_desc = add_overlay_font(font_size_ach_desc); + font_ach_title = add_overlay_font(font_size_ach_title, settings->overlay_appearance.font_override_ach_title); + font_ach_desc = add_overlay_font(font_size_ach_desc, settings->overlay_appearance.font_override_ach_desc); stats.font = font_fps; bool res = fonts_atlas.IsBuilt(); From 31cca37d82fd837143b1128492fe4ca85ae959a2 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 20 May 2026 01:40:50 +0200 Subject: [PATCH 133/159] Add parsing for Font_Achievement_Title_Bold setting --- dll/settings_parser.cpp | 4 ++++ post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini | 3 +++ 2 files changed, 7 insertions(+) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 93f9b18e6..3a60e6df8 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -310,6 +310,10 @@ static void load_overlay_appearance(class Settings *settings_client, class Setti float nfont_size = std::stof(value, NULL); settings_client->overlay_appearance.font_size_ach_desc = nfont_size; settings_server->overlay_appearance.font_size_ach_desc = nfont_size; + } else if (name.compare("Font_Achievement_Title_Bold") == 0) { + bool bold = (std::stol(value, NULL) != 0); + settings_client->overlay_appearance.font_ach_title_bold = bold; + settings_server->overlay_appearance.font_ach_title_bold = bold; } else if (name.compare("Icon_Size") == 0) { float nicon_size = std::stof(value, NULL); settings_client->overlay_appearance.icon_size = nicon_size; diff --git a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini index f9eba333f..9d5cf51d8 100644 --- a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini @@ -96,6 +96,9 @@ Font_Size=20.0 Font_Size_FPS=0.0 Font_Size_Achievement_Title=0.0 Font_Size_Achievement_Description=0.0 +# 1=bold font for achievement title +# default=0 +Font_Achievement_Title_Bold=0 # achievement icon size Icon_Size=64.0 From 659a1f0f3deba7d3d8de909553bd82bfa033f73b Mon Sep 17 00:00:00 2001 From: David Date: Wed, 20 May 2026 01:29:34 +0200 Subject: [PATCH 134/159] Add missing font override settings to overlay example config - Add Font_Override_Achievement_Title - Add Font_Override_Achievement_Description --- .../steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini index 9d5cf51d8..acca7dccb 100644 --- a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini @@ -88,6 +88,12 @@ key_combo=shift + tab # if that wasn't found, it will be looked up inside the global folder "GSE Settings/settings/fonts" # default= Font_Override=Roboto-Medium.ttf +# custom font for achievement title (leave empty to use default font) +# default= +Font_Override_Achievement_Title= +# custom font for achievement description (leave empty to use default font) +# default= +Font_Override_Achievement_Description= # global font size # for built-in font, multiple of 16 is recommended. e.g. 16 32... # default=16.0 From a4e69e5694fa2e9f3282f6515da3f8358905c3a0 Mon Sep 17 00:00:00 2001 From: LuKeSt0rm Date: Sat, 23 May 2026 02:21:53 +0200 Subject: [PATCH 135/159] ManualDispatchFix --- dll/dll.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index c60e77d9d..daa6ccce5 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -1347,6 +1347,7 @@ STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSte { PRINT_DEBUG("%i %p", hSteamPipe, pCallbackMsg); Steam_Client *steam_client = get_steam_client(); + steam_client->RunCallbacks(true, true); if (!steam_client->IsServerInit()) { while(!server_cb.empty()) server_cb.pop(); } @@ -1560,8 +1561,6 @@ SteamMasterServerUpdater bool steamclient_get_callback(HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg) { SteamAPI_ManualDispatch_Init(); - Steam_Client *steam_client = get_steam_client(); - steam_client->RunCallbacks(true, true); return SteamAPI_ManualDispatch_GetNextCallback(hSteamPipe, pCallbackMsg); } From a8f7fa44fcba54f173be86ae3f911f33e5f97b20 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 20 May 2026 01:26:56 +0200 Subject: [PATCH 136/159] Add rate-limiting queue for achievement notifications - Add achievement_notification_delay_ms setting to Settings class - Parse Achievement_Notification_Delay from configs.overlay.ini (in seconds) - Implement rate-limiting queue with scheduled_show_time tracking - Queue processes achievements based on minimum delay between starts - Default delay is 0 (no delay/simultaneous display) Test cases verified: - Simultaneous burst with spacing - Trigger within delay window (throttled) - Trigger after delay window passed (immediate) - Default behavior (delay=0) - Long chain stacking - Mixed stacking with catch-up - Mass burst with 0 delay - Spaced out burst --- dll/dll/settings.h | 2 + dll/settings_parser.cpp | 10 +++ overlay_experimental/overlay/steam_overlay.h | 15 ++++ overlay_experimental/steam_overlay.cpp | 82 +++++++++++++++++-- .../configs.overlay.EXAMPLE.ini | 5 ++ 5 files changed, 105 insertions(+), 9 deletions(-) diff --git a/dll/dll/settings.h b/dll/dll/settings.h index 5954c2a7a..a997fd18b 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -379,6 +379,8 @@ class Settings { bool overlay_always_show_playtime = false; // keys used to toggle the overlay, default = Shift + Tab std::vector overlay_toggle_keys{}; + // minimum time interval between achievement notifications (in milliseconds) + int achievement_notification_delay_ms = 0; // free weekend bool free_weekend = false; diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 3a60e6df8..37856fcc4 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -1644,6 +1644,16 @@ static void parse_overlay_general_config(class Settings *settings_client, class settings_server->overlay_fps_avg_window = val; } } + + // Achievement notification delay (in seconds, converted to milliseconds) + { + auto val = ini.GetLongValue("overlay::general", "Achievement_Notification_Delay", -1); + if (val >= 0) { + settings_client->achievement_notification_delay_ms = static_cast(val * 1000); + settings_server->achievement_notification_delay_ms = static_cast(val * 1000); + PRINT_DEBUG("Setting achievement notification delay to %i ms", settings_client->achievement_notification_delay_ms); + } + } } diff --git a/overlay_experimental/overlay/steam_overlay.h b/overlay_experimental/overlay/steam_overlay.h index bff318d78..7bd2b8aa6 100644 --- a/overlay_experimental/overlay/steam_overlay.h +++ b/overlay_experimental/overlay/steam_overlay.h @@ -4,6 +4,7 @@ #include "dll/base.h" #include #include +#include #ifdef EMU_OVERLAY @@ -148,6 +149,16 @@ class Steam_Overlay // used when the button "Invite all" is clicked std::atomic invite_all_friends_clicked = false; + // Rate-limiting queue for achievement notifications + struct ScheduledAchievement { + Overlay_Achievement ach; + bool for_progress; + std::chrono::milliseconds trigger_time; // when the achievement was triggered + std::chrono::milliseconds scheduled_show_time; // when the notification should be shown + }; + std::deque achievement_queue{}; + std::chrono::milliseconds last_scheduled_show_time{}; // tracks the last scheduled show time for spacing + bool overlay_state_changed = false; std::atomic i_have_lobby = false; @@ -290,6 +301,9 @@ class Steam_Overlay void FriendDisconnect(Friend _friend); void AddAchievementNotification(const std::string &ach_name, nlohmann::json const& ach, bool for_progress); + + // Rate-limiting queue functions + void process_achievement_queue(); }; #else // EMU_OVERLAY @@ -324,6 +338,7 @@ class Steam_Overlay void FriendDisconnect(Friend _friend) {} void AddAchievementNotification(const std::string &ach_name, nlohmann::json const& ach, bool for_progress) {} + void process_achievement_queue() {} }; #endif // EMU_OVERLAY diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 27f12e3f4..e6c1f5da4 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -1319,16 +1319,77 @@ void Steam_Overlay::post_achievement_notification(Overlay_Achievement &ach, bool PRINT_DEBUG_ENTRY(); std::lock_guard lock(overlay_mutex); if (!Ready()) return; +// Get current time + auto now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + + // Calculate scheduled show time based on rate limiting + std::chrono::milliseconds scheduled_show_time; + int delay_ms = settings->achievement_notification_delay_ms; + + if (delay_ms <= 0) { + // No delay - show immediately + scheduled_show_time = now; + } else { + // Apply rate limiting: earliest show time is last_scheduled_show_time + delay + scheduled_show_time = std::max(now, last_scheduled_show_time + std::chrono::milliseconds(delay_ms)); + } + + // Create scheduled achievement entry + ScheduledAchievement scheduled_ach; + scheduled_ach.ach = ach; + scheduled_ach.for_progress = for_progress; + scheduled_ach.trigger_time = now; + scheduled_ach.scheduled_show_time = scheduled_show_time; + + // Add to queue + achievement_queue.push_back(scheduled_ach); + + // Update last scheduled show time for next item + last_scheduled_show_time = scheduled_show_time; + + PRINT_DEBUG("Achievement queued: '%s', scheduled for %lld ms, delay=%d ms", + ach.name.c_str(), (long long)scheduled_show_time.count(), delay_ms); +} + +void Steam_Overlay::process_achievement_queue() +{ + std::lock_guard lock(overlay_mutex); + if (!Ready()) return; + if (achievement_queue.empty()) return; + + // Get current time + auto now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + + // Process all ready achievements + while (!achievement_queue.empty()) { + auto& scheduled_ach = achievement_queue.front(); + + // Check if it's time to show this notification + if (scheduled_ach.scheduled_show_time <= now) { + // Show the notification + bool achieved = !scheduled_ach.for_progress; + // force upload to GPU if the pagination is request-based + try_load_ach_icon(scheduled_ach.ach, achieved, settings->paginated_achievements_icons == 0); + + submit_notification( + scheduled_ach.for_progress ? notification_type::achievement_progress : notification_type::achievement, + scheduled_ach.ach.title + "\n" + scheduled_ach.ach.description, + {}, + &scheduled_ach.ach + ); + + PRINT_DEBUG("Achievement shown: '%s' at %lld ms", + scheduled_ach.ach.name.c_str(), (long long)now.count()); + + // Remove from queue + achievement_queue.pop_front(); + } else { + // This achievement is not ready yet, and queue is ordered by scheduled time, + // so no more achievements will be ready either + break; + } + } - bool achieved = !for_progress; // for progress notifications we want to load the gray icon - // force upload to GPU if the pagination is request-based - try_load_ach_icon(ach, achieved, settings->paginated_achievements_icons == 0); - submit_notification( - for_progress ? notification_type::achievement_progress : notification_type::achievement, - ach.title + "\n" + ach.description, - {}, - &ach - ); } bool Steam_Overlay::try_load_ach_icon(Overlay_Achievement &ach, bool achieved, bool upload_new_icon_to_gpu) @@ -1366,6 +1427,9 @@ void Steam_Overlay::overlay_render_proc() if (!Ready()) return; + // Process achievement queue to show scheduled notifications + process_achievement_queue(); + if (show_overlay) { render_main_window(); } diff --git a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini index acca7dccb..41449419e 100644 --- a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini @@ -137,6 +137,11 @@ Notification_Duration_Invitation=8.0 # duration of chat message Notification_Duration_Chat=4.0 +# minimum time interval between achievement notifications (in seconds) +# set to 0 to show all achievements immediately without delay +# default=0 +Achievement_Notification_Delay=0 + # format for the achievement unlock date/time, limited to 79 characters # if the output formatted string exceeded this limit, the builtin format will be used # look for the format here: https://en.cppreference.com/w/cpp/chrono/c/strftime From d9bc554c8b8c5436db9c6559075839c9d97d6165 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 20 May 2026 02:00:22 +0200 Subject: [PATCH 137/159] Delay achievement sound to play when notification is shown --- overlay_experimental/steam_overlay.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index e6c1f5da4..16ba33b90 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -735,8 +735,7 @@ void Steam_Overlay::show_test_achievement() } post_achievement_notification(ach, for_progress); - // here we always play the sound for testing - notify_sound_user_achievement(); + // sound is now played when notification is actually shown (delayed with queue) } void Steam_Overlay::build_friend_context_menu(Friend const& frd, friend_window_state& state) @@ -1378,6 +1377,11 @@ void Steam_Overlay::process_achievement_queue() &scheduled_ach.ach ); + // Play sound when notification is actually shown (delayed with queue) + if (!scheduled_ach.for_progress) { + notify_sound_user_achievement(); + } + PRINT_DEBUG("Achievement shown: '%s' at %lld ms", scheduled_ach.ach.name.c_str(), (long long)now.count()); @@ -2093,7 +2097,7 @@ void Steam_Overlay::AddAchievementNotification(const std::string &ach_name, nloh if (a.achieved && !for_progress) { // here we don't show the progress indications post_achievement_notification(a, for_progress); - notify_sound_user_achievement(); + // sound is now played when notification is actually shown (delayed with queue) } else if (for_progress && !settings->disable_overlay_achievement_progress) { // progress indication is shown for locked achievements only // post notification if this isn't a progress, or a progress and the user didn't disable these notifications post_achievement_notification(a, for_progress); From c73ace906b0b07675ef56b0266fdc595d373fc36 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 20 May 2026 02:11:56 +0200 Subject: [PATCH 138/159] Add sound for progress notifications --- overlay_experimental/steam_overlay.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 16ba33b90..faac4eee3 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -1378,9 +1378,7 @@ void Steam_Overlay::process_achievement_queue() ); // Play sound when notification is actually shown (delayed with queue) - if (!scheduled_ach.for_progress) { - notify_sound_user_achievement(); - } + notify_sound_user_achievement(); PRINT_DEBUG("Achievement shown: '%s' at %lld ms", scheduled_ach.ach.name.c_str(), (long long)now.count()); From 5ff21432967978207e3159937bfba27d1e6b560e Mon Sep 17 00:00:00 2001 From: David Date: Wed, 20 May 2026 01:51:53 +0200 Subject: [PATCH 139/159] Fix: parse Achievement_Notification_Delay in load_overlay_appearance --- dll/settings_parser.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 37856fcc4..f64a68d18 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -377,6 +377,10 @@ static void load_overlay_appearance(class Settings *settings_client, class Setti } else if (name.compare("Achievement_Unlock_Datetime_Format") == 0) { settings_client->overlay_appearance.ach_unlock_datetime_format = value; settings_server->overlay_appearance.ach_unlock_datetime_format = value; + } else if (name.compare("Achievement_Notification_Delay") == 0) { + float delay_sec = std::stof(value, NULL); + settings_client->achievement_notification_delay_ms = static_cast(delay_sec * 1000.0f); + settings_server->achievement_notification_delay_ms = static_cast(delay_sec * 1000.0f); } else if (name.compare("Background_R") == 0) { float nbackground_r = std::stof(value, NULL); settings_client->overlay_appearance.background_r = nbackground_r; From 995099e001925c67b3242ef57b17c71622794568 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 20 May 2026 01:43:16 +0200 Subject: [PATCH 140/159] Add debug output for achievement delay debugging --- overlay_experimental/steam_overlay.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index faac4eee3..ebcf4b2ac 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -1325,6 +1325,8 @@ void Steam_Overlay::post_achievement_notification(Overlay_Achievement &ach, bool std::chrono::milliseconds scheduled_show_time; int delay_ms = settings->achievement_notification_delay_ms; + PRINT_DEBUG("Achievement delay: %d ms", delay_ms); + if (delay_ms <= 0) { // No delay - show immediately scheduled_show_time = now; @@ -1364,6 +1366,10 @@ void Steam_Overlay::process_achievement_queue() auto& scheduled_ach = achievement_queue.front(); // Check if it's time to show this notification + PRINT_DEBUG("Queue check: scheduled=%lld, now=%lld, diff=%lld", + (long long)scheduled_ach.scheduled_show_time.count(), + (long long)now.count(), + (long long)(now - scheduled_ach.scheduled_show_time).count()); if (scheduled_ach.scheduled_show_time <= now) { // Show the notification bool achieved = !scheduled_ach.for_progress; From c223a83f29565078ac35e708a8832c6da44d30d8 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 24 May 2026 00:14:00 +0200 Subject: [PATCH 141/159] Remove redundant Achievement_Notification_Delay parse in parse_overlay_general_config The Achievement_Notification_Delay setting is already parsed in load_overlay_appearance() (reading from [overlay::appearance] section using std::stof for proper float handling). The duplicate block in parse_overlay_general_config() used GetLongValue which truncates decimal values and read from the wrong INI section. Resolves review feedback from universal963 on PR #522. --- dll/settings_parser.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index f64a68d18..e761288b6 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -1649,16 +1649,6 @@ static void parse_overlay_general_config(class Settings *settings_client, class } } - // Achievement notification delay (in seconds, converted to milliseconds) - { - auto val = ini.GetLongValue("overlay::general", "Achievement_Notification_Delay", -1); - if (val >= 0) { - settings_client->achievement_notification_delay_ms = static_cast(val * 1000); - settings_server->achievement_notification_delay_ms = static_cast(val * 1000); - PRINT_DEBUG("Setting achievement notification delay to %i ms", settings_client->achievement_notification_delay_ms); - } - } - } // main::misc::steam_game_stats_reports_dir From ee618de32535260f69704c71ac97df4da700a561 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 26 May 2026 20:21:24 +0200 Subject: [PATCH 142/159] Split achievement window into sorted unlocked/locked sections --- overlay_experimental/steam_overlay.cpp | 50 ++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 27f12e3f4..9a18dbe2a 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -1572,11 +1572,27 @@ void Steam_Overlay::render_main_window() if (ImGui::Begin(translationAchievementWindow[current_language], &show_achievements)) { ImGui::Text("%s", translationListOfAchievements[current_language]); ImGui::BeginChild(translationAchievements[current_language]); - for (auto & x : achievements) { - bool achieved = x.achieved; - bool hidden = x.hidden && !achieved; - // force upload to GPU if the pagination is request-based + // Build sorted index lists: unlocked by time desc, locked in API order + std::vector unlocked_idx, locked_idx; + unlocked_idx.reserve(achievements.size()); + locked_idx.reserve(achievements.size()); + for (size_t i = 0; i < achievements.size(); ++i) { + if (achievements[i].achieved) + unlocked_idx.push_back(i); + else + locked_idx.push_back(i); + } + std::sort(unlocked_idx.begin(), unlocked_idx.end(), + [this](size_t a, size_t b) { + return achievements[a].unlock_time > achievements[b].unlock_time; + }); + + // Lambda to render a single achievement card + auto render_ach = [this](Overlay_Achievement &x) { + const bool achieved = x.achieved; + const bool hidden = x.hidden && !achieved; + try_load_ach_icon(x, true, settings->paginated_achievements_icons == 0); try_load_ach_icon(x, false, settings->paginated_achievements_icons == 0); @@ -1605,7 +1621,6 @@ void Steam_Overlay::render_main_window() } } - // we want to display the ach text regardless the icons were displayed or not ImGui::Text("%s", x.title.c_str()); if (hidden) { @@ -1618,7 +1633,7 @@ void Steam_Overlay::render_main_window() char buffer[80]{}; time_t unlock_time = (time_t)x.unlock_time; size_t written = std::strftime(buffer, sizeof(buffer), settings->overlay_appearance.ach_unlock_datetime_format.c_str(), std::localtime(&unlock_time)); - if (!written) { // count was reached before the entire string could be stored, keep it safe + if (!written) { std::strftime(buffer, sizeof(buffer), "%Y/%m/%d - %H:%M:%S", std::localtime(&unlock_time)); } @@ -1631,7 +1646,30 @@ void Steam_Overlay::render_main_window() if (could_create_ach_table_entry) ImGui::EndTable(); ImGui::Separator(); + }; + + // --- Unlocked section --- + if (ImGui::CollapsingHeader("Unlocked", ImGuiTreeNodeFlags_DefaultOpen)) { + if (unlocked_idx.empty()) { + ImGui::TextDisabled("No achievements unlocked yet"); + } else { + for (auto idx : unlocked_idx) { + render_ach(achievements[idx]); + } + } } + + // --- Locked section --- + if (ImGui::CollapsingHeader("Locked", ImGuiTreeNodeFlags_DefaultOpen)) { + if (locked_idx.empty()) { + ImGui::TextDisabled("All achievements unlocked!"); + } else { + for (auto idx : locked_idx) { + render_ach(achievements[idx]); + } + } + } + ImGui::EndChild(); } From 4df98d30ab605c1df13494f578da8b25f683aacc Mon Sep 17 00:00:00 2001 From: David Date: Fri, 22 May 2026 20:22:13 +0200 Subject: [PATCH 143/159] Fix 2 bugs in achievement list display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. std::localtime thread safety — replaced with localtime_s using caller-provided stack buffer. Eliminates redundant second call by reusing the same struct tm for both strftime calls. 2. Redundant try_load_ach_icon calls — changed from dual call (both colored and gray icons every frame) to single call matching the current achievement state. The other variant is loaded by the background pagination system or on state change via the notification path. --- overlay_experimental/steam_overlay.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 9a18dbe2a..2fbf19cc3 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -1593,8 +1593,9 @@ void Steam_Overlay::render_main_window() const bool achieved = x.achieved; const bool hidden = x.hidden && !achieved; - try_load_ach_icon(x, true, settings->paginated_achievements_icons == 0); - try_load_ach_icon(x, false, settings->paginated_achievements_icons == 0); + // Load only the icon matching the current state. + // The other variant is loaded by the background pagination or on state change. + try_load_ach_icon(x, achieved, settings->paginated_achievements_icons == 0); ImGui::Separator(); @@ -1632,9 +1633,11 @@ void Steam_Overlay::render_main_window() if (achieved) { char buffer[80]{}; time_t unlock_time = (time_t)x.unlock_time; - size_t written = std::strftime(buffer, sizeof(buffer), settings->overlay_appearance.ach_unlock_datetime_format.c_str(), std::localtime(&unlock_time)); + struct tm unlock_tm{}; + localtime_s(&unlock_tm, &unlock_time); + size_t written = std::strftime(buffer, sizeof(buffer), settings->overlay_appearance.ach_unlock_datetime_format.c_str(), &unlock_tm); if (!written) { - std::strftime(buffer, sizeof(buffer), "%Y/%m/%d - %H:%M:%S", std::localtime(&unlock_time)); + std::strftime(buffer, sizeof(buffer), "%Y/%m/%d - %H:%M:%S", &unlock_tm); } ImGui::TextColored(ImVec4(0, 255, 0, 255), translationAchievedOn[current_language], buffer); From bf020438c72ec16b6b38145cac815bdd304e2a14 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 24 May 2026 17:17:17 +0200 Subject: [PATCH 144/159] Fix localtime_s -> localtime_r for Linux cross-compilation --- overlay_experimental/steam_overlay.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 2fbf19cc3..3c337cce2 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -1634,7 +1634,11 @@ void Steam_Overlay::render_main_window() char buffer[80]{}; time_t unlock_time = (time_t)x.unlock_time; struct tm unlock_tm{}; +#ifdef _MSC_VER localtime_s(&unlock_tm, &unlock_time); +#else + localtime_r(&unlock_time, &unlock_tm); +#endif size_t written = std::strftime(buffer, sizeof(buffer), settings->overlay_appearance.ach_unlock_datetime_format.c_str(), &unlock_tm); if (!written) { std::strftime(buffer, sizeof(buffer), "%Y/%m/%d - %H:%M:%S", &unlock_tm); From 0d3b7de73d23c0b1f91f47a7d51018d5ff3f392d Mon Sep 17 00:00:00 2001 From: David Date: Tue, 26 May 2026 20:22:04 +0200 Subject: [PATCH 145/159] Add Show button for hidden achievement descriptions --- overlay_experimental/steam_overlay.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 3c337cce2..914d8fa99 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -1626,6 +1626,16 @@ void Steam_Overlay::render_main_window() if (hidden) { ImGui::Text("%s", translationHiddenAchievement[current_language]); + ImGui::SameLine(); + + ImGui::PushID(&x); + ImGui::SmallButton("Show"); + bool show = ImGui::IsItemActive(); + ImGui::PopID(); + + if (show) { + ImGui::TextWrapped("%s", x.description.c_str()); + } } else { ImGui::TextWrapped("%s", x.description.c_str()); } From 933f6c645336c46ad7525840cf4ec29d3caed691 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 26 May 2026 20:28:56 +0200 Subject: [PATCH 146/159] Add persistent notification history panel to overlay --- overlay_experimental/overlay/steam_overlay.h | 14 +++ overlay_experimental/steam_overlay.cpp | 110 ++++++++++++++++++- 2 files changed, 120 insertions(+), 4 deletions(-) diff --git a/overlay_experimental/overlay/steam_overlay.h b/overlay_experimental/overlay/steam_overlay.h index bff318d78..f217e141c 100644 --- a/overlay_experimental/overlay/steam_overlay.h +++ b/overlay_experimental/overlay/steam_overlay.h @@ -92,6 +92,14 @@ struct Notification std::optional ach{}; }; +// Lightweight archive entry -- no pointers, no GPU resources, safe to store long-term +struct NotificationHistoryEntry +{ + std::chrono::milliseconds timestamp{}; + uint8 type{}; + std::string message{}; +}; + // notification coordinates { x, y } struct NotificationsCoords { @@ -145,6 +153,12 @@ class Steam_Overlay // Callback infos std::queue has_friend_action{}; std::vector notifications{}; + static constexpr size_t MAX_NOTIFICATION_HISTORY = 50; + std::deque notification_history{}; + bool show_notification_history = false; + // Cache for pre-formatted history lines — avoids rebuilding every frame + std::vector notification_history_cache{}; + bool notification_history_cache_dirty = false; // used when the button "Invite all" is clicked std::atomic invite_all_friends_clicked = false; diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 27f12e3f4..e4ea33639 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -1269,6 +1269,26 @@ void Steam_Overlay::build_notifications(float width, float height) break; } + // Archive to notification history (lightweight copy, no pointers/GPU resources) + { + NotificationHistoryEntry entry{}; + // Use actual achievement unlock time when available, + // otherwise fall back to the notification display time. + if (item.ach.has_value() && item.ach->unlock_time > 0) { + entry.timestamp = std::chrono::milliseconds( + static_cast(item.ach->unlock_time) * 1000); + } else { + entry.timestamp = item.start_time; + } + entry.type = item.type; + entry.message = item.message; + if (notification_history.size() >= MAX_NOTIFICATION_HISTORY) { + notification_history.pop_front(); + } + notification_history.push_back(std::move(entry)); + notification_history_cache_dirty = true; + } + return true; } @@ -1516,6 +1536,12 @@ void Steam_Overlay::render_main_window() show_settings = !show_settings; } + ImGui::SameLine(); + // user clicked on "notification history" + if (ImGui::Button("History")) { + show_notification_history = !show_notification_history; + } + ImGui::Spacing(); ImGui::Spacing(); // user clicked on "FPS" @@ -1536,6 +1562,80 @@ void Steam_Overlay::render_main_window() ImGui::Spacing(); ImGui::Spacing(); + + // --- Notification history panel --- + if (show_notification_history) { + if (ImGui::SmallButton("Clear All")) { + notification_history.clear(); + notification_history_cache.clear(); + notification_history_cache_dirty = false; + } + ImGui::Separator(); + if (notification_history.empty()) { + ImGui::TextDisabled("No notifications yet"); + } else { + ImGui::BeginChild("##history_scroll", ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 10), true); + + // Rebuild cache only when history actually changes + if (notification_history_cache_dirty) { + notification_history_cache.clear(); + notification_history_cache.reserve(notification_history.size()); + + for (auto it = notification_history.rbegin(); it != notification_history.rend(); ++it) { + // Format timestamp HH:MM:SS in local timezone + const time_t total_sec = std::chrono::duration_cast(it->timestamp).count(); + struct tm local_tm_buf{}; +#ifdef _MSC_VER + localtime_s(&local_tm_buf, &total_sec); +#else + localtime_r(&total_sec, &local_tm_buf); +#endif + const auto hr = local_tm_buf.tm_hour; + const auto min = local_tm_buf.tm_min; + const auto sec = local_tm_buf.tm_sec; + + // Type label + const char *type_label = "?"; + switch ((notification_type)it->type) { + case notification_type::message: type_label = "Chat"; break; + case notification_type::invite: type_label = "Invite"; break; + case notification_type::achievement: type_label = "Achievement"; break; + case notification_type::achievement_progress: type_label = "Progress"; break; + case notification_type::auto_accept_invite: type_label = "Auto-Invite"; break; + } + + // For achievements the message contains "title\ndescription" + // Replace newline with inline separator for compact display + std::string display_msg = it->message; + if (it->type == static_cast(notification_type::achievement) || + it->type == static_cast(notification_type::achievement_progress)) { + size_t pos = display_msg.find('\n'); + if (pos != std::string::npos) { + display_msg.replace(pos, 1, " — "); + } + } + + std::string line = (std::ostringstream{} + << "[" << std::setw(2) << std::setfill('0') << hr << ":" + << std::setw(2) << std::setfill('0') << min << ":" + << std::setw(2) << std::setfill('0') << sec << "] " + << type_label << " " + << display_msg).str(); + + notification_history_cache.push_back(std::move(line)); + } + notification_history_cache_dirty = false; + } + + // Render from cache + for (const auto &line : notification_history_cache) { + ImGui::TextWrapped("%s", line.c_str()); + ImGui::Separator(); + } + ImGui::EndChild(); + } + } + ImGui::LabelText("##label", "%s", translationFriends[current_language]); if (!friends.empty()) { @@ -2010,11 +2110,10 @@ void Steam_Overlay::AddAchievementNotification(const std::string &ach_name, nloh PRINT_DEBUG("'%s' %i", ach_name.c_str(), (int)for_progress); std::lock_guard lock(overlay_mutex); - if (!Ready()) return; - - // don't return early when disable_overlay_achievement_notification is true - // otherwise when you open the achievements list/menu you won't see the new unlock status + // Phase 1: Update achievement data regardless of Ready() state. + // This keeps the achievement list accurate even if the overlay isn't + // fully initialized yet (e.g., during the startup window before late_init_imgui). for (auto &a : achievements) { if (a.name == ach_name) { try { @@ -2027,6 +2126,9 @@ void Steam_Overlay::AddAchievementNotification(const std::string &ach_name, nloh a.max_progress = ach.value("max_progress", static_cast(0)); } catch(...) {} + // Phase 2: Only show notification if overlay is ready + if (!Ready()) return; + if (a.achieved && !for_progress) { // here we don't show the progress indications post_achievement_notification(a, for_progress); notify_sound_user_achievement(); From eca581c4b2fe384bc2c62750625fbfbd649c2978 Mon Sep 17 00:00:00 2001 From: Detanup01 <91248446+Detanup01@users.noreply.github.com> Date: Thu, 28 May 2026 19:52:45 +0200 Subject: [PATCH 147/159] remove imgui Update detours, simpleini, utfcpp, stb resize2 --- libs/detours/SOURCE.txt | 2 +- libs/detours/disasm.cpp | 104 ++++- libs/imgui/SOURCE.txt | 29 -- libs/simpleini/ConvertUTF.c | 821 ++++++++++++++++++----------------- libs/simpleini/ConvertUTF.h | 202 ++++----- libs/simpleini/SOURCE.txt | 4 +- libs/simpleini/SimpleIni.h | 91 ++-- libs/stb/SOURCE.txt | 2 +- libs/stb/stb_image_resize2.h | 350 ++++++++------- libs/utfcpp/SOURCE.txt | 2 +- libs/utfcpp/utf8/checked.h | 6 +- libs/utfcpp/utf8/core.h | 160 +++++-- libs/utfcpp/utf8/cpp17.h | 2 +- libs/utfcpp/utf8/unchecked.h | 23 +- 14 files changed, 1036 insertions(+), 762 deletions(-) delete mode 100644 libs/imgui/SOURCE.txt diff --git a/libs/detours/SOURCE.txt b/libs/detours/SOURCE.txt index ed35d8d0b..dfb0fec70 100644 --- a/libs/detours/SOURCE.txt +++ b/libs/detours/SOURCE.txt @@ -2,7 +2,7 @@ https://github.com/microsoft/Detours -VERSION: https://github.com/microsoft/Detours/tree/9764cebcb1a75940e68fa83d6730ffaf0f669401 +VERSION: https://github.com/microsoft/Detours/tree/d644ce94e8c7f7f5a31591577c78134ea3ac1fae #### LICENSE diff --git a/libs/detours/disasm.cpp b/libs/detours/disasm.cpp index 3f84c9d4e..b791e2fe0 100644 --- a/libs/detours/disasm.cpp +++ b/libs/detours/disasm.cpp @@ -244,10 +244,12 @@ class CDetourDis #define ENTRY_CopyF6 { ENTRY_DataIgnored &CDetourDis::CopyF6 } #define ENTRY_CopyF7 { ENTRY_DataIgnored &CDetourDis::CopyF7 } #define ENTRY_CopyFF { ENTRY_DataIgnored &CDetourDis::CopyFF } +#define ENTRY_CopyC7 { ENTRY_DataIgnored &CDetourDis::CopyC7 } #define ENTRY_CopyVex2 { ENTRY_DataIgnored &CDetourDis::CopyVex2 } #define ENTRY_CopyVex3 { ENTRY_DataIgnored &CDetourDis::CopyVex3 } #define ENTRY_CopyEvex { ENTRY_DataIgnored &CDetourDis::CopyEvex } // 62, 3 byte payload, then normal with implied prefixes like vex #define ENTRY_CopyXop { ENTRY_DataIgnored &CDetourDis::CopyXop } // 0x8F ... POP /0 or AMD XOP +#define ENTRY_CopyRex2 { ENTRY_DataIgnored &CDetourDis::CopyRex2 } // 0xD5 Intel APX REX2 (x64 only) #define ENTRY_CopyBytesXop { 5, 5, 4, 0, 0, &CDetourDis::CopyBytes } // 0x8F xop1 xop2 opcode modrm #define ENTRY_CopyBytesXop1 { 6, 6, 4, 0, 0, &CDetourDis::CopyBytes } // 0x8F xop1 xop2 opcode modrm ... imm8 #define ENTRY_CopyBytesXop4 { 9, 9, 4, 0, 0, &CDetourDis::CopyBytes } // 0x8F xop1 xop2 opcode modrm ... imm32 @@ -276,12 +278,14 @@ class CDetourDis PBYTE CopyF6(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyF7(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyFF(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); + PBYTE CopyC7(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyVex2(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyVex3(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyVexCommon(BYTE m, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyVexEvexCommon(BYTE m, PBYTE pbDst, PBYTE pbSrc, BYTE p, BYTE fp16 = 0); PBYTE CopyEvex(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyXop(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); + PBYTE CopyRex2(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); // AMD64 only, Intel APX REX2 protected: static const COPYENTRY s_rceCopyTable[]; @@ -362,12 +366,6 @@ PBYTE CDetourDis::CopyBytes(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) { UINT nBytesFixed; - if (m_bVex || m_bEvex) - { - ASSERT(pEntry->nFlagBits == 0); - ASSERT(pEntry->nFixedSize == pEntry->nFixedSize16); - } - UINT const nModOffset = pEntry->nModOffset; UINT const nFlagBits = pEntry->nFlagBits; UINT const nFixedSize = pEntry->nFixedSize; @@ -382,6 +380,12 @@ PBYTE CDetourDis::CopyBytes(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) nBytesFixed = nFixedSize + ((nFlagBits & RAX) ? 4 : 0); } #endif + else if (m_bVex || m_bEvex) { + // VEX/EVEX pp field does not shrink immediates to 16-bit. + // This matters for EVEX MAP4 (APX) where entries like opcode 81 + // (Group 1 imm32) have nFixedSize=6 but nFixedSize16=4. + nBytesFixed = nFixedSize; + } else { nBytesFixed = m_bOperandOverride ? nFixedSize16 : nFixedSize; } @@ -658,8 +662,10 @@ PBYTE CDetourDis::CopyF6(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) { (void)pEntry; - // TEST BYTE /0 - if (0x00 == (0x38 & pbSrc[1])) { // reg(bits 543) of ModR/M == 0 + // TEST BYTE /0 and /1 (both encodings are TEST per Intel SDM Vol 2A Group 3 + // and AMD APM Vol 3; /1 is an undocumented alias of /0 historically but is + // now documented in both manuals). + if (0x00 == (0x30 & pbSrc[1])) { // reg(bits 543) of ModR/M == 000 or 001 static const COPYENTRY ce = /* f6 */ ENTRY_CopyBytes2Mod1; return (this->*ce.pfCopy)(&ce, pbDst, pbSrc); } @@ -678,8 +684,8 @@ PBYTE CDetourDis::CopyF7(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) { (void)pEntry; - // TEST WORD /0 - if (0x00 == (0x38 & pbSrc[1])) { // reg(bits 543) of ModR/M == 0 + // TEST WORD /0 and /1 (see CopyF6 for /1 rationale). + if (0x00 == (0x30 & pbSrc[1])) { // reg(bits 543) of ModR/M == 000 or 001 static const COPYENTRY ce = /* f7 */ ENTRY_CopyBytes2ModOperand; return (this->*ce.pfCopy)(&ce, pbDst, pbSrc); } @@ -745,6 +751,21 @@ PBYTE CDetourDis::CopyFF(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) return pbOut; } +PBYTE CDetourDis::CopyC7(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) +{ + (void)pEntry; + + // C7 /7 is XBEGIN rel32 (or rel16 with 66 prefix). + // It has a relative displacement that must be relocated like CALL/JMP. + if (0x38 == (0x38 & pbSrc[1])) { // reg(bits 543) of ModR/M == 111 + static const COPYENTRY ce = /* c7 /7 */ { 6, 4, 0, 2, 0, &CDetourDis::CopyBytes }; + return (this->*ce.pfCopy)(&ce, pbDst, pbSrc); + } + // MOV /0 r/m, imm16/32 + static const COPYENTRY ce = /* c7 /0 */ ENTRY_CopyBytes2ModOperand; + return (this->*ce.pfCopy)(&ce, pbDst, pbSrc); +} + PBYTE CDetourDis::CopyVexEvexCommon(BYTE m, PBYTE pbDst, PBYTE pbSrc, BYTE p, BYTE fp16) // m is first instead of last in the hopes of pbDst/pbSrc being // passed along efficiently in the registers they were already in. @@ -763,14 +784,23 @@ PBYTE CDetourDis::CopyVexEvexCommon(BYTE m, PBYTE pbDst, PBYTE pbSrc, BYTE p, BY REFCOPYENTRY pEntry; // see https://software.intel.com/content/www/us/en/develop/download/intel-avx512-fp16-architecture-specification.html + // EVEX maps (with FP16 and APX mmm-bit extension): + // mmm=001 (MAP1) and mmm=101 (MAP5) share the legacy 0F opcode structure, + // so per-opcode sizing must come from s_rceCopyTable0F (e.g. MAP5 C2 /r ib = VCMPPH/VCMPSH). + // mmm=010 (MAP2) and mmm=110 (MAP6) share the legacy 0F 38 structure (ModR/M, no imm). + // mmm=011 (MAP3) has the 0F 3A structure (ModR/M + imm8). + // mmm=100 (MAP4, APX) mirrors legacy MAP0 opcode structure (per-opcode sizing). switch (m | fp16) { default: return Invalid(&ceInvalid, pbDst, pbSrc); + case 5: // MAP5 (FP16) - opcode structure mirrors 0F (MAP1). case 1: pEntry = &s_rceCopyTable0F[pbSrc[0]]; return (this->*pEntry->pfCopy)(pEntry, pbDst, pbSrc); - case 5: // fallthrough - case 6: // fallthrough + case 6: // MAP6 (FP16) - opcode structure mirrors 0F 38 (MAP2). case 2: return CopyBytes(&ceF38, pbDst, pbSrc); case 3: return CopyBytes(&ceF3A, pbDst, pbSrc); + case 4: // MAP4 (APX) - promoted legacy MAP0 instructions. + pEntry = &s_rceCopyTable[pbSrc[0]]; + return (this->*pEntry->pfCopy)(pEntry, pbDst, pbSrc); } } @@ -862,11 +892,6 @@ PBYTE CDetourDis::CopyEvex(REFCOPYENTRY, PBYTE pbDst, PBYTE pbSrc) static const COPYENTRY ceInvalid = /* 62 */ ENTRY_Invalid; - // This could also be handled by default in CopyVexEvexCommon - // if 4u changed to 4|8. - if (p0 & 8u) - return Invalid(&ceInvalid, pbDst, pbSrc); - BYTE const p1 = pbSrc[2]; if ((p1 & 0x04) != 0x04) @@ -881,6 +906,10 @@ PBYTE CDetourDis::CopyEvex(REFCOPYENTRY, PBYTE pbDst, PBYTE pbSrc) m_bRaxOverride |= !!(p1 & 0x80); // w #endif + // P0 layout: R'(7) X(6) B3(5) R'4(4) B4(3) m(2) m(1) m(0) + // Bits [2:0] = map (1-7). Bit 3 = B4 (APX register extension, not a map bit). + // For FP16: bit 2 extends map (MAP5=101, MAP6=110). + // For APX: map=4 (100) uses bit 2 as part of mmm field. return CopyVexEvexCommon(p0 & 3u, pbDst + 4, pbSrc + 4, p1 & 3u, p0 & 4u); } @@ -918,6 +947,43 @@ pp is like VEX but only instructions with 0 are defined } } +PBYTE CDetourDis::CopyRex2(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) +// Intel APX REX2 prefix 0xD5 (64-bit mode only) +// Byte 0: D5 +// Byte 1: M(7) R4(6) X4(5) B4(4) W(3) R3(2) X3(1) B3(0) +// M: 0 = opcode from MAP0, 1 = opcode from MAP1 (no 0F escape needed) +// W: operand size override to 64-bit (same as REX.W) +{ + (void)pEntry; + + BYTE const payload = pbSrc[1]; + + if (payload & 0x08) { // W bit (bit 3) + m_bRaxOverride = TRUE; + } + + pbDst[0] = pbSrc[0]; + pbDst[1] = pbSrc[1]; + + PBYTE pbOut; + if (payload & 0x80) { // M bit (bit 7) - MAP1 + REFCOPYENTRY pEntry2 = &s_rceCopyTable0F[pbSrc[2]]; + pbOut = (this->*pEntry2->pfCopy)(pEntry2, pbDst + 2, pbSrc + 2); + } + else { // MAP0 + REFCOPYENTRY pEntry2 = &s_rceCopyTable[pbSrc[2]]; + pbOut = (this->*pEntry2->pfCopy)(pEntry2, pbDst + 2, pbSrc + 2); + } + + // JMPABS: REX2 with payload=0x00 (M=0, W=0, all ext bits 0) and opcode A1. + // This is an absolute 64-bit jump whose target is the 8-byte immediate. + if (payload == 0x00 && pbSrc[2] == 0xA1) { + *m_ppbTarget = *(UNALIGNED PBYTE*)&pbSrc[3]; + } + + return pbOut; +} + ////////////////////////////////////////////////////////////////////////////// // PBYTE CDetourDis::s_pbModuleBeg = NULL; @@ -1227,7 +1293,7 @@ const CDetourDis::COPYENTRY CDetourDis::s_rceCopyTable[] = /* C4 */ ENTRY_CopyVex3, // LES, VEX 3-byte opcodes. /* C5 */ ENTRY_CopyVex2, // LDS, VEX 2-byte opcodes. /* C6 */ ENTRY_CopyBytes2Mod1, // MOV - /* C7 */ ENTRY_CopyBytes2ModOperand, // MOV/0 XBEGIN/7 + /* C7 */ ENTRY_CopyC7, // MOV/0 XBEGIN/7 /* C8 */ ENTRY_CopyBytes4, // ENTER /* C9 */ ENTRY_CopyBytes1, // LEAVE /* CA */ ENTRY_CopyBytes3Dynamic, // RET @@ -1246,7 +1312,7 @@ const CDetourDis::COPYENTRY CDetourDis::s_rceCopyTable[] = /* D3 */ ENTRY_CopyBytes2Mod, // RCL/2, etc. #ifdef DETOURS_X64 /* D4 */ ENTRY_Invalid, // Invalid - /* D5 */ ENTRY_Invalid, // Invalid + /* D5 */ ENTRY_CopyRex2, // REX2 (Intel APX) #else /* D4 */ ENTRY_CopyBytes2, // AAM /* D5 */ ENTRY_CopyBytes2, // AAD diff --git a/libs/imgui/SOURCE.txt b/libs/imgui/SOURCE.txt deleted file mode 100644 index f9c06b621..000000000 --- a/libs/imgui/SOURCE.txt +++ /dev/null @@ -1,29 +0,0 @@ -#### INFO - -https://github.com/ocornut/imgui - -VERSION: ???? - -#### LICENSE - -The MIT License (MIT) - -Copyright (c) 2014-2024 Omar Cornut - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/libs/simpleini/ConvertUTF.c b/libs/simpleini/ConvertUTF.c index 9eaf933a6..de6bf13ee 100644 --- a/libs/simpleini/ConvertUTF.c +++ b/libs/simpleini/ConvertUTF.c @@ -1,8 +1,10 @@ /* + * https://web.archive.org/web/20090529064329/http://www.unicode.org:80/Public/PROGRAMS/CVTUTF/ + * * Copyright 2001-2004 Unicode, Inc. - * + * * Disclaimer - * + * * This source code is provided as is by Unicode, Inc. No claims are * made as to fitness for any particular purpose. No warranties of any * kind are expressed or implied. The recipient agrees to determine @@ -10,9 +12,9 @@ * purchased on magnetic or optical media from Unicode, Inc., the * sole remedy for any claim will be exchange of defective media * within 90 days of receipt. - * + * * Limitations on Rights to Redistribute This Code - * + * * Unicode, Inc. hereby grants the right to freely use the information * supplied in this file in the creation of products supporting the * Unicode Standard, and to make copies of this file in any form @@ -20,23 +22,23 @@ * remains attached. */ -/* --------------------------------------------------------------------- + /* --------------------------------------------------------------------- - Conversions between UTF32, UTF-16, and UTF-8. Source code file. - Author: Mark E. Davis, 1994. - Rev History: Rick McGowan, fixes & updates May 2001. - Sept 2001: fixed const & error conditions per - mods suggested by S. Parent & A. Lillich. - June 2002: Tim Dodd added detection and handling of incomplete - source sequences, enhanced error detection, added casts - to eliminate compiler warnings. - July 2003: slight mods to back out aggressive FFFE detection. - Jan 2004: updated switches in from-UTF8 conversions. - Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. + Conversions between UTF32, UTF-16, and UTF-8. Source code file. + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Sept 2001: fixed const & error conditions per + mods suggested by S. Parent & A. Lillich. + June 2002: Tim Dodd added detection and handling of incomplete + source sequences, enhanced error detection, added casts + to eliminate compiler warnings. + July 2003: slight mods to back out aggressive FFFE detection. + Jan 2004: updated switches in from-UTF8 conversions. + Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. - See the header file "ConvertUTF.h" for complete documentation. + See the header file "ConvertUTF.h" for complete documentation. ------------------------------------------------------------------------- */ + ------------------------------------------------------------------------ */ #include "ConvertUTF.h" @@ -44,7 +46,7 @@ #include #endif -static const int halfShift = 10; /* used for shifting by 10 bits */ +static const int halfShift = 10; /* used for shifting by 10 bits */ static const UTF32 halfBase = 0x0010000UL; static const UTF32 halfMask = 0x3FFUL; @@ -58,108 +60,116 @@ static const UTF32 halfMask = 0x3FFUL; /* --------------------------------------------------------------------- */ -ConversionResult ConvertUTF32toUTF16 ( - const UTF32** sourceStart, const UTF32* sourceEnd, +ConversionResult ConvertUTF32toUTF16( + const UTF32** sourceStart, const UTF32* sourceEnd, UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF32* source = *sourceStart; - UTF16* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - if (target >= targetEnd) { - result = targetExhausted; break; - } - ch = *source++; - if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ - /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - *target++ = (UTF16)ch; /* normal case */ - } - } else if (ch > UNI_MAX_LEGAL_UTF32) { - if (flags == strictConversion) { - result = sourceIllegal; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - /* target is a character in range 0xFFFF - 0x10FFFF. */ - if (target + 1 >= targetEnd) { - --source; /* Back up source pointer! */ - result = targetExhausted; break; - } - ch -= halfBase; - *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); - *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + if (target >= targetEnd) { + result = targetExhausted; break; + } + ch = *source++; + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } + else { + *target++ = (UTF16)ch; /* normal case */ + } + } + else if (ch > UNI_MAX_LEGAL_UTF32) { + if (flags == strictConversion) { + result = sourceIllegal; + } + else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } + else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + --source; /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } } - } - *sourceStart = source; - *targetStart = target; - return result; + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- */ -ConversionResult ConvertUTF16toUTF32 ( - const UTF16** sourceStart, const UTF16* sourceEnd, +ConversionResult ConvertUTF16toUTF32( + const UTF16** sourceStart, const UTF16* sourceEnd, UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF16* source = *sourceStart; - UTF32* target = *targetStart; - UTF32 ch, ch2; - while (source < sourceEnd) { - const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ - ch = *source++; - /* If we have a surrogate pair, convert to UTF32 first. */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { - /* If the 16 bits following the high surrogate are in the source buffer... */ - if (source < sourceEnd) { - ch2 = *source; - /* If it's a low surrogate, convert to UTF32. */ - if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { - ch = ((ch - UNI_SUR_HIGH_START) << halfShift) - + (ch2 - UNI_SUR_LOW_START) + halfBase; - ++source; - } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } else { /* We don't have the 16 bits following the high surrogate. */ - --source; /* return to the high surrogate */ - result = sourceExhausted; - break; - } - } else if (flags == strictConversion) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - if (target >= targetEnd) { - source = oldSource; /* Back up source pointer! */ - result = targetExhausted; break; + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF32* target = *targetStart; + UTF32 ch, ch2; + while (source < sourceEnd) { + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } + else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } + else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + if (target >= targetEnd) { + source = oldSource; /* Back up source pointer! */ + result = targetExhausted; break; + } + *target++ = ch; } - *target++ = ch; - } - *sourceStart = source; - *targetStart = target; + *sourceStart = source; + *targetStart = target; #ifdef CVTUTF_DEBUG -if (result == sourceIllegal) { - fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); - fflush(stderr); -} + if (result == sourceIllegal) { + fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); + fflush(stderr); + } #endif - return result; + return result; } /* --------------------------------------------------------------------- */ @@ -172,14 +182,14 @@ if (result == sourceIllegal) { * allowed in earlier algorithms. */ static const char trailingBytesForUTF8[256] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 }; /* @@ -187,14 +197,14 @@ static const char trailingBytesForUTF8[256] = { * This table contains as many values as there might be trailing bytes * in a UTF-8 sequence. */ -static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, - 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; +static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; /* * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed * into the first byte, depending on how many bytes follow. There are * as many entries in this table as there are UTF-8 sequence types. - * (I.e., one byte sequence, two byte... etc.). Remember that sequences + * (I.e., one byte sequence, two byte... etc.). Remember that sequencs * for *legal* UTF-8 will be 4 or fewer bytes total. */ static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; @@ -209,74 +219,86 @@ static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC * into an inline function. */ -/* --------------------------------------------------------------------- */ + /* --------------------------------------------------------------------- */ -ConversionResult ConvertUTF16toUTF8 ( - const UTF16** sourceStart, const UTF16* sourceEnd, +ConversionResult ConvertUTF16toUTF8( + const UTF16** sourceStart, const UTF16* sourceEnd, UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF16* source = *sourceStart; - UTF8* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - unsigned short bytesToWrite = 0; - const UTF32 byteMask = 0xBF; - const UTF32 byteMark = 0x80; - const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ - ch = *source++; - /* If we have a surrogate pair, convert to UTF32 first. */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { - /* If the 16 bits following the high surrogate are in the source buffer... */ - if (source < sourceEnd) { - UTF32 ch2 = *source; - /* If it's a low surrogate, convert to UTF32. */ - if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { - ch = ((ch - UNI_SUR_HIGH_START) << halfShift) - + (ch2 - UNI_SUR_LOW_START) + halfBase; - ++source; - } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } else { /* We don't have the 16 bits following the high surrogate. */ - --source; /* return to the high surrogate */ - result = sourceExhausted; - break; - } - } else if (flags == strictConversion) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - /* Figure out how many bytes the result will require */ - if (ch < (UTF32)0x80) { bytesToWrite = 1; - } else if (ch < (UTF32)0x800) { bytesToWrite = 2; - } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; - } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; - } else { bytesToWrite = 3; - ch = UNI_REPLACEMENT_CHAR; - } + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + UTF32 ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } + else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } + else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* Figure out how many bytes the result will require */ + if (ch < (UTF32)0x80) { + bytesToWrite = 1; + } + else if (ch < (UTF32)0x800) { + bytesToWrite = 2; + } + else if (ch < (UTF32)0x10000) { + bytesToWrite = 3; + } + else if (ch < (UTF32)0x110000) { + bytesToWrite = 4; + } + else { + bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + } - target += bytesToWrite; - if (target > targetEnd) { - source = oldSource; /* Back up source pointer! */ - target -= bytesToWrite; result = targetExhausted; break; - } - switch (bytesToWrite) { /* note: everything falls through. */ - case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + target += bytesToWrite; + if (target > targetEnd) { + source = oldSource; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; } - target += bytesToWrite; - } - *sourceStart = source; - *targetStart = target; - return result; + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- */ @@ -292,29 +314,29 @@ ConversionResult ConvertUTF16toUTF8 ( * definition of UTF-8 goes up to 4-byte sequences. */ -static Boolean isLegalUTF8(const UTF8 *source, int length) { - UTF8 a; - const UTF8 *srcptr = source+length; - switch (length) { - default: return false; - /* Everything else falls through when "true"... */ - case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 2: if ((a = (*--srcptr)) > 0xBF) return false; - - switch (*source) { - /* no fall-through in this inner switch */ - case 0xE0: if (a < 0xA0) return false; break; - case 0xED: if (a > 0x9F) return false; break; - case 0xF0: if (a < 0x90) return false; break; - case 0xF4: if (a > 0x8F) return false; break; - default: if (a < 0x80) return false; - } +static Boolean isLegalUTF8(const UTF8* source, int length) { + UTF8 a; + const UTF8* srcptr = source + length; + switch (length) { + default: return false; + /* Everything else falls through when "true"... */ + case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 2: if ((a = (*--srcptr)) > 0xBF) return false; + + switch (*source) { + /* no fall-through in this inner switch */ + case 0xE0: if (a < 0xA0) return false; break; + case 0xED: if (a > 0x9F) return false; break; + case 0xF0: if (a < 0x90) return false; break; + case 0xF4: if (a > 0x8F) return false; break; + default: if (a < 0x80) return false; + } - case 1: if (*source >= 0x80 && *source < 0xC2) return false; - } - if (*source > 0xF4) return false; - return true; + case 1: if (*source >= 0x80 && *source < 0xC2) return false; + } + if (*source > 0xF4) return false; + return true; } /* --------------------------------------------------------------------- */ @@ -323,217 +345,234 @@ static Boolean isLegalUTF8(const UTF8 *source, int length) { * Exported function to return whether a UTF-8 sequence is legal or not. * This is not used here; it's just exported. */ -Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { - int length = trailingBytesForUTF8[*source]+1; - if (source+length > sourceEnd) { - return false; - } - return isLegalUTF8(source, length); +Boolean isLegalUTF8Sequence(const UTF8* source, const UTF8* sourceEnd) { + int length = trailingBytesForUTF8[*source] + 1; + if (source + length > sourceEnd) { + return false; + } + return isLegalUTF8(source, length); } /* --------------------------------------------------------------------- */ -ConversionResult ConvertUTF8toUTF16 ( - const UTF8** sourceStart, const UTF8* sourceEnd, +ConversionResult ConvertUTF8toUTF16( + const UTF8** sourceStart, const UTF8* sourceEnd, UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF8* source = *sourceStart; - UTF16* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch = 0; - unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; - if (source + extraBytesToRead >= sourceEnd) { - result = sourceExhausted; break; - } - /* Do this check whether lenient or strict */ - if (! isLegalUTF8(source, extraBytesToRead+1)) { - result = sourceIllegal; - break; - } - /* - * The cases all fall through. See "Note A" below. - */ - switch (extraBytesToRead) { - case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ - case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[extraBytesToRead]; + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (!isLegalUTF8(source, extraBytesToRead + 1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; - if (target >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - source -= (extraBytesToRead+1); /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - *target++ = (UTF16)ch; /* normal case */ - } - } else if (ch > UNI_MAX_UTF16) { - if (flags == strictConversion) { - result = sourceIllegal; - source -= (extraBytesToRead+1); /* return to the start */ - break; /* Bail out; shouldn't continue */ - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - /* target is a character in range 0xFFFF - 0x10FFFF. */ - if (target + 1 >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - ch -= halfBase; - *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); - *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + if (target >= targetEnd) { + source -= (extraBytesToRead + 1); /* Back up source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead + 1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } + else { + *target++ = (UTF16)ch; /* normal case */ + } + } + else if (ch > UNI_MAX_UTF16) { + if (flags == strictConversion) { + result = sourceIllegal; + source -= (extraBytesToRead + 1); /* return to the start */ + break; /* Bail out; shouldn't continue */ + } + else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } + else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + source -= (extraBytesToRead + 1); /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } } - } - *sourceStart = source; - *targetStart = target; - return result; + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- */ -ConversionResult ConvertUTF32toUTF8 ( - const UTF32** sourceStart, const UTF32* sourceEnd, +ConversionResult ConvertUTF32toUTF8( + const UTF32** sourceStart, const UTF32* sourceEnd, UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF32* source = *sourceStart; - UTF8* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - unsigned short bytesToWrite = 0; - const UTF32 byteMask = 0xBF; - const UTF32 byteMark = 0x80; - ch = *source++; - if (flags == strictConversion ) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - /* - * Figure out how many bytes the result will require. Turn any - * illegally large UTF32 things (> Plane 17) into replacement chars. - */ - if (ch < (UTF32)0x80) { bytesToWrite = 1; - } else if (ch < (UTF32)0x800) { bytesToWrite = 2; - } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; - } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; - } else { bytesToWrite = 3; - ch = UNI_REPLACEMENT_CHAR; - result = sourceIllegal; - } - - target += bytesToWrite; - if (target > targetEnd) { - --source; /* Back up source pointer! */ - target -= bytesToWrite; result = targetExhausted; break; - } - switch (bytesToWrite) { /* note: everything falls through. */ - case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + ch = *source++; + if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* + * Figure out how many bytes the result will require. Turn any + * illegally large UTF32 things (> Plane 17) into replacement chars. + */ + if (ch < (UTF32)0x80) { + bytesToWrite = 1; + } + else if (ch < (UTF32)0x800) { + bytesToWrite = 2; + } + else if (ch < (UTF32)0x10000) { + bytesToWrite = 3; + } + else if (ch <= UNI_MAX_LEGAL_UTF32) { + bytesToWrite = 4; + } + else { + bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + result = sourceIllegal; + } + + target += bytesToWrite; + if (target > targetEnd) { + --source; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; } - target += bytesToWrite; - } - *sourceStart = source; - *targetStart = target; - return result; + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- */ -ConversionResult ConvertUTF8toUTF32 ( - const UTF8** sourceStart, const UTF8* sourceEnd, +ConversionResult ConvertUTF8toUTF32( + const UTF8** sourceStart, const UTF8* sourceEnd, UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF8* source = *sourceStart; - UTF32* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch = 0; - unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; - if (source + extraBytesToRead >= sourceEnd) { - result = sourceExhausted; break; - } - /* Do this check whether lenient or strict */ - if (! isLegalUTF8(source, extraBytesToRead+1)) { - result = sourceIllegal; - break; - } - /* - * The cases all fall through. See "Note A" below. - */ - switch (extraBytesToRead) { - case 5: ch += *source++; ch <<= 6; - case 4: ch += *source++; ch <<= 6; - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[extraBytesToRead]; + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF32* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (!isLegalUTF8(source, extraBytesToRead + 1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; + case 4: ch += *source++; ch <<= 6; + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; - if (target >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up the source pointer! */ - result = targetExhausted; break; - } - if (ch <= UNI_MAX_LEGAL_UTF32) { - /* - * UTF-16 surrogate values are illegal in UTF-32, and anything - * over Plane 17 (> 0x10FFFF) is illegal. - */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - source -= (extraBytesToRead+1); /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; + if (target >= targetEnd) { + source -= (extraBytesToRead + 1); /* Back up the source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_LEGAL_UTF32) { + /* + * UTF-16 surrogate values are illegal in UTF-32, and anything + * over Plane 17 (> 0x10FFFF) is illegal. + */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead + 1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } + else { + *target++ = ch; + } + } + else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ + result = sourceIllegal; + *target++ = UNI_REPLACEMENT_CHAR; } - } else { - *target++ = ch; - } - } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ - result = sourceIllegal; - *target++ = UNI_REPLACEMENT_CHAR; } - } - *sourceStart = source; - *targetStart = target; - return result; + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- - Note A. - The fall-through switches in UTF-8 reading code save a - temp variable, some decrements & conditionals. The switches - are equivalent to the following loop: + Note A. + The fall-through switches in UTF-8 reading code save a + temp variable, some decrements & conditionals. The switches + are equivalent to the following loop: { - int tmpBytesToRead = extraBytesToRead+1; - do { + int tmpBytesToRead = extraBytesToRead+1; + do { ch += *source++; --tmpBytesToRead; if (tmpBytesToRead) ch <<= 6; - } while (tmpBytesToRead > 0); + } while (tmpBytesToRead > 0); } - In UTF-8 writing code, the switches on "bytesToWrite" are - similarly unrolled loops. + In UTF-8 writing code, the switches on "bytesToWrite" are + similarly unrolled loops. - --------------------------------------------------------------------- */ + --------------------------------------------------------------------- */ \ No newline at end of file diff --git a/libs/simpleini/ConvertUTF.h b/libs/simpleini/ConvertUTF.h index 6ecc819cb..528c96398 100644 --- a/libs/simpleini/ConvertUTF.h +++ b/libs/simpleini/ConvertUTF.h @@ -1,8 +1,10 @@ /* + * https://web.archive.org/web/20090529064329/http://www.unicode.org:80/Public/PROGRAMS/CVTUTF/ + * * Copyright 2001-2004 Unicode, Inc. - * + * * Disclaimer - * + * * This source code is provided as is by Unicode, Inc. No claims are * made as to fitness for any particular purpose. No warranties of any * kind are expressed or implied. The recipient agrees to determine @@ -10,9 +12,9 @@ * purchased on magnetic or optical media from Unicode, Inc., the * sole remedy for any claim will be exchange of defective media * within 90 days of receipt. - * + * * Limitations on Rights to Redistribute This Code - * + * * Unicode, Inc. hereby grants the right to freely use the information * supplied in this file in the creation of products supporting the * Unicode Standard, and to make copies of this file in any form @@ -20,74 +22,74 @@ * remains attached. */ -/* --------------------------------------------------------------------- - - Conversions between UTF32, UTF-16, and UTF-8. Header file. - - Several functions are included here, forming a complete set of - conversions between the three formats. UTF-7 is not included - here, but is handled in a separate source file. - - Each of these routines takes pointers to input buffers and output - buffers. The input buffers are const. - - Each routine converts the text between *sourceStart and sourceEnd, - putting the result into the buffer between *targetStart and - targetEnd. Note: the end pointers are *after* the last item: e.g. - *(sourceEnd - 1) is the last item. - - The return result indicates whether the conversion was successful, - and if not, whether the problem was in the source or target buffers. - (Only the first encountered problem is indicated.) - - After the conversion, *sourceStart and *targetStart are both - updated to point to the end of last text successfully converted in - the respective buffers. - - Input parameters: - sourceStart - pointer to a pointer to the source buffer. - The contents of this are modified on return so that - it points at the next thing to be converted. - targetStart - similarly, pointer to pointer to the target buffer. - sourceEnd, targetEnd - respectively pointers to the ends of the - two buffers, for overflow checking only. - - These conversion functions take a ConversionFlags argument. When this - flag is set to strict, both irregular sequences and isolated surrogates - will cause an error. When the flag is set to lenient, both irregular - sequences and isolated surrogates are converted. - - Whether the flag is strict or lenient, all illegal sequences will cause - an error return. This includes sequences such as: , , - or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code - must check for illegal sequences. - - When the flag is set to lenient, characters over 0x10FFFF are converted - to the replacement character; otherwise (when the flag is set to strict) - they constitute an error. - - Output parameters: - The value "sourceIllegal" is returned from some routines if the input - sequence is malformed. When "sourceIllegal" is returned, the source - value will point to the illegal value that caused the problem. E.g., - in UTF-8 when a sequence is malformed, it points to the start of the - malformed sequence. - - Author: Mark E. Davis, 1994. - Rev History: Rick McGowan, fixes & updates May 2001. - Fixes & updates, Sept 2001. - ------------------------------------------------------------------------- */ - -/* --------------------------------------------------------------------- - The following 4 definitions are compiler-specific. - The C standard does not guarantee that wchar_t has at least - 16 bits, so wchar_t is no less portable than unsigned short! - All should be unsigned values to avoid sign extension during - bit mask & shift operations. ------------------------------------------------------------------------- */ - -typedef unsigned int UTF32; /* at least 32 bits */ + /* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Header file. + + Several funtions are included here, forming a complete set of + conversions between the three formats. UTF-7 is not included + here, but is handled in a separate source file. + + Each of these routines takes pointers to input buffers and output + buffers. The input buffers are const. + + Each routine converts the text between *sourceStart and sourceEnd, + putting the result into the buffer between *targetStart and + targetEnd. Note: the end pointers are *after* the last item: e.g. + *(sourceEnd - 1) is the last item. + + The return result indicates whether the conversion was successful, + and if not, whether the problem was in the source or target buffers. + (Only the first encountered problem is indicated.) + + After the conversion, *sourceStart and *targetStart are both + updated to point to the end of last text successfully converted in + the respective buffers. + + Input parameters: + sourceStart - pointer to a pointer to the source buffer. + The contents of this are modified on return so that + it points at the next thing to be converted. + targetStart - similarly, pointer to pointer to the target buffer. + sourceEnd, targetEnd - respectively pointers to the ends of the + two buffers, for overflow checking only. + + These conversion functions take a ConversionFlags argument. When this + flag is set to strict, both irregular sequences and isolated surrogates + will cause an error. When the flag is set to lenient, both irregular + sequences and isolated surrogates are converted. + + Whether the flag is strict or lenient, all illegal sequences will cause + an error return. This includes sequences such as: , , + or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code + must check for illegal sequences. + + When the flag is set to lenient, characters over 0x10FFFF are converted + to the replacement character; otherwise (when the flag is set to strict) + they constitute an error. + + Output parameters: + The value "sourceIllegal" is returned from some routines if the input + sequence is malformed. When "sourceIllegal" is returned, the source + value will point to the illegal value that caused the problem. E.g., + in UTF-8 when a sequence is malformed, it points to the start of the + malformed sequence. + + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Fixes & updates, Sept 2001. + + ------------------------------------------------------------------------ */ + + /* --------------------------------------------------------------------- + The following 4 definitions are compiler-specific. + The C standard does not guarantee that wchar_t has at least + 16 bits, so wchar_t is no less portable than unsigned short! + All should be unsigned values to avoid sign extension during + bit mask & shift operations. + ------------------------------------------------------------------------ */ + +typedef unsigned long UTF32; /* at least 32 bits */ typedef unsigned short UTF16; /* at least 16 bits */ typedef unsigned char UTF8; /* typically 8 bits */ typedef unsigned char Boolean; /* 0 or 1 */ @@ -100,15 +102,15 @@ typedef unsigned char Boolean; /* 0 or 1 */ #define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF typedef enum { - conversionOK, /* conversion successful */ - sourceExhausted, /* partial character in source, but hit end */ - targetExhausted, /* insufficient room in target for conversion */ - sourceIllegal /* source sequence is illegal/malformed */ + conversionOK, /* conversion successful */ + sourceExhausted, /* partial character in source, but hit end */ + targetExhausted, /* insuff. room in target for conversion */ + sourceIllegal /* source sequence is illegal/malformed */ } ConversionResult; typedef enum { - strictConversion = 0, - lenientConversion + strictConversion = 0, + lenientConversion } ConversionFlags; /* This is for C++ and does no harm in C */ @@ -116,34 +118,34 @@ typedef enum { extern "C" { #endif -ConversionResult ConvertUTF8toUTF16 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + ConversionResult ConvertUTF8toUTF16( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); -ConversionResult ConvertUTF16toUTF8 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); - -ConversionResult ConvertUTF8toUTF32 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + ConversionResult ConvertUTF16toUTF8( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); -ConversionResult ConvertUTF32toUTF8 ( - const UTF32** sourceStart, const UTF32* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); - -ConversionResult ConvertUTF16toUTF32 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + ConversionResult ConvertUTF8toUTF32( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); -ConversionResult ConvertUTF32toUTF16 ( - const UTF32** sourceStart, const UTF32* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + ConversionResult ConvertUTF32toUTF8( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); -Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); + ConversionResult ConvertUTF16toUTF32( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + + ConversionResult ConvertUTF32toUTF16( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + + Boolean isLegalUTF8Sequence(const UTF8* source, const UTF8* sourceEnd); #ifdef __cplusplus } #endif -/* --------------------------------------------------------------------- */ +/* --------------------------------------------------------------------- */ \ No newline at end of file diff --git a/libs/simpleini/SOURCE.txt b/libs/simpleini/SOURCE.txt index b0139b522..d643c72d3 100644 --- a/libs/simpleini/SOURCE.txt +++ b/libs/simpleini/SOURCE.txt @@ -2,13 +2,13 @@ https://github.com/brofield/simpleini -VERSION: https://github.com/brofield/simpleini/releases/tag/v4.22 +VERSION: https://github.com/brofield/simpleini/tree/87f8e1b5021140727e75804224f6b1c305791378 #### LICENSE The MIT License (MIT) -Copyright (c) 2006-2022 Brodie Thiesfield +Copyright (c) 2006-2024 Brodie Thiesfield Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/libs/simpleini/SimpleIni.h b/libs/simpleini/SimpleIni.h index 1f0c0ae38..240f393f4 100644 --- a/libs/simpleini/SimpleIni.h +++ b/libs/simpleini/SimpleIni.h @@ -5,7 +5,7 @@ File SimpleIni.h Author Brodie Thiesfield Source https://github.com/brofield/simpleini - Version 4.22 + Version 4.25 Jump to the @link CSimpleIniTempl CSimpleIni @endlink interface documentation. @@ -53,7 +53,7 @@ -# If you will only be using straight utf8 files and access the data via the char interface, then you do not need any conversion library and could define SI_NO_CONVERSION. Note that no conversion also means no validation of the data. - If no converter is specified then the default converter is SI_CONVERT_GENERIC + If no converter is specified then the default converter is SI_NO_CONVERSION on Mac/Linux and SI_CONVERT_WIN32 on Windows. If you need widechar support on Mac/Linux then use either SI_CONVERT_GENERIC or SI_CONVERT_ICU. These are also supported on all platforms. @@ -161,6 +161,9 @@ @section notes NOTES + - The maximum supported file size is 1 GiB (SI_MAX_FILE_SIZE). Files larger + than this will be rejected with SI_FILE error to prevent excessive memory + allocation and potential denial of service attacks. - To load UTF-8 data on Windows 95, you need to use Microsoft Layer for Unicode, or SI_CONVERT_GENERIC, or SI_CONVERT_ICU. - When using SI_CONVERT_GENERIC, ConvertUTF.c must be compiled and linked. @@ -261,6 +264,10 @@ constexpr int SI_FAIL = -1; //!< Generic failure constexpr int SI_NOMEM = -2; //!< Out of memory error constexpr int SI_FILE = -3; //!< File error (see errno for detail error) +//! Maximum supported file size (1 GiB). Files larger than this will be rejected +//! to prevent excessive memory allocation and potential denial of service. +constexpr size_t SI_MAX_FILE_SIZE = 1024ULL * 1024ULL * 1024ULL; + #define SI_UTF8_SIGNATURE "\xEF\xBB\xBF" #ifdef _WIN32 @@ -357,7 +364,7 @@ class CSimpleIniTempl if (lhs.nOrder != rhs.nOrder) { return lhs.nOrder < rhs.nOrder; } - return KeyOrder()(lhs.pItem, rhs.pItem); + return KeyOrder()(lhs, rhs); } }; }; @@ -1457,9 +1464,14 @@ CSimpleIniTempl::LoadFile( if (lSize == 0) { return SI_OK; } - + + // check file size is within supported limits (SI_MAX_FILE_SIZE) + if (static_cast(lSize) > SI_MAX_FILE_SIZE) { + return SI_FILE; + } + // allocate and ensure NULL terminated - char * pData = new(std::nothrow) char[lSize+static_cast(1)]; + char * pData = new(std::nothrow) char[static_cast(lSize) + 1]; if (!pData) { return SI_NOMEM; } @@ -1511,13 +1523,18 @@ CSimpleIniTempl::LoadData( return SI_FAIL; } + // check converted data size is within supported limits (SI_MAX_FILE_SIZE) + if (uLen >= (SI_MAX_FILE_SIZE / sizeof(SI_CHAR))) { + return SI_FILE; + } + // allocate memory for the data, ensure that there is a NULL // terminator wherever the converted data ends - SI_CHAR * pData = new(std::nothrow) SI_CHAR[uLen+1]; + SI_CHAR * pData = new(std::nothrow) SI_CHAR[uLen + 1]; if (!pData) { return SI_NOMEM; } - memset(pData, 0, sizeof(SI_CHAR)*(uLen+1)); + memset(pData, 0, sizeof(SI_CHAR) * (uLen + 1)); // convert the data if (!converter.ConvertFromStore(a_pData, a_uDataLen, pData, uLen)) { @@ -1795,6 +1812,7 @@ CSimpleIniTempl::IsMultiLineData( } // embedded newlines + const SI_CHAR * pStart = a_pData; while (*a_pData) { if (IsNewLineChar(*a_pData)) { return true; @@ -1802,8 +1820,8 @@ CSimpleIniTempl::IsMultiLineData( ++a_pData; } - // check for suffix - if (IsSpace(*--a_pData)) { + // check for suffix (ensure we don't go before start of string) + if (a_pData > pStart && IsSpace(*(a_pData - 1))) { return true; } @@ -1816,7 +1834,7 @@ CSimpleIniTempl::IsSingleLineQuotedValue( const SI_CHAR* a_pData ) const { - // data needs quoting if it starts or ends with whitespace + // data needs quoting if it starts or ends with whitespace // and doesn't have embedded newlines // empty string @@ -1830,6 +1848,7 @@ CSimpleIniTempl::IsSingleLineQuotedValue( } // embedded newlines + const SI_CHAR * pStart = a_pData; while (*a_pData) { if (IsNewLineChar(*a_pData)) { return false; @@ -1837,8 +1856,8 @@ CSimpleIniTempl::IsSingleLineQuotedValue( ++a_pData; } - // check for suffix - if (IsSpace(*--a_pData)) { + // check for suffix (ensure we don't go before start of string) + if (a_pData > pStart && IsSpace(*(a_pData - 1))) { return true; } @@ -2083,7 +2102,8 @@ CSimpleIniTempl::AddEntry( if (pComment) { DeleteString(a_pComment); a_pComment = pComment; - CopyString(a_pComment); + rc = CopyString(a_pComment); + if (rc < 0) return rc; } Delete(a_pSection, a_pKey); iKey = keyval.end(); @@ -2252,12 +2272,13 @@ CSimpleIniTempl::GetDoubleValue( return a_nDefault; } - char * pszSuffix = NULL; + char * pszSuffix = szValue; double nValue = strtod(szValue, &pszSuffix); // any invalid strings will return the default value - if (!pszSuffix || *pszSuffix) { - return a_nDefault; + // check if no conversion was performed or if there are trailing characters + if (pszSuffix == szValue || *pszSuffix) { + return a_nDefault; } return nValue; @@ -2416,7 +2437,7 @@ CSimpleIniTempl::GetSectionSize( int nCount = 0; const SI_CHAR * pLastKey = NULL; typename TKeyVal::const_iterator iKeyVal = section.begin(); - for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n) { + for (; iKeyVal != section.end(); ++iKeyVal) { if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { ++nCount; pLastKey = iKeyVal->first.pItem; @@ -2448,7 +2469,7 @@ CSimpleIniTempl::GetAllSections( { a_names.clear(); typename TSection::const_iterator i = m_data.begin(); - for (int n = 0; i != m_data.end(); ++i, ++n ) { + for (; i != m_data.end(); ++i) { a_names.push_back(i->first); } } @@ -2474,7 +2495,7 @@ CSimpleIniTempl::GetAllKeys( const TKeyVal & section = iSection->second; const SI_CHAR * pLastKey = NULL; typename TKeyVal::const_iterator iKeyVal = section.begin(); - for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n ) { + for (; iKeyVal != section.end(); ++iKeyVal) { if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { a_names.push_back(iKeyVal->first); pLastKey = iKeyVal->first.pItem; @@ -2812,7 +2833,7 @@ CSimpleIniTempl::DeleteString( // strings may exist either inside the data block, or they will be // individually allocated and stored in m_strings. We only physically // delete those stored in m_strings. - if (a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { + if (!m_pData || a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { typename TNamesDepend::iterator i = m_strings.begin(); for (;i != m_strings.end(); ++i) { if (a_pString == i->pItem) { @@ -2834,17 +2855,19 @@ CSimpleIniTempl::DeleteString( // // SI_NO_CONVERSION Do not make the "W" wide character version of the // library available. Only CSimpleIniA etc is defined. +// Default on Linux/MacOS/etc. +// SI_CONVERT_WIN32 Use the Win32 API functions for conversion. +// Default on Windows. // SI_CONVERT_GENERIC Use the Unicode reference conversion library in // the accompanying files ConvertUTF.h/c // SI_CONVERT_ICU Use the IBM ICU conversion library. Requires // ICU headers on include path and icuuc.lib -// SI_CONVERT_WIN32 Use the Win32 API functions for conversion. #if !defined(SI_NO_CONVERSION) && !defined(SI_CONVERT_GENERIC) && !defined(SI_CONVERT_WIN32) && !defined(SI_CONVERT_ICU) # ifdef _WIN32 # define SI_CONVERT_WIN32 # else -# define SI_CONVERT_GENERIC +# define SI_NO_CONVERSION # endif #endif @@ -3063,14 +3086,18 @@ class SI_ConvertW { return a_uInputDataLen; } -#if defined(SI_NO_MBSTOWCS_NULL) || (!defined(_MSC_VER) && !defined(_linux)) + // get the required buffer size +#if defined(_MSC_VER) + size_t uBufSiz; + errno_t e = mbstowcs_s(&uBufSiz, NULL, 0, a_pInputData, a_uInputDataLen); + return (e == 0) ? uBufSiz : (size_t) -1; +#elif !defined(SI_NO_MBSTOWCS_NULL) + return mbstowcs(NULL, a_pInputData, a_uInputDataLen); +#else // fall back processing for platforms that don't support a NULL dest to mbstowcs // worst case scenario is 1:1, this will be a sufficient buffer size (void)a_pInputData; return a_uInputDataLen; -#else - // get the actual required buffer size - return mbstowcs(NULL, a_pInputData, a_uInputDataLen); #endif } @@ -3119,9 +3146,18 @@ class SI_ConvertW { } // convert to wchar_t +#if defined(_MSC_VER) + size_t uBufSiz; + errno_t e = mbstowcs_s(&uBufSiz, + a_pOutputData, a_uOutputDataSize, + a_pInputData, a_uInputDataLen); + (void)uBufSiz; + return (e == 0); +#else size_t retval = mbstowcs(a_pOutputData, a_pInputData, a_uOutputDataSize); return retval != (size_t)(-1); +#endif } /** Calculate the number of char required by the storage format of this @@ -3626,5 +3662,4 @@ typedef CSimpleIniTempl= 8 ) // is the overlap more than 8 away? + if ( ofs_to_dest >= 8 ) // is the overlap more than 8 away { char STBIR_SIMD_STREAMOUT_PTR( * ) s_end8 = ((char*) src) + (bytes&~7); - STBIR_NO_UNROLL_LOOP_START - do + + if ( ( ( ((ptrdiff_t)dest)|((ptrdiff_t)src) ) & 7 ) == 0 ) // is it 8byte aligned? + { + STBIR_NO_UNROLL_LOOP_START + do + { + STBIR_NO_UNROLL(sd); + *(stbir_uint64*)( sd + ofs_to_dest ) = *(stbir_uint64*) sd; + sd += 8; + } while ( sd < s_end8 ); + } + else { - STBIR_NO_UNROLL(sd); - *(stbir_uint64*)( sd + ofs_to_dest ) = *(stbir_uint64*) sd; - sd += 8; - } while ( sd < s_end8 ); + STBIR_NO_UNROLL_LOOP_START + do + { + int a,b; + STBIR_NO_UNROLL(sd); + a = ((int*)sd)[0]; + b = ((int*)sd)[1]; + ((int*)( sd + ofs_to_dest ))[0] = a; + ((int*)( sd + ofs_to_dest ))[1] = b; + sd += 8; + } while ( sd < s_end8 ); + } if ( sd == s_end ) return; @@ -3233,7 +3263,7 @@ static void stbir__get_extents( stbir__sampler * samp, stbir__extents * scanline newspan->n1 = ( max_left - min_left ) - left_margin; scanline_extents->edge_sizes[0] = 0; // don't need to copy the left margin, since we are directly decoding into the margin } - // if we can't merge the min_left range, add it as a second range + // if we can't merge the min_right range, add it as a second range else if ( ( right_margin ) && ( min_right != 0x7fffffff ) ) { @@ -3349,23 +3379,29 @@ static void stbir__calculate_coefficients_for_gather_upsample( float out_filter_ static void stbir__insert_coeff( stbir__contributors * contribs, float * coeffs, int new_pixel, float new_coeff, int max_width ) { - if ( new_pixel <= contribs->n1 ) // before the end + if ( contribs->n1 < contribs->n0 ) // this first clause should never happen, but handle in case + { + contribs->n0 = contribs->n1 = new_pixel; + coeffs[0] = new_coeff; + } + else if ( new_pixel <= contribs->n1 ) // before the end { if ( new_pixel < contribs->n0 ) // before the front? { if ( ( contribs->n1 - new_pixel + 1 ) <= max_width ) { int j, o = contribs->n0 - new_pixel; - for ( j = contribs->n1 - contribs->n0 ; j <= 0 ; j-- ) + for ( j = contribs->n1 - contribs->n0 ; j >= 0 ; j-- ) coeffs[ j + o ] = coeffs[ j ]; - for ( j = 1 ; j < o ; j-- ) - coeffs[ j ] = coeffs[ 0 ]; + for ( j = 1 ; j < o ; j++ ) + coeffs[ j ] = 0; coeffs[ 0 ] = new_coeff; contribs->n0 = new_pixel; } } else { + // add new weight to existing coeff if already there coeffs[ new_pixel - contribs->n0 ] += new_coeff; } } @@ -3812,7 +3848,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } } - // some horizontal routines read one float off the end (which is then masked off), so put in a sentinal so we don't read an snan or denormal + // some horizontal routines read one float off the end (which is then masked off), so put in a sentinel so we don't read an snan or denormal coefficents[ widest * num_contributors ] = 8888.0f; // the minimum we might read for unrolled filters widths is 12. So, we need to @@ -4610,7 +4646,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float if ( stbir_info->in_pixels_cb ) { // call the callback with a temp buffer (that they can choose to use or not). the temp is just right aligned memory in the decode_buffer itself - input_data = stbir_info->in_pixels_cb( ( (char*) end_decode ) - ( width * input_sample_in_bytes ) + sizeof(float)*STBIR_INPUT_CALLBACK_PADDING, input_plane_data, width, spans->pixel_offset_for_input, row, stbir_info->user_data ); + input_data = stbir_info->in_pixels_cb( ( (char*) end_decode ) - ( width * input_sample_in_bytes ) + ( ( stbir_info->input_type != STBIR_TYPE_FLOAT ) ? ( sizeof(float)*STBIR_INPUT_CALLBACK_PADDING ) : 0 ), input_plane_data, width, spans->pixel_offset_for_input, row, stbir_info->user_data ); } STBIR_PROFILE_START( decode ); @@ -6708,7 +6744,7 @@ static void stbir__get_conservative_extents( stbir__sampler * samp, stbir__contr } } -static void stbir__get_split_info( stbir__per_split_info* split_info, int splits, int output_height, int vertical_pixel_margin, int input_full_height ) +static void stbir__get_split_info( stbir__per_split_info* split_info, int splits, int output_height, int vertical_pixel_margin, int input_full_height, int is_gather, stbir__contributors * contribs ) { int i, cur; int left = output_height; @@ -6717,9 +6753,58 @@ static void stbir__get_split_info( stbir__per_split_info* split_info, int splits for( i = 0 ; i < splits ; i++ ) { int each; + split_info[i].start_output_y = cur; each = left / ( splits - i ); split_info[i].end_output_y = cur + each; + + // ok, when we are gathering, we need to make sure we are starting on a y offset that doesn't have + // a "special" set of coefficients. Basically, with exactly the right filter at exactly the right + // resize at exactly the right phase, some of the coefficents can be zero. When they are zero, we + // don't process them at all. But this leads to a tricky thing with the thread splits, where we + // might have a set of two coeffs like this for example: (4,4) and (3,6). The 4,4 means there was + // just one single coeff because things worked out perfectly (normally, they all have 4 coeffs + // like the range 3,6. The problem is that if we start right on the (4,4) on a brand new thread, + // then when we get to (3,6), we don't have the "3" sample in memory (because we didn't load + // it on the initial (4,4) range because it didn't have a 3 (we only add new samples that are + // larger than our existing samples - it's just how the eviction works). So, our solution here + // is pretty simple, if we start right on a range that has samples that start earlier, then we + // simply bump up our previous thread split range to include it, and then start this threads + // range with the smaller sample. It just moves one scanline from one thread split to another, + // so that we end with the unusual one, instead of start with it. To do this, we check 2-4 + // sample at each thread split start and then occassionally move them. + + if ( ( is_gather ) && ( i ) ) + { + stbir__contributors * small_contribs; + int j, smallest, stop, start_n0; + stbir__contributors * split_contribs = contribs + cur; + + // scan for a max of 3x the filter width or until the next thread split + stop = vertical_pixel_margin * 3; + if ( each < stop ) + stop = each; + + // loops a few times before early out + smallest = 0; + small_contribs = split_contribs; + start_n0 = small_contribs->n0; + for( j = 1 ; j <= stop ; j++ ) + { + ++split_contribs; + if ( split_contribs->n0 > start_n0 ) + break; + if ( split_contribs->n0 < small_contribs->n0 ) + { + small_contribs = split_contribs; + smallest = j; + } + } + + split_info[i-1].end_output_y += smallest; + split_info[i].start_output_y += smallest; + } + cur += each; left -= each; @@ -6815,45 +6900,45 @@ static float stbir__compute_weights[5][STBIR_RESIZE_CLASSIFICATIONS][4]= // 5 = { 0.56250f, 0.59375f, 0.00000f, 0.96875f }, { 1.00000f, 0.06250f, 0.00000f, 1.00000f }, { 0.00000f, 0.09375f, 1.00000f, 1.00000f }, - { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.31250f, 1.00000f }, { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, - { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, { 0.00000f, 1.00000f, 0.00000f, 0.03125f }, }, { { 0.00000f, 0.84375f, 0.00000f, 0.03125f }, { 0.09375f, 0.93750f, 0.00000f, 0.78125f }, { 0.87500f, 0.21875f, 0.00000f, 0.96875f }, { 0.09375f, 0.09375f, 1.00000f, 1.00000f }, - { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.00000f, 0.84375f, 0.00000f, 0.03125f }, { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, - { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, { 0.00000f, 1.00000f, 0.00000f, 0.53125f }, }, { { 0.00000f, 0.53125f, 0.00000f, 0.03125f }, { 0.06250f, 0.96875f, 0.00000f, 0.53125f }, { 0.87500f, 0.18750f, 0.00000f, 0.93750f }, { 0.00000f, 0.09375f, 1.00000f, 1.00000f }, - { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.00000f, 0.53125f, 0.00000f, 0.03125f }, { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, - { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, { 0.00000f, 1.00000f, 0.00000f, 0.56250f }, }, { { 0.00000f, 0.50000f, 0.00000f, 0.71875f }, { 0.06250f, 0.84375f, 0.00000f, 0.87500f }, { 1.00000f, 0.50000f, 0.50000f, 0.96875f }, { 1.00000f, 0.09375f, 0.31250f, 0.50000f }, - { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.00000f, 0.50000f, 0.00000f, 0.71875f }, { 1.00000f, 0.03125f, 0.03125f, 0.53125f }, - { 0.18750f, 0.12500f, 0.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, { 0.00000f, 1.00000f, 0.03125f, 0.18750f }, }, { { 0.00000f, 0.59375f, 0.00000f, 0.96875f }, { 0.06250f, 0.81250f, 0.06250f, 0.59375f }, { 0.75000f, 0.43750f, 0.12500f, 0.96875f }, { 0.87500f, 0.06250f, 0.18750f, 0.43750f }, - { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.00000f, 0.59375f, 0.00000f, 0.96875f }, { 0.15625f, 0.12500f, 1.00000f, 1.00000f }, - { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, { 0.00000f, 1.00000f, 0.03125f, 0.34375f }, } }; @@ -6907,16 +6992,16 @@ static int stbir__should_do_vertical_first( float weights_table[STBIR_RESIZE_CLA // categorize the resize into buckets if ( ( vertical_output_size <= 4 ) || ( horizontal_output_size <= 4 ) ) v_classification = ( vertical_output_size < horizontal_output_size ) ? 6 : 7; + else if ( ( !is_gather ) && ( ( vertical_output_size <= 16 ) || ( horizontal_output_size <= 16 ) ) ) + v_classification = 4; else if ( vertical_scale <= 1.0f ) v_classification = ( is_gather ) ? 1 : 0; else if ( vertical_scale <= 2.0f) v_classification = 2; else if ( vertical_scale <= 3.0f) v_classification = 3; - else if ( vertical_scale <= 4.0f) - v_classification = 5; - else - v_classification = 6; + else + v_classification = 5; // everything bigger than 3x // use the right weights weights = weights_table[ v_classification ]; @@ -7057,7 +7142,7 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample #ifdef STBIR__SEPARATE_ALLOCATIONS #define STBIR__NEXT_PTR( ptr, size, ntype ) if ( alloced ) { void * p = STBIR_MALLOC( size, user_data); if ( p == 0 ) { stbir__free_internal_mem( info ); return 0; } (ptr) = (ntype*)p; } #else - #define STBIR__NEXT_PTR( ptr, size, ntype ) advance_mem = (void*) ( ( ((size_t)advance_mem) + 15 ) & ~15 ); if ( alloced ) ptr = (ntype*)advance_mem; advance_mem = ((char*)advance_mem) + (size); + #define STBIR__NEXT_PTR( ptr, size, ntype ) advance_mem = (void*) ( ( ((size_t)advance_mem) + 15 ) & ~15 ); if ( alloced ) ptr = (ntype*)advance_mem; advance_mem = (char*)(((size_t)advance_mem) + (size)); #endif STBIR__NEXT_PTR( info, sizeof( stbir__info ), stbir__info ); @@ -7266,7 +7351,7 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample } // setup the vertical split ranges - stbir__get_split_info( info->split_info, info->splits, info->vertical.scale_info.output_sub_size, info->vertical.filter_pixel_margin, info->vertical.scale_info.input_full_size ); + stbir__get_split_info( info->split_info, info->splits, info->vertical.scale_info.output_sub_size, info->vertical.filter_pixel_margin, info->vertical.scale_info.input_full_size, info->vertical.is_gather, info->vertical.contributors ); // now we know precisely how many entries we need info->ring_buffer_num_entries = info->vertical.extent_info.widest; @@ -7540,7 +7625,7 @@ static int stbir__double_to_rational(double f, stbir_uint32 limit, stbir_uint32 numer_estimate = temp; } - // we didn't fine anything good enough for float, use a full range estimate + // we didn't find anything good enough for float, use a full range estimate if ( limit_denom ) { numer_estimate= (stbir_uint64)( f * (double)limit + 0.5 ); @@ -7830,7 +7915,7 @@ static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) stbir__set_sampler(&horizontal, resize->horizontal_filter, resize->horizontal_filter_kernel, resize->horizontal_filter_support, resize->horizontal_edge, &horizontal.scale_info, 1, resize->user_data ); stbir__get_conservative_extents( &horizontal, &conservative, resize->user_data ); - stbir__set_sampler(&vertical, resize->vertical_filter, resize->horizontal_filter_kernel, resize->vertical_filter_support, resize->vertical_edge, &vertical.scale_info, 0, resize->user_data ); + stbir__set_sampler(&vertical, resize->vertical_filter, resize->vertical_filter_kernel, resize->vertical_filter_support, resize->vertical_edge, &vertical.scale_info, 0, resize->user_data ); if ( ( vertical.scale_info.output_sub_size / splits ) < STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS ) // each split should be a minimum of 4 scanlines (handwavey choice) { @@ -7861,7 +7946,7 @@ static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) return 0; } -void stbir_free_samplers( STBIR_RESIZE * resize ) +STBIRDEF void stbir_free_samplers( STBIR_RESIZE * resize ) { if ( resize->samplers ) { @@ -7955,93 +8040,99 @@ STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start return stbir__perform_resize( resize->samplers, split_start, split_count ); } -static int stbir__check_output_stuff( void ** ret_ptr, int * ret_pitch, void * output_pixels, int type_size, int output_w, int output_h, int output_stride_in_bytes, stbir_internal_pixel_layout pixel_layout ) + +static void * stbir_quick_resize_helper( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout, stbir_datatype data_type, stbir_edge edge, stbir_filter filter ) { - size_t size; - int pitch; - void * ptr; + STBIR_RESIZE resize; + int scanline_output_in_bytes; + int positive_output_stride_in_bytes; + void * start_ptr; + void * free_ptr; - pitch = output_w * type_size * stbir__pixel_channels[ pixel_layout ]; - if ( pitch == 0 ) + scanline_output_in_bytes = output_w * stbir__type_size[ data_type ] * stbir__pixel_channels[ stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ]; + if ( scanline_output_in_bytes == 0 ) return 0; + // if zero stride, use scanline output if ( output_stride_in_bytes == 0 ) - output_stride_in_bytes = pitch; + output_stride_in_bytes = scanline_output_in_bytes; - if ( output_stride_in_bytes < pitch ) - return 0; + // abs value for inverted images (negative pitches) + positive_output_stride_in_bytes = output_stride_in_bytes; + if ( positive_output_stride_in_bytes < 0 ) + positive_output_stride_in_bytes = -positive_output_stride_in_bytes; - size = (size_t)output_stride_in_bytes * (size_t)output_h; - if ( size == 0 ) + // is the requested stride smaller than the scanline output? if so, just fail + if ( positive_output_stride_in_bytes < scanline_output_in_bytes ) return 0; - *ret_ptr = 0; - *ret_pitch = output_stride_in_bytes; + start_ptr = output_pixels; + free_ptr = 0; // no free pointer, since they passed buffer to use + // did they pass a zero for the dest? if so, allocate the buffer if ( output_pixels == 0 ) { - ptr = STBIR_MALLOC( size, 0 ); - if ( ptr == 0 ) + size_t size; + char * ptr; + + size = (size_t)positive_output_stride_in_bytes * (size_t)output_h; + if ( size == 0 ) return 0; - *ret_ptr = ptr; - *ret_pitch = pitch; - } - - return 1; -} - + ptr = (char*) STBIR_MALLOC( size, 0 ); + if ( ptr == 0 ) + return 0; -STBIRDEF unsigned char * stbir_resize_uint8_linear( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, - unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, - stbir_pixel_layout pixel_layout ) -{ - STBIR_RESIZE resize; - unsigned char * optr; - int opitch; + free_ptr = ptr; - if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( unsigned char ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) - return 0; + // point at the last scanline, if they requested a flipped image + if ( output_stride_in_bytes < 0 ) + start_ptr = ptr + ( (size_t)positive_output_stride_in_bytes * (size_t)( output_h - 1 ) ); + else + start_ptr = ptr; + } + // ok, now do the resize stbir_resize_init( &resize, input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, opitch, - pixel_layout, STBIR_TYPE_UINT8 ); + start_ptr, output_w, output_h, output_stride_in_bytes, + pixel_layout, data_type ); + + resize.horizontal_edge = edge; + resize.vertical_edge = edge; + resize.horizontal_filter = filter; + resize.vertical_filter = filter; if ( !stbir_resize_extended( &resize ) ) { - if ( optr ) - STBIR_FREE( optr, 0 ); + if ( free_ptr ) + STBIR_FREE( free_ptr, 0 ); return 0; } - return (optr) ? optr : output_pixels; + return (free_ptr) ? free_ptr : start_ptr; +} + + + +STBIRDEF unsigned char * stbir_resize_uint8_linear( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + return (unsigned char *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, STBIR_TYPE_UINT8, STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT ); } STBIRDEF unsigned char * stbir_resize_uint8_srgb( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, stbir_pixel_layout pixel_layout ) { - STBIR_RESIZE resize; - unsigned char * optr; - int opitch; - - if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( unsigned char ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) - return 0; - - stbir_resize_init( &resize, - input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, opitch, - pixel_layout, STBIR_TYPE_UINT8_SRGB ); - - if ( !stbir_resize_extended( &resize ) ) - { - if ( optr ) - STBIR_FREE( optr, 0 ); - return 0; - } - - return (optr) ? optr : output_pixels; + return (unsigned char *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, STBIR_TYPE_UINT8_SRGB, STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT ); } @@ -8049,66 +8140,27 @@ STBIRDEF float * stbir_resize_float_linear( const float *input_pixels , int inpu float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, stbir_pixel_layout pixel_layout ) { - STBIR_RESIZE resize; - float * optr; - int opitch; - - if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( float ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) - return 0; - - stbir_resize_init( &resize, - input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, opitch, - pixel_layout, STBIR_TYPE_FLOAT ); - - if ( !stbir_resize_extended( &resize ) ) - { - if ( optr ) - STBIR_FREE( optr, 0 ); - return 0; - } - - return (optr) ? optr : output_pixels; + return (float *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, STBIR_TYPE_FLOAT, STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT ); } STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, - stbir_pixel_layout pixel_layout, stbir_datatype data_type, - stbir_edge edge, stbir_filter filter ) + stbir_pixel_layout pixel_layout, stbir_datatype data_type, + stbir_edge edge, stbir_filter filter ) { - STBIR_RESIZE resize; - float * optr; - int opitch; - - if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, stbir__type_size[data_type], output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) - return 0; - - stbir_resize_init( &resize, - input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, output_stride_in_bytes, - pixel_layout, data_type ); - - resize.horizontal_edge = edge; - resize.vertical_edge = edge; - resize.horizontal_filter = filter; - resize.vertical_filter = filter; - - if ( !stbir_resize_extended( &resize ) ) - { - if ( optr ) - STBIR_FREE( optr, 0 ); - return 0; - } - - return (optr) ? optr : output_pixels; + return (void *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, data_type, edge, filter ); } #ifdef STBIR_PROFILE STBIRDEF void stbir_resize_build_profile_info( STBIR_PROFILE_INFO * info, STBIR_RESIZE const * resize ) { - static char const * bdescriptions[6] = { "Building", "Allocating", "Horizontal sampler", "Vertical sampler", "Coefficient cleanup", "Coefficient piovot" } ; + static char const * bdescriptions[6] = { "Building", "Allocating", "Horizontal sampler", "Vertical sampler", "Coefficient cleanup", "Coefficient pivot" } ; stbir__info* samp = resize->samplers; int i; @@ -8159,7 +8211,7 @@ STBIRDEF void stbir_resize_split_profile_info( STBIR_PROFILE_INFO * info, STBIR_ info->clocks[i] = sum; } - info->total_clocks = split_info->profile.named.total; + info->total_clocks = split_info->profile.named.total; info->descriptions = descriptions; info->count = STBIR__ARRAY_SIZE( descriptions ); } @@ -8707,7 +8759,7 @@ static float * STBIR__CODER_NAME(stbir__decode_uint8_srgb)( float * decodep, int { \ stbir__simdi temp; \ stbir__simdi_32shr( temp, stbir_simdi_castf( f ), 12 ) ; \ - stbir__simdi_and( temp, temp, STBIR__CONSTI(STBIR_mastissa_mask) ); \ + stbir__simdi_and( temp, temp, STBIR__CONSTI(STBIR_mantissa_mask) ); \ stbir__simdi_or( temp, temp, STBIR__CONSTI(STBIR_topscale) ); \ stbir__simdi_16madd( i, i, temp ); \ stbir__simdi_32shr( i, i, 16 ); \ @@ -8948,7 +9000,7 @@ static float * STBIR__CODER_NAME(stbir__decode_uint8_srgb2_linearalpha)( float * decode -= 4; if( decode < decode_end ) { - decode[0] = stbir__srgb_uchar_to_linear_float[ stbir__decode_order0 ]; + decode[0] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order0] ]; decode[1] = ( (float) input[stbir__decode_order1] ) * stbir__max_uint8_as_float_inverted; } return decode_end; @@ -9684,7 +9736,7 @@ static float * STBIR__CODER_NAME(stbir__decode_float_linear)( float * decodep, i static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int width_times_channels, float const * encode ) { - #if !defined( STBIR_FLOAT_HIGH_CLAMP ) && !defined(STBIR_FLOAT_LO_CLAMP) && !defined(stbir__decode_swizzle) + #if !defined( STBIR_FLOAT_HIGH_CLAMP ) && !defined(STBIR_FLOAT_LOW_CLAMP) && !defined(stbir__decode_swizzle) if ( (void*)outputp != (void*) encode ) STBIR_MEMCPY( outputp, encode, width_times_channels * sizeof( float ) ); @@ -9739,7 +9791,7 @@ static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int stbir__simdfX_store( output+stbir__simdfX_float_count, e1 ); encode += stbir__simdfX_float_count * 2; output += stbir__simdfX_float_count * 2; - if ( output < end_output ) + if ( output <= end_output ) continue; if ( output == ( end_output + ( stbir__simdfX_float_count * 2 ) ) ) break; diff --git a/libs/utfcpp/SOURCE.txt b/libs/utfcpp/SOURCE.txt index cca45d206..d2b411964 100644 --- a/libs/utfcpp/SOURCE.txt +++ b/libs/utfcpp/SOURCE.txt @@ -2,7 +2,7 @@ https://github.com/nemtrif/utfcpp -VERSION: https://github.com/nemtrif/utfcpp/releases/tag/v4.0.6 +VERSION: https://github.com/nemtrif/utfcpp/releases/tag/v4.1.1 #### LICENSE diff --git a/libs/utfcpp/utf8/checked.h b/libs/utfcpp/utf8/checked.h index 98949f8bc..96ceb4d50 100644 --- a/libs/utfcpp/utf8/checked.h +++ b/libs/utfcpp/utf8/checked.h @@ -129,7 +129,7 @@ namespace utf8 template inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) { - static const utfchar32_t replacement_marker = utf8::internal::mask16(0xfffd); + static const utfchar32_t replacement_marker = static_cast(utf8::internal::mask16(0xfffd)); return utf8::replace_invalid(start, end, out, replacement_marker); } @@ -227,11 +227,11 @@ namespace utf8 octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) { while (start != end) { - utfchar32_t cp = utf8::internal::mask16(*start++); + utfchar32_t cp = static_cast(utf8::internal::mask16(*start++)); // Take care of surrogate pairs first if (utf8::internal::is_lead_surrogate(cp)) { if (start != end) { - const utfchar32_t trail_surrogate = utf8::internal::mask16(*start++); + const utfchar32_t trail_surrogate = static_cast(utf8::internal::mask16(*start++)); if (utf8::internal::is_trail_surrogate(trail_surrogate)) cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; else diff --git a/libs/utfcpp/utf8/core.h b/libs/utfcpp/utf8/core.h index 627133c61..3ace403e0 100644 --- a/libs/utfcpp/utf8/core.h +++ b/libs/utfcpp/utf8/core.h @@ -43,9 +43,12 @@ DEALINGS IN THE SOFTWARE. #if UTF_CPP_CPLUSPLUS >= 201103L // C++ 11 or later #define UTF_CPP_OVERRIDE override #define UTF_CPP_NOEXCEPT noexcept + #define UTF_CPP_STATIC_ASSERT(condition) static_assert(condition, "UTFCPP static assert"); #else // C++ 98/03 #define UTF_CPP_OVERRIDE #define UTF_CPP_NOEXCEPT throw() + // Not worth simulating static_assert: + #define UTF_CPP_STATIC_ASSERT(condition) (void)(condition); #endif // C++ 11 or later @@ -87,6 +90,7 @@ namespace internal { return static_cast(0xff & oc); } + template inline utfchar16_t mask16(u16_type oc) { @@ -101,17 +105,17 @@ namespace internal inline bool is_lead_surrogate(utfchar32_t cp) { - return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX); + return (cp >= static_cast(LEAD_SURROGATE_MIN) && cp <= static_cast(LEAD_SURROGATE_MAX)); } inline bool is_trail_surrogate(utfchar32_t cp) { - return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + return (cp >= static_cast(TRAIL_SURROGATE_MIN) && cp <= static_cast(TRAIL_SURROGATE_MAX)); } inline bool is_surrogate(utfchar32_t cp) { - return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + return (cp >= static_cast(LEAD_SURROGATE_MIN) && cp <= static_cast(TRAIL_SURROGATE_MAX)); } inline bool is_code_point_valid(utfchar32_t cp) @@ -143,15 +147,15 @@ namespace internal inline bool is_overlong_sequence(utfchar32_t cp, int length) { if (cp < 0x80) { - if (length != 1) + if (length != 1) return true; } else if (cp < 0x800) { - if (length != 2) + if (length != 2) return true; } else if (cp < 0x10000) { - if (length != 3) + if (length != 3) return true; } return false; @@ -181,7 +185,7 @@ namespace internal if (it == end) return NOT_ENOUGH_ROOM; - code_point = utf8::internal::mask8(*it); + code_point = static_cast(utf8::internal::mask8(*it)); return UTF8_OK; } @@ -189,10 +193,10 @@ namespace internal template utf_error get_sequence_2(octet_iterator& it, octet_iterator end, utfchar32_t& code_point) { - if (it == end) + if (it == end) return NOT_ENOUGH_ROOM; - code_point = utf8::internal::mask8(*it); + code_point = static_cast(utf8::internal::mask8(*it)); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) @@ -206,8 +210,8 @@ namespace internal { if (it == end) return NOT_ENOUGH_ROOM; - - code_point = utf8::internal::mask8(*it); + + code_point = static_cast(utf8::internal::mask8(*it)); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) @@ -226,7 +230,7 @@ namespace internal if (it == end) return NOT_ENOUGH_ROOM; - code_point = utf8::internal::mask8(*it); + code_point = static_cast(utf8::internal::mask8(*it)); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) @@ -290,7 +294,7 @@ namespace internal else err = OVERLONG_SEQUENCE; } - else + else err = INVALID_CODE_POINT; } @@ -301,13 +305,118 @@ namespace internal template inline utf_error validate_next(octet_iterator& it, octet_iterator end) { - utfchar32_t ignored; - return utf8::internal::validate_next(it, end, ignored); - } + if (it == end) + return NOT_ENOUGH_ROOM; + + octet_iterator original_it = it; + const utfchar8_t lead = utf8::internal::mask8(*it); + + if (lead < 0x80) { + ++it; + return UTF8_OK; + } else if ((lead & 0xE0) == 0xC0) { + // two-byte sequence + if (lead == 0xC0 || lead == 0xC1) { + it = original_it; + return OVERLONG_SEQUENCE; + } + if (++it == end) { + it = original_it; + return NOT_ENOUGH_ROOM; + } + const utfchar8_t trail1 = utf8::internal::mask8(*it); + if ((trail1 & 0xC0) != 0x80) { + it = original_it; + return INCOMPLETE_SEQUENCE; + } + ++it; + return UTF8_OK; + } else if ((lead & 0xF0) == 0xE0) { + // three-byte sequence + if (++it == end) { + it = original_it; + return NOT_ENOUGH_ROOM; + } + const utfchar8_t trail1 = utf8::internal::mask8(*it); + if ((trail1 & 0xC0) != 0x80) { + it = original_it; + return INCOMPLETE_SEQUENCE; + } + if (++it == end) { + it = original_it; + return NOT_ENOUGH_ROOM; + } + const utfchar8_t trail2 = utf8::internal::mask8(*it); + if ((trail2 & 0xC0) != 0x80) { + it = original_it; + return INCOMPLETE_SEQUENCE; + } + if (lead == 0xE0 && trail1 < 0xA0) { + it = original_it; + return OVERLONG_SEQUENCE; + } + if (lead == 0xED && trail1 > 0x9F) { + it = original_it; + return INVALID_CODE_POINT; + } + ++it; + return UTF8_OK; + } else if ((lead & 0xF8) == 0xF0) { + // four-byte sequence + if (++it == end) { + it = original_it; + return NOT_ENOUGH_ROOM; + } + const utfchar8_t trail1 = utf8::internal::mask8(*it); + if ((trail1 & 0xC0) != 0x80) { + it = original_it; + return INCOMPLETE_SEQUENCE; + } + if (++it == end) { + it = original_it; + return NOT_ENOUGH_ROOM; + } + const utfchar8_t trail2 = utf8::internal::mask8(*it); + if ((trail2 & 0xC0) != 0x80) { + it = original_it; + return INCOMPLETE_SEQUENCE; + } + if (++it == end) { + it = original_it; + return NOT_ENOUGH_ROOM; + } + const utfchar8_t trail3 = utf8::internal::mask8(*it); + if ((trail3 & 0xC0) != 0x80) { + it = original_it; + return INCOMPLETE_SEQUENCE; + } + if (lead == 0xF0 && trail1 < 0x90) { + it = original_it; + return OVERLONG_SEQUENCE; + } + if (lead == 0xF4 && trail1 > 0x8F) { + it = original_it; + return INVALID_CODE_POINT; + } + if (lead >= 0xF5) { + it = original_it; + return INVALID_CODE_POINT; + } + ++it; + return UTF8_OK; + } else { + it = original_it; + return INVALID_LEAD; + } + } template utf_error validate_next16(word_iterator& it, word_iterator end, utfchar32_t& code_point) { + // Make sure the iterator dereferences a large enough type + typedef typename std::iterator_traits::value_type word_type; + UTF_CPP_STATIC_ASSERT(sizeof(word_type) >= sizeof(utfchar16_t)); + // Check the edge case: if (it == end) return NOT_ENOUGH_ROOM; // Save the original value of it so we can go back in case of failure @@ -326,14 +435,14 @@ namespace internal err = NOT_ENOUGH_ROOM; else if (is_lead_surrogate(first_word)) { const utfchar16_t second_word = *it++; - if (is_trail_surrogate(second_word)) { - code_point = static_cast(first_word << 10) + second_word + SURROGATE_OFFSET; + if (is_trail_surrogate(static_cast(second_word))) { + code_point = static_cast(first_word << 10) + static_cast(second_word) + SURROGATE_OFFSET; return UTF8_OK; - } else - err = INCOMPLETE_SEQUENCE; - + } else + err = INCOMPLETE_SEQUENCE; + } else { - err = INVALID_LEAD; + err = INVALID_LEAD; } } // error branch @@ -365,7 +474,7 @@ namespace internal } return result; } - + // One of the following overloads will be invoked from the API calls // A simple (but dangerous) case: the caller appends byte(s) to a char array @@ -395,6 +504,7 @@ namespace internal // the word_type. template word_iterator append16(utfchar32_t cp, word_iterator result) { + UTF_CPP_STATIC_ASSERT(sizeof(word_type) >= sizeof(utfchar16_t)); if (is_in_bmp(cp)) *(result++) = static_cast(cp); else { @@ -444,7 +554,7 @@ namespace internal inline const char* find_invalid(const char* str) { const char* end = str + std::strlen(str); - return find_invalid(str, end); + return find_invalid(str, end); } inline std::size_t find_invalid(const std::string& s) @@ -484,7 +594,7 @@ namespace internal inline bool starts_with_bom(const std::string& s) { return starts_with_bom(s.begin(), s.end()); - } + } } // namespace utf8 #endif // header guard diff --git a/libs/utfcpp/utf8/cpp17.h b/libs/utfcpp/utf8/cpp17.h index 6e2fcc238..075873003 100644 --- a/libs/utfcpp/utf8/cpp17.h +++ b/libs/utfcpp/utf8/cpp17.h @@ -89,7 +89,7 @@ namespace utf8 { return starts_with_bom(s.begin(), s.end()); } - + } // namespace utf8 #endif // header guard diff --git a/libs/utfcpp/utf8/unchecked.h b/libs/utfcpp/utf8/unchecked.h index bf2917893..173d0302e 100644 --- a/libs/utfcpp/utf8/unchecked.h +++ b/libs/utfcpp/utf8/unchecked.h @@ -82,7 +82,7 @@ namespace utf8 template inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) { - static const utfchar32_t replacement_marker = utf8::internal::mask16(0xfffd); + static const utfchar32_t replacement_marker = static_cast(utf8::internal::mask16(0xfffd)); return utf8::unchecked::replace_invalid(start, end, out, replacement_marker); } @@ -108,22 +108,22 @@ namespace utf8 case 1: break; case 2: - it++; + ++it; cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); break; case 3: - ++it; + ++it; cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); ++it; cp = static_cast(cp + ((*it) & 0x3f)); break; case 4: ++it; - cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); ++it; cp = static_cast(cp + ((utf8::internal::mask8(*it) << 6) & 0xfff)); ++it; - cp = static_cast(cp + ((*it) & 0x3f)); + cp = static_cast(cp + ((*it) & 0x3f)); break; } ++it; @@ -173,7 +173,7 @@ namespace utf8 distance(octet_iterator first, octet_iterator last) { typename std::iterator_traits::difference_type dist; - for (dist = 0; first < last; ++dist) + for (dist = 0; first < last; ++dist) utf8::unchecked::next(first); return dist; } @@ -247,15 +247,15 @@ namespace utf8 octet_iterator temp = it; return utf8::unchecked::next(temp); } - bool operator == (const iterator& rhs) const - { + bool operator == (const iterator& rhs) const + { return (it == rhs.it); } bool operator != (const iterator& rhs) const { return !(operator == (rhs)); } - iterator& operator ++ () + iterator& operator ++ () { ::std::advance(it, utf8::internal::sequence_length(it)); return *this; @@ -265,7 +265,7 @@ namespace utf8 iterator temp = *this; ::std::advance(it, utf8::internal::sequence_length(it)); return temp; - } + } iterator& operator -- () { utf8::unchecked::prior(it); @@ -280,8 +280,7 @@ namespace utf8 }; // class iterator } // namespace utf8::unchecked -} // namespace utf8 - +} // namespace utf8 #endif // header guard From 2c4787b171fc66aaa054f68209872fffd9fc5afc Mon Sep 17 00:00:00 2001 From: Piezometric Date: Mon, 8 Jun 2026 13:20:45 +0300 Subject: [PATCH 148/159] Update steam_overlay.cpp The code has been updated to address incomplete translations. --- overlay_experimental/steam_overlay.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 0432e7857..c611c7ee5 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -326,11 +326,18 @@ void Steam_Overlay::create_fonts() font_builder.AddText(translationShowAchievements[i]); font_builder.AddText(translationSettings[i]); font_builder.AddText(translationFriends[i]); + font_builder.AddText(translationtranslationNoNotification[i]); + font_builder.AddText(translationClearAll[i]); font_builder.AddText(translationAchievementWindow[i]); font_builder.AddText(translationListOfAchievements[i]); font_builder.AddText(translationAchievements[i]); font_builder.AddText(translationHiddenAchievement[i]); + font_builder.AddText(translationShow[i]); font_builder.AddText(translationAchievedOn[i]); + font_builder.AddText(translationUnlocked[i]); + font_builder.AddText(translationNoUnlockedAchievements[i]); + font_builder.AddText(translationLocked[i]); + font_builder.AddText(translationAllAchievementsUnlocked[i]); font_builder.AddText(translationNotAchieved[i]); font_builder.AddText(translationGlobalSettingsWindow[i]); font_builder.AddText(translationGlobalSettingsWindowDescription[i]); @@ -1637,14 +1644,14 @@ void Steam_Overlay::render_main_window() // --- Notification history panel --- if (show_notification_history) { - if (ImGui::SmallButton("Clear All")) { + if (ImGui::SmallButton(translationClearAll[current_language])) { notification_history.clear(); notification_history_cache.clear(); notification_history_cache_dirty = false; } ImGui::Separator(); if (notification_history.empty()) { - ImGui::TextDisabled("No notifications yet"); + ImGui::TextDisabled(translationNoNotification[current_language]); } else { ImGui::BeginChild("##history_scroll", ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 10), true); @@ -1801,7 +1808,7 @@ void Steam_Overlay::render_main_window() ImGui::SameLine(); ImGui::PushID(&x); - ImGui::SmallButton("Show"); + ImGui::SmallButton(translationShow[current_language]); bool show = ImGui::IsItemActive(); ImGui::PopID(); @@ -1838,9 +1845,9 @@ void Steam_Overlay::render_main_window() }; // --- Unlocked section --- - if (ImGui::CollapsingHeader("Unlocked", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(translationUnlocked[current_language], ImGuiTreeNodeFlags_DefaultOpen)) { if (unlocked_idx.empty()) { - ImGui::TextDisabled("No achievements unlocked yet"); + ImGui::TextDisabled(translationNoUnlockedAchievements[current_language]); } else { for (auto idx : unlocked_idx) { render_ach(achievements[idx]); @@ -1849,9 +1856,9 @@ void Steam_Overlay::render_main_window() } // --- Locked section --- - if (ImGui::CollapsingHeader("Locked", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(translationLocked[current_language], ImGuiTreeNodeFlags_DefaultOpen)) { if (locked_idx.empty()) { - ImGui::TextDisabled("All achievements unlocked!"); + ImGui::TextDisabled(translationAllAchievementsUnlocked[current_language]); } else { for (auto idx : locked_idx) { render_ach(achievements[idx]); From 43405ebba6891e5b60a6ac7c7239ce45a1be13aa Mon Sep 17 00:00:00 2001 From: Piezometric Date: Mon, 8 Jun 2026 13:31:53 +0300 Subject: [PATCH 149/159] Update steam_overlay_translations.h The missing translations have been added, and a Turkish translation has been provided. --- .../overlay/steam_overlay_translations.h | 672 ++++++++++++++++++ 1 file changed, 672 insertions(+) diff --git a/overlay_experimental/overlay/steam_overlay_translations.h b/overlay_experimental/overlay/steam_overlay_translations.h index de9e65627..6f1dad391 100644 --- a/overlay_experimental/overlay/steam_overlay_translations.h +++ b/overlay_experimental/overlay/steam_overlay_translations.h @@ -1559,6 +1559,678 @@ const char translationFriends[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFE }; +const char translationNoNotification[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"No notifications yet", + + // 1 - Arabic + u8"No notifications yet", + + // 2 - Bulgarian + u8"No notifications yet", + + // 3 - Simplified Chinese + u8"No notifications yet", + + // 4 - Traditional Chinese + u8"No notifications yet", + + // 5 - Czech + u8"No notifications yet", + + // 6 - Danish + u8"No notifications yet", + + // 7 - Dutch + u8"No notifications yet", + + // 8 - Finnish + u8"No notifications yet", + + // 9 - French + u8"No notifications yet", + + // 10 - German + u8"No notifications yet", + + // 11 - Greek + u8"No notifications yet", + + // 12 - Hungarian + u8"No notifications yet", + + // 13 - Italian + u8"No notifications yet", + + // 14 - Japanese + u8"No notifications yet", + + // 15 - Korean + u8"No notifications yet", + + // 16 - Norwegian + u8"No notifications yet", + + // 17 - Polish + u8"No notifications yet", + + // 18 - Portuguese + u8"No notifications yet", + + // 19 - Brazilian Portuguese + u8"No notifications yet", + + // 20 - Romanian + u8"No notifications yet", + + // 21 - Russian + u8"No notifications yet", + + // 22 - Spanish + u8"No notifications yet", + + // 23 - Latin American + u8"No notifications yet", + + // 24 - Swedish + u8"No notifications yet", + + // 25 - Thai + u8"No notifications yet", + + // 26 - Turkish + u8"Henüz bildirim yok", + + // 27 - Ukrainian + u8"No notifications yet", + + // 28 - Vietnamese + u8"No notifications yet", + + // 29 - Croatian + u8"No notifications yet", + + // 30 - Indonesian + u8"No notifications yet", + +}; + +const char translationClearAll[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"Clear All", + + // 1 - Arabic + u8"Clear All", + + // 2 - Bulgarian + u8"Clear All", + + // 3 - Simplified Chinese + u8"Clear All", + + // 4 - Traditional Chinese + u8"Clear All", + + // 5 - Czech + u8"Clear All", + + // 6 - Danish + u8"Clear All", + + // 7 - Dutch + u8"Clear All", + + // 8 - Finnish + u8"Clear All", + + // 9 - French + u8"Clear All", + + // 10 - German + u8"Clear All", + + // 11 - Greek + u8"Clear All", + + // 12 - Hungarian + u8"Clear All", + + // 13 - Italian + u8"Clear All", + + // 14 - Japanese + u8"Clear All", + + // 15 - Korean + u8"Clear All", + + // 16 - Norwegian + u8"Clear All", + + // 17 - Polish + u8"Clear All", + + // 18 - Portuguese + u8"Clear All", + + // 19 - Brazilian Portuguese + u8"Clear All", + + // 20 - Romanian + u8"Clear All", + + // 21 - Russian + u8"Clear All", + + // 22 - Spanish + u8"Clear All", + + // 23 - Latin American + u8"Clear All", + + // 24 - Swedish + u8"Clear All", + + // 25 - Thai + u8"Clear All", + + // 26 - Turkish + u8"Hepsini Temizle", + + // 27 - Ukrainian + u8"Clear All", + + // 28 - Vietnamese + u8"Clear All", + + // 29 - Croatian + u8"Clear All", + + // 30 - Indonesian + u8"Clear All", + +}; + +const char translationShow[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"Show", + + // 1 - Arabic + u8"Show", + + // 2 - Bulgarian + u8"Show", + + // 3 - Simplified Chinese + u8"Show", + + // 4 - Traditional Chinese + u8"Show", + + // 5 - Czech + u8"Show", + + // 6 - Danish + u8"Show", + + // 7 - Dutch + u8"Show", + + // 8 - Finnish + u8"Show", + + // 9 - French + u8"Show", + + // 10 - German + u8"Show", + + // 11 - Greek + u8"Show", + + // 12 - Hungarian + u8"Show", + + // 13 - Italian + u8"Show", + + // 14 - Japanese + u8"Show", + + // 15 - Korean + u8"Show", + + // 16 - Norwegian + u8"Show", + + // 17 - Polish + u8"Show", + + // 18 - Portuguese + u8"Show", + + // 19 - Brazilian Portuguese + u8"Show", + + // 20 - Romanian + u8"Show", + + // 21 - Russian + u8"Show", + + // 22 - Spanish + u8"Show", + + // 23 - Latin American + u8"Show", + + // 24 - Swedish + u8"Show", + + // 25 - Thai + u8"Show", + + // 26 - Turkish + u8"Göster", + + // 27 - Ukrainian + u8"Show", + + // 28 - Vietnamese + u8"Show", + + // 29 - Croatian + u8"Show", + + // 30 - Indonesian + u8"Show", + +}; + +const char translationUnlocked[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"Unlocked", + + // 1 - Arabic + u8"Unlocked", + + // 2 - Bulgarian + u8"Unlocked", + + // 3 - Simplified Chinese + u8"Unlocked", + + // 4 - Traditional Chinese + u8"Unlocked", + + // 5 - Czech + u8"Unlocked", + + // 6 - Danish + u8"Unlocked", + + // 7 - Dutch + u8"Unlocked", + + // 8 - Finnish + u8"Unlocked", + + // 9 - French + u8"Unlocked", + + // 10 - German + u8"Unlocked", + + // 11 - Greek + u8"Unlocked", + + // 12 - Hungarian + u8"Unlocked", + + // 13 - Italian + u8"Unlocked", + + // 14 - Japanese + u8"Unlocked", + + // 15 - Korean + u8"Unlocked", + + // 16 - Norwegian + u8"Unlocked", + + // 17 - Polish + u8"Unlocked", + + // 18 - Portuguese + u8"Unlocked", + + // 19 - Brazilian Portuguese + u8"Unlocked", + + // 20 - Romanian + u8"Unlocked", + + // 21 - Russian + u8"Unlocked", + + // 22 - Spanish + u8"Unlocked", + + // 23 - Latin American + u8"Unlocked", + + // 24 - Swedish + u8"Unlocked", + + // 25 - Thai + u8"Unlocked", + + // 26 - Turkish + u8"Kilidi Açıldı", + + // 27 - Ukrainian + u8"Unlocked", + + // 28 - Vietnamese + u8"Unlocked", + + // 29 - Croatian + u8"Unlocked", + + // 30 - Indonesian + u8"Unlocked", + +}; + +const char translationNoUnlockedAchievements[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"No achievements unlocked yet", + + // 1 - Arabic + u8"No achievements unlocked yet", + + // 2 - Bulgarian + u8"No achievements unlocked yet", + + // 3 - Simplified Chinese + u8"No achievements unlocked yet", + + // 4 - Traditional Chinese + u8"No achievements unlocked yet", + + // 5 - Czech + u8"No achievements unlocked yet", + + // 6 - Danish + u8"No achievements unlocked yet", + + // 7 - Dutch + u8"No achievements unlocked yet", + + // 8 - Finnish + u8"No achievements unlocked yet", + + // 9 - French + u8"No achievements unlocked yet", + + // 10 - German + u8"No achievements unlocked yet", + + // 11 - Greek + u8"No achievements unlocked yet", + + // 12 - Hungarian + u8"No achievements unlocked yet", + + // 13 - Italian + u8"No achievements unlocked yet", + + // 14 - Japanese + u8"No achievements unlocked yet", + + // 15 - Korean + u8"No achievements unlocked yet", + + // 16 - Norwegian + u8"No achievements unlocked yet", + + // 17 - Polish + u8"No achievements unlocked yet", + + // 18 - Portuguese + u8"No achievements unlocked yet", + + // 19 - Brazilian Portuguese + u8"No achievements unlocked yet", + + // 20 - Romanian + u8"No achievements unlocked yet", + + // 21 - Russian + u8"No achievements unlocked yet", + + // 22 - Spanish + u8"No achievements unlocked yet", + + // 23 - Latin American + u8"No achievements unlocked yet", + + // 24 - Swedish + u8"No achievements unlocked yet", + + // 25 - Thai + u8"No achievements unlocked yet", + + // 26 - Turkish + u8"Henüz hiçbir başarı açılmadı", + + // 27 - Ukrainian + u8"No achievements unlocked yet", + + // 28 - Vietnamese + u8"No achievements unlocked yet", + + // 29 - Croatian + u8"No achievements unlocked yet", + + // 30 - Indonesian + u8"No achievements unlocked yet", + +}; + +const char translationLocked[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"Locked", + + // 1 - Arabic + u8"Locked", + + // 2 - Bulgarian + u8"Locked", + + // 3 - Simplified Chinese + u8"Locked", + + // 4 - Traditional Chinese + u8"Locked", + + // 5 - Czech + u8"Locked", + + // 6 - Danish + u8"Locked", + + // 7 - Dutch + u8"Locked", + + // 8 - Finnish + u8"Locked", + + // 9 - French + u8"Locked", + + // 10 - German + u8"Locked", + + // 11 - Greek + u8"Locked", + + // 12 - Hungarian + u8"Locked", + + // 13 - Italian + u8"Locked", + + // 14 - Japanese + u8"Locked", + + // 15 - Korean + u8"Locked", + + // 16 - Norwegian + u8"Locked", + + // 17 - Polish + u8"Locked", + + // 18 - Portuguese + u8"Locked", + + // 19 - Brazilian Portuguese + u8"Locked", + + // 20 - Romanian + u8"Locked", + + // 21 - Russian + u8"Locked", + + // 22 - Spanish + u8"Locked", + + // 23 - Latin American + u8"Locked", + + // 24 - Swedish + u8"Locked", + + // 25 - Thai + u8"Locked", + + // 26 - Turkish + u8"Kilitli", + + // 27 - Ukrainian + u8"Locked", + + // 28 - Vietnamese + u8"Locked", + + // 29 - Croatian + u8"Locked", + + // 30 - Indonesian + u8"Locked", + +}; + +const char translationAllAchievementsUnlocked[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"All achievements unlocked!", + + // 1 - Arabic + u8"All achievements unlocked!", + + // 2 - Bulgarian + u8"All achievements unlocked!", + + // 3 - Simplified Chinese + u8"All achievements unlocked!", + + // 4 - Traditional Chinese + u8"All achievements unlocked!", + + // 5 - Czech + u8"All achievements unlocked!", + + // 6 - Danish + u8"All achievements unlocked!", + + // 7 - Dutch + u8"All achievements unlocked!", + + // 8 - Finnish + u8"All achievements unlocked!", + + // 9 - French + u8"All achievements unlocked!", + + // 10 - German + u8"All achievements unlocked!", + + // 11 - Greek + u8"All achievements unlocked!", + + // 12 - Hungarian + u8"All achievements unlocked!", + + // 13 - Italian + u8"All achievements unlocked!", + + // 14 - Japanese + u8"All achievements unlocked!", + + // 15 - Korean + u8"All achievements unlocked!", + + // 16 - Norwegian + u8"All achievements unlocked!", + + // 17 - Polish + u8"All achievements unlocked!", + + // 18 - Portuguese + u8"All achievements unlocked!", + + // 19 - Brazilian Portuguese + u8"All achievements unlocked!", + + // 20 - Romanian + u8"All achievements unlocked!", + + // 21 - Russian + u8"All achievements unlocked!", + + // 22 - Spanish + u8"All achievements unlocked!", + + // 23 - Latin American + u8"All achievements unlocked!", + + // 24 - Swedish + u8"All achievements unlocked!", + + // 25 - Thai + u8"All achievements unlocked!", + + // 26 - Turkish + u8"Tüm başarılar açıldı!", + + // 27 - Ukrainian + u8"All achievements unlocked!", + + // 28 - Vietnamese + u8"All achievements unlocked!", + + // 29 - Croatian + u8"All achievements unlocked!", + + // 30 - Indonesian + u8"All achievements unlocked!", + +}; + const char translationAchievementWindow[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { // 0 - English u8"Achievement Window", From 7a8bc8715512372729baf11166ea54e86fdb166b Mon Sep 17 00:00:00 2001 From: Piezometric Date: Mon, 8 Jun 2026 13:55:26 +0300 Subject: [PATCH 150/159] Update steam_overlay.cpp --- overlay_experimental/steam_overlay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index c611c7ee5..b6987eb6f 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -326,7 +326,7 @@ void Steam_Overlay::create_fonts() font_builder.AddText(translationShowAchievements[i]); font_builder.AddText(translationSettings[i]); font_builder.AddText(translationFriends[i]); - font_builder.AddText(translationtranslationNoNotification[i]); + font_builder.AddText(translationNoNotification[i]); font_builder.AddText(translationClearAll[i]); font_builder.AddText(translationAchievementWindow[i]); font_builder.AddText(translationListOfAchievements[i]); From 935d7c5f3789b75ab810e862346ccf092c6fa50c Mon Sep 17 00:00:00 2001 From: Piezometric Date: Mon, 8 Jun 2026 21:43:57 +0300 Subject: [PATCH 151/159] Update steam_overlay_translations.h --- .../overlay/steam_overlay_translations.h | 110 ++++++++++++++++-- 1 file changed, 103 insertions(+), 7 deletions(-) diff --git a/overlay_experimental/overlay/steam_overlay_translations.h b/overlay_experimental/overlay/steam_overlay_translations.h index 6f1dad391..a8ad02502 100644 --- a/overlay_experimental/overlay/steam_overlay_translations.h +++ b/overlay_experimental/overlay/steam_overlay_translations.h @@ -767,7 +767,7 @@ const char translationAccept[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER u8"ยอมรับ", // 26 - Turkish - u8"Kabul Et", + u8"Kabul et", // 27 - Ukrainian u8"Прийняти", @@ -1461,6 +1461,102 @@ const char translationSettings[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFF }; +const char translationHistory[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"History", + + // 1 - Arabic + u8"History", + + // 2 - Bulgarian + u8"History", + + // 3 - Simplified Chinese + u8"History", + + // 4 - Traditional Chinese + u8"History", + + // 5 - Czech + u8"History", + + // 6 - Danish + u8"History", + + // 7 - Dutch + u8"History", + + // 8 - Finnish + u8"History", + + // 9 - French + u8"History", + + // 10 - German + u8"History", + + // 11 - Greek + u8"History", + + // 12 - Hungarian + u8"History", + + // 13 - Italian + u8"History", + + // 14 - Japanese + u8"History", + + // 15 - Korean + u8"History", + + // 16 - Norwegian + u8"History", + + // 17 - Polish + u8"History", + + // 18 - Portuguese + u8"History", + + // 19 - Brazilian Portuguese + u8"History", + + // 20 - Romanian + u8"History", + + // 21 - Russian + u8"History", + + // 22 - Spanish + u8"History", + + // 23 - Latin American + u8"History", + + // 24 - Swedish + u8"History", + + // 25 - Thai + u8"History", + + // 26 - Turkish + u8"Geçmiş", + + // 27 - Ukrainian + u8"History", + + // 28 - Vietnamese + u8"History", + + // 29 - Croatian + u8"History", + + // 30 - Indonesian + u8"History", + +}; + // C:\Program Files (x86)\Steam\resource\overlay_*.txt // steam_menu_friends_view const char translationFriends[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { @@ -1927,7 +2023,7 @@ const char translationUnlocked[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFF u8"Unlocked", // 26 - Turkish - u8"Kilidi Açıldı", + u8"Kazanılmış", // 27 - Ukrainian u8"Unlocked", @@ -2023,7 +2119,7 @@ const char translationNoUnlockedAchievements[TRANSLATION_NUMBER_OF_LANGUAGES][TR u8"No achievements unlocked yet", // 26 - Turkish - u8"Henüz hiçbir başarı açılmadı", + u8"Henüz hiçbir başarım kazanılmadı", // 27 - Ukrainian u8"No achievements unlocked yet", @@ -2119,7 +2215,7 @@ const char translationLocked[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER u8"Locked", // 26 - Turkish - u8"Kilitli", + u8"Kazanılmamış Başarımlar", // 27 - Ukrainian u8"Locked", @@ -2215,7 +2311,7 @@ const char translationAllAchievementsUnlocked[TRANSLATION_NUMBER_OF_LANGUAGES][T u8"All achievements unlocked!", // 26 - Turkish - u8"Tüm başarılar açıldı!", + u8"Tüm başarılar kazanıldı!", // 27 - Ukrainian u8"All achievements unlocked!", @@ -2695,7 +2791,7 @@ const char translationAchievedOn[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BU u8"achieved on %s", // 26 - Turkish - u8"%s tarihinde elde edildi", + u8"%s tarihinde kazanıldı", // 27 - Ukrainian u8"achieved on %s", @@ -2791,7 +2887,7 @@ const char translationNotAchieved[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_B u8"not achieved", // 26 - Turkish - u8"Başarılmadı", + u8"kazanılmadı", // 27 - Ukrainian u8"not achieved", From cd3157be352daa23f565492057e4141a25f2cec4 Mon Sep 17 00:00:00 2001 From: Piezometric Date: Mon, 8 Jun 2026 21:44:15 +0300 Subject: [PATCH 152/159] Update steam_overlay.cpp --- overlay_experimental/steam_overlay.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index b6987eb6f..cb7c0df9c 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -325,6 +325,7 @@ void Steam_Overlay::create_fonts() font_builder.AddText(translationRenderer[i]); font_builder.AddText(translationShowAchievements[i]); font_builder.AddText(translationSettings[i]); + font_builder.AddText(translationHistory[i]); font_builder.AddText(translationFriends[i]); font_builder.AddText(translationNoNotification[i]); font_builder.AddText(translationClearAll[i]); @@ -1617,7 +1618,7 @@ void Steam_Overlay::render_main_window() ImGui::SameLine(); // user clicked on "notification history" - if (ImGui::Button("History")) { + if (ImGui::Button(translationHistory[current_language])) { show_notification_history = !show_notification_history; } From 995b33318a8af8ab627cd1545df6d510a87bb119 Mon Sep 17 00:00:00 2001 From: Piezometric Date: Mon, 8 Jun 2026 23:18:34 +0300 Subject: [PATCH 153/159] Update steam_overlay.cpp The locations where the SmallButton was used are not suitable for a button of this size. Therefore, it was replaced with a full-size button. --- overlay_experimental/steam_overlay.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index cb7c0df9c..c63749b56 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -1645,7 +1645,7 @@ void Steam_Overlay::render_main_window() // --- Notification history panel --- if (show_notification_history) { - if (ImGui::SmallButton(translationClearAll[current_language])) { + if (ImGui::Button(translationClearAll[current_language])) { notification_history.clear(); notification_history_cache.clear(); notification_history_cache_dirty = false; @@ -1809,7 +1809,7 @@ void Steam_Overlay::render_main_window() ImGui::SameLine(); ImGui::PushID(&x); - ImGui::SmallButton(translationShow[current_language]); + ImGui::Button(translationShow[current_language]); bool show = ImGui::IsItemActive(); ImGui::PopID(); From 62c11ee7afa53d3ebedddb0bb98d79a6740c52a8 Mon Sep 17 00:00:00 2001 From: Piezometric Date: Mon, 8 Jun 2026 23:18:55 +0300 Subject: [PATCH 154/159] Update steam_overlay_translations.h --- overlay_experimental/overlay/steam_overlay_translations.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overlay_experimental/overlay/steam_overlay_translations.h b/overlay_experimental/overlay/steam_overlay_translations.h index a8ad02502..b3b1a1fe2 100644 --- a/overlay_experimental/overlay/steam_overlay_translations.h +++ b/overlay_experimental/overlay/steam_overlay_translations.h @@ -2887,7 +2887,7 @@ const char translationNotAchieved[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_B u8"not achieved", // 26 - Turkish - u8"kazanılmadı", + u8"Kazanılmadı", // 27 - Ukrainian u8"not achieved", From 8d84f442d6278595c6e4767b7b153ea747c0f8a6 Mon Sep 17 00:00:00 2001 From: Piezometric Date: Tue, 9 Jun 2026 00:13:14 +0300 Subject: [PATCH 155/159] Change button to small button for achievements --- overlay_experimental/steam_overlay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index c63749b56..2416ccec6 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -1593,7 +1593,7 @@ void Steam_Overlay::render_main_window() ImGui::SameLine(); // user clicked on "show achievements" - if (ImGui::Button(translationShowAchievements[current_language])) { + if (ImGui::SmallButton(translationShowAchievements[current_language])) { show_achievements = !show_achievements; } From 83a92fc40ed89a68e18a65d4ffbd63b9939e1458 Mon Sep 17 00:00:00 2001 From: Piezometric Date: Tue, 9 Jun 2026 01:50:11 +0300 Subject: [PATCH 156/159] Change button styles for achievements display I shrunk the wrong thing... --- overlay_experimental/steam_overlay.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 2416ccec6..ec1597c43 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -1593,7 +1593,7 @@ void Steam_Overlay::render_main_window() ImGui::SameLine(); // user clicked on "show achievements" - if (ImGui::SmallButton(translationShowAchievements[current_language])) { + if (ImGui::Button(translationShowAchievements[current_language])) { show_achievements = !show_achievements; } @@ -1809,7 +1809,7 @@ void Steam_Overlay::render_main_window() ImGui::SameLine(); ImGui::PushID(&x); - ImGui::Button(translationShow[current_language]); + ImGui::SmallButton(translationShow[current_language]); bool show = ImGui::IsItemActive(); ImGui::PopID(); From 284bc36ac1a952b7b386223964c144220f5c1390 Mon Sep 17 00:00:00 2001 From: Piezometric Date: Tue, 9 Jun 2026 03:08:22 +0300 Subject: [PATCH 157/159] Update Turkish translation for achievements --- overlay_experimental/overlay/steam_overlay_translations.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overlay_experimental/overlay/steam_overlay_translations.h b/overlay_experimental/overlay/steam_overlay_translations.h index b3b1a1fe2..080b648ab 100644 --- a/overlay_experimental/overlay/steam_overlay_translations.h +++ b/overlay_experimental/overlay/steam_overlay_translations.h @@ -2023,7 +2023,7 @@ const char translationUnlocked[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFF u8"Unlocked", // 26 - Turkish - u8"Kazanılmış", + u8"Kazanılmış Başarımlar", // 27 - Ukrainian u8"Unlocked", From 8a6aa90cd8ca092c883271b8a9335f36eabd910d Mon Sep 17 00:00:00 2001 From: universal963 <36097923+universal963@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:40:16 +0800 Subject: [PATCH 158/159] Update Simplified Chinese translations --- .../overlay/steam_overlay_translations.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/overlay_experimental/overlay/steam_overlay_translations.h b/overlay_experimental/overlay/steam_overlay_translations.h index 080b648ab..86147e07d 100644 --- a/overlay_experimental/overlay/steam_overlay_translations.h +++ b/overlay_experimental/overlay/steam_overlay_translations.h @@ -1472,7 +1472,7 @@ const char translationHistory[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFE u8"History", // 3 - Simplified Chinese - u8"History", + u8"历史记录", // 4 - Traditional Chinese u8"History", @@ -1666,7 +1666,7 @@ const char translationNoNotification[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATIO u8"No notifications yet", // 3 - Simplified Chinese - u8"No notifications yet", + u8"暂无通知", // 4 - Traditional Chinese u8"No notifications yet", @@ -1762,7 +1762,7 @@ const char translationClearAll[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFF u8"Clear All", // 3 - Simplified Chinese - u8"Clear All", + u8"清除所有通知", // 4 - Traditional Chinese u8"Clear All", @@ -1858,7 +1858,7 @@ const char translationShow[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_S u8"Show", // 3 - Simplified Chinese - u8"Show", + u8"显示", // 4 - Traditional Chinese u8"Show", @@ -1954,7 +1954,7 @@ const char translationUnlocked[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFF u8"Unlocked", // 3 - Simplified Chinese - u8"Unlocked", + u8"已解锁", // 4 - Traditional Chinese u8"Unlocked", @@ -2050,7 +2050,7 @@ const char translationNoUnlockedAchievements[TRANSLATION_NUMBER_OF_LANGUAGES][TR u8"No achievements unlocked yet", // 3 - Simplified Chinese - u8"No achievements unlocked yet", + u8"暂无已解锁成就", // 4 - Traditional Chinese u8"No achievements unlocked yet", @@ -2146,7 +2146,7 @@ const char translationLocked[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER u8"Locked", // 3 - Simplified Chinese - u8"Locked", + u8"未解锁", // 4 - Traditional Chinese u8"Locked", @@ -2242,7 +2242,7 @@ const char translationAllAchievementsUnlocked[TRANSLATION_NUMBER_OF_LANGUAGES][T u8"All achievements unlocked!", // 3 - Simplified Chinese - u8"All achievements unlocked!", + u8"已解锁所有成就!", // 4 - Traditional Chinese u8"All achievements unlocked!", From b15855c848ed027e06152ceccdf6099b95b36361 Mon Sep 17 00:00:00 2001 From: gabrielgad Date: Fri, 12 Jun 2026 14:57:12 -0400 Subject: [PATCH 159/159] fix(networking): deliver P2P messages to all connections sharing a Steam ID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SteamNetworkingSockets P2P broke when multiple local processes share one Steam account (e.g. a bootstrap launcher exe + the shipping game exe both loading steam_api64, as Satisfactory does). CONNECTION_ACCEPTED and DATA are addressed by destination Steam ID and routed via find_connection(), which returns only the FIRST matching connection — so the reply landed on the wrong process (no socket, silently dropped) and the process that called ConnectP2P never reached Connected -> 120s ConnectionTimedOut. Add Networking::sendToAllWithID() which delivers to EVERY connection holding the dest Steam ID; the process owning the socket handles it, the rest drop it on socket lookup. Route all four P2P paths through it (REQUEST/ACCEPTED in send_packet_new_connection, CONNECTION_END in CloseConnection, DATA in both SendMessageToConnection overloads). Verified: Satisfactory two-instance Steam co-op now connects end-to-end (Welcomed by server, data flowing, joiner loads host world). --- dll/dll/network.h | 12 +++++++++++- dll/network.cpp | 30 ++++++++++++++++++++++++++++++ dll/steam_networking_sockets.cpp | 11 +++++++---- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/dll/dll/network.h b/dll/dll/network.h index c62756a88..fc4a6d94e 100644 --- a/dll/dll/network.h +++ b/dll/dll/network.h @@ -147,7 +147,17 @@ class Networking // send to a specific user, set_dest_id() must be called bool sendTo(Common_Message *msg, bool reliable, Connection *conn = NULL); - + + // send to EVERY connection that matches msg->dest_id() (not just the first). + // Needed when multiple local processes share one Steam account (e.g. a + // bootstrap launcher exe + the shipping game exe both loading steam_api): + // P2P connection-establishment + DATA messages are addressed by Steam ID, + // and routing to a single connection delivers them to the wrong process, + // which has no matching socket and silently drops them. Delivering to all + // matching connections lets the process that owns the socket handle it; + // the others ignore it (their socket lookup fails). + bool sendToAllWithID(Common_Message *msg, bool reliable); + // send to all users whose account type is Individual, no need to call set_dest_id(), this is done automatically bool sendToAllIndividuals(Common_Message *msg, bool reliable); diff --git a/dll/network.cpp b/dll/network.cpp index 0a46e5389..b11177ae3 100644 --- a/dll/network.cpp +++ b/dll/network.cpp @@ -1238,6 +1238,36 @@ bool Networking::sendTo(Common_Message *msg, bool reliable, Connection *conn) return ret; } +bool Networking::sendToAllWithID(Common_Message *msg, bool reliable) +{ + if (!enabled) return false; + + CSteamID dest_id((uint64)msg->dest_id()); + bool ret = false; + + // Deliver to ourselves if we also hold the dest id (loopback path). + if (std::find(ids.begin(), ids.end(), dest_id) != ids.end()) { + local_send.push_back(*msg); + ret = true; + } + + // Deliver to EVERY connection that holds the dest id, not just the first. + // Multiple local processes can share one Steam account (bootstrap launcher + // exe + shipping game exe); the one owning the P2P socket handles it, the + // rest drop it on socket lookup. + for (auto &conn : connections) { + for (const auto &id : conn.ids) { + if (id == dest_id) { + sendTo(msg, reliable, &conn); + ret = true; + break; + } + } + } + + return ret; +} + bool Networking::sendToAllIndividuals(Common_Message *msg, bool reliable) { for (auto &conn: connections) { diff --git a/dll/steam_networking_sockets.cpp b/dll/steam_networking_sockets.cpp index e12e0ce18..395014f6f 100644 --- a/dll/steam_networking_sockets.cpp +++ b/dll/steam_networking_sockets.cpp @@ -136,7 +136,10 @@ bool Steam_Networking_Sockets::send_packet_new_connection(HSteamNetConnection m_ uint64_t steam_id = connect_socket->second.remote_identity.GetSteamID64(); if (steam_id) { msg.set_dest_id(steam_id); - return network->sendTo(&msg, true); + // sendToAllWithID: a peer Steam account may map to several local + // connections (bootstrap + shipping exe). Route the REQUEST/ACCEPTED to + // all of them so the process owning the socket actually receives it. + return network->sendToAllWithID(&msg, true); } const SteamNetworkingIPAddr *ip_addr = connect_socket->second.remote_identity.GetIPAddr(); @@ -575,7 +578,7 @@ bool Steam_Networking_Sockets::CloseConnection( HSteamNetConnection hPeer, int n 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); + network->sendToAllWithID(&msg, true); } sbcs->connect_sockets.erase(connect_socket); @@ -766,7 +769,7 @@ EResult Steam_Networking_Sockets::SendMessageToConnection( HSteamNetConnection h bool reliable = false; if (nSendFlags & k_nSteamNetworkingSend_Reliable) reliable = true; - if (network->sendTo(&msg, reliable)) { + if (network->sendToAllWithID(&msg, reliable)) { if (pOutMessageNumber) *pOutMessageNumber = message_number; return k_EResultOK; } @@ -847,7 +850,7 @@ void Steam_Networking_Sockets::SendMessages( int nMessages, SteamNetworkingMessa bool reliable = false; if (pMessages[i]->m_nFlags & k_nSteamNetworkingSend_Reliable) reliable = true; - if (network->sendTo(&msg, reliable)) { + if (network->sendToAllWithID(&msg, reliable)) { out_number = message_number; result = k_EResultOK; } else {