using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; namespace OmegaLeo.HelperLib.Steamworks { internal readonly struct SteamNativeCallbackMessage { public SteamNativeCallbackMessage(int steamUser, int callbackId, IntPtr param, int paramSize) { SteamUser = steamUser; CallbackId = callbackId; Param = param; ParamSize = paramSize; } public int SteamUser { get; } public int CallbackId { get; } public IntPtr Param { get; } public int ParamSize { get; } } internal static class SteamNativeApi { [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamApiInitDelegate(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int SteamApiInitFlatDelegate(IntPtr errorMessage); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamApiRestartAppIfNecessaryDelegate(uint appId); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamApiVoidDelegate(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int SteamApiGetHSteamPipeDelegate(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamApiManualDispatchPipeDelegate(int steamPipe); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamApiManualDispatchGetNextCallbackDelegate(int steamPipe, IntPtr callbackMessage); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamApiManualDispatchGetApiCallResultDelegate(int steamPipe, ulong apiCall, IntPtr callbackBuffer, int callbackSize, int callbackExpected, [MarshalAs(UnmanagedType.I1)] out bool failed); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamAppsBoolDelegate(IntPtr steamApps); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamAppsAppIdBoolDelegate(IntPtr steamApps, uint appId); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr SteamAppsStringDelegate(IntPtr steamApps); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int SteamAppsGetAppBuildIdDelegate(IntPtr steamApps); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr SteamFriendsAccessorDelegate(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr SteamUserStatsAccessorDelegate(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr SteamUgcAccessorDelegate(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr SteamRemotePlayAccessorDelegate(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr SteamInterfaceAccessorDelegate(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr SteamFriendsGetPersonaNameDelegate(IntPtr steamFriends); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUserStatsSetAchievementDelegate(IntPtr steamUserStats, [MarshalAs(UnmanagedType.LPStr)] string achievementId); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUserStatsStoreStatsDelegate(IntPtr steamUserStats); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUserStatsGetAchievementDelegate(IntPtr steamUserStats, [MarshalAs(UnmanagedType.LPStr)] string achievementId, [MarshalAs(UnmanagedType.I1)] out bool achieved); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong SteamUgcCreateQueryAllRequestDelegate(IntPtr steamUgc, uint queryType, uint matchingType, uint creatorAppId, uint consumerAppId, uint page); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong SteamUgcSendQueryRequestDelegate(IntPtr steamUgc, ulong queryHandle); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUgcReleaseQueryRequestDelegate(IntPtr steamUgc, ulong queryHandle); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint SteamUgcGetNumSubscribedItemsDelegate(IntPtr steamUgc); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint SteamUgcGetSubscribedItemsDelegate(IntPtr steamUgc, [Out] ulong[] items, uint maxEntries); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong SteamUgcSubscribeItemDelegate(IntPtr steamUgc, ulong publishedFileId); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong SteamUgcUnsubscribeItemDelegate(IntPtr steamUgc, ulong publishedFileId); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong SteamUgcCreateItemDelegate(IntPtr steamUgc, uint consumerAppId, uint fileType); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong SteamUgcStartItemUpdateDelegate(IntPtr steamUgc, uint consumerAppId, ulong publishedFileId); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUgcSetItemTitleDelegate(IntPtr steamUgc, ulong updateHandle, [MarshalAs(UnmanagedType.LPStr)] string title); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUgcSetItemDescriptionDelegate(IntPtr steamUgc, ulong updateHandle, [MarshalAs(UnmanagedType.LPStr)] string description); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUgcSetItemMetadataDelegate(IntPtr steamUgc, ulong updateHandle, [MarshalAs(UnmanagedType.LPStr)] string metadata); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUgcSetItemVisibilityDelegate(IntPtr steamUgc, ulong updateHandle, uint visibility); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUgcSetItemTagsDelegate(IntPtr steamUgc, ulong updateHandle, IntPtr tags); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUgcSetItemContentDelegate(IntPtr steamUgc, ulong updateHandle, [MarshalAs(UnmanagedType.LPStr)] string contentFolder); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUgcSetItemPreviewDelegate(IntPtr steamUgc, ulong updateHandle, [MarshalAs(UnmanagedType.LPStr)] string previewFile); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong SteamUgcSubmitItemUpdateDelegate(IntPtr steamUgc, ulong updateHandle, [MarshalAs(UnmanagedType.LPStr)] string changeNote); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint SteamUgcGetItemUpdateProgressDelegate(IntPtr steamUgc, ulong updateHandle, out ulong bytesProcessed, out ulong bytesTotal); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint SteamRemotePlayGetSessionCountDelegate(IntPtr steamRemotePlay); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint SteamRemotePlayGetSessionIdDelegate(IntPtr steamRemotePlay, int sessionIndex); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong SteamRemotePlayGetSessionSteamIdDelegate(IntPtr steamRemotePlay, uint sessionId); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr SteamRemotePlayGetSessionClientNameDelegate(IntPtr steamRemotePlay, uint sessionId); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int SteamRemotePlayGetSessionClientFormFactorDelegate(IntPtr steamRemotePlay, uint sessionId); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamRemotePlayGetSessionClientResolutionDelegate(IntPtr steamRemotePlay, uint sessionId, out int resolutionX, out int resolutionY); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int SteamFriendsGetFriendCountDelegate(IntPtr steamFriends, int friendFlags); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr SteamFriendsGetFriendPersonaNameDelegate(IntPtr steamFriends, ulong steamIdFriend); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamFriendsActivateGameOverlayInviteDialogDelegate(IntPtr steamFriends, ulong steamIdLobby); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamFriendsSetRichPresenceDelegate(IntPtr steamFriends, [MarshalAs(UnmanagedType.LPStr)] string key, [MarshalAs(UnmanagedType.LPStr)] string value); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamFriendsClearRichPresenceDelegate(IntPtr steamFriends); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr SteamFriendsGetFriendRichPresenceDelegate(IntPtr steamFriends, ulong steamIdFriend, [MarshalAs(UnmanagedType.LPStr)] string key); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamFriendsInviteUserToGameDelegate(IntPtr steamFriends, ulong steamIdFriend, [MarshalAs(UnmanagedType.LPStr)] string connectString); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint SteamUtilsGetAppIdDelegate(IntPtr steamUtils); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr SteamUtilsGetIpCountryDelegate(IntPtr steamUtils); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUtilsIsOverlayEnabledDelegate(IntPtr steamUtils); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamRemoteStorageFileExistsDelegate(IntPtr steamRemoteStorage, [MarshalAs(UnmanagedType.LPStr)] string fileName); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int SteamRemoteStorageGetFileCountDelegate(IntPtr steamRemoteStorage); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamRemoteStorageIsCloudEnabledForAppDelegate(IntPtr steamRemoteStorage); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamRemoteStorageFileWriteDelegate(IntPtr steamRemoteStorage, [MarshalAs(UnmanagedType.LPStr)] string fileName, byte[] data, int dataSize); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int SteamRemoteStorageFileReadDelegate(IntPtr steamRemoteStorage, [MarshalAs(UnmanagedType.LPStr)] string fileName, [Out] byte[] dataBuffer, int dataToRead); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamRemoteStorageFileDeleteDelegate(IntPtr steamRemoteStorage, [MarshalAs(UnmanagedType.LPStr)] string fileName); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int SteamRemoteStorageGetFileSizeDelegate(IntPtr steamRemoteStorage, [MarshalAs(UnmanagedType.LPStr)] string fileName); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamInputInitDelegate(IntPtr steamInput, [MarshalAs(UnmanagedType.I1)] bool explicitlyCallRunFrame); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamInputShutdownDelegate(IntPtr steamInput); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamInputRunFrameDelegate(IntPtr steamInput, [MarshalAs(UnmanagedType.I1)] bool reservedValue); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamInventoryLoadItemDefinitionsDelegate(IntPtr steamInventory); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamInventoryGetAllItemsDelegate(IntPtr steamInventory, out int inventoryResultHandle); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamInventoryDestroyResultDelegate(IntPtr steamInventory, int inventoryResultHandle); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong SteamMatchmakingRequestLobbyListDelegate(IntPtr steamMatchmaking); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamMatchmakingAddRequestLobbyListResultCountFilterDelegate(IntPtr steamMatchmaking, int maxResults); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong SteamMatchmakingCreateLobbyDelegate(IntPtr steamMatchmaking, int lobbyType, int maxMembers); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong SteamMatchmakingJoinLobbyDelegate(IntPtr steamMatchmaking, ulong steamIdLobby); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamMatchmakingLeaveLobbyDelegate(IntPtr steamMatchmaking, ulong steamIdLobby); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int SteamMatchmakingGetNumLobbyMembersDelegate(IntPtr steamMatchmaking, ulong steamIdLobby); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint SteamScreenshotsAddScreenshotToLibraryDelegate(IntPtr steamScreenshots, [MarshalAs(UnmanagedType.LPStr)] string fileNameJpg, [MarshalAs(UnmanagedType.LPStr)] string fileNameThumbnail, int width, int height); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamScreenshotsTriggerScreenshotDelegate(IntPtr steamScreenshots); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamScreenshotsHookScreenshotsDelegate(IntPtr steamScreenshots, [MarshalAs(UnmanagedType.I1)] bool hook); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamScreenshotsIsScreenshotsHookedDelegate(IntPtr steamScreenshots); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamMusicIsEnabledDelegate(IntPtr steamMusic); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamMusicIsPlayingDelegate(IntPtr steamMusic); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamMusicPlayDelegate(IntPtr steamMusic); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamMusicPauseDelegate(IntPtr steamMusic); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamMusicPlayNextDelegate(IntPtr steamMusic); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamMusicPlayPreviousDelegate(IntPtr steamMusic); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate float SteamMusicGetVolumeDelegate(IntPtr steamMusic); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamMusicSetVolumeDelegate(IntPtr steamMusic, float volume); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SteamVideoGetVideoUrlDelegate(IntPtr steamVideo, uint videoAppId); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamVideoIsBroadcastingDelegate(IntPtr steamVideo, out int numberOfViewers); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong SteamUserStatsRequestUserStatsDelegate(IntPtr steamUserStats, ulong steamIdUser); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong SteamUserStatsRequestGlobalStatsDelegate(IntPtr steamUserStats, int historyDays); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUserStatsResetAllStatsDelegate(IntPtr steamUserStats, [MarshalAs(UnmanagedType.I1)] bool achievementsToo); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUserStatsGetStatInt32Delegate(IntPtr steamUserStats, [MarshalAs(UnmanagedType.LPStr)] string name, out int data); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private delegate bool SteamUserStatsSetStatInt32Delegate(IntPtr steamUserStats, [MarshalAs(UnmanagedType.LPStr)] string name, int data); private static readonly object SyncRoot = new object(); // SteamErrMsg is a char[1024] buffer in steam_api_common.h. private const int SteamErrMsgSize = 1024; // Large enough for CallbackMsg_t on 32-bit and 64-bit (24 bytes on x64). private const int CallbackMessageBufferSize = 64; private static IntPtr _libraryHandle; private static bool _libraryLoaded; private static bool _loadFailureLogged; private static bool _initialized; private static bool _manualDispatchEnabled = true; private static bool _manualDispatchActive; private static int _steamPipe; private static IntPtr _callbackMessageBuffer; private static readonly List CustomLibraryPaths = new List(); private static SteamApiInitFlatDelegate _steamApiInitFlat; private static SteamApiInitDelegate _steamApiInit; private static SteamApiRestartAppIfNecessaryDelegate _steamApiRestartAppIfNecessary; private static SteamApiVoidDelegate _steamApiShutdown; private static SteamApiVoidDelegate _steamApiRunCallbacks; private static SteamApiGetHSteamPipeDelegate _steamApiGetHSteamPipe; private static SteamApiVoidDelegate _steamApiManualDispatchInit; private static SteamApiManualDispatchPipeDelegate _steamApiManualDispatchRunFrame; private static SteamApiManualDispatchGetNextCallbackDelegate _steamApiManualDispatchGetNextCallback; private static SteamApiManualDispatchPipeDelegate _steamApiManualDispatchFreeLastCallback; private static SteamApiManualDispatchGetApiCallResultDelegate _steamApiManualDispatchGetApiCallResult; private static SteamAppsBoolDelegate _steamAppsIsSubscribed; private static SteamAppsAppIdBoolDelegate _steamAppsIsSubscribedApp; private static SteamAppsAppIdBoolDelegate _steamAppsIsDlcInstalled; private static SteamAppsAppIdBoolDelegate _steamAppsIsAppInstalled; private static SteamAppsStringDelegate _steamAppsGetCurrentGameLanguage; private static SteamAppsStringDelegate _steamAppsGetAvailableGameLanguages; private static SteamAppsGetAppBuildIdDelegate _steamAppsGetAppBuildId; private static SteamFriendsAccessorDelegate _steamFriendsAccessor; private static SteamUserStatsAccessorDelegate _steamUserStatsAccessor; private static SteamUgcAccessorDelegate _steamUgcAccessor; private static SteamRemotePlayAccessorDelegate _steamRemotePlayAccessor; private static SteamFriendsGetPersonaNameDelegate _steamFriendsGetPersonaName; private static SteamUserStatsSetAchievementDelegate _steamUserStatsSetAchievement; private static SteamUserStatsStoreStatsDelegate _steamUserStatsStoreStats; private static SteamUserStatsGetAchievementDelegate _steamUserStatsGetAchievement; private static SteamUgcCreateQueryAllRequestDelegate _steamUgcCreateQueryAllRequest; private static SteamUgcSendQueryRequestDelegate _steamUgcSendQueryRequest; private static SteamUgcReleaseQueryRequestDelegate _steamUgcReleaseQueryRequest; private static SteamUgcGetNumSubscribedItemsDelegate _steamUgcGetNumSubscribedItems; private static SteamUgcGetSubscribedItemsDelegate _steamUgcGetSubscribedItems; private static SteamUgcSubscribeItemDelegate _steamUgcSubscribeItem; private static SteamUgcUnsubscribeItemDelegate _steamUgcUnsubscribeItem; private static SteamUgcCreateItemDelegate _steamUgcCreateItem; private static SteamUgcStartItemUpdateDelegate _steamUgcStartItemUpdate; private static SteamUgcSetItemTitleDelegate _steamUgcSetItemTitle; private static SteamUgcSetItemDescriptionDelegate _steamUgcSetItemDescription; private static SteamUgcSetItemMetadataDelegate _steamUgcSetItemMetadata; private static SteamUgcSetItemVisibilityDelegate _steamUgcSetItemVisibility; private static SteamUgcSetItemTagsDelegate _steamUgcSetItemTags; private static SteamUgcSetItemContentDelegate _steamUgcSetItemContent; private static SteamUgcSetItemPreviewDelegate _steamUgcSetItemPreview; private static SteamUgcSubmitItemUpdateDelegate _steamUgcSubmitItemUpdate; private static SteamUgcGetItemUpdateProgressDelegate _steamUgcGetItemUpdateProgress; private static SteamRemotePlayGetSessionCountDelegate _steamRemotePlayGetSessionCount; private static SteamRemotePlayGetSessionIdDelegate _steamRemotePlayGetSessionId; private static SteamRemotePlayGetSessionSteamIdDelegate _steamRemotePlayGetSessionSteamId; private static SteamRemotePlayGetSessionClientNameDelegate _steamRemotePlayGetSessionClientName; private static SteamRemotePlayGetSessionClientFormFactorDelegate _steamRemotePlayGetSessionClientFormFactor; private static SteamRemotePlayGetSessionClientResolutionDelegate _steamRemotePlayGetSessionClientResolution; private static SteamFriendsGetFriendCountDelegate _steamFriendsGetFriendCount; private static SteamFriendsGetFriendPersonaNameDelegate _steamFriendsGetFriendPersonaName; private static SteamFriendsActivateGameOverlayInviteDialogDelegate _steamFriendsActivateGameOverlayInviteDialog; private static SteamFriendsSetRichPresenceDelegate _steamFriendsSetRichPresence; private static SteamFriendsClearRichPresenceDelegate _steamFriendsClearRichPresence; private static SteamFriendsGetFriendRichPresenceDelegate _steamFriendsGetFriendRichPresence; private static SteamFriendsInviteUserToGameDelegate _steamFriendsInviteUserToGame; private static SteamUtilsGetAppIdDelegate _steamUtilsGetAppId; private static SteamUtilsGetIpCountryDelegate _steamUtilsGetIpCountry; private static SteamUtilsIsOverlayEnabledDelegate _steamUtilsIsOverlayEnabled; private static SteamRemoteStorageFileExistsDelegate _steamRemoteStorageFileExists; private static SteamRemoteStorageGetFileCountDelegate _steamRemoteStorageGetFileCount; private static SteamRemoteStorageIsCloudEnabledForAppDelegate _steamRemoteStorageIsCloudEnabledForApp; private static SteamRemoteStorageFileWriteDelegate _steamRemoteStorageFileWrite; private static SteamRemoteStorageFileReadDelegate _steamRemoteStorageFileRead; private static SteamRemoteStorageFileDeleteDelegate _steamRemoteStorageFileDelete; private static SteamRemoteStorageGetFileSizeDelegate _steamRemoteStorageGetFileSize; private static SteamInputInitDelegate _steamInputInit; private static SteamInputShutdownDelegate _steamInputShutdown; private static SteamInputRunFrameDelegate _steamInputRunFrame; private static SteamInventoryLoadItemDefinitionsDelegate _steamInventoryLoadItemDefinitions; private static SteamInventoryGetAllItemsDelegate _steamInventoryGetAllItems; private static SteamInventoryDestroyResultDelegate _steamInventoryDestroyResult; private static SteamMatchmakingRequestLobbyListDelegate _steamMatchmakingRequestLobbyList; private static SteamMatchmakingAddRequestLobbyListResultCountFilterDelegate _steamMatchmakingAddRequestLobbyListResultCountFilter; private static SteamMatchmakingCreateLobbyDelegate _steamMatchmakingCreateLobby; private static SteamMatchmakingJoinLobbyDelegate _steamMatchmakingJoinLobby; private static SteamMatchmakingLeaveLobbyDelegate _steamMatchmakingLeaveLobby; private static SteamMatchmakingGetNumLobbyMembersDelegate _steamMatchmakingGetNumLobbyMembers; private static SteamScreenshotsAddScreenshotToLibraryDelegate _steamScreenshotsAddScreenshotToLibrary; private static SteamScreenshotsTriggerScreenshotDelegate _steamScreenshotsTriggerScreenshot; private static SteamScreenshotsHookScreenshotsDelegate _steamScreenshotsHookScreenshots; private static SteamScreenshotsIsScreenshotsHookedDelegate _steamScreenshotsIsScreenshotsHooked; private static SteamMusicIsEnabledDelegate _steamMusicIsEnabled; private static SteamMusicIsPlayingDelegate _steamMusicIsPlaying; private static SteamMusicPlayDelegate _steamMusicPlay; private static SteamMusicPauseDelegate _steamMusicPause; private static SteamMusicPlayNextDelegate _steamMusicPlayNext; private static SteamMusicPlayPreviousDelegate _steamMusicPlayPrevious; private static SteamMusicGetVolumeDelegate _steamMusicGetVolume; private static SteamMusicSetVolumeDelegate _steamMusicSetVolume; private static SteamVideoGetVideoUrlDelegate _steamVideoGetVideoUrl; private static SteamVideoIsBroadcastingDelegate _steamVideoIsBroadcasting; private static SteamUserStatsRequestUserStatsDelegate _steamUserStatsRequestUserStats; private static SteamUserStatsRequestGlobalStatsDelegate _steamUserStatsRequestGlobalStats; private static SteamUserStatsResetAllStatsDelegate _steamUserStatsResetAllStats; private static SteamUserStatsGetStatInt32Delegate _steamUserStatsGetStatInt32; private static SteamUserStatsSetStatInt32Delegate _steamUserStatsSetStatInt32; private static IntPtr _steamFriends; private static IntPtr _steamUserStats; private static IntPtr _steamUgc; private static IntPtr _steamRemotePlay; private static readonly Dictionary AdditionalInterfaceAccessors = new Dictionary(); private static readonly Dictionary AdditionalInterfacePointers = new Dictionary(); private const string InterfaceSteamApps = "SteamApps"; private const string InterfaceSteamController = "SteamController"; private const string InterfaceSteamGameCoordinator = "SteamGameCoordinator"; private const string InterfaceSteamGameServer = "SteamGameServer"; private const string InterfaceSteamGameServerStats = "SteamGameServerStats"; private const string InterfaceSteamHtmlSurface = "SteamHTMLSurface"; private const string InterfaceSteamInput = "SteamInput"; private const string InterfaceSteamInventory = "SteamInventory"; private const string InterfaceSteamMatchmaking = "SteamMatchmaking"; private const string InterfaceSteamMatchmakingServers = "SteamMatchmakingServers"; private const string InterfaceSteamMusic = "SteamMusic"; private const string InterfaceSteamParties = "SteamParties"; private const string InterfaceSteamRemoteStorage = "SteamRemoteStorage"; private const string InterfaceSteamScreenshots = "SteamScreenshots"; private const string InterfaceSteamUtils = "SteamUtils"; private const string InterfaceSteamVideo = "SteamVideo"; public static bool IsAvailable => EnsureLoaded(); public static bool IsManualDispatchActive => _manualDispatchActive; public static string LastInitErrorMessage { get; private set; } = string.Empty; private static ILogger Logger => SteamManager.Logger; public static bool AddLibrarySearchPath(string directoryOrFile) { if (string.IsNullOrWhiteSpace(directoryOrFile)) { return false; } lock (SyncRoot) { if (_libraryLoaded) { return false; } CustomLibraryPaths.Add(directoryOrFile); _loadFailureLogged = false; return true; } } public static bool ConfigureManualDispatch(bool enabled) { lock (SyncRoot) { if (_initialized) { return false; } _manualDispatchEnabled = enabled; return true; } } public static bool Initialize(uint? appId = null) { lock (SyncRoot) { if (appId.HasValue && appId.Value > 0) { ApplyAppIdEnvironment(appId.Value); } if (_initialized) { return true; } if (!EnsureLoaded()) { LastInitErrorMessage = "The Steam API native library could not be loaded."; return false; } _initialized = InvokeSteamApiInit(); if (_initialized) { CacheInterfaces(); TryEnableManualDispatch(); } return _initialized; } } public static bool RestartAppIfNecessary(uint appId) { if (!EnsureLoaded()) { Logger.LogError("SteamAPI_RestartAppIfNecessary skipped: the Steam API native library could not be loaded."); return false; } if (_steamApiRestartAppIfNecessary == null) { Logger.LogWarning("SteamAPI_RestartAppIfNecessary export not found in the loaded Steam API library."); return false; } return _steamApiRestartAppIfNecessary(appId); } public static void ConfigureAppId(uint appId, bool writeSteamAppIdFile) { if (appId == 0) { return; } lock (SyncRoot) { ApplyAppIdEnvironment(appId); if (writeSteamAppIdFile) { TryWriteSteamAppIdFile(appId); } } } public static void Shutdown() { lock (SyncRoot) { if (!_initialized) { return; } _steamApiShutdown?.Invoke(); _initialized = false; _manualDispatchActive = false; _steamPipe = 0; if (_callbackMessageBuffer != IntPtr.Zero) { Marshal.FreeHGlobal(_callbackMessageBuffer); _callbackMessageBuffer = IntPtr.Zero; } _steamFriends = IntPtr.Zero; _steamUserStats = IntPtr.Zero; _steamUgc = IntPtr.Zero; _steamRemotePlay = IntPtr.Zero; AdditionalInterfacePointers.Clear(); } } public static void RunCallbacks() { // SteamAPI_RunCallbacks must never run once manual dispatch is in use. if (!_initialized || _manualDispatchActive) { return; } _steamApiRunCallbacks?.Invoke(); } public static void ManualDispatchRunFrame() { if (!_manualDispatchActive) { return; } _steamApiManualDispatchRunFrame(_steamPipe); } public static bool ManualDispatchGetNextCallback(out SteamNativeCallbackMessage message) { message = default; if (!_manualDispatchActive || !_steamApiManualDispatchGetNextCallback(_steamPipe, _callbackMessageBuffer)) { return false; } // CallbackMsg_t { HSteamUser m_hSteamUser; int m_iCallback; uint8 *m_pubParam; int m_cubParam; } // The pointer lands on offset 8 under both pack 4 and pack 8, so the layout is the same on every platform. message = new SteamNativeCallbackMessage( Marshal.ReadInt32(_callbackMessageBuffer, 0), Marshal.ReadInt32(_callbackMessageBuffer, 4), Marshal.ReadIntPtr(_callbackMessageBuffer, 8), Marshal.ReadInt32(_callbackMessageBuffer, 8 + IntPtr.Size)); return true; } public static void ManualDispatchFreeLastCallback() { if (!_manualDispatchActive) { return; } _steamApiManualDispatchFreeLastCallback(_steamPipe); } public static bool ManualDispatchGetApiCallResult(ulong apiCall, IntPtr callbackBuffer, int callbackSize, int callbackExpected, out bool failed) { failed = true; if (!_manualDispatchActive) { return false; } return _steamApiManualDispatchGetApiCallResult(_steamPipe, apiCall, callbackBuffer, callbackSize, callbackExpected, out failed); } private static bool InvokeSteamApiInit() { LastInitErrorMessage = string.Empty; if (_steamApiInitFlat != null) { var errorBuffer = Marshal.AllocHGlobal(SteamErrMsgSize); try { Marshal.WriteByte(errorBuffer, 0, 0); var result = _steamApiInitFlat(errorBuffer); if (result == 0) { Logger.LogDebug("Steamworks initialized through SteamAPI_InitFlat."); return true; } var message = Marshal.PtrToStringAnsi(errorBuffer) ?? string.Empty; LastInitErrorMessage = string.IsNullOrWhiteSpace(message) ? DescribeInitResult(result) : message; Logger.LogWarning("SteamAPI_InitFlat failed with {InitResult} ({InitResultName}): {InitErrorMessage}", result, DescribeInitResult(result), LastInitErrorMessage); return false; } finally { Marshal.FreeHGlobal(errorBuffer); } } if (_steamApiInit != null) { if (_steamApiInit()) { Logger.LogDebug("Steamworks initialized through the legacy SteamAPI_Init export."); return true; } LastInitErrorMessage = "SteamAPI_Init returned false (this SDK version does not report a reason)."; return false; } LastInitErrorMessage = "No SteamAPI_InitFlat, SteamAPI_Init or SteamAPI_InitSafe export was found."; Logger.LogError(LastInitErrorMessage); return false; } private static string DescribeInitResult(int result) { // ESteamAPIInitResult switch (result) { case 0: return "OK"; case 1: return "FailedGeneric"; case 2: return "NoSteamClient"; case 3: return "VersionMismatch"; default: return "Unknown"; } } private static void TryEnableManualDispatch() { if (!_manualDispatchEnabled) { Logger.LogInformation("Manual callback dispatch disabled; using SteamAPI_RunCallbacks."); return; } if (_steamApiGetHSteamPipe == null || _steamApiManualDispatchInit == null || _steamApiManualDispatchRunFrame == null || _steamApiManualDispatchGetNextCallback == null || _steamApiManualDispatchFreeLastCallback == null || _steamApiManualDispatchGetApiCallResult == null) { Logger.LogWarning("Steam manual dispatch exports not found; falling back to SteamAPI_RunCallbacks. Callback events and async call results will not be delivered."); return; } var steamPipe = _steamApiGetHSteamPipe(); if (steamPipe == 0) { Logger.LogWarning("SteamAPI_GetHSteamPipe returned an invalid pipe; falling back to SteamAPI_RunCallbacks."); return; } _steamApiManualDispatchInit(); _steamPipe = steamPipe; if (_callbackMessageBuffer == IntPtr.Zero) { _callbackMessageBuffer = Marshal.AllocHGlobal(CallbackMessageBufferSize); } _manualDispatchActive = true; Logger.LogDebug("Steam manual callback dispatch enabled."); } public static string GetPersonaName() { if (!EnsureReady()) { return string.Empty; } var namePtr = _steamFriendsGetPersonaName?.Invoke(_steamFriends) ?? IntPtr.Zero; return namePtr == IntPtr.Zero ? string.Empty : Marshal.PtrToStringAnsi(namePtr) ?? string.Empty; } public static bool SetAchievement(string achievementId) { if (!EnsureReady()) { return false; } return _steamUserStatsSetAchievement?.Invoke(_steamUserStats, achievementId) ?? false; } public static bool StoreStats() { if (!EnsureReady()) { return false; } return _steamUserStatsStoreStats?.Invoke(_steamUserStats) ?? false; } public static bool GetAchievement(string achievementId, out bool achieved) { achieved = false; if (!EnsureReady()) { return false; } return _steamUserStatsGetAchievement?.Invoke(_steamUserStats, achievementId, out achieved) ?? false; } public static ulong CreateQueryAllUgcRequest(uint queryType, uint matchingType, uint creatorAppId, uint consumerAppId, uint page) { if (!EnsureUgcReady()) { return 0; } return _steamUgcCreateQueryAllRequest?.Invoke(_steamUgc, queryType, matchingType, creatorAppId, consumerAppId, page) ?? 0; } public static ulong SendQueryUgcRequest(ulong queryHandle) { if (!EnsureUgcReady()) { return 0; } return _steamUgcSendQueryRequest?.Invoke(_steamUgc, queryHandle) ?? 0; } public static bool ReleaseQueryUgcRequest(ulong queryHandle) { if (!EnsureUgcReady()) { return false; } return _steamUgcReleaseQueryRequest?.Invoke(_steamUgc, queryHandle) ?? false; } public static uint GetNumSubscribedItems() { if (!EnsureUgcReady()) { return 0; } return _steamUgcGetNumSubscribedItems?.Invoke(_steamUgc) ?? 0; } public static ulong[] GetSubscribedItems(uint maxEntries) { if (!EnsureUgcReady() || maxEntries == 0) { return Array.Empty(); } var buffer = new ulong[maxEntries]; var count = _steamUgcGetSubscribedItems?.Invoke(_steamUgc, buffer, maxEntries) ?? 0; if (count == 0) { return Array.Empty(); } if (count == buffer.Length) { return buffer; } var result = new ulong[count]; Array.Copy(buffer, result, count); return result; } public static ulong SubscribeItem(ulong publishedFileId) { if (!EnsureUgcReady()) { return 0; } return _steamUgcSubscribeItem?.Invoke(_steamUgc, publishedFileId) ?? 0; } public static ulong UnsubscribeItem(ulong publishedFileId) { if (!EnsureUgcReady()) { return 0; } return _steamUgcUnsubscribeItem?.Invoke(_steamUgc, publishedFileId) ?? 0; } public static ulong CreateItem(uint consumerAppId, uint fileType) { if (!EnsureUgcReady()) { return 0; } return _steamUgcCreateItem?.Invoke(_steamUgc, consumerAppId, fileType) ?? 0; } public static ulong StartItemUpdate(uint consumerAppId, ulong publishedFileId) { if (!EnsureUgcReady()) { return 0; } return _steamUgcStartItemUpdate?.Invoke(_steamUgc, consumerAppId, publishedFileId) ?? 0; } public static bool SetItemTitle(ulong updateHandle, string title) { if (!EnsureUgcReady()) { return false; } return _steamUgcSetItemTitle?.Invoke(_steamUgc, updateHandle, title) ?? false; } public static bool SetItemDescription(ulong updateHandle, string description) { if (!EnsureUgcReady()) { return false; } return _steamUgcSetItemDescription?.Invoke(_steamUgc, updateHandle, description) ?? false; } public static bool SetItemMetadata(ulong updateHandle, string metadata) { if (!EnsureUgcReady()) { return false; } return _steamUgcSetItemMetadata?.Invoke(_steamUgc, updateHandle, metadata) ?? false; } public static bool SetItemVisibility(ulong updateHandle, uint visibility) { if (!EnsureUgcReady()) { return false; } return _steamUgcSetItemVisibility?.Invoke(_steamUgc, updateHandle, visibility) ?? false; } public static bool SetItemTags(ulong updateHandle, string[] tags) { if (!EnsureUgcReady() || tags == null) { return false; } return WithSteamStringArray(tags, nativePtr => _steamUgcSetItemTags?.Invoke(_steamUgc, updateHandle, nativePtr) ?? false); } public static bool SetItemContent(ulong updateHandle, string contentFolder) { if (!EnsureUgcReady()) { return false; } return _steamUgcSetItemContent?.Invoke(_steamUgc, updateHandle, contentFolder) ?? false; } public static bool SetItemPreview(ulong updateHandle, string previewFile) { if (!EnsureUgcReady()) { return false; } return _steamUgcSetItemPreview?.Invoke(_steamUgc, updateHandle, previewFile) ?? false; } public static ulong SubmitItemUpdate(ulong updateHandle, string changeNote) { if (!EnsureUgcReady()) { return 0; } return _steamUgcSubmitItemUpdate?.Invoke(_steamUgc, updateHandle, changeNote ?? string.Empty) ?? 0; } public static uint GetItemUpdateProgress(ulong updateHandle, out ulong bytesProcessed, out ulong bytesTotal) { bytesProcessed = 0; bytesTotal = 0; if (!EnsureUgcReady()) { return 0; } return _steamUgcGetItemUpdateProgress?.Invoke(_steamUgc, updateHandle, out bytesProcessed, out bytesTotal) ?? 0; } public static uint GetRemotePlaySessionCount() { if (!EnsureRemotePlayReady()) { return 0; } return _steamRemotePlayGetSessionCount?.Invoke(_steamRemotePlay) ?? 0; } public static uint GetRemotePlaySessionId(int sessionIndex) { if (!EnsureRemotePlayReady()) { return 0; } return _steamRemotePlayGetSessionId?.Invoke(_steamRemotePlay, sessionIndex) ?? 0; } public static ulong GetRemotePlaySessionSteamId(uint sessionId) { if (!EnsureRemotePlayReady()) { return 0; } return _steamRemotePlayGetSessionSteamId?.Invoke(_steamRemotePlay, sessionId) ?? 0; } public static string GetRemotePlaySessionClientName(uint sessionId) { if (!EnsureRemotePlayReady()) { return string.Empty; } var namePtr = _steamRemotePlayGetSessionClientName?.Invoke(_steamRemotePlay, sessionId) ?? IntPtr.Zero; return namePtr == IntPtr.Zero ? string.Empty : Marshal.PtrToStringAnsi(namePtr) ?? string.Empty; } public static int GetRemotePlaySessionClientFormFactor(uint sessionId) { if (!EnsureRemotePlayReady()) { return 0; } return _steamRemotePlayGetSessionClientFormFactor?.Invoke(_steamRemotePlay, sessionId) ?? 0; } public static bool TryGetRemotePlaySessionClientResolution(uint sessionId, out int resolutionX, out int resolutionY) { resolutionX = 0; resolutionY = 0; if (!EnsureRemotePlayReady()) { return false; } return _steamRemotePlayGetSessionClientResolution?.Invoke(_steamRemotePlay, sessionId, out resolutionX, out resolutionY) ?? false; } public static bool IsSteamAppsAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamApps); public static bool IsSteamControllerAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamController); public static bool IsSteamFriendsAvailable() => EnsureFriendsReady(); public static bool IsSteamGameCoordinatorAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamGameCoordinator); public static bool IsSteamGameServerAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamGameServer); public static bool IsSteamGameServerStatsAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamGameServerStats); public static bool IsSteamHtmlSurfaceAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamHtmlSurface); public static bool IsSteamInputAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamInput); public static bool IsSteamInventoryAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamInventory); public static bool IsSteamMatchmakingAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamMatchmaking); public static bool IsSteamMatchmakingServersAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamMatchmakingServers); public static bool IsSteamMusicAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamMusic); public static bool IsSteamPartiesAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamParties); public static bool IsSteamRemoteStorageAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamRemoteStorage); public static bool IsSteamScreenshotsAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamScreenshots); public static bool IsSteamUserStatsAvailable() => EnsureUserStatsReady(); public static bool IsSteamUgcAvailable() => EnsureUgcReady(); public static bool IsSteamRemotePlayAvailable() => EnsureRemotePlayReady(); public static bool IsSteamUtilsAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamUtils); public static bool IsSteamVideoAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamVideo); public static int GetFriendCount(int friendFlags) { if (!EnsureFriendsReady()) { return 0; } return _steamFriendsGetFriendCount?.Invoke(_steamFriends, friendFlags) ?? 0; } public static string GetFriendPersonaName(ulong steamIdFriend) { if (!EnsureFriendsReady()) { return string.Empty; } var namePtr = _steamFriendsGetFriendPersonaName?.Invoke(_steamFriends, steamIdFriend) ?? IntPtr.Zero; return namePtr == IntPtr.Zero ? string.Empty : Marshal.PtrToStringAnsi(namePtr) ?? string.Empty; } public static void FriendsActivateGameOverlayInviteDialog(ulong steamIdLobby) { if (!EnsureFriendsReady()) { return; } _steamFriendsActivateGameOverlayInviteDialog?.Invoke(_steamFriends, steamIdLobby); } public static bool FriendsSetRichPresence(string key, string value) { if (!EnsureFriendsReady()) { return false; } return _steamFriendsSetRichPresence?.Invoke(_steamFriends, key, value) ?? false; } public static void FriendsClearRichPresence() { if (!EnsureFriendsReady()) { return; } _steamFriendsClearRichPresence?.Invoke(_steamFriends); } public static string FriendsGetFriendRichPresence(ulong steamIdFriend, string key) { if (!EnsureFriendsReady()) { return string.Empty; } var valuePtr = _steamFriendsGetFriendRichPresence?.Invoke(_steamFriends, steamIdFriend, key) ?? IntPtr.Zero; return valuePtr == IntPtr.Zero ? string.Empty : Marshal.PtrToStringAnsi(valuePtr) ?? string.Empty; } public static bool FriendsInviteUserToGame(ulong steamIdFriend, string connectString) { if (!EnsureFriendsReady()) { return false; } return _steamFriendsInviteUserToGame?.Invoke(_steamFriends, steamIdFriend, connectString) ?? false; } public static uint GetAppId() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamUtils)) { return 0; } var steamUtils = GetAdditionalInterfacePointer(InterfaceSteamUtils); return _steamUtilsGetAppId?.Invoke(steamUtils) ?? 0; } public static string GetIpCountry() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamUtils)) { return string.Empty; } var steamUtils = GetAdditionalInterfacePointer(InterfaceSteamUtils); var countryPtr = _steamUtilsGetIpCountry?.Invoke(steamUtils) ?? IntPtr.Zero; return countryPtr == IntPtr.Zero ? string.Empty : Marshal.PtrToStringAnsi(countryPtr) ?? string.Empty; } public static bool IsOverlayEnabled() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamUtils)) { return false; } var steamUtils = GetAdditionalInterfacePointer(InterfaceSteamUtils); return _steamUtilsIsOverlayEnabled?.Invoke(steamUtils) ?? false; } public static bool AppsIsSubscribed() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps)) { return false; } var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps); return _steamAppsIsSubscribed?.Invoke(steamApps) ?? false; } public static bool AppsIsSubscribedApp(uint appId) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps)) { return false; } var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps); return _steamAppsIsSubscribedApp?.Invoke(steamApps, appId) ?? false; } public static bool AppsIsDlcInstalled(uint appId) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps)) { return false; } var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps); return _steamAppsIsDlcInstalled?.Invoke(steamApps, appId) ?? false; } public static bool AppsIsAppInstalled(uint appId) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps)) { return false; } var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps); return _steamAppsIsAppInstalled?.Invoke(steamApps, appId) ?? false; } public static string AppsGetCurrentGameLanguage() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps)) { return string.Empty; } var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps); var languagePtr = _steamAppsGetCurrentGameLanguage?.Invoke(steamApps) ?? IntPtr.Zero; return languagePtr == IntPtr.Zero ? string.Empty : Marshal.PtrToStringAnsi(languagePtr) ?? string.Empty; } public static string AppsGetAvailableGameLanguages() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps)) { return string.Empty; } var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps); var languagesPtr = _steamAppsGetAvailableGameLanguages?.Invoke(steamApps) ?? IntPtr.Zero; return languagesPtr == IntPtr.Zero ? string.Empty : Marshal.PtrToStringAnsi(languagesPtr) ?? string.Empty; } public static int AppsGetAppBuildId() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps)) { return 0; } var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps); return _steamAppsGetAppBuildId?.Invoke(steamApps) ?? 0; } public static bool RemoteStorageFileExists(string fileName) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamRemoteStorage)) { return false; } var steamRemoteStorage = GetAdditionalInterfacePointer(InterfaceSteamRemoteStorage); return _steamRemoteStorageFileExists?.Invoke(steamRemoteStorage, fileName) ?? false; } public static int RemoteStorageGetFileCount() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamRemoteStorage)) { return 0; } var steamRemoteStorage = GetAdditionalInterfacePointer(InterfaceSteamRemoteStorage); return _steamRemoteStorageGetFileCount?.Invoke(steamRemoteStorage) ?? 0; } public static bool RemoteStorageIsCloudEnabledForApp() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamRemoteStorage)) { return false; } var steamRemoteStorage = GetAdditionalInterfacePointer(InterfaceSteamRemoteStorage); return _steamRemoteStorageIsCloudEnabledForApp?.Invoke(steamRemoteStorage) ?? false; } public static bool RemoteStorageFileWrite(string fileName, byte[] data) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamRemoteStorage) || data == null) { return false; } var steamRemoteStorage = GetAdditionalInterfacePointer(InterfaceSteamRemoteStorage); return _steamRemoteStorageFileWrite?.Invoke(steamRemoteStorage, fileName, data, data.Length) ?? false; } public static bool RemoteStorageFileRead(string fileName, out byte[] data) { data = Array.Empty(); if (!EnsureAdditionalInterfaceReady(InterfaceSteamRemoteStorage)) { return false; } var steamRemoteStorage = GetAdditionalInterfacePointer(InterfaceSteamRemoteStorage); var size = _steamRemoteStorageGetFileSize?.Invoke(steamRemoteStorage, fileName) ?? -1; if (size <= 0) { return false; } var buffer = new byte[size]; var bytesRead = _steamRemoteStorageFileRead?.Invoke(steamRemoteStorage, fileName, buffer, size) ?? 0; if (bytesRead <= 0) { return false; } if (bytesRead != size) { var resized = new byte[bytesRead]; Array.Copy(buffer, resized, bytesRead); data = resized; return true; } data = buffer; return true; } public static bool RemoteStorageFileDelete(string fileName) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamRemoteStorage)) { return false; } var steamRemoteStorage = GetAdditionalInterfacePointer(InterfaceSteamRemoteStorage); return _steamRemoteStorageFileDelete?.Invoke(steamRemoteStorage, fileName) ?? false; } public static int RemoteStorageGetFileSize(string fileName) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamRemoteStorage)) { return 0; } var steamRemoteStorage = GetAdditionalInterfacePointer(InterfaceSteamRemoteStorage); return _steamRemoteStorageGetFileSize?.Invoke(steamRemoteStorage, fileName) ?? 0; } public static bool InputInit(bool explicitlyCallRunFrame) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamInput)) { return false; } var steamInput = GetAdditionalInterfacePointer(InterfaceSteamInput); return _steamInputInit?.Invoke(steamInput, explicitlyCallRunFrame) ?? false; } public static bool InputShutdown() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamInput)) { return false; } var steamInput = GetAdditionalInterfacePointer(InterfaceSteamInput); return _steamInputShutdown?.Invoke(steamInput) ?? false; } public static void InputRunFrame() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamInput)) { return; } var steamInput = GetAdditionalInterfacePointer(InterfaceSteamInput); _steamInputRunFrame?.Invoke(steamInput, false); } public static bool InventoryLoadItemDefinitions() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamInventory)) { return false; } var steamInventory = GetAdditionalInterfacePointer(InterfaceSteamInventory); return _steamInventoryLoadItemDefinitions?.Invoke(steamInventory) ?? false; } public static bool InventoryGetAllItems(out int inventoryResultHandle) { inventoryResultHandle = 0; if (!EnsureAdditionalInterfaceReady(InterfaceSteamInventory)) { return false; } var steamInventory = GetAdditionalInterfacePointer(InterfaceSteamInventory); return _steamInventoryGetAllItems?.Invoke(steamInventory, out inventoryResultHandle) ?? false; } public static void InventoryDestroyResult(int inventoryResultHandle) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamInventory)) { return; } var steamInventory = GetAdditionalInterfacePointer(InterfaceSteamInventory); _steamInventoryDestroyResult?.Invoke(steamInventory, inventoryResultHandle); } public static ulong MatchmakingRequestLobbyList() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMatchmaking)) { return 0; } var steamMatchmaking = GetAdditionalInterfacePointer(InterfaceSteamMatchmaking); return _steamMatchmakingRequestLobbyList?.Invoke(steamMatchmaking) ?? 0; } public static void MatchmakingAddRequestLobbyListResultCountFilter(int maxResults) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMatchmaking)) { return; } var steamMatchmaking = GetAdditionalInterfacePointer(InterfaceSteamMatchmaking); _steamMatchmakingAddRequestLobbyListResultCountFilter?.Invoke(steamMatchmaking, maxResults); } public static ulong MatchmakingCreateLobby(int lobbyType, int maxMembers) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMatchmaking)) { return 0; } var steamMatchmaking = GetAdditionalInterfacePointer(InterfaceSteamMatchmaking); return _steamMatchmakingCreateLobby?.Invoke(steamMatchmaking, lobbyType, maxMembers) ?? 0; } public static ulong MatchmakingJoinLobby(ulong steamIdLobby) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMatchmaking)) { return 0; } var steamMatchmaking = GetAdditionalInterfacePointer(InterfaceSteamMatchmaking); return _steamMatchmakingJoinLobby?.Invoke(steamMatchmaking, steamIdLobby) ?? 0; } public static void MatchmakingLeaveLobby(ulong steamIdLobby) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMatchmaking)) { return; } var steamMatchmaking = GetAdditionalInterfacePointer(InterfaceSteamMatchmaking); _steamMatchmakingLeaveLobby?.Invoke(steamMatchmaking, steamIdLobby); } public static int MatchmakingGetNumLobbyMembers(ulong steamIdLobby) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMatchmaking)) { return 0; } var steamMatchmaking = GetAdditionalInterfacePointer(InterfaceSteamMatchmaking); return _steamMatchmakingGetNumLobbyMembers?.Invoke(steamMatchmaking, steamIdLobby) ?? 0; } public static uint ScreenshotsAddScreenshotToLibrary(string fileNameJpg, string fileNameThumbnail, int width, int height) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamScreenshots)) { return 0; } var steamScreenshots = GetAdditionalInterfacePointer(InterfaceSteamScreenshots); return _steamScreenshotsAddScreenshotToLibrary?.Invoke(steamScreenshots, fileNameJpg, fileNameThumbnail, width, height) ?? 0; } public static void ScreenshotsTriggerScreenshot() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamScreenshots)) { return; } var steamScreenshots = GetAdditionalInterfacePointer(InterfaceSteamScreenshots); _steamScreenshotsTriggerScreenshot?.Invoke(steamScreenshots); } public static void ScreenshotsHookScreenshots(bool hook) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamScreenshots)) { return; } var steamScreenshots = GetAdditionalInterfacePointer(InterfaceSteamScreenshots); _steamScreenshotsHookScreenshots?.Invoke(steamScreenshots, hook); } public static bool ScreenshotsIsScreenshotsHooked() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamScreenshots)) { return false; } var steamScreenshots = GetAdditionalInterfacePointer(InterfaceSteamScreenshots); return _steamScreenshotsIsScreenshotsHooked?.Invoke(steamScreenshots) ?? false; } public static bool MusicIsEnabled() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMusic)) { return false; } var steamMusic = GetAdditionalInterfacePointer(InterfaceSteamMusic); return _steamMusicIsEnabled?.Invoke(steamMusic) ?? false; } public static bool MusicIsPlaying() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMusic)) { return false; } var steamMusic = GetAdditionalInterfacePointer(InterfaceSteamMusic); return _steamMusicIsPlaying?.Invoke(steamMusic) ?? false; } public static void MusicPlay() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMusic)) { return; } var steamMusic = GetAdditionalInterfacePointer(InterfaceSteamMusic); _steamMusicPlay?.Invoke(steamMusic); } public static void MusicPause() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMusic)) { return; } var steamMusic = GetAdditionalInterfacePointer(InterfaceSteamMusic); _steamMusicPause?.Invoke(steamMusic); } public static void MusicPlayNext() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMusic)) { return; } var steamMusic = GetAdditionalInterfacePointer(InterfaceSteamMusic); _steamMusicPlayNext?.Invoke(steamMusic); } public static void MusicPlayPrevious() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMusic)) { return; } var steamMusic = GetAdditionalInterfacePointer(InterfaceSteamMusic); _steamMusicPlayPrevious?.Invoke(steamMusic); } public static float MusicGetVolume() { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMusic)) { return 0f; } var steamMusic = GetAdditionalInterfacePointer(InterfaceSteamMusic); return _steamMusicGetVolume?.Invoke(steamMusic) ?? 0f; } public static void MusicSetVolume(float volume) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamMusic)) { return; } var steamMusic = GetAdditionalInterfacePointer(InterfaceSteamMusic); _steamMusicSetVolume?.Invoke(steamMusic, volume); } public static void VideoGetVideoUrl(uint videoAppId) { if (!EnsureAdditionalInterfaceReady(InterfaceSteamVideo)) { return; } var steamVideo = GetAdditionalInterfacePointer(InterfaceSteamVideo); _steamVideoGetVideoUrl?.Invoke(steamVideo, videoAppId); } public static bool VideoIsBroadcasting(out int numberOfViewers) { numberOfViewers = 0; if (!EnsureAdditionalInterfaceReady(InterfaceSteamVideo)) { return false; } var steamVideo = GetAdditionalInterfacePointer(InterfaceSteamVideo); return _steamVideoIsBroadcasting?.Invoke(steamVideo, out numberOfViewers) ?? false; } public static ulong UserStatsRequestUserStats(ulong steamIdUser) { if (!EnsureUserStatsReady()) { return 0; } return _steamUserStatsRequestUserStats?.Invoke(_steamUserStats, steamIdUser) ?? 0; } public static ulong UserStatsRequestGlobalStats(int historyDays) { if (!EnsureUserStatsReady()) { return 0; } return _steamUserStatsRequestGlobalStats?.Invoke(_steamUserStats, historyDays) ?? 0; } public static bool UserStatsResetAllStats(bool achievementsToo) { if (!EnsureUserStatsReady()) { return false; } return _steamUserStatsResetAllStats?.Invoke(_steamUserStats, achievementsToo) ?? false; } public static bool UserStatsGetStatInt32(string name, out int data) { data = 0; if (!EnsureUserStatsReady()) { return false; } return _steamUserStatsGetStatInt32?.Invoke(_steamUserStats, name, out data) ?? false; } public static bool UserStatsSetStatInt32(string name, int data) { if (!EnsureUserStatsReady()) { return false; } return _steamUserStatsSetStatInt32?.Invoke(_steamUserStats, name, data) ?? false; } private static bool EnsureReady() { if (!EnsureLoaded()) { return false; } if (!_initialized) { return false; } if (_steamFriends == IntPtr.Zero || _steamUserStats == IntPtr.Zero) { CacheInterfaces(); } return _steamFriends != IntPtr.Zero && _steamUserStats != IntPtr.Zero; } private static bool EnsureFriendsReady() { if (!EnsureLoaded() || !_initialized) { return false; } if (_steamFriends == IntPtr.Zero) { CacheInterfaces(); } return _steamFriends != IntPtr.Zero; } private static bool EnsureUserStatsReady() { if (!EnsureLoaded() || !_initialized) { return false; } if (_steamUserStats == IntPtr.Zero) { CacheInterfaces(); } return _steamUserStats != IntPtr.Zero; } private static bool EnsureUgcReady() { if (!EnsureLoaded()) { return false; } if (!_initialized) { return false; } if (_steamUgc == IntPtr.Zero) { CacheInterfaces(); } return _steamUgc != IntPtr.Zero; } private static bool EnsureRemotePlayReady() { if (!EnsureLoaded()) { return false; } if (!_initialized) { return false; } if (_steamRemotePlay == IntPtr.Zero) { CacheInterfaces(); } return _steamRemotePlay != IntPtr.Zero; } private static bool EnsureAdditionalInterfaceReady(string key) { if (!EnsureLoaded() || !_initialized) { return false; } if (!AdditionalInterfacePointers.TryGetValue(key, out var pointer) || pointer == IntPtr.Zero) { CacheAdditionalInterface(key); AdditionalInterfacePointers.TryGetValue(key, out pointer); } return pointer != IntPtr.Zero; } private static bool EnsureLoaded() { lock (SyncRoot) { if (_libraryLoaded) { return true; } var failures = new List(); foreach (var candidate in GetLibraryCandidates()) { try { var handle = LoadUnmanagedLibrary(candidate); if (handle != IntPtr.Zero) { _libraryHandle = handle; _libraryLoaded = true; BindDelegates(); Logger.LogInformation("Loaded Steam API native library from '{SteamLibraryPath}'.", candidate); return true; } failures.Add($"{candidate} ({GetLastLoadError()})"); } catch (Exception e) { // Try the next candidate. failures.Add($"{candidate} ({e.GetType().Name}: {e.Message})"); } } if (!_loadFailureLogged) { _loadFailureLogged = true; Logger.LogError( "Could not load the Steam API native library '{SteamLibraryName}'. Call SteamManager.ConfigureLibraryPath with the folder that contains it. Tried: {SteamLibraryCandidates}", GetLibraryFileName(), string.Join("; ", failures)); } return false; } } private static void BindDelegates() { // SDK 1.58+ exposes SteamAPI_InitFlat (with an error message); older SDKs only have SteamAPI_Init/InitSafe. _steamApiInitFlat = Bind("SteamAPI_InitFlat"); _steamApiInit = Bind("SteamAPI_Init") ?? Bind("SteamAPI_InitSafe"); _steamApiRestartAppIfNecessary = Bind("SteamAPI_RestartAppIfNecessary"); _steamApiShutdown = Bind("SteamAPI_Shutdown"); _steamApiRunCallbacks = Bind("SteamAPI_RunCallbacks"); _steamApiGetHSteamPipe = Bind("SteamAPI_GetHSteamPipe"); _steamApiManualDispatchInit = Bind("SteamAPI_ManualDispatch_Init"); _steamApiManualDispatchRunFrame = Bind("SteamAPI_ManualDispatch_RunFrame"); _steamApiManualDispatchGetNextCallback = Bind("SteamAPI_ManualDispatch_GetNextCallback"); _steamApiManualDispatchFreeLastCallback = Bind("SteamAPI_ManualDispatch_FreeLastCallback"); _steamApiManualDispatchGetApiCallResult = Bind("SteamAPI_ManualDispatch_GetAPICallResult"); _steamAppsIsSubscribed = Bind("SteamAPI_ISteamApps_BIsSubscribed"); _steamAppsIsSubscribedApp = Bind("SteamAPI_ISteamApps_BIsSubscribedApp"); _steamAppsIsDlcInstalled = Bind("SteamAPI_ISteamApps_BIsDlcInstalled"); _steamAppsIsAppInstalled = Bind("SteamAPI_ISteamApps_BIsAppInstalled"); _steamAppsGetCurrentGameLanguage = Bind("SteamAPI_ISteamApps_GetCurrentGameLanguage"); _steamAppsGetAvailableGameLanguages = Bind("SteamAPI_ISteamApps_GetAvailableGameLanguages"); _steamAppsGetAppBuildId = Bind("SteamAPI_ISteamApps_GetAppBuildId"); _steamFriendsAccessor = BindInterfaceAccessor("SteamFriends", "ISteamFriends"); _steamUserStatsAccessor = BindInterfaceAccessor("SteamUserStats", "ISteamUserStats"); _steamUgcAccessor = BindInterfaceAccessor("SteamUGC", "ISteamUGC"); _steamRemotePlayAccessor = BindInterfaceAccessor("SteamRemotePlay", "ISteamRemotePlay"); _steamFriendsGetPersonaName = Bind("SteamAPI_ISteamFriends_GetPersonaName"); _steamUserStatsSetAchievement = Bind("SteamAPI_ISteamUserStats_SetAchievement"); _steamUserStatsStoreStats = Bind("SteamAPI_ISteamUserStats_StoreStats"); _steamUserStatsGetAchievement = Bind("SteamAPI_ISteamUserStats_GetAchievement"); _steamUgcCreateQueryAllRequest = Bind("SteamAPI_ISteamUGC_CreateQueryAllUGCRequest"); _steamUgcSendQueryRequest = Bind("SteamAPI_ISteamUGC_SendQueryUGCRequest"); _steamUgcReleaseQueryRequest = Bind("SteamAPI_ISteamUGC_ReleaseQueryUGCRequest"); _steamUgcGetNumSubscribedItems = Bind("SteamAPI_ISteamUGC_GetNumSubscribedItems"); _steamUgcGetSubscribedItems = Bind("SteamAPI_ISteamUGC_GetSubscribedItems"); _steamUgcSubscribeItem = Bind("SteamAPI_ISteamUGC_SubscribeItem"); _steamUgcUnsubscribeItem = Bind("SteamAPI_ISteamUGC_UnsubscribeItem"); _steamUgcCreateItem = Bind("SteamAPI_ISteamUGC_CreateItem"); _steamUgcStartItemUpdate = Bind("SteamAPI_ISteamUGC_StartItemUpdate"); _steamUgcSetItemTitle = Bind("SteamAPI_ISteamUGC_SetItemTitle"); _steamUgcSetItemDescription = Bind("SteamAPI_ISteamUGC_SetItemDescription"); _steamUgcSetItemMetadata = Bind("SteamAPI_ISteamUGC_SetItemMetadata"); _steamUgcSetItemVisibility = Bind("SteamAPI_ISteamUGC_SetItemVisibility"); _steamUgcSetItemTags = Bind("SteamAPI_ISteamUGC_SetItemTags"); _steamUgcSetItemContent = Bind("SteamAPI_ISteamUGC_SetItemContent"); _steamUgcSetItemPreview = Bind("SteamAPI_ISteamUGC_SetItemPreview"); _steamUgcSubmitItemUpdate = Bind("SteamAPI_ISteamUGC_SubmitItemUpdate"); _steamUgcGetItemUpdateProgress = Bind("SteamAPI_ISteamUGC_GetItemUpdateProgress"); _steamRemotePlayGetSessionCount = Bind("SteamAPI_ISteamRemotePlay_GetSessionCount"); _steamRemotePlayGetSessionId = Bind("SteamAPI_ISteamRemotePlay_GetSessionID"); _steamRemotePlayGetSessionSteamId = Bind("SteamAPI_ISteamRemotePlay_GetSessionSteamID"); _steamRemotePlayGetSessionClientName = Bind("SteamAPI_ISteamRemotePlay_GetSessionClientName"); _steamRemotePlayGetSessionClientFormFactor = Bind("SteamAPI_ISteamRemotePlay_GetSessionClientFormFactor"); _steamRemotePlayGetSessionClientResolution = Bind("SteamAPI_ISteamRemotePlay_BGetSessionClientResolution"); _steamFriendsGetFriendCount = Bind("SteamAPI_ISteamFriends_GetFriendCount"); _steamFriendsGetFriendPersonaName = Bind("SteamAPI_ISteamFriends_GetFriendPersonaName"); _steamFriendsActivateGameOverlayInviteDialog = Bind("SteamAPI_ISteamFriends_ActivateGameOverlayInviteDialog"); _steamFriendsSetRichPresence = Bind("SteamAPI_ISteamFriends_SetRichPresence"); _steamFriendsClearRichPresence = Bind("SteamAPI_ISteamFriends_ClearRichPresence"); _steamFriendsGetFriendRichPresence = Bind("SteamAPI_ISteamFriends_GetFriendRichPresence"); _steamFriendsInviteUserToGame = Bind("SteamAPI_ISteamFriends_InviteUserToGame"); _steamUtilsGetAppId = Bind("SteamAPI_ISteamUtils_GetAppID"); _steamUtilsGetIpCountry = Bind("SteamAPI_ISteamUtils_GetIPCountry"); _steamUtilsIsOverlayEnabled = Bind("SteamAPI_ISteamUtils_IsOverlayEnabled"); _steamRemoteStorageFileExists = Bind("SteamAPI_ISteamRemoteStorage_FileExists"); _steamRemoteStorageGetFileCount = Bind("SteamAPI_ISteamRemoteStorage_GetFileCount"); _steamRemoteStorageIsCloudEnabledForApp = Bind("SteamAPI_ISteamRemoteStorage_IsCloudEnabledForApp"); _steamRemoteStorageFileWrite = Bind("SteamAPI_ISteamRemoteStorage_FileWrite"); _steamRemoteStorageFileRead = Bind("SteamAPI_ISteamRemoteStorage_FileRead"); _steamRemoteStorageFileDelete = Bind("SteamAPI_ISteamRemoteStorage_FileDelete"); _steamRemoteStorageGetFileSize = Bind("SteamAPI_ISteamRemoteStorage_GetFileSize"); _steamInputInit = Bind("SteamAPI_ISteamInput_Init"); _steamInputShutdown = Bind("SteamAPI_ISteamInput_Shutdown"); _steamInputRunFrame = Bind("SteamAPI_ISteamInput_RunFrame"); _steamInventoryLoadItemDefinitions = Bind("SteamAPI_ISteamInventory_LoadItemDefinitions"); _steamInventoryGetAllItems = Bind("SteamAPI_ISteamInventory_GetAllItems"); _steamInventoryDestroyResult = Bind("SteamAPI_ISteamInventory_DestroyResult"); _steamMatchmakingRequestLobbyList = Bind("SteamAPI_ISteamMatchmaking_RequestLobbyList"); _steamMatchmakingAddRequestLobbyListResultCountFilter = Bind("SteamAPI_ISteamMatchmaking_AddRequestLobbyListResultCountFilter"); _steamMatchmakingCreateLobby = Bind("SteamAPI_ISteamMatchmaking_CreateLobby"); _steamMatchmakingJoinLobby = Bind("SteamAPI_ISteamMatchmaking_JoinLobby"); _steamMatchmakingLeaveLobby = Bind("SteamAPI_ISteamMatchmaking_LeaveLobby"); _steamMatchmakingGetNumLobbyMembers = Bind("SteamAPI_ISteamMatchmaking_GetNumLobbyMembers"); _steamScreenshotsAddScreenshotToLibrary = Bind("SteamAPI_ISteamScreenshots_AddScreenshotToLibrary"); _steamScreenshotsTriggerScreenshot = Bind("SteamAPI_ISteamScreenshots_TriggerScreenshot"); _steamScreenshotsHookScreenshots = Bind("SteamAPI_ISteamScreenshots_HookScreenshots"); _steamScreenshotsIsScreenshotsHooked = Bind("SteamAPI_ISteamScreenshots_IsScreenshotsHooked"); _steamMusicIsEnabled = Bind("SteamAPI_ISteamMusic_BIsEnabled"); _steamMusicIsPlaying = Bind("SteamAPI_ISteamMusic_BIsPlaying"); _steamMusicPlay = Bind("SteamAPI_ISteamMusic_Play"); _steamMusicPause = Bind("SteamAPI_ISteamMusic_Pause"); _steamMusicPlayNext = Bind("SteamAPI_ISteamMusic_PlayNext"); _steamMusicPlayPrevious = Bind("SteamAPI_ISteamMusic_PlayPrevious"); _steamMusicGetVolume = Bind("SteamAPI_ISteamMusic_GetVolume"); _steamMusicSetVolume = Bind("SteamAPI_ISteamMusic_SetVolume"); _steamVideoGetVideoUrl = Bind("SteamAPI_ISteamVideo_GetVideoURL"); _steamVideoIsBroadcasting = Bind("SteamAPI_ISteamVideo_IsBroadcasting"); _steamUserStatsRequestUserStats = Bind("SteamAPI_ISteamUserStats_RequestUserStats"); _steamUserStatsRequestGlobalStats = Bind("SteamAPI_ISteamUserStats_RequestGlobalStats"); _steamUserStatsResetAllStats = Bind("SteamAPI_ISteamUserStats_ResetAllStats"); _steamUserStatsGetStatInt32 = Bind("SteamAPI_ISteamUserStats_GetStatInt32"); _steamUserStatsSetStatInt32 = Bind("SteamAPI_ISteamUserStats_SetStatInt32"); // Additional interface accessors requested for broad API coverage. BindAdditionalInterfaceAccessor(InterfaceSteamApps, "SteamApps", "ISteamApps"); BindAdditionalInterfaceAccessor(InterfaceSteamController, "SteamController", "ISteamController"); BindAdditionalInterfaceAccessor(InterfaceSteamGameCoordinator, "SteamGameCoordinator", "ISteamGameCoordinator"); BindAdditionalInterfaceAccessor(InterfaceSteamGameServer, "SteamGameServer", "ISteamGameServer"); BindAdditionalInterfaceAccessor(InterfaceSteamGameServerStats, "SteamGameServerStats", "ISteamGameServerStats"); BindAdditionalInterfaceAccessor(InterfaceSteamHtmlSurface, "SteamHTMLSurface", "ISteamHTMLSurface"); BindAdditionalInterfaceAccessor(InterfaceSteamInput, "SteamInput", "ISteamInput"); BindAdditionalInterfaceAccessor(InterfaceSteamInventory, "SteamInventory", "ISteamInventory"); BindAdditionalInterfaceAccessor(InterfaceSteamMatchmaking, "SteamMatchmaking", "ISteamMatchmaking"); BindAdditionalInterfaceAccessor(InterfaceSteamMatchmakingServers, "SteamMatchmakingServers", "ISteamMatchmakingServers"); BindAdditionalInterfaceAccessor(InterfaceSteamMusic, "SteamMusic", "ISteamMusic"); BindAdditionalInterfaceAccessor(InterfaceSteamParties, "SteamParties", "ISteamParties"); BindAdditionalInterfaceAccessor(InterfaceSteamRemoteStorage, "SteamRemoteStorage", "ISteamRemoteStorage"); BindAdditionalInterfaceAccessor(InterfaceSteamScreenshots, "SteamScreenshots", "ISteamScreenshots"); BindAdditionalInterfaceAccessor(InterfaceSteamUtils, "SteamUtils", "ISteamUtils"); BindAdditionalInterfaceAccessor(InterfaceSteamVideo, "SteamVideo", "ISteamVideo"); } private static T Bind(string exportName) where T : class { return TryGetExport(_libraryHandle, exportName, out var export) ? Marshal.GetDelegateForFunctionPointer(export, typeof(T)) as T : null; } private static T BindInterfaceAccessor(string friendlyName, string interfaceName) where T : class { for (var version = 1; version <= 64; version++) { var exportName = $"SteamAPI_{friendlyName}_v{version:000}"; if (TryGetExport(_libraryHandle, exportName, out var export) || TryGetExport(_libraryHandle, $"SteamAPI_{interfaceName}_v{version:000}", out export)) { return Marshal.GetDelegateForFunctionPointer(export, typeof(T)) as T; } } return null; } private static void CacheInterfaces() { _steamFriends = _steamFriendsAccessor?.Invoke() ?? IntPtr.Zero; _steamUserStats = _steamUserStatsAccessor?.Invoke() ?? IntPtr.Zero; _steamUgc = _steamUgcAccessor?.Invoke() ?? IntPtr.Zero; _steamRemotePlay = _steamRemotePlayAccessor?.Invoke() ?? IntPtr.Zero; CacheAdditionalInterfaces(); } private static void BindAdditionalInterfaceAccessor(string key, string friendlyName, string interfaceName) { AdditionalInterfaceAccessors[key] = BindInterfaceAccessor(friendlyName, interfaceName); } private static void CacheAdditionalInterfaces() { foreach (var key in AdditionalInterfaceAccessors.Keys) { CacheAdditionalInterface(key); } } private static void CacheAdditionalInterface(string key) { if (!AdditionalInterfaceAccessors.TryGetValue(key, out var accessor)) { AdditionalInterfacePointers[key] = IntPtr.Zero; return; } AdditionalInterfacePointers[key] = accessor?.Invoke() ?? IntPtr.Zero; } private static IntPtr GetAdditionalInterfacePointer(string key) { return AdditionalInterfacePointers.TryGetValue(key, out var pointer) ? pointer : IntPtr.Zero; } [StructLayout(LayoutKind.Sequential)] private struct SteamParamStringArrayNative { public IntPtr Strings; public int NumStrings; } private static bool WithSteamStringArray(string[] values, Func action) { var stringPointers = new IntPtr[values.Length]; IntPtr arrayPtr = IntPtr.Zero; IntPtr structPtr = IntPtr.Zero; try { for (var i = 0; i < values.Length; i++) { stringPointers[i] = Marshal.StringToHGlobalAnsi(values[i] ?? string.Empty); } arrayPtr = Marshal.AllocHGlobal(IntPtr.Size * values.Length); Marshal.Copy(stringPointers, 0, arrayPtr, values.Length); var native = new SteamParamStringArrayNative { Strings = arrayPtr, NumStrings = values.Length }; structPtr = Marshal.AllocHGlobal(Marshal.SizeOf()); Marshal.StructureToPtr(native, structPtr, false); return action(structPtr); } finally { if (structPtr != IntPtr.Zero) { Marshal.FreeHGlobal(structPtr); } if (arrayPtr != IntPtr.Zero) { Marshal.FreeHGlobal(arrayPtr); } for (var i = 0; i < stringPointers.Length; i++) { if (stringPointers[i] != IntPtr.Zero) { Marshal.FreeHGlobal(stringPointers[i]); } } } } private static string[] GetLibraryCandidates() { var libraryName = GetLibraryFileName(); var runtimeIdentifier = GetRuntimeIdentifier(); var candidates = new List(); // Paths from SteamManager.ConfigureLibraryPath are tried first, in the order they were added. foreach (var customPath in CustomLibraryPaths) { var isFile = File.Exists(customPath) || string.Equals(Path.GetFileName(customPath), libraryName, StringComparison.OrdinalIgnoreCase); candidates.Add(isFile ? customPath : Path.Combine(customPath, libraryName)); } candidates.Add(Path.Combine(AppContext.BaseDirectory, "runtimes", runtimeIdentifier, "native", libraryName)); candidates.Add(Path.Combine(AppContext.BaseDirectory, libraryName)); candidates.Add(libraryName); return candidates.ToArray(); } private static string GetLastLoadError() { try { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()).Message; } var errorPtr = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? DlerrorMac() : DlerrorLinux(); return errorPtr == IntPtr.Zero ? "unknown error" : Marshal.PtrToStringAnsi(errorPtr) ?? "unknown error"; } catch (Exception e) { return e.Message; } } private static string GetRuntimeIdentifier() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return Environment.Is64BitProcess ? "win-x64" : "win-x86"; } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return Environment.Is64BitProcess ? "osx-x64" : "osx-x86"; } return Environment.Is64BitProcess ? "linux-x64" : "linux-x86"; } private static string GetLibraryFileName() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return Environment.Is64BitProcess ? "steam_api64.dll" : "steam_api.dll"; } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return "libsteam_api.dylib"; } return "libsteam_api.so"; } private static void ApplyAppIdEnvironment(uint appId) { var appIdValue = appId.ToString(); Environment.SetEnvironmentVariable("SteamAppId", appIdValue); Environment.SetEnvironmentVariable("SteamGameId", appIdValue); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return; } // Some runtimes keep Environment.SetEnvironmentVariable managed-only on Unix, // so mirror the values into the native environment that steam_api reads. try { if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { SetEnvMac("SteamAppId", appIdValue, 1); SetEnvMac("SteamGameId", appIdValue, 1); } else { SetEnvLinux("SteamAppId", appIdValue, 1); SetEnvLinux("SteamGameId", appIdValue, 1); } } catch (Exception e) { Logger.LogDebug(e, "Could not set the native SteamAppId environment variables."); } } private static void TryWriteSteamAppIdFile(uint appId) { var appIdValue = appId.ToString(); var candidatePaths = new[] { Path.Combine(AppContext.BaseDirectory, "steam_appid.txt"), Path.Combine(Directory.GetCurrentDirectory(), "steam_appid.txt") }; foreach (var path in candidatePaths) { try { File.WriteAllText(path, appIdValue); } catch { // Ignore write failures and continue with other candidates. } } } private static IntPtr LoadUnmanagedLibrary(string path) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return LoadLibraryWindows(path); } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return DlopenMac(path, RtldNow); } return DlopenLinux(path, RtldNow); } private static bool TryGetExport(IntPtr libraryHandle, string exportName, out IntPtr export) { if (libraryHandle == IntPtr.Zero) { export = IntPtr.Zero; return false; } if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { export = GetProcAddressWindows(libraryHandle, exportName); return export != IntPtr.Zero; } export = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? DlsymMac(libraryHandle, exportName) : DlsymLinux(libraryHandle, exportName); return export != IntPtr.Zero; } [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "LoadLibraryW")] private static extern IntPtr LoadLibraryWindows(string lpFileName); [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true, EntryPoint = "GetProcAddress")] private static extern IntPtr GetProcAddressWindows(IntPtr hModule, string procName); [DllImport("libdl.so.2", EntryPoint = "dlopen")] private static extern IntPtr DlopenLinux(string fileName, int flags); [DllImport("libdl.so.2", EntryPoint = "dlsym")] private static extern IntPtr DlsymLinux(IntPtr handle, string symbol); [DllImport("libSystem.B.dylib", EntryPoint = "dlopen")] private static extern IntPtr DlopenMac(string fileName, int flags); [DllImport("libSystem.B.dylib", EntryPoint = "dlsym")] private static extern IntPtr DlsymMac(IntPtr handle, string symbol); [DllImport("libdl.so.2", EntryPoint = "dlerror")] private static extern IntPtr DlerrorLinux(); [DllImport("libSystem.B.dylib", EntryPoint = "dlerror")] private static extern IntPtr DlerrorMac(); [DllImport("libc.so.6", EntryPoint = "setenv")] private static extern int SetEnvLinux(string name, string value, int overwrite); [DllImport("libSystem.B.dylib", EntryPoint = "setenv")] private static extern int SetEnvMac(string name, string value, int overwrite); private const int RtldNow = 2; } }