updated SteamWorks library
This commit is contained in:
@@ -2,18 +2,70 @@ 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();
|
||||
|
||||
@@ -289,13 +341,41 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
|
||||
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<string> CustomLibraryPaths = new List<string>();
|
||||
|
||||
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;
|
||||
@@ -385,6 +465,7 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
private static readonly Dictionary<string, SteamInterfaceAccessorDelegate> AdditionalInterfaceAccessors = new Dictionary<string, SteamInterfaceAccessorDelegate>();
|
||||
private static readonly Dictionary<string, IntPtr> AdditionalInterfacePointers = new Dictionary<string, IntPtr>();
|
||||
|
||||
private const string InterfaceSteamApps = "SteamApps";
|
||||
private const string InterfaceSteamController = "SteamController";
|
||||
private const string InterfaceSteamGameCoordinator = "SteamGameCoordinator";
|
||||
private const string InterfaceSteamGameServer = "SteamGameServer";
|
||||
@@ -403,13 +484,53 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
|
||||
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)
|
||||
{
|
||||
ConfigureAppId(appId.Value);
|
||||
ApplyAppIdEnvironment(appId.Value);
|
||||
}
|
||||
|
||||
if (_initialized)
|
||||
@@ -419,25 +540,39 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
|
||||
if (!EnsureLoaded())
|
||||
{
|
||||
LastInitErrorMessage = "The Steam API native library could not be loaded.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_steamApiInit == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_initialized = _steamApiInit();
|
||||
_initialized = InvokeSteamApiInit();
|
||||
if (_initialized)
|
||||
{
|
||||
CacheInterfaces();
|
||||
TryEnableManualDispatch();
|
||||
}
|
||||
|
||||
return _initialized;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ConfigureAppId(uint appId)
|
||||
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)
|
||||
{
|
||||
@@ -447,7 +582,10 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
lock (SyncRoot)
|
||||
{
|
||||
ApplyAppIdEnvironment(appId);
|
||||
TryWriteSteamAppIdFile(appId);
|
||||
if (writeSteamAppIdFile)
|
||||
{
|
||||
TryWriteSteamAppIdFile(appId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,6 +600,14 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
|
||||
_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;
|
||||
@@ -472,7 +618,8 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
|
||||
public static void RunCallbacks()
|
||||
{
|
||||
if (!_initialized)
|
||||
// SteamAPI_RunCallbacks must never run once manual dispatch is in use.
|
||||
if (!_initialized || _manualDispatchActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -480,6 +627,152 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
_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())
|
||||
@@ -784,6 +1077,8 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
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();
|
||||
@@ -928,6 +1223,85 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
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))
|
||||
@@ -1486,35 +1860,66 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
return true;
|
||||
}
|
||||
|
||||
var failures = new List<string>();
|
||||
foreach (var candidate in GetLibraryCandidates())
|
||||
{
|
||||
try
|
||||
{
|
||||
_libraryHandle = LoadUnmanagedLibrary(candidate);
|
||||
_libraryLoaded = _libraryHandle != IntPtr.Zero;
|
||||
if (_libraryLoaded)
|
||||
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
|
||||
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<SteamApiInitFlatDelegate>("SteamAPI_InitFlat");
|
||||
_steamApiInit =
|
||||
Bind<SteamApiInitDelegate>("SteamAPI_Init")
|
||||
?? Bind<SteamApiInitDelegate>("SteamAPI_InitSafe");
|
||||
_steamApiRestartAppIfNecessary = Bind<SteamApiRestartAppIfNecessaryDelegate>("SteamAPI_RestartAppIfNecessary");
|
||||
_steamApiShutdown = Bind<SteamApiVoidDelegate>("SteamAPI_Shutdown");
|
||||
_steamApiRunCallbacks = Bind<SteamApiVoidDelegate>("SteamAPI_RunCallbacks");
|
||||
_steamApiGetHSteamPipe = Bind<SteamApiGetHSteamPipeDelegate>("SteamAPI_GetHSteamPipe");
|
||||
_steamApiManualDispatchInit = Bind<SteamApiVoidDelegate>("SteamAPI_ManualDispatch_Init");
|
||||
_steamApiManualDispatchRunFrame = Bind<SteamApiManualDispatchPipeDelegate>("SteamAPI_ManualDispatch_RunFrame");
|
||||
_steamApiManualDispatchGetNextCallback = Bind<SteamApiManualDispatchGetNextCallbackDelegate>("SteamAPI_ManualDispatch_GetNextCallback");
|
||||
_steamApiManualDispatchFreeLastCallback = Bind<SteamApiManualDispatchPipeDelegate>("SteamAPI_ManualDispatch_FreeLastCallback");
|
||||
_steamApiManualDispatchGetApiCallResult = Bind<SteamApiManualDispatchGetApiCallResultDelegate>("SteamAPI_ManualDispatch_GetAPICallResult");
|
||||
_steamAppsIsSubscribed = Bind<SteamAppsBoolDelegate>("SteamAPI_ISteamApps_BIsSubscribed");
|
||||
_steamAppsIsSubscribedApp = Bind<SteamAppsAppIdBoolDelegate>("SteamAPI_ISteamApps_BIsSubscribedApp");
|
||||
_steamAppsIsDlcInstalled = Bind<SteamAppsAppIdBoolDelegate>("SteamAPI_ISteamApps_BIsDlcInstalled");
|
||||
_steamAppsIsAppInstalled = Bind<SteamAppsAppIdBoolDelegate>("SteamAPI_ISteamApps_BIsAppInstalled");
|
||||
_steamAppsGetCurrentGameLanguage = Bind<SteamAppsStringDelegate>("SteamAPI_ISteamApps_GetCurrentGameLanguage");
|
||||
_steamAppsGetAvailableGameLanguages = Bind<SteamAppsStringDelegate>("SteamAPI_ISteamApps_GetAvailableGameLanguages");
|
||||
_steamAppsGetAppBuildId = Bind<SteamAppsGetAppBuildIdDelegate>("SteamAPI_ISteamApps_GetAppBuildId");
|
||||
_steamFriendsAccessor = BindInterfaceAccessor<SteamFriendsAccessorDelegate>("SteamFriends", "ISteamFriends");
|
||||
_steamUserStatsAccessor = BindInterfaceAccessor<SteamUserStatsAccessorDelegate>("SteamUserStats", "ISteamUserStats");
|
||||
_steamUgcAccessor = BindInterfaceAccessor<SteamUgcAccessorDelegate>("SteamUGC", "ISteamUGC");
|
||||
@@ -1597,6 +2002,7 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
_steamUserStatsSetStatInt32 = Bind<SteamUserStatsSetStatInt32Delegate>("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");
|
||||
@@ -1735,13 +2141,38 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
{
|
||||
var libraryName = GetLibraryFileName();
|
||||
var runtimeIdentifier = GetRuntimeIdentifier();
|
||||
var candidates = new List<string>();
|
||||
|
||||
return new[]
|
||||
// Paths from SteamManager.ConfigureLibraryPath are tried first, in the order they were added.
|
||||
foreach (var customPath in CustomLibraryPaths)
|
||||
{
|
||||
Path.Combine(AppContext.BaseDirectory, "runtimes", runtimeIdentifier, "native", libraryName),
|
||||
Path.Combine(AppContext.BaseDirectory, libraryName),
|
||||
libraryName
|
||||
};
|
||||
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()
|
||||
@@ -1779,6 +2210,31 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
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)
|
||||
@@ -1856,6 +2312,18 @@ namespace OmegaLeo.HelperLib.Steamworks
|
||||
[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;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user