using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using Steamworks.Data; namespace Steamworks { /// /// Functions for accessing and manipulating Steam user information. /// This is also where the APIs for Steam Voice are exposed. /// public class SteamUGC : SteamSharedClass { internal static ISteamUGC? Internal => Interface as ISteamUGC; internal override bool InitializeInterface( bool server ) { SetInterface( server, new ISteamUGC( server ) ); if ( Interface is null || Interface.Self == IntPtr.Zero ) return false; InstallEvents( server ); return true; } internal static void InstallEvents( bool server ) { Dispatch.Install( x => { if ( x.AppID == SteamClient.AppId ) { OnDownloadItemResult?.Invoke( x.Result, x.PublishedFileId ); } }, server); Dispatch.Install( x => { if ( x.AppID == SteamClient.AppId ) { OnItemSubscribed?.Invoke( x.AppID.Value, x.PublishedFileId ); } }, server ); Dispatch.Install( x => { if ( x.AppID == SteamClient.AppId ) { OnItemUnsubscribed?.Invoke( x.AppID.Value, x.PublishedFileId ); } }, server ); Dispatch.Install( x => { if ( x.AppID == SteamClient.AppId ) { OnItemInstalled?.Invoke( x.AppID.Value, x.PublishedFileId ); } }, server ); } /// /// Invoked after an item is downloaded. /// public static event Action? OnDownloadItemResult; /// /// Invoked when a new item is subscribed. /// public static event Action? OnItemSubscribed; public static event Action? OnItemUnsubscribed; public static event Action? OnItemInstalled; public static async Task DeleteFileAsync( PublishedFileId fileId ) { if (Internal is null) { return false; } var r = await Internal.DeleteItem( fileId ); return r?.Result == Result.OK; } /// /// Start downloading this item. You'll get notified of completion via . /// /// The ID of the file to download. /// If this should go straight to the top of the download list. /// if nothing went wrong and the download is started. public static bool Download( PublishedFileId fileId, bool highPriority = false ) { return Internal != null && Internal.DownloadItem( fileId, highPriority ); } /// /// Will attempt to download this item asyncronously - allowing you to instantly react to its installation. /// /// The ID of the file you download. /// An optional callback /// Allows to send a message to cancel the download anywhere during the process. /// How often to call the progress function. /// if downloaded and installed properly. public static async Task DownloadAsync( PublishedFileId fileId, Action? progress = null, int millisecondsUpdateDelay = 60, CancellationToken? ct = default ) { var item = new Steamworks.Ugc.Item( fileId ); var cancellationToken = ct ?? new CancellationTokenSource(TimeSpan.FromSeconds(60)).Token; async Task waitOrCancel() { cancellationToken.ThrowIfCancellationRequested(); await Task.Delay(millisecondsUpdateDelay); } progress?.Invoke( 0.0f ); Result downloadStartResult = Result.None; void onDownloadFinished(Result r, PublishedFileId id) { if (id != item.Id) { return; } downloadStartResult = r; } OnDownloadItemResult += onDownloadFinished; if (!Download(fileId, highPriority: true)) { return item.IsInstalled; } await Task.Delay(500); try { while (true) { cancellationToken.ThrowIfCancellationRequested(); progress?.Invoke(item.DownloadAmount); if (downloadStartResult != Result.None) { if (downloadStartResult != Result.OK) { return false; } break; } if (!item.IsDownloadPending && !item.IsDownloading) { if (item.IsInstalled) { break; } if (!Download(fileId, highPriority: true)) { return item.IsInstalled; } } await Task.Delay( millisecondsUpdateDelay ); } } finally { OnDownloadItemResult -= onDownloadFinished; } progress?.Invoke( 1.0f ); return item.IsInstalled; } /// /// Utility function to fetch a single item. Internally this uses Ugc.FileQuery - /// which you can use to query multiple items if you need to. /// public static async Task QueryFileAsync( PublishedFileId fileId ) { var result = await Ugc.Query.All .WithFileId( fileId ) .GetPageAsync( 1 ); if ( !result.HasValue || result.Value.ResultCount != 1 ) return null; var item = result.Value.Entries.First(); result.Value.Dispose(); return item; } public static async Task StartPlaytimeTracking(PublishedFileId fileId) { if (Internal is null) { return false; } var result = await Internal.StartPlaytimeTracking(new[] {fileId}, 1); return result?.Result == Result.OK; } public static async Task StopPlaytimeTracking(PublishedFileId fileId) { if (Internal is null) { return false; } var result = await Internal.StopPlaytimeTracking(new[] {fileId}, 1); return result?.Result == Result.OK; } public static async Task StopPlaytimeTrackingForAllItems() { if (Internal is null) { return false; } var result = await Internal.StopPlaytimeTrackingForAllItems(); return result?.Result == Result.OK; } public static uint NumSubscribedItems { get { return Internal?.GetNumSubscribedItems() ?? 0; } } public static PublishedFileId[] GetSubscribedItems() { if (Internal is null) { return Array.Empty(); } uint numSubscribed = NumSubscribedItems; PublishedFileId[] ids = new PublishedFileId[numSubscribed]; Internal.GetSubscribedItems(ids, numSubscribed); return ids; } /// /// Suspends all workshop downloads. /// Downloads will be suspended until you resume them by calling or when the game ends. /// public static void SuspendDownloads() => Internal?.SuspendDownloads(true); /// /// Resumes all workshop downloads. /// public static void ResumeDownloads() => Internal?.SuspendDownloads(false); /// /// Show the app's latest Workshop EULA to the user in an overlay window, where they can accept it or not. /// public static bool ShowWorkshopEula() { return Internal != null && Internal.ShowWorkshopEULA(); } /// /// Retrieve information related to the user's acceptance or not of the app's specific Workshop EULA. /// public static async Task GetWorkshopEulaStatus() { if ( Internal is null ) { return null; } var status = await Internal.GetWorkshopEULAStatus(); return status?.Accepted; } } }