From 4a4580380510f95ba78ceee9cc254e3cc0ea736c Mon Sep 17 00:00:00 2001 From: Gleb Sinyavskiy Date: Sun, 14 May 2023 12:27:32 +0200 Subject: [PATCH 1/7] Add SteamInput's SetInputActionManifestFilePath --- library/Makefile | 3 ++- library/SteamworksPy.cpp | 13 +++++++++++-- steamworks/interfaces/input.py | 3 +++ steamworks/methods.py | 4 ++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/library/Makefile b/library/Makefile index 416dc31..dbac57f 100644 --- a/library/Makefile +++ b/library/Makefile @@ -1,4 +1,5 @@ -makeall: +SteamworksPy.so: SteamworksPy.cpp g++ -std=c++11 -o SteamworksPy.so -shared -fPIC SteamworksPy.cpp -l steam_api -L. + clean: rm SteamworksPy.so diff --git a/library/SteamworksPy.cpp b/library/SteamworksPy.cpp index 49013ec..3a7017c 100644 --- a/library/SteamworksPy.cpp +++ b/library/SteamworksPy.cpp @@ -13,6 +13,7 @@ #include "TargetConditionals.h" #define SW_PY extern "C" __attribute__ ((visibility("default"))) #elif defined( __linux__ ) +#include #include "sdk/steam/steam_api.h" #define SW_PY extern "C" __attribute__ ((visibility("default"))) #else @@ -665,7 +666,7 @@ SW_PY uint64_t GetCurrentActionSet(uint64_t controllerHandle){ } return (uint64_t) SteamInput()->GetCurrentActionSet((InputHandle_t) controllerHandle); } -// Get the input type (device model) for the specified controller. +// Get the input type (device model) for the specified controller. SW_PY uint64_t GetInputTypeForHandle(uint64_t controllerHandle){ if(SteamInput() == NULL){ return 0; @@ -736,6 +737,13 @@ SW_PY bool ControllerInit(bool bExplicitlyCallRunFrame) { return SteamInput()->Init(bExplicitlyCallRunFrame); } +SW_PY bool SetInputActionManifestFilePath(const char *path) { + if (SteamInput() == NULL) { + return false; + } + return SteamInput()->SetInputActionManifestFilePath(path); +} + // Syncronize controllers. SW_PY void RunFrame() { if (SteamInput() == NULL) { @@ -1467,4 +1475,5 @@ SW_PY void MicroTxn_SetAuthorizationResponseCallback(MicroTxnAuthorizationRespon return; } microtxn.SetAuthorizationResponseCallback(callback); -} \ No newline at end of file +} + diff --git a/steamworks/interfaces/input.py b/steamworks/interfaces/input.py index e5820e5..0aa9af6 100644 --- a/steamworks/interfaces/input.py +++ b/steamworks/interfaces/input.py @@ -12,6 +12,9 @@ def __init__(self, steam: object): def Init(self, explicitlyCallRunFrame=False): return self.steam.ControllerInit(explicitlyCallRunFrame) + def SetInputActionManifestFilePath(self, path): + return self.steam.SetInputActionManifestFilePath(path.encode()) + def RunFrame(self): return self.steam.RunFrame() diff --git a/steamworks/methods.py b/steamworks/methods.py index 9567d8e..4d22035 100644 --- a/steamworks/methods.py +++ b/steamworks/methods.py @@ -135,6 +135,10 @@ class InputDigitalActionData_t(Structure): 'restype': None, 'argtypes': [c_uint64] }, + 'SetInputActionManifestFilePath': { + 'restype': bool, + 'argtypes': [c_char_p] + }, 'ActivateActionSet': { 'restype': None, 'argtypes': [c_uint64, c_uint64] From 0b2234a6be6ebced46cefff9bc31ca29dc3fef4a Mon Sep 17 00:00:00 2001 From: DaFluffyPotato Date: Sun, 29 Sep 2024 18:53:44 -0400 Subject: [PATCH 2/7] corrected function link for Friends.ActivateGameOverlayToStore --- steamworks/interfaces/friends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/steamworks/interfaces/friends.py b/steamworks/interfaces/friends.py index 3e22a2f..3de9b26 100644 --- a/steamworks/interfaces/friends.py +++ b/steamworks/interfaces/friends.py @@ -129,7 +129,7 @@ def ActivateGameOverlayToStore(self, app_id: int) -> None: :param app_id: int :return: None """ - self.steam.ActivateGameOverlayToWebPage(app_id) + self.steam.ActivateGameOverlayToStore(app_id) def ActivateGameOverlayInviteDialog(self, steam_lobby_id: int) -> None: From 3826bccb68c34cccfb20beef470bc50e8f7db96e Mon Sep 17 00:00:00 2001 From: Alexander Jordan Date: Sun, 30 Mar 2025 10:56:56 -0500 Subject: [PATCH 3/7] Fixes basic compatibility with Steamworks SDK 162, and fixes access violation error. This is a janky fix, but it gets this working again with SDK 162. First change is an early return after checking isInitSuccess. Not ideal, but it prevents the access violation error when the appid is not present in the persons library. Other change is to comment out deprecated RequestCurrentStats(); call. Unclear to me if line 1066 should remain. --- library/SteamworksPy.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/library/SteamworksPy.cpp b/library/SteamworksPy.cpp index 3a7017c..4fe8e15 100644 --- a/library/SteamworksPy.cpp +++ b/library/SteamworksPy.cpp @@ -265,10 +265,15 @@ SW_PY int SteamInit() { bool isInitSuccess = SteamAPI_Init(); // Set the default status response int status = FAILED; + // Steamworks initialized with no problems if (isInitSuccess) { status = OK; + }else + { + return status; } + // The Steam client is not running if (!SteamAPI_IsSteamRunning()) { status = ERR_NO_CLIENT; @@ -278,9 +283,11 @@ SW_PY int SteamInit() { status = ERR_NO_CONNECTION; } // Steam is connected and active, so load the stats and achievements - if (status == OK && SteamUserStats() != NULL) { - SteamUserStats()->RequestCurrentStats(); - } + // FULLY DEPRECATED, WILL NOT COMPILE + //if (status == OK && SteamUserStats() != NULL) { + //SteamUserStats()->RequestCurrentStats(); + //} + // Return the Steamworks status return status; } @@ -992,7 +999,7 @@ SW_PY int GetAuthSessionTicket(char* buffer) { return 0; } uint32 size{}; - SteamUser()->GetAuthSessionTicket(buffer, 1024, &size); + SteamUser()->GetAuthSessionTicket(buffer, 1024, &size, nullptr); return size; } @@ -1060,7 +1067,7 @@ SW_PY bool RequestCurrentStats() { if (SteamUser() == NULL) { return false; } - return SteamUserStats()->RequestCurrentStats(); + return true; } SW_PY bool SetAchievement(const char *name) { @@ -1476,4 +1483,3 @@ SW_PY void MicroTxn_SetAuthorizationResponseCallback(MicroTxnAuthorizationRespon } microtxn.SetAuthorizationResponseCallback(callback); } - From 645b0982b5bf02b5292945393d5b6bd906ed3c9a Mon Sep 17 00:00:00 2001 From: buzz Date: Sat, 17 May 2025 14:36:43 +0200 Subject: [PATCH 4/7] Add support for querying Steam Workshop items Implement functions to handle UGC queries and retrieve results. --- library/SteamworksPy.cpp | 43 +++++++++++++++++++++++ steamworks/interfaces/workshop.py | 58 +++++++++++++++++++++++++++++++ steamworks/methods.py | 15 ++++++++ steamworks/structs.py | 42 ++++++++++++++++++++++ 4 files changed, 158 insertions(+) diff --git a/library/SteamworksPy.cpp b/library/SteamworksPy.cpp index 4fe8e15..0e5229d 100644 --- a/library/SteamworksPy.cpp +++ b/library/SteamworksPy.cpp @@ -95,6 +95,7 @@ typedef void(*RemoteStorageSubscribeFileResultCallback_t)(SubscriptionResult); typedef void(*RemoteStorageUnsubscribeFileResultCallback_t)(SubscriptionResult); typedef void(*LeaderboardFindResultCallback_t)(LeaderboardFindResult_t); typedef void(*MicroTxnAuthorizationResponseCallback_t)(MicroTxnAuthorizationResponse_t); +typedef void(*SteamUGCQueryCompletedCallback_t)(SteamUGCQueryCompleted_t); //----------------------------------------------- // Workshop Class @@ -106,11 +107,13 @@ class Workshop { ItemInstalledCallback_t _pyItemInstalledCallback; RemoteStorageSubscribeFileResultCallback_t _pyItemSubscribedCallback; RemoteStorageUnsubscribeFileResultCallback_t _pyItemUnsubscribedCallback; + SteamUGCQueryCompletedCallback_t _pyQueryCompletedCallback; CCallResult _itemCreatedCallback; CCallResult _itemUpdatedCallback; CCallResult _itemSubscribedCallback; CCallResult _itemUnsubscribedCallback; + CCallResult _queryCompletedCallback; CCallback _itemInstalledCallback; @@ -140,6 +143,10 @@ class Workshop { _pyItemUnsubscribedCallback = callback; } + void SetQueryCompletedCallback(SteamUGCQueryCompletedCallback_t callback) { + _pyQueryCompletedCallback = callback; + } + void CreateItem(AppId_t consumerAppId, EWorkshopFileType fileType) { //TODO: Check if fileType is a valid value? SteamAPICall_t createItemCall = SteamUGC()->CreateItem(consumerAppId, fileType); @@ -161,6 +168,11 @@ class Workshop { _itemUnsubscribedCallback.Set(unsubscribeItemCall, this, &Workshop::OnItemUnsubscribed); } + void SendQueryRequest(UGCQueryHandle_t queryHandle) { + SteamAPICall_t queryRequestCall = SteamUGC()->SendQueryUGCRequest(queryHandle); + _queryCompletedCallback.Set(queryRequestCall, this, &Workshop::OnQueryCompleted); + } + private: void OnWorkshopItemCreated(CreateItemResult_t *createItemResult, bool bIOFailure) { if (_pyItemCreatedCallback != nullptr) { @@ -193,6 +205,12 @@ class Workshop { _pyItemUnsubscribedCallback(result); } } + + void OnQueryCompleted(SteamUGCQueryCompleted_t *queryCompletedResult, bool bIOFailure) { + if (_pyQueryCompletedCallback != nullptr) { + _pyQueryCompletedCallback(*queryCompletedResult); + } + } }; static Workshop workshop; @@ -1457,6 +1475,31 @@ SW_PY void Workshop_SuspendDownloads(bool bSuspend) { SteamUGC()->SuspendDownloads(bSuspend); } +SW_PY UGCQueryHandle_t Workshop_CreateQueryUGCDetailsRequest(PublishedFileId_t * pvecPublishedFileID, uint32 unNumPublishedFileIDs) { + return SteamUGC()->CreateQueryUGCDetailsRequest(pvecPublishedFileID, unNumPublishedFileIDs); +} + +SW_PY void Workshop_SetQueryCompletedCallback(SteamUGCQueryCompletedCallback_t callback) { + if (SteamUGC() == NULL) { + return; + } + workshop.SetQueryCompletedCallback(callback); +} + +SW_PY void Workshop_SendQueryUGCRequest(UGCQueryHandle_t handle) { + if(SteamUGC() == NULL){ + return; + } + workshop.SendQueryRequest(handle); +} + +SW_PY bool Workshop_GetQueryUGCResult(UGCQueryHandle_t handle, uint32 index, SteamUGCDetails_t * pDetails) { + if(SteamUGC() == NULL){ + return false; + } + return SteamUGC()->GetQueryUGCResult(handle, index, pDetails); +} + //----------------------------------------------- // Steam Leaderboard //----------------------------------------------- diff --git a/steamworks/interfaces/workshop.py b/steamworks/interfaces/workshop.py index 9366a63..9d40a28 100644 --- a/steamworks/interfaces/workshop.py +++ b/steamworks/interfaces/workshop.py @@ -13,12 +13,14 @@ class SteamWorkshop(object): _ItemInstalled_t = CFUNCTYPE(None, ItemInstalled_t) _RemoteStorageSubscribePublishedFileResult_t = CFUNCTYPE(None, SubscriptionResult) _RemoteStorageUnsubscribePublishedFileResult_t = CFUNCTYPE(None, SubscriptionResult) + _SteamUGCQueryCompleted_t = CFUNCTYPE(None, SteamUGCQueryCompleted_t) _CreateItemResult = None _SubmitItemUpdateResult = None _ItemInstalled = None _RemoteStorageSubscribePublishedFileResult = None _RemoteStorageUnsubscribePublishedFileResult = None + _SteamUGCQueryCompleted = None def __init__(self, steam: object): @@ -376,3 +378,59 @@ def GetItemDownloadInfo(self, published_file_id: int) -> dict: } return {} + + + def CreateQueryUGCDetailsRequest(self, published_file_ids: list) -> int: + """Create UGC item details query request + + :param published_file_ids: int list + :return: int + """ + published_files_c = (c_uint64 * len(published_file_ids))() + for index, published_file_id in enumerate(published_file_ids): + published_files_c[index] = c_uint64(published_file_id) + + return self.steam.Workshop_CreateQueryUGCDetailsRequest(published_files_c, len(published_file_ids)) + + + def SetQueryUGCRequestCallback(self, callback: object) -> bool: + """Set callback for UGC query + + :param callback: callable + :return: bool + """ + self._SteamUGCQueryCompleted = SteamWorkshop._SteamUGCQueryCompleted_t(callback) + self.steam.Workshop_SetQueryCompletedCallback(self._SteamUGCQueryCompleted) + return True + + + def SendQueryUGCRequest(self, handle: int, callback: object = None, override_callback: bool = False) -> None: + """Create UGC item details query request + + :param handle: query handle + :param callback: callable + :param override_callback: bool + :return: + """ + if override_callback: + self.SetQueryUGCRequestCallback(callback) + + elif callback and not self._SteamUGCQueryCompleted: + self.SetQueryUGCRequestCallback(callback) + + if self._SteamUGCQueryCompleted is None: + raise SetupRequired('Call `SetQueryUGCRequestCallback` first or supply a `callback`') + + self.steam.Workshop_SendQueryUGCRequest(handle) + + + def GetQueryUGCResult(self, handle: int, index: int) -> SteamUGCDetails_t: + """Create UGC item details query request + + :param handle: query handle + :param index: int + :return: SteamUGCDetails_t + """ + details = SteamUGCDetails_t() + self.steam.Workshop_GetQueryUGCResult(handle, index, byref(details)) + return details diff --git a/steamworks/methods.py b/steamworks/methods.py index 4d22035..98a8132 100644 --- a/steamworks/methods.py +++ b/steamworks/methods.py @@ -465,6 +465,21 @@ class InputDigitalActionData_t(Structure): 'restype': None, 'argtypes': [c_uint64] }, + 'Workshop_CreateQueryUGCDetailsRequest': { + 'restype': c_uint64, + 'argtypes': [POINTER(c_uint64), c_uint32] + }, + 'Workshop_SetQueryCompletedCallback': { + 'restype': None, + 'argtypes': [MAKE_CALLBACK(None, structs.SteamUGCQueryCompleted_t)] + }, + 'Workshop_SendQueryUGCRequest': { + 'argtypes': [c_uint64] + }, + 'Workshop_GetQueryUGCResult': { + 'restype': bool, + 'argtypes': [c_uint64, c_uint32, POINTER(structs.SteamUGCDetails_t)] + }, 'MicroTxn_SetAuthorizationResponseCallback': { 'restype': None, 'argtypes': [MAKE_CALLBACK(None, structs.MicroTxnAuthorizationResponse_t)] diff --git a/steamworks/structs.py b/steamworks/structs.py index 7f6f7b4..e547ceb 100644 --- a/steamworks/structs.py +++ b/steamworks/structs.py @@ -38,6 +38,48 @@ class SubscriptionResult(Structure): ("publishedFileId", c_uint64) ] + +class SteamUGCQueryCompleted_t(Structure): + _fields_ = [ + ("handle", c_uint64), + ("result", c_int), + ("numResultsReturned", c_uint32), + ("totalMatchingResults", c_uint32), + ("cachedData", c_bool) + ] + + +class SteamUGCDetails_t(Structure): + _fields_ = [ + ("publishedFileId", c_uint64), + ("result", c_int), + ("fileType", c_int), + ("creatorAppID", c_uint32), + ("consumerAppID", c_uint32), + ("title", c_char * 129), + ("description", c_char * 8000), + ("steamIDOwner", c_uint64), + ("timeCreated", c_uint32), + ("timeUpdated", c_uint32), + ("timeAddedToUserList", c_uint32), + ("visibility", c_int), + ("banned", c_bool), + ("acceptedForUse", c_bool), + ("tagsTruncated", c_bool), + ("tags", c_char * 1025), + ("file", c_uint64), + ("previewFile", c_uint64), + ("fileName", c_char * 260), + ("fileSize", c_uint32), + ("previewFileSize", c_uint32), + ("URL", c_char * 256), + ("votesUp", c_uint32), + ("votesDown", c_uint32), + ("score", c_float), + ("numChildren", c_uint32), + ] + + class MicroTxnAuthorizationResponse_t(Structure): _fields_ = [ ("appId", c_uint32), From 228a68836aafff0c97afefce5a2bc7c474bd5705 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Sat, 13 Sep 2025 17:44:14 +0200 Subject: [PATCH 5/7] Update README.md fix directory for steam files, which are required in library (with current linux command) --- library/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/README.md b/library/README.md index f32ea35..e1ef502 100644 --- a/library/README.md +++ b/library/README.md @@ -10,4 +10,4 @@ The source files can be downloaded here (log-in required): https://partner.steam Unpack the archive and place the contents of: - /sdk/public/steam in SteamworksPy/library/sdk/steam -- /sdk/redistributable_bin/%your_os% in SteamworksPy/library/sdk/redist +- /sdk/redistributable_bin/%your_os% in SteamworksPy/library From ecf32ae1d0627e0154ce39701eee313da3101d28 Mon Sep 17 00:00:00 2001 From: Anten Skrabec Date: Thu, 25 Dec 2025 03:20:07 -0700 Subject: [PATCH 6/7] implement changes from #91 without the formatting changes included in the original --- library/SteamworksPy.cpp | 54 +++++++++++++++++++++++++++++-- steamworks/__init__.py | 11 ++++++- steamworks/interfaces/workshop.py | 33 +++++++++++++++++++ steamworks/methods.py | 8 +++++ steamworks/structs.py | 18 +++++++++++ 5 files changed, 121 insertions(+), 3 deletions(-) diff --git a/library/SteamworksPy.cpp b/library/SteamworksPy.cpp index 0e5229d..4cff86e 100644 --- a/library/SteamworksPy.cpp +++ b/library/SteamworksPy.cpp @@ -86,11 +86,20 @@ typedef void(*CreateItemResultCallback_t)(CreateItemResult_t); typedef void(*SubmitItemUpdateResultCallback_t)(SubmitItemUpdateResult_t); typedef void(*ItemInstalledCallback_t)(ItemInstalled_t); +struct GetAppDependenciesResult { + std::int32_t result; + std::uint64_t publishedFileId; + std::uint32_t* array_app_dependencies; + std::uint32_t array_num_app_dependencies; + std::uint32_t total_num_app_dependencies; +}; + struct SubscriptionResult { std::int32_t result; std::uint64_t publishedFileId; }; +typedef void(*GetAppDependenciesResultCallback_t)(GetAppDependenciesResult); typedef void(*RemoteStorageSubscribeFileResultCallback_t)(SubscriptionResult); typedef void(*RemoteStorageUnsubscribeFileResultCallback_t)(SubscriptionResult); typedef void(*LeaderboardFindResultCallback_t)(LeaderboardFindResult_t); @@ -105,12 +114,14 @@ class Workshop { CreateItemResultCallback_t _pyItemCreatedCallback; SubmitItemUpdateResultCallback_t _pyItemUpdatedCallback; ItemInstalledCallback_t _pyItemInstalledCallback; + GetAppDependenciesResultCallback_t _pyGetAppDependenciesCallback; RemoteStorageSubscribeFileResultCallback_t _pyItemSubscribedCallback; RemoteStorageUnsubscribeFileResultCallback_t _pyItemUnsubscribedCallback; SteamUGCQueryCompletedCallback_t _pyQueryCompletedCallback; CCallResult _itemCreatedCallback; CCallResult _itemUpdatedCallback; + CCallResult _getAppDependenciesCallback; CCallResult _itemSubscribedCallback; CCallResult _itemUnsubscribedCallback; CCallResult _queryCompletedCallback; @@ -135,6 +146,10 @@ class Workshop { _pyItemInstalledCallback = nullptr; } + void SetGetAppDependenciesResultCallback(GetAppDependenciesResultCallback_t callback) { + _pyGetAppDependenciesCallback = callback; + } + void SetItemSubscribedCallback(RemoteStorageSubscribeFileResultCallback_t callback) { _pyItemSubscribedCallback = callback; } @@ -158,6 +173,11 @@ class Workshop { _itemUpdatedCallback.Set(submitItemUpdateCall, this, &Workshop::OnItemUpdateSubmitted); } + void GetAppDependencies(PublishedFileId_t publishedFileID) { + SteamAPICall_t getAppDependenciesCall = SteamUGC()->GetAppDependencies(publishedFileID); + _getAppDependenciesCallback.Set(getAppDependenciesCall, this, &Workshop::OnGetAppDependencies); + } + void SubscribeItem(PublishedFileId_t publishedFileID) { SteamAPICall_t subscribeItemCall = SteamUGC()->SubscribeItem(publishedFileID); _itemSubscribedCallback.Set(subscribeItemCall, this, &Workshop::OnItemSubscribed); @@ -192,6 +212,22 @@ class Workshop { } } + void OnGetAppDependencies(GetAppDependenciesResult_t* getAppDependenciesResult, bool bIOFailure) { + if (_pyGetAppDependenciesCallback != nullptr) { + GetAppDependenciesResult result; + result.result = getAppDependenciesResult->m_eResult; + result.publishedFileId = getAppDependenciesResult->m_nPublishedFileId; + result.array_num_app_dependencies = getAppDependenciesResult->m_nNumAppDependencies; + result.total_num_app_dependencies = getAppDependenciesResult->m_nTotalNumAppDependencies; + result.array_app_dependencies = new std::uint32_t[result.array_num_app_dependencies]; + std::copy(getAppDependenciesResult->m_rgAppIDs, + getAppDependenciesResult->m_rgAppIDs + result.array_num_app_dependencies, + result.array_app_dependencies); + _pyGetAppDependenciesCallback(result); + delete[] result.array_app_dependencies; + } + } + void OnItemSubscribed(RemoteStorageSubscribePublishedFileResult_t *itemSubscribedResult, bool bIOFailure) { if (_pyItemSubscribedCallback != nullptr) { SubscriptionResult result{itemSubscribedResult->m_eResult, itemSubscribedResult->m_nPublishedFileId}; @@ -462,7 +498,7 @@ SW_PY const char *GetAppInstallDir(AppId_t appID) { char *buffer = new char[folderBuffer]; SteamApps()->GetAppInstallDir(appID, (char *) buffer, folderBuffer); const char *appDir = buffer; - delete buffer; + delete[] buffer; return appDir; } @@ -998,7 +1034,7 @@ SW_PY const char *GetUserDataFolder() { char *buffer = new char[bufferSize]; SteamUser()->GetUserDataFolder((char *) buffer, bufferSize); char *data_path = buffer; - delete buffer; + delete[] buffer; return data_path; } @@ -1440,6 +1476,20 @@ SW_PY void Workshop_ClearItemInstalledCallback() { workshop.ClearItemInstallCallback(); } +SW_PY void Workshop_GetAppDependencies(PublishedFileId_t publishedFileID) { + if(SteamUGC() == NULL){ + return; + } + workshop.GetAppDependencies(publishedFileID); +} + +SW_PY void Workshop_SetGetAppDependenciesResultCallback(GetAppDependenciesResultCallback_t callback) { + if (SteamUGC() == NULL) { + return; + } + workshop.SetGetAppDependenciesResultCallback(callback); +} + SW_PY void Workshop_SetItemSubscribedCallback(RemoteStorageSubscribeFileResultCallback_t callback) { if (SteamUGC() == NULL) { return; diff --git a/steamworks/__init__.py b/steamworks/__init__.py index 9afd727..3e4a4c1 100644 --- a/steamworks/__init__.py +++ b/steamworks/__init__.py @@ -32,6 +32,8 @@ from steamworks.interfaces.microtxn import SteamMicroTxn from steamworks.interfaces.input import SteamInput +is_nuitka = '__compiled__' in globals() + os.environ['LD_LIBRARY_PATH'] = os.getcwd() @@ -71,6 +73,8 @@ def _initialize(self) -> bool: cdll.LoadLibrary(os.path.join(os.getcwd(), 'libsteam_api.so')) #if i do this then linux works elif os.path.isfile(os.path.join(os.path.dirname(__file__), 'libsteam_api.so')): cdll.LoadLibrary(os.path.join(os.path.dirname(__file__), 'libsteam_api.so')) + elif is_nuitka and os.path.isfile(os.path.join(os.path.split(os.path.split(__file__)[0])[0], 'libsteam_api.so')): + cdll.LoadLibrary(os.path.join(os.path.split(os.path.split(__file__)[0])[0], 'libsteam_api.so')) else: raise MissingSteamworksLibraryException(f'Missing library "libsteam_api.so"') @@ -88,10 +92,15 @@ def _initialize(self) -> bool: library_path = os.path.join(os.getcwd(), library_file_name) elif os.path.isfile(os.path.join(os.path.dirname(__file__), library_file_name)): library_path = os.path.join(os.path.dirname(__file__), library_file_name) + elif is_nuitka and os.path.isfile(os.path.join(os.path.split(os.path.split(__file__)[0])[0], library_file_name)): + library_path = os.path.join(os.path.split(os.path.split(__file__)[0])[0], library_file_name) else: raise MissingSteamworksLibraryException(f'Missing library {library_file_name}') - app_id_file = os.path.join(os.getcwd(), 'steam_appid.txt') + if is_nuitka and os.path.isfile(os.path.join(os.path.split(os.path.split(__file__)[0])[0], 'steam_appid.txt')): + app_id_file = os.path.join(os.path.split(os.path.split(__file__)[0])[0], 'steam_appid.txt') + else: + app_id_file = os.path.join(os.getcwd(), 'steam_appid.txt') if not os.path.isfile(app_id_file): raise FileNotFoundError(f'steam_appid.txt missing from {os.getcwd()}') diff --git a/steamworks/interfaces/workshop.py b/steamworks/interfaces/workshop.py index 9d40a28..28656ee 100644 --- a/steamworks/interfaces/workshop.py +++ b/steamworks/interfaces/workshop.py @@ -11,6 +11,7 @@ class SteamWorkshop(object): _CreateItemResult_t = CFUNCTYPE(None, CreateItemResult_t) _SubmitItemUpdateResult_t = CFUNCTYPE(None, SubmitItemUpdateResult_t) _ItemInstalled_t = CFUNCTYPE(None, ItemInstalled_t) + _GetAppDependenciesResult_t = CFUNCTYPE(None, GetAppDependenciesResult) _RemoteStorageSubscribePublishedFileResult_t = CFUNCTYPE(None, SubscriptionResult) _RemoteStorageUnsubscribePublishedFileResult_t = CFUNCTYPE(None, SubscriptionResult) _SteamUGCQueryCompleted_t = CFUNCTYPE(None, SteamUGCQueryCompleted_t) @@ -18,6 +19,7 @@ class SteamWorkshop(object): _CreateItemResult = None _SubmitItemUpdateResult = None _ItemInstalled = None + _GetAppDependenciesResult = None _RemoteStorageSubscribePublishedFileResult = None _RemoteStorageUnsubscribePublishedFileResult = None _SteamUGCQueryCompleted = None @@ -73,6 +75,17 @@ def ClearItemInstalledCallback(self) -> None: self.steam.Workshop_ClearItemInstalledCallback() + def SetGetAppDependenciesResultCallback(self, callback: object) -> bool: + """Set callback for item GetAppDependencies + + :param callback: callable + :return: bool + """ + self._GetAppDependenciesResult = self._GetAppDependenciesResult_t(callback) + self.steam.Workshop_SetGetAppDependenciesResultCallback(self._GetAppDependenciesResult) + return True + + def SetItemSubscribedCallback(self, callback: object) -> bool: """Set callback for item subscribed @@ -113,6 +126,26 @@ def CreateItem(self, app_id: int, filetype: EWorkshopFileType, callback: object self.steam.Workshop_CreateItem(app_id, filetype.value) + def GetAppDependencies(self, published_file_id: int, callback: object = None, override_callback: bool = False) -> None: + """Get a list of AppID dependencies from a UGC (Workshop) item + + :param published_file_id: int + :param callback: callable + :param override_callback: bool + :return: + """ + if override_callback: + self.SetGetAppDependenciesResultCallback(callback) + + elif callback and not self._GetAppDependenciesResult: + self.SetGetAppDependenciesResultCallback(callback) + + if self._GetAppDependenciesResult is None: + raise SetupRequired('Call `SetGetAppDependenciesResultCallback` first or supply a `callback`') + + self.steam.Workshop_GetAppDependencies(published_file_id) + + def SubscribeItem(self, published_file_id: int, callback: object = None, override_callback: bool = False) -> None: """ Subscribe to a UGC (Workshp) item diff --git a/steamworks/methods.py b/steamworks/methods.py index 98a8132..be08d22 100644 --- a/steamworks/methods.py +++ b/steamworks/methods.py @@ -453,6 +453,14 @@ class InputDigitalActionData_t(Structure): 'restype': None, 'argtypes': [MAKE_CALLBACK(None, structs.SubscriptionResult)] }, + 'Workshop_GetAppDependencies': { + 'restype': None, + 'argtypes': [c_uint64] + }, + 'Workshop_SetGetAppDependenciesResultCallback': { + 'restype': None, + 'argtypes': [MAKE_CALLBACK(None, structs.GetAppDependenciesResult)] + }, 'Workshop_SuspendDownloads': { 'restype': None, 'argtypes': [c_bool] diff --git a/steamworks/structs.py b/steamworks/structs.py index e547ceb..e244e74 100644 --- a/steamworks/structs.py +++ b/steamworks/structs.py @@ -32,6 +32,24 @@ class ItemInstalled_t(Structure): ] +class GetAppDependenciesResult(Structure): + _fields_ = [ + ("result", c_int32), + ("publishedFileId", c_uint64), + ("array_app_dependencies", POINTER(c_int32)), + ("array_num_app_dependencies", c_int32), + ("total_num_app_dependencies", c_int32) + ] + + def get_app_dependencies_list(self) -> list: + dependencies_list = [] + array_size = self.array_num_app_dependencies + array_type = c_uint32 * array_size + array = array_type.from_address(addressof(self.array_app_dependencies.contents)) + dependencies_list.extend(array) + return dependencies_list + + class SubscriptionResult(Structure): _fields_ = [ ("result", c_int32), From 97d5ff7f7ade06a9ee3b0796d96bd5304eddd17d Mon Sep 17 00:00:00 2001 From: Anten Skrabec Date: Fri, 26 Dec 2025 00:49:20 -0700 Subject: [PATCH 7/7] add GetAppDependencies and DownloadItem to the workshop interface --- .gitignore | 3 +- library/SteamworksPy.cpp | 65 +++++++++++++++++++++++++++++ steamworks/interfaces/workshop.py | 68 +++++++++++++++++++++++++++++++ steamworks/methods.py | 16 ++++++++ steamworks/structs.py | 28 +++++++++++++ 5 files changed, 179 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9ab794a..a993d83 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ *.dylib !redist/windows/*.dll !redist/osx/*.dylib -!redist/linux/*.so \ No newline at end of file +!redist/linux/*.so +library/sdk/steam/ diff --git a/library/SteamworksPy.cpp b/library/SteamworksPy.cpp index 0e5229d..81199dc 100644 --- a/library/SteamworksPy.cpp +++ b/library/SteamworksPy.cpp @@ -96,6 +96,8 @@ typedef void(*RemoteStorageUnsubscribeFileResultCallback_t)(SubscriptionResult); typedef void(*LeaderboardFindResultCallback_t)(LeaderboardFindResult_t); typedef void(*MicroTxnAuthorizationResponseCallback_t)(MicroTxnAuthorizationResponse_t); typedef void(*SteamUGCQueryCompletedCallback_t)(SteamUGCQueryCompleted_t); +typedef void(*GetAppDependenciesResultCallback_t)(GetAppDependenciesResult_t); +typedef void(*DownloadItemResultCallback_t)(DownloadItemResult_t); //----------------------------------------------- // Workshop Class @@ -108,12 +110,16 @@ class Workshop { RemoteStorageSubscribeFileResultCallback_t _pyItemSubscribedCallback; RemoteStorageUnsubscribeFileResultCallback_t _pyItemUnsubscribedCallback; SteamUGCQueryCompletedCallback_t _pyQueryCompletedCallback; + GetAppDependenciesResultCallback_t _pyGetAppDependenciesCallback; + DownloadItemResultCallback_t _pyDownloadItemCallback; CCallResult _itemCreatedCallback; CCallResult _itemUpdatedCallback; CCallResult _itemSubscribedCallback; CCallResult _itemUnsubscribedCallback; CCallResult _queryCompletedCallback; + CCallResult _getAppDependenciesCallback; + CCallResult _downloadItemCallback; CCallback _itemInstalledCallback; @@ -147,6 +153,14 @@ class Workshop { _pyQueryCompletedCallback = callback; } + void SetGetAppDependenciesCallback(GetAppDependenciesResultCallback_t callback) { + _pyGetAppDependenciesCallback = callback; + } + + void SetDownloadItemCallback(DownloadItemResultCallback_t callback) { + _pyDownloadItemCallback = callback; + } + void CreateItem(AppId_t consumerAppId, EWorkshopFileType fileType) { //TODO: Check if fileType is a valid value? SteamAPICall_t createItemCall = SteamUGC()->CreateItem(consumerAppId, fileType); @@ -173,6 +187,17 @@ class Workshop { _queryCompletedCallback.Set(queryRequestCall, this, &Workshop::OnQueryCompleted); } + void GetAppDependencies(PublishedFileId_t publishedFileID) { + SteamAPICall_t getAppDependenciesCall = SteamUGC()->GetAppDependencies(publishedFileID); + _getAppDependenciesCallback.Set(getAppDependenciesCall, this, &Workshop::OnGetAppDependencies); + } + + bool DownloadItem(PublishedFileId_t publishedFileID, bool bHighPriority) { + SteamAPICall_t downloadItemCall = SteamUGC()->DownloadItem(publishedFileID, bHighPriority); + _downloadItemCallback.Set(downloadItemCall, this, &Workshop::OnDownloadItem); + return true; // Returns true if successfully queued + } + private: void OnWorkshopItemCreated(CreateItemResult_t *createItemResult, bool bIOFailure) { if (_pyItemCreatedCallback != nullptr) { @@ -211,6 +236,18 @@ class Workshop { _pyQueryCompletedCallback(*queryCompletedResult); } } + + void OnGetAppDependencies(GetAppDependenciesResult_t *result, bool bIOFailure) { + if (_pyGetAppDependenciesCallback != nullptr) { + _pyGetAppDependenciesCallback(*result); + } + } + + void OnDownloadItem(DownloadItemResult_t *result, bool bIOFailure) { + if (_pyDownloadItemCallback != nullptr) { + _pyDownloadItemCallback(*result); + } + } }; static Workshop workshop; @@ -1500,6 +1537,34 @@ SW_PY bool Workshop_GetQueryUGCResult(UGCQueryHandle_t handle, uint32 index, Ste return SteamUGC()->GetQueryUGCResult(handle, index, pDetails); } +SW_PY void Workshop_SetGetAppDependenciesCallback(GetAppDependenciesResultCallback_t callback) { + if (SteamUGC() == NULL) { + return; + } + workshop.SetGetAppDependenciesCallback(callback); +} + +SW_PY void Workshop_GetAppDependencies(PublishedFileId_t publishedFileID) { + if (SteamUGC() == NULL) { + return; + } + workshop.GetAppDependencies(publishedFileID); +} + +SW_PY void Workshop_SetDownloadItemCallback(DownloadItemResultCallback_t callback) { + if (SteamUGC() == NULL) { + return; + } + workshop.SetDownloadItemCallback(callback); +} + +SW_PY bool Workshop_DownloadItem(PublishedFileId_t publishedFileID, bool bHighPriority) { + if (SteamUGC() == NULL) { + return false; + } + return workshop.DownloadItem(publishedFileID, bHighPriority); +} + //----------------------------------------------- // Steam Leaderboard //----------------------------------------------- diff --git a/steamworks/interfaces/workshop.py b/steamworks/interfaces/workshop.py index 9d40a28..e84a32b 100644 --- a/steamworks/interfaces/workshop.py +++ b/steamworks/interfaces/workshop.py @@ -14,6 +14,8 @@ class SteamWorkshop(object): _RemoteStorageSubscribePublishedFileResult_t = CFUNCTYPE(None, SubscriptionResult) _RemoteStorageUnsubscribePublishedFileResult_t = CFUNCTYPE(None, SubscriptionResult) _SteamUGCQueryCompleted_t = CFUNCTYPE(None, SteamUGCQueryCompleted_t) + _GetAppDependenciesResult_t = CFUNCTYPE(None, GetAppDependenciesResult_t) + _DownloadItemResult_t = CFUNCTYPE(None, DownloadItemResult_t) _CreateItemResult = None _SubmitItemUpdateResult = None @@ -21,6 +23,8 @@ class SteamWorkshop(object): _RemoteStorageSubscribePublishedFileResult = None _RemoteStorageUnsubscribePublishedFileResult = None _SteamUGCQueryCompleted = None + _GetAppDependenciesResult = None + _DownloadItemResult = None def __init__(self, steam: object): @@ -434,3 +438,67 @@ def GetQueryUGCResult(self, handle: int, index: int) -> SteamUGCDetails_t: details = SteamUGCDetails_t() self.steam.Workshop_GetQueryUGCResult(handle, index, byref(details)) return details + + + def SetGetAppDependenciesCallback(self, callback: object) -> bool: + """Set callback for GetAppDependencies result + + :param callback: callable + :return: bool + """ + self._GetAppDependenciesResult = self._GetAppDependenciesResult_t(callback) + self.steam.Workshop_SetGetAppDependenciesCallback(self._GetAppDependenciesResult) + return True + + + def GetAppDependencies(self, published_file_id: int, callback: object = None, + override_callback: bool = False) -> None: + """Get app dependencies for a workshop item + + Returns a list of app IDs that the workshop item depends on. + These are "soft" dependencies shown on the Steam Workshop web page. + The callback may be called multiple times if there are more than 32 dependencies. + + :param published_file_id: int + :param callback: callable - receives GetAppDependenciesResult_t + :param override_callback: bool + :return: None + """ + if override_callback: + self.SetGetAppDependenciesCallback(callback) + + elif callback and not self._GetAppDependenciesResult: + self.SetGetAppDependenciesCallback(callback) + + if self._GetAppDependenciesResult is None: + raise SetupRequired('Call `SetGetAppDependenciesCallback` first or supply a `callback`') + + self.steam.Workshop_GetAppDependencies(published_file_id) + + + def DownloadItem(self, published_file_id: int, high_priority: bool = False, + callback: object = None, override_callback: bool = False) -> bool: + """Initiate or prioritize download of a workshop item + + Downloads or updates a workshop item. If high_priority is True, this item will + be downloaded before any other items. The function returns immediately, and you + should wait for the callback before accessing the item on disk. + + NOTE: The callback will be triggered for all item downloads regardless of the + running application, so check the appID in the callback result. + + :param published_file_id: int + :param high_priority: bool - Set to True to pause other downloads and prioritize this one + :param callback: callable - receives DownloadItemResult_t when download completes (REQUIRED) + :param override_callback: bool + :return: bool - True if download initiated successfully + """ + # Callback is required - set it up internally + if callback is None: + raise ValueError('callback parameter is required for DownloadItem') + + if override_callback or not self._DownloadItemResult: + self._DownloadItemResult = self._DownloadItemResult_t(callback) + self.steam.Workshop_SetDownloadItemCallback(self._DownloadItemResult) + + return self.steam.Workshop_DownloadItem(published_file_id, high_priority) diff --git a/steamworks/methods.py b/steamworks/methods.py index 98a8132..35430a4 100644 --- a/steamworks/methods.py +++ b/steamworks/methods.py @@ -480,6 +480,22 @@ class InputDigitalActionData_t(Structure): 'restype': bool, 'argtypes': [c_uint64, c_uint32, POINTER(structs.SteamUGCDetails_t)] }, + 'Workshop_SetGetAppDependenciesCallback': { + 'restype': None, + 'argtypes': [MAKE_CALLBACK(None, structs.GetAppDependenciesResult_t)] + }, + 'Workshop_GetAppDependencies': { + 'restype': None, + 'argtypes': [c_uint64] + }, + 'Workshop_SetDownloadItemCallback': { + 'restype': None, + 'argtypes': [MAKE_CALLBACK(None, structs.DownloadItemResult_t)] + }, + 'Workshop_DownloadItem': { + 'restype': c_bool, + 'argtypes': [c_uint64, c_bool] + }, 'MicroTxn_SetAuthorizationResponseCallback': { 'restype': None, 'argtypes': [MAKE_CALLBACK(None, structs.MicroTxnAuthorizationResponse_t)] diff --git a/steamworks/structs.py b/steamworks/structs.py index e547ceb..2d55a17 100644 --- a/steamworks/structs.py +++ b/steamworks/structs.py @@ -86,3 +86,31 @@ class MicroTxnAuthorizationResponse_t(Structure): ("orderId", c_uint64), ("authorized", c_bool) ] + + +class GetAppDependenciesResult_t(Structure): + """Result from GetAppDependencies call + + Returns app dependencies associated with a workshop item. + These are "soft" dependencies shown on the web. + """ + _fields_ = [ + ("result", c_int), # EResult + ("publishedFileId", c_uint64), # PublishedFileId_t + ("rgAppIDs", c_uint32 * 32), # Array of AppId_t (max 32) + ("numAppDependencies", c_uint32), # Count returned in this struct + ("totalNumAppDependencies", c_uint32) # Total dependencies found + ] + + +class DownloadItemResult_t(Structure): + """Result from DownloadItem call + + Callback fired when workshop item has been downloaded. + Contains the app ID associated with the workshop item. + """ + _fields_ = [ + ("appID", c_uint32), # AppId_t - associated app + ("publishedFileId", c_uint64), # PublishedFileId_t + ("result", c_int) # EResult + ]