From 644b340ba7e1239990c572a4eaf64fec24fb5be0 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Sun, 11 Feb 2024 22:23:40 -0800 Subject: [PATCH] experiment: Unix Steam uses Steamworks.Net Completely untested, my favorite! --- src/XIVLauncher.Common.Unix/UnixSteam.cs | 202 +++++++++++++----- .../XIVLauncher.Common.Unix.csproj | 6 +- .../WindowsSteam.cs | 8 +- .../PlatformAbstractions/ISteam.cs | 3 +- 4 files changed, 165 insertions(+), 54 deletions(-) diff --git a/src/XIVLauncher.Common.Unix/UnixSteam.cs b/src/XIVLauncher.Common.Unix/UnixSteam.cs index 9fa1c8af4..8ca1280bf 100644 --- a/src/XIVLauncher.Common.Unix/UnixSteam.cs +++ b/src/XIVLauncher.Common.Unix/UnixSteam.cs @@ -1,81 +1,185 @@ +#nullable enable using System; +using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Steamworks; using XIVLauncher.Common.PlatformAbstractions; -namespace XIVLauncher.Common.Unix +namespace XIVLauncher.Common.Unix; + +/// +/// An implementation of ISteam for Unix endpoints. Borrows logic heavily from Facepunch's lib. +/// +public class UnixSteam : ISteam { - public class UnixSteam : ISteam + private Callback? textDismissedCallback; + + public void Initialize(uint appId) { - public UnixSteam() - { - SteamUtils.OnGamepadTextInputDismissed += b => OnGamepadTextInputDismissed?.Invoke(b); - } + // workaround because SetEnvironmentVariable doesn't actually touch the process environment on unix + [System.Runtime.InteropServices.DllImport("c")] + static extern int setenv(string name, string value, int overwrite); - public void Initialize(uint appId) - { - // workaround because SetEnvironmentVariable doesn't actually touch the process environment on unix - [System.Runtime.InteropServices.DllImport("c")] - static extern int setenv(string name, string value, int overwrite); + setenv("SteamAppId", appId.ToString(), 1); + setenv("SteamGameId", appId.ToString(), 1); - setenv("SteamAppId", appId.ToString(), 1); - setenv("SteamGameId", appId.ToString(), 1); + this.IsValid = SteamAPI.Init(); - SteamClient.Init(appId); - } + this.textDismissedCallback = Callback.Create(this.GamepadTextInputCallback); + } - public bool IsValid => SteamClient.IsValid; + public bool IsValid { get; private set; } - public bool BLoggedOn => SteamClient.IsLoggedOn; + public bool BLoggedOn => SteamUser.BLoggedOn(); - public bool BOverlayNeedsPresent => SteamUtils.DoesOverlayNeedPresent; + public bool BOverlayNeedsPresent => SteamUtils.BOverlayNeedsPresent(); - public void Shutdown() - { - SteamClient.Shutdown(); - } + public void Shutdown() + { + this.textDismissedCallback?.Dispose(); + SteamAPI.Shutdown(); + this.IsValid = false; + } - public async Task GetAuthSessionTicketAsync() - { - var ticket = await SteamUser.GetAuthSessionTicketAsync().ConfigureAwait(true); - return ticket?.Data; - } + public async Task GetAuthSessionTicketAsync() + { + var result = EResult.k_EResultPending; + AuthTicket? ticket = null; + var stopwatch = Stopwatch.StartNew(); - public bool IsAppInstalled(uint appId) + // We need to register our callback _before_ we can request our auth session ticket. Ignore the modified closure warning, this is what + // we expect after all. + using var cb = Callback.Create(t => { - return SteamApps.IsAppInstalled(appId); - } + // ReSharper disable AccessToModifiedClosure + if (ticket == null || t.m_hAuthTicket != ticket.Handle) return; - public string GetAppInstallDir(uint appId) - { - return SteamApps.AppInstallDir(appId); - } + result = t.m_eResult; + }); - public bool ShowGamepadTextInput(bool password, bool multiline, string description, int maxChars, string existingText = "") - { - return SteamUtils.ShowGamepadTextInput(password ? GamepadTextInputMode.Password : GamepadTextInputMode.Normal, multiline ? GamepadTextInputLineMode.MultipleLines : GamepadTextInputLineMode.SingleLine, description, maxChars, existingText); - } + ticket = this.GetAuthSessionTicket(); + if (ticket == null) return null; - public string GetEnteredGamepadText() + while (result == EResult.k_EResultPending) { - return SteamUtils.GetEnteredGamepadText(); + await Task.Delay(10); + + if (stopwatch.Elapsed.TotalSeconds > 10) + { + ticket.Cancel(); + return null; + } } - public bool ShowFloatingGamepadTextInput(ISteam.EFloatingGamepadTextInputMode mode, int x, int y, int width, int height) + if (result == EResult.k_EResultOK) { - // Facepunch.Steamworks doesn't have this... - return false; + return ticket.Data; } - public bool IsRunningOnSteamDeck() => SteamUtils.IsRunningOnSteamDeck; + ticket.Cancel(); + return null; + } + + public bool IsAppInstalled(uint appId) + { + return SteamApps.BIsAppInstalled((AppId_t)appId); + } + + public string GetAppInstallDir(uint appId) + { + SteamApps.GetAppInstallDir((AppId_t)appId, out var result, 1024); + return result; + } + + public bool ShowGamepadTextInput(bool password, bool multiline, string description, int maxChars, string existingText = "") + { + return SteamUtils.ShowGamepadTextInput( + password ? EGamepadTextInputMode.k_EGamepadTextInputModePassword : EGamepadTextInputMode.k_EGamepadTextInputModeNormal, + multiline ? EGamepadTextInputLineMode.k_EGamepadTextInputLineModeMultipleLines : EGamepadTextInputLineMode.k_EGamepadTextInputLineModeSingleLine, + description, + (uint)maxChars, + existingText + ); + } + + public string GetEnteredGamepadText() + { + var length = SteamUtils.GetEnteredGamepadTextLength(); + SteamUtils.GetEnteredGamepadTextInput(out var result, length); + + return result; + } + + public bool ShowFloatingGamepadTextInput(ISteam.EFloatingGamepadTextInputMode mode, int x, int y, int width, int height) + { + return SteamUtils.ShowFloatingGamepadTextInput((EFloatingGamepadTextInputMode)mode, x, y, width, height); + } + + public bool DismissFloatingGamepadTextInput() + { + return SteamUtils.DismissFloatingGamepadTextInput(); + } + + public bool IsRunningOnSteamDeck() + { + return SteamUtils.IsSteamRunningOnSteamDeck(); + } + + public uint GetServerRealTime() + { + return SteamUtils.GetServerRealTime(); + } + + public void ActivateGameOverlayToWebPage(string url, bool modal = false) + { + var mode = modal + ? EActivateGameOverlayToWebPageMode.k_EActivateGameOverlayToWebPageMode_Modal + : EActivateGameOverlayToWebPageMode.k_EActivateGameOverlayToWebPageMode_Default; + + SteamFriends.ActivateGameOverlayToWebPage(url, mode); + } + + public event Action? OnGamepadTextInputDismissed; + + private void GamepadTextInputCallback(GamepadTextInputDismissed_t cb) + { + this.OnGamepadTextInputDismissed?.Invoke(cb.m_bSubmitted); + } + + private AuthTicket? GetAuthSessionTicket() + { + var buffer = new byte[1024]; + var ticket = SteamUser.GetAuthSessionTicket(buffer, buffer.Length, out var ticketLength); + + if (ticket == HAuthTicket.Invalid) return null; + + return new AuthTicket + { + Data = buffer.Take((int)ticketLength).ToArray(), + Handle = ticket + }; + } - public uint GetServerRealTime() => (uint)((DateTimeOffset)SteamUtils.SteamServerTime).ToUnixTimeSeconds(); + private class AuthTicket : IDisposable + { + public byte[]? Data { get; set; } + public HAuthTicket Handle { get; set; } - public void ActivateGameOverlayToWebPage(string url, bool modal = false) + public void Cancel() { - SteamFriends.OpenWebOverlay(url, modal); + if (this.Handle != HAuthTicket.Invalid) + { + SteamUser.CancelAuthTicket(this.Handle); + } + + this.Handle = HAuthTicket.Invalid; + this.Data = null; } - public event Action OnGamepadTextInputDismissed; + public void Dispose() + { + this.Cancel(); + } } -} \ No newline at end of file +} diff --git a/src/XIVLauncher.Common.Unix/XIVLauncher.Common.Unix.csproj b/src/XIVLauncher.Common.Unix/XIVLauncher.Common.Unix.csproj index bcc892546..9fcd7d7dc 100644 --- a/src/XIVLauncher.Common.Unix/XIVLauncher.Common.Unix.csproj +++ b/src/XIVLauncher.Common.Unix/XIVLauncher.Common.Unix.csproj @@ -5,6 +5,7 @@ Shared XIVLauncher platform-specific implementations for Unix-like systems. 1.0.0 disable + true @@ -43,7 +44,6 @@ - - + - \ No newline at end of file + diff --git a/src/XIVLauncher.Common.Windows/WindowsSteam.cs b/src/XIVLauncher.Common.Windows/WindowsSteam.cs index a34259ab3..ce7b69db1 100644 --- a/src/XIVLauncher.Common.Windows/WindowsSteam.cs +++ b/src/XIVLauncher.Common.Windows/WindowsSteam.cs @@ -161,6 +161,12 @@ public bool ShowFloatingGamepadTextInput(ISteam.EFloatingGamepadTextInputMode mo return false; } + public bool DismissFloatingGamepadTextInput() + { + // Facepunch.Steamworks doesn't have this... + return false; + } + public bool IsRunningOnSteamDeck() => SteamUtils.IsRunningOnSteamDeck; public uint GetServerRealTime() => (uint)((DateTimeOffset)SteamUtils.SteamServerTime).ToUnixTimeSeconds(); @@ -172,4 +178,4 @@ public void ActivateGameOverlayToWebPage(string url, bool modal = false) public event Action OnGamepadTextInputDismissed; } -} \ No newline at end of file +} diff --git a/src/XIVLauncher.Common/PlatformAbstractions/ISteam.cs b/src/XIVLauncher.Common/PlatformAbstractions/ISteam.cs index 9c8a5cefe..223a752df 100644 --- a/src/XIVLauncher.Common/PlatformAbstractions/ISteam.cs +++ b/src/XIVLauncher.Common/PlatformAbstractions/ISteam.cs @@ -16,6 +16,7 @@ public interface ISteam bool ShowGamepadTextInput(bool password, bool multiline, string description, int maxChars, string existingText = ""); string GetEnteredGamepadText(); bool ShowFloatingGamepadTextInput(EFloatingGamepadTextInputMode mode, int x, int y, int width, int height); + bool DismissFloatingGamepadTextInput(); bool IsRunningOnSteamDeck(); uint GetServerRealTime(); public void ActivateGameOverlayToWebPage(string url, bool modal = false); @@ -29,4 +30,4 @@ enum EFloatingGamepadTextInputMode } event Action OnGamepadTextInputDismissed; -} \ No newline at end of file +}