From a1d5cb030253b417ba5a663b771e242aa8a01fd2 Mon Sep 17 00:00:00 2001 From: twstagg Date: Fri, 7 Apr 2023 20:59:24 -0500 Subject: [PATCH 1/4] Add support when being run from a compiled Nuitka --onefile executable --- steamworks/__init__.py | 190 ++++++++++++++++++++++++----------------- 1 file changed, 113 insertions(+), 77 deletions(-) diff --git a/steamworks/__init__.py b/steamworks/__init__.py index 9afd727..fc4f7ec 100644 --- a/steamworks/__init__.py +++ b/steamworks/__init__.py @@ -7,51 +7,53 @@ 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. """ -__version__ = '2.0.0' -__author__ = 'GP Garcia' +__version__ = "2.0.0" +__author__ = "GP Garcia" import sys, os, time from ctypes import * from enum import Enum -import steamworks.util as steamworks_util -from steamworks.enums import * -from steamworks.structs import * -from steamworks.exceptions import * -from steamworks.methods import STEAMWORKS_METHODS +import steamworks.util as steamworks_util +from steamworks.enums import * +from steamworks.structs import * +from steamworks.exceptions import * +from steamworks.methods import STEAMWORKS_METHODS -from steamworks.interfaces.apps import SteamApps -from steamworks.interfaces.friends import SteamFriends -from steamworks.interfaces.matchmaking import SteamMatchmaking -from steamworks.interfaces.music import SteamMusic -from steamworks.interfaces.screenshots import SteamScreenshots -from steamworks.interfaces.users import SteamUsers -from steamworks.interfaces.userstats import SteamUserStats -from steamworks.interfaces.utils import SteamUtils -from steamworks.interfaces.workshop import SteamWorkshop -from steamworks.interfaces.microtxn import SteamMicroTxn -from steamworks.interfaces.input import SteamInput +from steamworks.interfaces.apps import SteamApps +from steamworks.interfaces.friends import SteamFriends +from steamworks.interfaces.matchmaking import SteamMatchmaking +from steamworks.interfaces.music import SteamMusic +from steamworks.interfaces.screenshots import SteamScreenshots +from steamworks.interfaces.users import SteamUsers +from steamworks.interfaces.userstats import SteamUserStats +from steamworks.interfaces.utils import SteamUtils +from steamworks.interfaces.workshop import SteamWorkshop +from steamworks.interfaces.microtxn import SteamMicroTxn +from steamworks.interfaces.input import SteamInput -os.environ['LD_LIBRARY_PATH'] = os.getcwd() +is_nuitka = "__compiled__" in globals() + +os.environ["LD_LIBRARY_PATH"] = os.getcwd() class STEAMWORKS(object): """ - Primary STEAMWORKS class used for fundamental handling of the STEAMWORKS API + Primary STEAMWORKS class used for fundamental handling of the STEAMWORKS API """ + _arch = steamworks_util.get_arch() - _native_supported_platforms = ['linux', 'linux2', 'darwin', 'win32'] + _native_supported_platforms = ["linux", "linux2", "darwin", "win32"] def __init__(self, supported_platforms: list = []) -> None: self._supported_platforms = supported_platforms - self._loaded = False - self._cdll = None + self._loaded = False + self._cdll = None - self.app_id = 0 + self.app_id = 0 self._initialize() - def _initialize(self) -> bool: """Initialize module by loading STEAMWORKS library @@ -64,21 +66,42 @@ def _initialize(self) -> bool: if platform not in STEAMWORKS._native_supported_platforms: raise UnsupportedPlatformException(f'"{platform}" is not being supported') - library_file_name = '' - if platform in ['linux', 'linux2']: - library_file_name = 'SteamworksPy.so' - if os.path.isfile(os.path.join(os.getcwd(), 'libsteam_api.so')): - 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')) + library_file_name = "" + if platform in ["linux", "linux2"]: + library_file_name = "SteamworksPy.so" + if os.path.isfile(os.path.join(os.getcwd(), "libsteam_api.so")): + 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 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"') - - elif platform == 'darwin': - library_file_name = 'SteamworksPy.dylib' - - elif platform == 'win32': - library_file_name = 'SteamworksPy.dll' if STEAMWORKS._arch == Arch.x86 else 'SteamworksPy64.dll' + raise MissingSteamworksLibraryException( + f'Missing library "libsteam_api.so"' + ) + + elif platform == "darwin": + library_file_name = "SteamworksPy.dylib" + elif platform == "win32": + library_file_name = ( + "SteamworksPy.dll" + if STEAMWORKS._arch == Arch.x86 + else "SteamworksPy64.dll" + ) else: # This case is theoretically unreachable @@ -88,62 +111,75 @@ 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') + raise MissingSteamworksLibraryException( + f"Missing library {library_file_name}" + ) + if is_nuitka and 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()}') + raise FileNotFoundError(f"steam_appid.txt missing from {os.getcwd()}") - with open(app_id_file, 'r') as f: - self.app_id = int(f.read()) + with open(app_id_file, "r") as f: + self.app_id = int(f.read()) - self._cdll = CDLL(library_path) # Throw native exception in case of error - self._loaded = True + self._cdll = CDLL(library_path) # Throw native exception in case of error + self._loaded = True self._load_steamworks_api() return self._loaded - def _load_steamworks_api(self) -> None: """Load all methods from steamworks api and assign their correct arg/res types based on method map :return: None """ if not self._loaded: - raise SteamNotLoadedException('STEAMWORKS not yet loaded') + raise SteamNotLoadedException("STEAMWORKS not yet loaded") for method_name, attributes in STEAMWORKS_METHODS.items(): f = getattr(self._cdll, method_name) - if 'restype' in attributes: - f.restype = attributes['restype'] + if "restype" in attributes: + f.restype = attributes["restype"] - if 'argtypes' in attributes: - f.argtypes = attributes['argtypes'] + if "argtypes" in attributes: + f.argtypes = attributes["argtypes"] setattr(self, method_name, f) self._reload_steamworks_interfaces() - def _reload_steamworks_interfaces(self) -> None: """Reload all interface classes :return: None """ - self.Apps = SteamApps(self) - self.Friends = SteamFriends(self) - self.Matchmaking = SteamMatchmaking(self) - self.Music = SteamMusic(self) - self.Screenshots = SteamScreenshots(self) - self.Users = SteamUsers(self) - self.UserStats = SteamUserStats(self) - self.Utils = SteamUtils(self) - self.Workshop = SteamWorkshop(self) - self.MicroTxn = SteamMicroTxn(self) - self.Input = SteamInput(self) - + self.Apps = SteamApps(self) + self.Friends = SteamFriends(self) + self.Matchmaking = SteamMatchmaking(self) + self.Music = SteamMusic(self) + self.Screenshots = SteamScreenshots(self) + self.Users = SteamUsers(self) + self.UserStats = SteamUserStats(self) + self.Utils = SteamUtils(self) + self.Workshop = SteamWorkshop(self) + self.MicroTxn = SteamMicroTxn(self) + self.Input = SteamInput(self) def initialize(self) -> bool: """Initialize Steam API connection @@ -151,21 +187,23 @@ def initialize(self) -> bool: :return: bool """ if not self.loaded(): - raise SteamNotLoadedException('STEAMWORKS not yet loaded') + raise SteamNotLoadedException("STEAMWORKS not yet loaded") if not self.IsSteamRunning(): - raise SteamNotRunningException('Steam is not running') + raise SteamNotRunningException("Steam is not running") # Boot up the Steam API result = self._cdll.SteamInit() if result == 2: - raise SteamNotRunningException('Steam is not running') + raise SteamNotRunningException("Steam is not running") elif result == 3: - raise SteamConnectionException('Not logged on or connection to Steam client could not be established') + raise SteamConnectionException( + "Not logged on or connection to Steam client could not be established" + ) elif result != 0: - raise GenericSteamException('Failed to initialize STEAMWORKS API') + raise GenericSteamException("Failed to initialize STEAMWORKS API") return True @@ -183,17 +221,15 @@ def unload(self) -> None: :return: None """ self._cdll.SteamShutdown() - self._loaded = False - self._cdll = None - + self._loaded = False + self._cdll = None def loaded(self) -> bool: """Is library loaded and everything populated :return: bool """ - return (self._loaded and self._cdll) - + return self._loaded and self._cdll def run_callbacks(self) -> bool: """Execute all callbacks @@ -201,7 +237,7 @@ def run_callbacks(self) -> bool: :return: bool """ if not self.loaded(): - raise SteamNotLoadedException('STEAMWORKS not yet loaded') + raise SteamNotLoadedException("STEAMWORKS not yet loaded") self._cdll.RunCallbacks() return True From 881fa365b0aa33f61335dd12dfe23f2d3f7b38ac Mon Sep 17 00:00:00 2001 From: twstagg Date: Thu, 25 May 2023 09:26:11 -0500 Subject: [PATCH 2/4] Implement ISteamUGC/GetAppDependencies See: https://partner.steamgames.com/doc/api/ISteamUGC#GetAppDependencies steamworks.structs.GetAppDependencies(ctypes.Structure).get_app_dependencies_list() will return Python list of these integers. --- library/SteamworksPy.cpp | 50 +++ library/sdk/redist/.gitignore | 4 - steamworks/interfaces/workshop.py | 216 ++++++---- steamworks/methods.py | 648 +++++++++--------------------- steamworks/structs.py | 43 +- 5 files changed, 410 insertions(+), 551 deletions(-) delete mode 100644 library/sdk/redist/.gitignore diff --git a/library/SteamworksPy.cpp b/library/SteamworksPy.cpp index 49013ec..48811d9 100644 --- a/library/SteamworksPy.cpp +++ b/library/SteamworksPy.cpp @@ -85,11 +85,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); @@ -103,11 +112,13 @@ class Workshop { CreateItemResultCallback_t _pyItemCreatedCallback; SubmitItemUpdateResultCallback_t _pyItemUpdatedCallback; ItemInstalledCallback_t _pyItemInstalledCallback; + GetAppDependenciesResultCallback_t _pyGetAppDependenciesCallback; RemoteStorageSubscribeFileResultCallback_t _pyItemSubscribedCallback; RemoteStorageUnsubscribeFileResultCallback_t _pyItemUnsubscribedCallback; CCallResult _itemCreatedCallback; CCallResult _itemUpdatedCallback; + CCallResult _getAppDependenciesCallback; CCallResult _itemSubscribedCallback; CCallResult _itemUnsubscribedCallback; @@ -131,6 +142,10 @@ class Workshop { _pyItemInstalledCallback = nullptr; } + void SetGetAppDependenciesResultCallback(GetAppDependenciesResultCallback_t callback) { + _pyGetAppDependenciesCallback = callback; + } + void SetItemSubscribedCallback(RemoteStorageSubscribeFileResultCallback_t callback) { _pyItemSubscribedCallback = callback; } @@ -150,6 +165,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); @@ -179,6 +199,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}; @@ -1407,6 +1443,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/library/sdk/redist/.gitignore b/library/sdk/redist/.gitignore deleted file mode 100644 index 86d0cb2..0000000 --- a/library/sdk/redist/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore everything in this directory -* -# Except this file -!.gitignore \ No newline at end of file diff --git a/steamworks/interfaces/workshop.py b/steamworks/interfaces/workshop.py index 9366a63..635b1ab 100644 --- a/steamworks/interfaces/workshop.py +++ b/steamworks/interfaces/workshop.py @@ -1,33 +1,33 @@ from ctypes import * from enum import Enum -import steamworks.util as util -from steamworks.enums import * -from steamworks.structs import * -from steamworks.exceptions import * +import steamworks.util as util +from steamworks.enums import * +from steamworks.structs import * +from steamworks.exceptions import * class SteamWorkshop(object): - _CreateItemResult_t = CFUNCTYPE(None, CreateItemResult_t) - _SubmitItemUpdateResult_t = CFUNCTYPE(None, SubmitItemUpdateResult_t) - _ItemInstalled_t = CFUNCTYPE(None, ItemInstalled_t) - _RemoteStorageSubscribePublishedFileResult_t = CFUNCTYPE(None, SubscriptionResult) - _RemoteStorageUnsubscribePublishedFileResult_t = CFUNCTYPE(None, SubscriptionResult) - - _CreateItemResult = None - _SubmitItemUpdateResult = None - _ItemInstalled = None - _RemoteStorageSubscribePublishedFileResult = None + _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) + + _CreateItemResult = None + _SubmitItemUpdateResult = None + _ItemInstalled = None + _GetAppDependenciesResult = None + _RemoteStorageSubscribePublishedFileResult = None _RemoteStorageUnsubscribePublishedFileResult = None - def __init__(self, steam: object): self.steam = steam if not self.steam.loaded(): - raise SteamNotLoadedException('STEAMWORKS not yet loaded') - - self.GetNumSubscribedItems() # This fixes #58 + raise SteamNotLoadedException("STEAMWORKS not yet loaded") + self.GetNumSubscribedItems() # This fixes #58 def SetItemCreatedCallback(self, callback: object) -> bool: """Set callback for item created @@ -39,7 +39,6 @@ def SetItemCreatedCallback(self, callback: object) -> bool: self.steam.Workshop_SetItemCreatedCallback(self._CreateItemResult) return True - def SetItemUpdatedCallback(self, callback: object) -> bool: """Set callback for item updated @@ -50,7 +49,6 @@ def SetItemUpdatedCallback(self, callback: object) -> bool: self.steam.Workshop_SetItemUpdatedCallback(self._SubmitItemUpdateResult) return True - def SetItemInstalledCallback(self, callback: object) -> bool: """Set callback for item installed @@ -61,7 +59,6 @@ def SetItemInstalledCallback(self, callback: object) -> bool: self.steam.Workshop_SetItemInstalledCallback(self._ItemInstalled) return True - def ClearItemInstalledCallback(self) -> None: """Clear item installed callback @@ -70,6 +67,17 @@ def ClearItemInstalledCallback(self) -> None: self._ItemInstalled = 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 @@ -77,23 +85,35 @@ def SetItemSubscribedCallback(self, callback: object) -> bool: :param callback: callable :return: bool """ - self._RemoteStorageSubscribePublishedFileResult = self._RemoteStorageSubscribePublishedFileResult_t(callback) - self.steam.Workshop_SetItemSubscribedCallback(self._RemoteStorageSubscribePublishedFileResult) + self._RemoteStorageSubscribePublishedFileResult = ( + self._RemoteStorageSubscribePublishedFileResult_t(callback) + ) + self.steam.Workshop_SetItemSubscribedCallback( + self._RemoteStorageSubscribePublishedFileResult + ) return True - def SetItemUnsubscribedCallback(self, callback: object) -> bool: """Set callback for item unsubscribed :param callback: callable :return: bool """ - self._RemoteStorageUnsubscribePublishedFileResult = self._RemoteStorageUnsubscribePublishedFileResult_t(callback) - self.steam.Workshop_SetItemUnsubscribedCallback(self._RemoteStorageUnsubscribePublishedFileResult) + self._RemoteStorageUnsubscribePublishedFileResult = ( + self._RemoteStorageUnsubscribePublishedFileResult_t(callback) + ) + self.steam.Workshop_SetItemUnsubscribedCallback( + self._RemoteStorageUnsubscribePublishedFileResult + ) return True - - def CreateItem(self, app_id: int, filetype: EWorkshopFileType, callback: object = None, override_callback: bool = False) -> None: + def CreateItem( + self, + app_id: int, + filetype: EWorkshopFileType, + callback: object = None, + override_callback: bool = False, + ) -> None: """Creates a new workshop item with no content attached yet :param app_id: int @@ -110,9 +130,39 @@ 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 + def SubscribeItem( + self, + published_file_id: int, + callback: object = None, + override_callback: bool = False, + ) -> None: + """Subscribe to a UGC (Workshp) item :param published_file_id: int :param callback: callable @@ -126,13 +176,19 @@ def SubscribeItem(self, published_file_id: int, callback: object = None, overrid self.SetItemSubscribedCallback(callback) if self._RemoteStorageSubscribePublishedFileResult is None: - raise SetupRequired('Call `SetItemSubscribedCallback` first or supply a `callback`') + raise SetupRequired( + "Call `SetItemSubscribedCallback` first or supply a `callback`" + ) self.steam.Workshop_SubscribeItem(published_file_id) - - def UnsubscribeItem(self, published_file_id: int, callback: object = None, override_callback: bool = False) -> None: - """ Unsubscribe to a UGC (Workshp) item + def UnsubscribeItem( + self, + published_file_id: int, + callback: object = None, + override_callback: bool = False, + ) -> None: + """Unsubscribe to a UGC (Workshp) item :param published_file_id: int :param callback: callable @@ -146,13 +202,14 @@ def UnsubscribeItem(self, published_file_id: int, callback: object = None, overr self.SetItemUnsubscribedCallback(callback) if self._RemoteStorageUnsubscribePublishedFileResult is None: - raise SetupRequired('Call `SetItemUnsubscribedCallback` first or supply a `callback`') + raise SetupRequired( + "Call `SetItemUnsubscribedCallback` first or supply a `callback`" + ) self.steam.Workshop_UnsubscribeItem(published_file_id) - def StartItemUpdate(self, app_id: int, published_file_id: int) -> int: - """ Start the item update process and receive an update handle + """Start the item update process and receive an update handle :param app_id: int :param published_file_id: int @@ -160,7 +217,6 @@ def StartItemUpdate(self, app_id: int, published_file_id: int) -> int: """ return self.steam.Workshop_StartItemUpdate(app_id, c_uint64(published_file_id)) - def SetItemTitle(self, update_handle: int, title: str) -> bool: """Set the title of a Workshop item @@ -169,11 +225,10 @@ def SetItemTitle(self, update_handle: int, title: str) -> bool: :return: bool """ if len(title) > 128: - raise AttributeError('title exceeds 128 characters') + raise AttributeError("title exceeds 128 characters") return self.steam.Workshop_SetItemTitle(update_handle, title.encode()) - def SetItemDescription(self, update_handle: int, description: str) -> bool: """Set the description of a Workshop item @@ -182,10 +237,11 @@ def SetItemDescription(self, update_handle: int, description: str) -> bool: :return: bool """ if len(description) > 8000: - raise AttributeError('description exceeds 8000 characters') - - return self.steam.Workshop_SetItemDescription(update_handle, description.encode()) + raise AttributeError("description exceeds 8000 characters") + return self.steam.Workshop_SetItemDescription( + update_handle, description.encode() + ) def SetItemTags(self, update_handle: int, tags: list) -> bool: """Sets which tags apply to the Workshop item @@ -199,10 +255,13 @@ def SetItemTags(self, update_handle: int, tags: list) -> bool: for index, tag in enumerate(tags): pointer_storage[index] = tag.encode() - return self.steam.Workshop_SetItemTags(update_handle, pointer_storage, len(tags)) + return self.steam.Workshop_SetItemTags( + update_handle, pointer_storage, len(tags) + ) - - def SetItemVisibility(self, update_handle: int, vis: ERemoteStoragePublishedFileVisibility) -> bool: + def SetItemVisibility( + self, update_handle: int, vis: ERemoteStoragePublishedFileVisibility + ) -> bool: """Sets which users can see the Workshop item :param update_handle: int @@ -212,16 +271,16 @@ def SetItemVisibility(self, update_handle: int, vis: ERemoteStoragePublishedFile return self.steam.Workshop_SetItemVisibility(update_handle, vis.value) - def SetItemContent(self, update_handle: int, content_directory: str) -> bool: - """ Set the directory containing the content you wish to upload to Workshop + """Set the directory containing the content you wish to upload to Workshop :param update_handle: int :param content_directory: str :return: bool """ - return self.steam.Workshop_SetItemContent(update_handle, content_directory.encode()) - + return self.steam.Workshop_SetItemContent( + update_handle, content_directory.encode() + ) def SetItemPreview(self, update_handle: int, preview_image: str) -> bool: """Set the preview image of the Workshop item. @@ -232,9 +291,13 @@ def SetItemPreview(self, update_handle: int, preview_image: str) -> bool: """ return self.steam.Workshop_SetItemPreview(update_handle, preview_image.encode()) - - def SubmitItemUpdate(self, update_handle: int, change_note: str, callback: object = None, \ - override_callback: bool = False) -> None: + def SubmitItemUpdate( + self, + update_handle: int, + change_note: str, + callback: object = None, + override_callback: bool = False, + ) -> None: """Submit the item update with the given handle to Steam :param update_handle: int @@ -256,7 +319,6 @@ def SubmitItemUpdate(self, update_handle: int, change_note: str, callback: objec self.steam.Workshop_SubmitItemUpdate(update_handle, change_note) - def GetItemUpdateProgress(self, update_handle: int) -> dict: """Get the progress of an item update request @@ -267,18 +329,16 @@ def GetItemUpdateProgress(self, update_handle: int) -> dict: punBytesTotal = c_uint64() update_status = self.steam.Workshop_GetItemUpdateProgress( - update_handle, - pointer(punBytesProcessed), - pointer(punBytesTotal)) + update_handle, pointer(punBytesProcessed), pointer(punBytesTotal) + ) return { - 'status' : EItemUpdateStatus(update_status), - 'processed' : punBytesProcessed.value, - 'total' : punBytesTotal.value, - 'progress' : ( punBytesProcessed.value / (punBytesTotal.value or 1) ) + "status": EItemUpdateStatus(update_status), + "processed": punBytesProcessed.value, + "total": punBytesTotal.value, + "progress": (punBytesProcessed.value / (punBytesTotal.value or 1)), } - def GetNumSubscribedItems(self) -> int: """Get the total number of items the user is subscribed to for this game or application @@ -286,7 +346,6 @@ def GetNumSubscribedItems(self) -> int: """ return self.steam.Workshop_GetNumSubscribedItems() - def SuspendDownloads(self, paused: bool = True) -> None: """Suspend active workshop downloads @@ -295,7 +354,6 @@ def SuspendDownloads(self, paused: bool = True) -> None: """ return self.steam.Workshop_SuspendDownloads(paused) - def GetSubscribedItems(self, max_items: int = 0) -> list: """Get a list of published file IDs that the user is subscribed to @@ -313,14 +371,15 @@ def GetSubscribedItems(self, max_items: int = 0) -> list: # TODO: We might need to add an exception check here to catch any errors while # writing to the 'pvecPublishedFileIds' array. - actual_item_count = self.steam.Workshop_GetSubscribedItems(published_files, max_items) + actual_item_count = self.steam.Workshop_GetSubscribedItems( + published_files, max_items + ) # According to sdk's example, it is possible for numItems to be greater than maxEntries so we crop. if actual_item_count > max_items: published_files = published_files[:max_items] return published_files - def GetItemState(self, published_file_id: int) -> EItemState: """Get the current state of a workshop item @@ -329,8 +388,9 @@ def GetItemState(self, published_file_id: int) -> EItemState: """ return EItemState(self.steam.Workshop_GetItemState(published_file_id)) - - def GetItemInstallInfo(self, published_file_id: int, max_path_length: int = 1024) -> dict: + def GetItemInstallInfo( + self, published_file_id: int, max_path_length: int = 1024 + ) -> dict: """Get info about an installed item :param published_file_id: int @@ -342,18 +402,18 @@ def GetItemInstallInfo(self, published_file_id: int, max_path_length: int = 1024 pchFolder = create_string_buffer(max_path_length) is_installed = self.steam.Workshop_GetItemInstallInfo( - published_file_id, punSizeOnDisk, pchFolder, max_path_length, punTimeStamp) + published_file_id, punSizeOnDisk, pchFolder, max_path_length, punTimeStamp + ) if not is_installed: return {} return { - 'disk_size' : punSizeOnDisk, - 'folder' : pchFolder.value.decode(), - 'timestamp' : punTimeStamp.contents.value + "disk_size": punSizeOnDisk, + "folder": pchFolder.value.decode(), + "timestamp": punTimeStamp.contents.value, } - def GetItemDownloadInfo(self, published_file_id: int) -> dict: """Get download info for a subscribed item @@ -364,15 +424,17 @@ def GetItemDownloadInfo(self, published_file_id: int) -> dict: punBytesTotal = pointer(c_uint64(0)) # pBytesTotal will only be valid after the download has started. - available = self.steam.Workshop_GetItemDownloadInfo(published_file_id, punBytesDownloaded, punBytesTotal) + available = self.steam.Workshop_GetItemDownloadInfo( + published_file_id, punBytesDownloaded, punBytesTotal + ) if available: downloaded = punBytesDownloaded.contents.value total = punBytesTotal.contents.value return { - 'downloaded' : downloaded, - 'total' : total, - 'progress' : 0.0 if total <= 0 else ( downloaded / total ) + "downloaded": downloaded, + "total": total, + "progress": 0.0 if total <= 0 else (downloaded / total), } return {} diff --git a/steamworks/methods.py b/steamworks/methods.py index 9567d8e..b6f4452 100644 --- a/steamworks/methods.py +++ b/steamworks/methods.py @@ -6,463 +6,207 @@ # (ala CFUNCTYPE vs WINFUNCTYPE), but so far even on Win64, it's all cdecl MAKE_CALLBACK = CFUNCTYPE + class InputAnalogActionData_t(Structure): - _fields_ = [('eMode', c_uint32), - ('x', c_float), - ('y', c_float), - ('bActive', c_bool)] + _fields_ = [ + ("eMode", c_uint32), + ("x", c_float), + ("y", c_float), + ("bActive", c_bool), + ] + class InputDigitalActionData_t(Structure): - _fields_ = [('bState', c_bool), - ('bActive', c_bool)] + _fields_ = [("bState", c_bool), ("bActive", c_bool)] STEAMWORKS_METHODS = { - 'SteamShutdown': { - 'restype': None - }, - 'RestartAppIfNecessary': { - 'restype': bool - }, - 'IsSteamRunning': { - 'restype': c_bool - }, - 'IsSubscribed': { - 'restype': bool - }, - 'IsLowViolence': { - 'restype': bool - }, - 'IsCybercafe': { - 'restype': bool - }, - 'IsVACBanned': { - 'restype': bool - }, - 'GetCurrentGameLanguage': { - 'restype': c_char_p - }, - 'GetAvailableGameLanguages': { - 'restype': c_char_p - }, - 'IsSubscribedApp': { - 'restype': bool - }, - 'IsDLCInstalled': { - 'restype': bool - }, - 'GetEarliestPurchaseUnixTime': { - 'restype': int - }, - 'IsSubscribedFromFreeWeekend': { - 'restype': bool - }, - 'GetDLCCount': { - 'restype': int - }, - 'InstallDLC': { - 'restype': None - }, - 'UninstallDLC': { - 'restype': None - }, - 'MarkContentCorrupt': { - 'restype': bool - }, - 'GetAppInstallDir': { - 'restype': c_char_p - }, - 'IsAppInstalled': { - 'restype': bool - }, - 'GetAppOwner': { - 'restype': int - }, - 'GetLaunchQueryParam': { - 'restype': c_char_p - }, - 'GetAppBuildId': { - 'restype': int - }, - 'GetFileDetails': { - 'restype': None - }, - 'GetFriendCount': { - 'restype': int - }, - 'GetFriendByIndex': { - 'restype': c_uint64 - }, - 'GetPersonaName': { - 'restype': c_char_p - }, - 'GetPersonaState': { - 'restype': int - }, - 'GetFriendPersonaName': { - 'restype': c_char_p, - 'argtypes': [c_uint64] - }, - 'SetGameInfo': { - 'restype': None - }, - 'ClearGameInfo': { - 'restype': None - }, - 'InviteFriend': { - 'restype': None - }, - 'SetPlayedWith': { - 'restype': None - }, - 'ActivateGameOverlay': { - 'restype': None, - 'argtypes': [c_char_p] - }, - 'ActivateGameOverlayToUser': { - 'restype': None, - 'argtypes': [c_char_p, c_uint32] - }, - 'ActivateGameOverlayToWebPage': { - 'restype': None, - 'argtypes': [c_char_p] - }, - 'ActivateGameOverlayToStore': { - 'restype': None, - 'argtypes': [c_uint32] - }, - 'ActivateGameOverlayInviteDialog': { - 'restype': None, - 'argtypes': [c_uint64] - }, - 'ActivateActionSet': { - 'restype': None, - 'argtypes': [c_uint64, c_uint64] - }, - 'GetActionSetHandle': { - 'restype': c_uint64, - 'argtypes': [c_char_p] - }, - 'GetAnalogActionHandle': { - 'restype': c_uint64, - 'argtypes': [c_char_p] - }, - 'GetAnalogActionData': { - 'restype': InputAnalogActionData_t, - 'argtypes': [c_uint64, c_uint64] - }, - 'GetControllerForGamepadIndex': { - 'restype': c_uint64 - }, - 'GetCurrentActionSet': { - 'restype': c_uint64 - }, - 'GetConnectedControllers': { - 'restype': POINTER(c_uint64) - }, - 'GetInputTypeForHandle': { - 'restype': c_uint64 - }, - 'GetDigitalActionHandle': { - 'restype': c_uint64 - }, - 'GetDigitalActionData': { - 'restype': InputDigitalActionData_t, - 'argtypes': [c_uint64, c_uint64] - }, - 'GetGamepadIndexForController': { - 'restype': int - }, - 'ControllerInit': { - 'restype': bool, - 'argtypes': [c_bool] - }, - 'RunFrame': { - 'restype': None - }, - 'ShowBindingPanel': { - 'restype': bool - }, - 'ControllerShutdown': { - 'restype': bool - }, - 'TriggerVibration': { - 'restype': None - }, - 'CreateLobby': { - 'restype': None, - 'argtypes': [c_uint64, c_uint64] - }, - 'JoinLobby': { - 'restype': None, - 'argtypes': [c_uint64] - }, - 'LeaveLobby': { - 'restype': None, - 'argtypes': [c_uint64] - }, - 'InviteUserToLobby': { - 'restype': None, - 'argtypes': [c_uint64, c_uint64] - }, - 'MusicIsEnabled': { - 'restype': None - }, - 'MusicIsPlaying': { - 'restype': None - }, - 'MusicGetVolume': { - 'restype': c_float - }, - 'MusicPause': { - 'restype': None - }, - 'MusicPlay': { - 'restype': None - }, - 'MusicPlayNext': { - 'restype': None - }, - 'MusicPlayPrev': { - 'restype': None - }, - 'MusicSetVolume': { - 'restype': None - }, - 'AddScreenshotToLibrary': { - 'restype': c_uint32 - }, - 'HookScreenshots': { - 'restype': None - }, - 'IsScreenshotsHooked': { - 'restype': bool - }, - 'SetLocation': { - 'restype': None - }, - 'TriggerScreenshot': { - 'restype': None - }, - 'GetSteamID': { - 'restype': c_uint64 - }, - 'LoggedOn': { - 'restype': bool - }, - 'GetPlayerSteamLevel': { - 'restype': int - }, - 'GetUserDataFolder': { - 'restype': c_char_p - }, - 'GetGameBadgeLevel': { - 'restype': int - }, - 'GetAuthSessionTicket': { - 'restype': c_int, - 'argtypes': [c_char_p] - }, - 'GetAchievement': { - 'restype': bool - }, - 'GetNumAchievements': { - 'restype': int - }, - 'GetAchievementName': { - 'restype': c_char_p - }, - 'GetAchievementDisplayAttribute': { - 'restype': c_char_p - }, - 'GetStatInt': { - 'restype': int - }, - 'GetStatFloat': { - 'restype': c_float - }, - 'ResetAllStats': { - 'restype': bool - }, - 'RequestCurrentStats': { - 'restype': bool - }, - 'SetAchievement': { - 'restype': bool - }, - 'SetStatInt': { - 'restype': bool - }, - 'SetStatFloat': { - 'restype': bool - }, - 'StoreStats': { - 'restype': bool - }, - 'ClearAchievement': { - 'restype': bool - }, - 'Leaderboard_FindLeaderboard': { - 'restype': bool, - 'argtypes': [c_char_p] - }, - 'OverlayNeedsPresent': { - 'restype': bool - }, - 'GetAppID': { - 'restype': int - }, - 'GetCurrentBatteryPower': { - 'restype': int - }, - 'GetIPCCallCount': { - 'restype': c_uint32 - }, - 'GetIPCountry': { - 'restype': c_char_p - }, - 'GetSecondsSinceAppActive': { - 'restype': int - }, - 'GetSecondsSinceComputerActive': { - 'restype': int - }, - 'GetServerRealTime': { - 'restype': int - }, - 'GetSteamUILanguage': { - 'restype': c_char_p - }, - 'IsOverlayEnabled': { - 'restype': bool - }, - 'IsSteamInBigPictureMode': { - 'restype': bool - }, - 'IsSteamRunningInVR': { - 'restype': bool - }, - 'IsVRHeadsetStreamingEnabled': { - 'restype': bool - }, - 'SetOverlayNotificationInset': { - 'restype': None - }, - 'SetOverlayNotificationPosition': { - 'restype': None - }, - 'SetVRHeadsetStreamingEnabled': { - 'restype': None - }, - 'ShowGamepadTextInput': { - 'restype': bool - }, - 'StartVRDashboard': { - 'restype': None - }, - 'Workshop_SetItemCreatedCallback': { - 'restype': None, - 'argtypes': [MAKE_CALLBACK(None, structs.CreateItemResult_t)] - }, - 'Workshop_CreateItem': { - 'restype': None, - 'argtypes': [c_uint32, c_int32] - }, - 'Workshop_SetItemUpdatedCallback': { - 'restype': None, - 'argtypes': [MAKE_CALLBACK(None, structs.SubmitItemUpdateResult_t)] - }, - 'Workshop_StartItemUpdate': { - 'restype': c_uint64, - 'argtypes': [c_uint32, c_uint64] - }, - 'Workshop_SetItemTitle': { - 'restype': bool, - 'argtypes': [c_uint64, c_char_p] - }, - 'Workshop_SetItemDescription': { - 'restype': bool, - 'argtypes': [c_uint64, c_char_p] - }, - 'Workshop_SetItemUpdateLanguage': { - 'restype': bool, - 'argtypes': [c_uint64, c_char_p] - }, - 'Workshop_SetItemMetadata': { - 'restype': bool, - 'argtypes': [c_uint64, c_char_p] - }, - 'Workshop_SetItemVisibility': { - 'restype': bool, - 'argtypes': [c_uint64, c_int32] - }, - 'Workshop_SetItemTags': { - 'restype': bool, - 'argtypes': [c_uint64, POINTER(c_char_p), c_int32] - }, - 'Workshop_SetItemContent': { - 'restype': bool, - 'argtypes': [c_uint64, c_char_p] - }, - 'Workshop_SetItemPreview': { - 'restype': bool, - 'argtypes': [c_uint64, c_char_p] - }, - 'Workshop_SubmitItemUpdate': { - 'argtypes': [c_uint64, c_char_p] - }, - 'Workshop_GetItemUpdateProgress': { - 'restype': c_int32, - 'argtypes': [c_uint64, POINTER(c_uint64), POINTER(c_uint64)] - }, - 'Workshop_GetNumSubscribedItems': { - 'restype': c_uint32 - }, - 'Workshop_GetSubscribedItems': { - 'restype': c_uint32, - 'argtypes': [POINTER(c_uint64), c_uint32] - }, - 'Workshop_GetItemState': { - 'restype': c_uint32, - 'argtypes': [c_uint64] - }, - 'Workshop_GetItemInstallInfo': { - 'restype': bool, - 'argtypes': [c_uint64, POINTER(c_uint64), c_char_p, c_uint32, POINTER(c_uint32)] - }, - 'Workshop_GetItemDownloadInfo': { - 'restype': bool, - 'argtypes': [c_uint64, POINTER(c_uint64), POINTER(c_uint64)] - }, - 'Workshop_SetItemInstalledCallback': { - 'restype': None, - 'argtypes': [MAKE_CALLBACK(None, structs.ItemInstalled_t)] - }, - 'Workshop_ClearItemInstalledCallback': { - 'restype': None - }, - 'Workshop_SetItemSubscribedCallback': { - 'restype': None, - 'argtypes': [MAKE_CALLBACK(None, structs.SubscriptionResult)] - }, - 'Workshop_SetItemUnsubscribedCallback': { - 'restype': None, - 'argtypes': [MAKE_CALLBACK(None, structs.SubscriptionResult)] - }, - 'Workshop_SuspendDownloads': { - 'restype': None, - 'argtypes': [c_bool] - }, - 'Workshop_SubscribeItem': { - 'restype': None, - 'argtypes': [c_uint64] - }, - 'Workshop_UnsubscribeItem': { - 'restype': None, - 'argtypes': [c_uint64] - }, - 'MicroTxn_SetAuthorizationResponseCallback': { - 'restype': None, - 'argtypes': [MAKE_CALLBACK(None, structs.MicroTxnAuthorizationResponse_t)] + "SteamShutdown": {"restype": None}, + "RestartAppIfNecessary": {"restype": bool}, + "IsSteamRunning": {"restype": c_bool}, + "IsSubscribed": {"restype": bool}, + "IsLowViolence": {"restype": bool}, + "IsCybercafe": {"restype": bool}, + "IsVACBanned": {"restype": bool}, + "GetCurrentGameLanguage": {"restype": c_char_p}, + "GetAvailableGameLanguages": {"restype": c_char_p}, + "IsSubscribedApp": {"restype": bool}, + "IsDLCInstalled": {"restype": bool}, + "GetEarliestPurchaseUnixTime": {"restype": int}, + "IsSubscribedFromFreeWeekend": {"restype": bool}, + "GetDLCCount": {"restype": int}, + "InstallDLC": {"restype": None}, + "UninstallDLC": {"restype": None}, + "MarkContentCorrupt": {"restype": bool}, + "GetAppInstallDir": {"restype": c_char_p}, + "IsAppInstalled": {"restype": bool}, + "GetAppOwner": {"restype": int}, + "GetLaunchQueryParam": {"restype": c_char_p}, + "GetAppBuildId": {"restype": int}, + "GetFileDetails": {"restype": None}, + "GetFriendCount": {"restype": int}, + "GetFriendByIndex": {"restype": c_uint64}, + "GetPersonaName": {"restype": c_char_p}, + "GetPersonaState": {"restype": int}, + "GetFriendPersonaName": {"restype": c_char_p, "argtypes": [c_uint64]}, + "SetGameInfo": {"restype": None}, + "ClearGameInfo": {"restype": None}, + "InviteFriend": {"restype": None}, + "SetPlayedWith": {"restype": None}, + "ActivateGameOverlay": {"restype": None, "argtypes": [c_char_p]}, + "ActivateGameOverlayToUser": {"restype": None, "argtypes": [c_char_p, c_uint32]}, + "ActivateGameOverlayToWebPage": {"restype": None, "argtypes": [c_char_p]}, + "ActivateGameOverlayToStore": {"restype": None, "argtypes": [c_uint32]}, + "ActivateGameOverlayInviteDialog": {"restype": None, "argtypes": [c_uint64]}, + "ActivateActionSet": {"restype": None, "argtypes": [c_uint64, c_uint64]}, + "GetActionSetHandle": {"restype": c_uint64, "argtypes": [c_char_p]}, + "GetAnalogActionHandle": {"restype": c_uint64, "argtypes": [c_char_p]}, + "GetAnalogActionData": { + "restype": InputAnalogActionData_t, + "argtypes": [c_uint64, c_uint64], + }, + "GetControllerForGamepadIndex": {"restype": c_uint64}, + "GetCurrentActionSet": {"restype": c_uint64}, + "GetConnectedControllers": {"restype": POINTER(c_uint64)}, + "GetInputTypeForHandle": {"restype": c_uint64}, + "GetDigitalActionHandle": {"restype": c_uint64}, + "GetDigitalActionData": { + "restype": InputDigitalActionData_t, + "argtypes": [c_uint64, c_uint64], + }, + "GetGamepadIndexForController": {"restype": int}, + "ControllerInit": {"restype": bool, "argtypes": [c_bool]}, + "RunFrame": {"restype": None}, + "ShowBindingPanel": {"restype": bool}, + "ControllerShutdown": {"restype": bool}, + "TriggerVibration": {"restype": None}, + "CreateLobby": {"restype": None, "argtypes": [c_uint64, c_uint64]}, + "JoinLobby": {"restype": None, "argtypes": [c_uint64]}, + "LeaveLobby": {"restype": None, "argtypes": [c_uint64]}, + "InviteUserToLobby": {"restype": None, "argtypes": [c_uint64, c_uint64]}, + "MusicIsEnabled": {"restype": None}, + "MusicIsPlaying": {"restype": None}, + "MusicGetVolume": {"restype": c_float}, + "MusicPause": {"restype": None}, + "MusicPlay": {"restype": None}, + "MusicPlayNext": {"restype": None}, + "MusicPlayPrev": {"restype": None}, + "MusicSetVolume": {"restype": None}, + "AddScreenshotToLibrary": {"restype": c_uint32}, + "HookScreenshots": {"restype": None}, + "IsScreenshotsHooked": {"restype": bool}, + "SetLocation": {"restype": None}, + "TriggerScreenshot": {"restype": None}, + "GetSteamID": {"restype": c_uint64}, + "LoggedOn": {"restype": bool}, + "GetPlayerSteamLevel": {"restype": int}, + "GetUserDataFolder": {"restype": c_char_p}, + "GetGameBadgeLevel": {"restype": int}, + "GetAuthSessionTicket": {"restype": c_int, "argtypes": [c_char_p]}, + "GetAchievement": {"restype": bool}, + "GetNumAchievements": {"restype": int}, + "GetAchievementName": {"restype": c_char_p}, + "GetAchievementDisplayAttribute": {"restype": c_char_p}, + "GetStatInt": {"restype": int}, + "GetStatFloat": {"restype": c_float}, + "ResetAllStats": {"restype": bool}, + "RequestCurrentStats": {"restype": bool}, + "SetAchievement": {"restype": bool}, + "SetStatInt": {"restype": bool}, + "SetStatFloat": {"restype": bool}, + "StoreStats": {"restype": bool}, + "ClearAchievement": {"restype": bool}, + "Leaderboard_FindLeaderboard": {"restype": bool, "argtypes": [c_char_p]}, + "OverlayNeedsPresent": {"restype": bool}, + "GetAppID": {"restype": int}, + "GetCurrentBatteryPower": {"restype": int}, + "GetIPCCallCount": {"restype": c_uint32}, + "GetIPCountry": {"restype": c_char_p}, + "GetSecondsSinceAppActive": {"restype": int}, + "GetSecondsSinceComputerActive": {"restype": int}, + "GetServerRealTime": {"restype": int}, + "GetSteamUILanguage": {"restype": c_char_p}, + "IsOverlayEnabled": {"restype": bool}, + "IsSteamInBigPictureMode": {"restype": bool}, + "IsSteamRunningInVR": {"restype": bool}, + "IsVRHeadsetStreamingEnabled": {"restype": bool}, + "SetOverlayNotificationInset": {"restype": None}, + "SetOverlayNotificationPosition": {"restype": None}, + "SetVRHeadsetStreamingEnabled": {"restype": None}, + "ShowGamepadTextInput": {"restype": bool}, + "StartVRDashboard": {"restype": None}, + "Workshop_SetItemCreatedCallback": { + "restype": None, + "argtypes": [MAKE_CALLBACK(None, structs.CreateItemResult_t)], + }, + "Workshop_CreateItem": {"restype": None, "argtypes": [c_uint32, c_int32]}, + "Workshop_SetItemUpdatedCallback": { + "restype": None, + "argtypes": [MAKE_CALLBACK(None, structs.SubmitItemUpdateResult_t)], + }, + "Workshop_StartItemUpdate": {"restype": c_uint64, "argtypes": [c_uint32, c_uint64]}, + "Workshop_SetItemTitle": {"restype": bool, "argtypes": [c_uint64, c_char_p]}, + "Workshop_SetItemDescription": {"restype": bool, "argtypes": [c_uint64, c_char_p]}, + "Workshop_SetItemUpdateLanguage": { + "restype": bool, + "argtypes": [c_uint64, c_char_p], + }, + "Workshop_SetItemMetadata": {"restype": bool, "argtypes": [c_uint64, c_char_p]}, + "Workshop_SetItemVisibility": {"restype": bool, "argtypes": [c_uint64, c_int32]}, + "Workshop_SetItemTags": { + "restype": bool, + "argtypes": [c_uint64, POINTER(c_char_p), c_int32], + }, + "Workshop_SetItemContent": {"restype": bool, "argtypes": [c_uint64, c_char_p]}, + "Workshop_SetItemPreview": {"restype": bool, "argtypes": [c_uint64, c_char_p]}, + "Workshop_SubmitItemUpdate": {"argtypes": [c_uint64, c_char_p]}, + "Workshop_GetItemUpdateProgress": { + "restype": c_int32, + "argtypes": [c_uint64, POINTER(c_uint64), POINTER(c_uint64)], + }, + "Workshop_GetNumSubscribedItems": {"restype": c_uint32}, + "Workshop_GetSubscribedItems": { + "restype": c_uint32, + "argtypes": [POINTER(c_uint64), c_uint32], + }, + "Workshop_GetItemState": {"restype": c_uint32, "argtypes": [c_uint64]}, + "Workshop_GetItemInstallInfo": { + "restype": bool, + "argtypes": [ + c_uint64, + POINTER(c_uint64), + c_char_p, + c_uint32, + POINTER(c_uint32), + ], + }, + "Workshop_GetAppDependencies": {"restype": bool, "argtypes": [c_uint64]}, + "Workshop_GetItemDownloadInfo": { + "restype": bool, + "argtypes": [c_uint64, POINTER(c_uint64), POINTER(c_uint64)], + }, + "Workshop_SetItemInstalledCallback": { + "restype": None, + "argtypes": [MAKE_CALLBACK(None, structs.ItemInstalled_t)], + }, + "Workshop_ClearItemInstalledCallback": {"restype": None}, + "Workshop_SetGetAppDependenciesResultCallback": { + "restype": None, + "argtypes": [MAKE_CALLBACK(None, structs.GetAppDependenciesResult)], + }, + "Workshop_SetItemSubscribedCallback": { + "restype": None, + "argtypes": [MAKE_CALLBACK(None, structs.SubscriptionResult)], + }, + "Workshop_SetItemUnsubscribedCallback": { + "restype": None, + "argtypes": [MAKE_CALLBACK(None, structs.SubscriptionResult)], + }, + "Workshop_SuspendDownloads": {"restype": None, "argtypes": [c_bool]}, + "Workshop_SubscribeItem": {"restype": None, "argtypes": [c_uint64]}, + "Workshop_UnsubscribeItem": {"restype": None, "argtypes": [c_uint64]}, + "MicroTxn_SetAuthorizationResponseCallback": { + "restype": None, + "argtypes": [MAKE_CALLBACK(None, structs.MicroTxnAuthorizationResponse_t)], }, } diff --git a/steamworks/structs.py b/steamworks/structs.py index 7f6f7b4..e93a432 100644 --- a/steamworks/structs.py +++ b/steamworks/structs.py @@ -2,18 +2,16 @@ class FindLeaderboardResult_t(Structure): - """ Represents the STEAMWORKS LeaderboardFindResult_t call result type """ - _fields_ = [ - ("leaderboardHandle", c_uint64), - ("leaderboardFound", c_uint32) - ] + """Represents the STEAMWORKS LeaderboardFindResult_t call result type""" + + _fields_ = [("leaderboardHandle", c_uint64), ("leaderboardFound", c_uint32)] class CreateItemResult_t(Structure): _fields_ = [ ("result", c_int), ("publishedFileId", c_uint64), - ("userNeedsToAcceptWorkshopLegalAgreement", c_bool) + ("userNeedsToAcceptWorkshopLegalAgreement", c_bool), ] @@ -21,26 +19,35 @@ class SubmitItemUpdateResult_t(Structure): _fields_ = [ ("result", c_int), ("userNeedsToAcceptWorkshopLegalAgreement", c_bool), - ("publishedFileId", c_uint64) + ("publishedFileId", c_uint64), ] class ItemInstalled_t(Structure): - _fields_ = [ - ("appId", c_uint32), - ("publishedFileId", c_uint64) - ] + _fields_ = [("appId", c_uint32), ("publishedFileId", c_uint64)] -class SubscriptionResult(Structure): +class GetAppDependenciesResult(Structure): _fields_ = [ ("result", c_int32), - ("publishedFileId", c_uint64) + ("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), ("publishedFileId", c_uint64)] + + class MicroTxnAuthorizationResponse_t(Structure): - _fields_ = [ - ("appId", c_uint32), - ("orderId", c_uint64), - ("authorized", c_bool) - ] + _fields_ = [("appId", c_uint32), ("orderId", c_uint64), ("authorized", c_bool)] From 2c602345bd89572d45698e33626e5faabe2e9650 Mon Sep 17 00:00:00 2001 From: twstagg Date: Thu, 25 May 2023 11:11:51 -0500 Subject: [PATCH 3/4] Fix compile warnings on MacOS --- library/SteamworksPy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/SteamworksPy.cpp b/library/SteamworksPy.cpp index 48811d9..b23273e 100644 --- a/library/SteamworksPy.cpp +++ b/library/SteamworksPy.cpp @@ -472,7 +472,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; } @@ -1001,7 +1001,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; } From 2b14087a0aac1cc589ae7c804567414a6cfdcf1c Mon Sep 17 00:00:00 2001 From: twstagg Date: Sat, 25 Nov 2023 10:00:35 -0600 Subject: [PATCH 4/4] Allow passing _libs path param to preload libs, or rely on SWPY_PATH env var --- steamworks/__init__.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/steamworks/__init__.py b/steamworks/__init__.py index fc4f7ec..8094a67 100644 --- a/steamworks/__init__.py +++ b/steamworks/__init__.py @@ -45,13 +45,15 @@ class STEAMWORKS(object): _arch = steamworks_util.get_arch() _native_supported_platforms = ["linux", "linux2", "darwin", "win32"] - def __init__(self, supported_platforms: list = []) -> None: + def __init__(self, supported_platforms: list = [], _libs=None) -> None: self._supported_platforms = supported_platforms self._loaded = False self._cdll = None self.app_id = 0 + self._libs = _libs + self._initialize() def _initialize(self) -> bool: @@ -69,7 +71,17 @@ def _initialize(self) -> bool: library_file_name = "" if platform in ["linux", "linux2"]: library_file_name = "SteamworksPy.so" - if os.path.isfile(os.path.join(os.getcwd(), "libsteam_api.so")): + if self._libs and os.path.exists( + os.path.join(self._libs, "libsteam_api.so") + ): + cdll.LoadLibrary(os.path.join(self._libs, "libsteam_api.so")) + elif os.environ.get("SWPY_PATH") and os.path.exists( + os.path.join(os.environ["SWPY_PATH"], "libsteam_api.so") + ): + cdll.LoadLibrary( + os.path.join(os.environ["SWPY_PATH"], "libsteam_api.so") + ) + elif os.path.isfile(os.path.join(os.getcwd(), "libsteam_api.so")): cdll.LoadLibrary( os.path.join(os.getcwd(), "libsteam_api.so") ) # if i do this then linux works @@ -106,8 +118,13 @@ def _initialize(self) -> bool: else: # This case is theoretically unreachable raise UnsupportedPlatformException(f'"{platform}" is not being supported') - - if os.path.isfile(os.path.join(os.getcwd(), library_file_name)): + if self._libs and os.path.exists(os.path.join(self._libs, library_file_name)): + library_path = os.path.join(self._libs, library_file_name) + elif os.environ.get("SWPY_PATH") and os.path.exists( + os.path.join(os.environ["SWPY_PATH"], library_file_name) + ): + library_path = os.path.join(os.environ["SWPY_PATH"], library_file_name) + elif os.path.isfile(os.path.join(os.getcwd(), library_file_name)): 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)