Move Steam integrations to separate assembly, add cmdlets for various Steam actions
This commit is contained in:
parent
036ef8a8c4
commit
c5506b440f
67 changed files with 2512 additions and 517 deletions
|
|
@ -5,6 +5,9 @@ using LANCommander.SDK.PowerShell;
|
|||
using LANCommander.SDK.Providers;
|
||||
using LANCommander.SDK.Rpc.Client;
|
||||
using LANCommander.SDK.Services;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Extensions;
|
||||
using LANCommander.Steam.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using RpcSubscriber = LANCommander.SDK.Rpc.Clients.RpcSubscriber;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
<PackageReference Include="PeanutButter.INI" />
|
||||
<PackageReference Include="Semver" />
|
||||
<PackageReference Include="SharpCompress" />
|
||||
<PackageReference Include="Svrooij.PowerShell.DI" />
|
||||
<PackageReference Include="System.Management.Automation" />
|
||||
<PackageReference Include="YamlDotNet" />
|
||||
</ItemGroup>
|
||||
|
|
@ -50,5 +51,9 @@
|
|||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LANCommander.Steam\LANCommander.Steam.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ public class Settings
|
|||
public DebugSettings Debug { get; set; } = new();
|
||||
public UpdateSettings Updates { get; set; } = new();
|
||||
public IPXRelaySettings IPXRelay { get; set; } = new();
|
||||
public SteamSettings Steam { get; set; } = new();
|
||||
|
||||
public string Culture { get; set; } = "en-US";
|
||||
}
|
||||
11
LANCommander.SDK/Models/Settings/SteamSettings.cs
Normal file
11
LANCommander.SDK/Models/Settings/SteamSettings.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using LANCommander.Steam.Models;
|
||||
|
||||
namespace LANCommander.SDK.Models;
|
||||
|
||||
public class SteamSettings
|
||||
{
|
||||
public string Path { get; set; } = string.Empty;
|
||||
public string InstallDirectory { get; set; } = "";
|
||||
public ICollection<SteamCmdProfile> Profiles { get; set; } = [];
|
||||
}
|
||||
63
LANCommander.SDK/PowerShell/Cmdlets/Connect-SteamCmd.cs
Normal file
63
LANCommander.SDK/PowerShell/Cmdlets/Connect-SteamCmd.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Security;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Enums;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommunications.Connect, "SteamCmd")]
|
||||
[OutputType(typeof(SteamCmdStatus))]
|
||||
[GenerateBindings]
|
||||
public partial class ConnectSteamCmdCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false)]
|
||||
public SecureString? Password { get; set; }
|
||||
|
||||
[ServiceDependency]
|
||||
private ISteamCmdService _steamCmdService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken token)
|
||||
{
|
||||
if (_steamCmdService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamCmdService is not available in the PowerShell session"),
|
||||
"SteamCmdServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string? password = null;
|
||||
if (Password != null)
|
||||
{
|
||||
var ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(Password);
|
||||
try
|
||||
{
|
||||
password = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
var status = await _steamCmdService.LoginToSteamAsync(Username, password);
|
||||
|
||||
WriteObject(status);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "LoginError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
|
||||
[Cmdlet(VerbsData.Convert, "AspectRatio")]
|
||||
[OutputType(typeof(string))]
|
||||
public class ConvertAspectRatioCmdlet : BaseCmdlet
|
||||
public class ConvertAspectRatioCmdlet : Cmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public int Width { get; set; }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
{
|
||||
[Cmdlet(VerbsData.ConvertFrom, "SerializedBase64")]
|
||||
[OutputType(typeof(object))]
|
||||
public class ConvertFromSerializedBase64Cmdlet : BaseCmdlet
|
||||
public class ConvertFromSerializedBase64Cmdlet : Cmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
|
||||
public string Input { get; set; }
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
{
|
||||
[Cmdlet(VerbsData.ConvertTo, "SerializedBase64")]
|
||||
[OutputType(typeof(object))]
|
||||
public class ConvertToSerializedBase64Cmdlet : BaseCmdlet
|
||||
public class ConvertToSerializedBase64Cmdlet : Cmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
|
||||
public object Input { get; set; }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
{
|
||||
[Cmdlet(VerbsData.ConvertTo, "StringBytes")]
|
||||
[OutputType(typeof(byte[]))]
|
||||
public class ConvertToStringBytesCmdlet : BaseCmdlet
|
||||
public class ConvertToStringBytesCmdlet : Cmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
|
||||
public string Input { get; set; }
|
||||
|
|
|
|||
45
LANCommander.SDK/PowerShell/Cmdlets/Disconnect-SteamCmd.cs
Normal file
45
LANCommander.SDK/PowerShell/Cmdlets/Disconnect-SteamCmd.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Enums;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommunications.Disconnect, "SteamCmd")]
|
||||
[OutputType(typeof(SteamCmdStatus))]
|
||||
[GenerateBindings]
|
||||
public partial class DisconnectSteamCmdCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[ServiceDependency]
|
||||
private ISteamCmdService _steamCmdService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamCmdService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamCmdService is not available in the PowerShell session"),
|
||||
"SteamCmdServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var status = await _steamCmdService.LogoutAsync(Username);
|
||||
|
||||
WriteObject(status);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "LogoutError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ using System.Management.Automation;
|
|||
namespace LANCommander.SDK.PowerShell.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsData.Edit, "PatchBinary")]
|
||||
public class EditPatchBinaryCmdlet : BaseCmdlet
|
||||
public class EditPatchBinaryCmdlet : Cmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public long Offset { get; set; }
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
|
|||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsData.Edit, "PatchGameSpy")]
|
||||
public class EditPatchGameSpy : BaseCmdlet
|
||||
public class EditPatchGameSpy : Cmdlet
|
||||
{
|
||||
private const string GAMESPY_HOSTNAME = "gamespy.com";
|
||||
private const string GAMESPY_PUBLICKEY = "BF05D63E93751AD4A59A4A7389CF0BE8A22CCDEEA1E7F12C062D6E194472EFDA5184CCECEB4FBADF5EB1D7ABFE91181453972AA971F624AF9BA8F0F82E2869FB7D44BDE8D56EE50977898F3FEE75869622C4981F07506248BD3D092E8EA05C12B2FA37881176084C8F8B8756C4722CDC57D2AD28ACD3AD85934FB48D6B2D2027";
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
{
|
||||
[Cmdlet(VerbsCommon.Get, "GameManifest")]
|
||||
[OutputType(typeof(SDK.Models.Manifest.Game))]
|
||||
public class GetGameManifestCmdlet : BaseCmdlet
|
||||
public class GetGameManifestCmdlet : Cmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
|
||||
public string Path { get; set; }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
{
|
||||
[Cmdlet(VerbsCommon.Get, "VerticalFov")]
|
||||
[OutputType(typeof(string))]
|
||||
public class GetHorizontalFovCmdlet : BaseCmdlet
|
||||
public class GetHorizontalFovCmdlet : Cmdlet
|
||||
{
|
||||
[Parameter] public int Width { get; set; } = 0;
|
||||
[Parameter] public int Height { get; set; } = 0;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
{
|
||||
[Cmdlet(VerbsCommon.Get, "PrimaryDisplay")]
|
||||
[OutputType(typeof(string))]
|
||||
public class GetPrimaryDisplayCmdlet : BaseCmdlet
|
||||
public class GetPrimaryDisplayCmdlet : Cmdlet
|
||||
{
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Enums;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Get, "SteamCmdConnectionStatus")]
|
||||
[OutputType(typeof(SteamCmdConnectionStatus))]
|
||||
[GenerateBindings]
|
||||
public partial class GetSteamCmdConnectionStatusCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[ServiceDependency]
|
||||
private ISteamCmdService _steamCmdService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamCmdService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamCmdService is not available in the PowerShell session"),
|
||||
"SteamCmdServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var status = await _steamCmdService.GetConnectionStatusAsync(Username);
|
||||
WriteObject(status);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "GetConnectionStatusError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
43
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamCmdPath.cs
Normal file
43
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamCmdPath.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Get, "SteamCmdPath")]
|
||||
[OutputType(typeof(string))]
|
||||
[GenerateBindings]
|
||||
public partial class GetSteamCmdPathCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[ServiceDependency]
|
||||
private ISteamCmdService _steamCmdService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamCmdService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamCmdService is not available in the PowerShell session"),
|
||||
"SteamCmdServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var path = await _steamCmdService.AutoDetectSteamCmdPathAsync();
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
WriteObject(path);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "AutoDetectPathError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
47
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamCmdProfile.cs
Normal file
47
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamCmdProfile.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Models;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Get, "SteamCmdProfile")]
|
||||
[OutputType(typeof(SteamCmdProfile))]
|
||||
[GenerateBindings]
|
||||
public partial class GetSteamCmdProfileCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[ServiceDependency]
|
||||
private ISteamCmdService _steamCmdService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamCmdService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamCmdService is not available in the PowerShell session"),
|
||||
"SteamCmdServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var profile = await _steamCmdService.GetProfileAsync(Username);
|
||||
if (profile != null)
|
||||
{
|
||||
WriteObject(profile);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "GetProfileError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
44
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamCmdProfiles.cs
Normal file
44
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamCmdProfiles.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Models;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Get, "SteamCmdProfiles")]
|
||||
[OutputType(typeof(SteamCmdProfile))]
|
||||
[GenerateBindings]
|
||||
public partial class GetSteamCmdProfilesCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[ServiceDependency]
|
||||
private ISteamCmdService _steamCmdService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamCmdService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamCmdService is not available in the PowerShell session"),
|
||||
"SteamCmdServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var profiles = await _steamCmdService.GetProfilesAsync();
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
WriteObject(profile);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "GetProfilesError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
47
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamInstallJob.cs
Normal file
47
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamInstallJob.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Models;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Get, "SteamInstallJob")]
|
||||
[OutputType(typeof(SteamCmdInstallJob))]
|
||||
[GenerateBindings]
|
||||
public partial class GetSteamInstallJobCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public Guid JobId { get; set; }
|
||||
|
||||
[ServiceDependency]
|
||||
private ISteamCmdService _steamCmdService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamCmdService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamCmdService is not available in the PowerShell session"),
|
||||
"SteamCmdServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var job = _steamCmdService.GetInstallJob(JobId);
|
||||
if (job != null)
|
||||
{
|
||||
WriteObject(job);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "GetInstallJobError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
44
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamInstallJobs.cs
Normal file
44
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamInstallJobs.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Models;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Get, "SteamInstallJobs")]
|
||||
[OutputType(typeof(SteamCmdInstallJob))]
|
||||
[GenerateBindings]
|
||||
public partial class GetSteamInstallJobsCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[ServiceDependency]
|
||||
private ISteamCmdService _steamCmdService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamCmdService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamCmdService is not available in the PowerShell session"),
|
||||
"SteamCmdServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var jobs = _steamCmdService.GetInstallJobs();
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
WriteObject(job);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "GetInstallJobsError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
62
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamManual.cs
Normal file
62
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamManual.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Services;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Get, "SteamManual")]
|
||||
[OutputType(typeof(byte[]))]
|
||||
[GenerateBindings]
|
||||
public partial class GetSteamManualCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public int AppId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = false)]
|
||||
public string? OutputPath { get; set; }
|
||||
|
||||
[ServiceDependency]
|
||||
private SteamStoreService _steamStoreService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamStoreService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamStoreService is not available in the PowerShell session"),
|
||||
"SteamStoreServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var manualData = await _steamStoreService.DownloadManualAsync(AppId);
|
||||
|
||||
if (manualData == null || manualData.Length == 0)
|
||||
{
|
||||
WriteWarning($"No manual found for App ID {AppId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(OutputPath))
|
||||
{
|
||||
await File.WriteAllBytesAsync(OutputPath, manualData, cancellationToken);
|
||||
WriteObject(OutputPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteObject(manualData);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "DownloadManualError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
30
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamManualUri.cs
Normal file
30
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamManualUri.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Services;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Get, "SteamManualUri")]
|
||||
[OutputType(typeof(Uri))]
|
||||
[GenerateBindings]
|
||||
public partial class GetSteamManualUriCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public int AppId { get; set; }
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var uri = SteamStoreService.GetManualUri(AppId);
|
||||
WriteObject(uri);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "GetManualUriError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
34
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamWebAssetUri.cs
Normal file
34
LANCommander.SDK/PowerShell/Cmdlets/Get-SteamWebAssetUri.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam;
|
||||
using LANCommander.Steam.Services;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Get, "SteamWebAssetUri")]
|
||||
[OutputType(typeof(Uri))]
|
||||
[GenerateBindings]
|
||||
public partial class GetSteamWebAssetUriCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public int AppId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, Position = 1)]
|
||||
public WebAssetType WebAssetType { get; set; }
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var uri = SteamStoreService.GetWebAssetUri(AppId, WebAssetType);
|
||||
WriteObject(uri);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "GetWebAssetUriError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
{
|
||||
[Cmdlet(VerbsCommon.Get, "UserCustomField")]
|
||||
[OutputType(typeof(string))]
|
||||
public class GetUserCustomFieldCmdlet(ProfileClient profileClient) : BaseCmdlet
|
||||
public class GetUserCustomFieldCmdlet(ProfileClient profileClient) : Cmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public string Name { get; set; }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
{
|
||||
[Cmdlet(VerbsCommon.Get, "VerticalFov")]
|
||||
[OutputType(typeof(string))]
|
||||
public class GetVerticalFovCmdlet : BaseCmdlet
|
||||
public class GetVerticalFovCmdlet : Cmdlet
|
||||
{
|
||||
[Parameter] public int Width { get; set; } = 0;
|
||||
[Parameter] public int Height { get; set; } = 0;
|
||||
|
|
|
|||
50
LANCommander.SDK/PowerShell/Cmdlets/Install-SteamContent.cs
Normal file
50
LANCommander.SDK/PowerShell/Cmdlets/Install-SteamContent.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Models;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsLifecycle.Install, "SteamContent")]
|
||||
[OutputType(typeof(SteamCmdInstallJob))]
|
||||
[GenerateBindings]
|
||||
public partial class InstallSteamContentCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public uint AppId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, Position = 1)]
|
||||
public string InstallDirectory { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = false)]
|
||||
public string? Username { get; set; }
|
||||
|
||||
[ServiceDependency]
|
||||
private ISteamCmdService _steamCmdService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamCmdService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamCmdService is not available in the PowerShell session"),
|
||||
"SteamCmdServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var job = await _steamCmdService.InstallContentAsync(AppId, InstallDirectory, Username);
|
||||
WriteObject(job);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "InstallContentError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
{
|
||||
[Cmdlet(VerbsData.Out, "PlayerAvatar")]
|
||||
[OutputType(typeof(string))]
|
||||
public class OutPlayerAvatarCmdlet(ProfileClient profileClient) : BaseCmdlet
|
||||
public class OutPlayerAvatarCmdlet(ProfileClient profileClient) : Cmdlet
|
||||
{
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Remove, "SteamCmdProfile")]
|
||||
[GenerateBindings]
|
||||
public partial class RemoveSteamCmdProfileCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[ServiceDependency]
|
||||
private ISteamCmdService _steamCmdService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamCmdService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamCmdService is not available in the PowerShell session"),
|
||||
"SteamCmdServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _steamCmdService.DeleteProfileAsync(Username);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "DeleteProfileError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
44
LANCommander.SDK/PowerShell/Cmdlets/Remove-SteamContent.cs
Normal file
44
LANCommander.SDK/PowerShell/Cmdlets/Remove-SteamContent.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Enums;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Remove, "SteamContent")]
|
||||
[OutputType(typeof(SteamCmdStatus))]
|
||||
[GenerateBindings]
|
||||
public partial class RemoveSteamContentCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public string InstallDirectory { get; set; } = string.Empty;
|
||||
|
||||
[ServiceDependency]
|
||||
private ISteamCmdService _steamCmdService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamCmdService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamCmdService is not available in the PowerShell session"),
|
||||
"SteamCmdServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var status = await _steamCmdService.RemoveContentAsync(InstallDirectory);
|
||||
WriteObject(status);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "RemoveContentError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
47
LANCommander.SDK/PowerShell/Cmdlets/Search-SteamGames.cs
Normal file
47
LANCommander.SDK/PowerShell/Cmdlets/Search-SteamGames.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam;
|
||||
using LANCommander.Steam.Services;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Search, "SteamGames")]
|
||||
[OutputType(typeof(GameSearchResult))]
|
||||
[GenerateBindings]
|
||||
public partial class SearchSteamGamesCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public string Keyword { get; set; } = string.Empty;
|
||||
|
||||
[ServiceDependency]
|
||||
private SteamStoreService _steamStoreService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamStoreService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamStoreService is not available in the PowerShell session"),
|
||||
"SteamStoreServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var results = await _steamStoreService.SearchGamesAsync(Keyword);
|
||||
foreach (var result in results)
|
||||
{
|
||||
WriteObject(result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "SearchGamesError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
51
LANCommander.SDK/PowerShell/Cmdlets/Set-SteamCmdProfile.cs
Normal file
51
LANCommander.SDK/PowerShell/Cmdlets/Set-SteamCmdProfile.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Models;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsCommon.Set, "SteamCmdProfile")]
|
||||
[GenerateBindings]
|
||||
public partial class SetSteamCmdProfileCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[Parameter(Mandatory = true, Position = 1)]
|
||||
public string InstallDirectory { get; set; } = string.Empty;
|
||||
|
||||
[ServiceDependency]
|
||||
private ISteamCmdService _steamCmdService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamCmdService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamCmdService is not available in the PowerShell session"),
|
||||
"SteamCmdServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var profile = new SteamCmdProfile
|
||||
{
|
||||
Username = Username,
|
||||
InstallDirectory = InstallDirectory
|
||||
};
|
||||
|
||||
await _steamCmdService.SaveProfileAsync(profile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "SaveProfileError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
43
LANCommander.SDK/PowerShell/Cmdlets/Stop-SteamInstallJob.cs
Normal file
43
LANCommander.SDK/PowerShell/Cmdlets/Stop-SteamInstallJob.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsLifecycle.Stop, "SteamInstallJob")]
|
||||
[OutputType(typeof(bool))]
|
||||
[GenerateBindings]
|
||||
public partial class StopSteamInstallJobCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public Guid JobId { get; set; }
|
||||
|
||||
[ServiceDependency]
|
||||
private ISteamCmdService _steamCmdService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamCmdService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamCmdService is not available in the PowerShell session"),
|
||||
"SteamCmdServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var cancelled = await _steamCmdService.CancelInstallJobAsync(JobId);
|
||||
WriteObject(cancelled);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "CancelInstallJobError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
43
LANCommander.SDK/PowerShell/Cmdlets/Test-SteamManual.cs
Normal file
43
LANCommander.SDK/PowerShell/Cmdlets/Test-SteamManual.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Services;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsDiagnostic.Test, "SteamManual")]
|
||||
[OutputType(typeof(bool))]
|
||||
[GenerateBindings]
|
||||
public partial class TestSteamManualCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public int AppId { get; set; }
|
||||
|
||||
[ServiceDependency]
|
||||
private SteamStoreService _steamStoreService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamStoreService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamStoreService is not available in the PowerShell session"),
|
||||
"SteamStoreServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var exists = await _steamStoreService.HasManualAsync(AppId);
|
||||
WriteObject(exists);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "HasManualError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
47
LANCommander.SDK/PowerShell/Cmdlets/Test-SteamWebAsset.cs
Normal file
47
LANCommander.SDK/PowerShell/Cmdlets/Test-SteamWebAsset.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam;
|
||||
using LANCommander.Steam.Services;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets;
|
||||
|
||||
[Cmdlet(VerbsDiagnostic.Test, "SteamWebAsset")]
|
||||
[OutputType(typeof(bool))]
|
||||
[GenerateBindings]
|
||||
public partial class TestSteamWebAssetCmdlet : DependencyCmdlet<PowerShellStartup>
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public int AppId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, Position = 1)]
|
||||
public WebAssetType WebAssetType { get; set; }
|
||||
|
||||
[ServiceDependency]
|
||||
private SteamStoreService _steamStoreService;
|
||||
|
||||
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_steamStoreService == null)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException("SteamStoreService is not available in the PowerShell session"),
|
||||
"SteamStoreServiceNotAvailable",
|
||||
ErrorCategory.InvalidOperation,
|
||||
null));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var (exists, _) = await _steamStoreService.HasWebAssetAsync(AppId, WebAssetType);
|
||||
WriteObject(exists);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteError(new ErrorRecord(ex, "HasWebAssetError", ErrorCategory.OperationStopped, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
/// </summary>
|
||||
[Cmdlet(VerbsData.Update, "IniValue")]
|
||||
[OutputType(typeof(string))]
|
||||
public class UpdateIniValueCmdlet : BaseCmdlet
|
||||
public class UpdateIniValueCmdlet : Cmdlet
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the section in the INI file that contains the key to be updated.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
{
|
||||
[Cmdlet(VerbsData.Update, "UserCustomField")]
|
||||
[OutputType(typeof(string))]
|
||||
public class UpdateUserCustomFieldCmdlet(ProfileClient profileClient) : BaseCmdlet
|
||||
public class UpdateUserCustomFieldCmdlet(ProfileClient profileClient) : Cmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public string Name { get; set; }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
{
|
||||
[Cmdlet(VerbsCommunications.Write, "GameManifest")]
|
||||
[OutputType(typeof(string))]
|
||||
public class WriteGameManifestCmdlet : BaseCmdlet
|
||||
public class WriteGameManifestCmdlet : Cmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
|
||||
public string Path { get; set; }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
|
|||
|
||||
[Cmdlet(VerbsCommunications.Write, "ReplaceContentInFile")]
|
||||
[OutputType(typeof(string))]
|
||||
public class ReplaceContentInFileCmdlet : BaseCmdlet
|
||||
public class ReplaceContentInFileCmdlet : Cmdlet
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
public string Pattern { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
using System.Management.Automation;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell.Cmdlets
|
||||
{
|
||||
public abstract class BaseCmdlet : Cmdlet
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -27,5 +27,28 @@ public static class InitialSessionStateExtensions
|
|||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Update-UserCustomField", typeof(UpdateUserCustomFieldCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Write-GameManifest", typeof(WriteGameManifestCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Write-ReplaceContentInFile", typeof(ReplaceContentInFileCmdlet), null));
|
||||
|
||||
// SteamCMD cmdlets
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Connect-SteamCmd", typeof(ConnectSteamCmdCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Disconnect-SteamCmd", typeof(DisconnectSteamCmdCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamCmdConnectionStatus", typeof(GetSteamCmdConnectionStatusCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamCmdPath", typeof(GetSteamCmdPathCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamCmdProfile", typeof(GetSteamCmdProfileCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamCmdProfiles", typeof(GetSteamCmdProfilesCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamInstallJob", typeof(GetSteamInstallJobCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamInstallJobs", typeof(GetSteamInstallJobsCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Install-SteamContent", typeof(InstallSteamContentCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Remove-SteamContent", typeof(RemoveSteamContentCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Remove-SteamCmdProfile", typeof(RemoveSteamCmdProfileCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Set-SteamCmdProfile", typeof(SetSteamCmdProfileCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Stop-SteamInstallJob", typeof(StopSteamInstallJobCmdlet), null));
|
||||
|
||||
// Steam Store cmdlets
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamManual", typeof(GetSteamManualCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamManualUri", typeof(GetSteamManualUriCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Get-SteamWebAssetUri", typeof(GetSteamWebAssetUriCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Search-SteamGames", typeof(SearchSteamGamesCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Test-SteamManual", typeof(TestSteamManualCmdlet), null));
|
||||
initialSessionState.Commands.Add(new SessionStateCmdletEntry("Test-SteamWebAsset", typeof(TestSteamWebAssetCmdlet), null));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
using LANCommander.SDK.Enums;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
|||
18
LANCommander.SDK/PowerShell/PowerShellStartup.cs
Normal file
18
LANCommander.SDK/PowerShell/PowerShellStartup.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using LANCommander.SDK.Extensions;
|
||||
using LANCommander.SDK.Models;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Svrooij.PowerShell.DI;
|
||||
|
||||
namespace LANCommander.SDK.PowerShell;
|
||||
|
||||
public class PowerShellStartup : PsStartup
|
||||
{
|
||||
public override void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddLANCommanderClient<Settings>();
|
||||
services.AddScoped<ISteamCmdService, SteamCmdService>();
|
||||
services.AddScoped<SteamStoreService>();
|
||||
}
|
||||
}
|
||||
49
LANCommander.SDK/Providers/SteamCmdProfileStore.cs
Normal file
49
LANCommander.SDK/Providers/SteamCmdProfileStore.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.SDK.Abstractions;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Models;
|
||||
|
||||
namespace LANCommander.SDK.Providers;
|
||||
|
||||
public class SteamCmdProfileStore(ISettingsProvider settingsProvider) : ISteamCmdProfileStore
|
||||
{
|
||||
public async Task<IEnumerable<SteamCmdProfile>> GetAllAsync()
|
||||
=> settingsProvider.CurrentValue.Steam.Profiles;
|
||||
|
||||
public async Task<SteamCmdProfile?> GetByUsernameAsync(string username)
|
||||
=> settingsProvider.CurrentValue.Steam.Profiles.FirstOrDefault(p => p.Username == username);
|
||||
|
||||
public async Task SaveAsync(SteamCmdProfile profile)
|
||||
{
|
||||
settingsProvider.Update(s =>
|
||||
{
|
||||
var existing = s.Steam.Profiles.FirstOrDefault(p => p.Username == profile.Username);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
// Update existing profile
|
||||
existing.InstallDirectory = profile.InstallDirectory;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new profile
|
||||
s.Steam.Profiles.Add(profile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(string username)
|
||||
{
|
||||
settingsProvider.Update(s =>
|
||||
{
|
||||
var existing = s.Steam.Profiles.FirstOrDefault(p => p.Username == username);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
s.Steam.Profiles.Remove(existing);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -55,7 +55,6 @@ public static class IServiceCollectionExtensions
|
|||
services.AddScoped<RoleService>();
|
||||
services.AddScoped<UserCustomFieldService>();
|
||||
services.AddScoped<GameCustomFieldService>();
|
||||
services.AddScoped<SteamCMDService>();
|
||||
services.AddScoped<ChatService>();
|
||||
services.AddScoped<ChatMessageService>();
|
||||
services.AddScoped<ChatThreadService>();
|
||||
|
|
|
|||
|
|
@ -1,441 +0,0 @@
|
|||
using LANCommander.Server.Services.Enums;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace LANCommander.Server.Services;
|
||||
|
||||
public class SteamCMDService(
|
||||
ILogger<SteamCMDService> logger,
|
||||
SettingsProvider<Settings.Settings> settingsProvider) : BaseService(logger, settingsProvider)
|
||||
{
|
||||
public async Task<SteamCmdConnectionStatus> GetConnectionStatusAsync(string username)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsValidUsername(username))
|
||||
throw new ArgumentException("Invalid username", nameof(username));
|
||||
|
||||
// Auto-populate SteamCMD path if not configured
|
||||
if (string.IsNullOrWhiteSpace(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
|
||||
{
|
||||
var detectedPath = await AutoDetectSteamCmdPathAsync();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(detectedPath))
|
||||
{
|
||||
_settingsProvider.Update(s =>
|
||||
{
|
||||
s.Server.SteamCMD.Path = detectedPath;
|
||||
});
|
||||
|
||||
_logger.LogInformation("Auto-detected SteamCMD at: {Path}", detectedPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("SteamCMD path is not configured and could not be auto-detected");
|
||||
return SteamCmdConnectionStatus.NotInstalled;
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
|
||||
{
|
||||
_logger.LogWarning("SteamCMD executable not found at configured path: {Path}", _settingsProvider.CurrentValue.Server.SteamCMD.Path);
|
||||
return SteamCmdConnectionStatus.NotInstalled;
|
||||
}
|
||||
|
||||
// Try to run steamcmd with +quit to check if it's working
|
||||
var result = await ExecuteSteamCmdCommandAsync("+quit");
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
// Check if we're logged in by trying to get user info
|
||||
var loginResult = await ExecuteSteamCmdCommandAsync($"+login {username} +quit", TimeSpan.FromSeconds(30));
|
||||
return loginResult.Success ? SteamCmdConnectionStatus.Authenticated : SteamCmdConnectionStatus.Unauthenticated;
|
||||
}
|
||||
|
||||
return SteamCmdConnectionStatus.NotInstalled;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error checking SteamCMD connection status");
|
||||
return SteamCmdConnectionStatus.NotInstalled;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> AutoDetectSteamCmdPathAsync()
|
||||
{
|
||||
var possiblePaths = new List<string>();
|
||||
|
||||
// Windows paths
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// Common Steam installation directories
|
||||
var steamPaths = new[]
|
||||
{
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Steam", "steamcmd.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Steam", "steamcmd.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Steam", "steamcmd.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Steam", "steamcmd.exe"),
|
||||
"C:\\Steam\\steamcmd.exe",
|
||||
"C:\\Program Files\\Steam\\steamcmd.exe",
|
||||
"C:\\Program Files (x86)\\Steam\\steamcmd.exe"
|
||||
};
|
||||
|
||||
possiblePaths.AddRange(steamPaths);
|
||||
|
||||
// Check PATH environment variable
|
||||
var pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? [];
|
||||
|
||||
foreach (var dir in pathDirs)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(dir))
|
||||
{
|
||||
possiblePaths.Add(Path.Combine(dir, "steamcmd.exe"));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Linux paths
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
possiblePaths.AddRange(new[]
|
||||
{
|
||||
"/app/Data/Steam/steamcmd.sh",
|
||||
"/usr/local/bin/steamcmd",
|
||||
"/usr/bin/steamcmd",
|
||||
"/home/steam/steamcmd/steamcmd.sh",
|
||||
"/opt/steamcmd/steamcmd.sh",
|
||||
"/var/lib/steam/steamcmd/steamcmd.sh"
|
||||
});
|
||||
|
||||
// Check PATH environment variable
|
||||
var pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(':') ?? [];
|
||||
|
||||
foreach (var dir in pathDirs)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(dir))
|
||||
{
|
||||
possiblePaths.Add(Path.Combine(dir, "steamcmd"));
|
||||
}
|
||||
}
|
||||
}
|
||||
// macOS paths
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
possiblePaths.AddRange(new[]
|
||||
{
|
||||
"/usr/local/bin/steamcmd",
|
||||
"/opt/homebrew/bin/steamcmd",
|
||||
"/Applications/Steam.app/Contents/MacOS/steamcmd"
|
||||
});
|
||||
|
||||
// Check PATH environment variable
|
||||
var pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(':') ?? [];
|
||||
|
||||
foreach (var dir in pathDirs)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(dir))
|
||||
{
|
||||
possiblePaths.Add(Path.Combine(dir, "steamcmd"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check each possible path
|
||||
foreach (var path in possiblePaths)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Verify it's actually SteamCMD by checking if it responds to --version or +quit
|
||||
var testResult = await ExecuteSteamCmdCommandAsync("+quit", TimeSpan.FromSeconds(30), path);
|
||||
|
||||
if (testResult.Success)
|
||||
{
|
||||
_logger.LogDebug("Found SteamCMD at: {Path}", path);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Failed to verify SteamCMD at: {Path}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogWarning("SteamCMD not found in common installation locations");
|
||||
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
public async Task<SteamCmdStatus> LoginToSteamAsync(string username, string? password = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsValidUsername(username))
|
||||
throw new ArgumentException("Invalid username", nameof(username));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
|
||||
{
|
||||
_logger.LogError("SteamCMD path is not configured");
|
||||
return SteamCmdStatus.PathNotConfigured;
|
||||
}
|
||||
|
||||
if (!File.Exists(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
|
||||
{
|
||||
_logger.LogError("SteamCMD executable not found at configured path: {Path}", _settingsProvider.CurrentValue.Server.SteamCMD.Path);
|
||||
return SteamCmdStatus.ExecutableNotFound;
|
||||
}
|
||||
|
||||
var loginCommand = string.IsNullOrWhiteSpace(password)
|
||||
? $"+login {username}"
|
||||
: $"+login {username} {password}";
|
||||
|
||||
var result = await ExecuteSteamCmdCommandAsync($"{loginCommand} +quit", TimeSpan.FromSeconds(30));
|
||||
|
||||
if (result.Output.Contains("Invalid Password"))
|
||||
return SteamCmdStatus.InvalidPassword;
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.LogInformation("Successfully logged into Steam as {Username}", username);
|
||||
return SteamCmdStatus.Success;
|
||||
}
|
||||
|
||||
_logger.LogError("Failed to log into Steam: {Error}", result.ErrorOutput);
|
||||
return SteamCmdStatus.Error;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error logging into Steam");
|
||||
return SteamCmdStatus.UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SteamCmdStatus> LogoutAsync(string username)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsValidUsername(username))
|
||||
return SteamCmdStatus.InvalidUsername;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
|
||||
{
|
||||
_logger.LogError("SteamCMD path is not configured");
|
||||
return SteamCmdStatus.PathNotConfigured;
|
||||
}
|
||||
|
||||
if (!File.Exists(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
|
||||
{
|
||||
_logger.LogError("SteamCMD executable not found at configured path: {Path}", _settingsProvider.CurrentValue.Server.SteamCMD.Path);
|
||||
return SteamCmdStatus.ExecutableNotFound;
|
||||
}
|
||||
|
||||
var logoutCommand = $"+logout {username}";
|
||||
|
||||
var result = await ExecuteSteamCmdCommandAsync($"{logoutCommand} +quit");
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.LogInformation("Successfully logged out of the Steam account {Username}", username);
|
||||
return SteamCmdStatus.Success;
|
||||
}
|
||||
|
||||
_logger.LogError("Failed to log out of Steam: {Error}", result.ErrorOutput);
|
||||
return SteamCmdStatus.Error;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error logging out of Steam");
|
||||
return SteamCmdStatus.UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SteamCmdStatus> InstallContentAsync(uint appId, string installDirectory, string username)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsValidUsername(username))
|
||||
throw new ArgumentException("Invalid username", nameof(username));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
|
||||
{
|
||||
_logger.LogError("SteamCMD path is not configured");
|
||||
return SteamCmdStatus.PathNotConfigured;
|
||||
}
|
||||
|
||||
if (!File.Exists(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
|
||||
{
|
||||
_logger.LogError("SteamCMD executable not found at configured path: {Path}", _settingsProvider.CurrentValue.Server.SteamCMD.Path);
|
||||
return SteamCmdStatus.ExecutableNotFound;
|
||||
}
|
||||
|
||||
// Ensure install directory exists
|
||||
Directory.CreateDirectory(installDirectory);
|
||||
|
||||
var commands = new List<string>();
|
||||
|
||||
// Add login command if credentials provided
|
||||
if (!string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
var loginCommand = $"+login {username}";
|
||||
|
||||
commands.Add(loginCommand);
|
||||
}
|
||||
else
|
||||
{
|
||||
commands.Add("+login anonymous");
|
||||
}
|
||||
|
||||
// Add install commands
|
||||
commands.Add($"+force_install_dir \"{installDirectory}\"");
|
||||
commands.Add($"+app_update {appId} validate");
|
||||
commands.Add("+quit");
|
||||
|
||||
var commandString = string.Join(" ", commands);
|
||||
var result = await ExecuteSteamCmdCommandAsync(commandString);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.LogInformation("Successfully installed Steam app {AppId} to {InstallDirectory}", appId, installDirectory);
|
||||
return SteamCmdStatus.Success;
|
||||
}
|
||||
|
||||
_logger.LogError("Failed to install Steam app {AppId}: {Error}", appId, result.ErrorOutput);
|
||||
return SteamCmdStatus.Error;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error installing Steam content for app {AppId}", appId);
|
||||
return SteamCmdStatus.UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SteamCmdStatus> RemoveContentAsync(string installDirectory)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(installDirectory))
|
||||
{
|
||||
_logger.LogError("Install directory is not specified");
|
||||
return SteamCmdStatus.InstallDirectoryNotFound;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(installDirectory))
|
||||
{
|
||||
_logger.LogWarning("Install directory does not exist: {InstallDirectory}", installDirectory);
|
||||
return SteamCmdStatus.Success; // Consider it already removed
|
||||
}
|
||||
|
||||
// Remove the directory and all its contents
|
||||
Directory.Delete(installDirectory, true);
|
||||
|
||||
_logger.LogInformation("Successfully removed content from {InstallDirectory}", installDirectory);
|
||||
return SteamCmdStatus.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error removing content from {InstallDirectory}", installDirectory);
|
||||
return SteamCmdStatus.UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<SteamCmdResult> ExecuteSteamCmdCommandAsync(string arguments, TimeSpan? timeout = null, string steamCmdPath = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
var executablePath = String.IsNullOrWhiteSpace(steamCmdPath) ? _settingsProvider.CurrentValue.Server.SteamCMD.Path : steamCmdPath;
|
||||
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = executablePath,
|
||||
Arguments = arguments,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
|
||||
using var process = new Process { StartInfo = processStartInfo };
|
||||
var output = new StringBuilder();
|
||||
var error = new StringBuilder();
|
||||
|
||||
process.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
output.AppendLine(e.Data);
|
||||
_logger.LogDebug("SteamCMD Output: {Output}", e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
error.AppendLine(e.Data);
|
||||
_logger.LogDebug("SteamCMD Error: {Error}", e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
try
|
||||
{
|
||||
if (timeout.HasValue)
|
||||
await process.WaitForExitAsync().WaitAsync(timeout.Value);
|
||||
else
|
||||
await process.WaitForExitAsync();
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
_logger.LogWarning("SteamCMD timed out");
|
||||
|
||||
process.Kill();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while executing SteamCMD");
|
||||
}
|
||||
|
||||
return new SteamCmdResult
|
||||
{
|
||||
Success = process.ExitCode == 0,
|
||||
ExitCode = process.ExitCode,
|
||||
Output = output.ToString(),
|
||||
ErrorOutput = error.ToString()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error executing SteamCMD command: {Arguments}", arguments);
|
||||
|
||||
return new SteamCmdResult
|
||||
{
|
||||
Success = false,
|
||||
ExitCode = -1,
|
||||
ErrorOutput = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidUsername(string value)
|
||||
{
|
||||
return !string.IsNullOrEmpty(value) &&
|
||||
Regex.IsMatch(value, @"^[a-zA-Z0-9_]+$");
|
||||
}
|
||||
|
||||
private class SteamCmdResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public int ExitCode { get; set; }
|
||||
public string Output { get; set; } = string.Empty;
|
||||
public string ErrorOutput { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,6 @@ public class ServerSettings
|
|||
public RoleSettings Roles { get; set; } = new();
|
||||
public ScriptSettings Scripts { get; set; } = new();
|
||||
public GameServerSettings GameServers { get; set; } = new();
|
||||
public SteamCmdSettings SteamCMD { get; set; } = new();
|
||||
public UpdateSettings Update { get; set; } = new();
|
||||
public UserSaveSettings UserSaves { get; set; } = new();
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
namespace LANCommander.Server.Settings.Models;
|
||||
|
||||
public class SteamCmdProfile
|
||||
{
|
||||
public string Username { get; set; } = String.Empty;
|
||||
public string InstallDirectory { get; set; } = String.Empty;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
namespace LANCommander.Server.Settings.Models;
|
||||
|
||||
public class SteamCmdSettings
|
||||
{
|
||||
public string Path { get; set; } = String.Empty;
|
||||
public string InstallDirectory { get; set; } = "";
|
||||
public ICollection<SteamCmdProfile> Profiles { get; set; } = [];
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ builder.AddServerProcessStatusMonitor();
|
|||
builder.AddLANCommanderServices();
|
||||
builder.AddMigrations();
|
||||
builder.AddDatabase(args);
|
||||
builder.UseSteam();
|
||||
|
||||
builder.Services.AddHealthChecks();
|
||||
|
||||
|
|
|
|||
18
LANCommander.Server/Startup/Steam.cs
Normal file
18
LANCommander.Server/Startup/Steam.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using LANCommander.Steam.Extensions;
|
||||
|
||||
namespace LANCommander.Server.Startup;
|
||||
|
||||
public static class Steam
|
||||
{
|
||||
public static WebApplicationBuilder UseSteam(this WebApplicationBuilder builder)
|
||||
{
|
||||
var settings = builder.Configuration.Get<Settings.Settings>();
|
||||
|
||||
builder.Services.AddSteamCmd(o =>
|
||||
{
|
||||
o.ExecutablePath = settings?.Steam.Path;
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
@page "/Settings/Integrations/Steam"
|
||||
@using LANCommander.Server.Services.Enums
|
||||
@using LANCommander.Server.Settings
|
||||
@using LANCommander.Server.Settings.Models
|
||||
@using LANCommander.Server.UI.Pages.Settings.Components
|
||||
@using LANCommander.Steam.Abstractions
|
||||
@using LANCommander.Steam.Enums
|
||||
@using LANCommander.Steam.Models
|
||||
@using Microsoft.Extensions.Options
|
||||
@inject SteamCMDService SteamCmdService
|
||||
@inject ISteamCmdService SteamCmdService
|
||||
@inject ISteamCmdProfileStore SteamCmdProfileStore
|
||||
@inject IOptions<Settings> Settings
|
||||
@inject SettingsProvider<Settings> SettingsProvider
|
||||
@inject IMessageService MessageService
|
||||
|
|
@ -20,7 +22,7 @@
|
|||
<PageContent>
|
||||
<Form Model="Settings.Value" Layout="@FormLayout.Vertical">
|
||||
<FormItem Label="SteamCMD Path">
|
||||
<FilePicker @bind-Value="context.Server.SteamCMD.Path" Title="Choose SteamCMD Executable Path" Root="@_rootPath" />
|
||||
<FilePicker @bind-Value="SteamCmdService.ExecutablePath" Title="Choose SteamCMD Executable Path" Root="@_rootPath" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
|
|
@ -28,7 +30,7 @@
|
|||
|
||||
<Flex Direction="FlexDirection.Vertical" Gap="FlexGap.Large">
|
||||
<Collapse>
|
||||
@foreach (var profile in Settings.Value.Server.SteamCMD.Profiles)
|
||||
@foreach (var profile in _profiles)
|
||||
{
|
||||
<Panel Header="@(String.IsNullOrWhiteSpace(profile.Username) ? "New Profile" : profile.Username)">
|
||||
<ExtraTemplate>
|
||||
|
|
@ -57,6 +59,8 @@
|
|||
|
||||
SteamCmdConnectionStatus _connectionStatus;
|
||||
|
||||
List<SteamCmdProfile> _profiles = [];
|
||||
|
||||
string _username;
|
||||
string _password;
|
||||
|
||||
|
|
@ -64,35 +68,20 @@
|
|||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!Settings.Value.Server.SteamCMD.Profiles.Any())
|
||||
_profiles = (await SteamCmdService.GetProfilesAsync()).ToList();
|
||||
|
||||
if (!_profiles.Any())
|
||||
AddProfile();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Settings.Value.Server.SteamCMD.Path))
|
||||
{
|
||||
var detectedPath = await SteamCmdService.AutoDetectSteamCmdPathAsync();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(detectedPath))
|
||||
{
|
||||
Settings.Value.Server.SteamCMD.Path = detectedPath;
|
||||
|
||||
SettingsProvider.Update(s =>
|
||||
{
|
||||
s.Server.SteamCMD.Path = detectedPath;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddProfile()
|
||||
{
|
||||
Settings.Value.Server.SteamCMD.Profiles.Add(new SteamCmdProfile());
|
||||
_profiles.Add(new SteamCmdProfile());
|
||||
}
|
||||
|
||||
async Task RemoveProfile(SteamCmdProfile profile)
|
||||
{
|
||||
Settings.Value.Server.SteamCMD.Profiles.Remove(profile);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(profile.Username))
|
||||
if (!string.IsNullOrWhiteSpace(profile.Username))
|
||||
{
|
||||
var logoutMessage = new MessageConfig
|
||||
{
|
||||
|
|
@ -104,14 +93,17 @@
|
|||
MessageService.Loading(logoutMessage);
|
||||
|
||||
await SteamCmdService.LogoutAsync(profile.Username);
|
||||
await SteamCmdService.DeleteProfileAsync(profile.Username);
|
||||
|
||||
logoutMessage.Content = "Successfully logged out from Steam!";
|
||||
logoutMessage.Duration = 3;
|
||||
|
||||
_profiles = (await SteamCmdService.GetProfilesAsync()).ToList();
|
||||
|
||||
MessageService.Success(logoutMessage);
|
||||
}
|
||||
|
||||
if (!Settings.Value.Server.SteamCMD.Profiles.Any())
|
||||
if (!_profiles.Any())
|
||||
AddProfile();
|
||||
}
|
||||
|
||||
|
|
@ -119,12 +111,10 @@
|
|||
{
|
||||
try
|
||||
{
|
||||
Settings.Value.Server.SteamCMD.Profiles = Settings.Value.Server.SteamCMD.Profiles.Where(p => !String.IsNullOrWhiteSpace(p.Username)).ToList();
|
||||
|
||||
SettingsProvider.Update(s =>
|
||||
foreach (var profile in _profiles)
|
||||
{
|
||||
s.Server.SteamCMD = Settings.Value.Server.SteamCMD;
|
||||
});
|
||||
await SteamCmdService.SaveProfileAsync(profile);
|
||||
}
|
||||
|
||||
MessageService.Success("Settings saved!");
|
||||
}
|
||||
|
|
|
|||
32
LANCommander.Steam/Abstractions/ISteamCmdProfileStore.cs
Normal file
32
LANCommander.Steam/Abstractions/ISteamCmdProfileStore.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Models;
|
||||
|
||||
namespace LANCommander.Steam.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for storing and retrieving SteamCMD profiles
|
||||
/// Allows consumers to implement their own storage mechanism
|
||||
/// </summary>
|
||||
public interface ISteamCmdProfileStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all profiles
|
||||
/// </summary>
|
||||
Task<IEnumerable<SteamCmdProfile>> GetAllAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Get a profile by username
|
||||
/// </summary>
|
||||
Task<SteamCmdProfile?> GetByUsernameAsync(string username);
|
||||
|
||||
/// <summary>
|
||||
/// Save or update a profile
|
||||
/// </summary>
|
||||
Task SaveAsync(SteamCmdProfile profile);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a profile by username
|
||||
/// </summary>
|
||||
Task DeleteAsync(string username);
|
||||
}
|
||||
95
LANCommander.Steam/Abstractions/ISteamCmdService.cs
Normal file
95
LANCommander.Steam/Abstractions/ISteamCmdService.cs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Enums;
|
||||
using LANCommander.Steam.Events;
|
||||
using LANCommander.Steam.Models;
|
||||
|
||||
namespace LANCommander.Steam.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for SteamCMD service operations
|
||||
/// </summary>
|
||||
public interface ISteamCmdService
|
||||
{
|
||||
/// <summary>
|
||||
/// Get or set the SteamCMD executable path
|
||||
/// </summary>
|
||||
string? ExecutablePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when an install job status changes (started, completed, failed)
|
||||
/// </summary>
|
||||
event EventHandler<SteamCmdInstallStatusEventArgs>? InstallStatusChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when install progress is updated
|
||||
/// </summary>
|
||||
event EventHandler<SteamCmdInstallProgressEventArgs>? InstallProgress;
|
||||
|
||||
/// <summary>
|
||||
/// Check the connection status for a username
|
||||
/// </summary>
|
||||
Task<SteamCmdConnectionStatus> GetConnectionStatusAsync(string username);
|
||||
|
||||
/// <summary>
|
||||
/// Auto-detect the SteamCMD executable path
|
||||
/// </summary>
|
||||
Task<string> AutoDetectSteamCmdPathAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Login to Steam with username and optional password
|
||||
/// </summary>
|
||||
Task<SteamCmdStatus> LoginToSteamAsync(string username, string? password = null);
|
||||
|
||||
/// <summary>
|
||||
/// Logout from Steam
|
||||
/// </summary>
|
||||
Task<SteamCmdStatus> LogoutAsync(string username);
|
||||
|
||||
/// <summary>
|
||||
/// Queue an installation job for Steam content
|
||||
/// Returns a job ID that can be used to track progress
|
||||
/// </summary>
|
||||
Task<SteamCmdInstallJob> InstallContentAsync(uint appId, string installDirectory, string? username = null);
|
||||
|
||||
/// <summary>
|
||||
/// Get an install job by ID
|
||||
/// </summary>
|
||||
SteamCmdInstallJob? GetInstallJob(Guid jobId);
|
||||
|
||||
/// <summary>
|
||||
/// Get all install jobs
|
||||
/// </summary>
|
||||
IEnumerable<SteamCmdInstallJob> GetInstallJobs();
|
||||
|
||||
/// <summary>
|
||||
/// Cancel an install job
|
||||
/// </summary>
|
||||
Task<bool> CancelInstallJobAsync(Guid jobId);
|
||||
|
||||
/// <summary>
|
||||
/// Remove installed content from a directory
|
||||
/// </summary>
|
||||
Task<SteamCmdStatus> RemoveContentAsync(string installDirectory);
|
||||
|
||||
/// <summary>
|
||||
/// Get all profiles (requires profile store to be configured)
|
||||
/// </summary>
|
||||
Task<IEnumerable<SteamCmdProfile>> GetProfilesAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Get a profile by username (requires profile store to be configured)
|
||||
/// </summary>
|
||||
Task<SteamCmdProfile?> GetProfileAsync(string username);
|
||||
|
||||
/// <summary>
|
||||
/// Save a profile (requires profile store to be configured)
|
||||
/// </summary>
|
||||
Task SaveProfileAsync(SteamCmdProfile profile);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a profile by username (requires profile store to be configured)
|
||||
/// </summary>
|
||||
Task DeleteProfileAsync(string username);
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace LANCommander.Server.Services.Enums;
|
||||
namespace LANCommander.Steam.Enums;
|
||||
|
||||
public enum SteamCmdConnectionStatus
|
||||
{
|
||||
32
LANCommander.Steam/Enums/SteamCmdInstallStatus.cs
Normal file
32
LANCommander.Steam/Enums/SteamCmdInstallStatus.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
namespace LANCommander.Steam.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Status of an installation job
|
||||
/// </summary>
|
||||
public enum SteamCmdInstallStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Job is queued and waiting to start
|
||||
/// </summary>
|
||||
Queued,
|
||||
|
||||
/// <summary>
|
||||
/// Job is currently being processed
|
||||
/// </summary>
|
||||
InProgress,
|
||||
|
||||
/// <summary>
|
||||
/// Job completed successfully
|
||||
/// </summary>
|
||||
Completed,
|
||||
|
||||
/// <summary>
|
||||
/// Job failed
|
||||
/// </summary>
|
||||
Failed,
|
||||
|
||||
/// <summary>
|
||||
/// Job was cancelled
|
||||
/// </summary>
|
||||
Cancelled
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace LANCommander.Server.Services.Enums;
|
||||
namespace LANCommander.Steam.Enums;
|
||||
|
||||
public enum SteamCmdStatus
|
||||
{
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using LANCommander.Steam.Models;
|
||||
|
||||
namespace LANCommander.Steam.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for install progress updates
|
||||
/// </summary>
|
||||
public class SteamCmdInstallProgressEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The install job this progress update is for
|
||||
/// </summary>
|
||||
public SteamCmdInstallJob Job { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Progress percentage (0-100)
|
||||
/// </summary>
|
||||
public double Progress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Status message
|
||||
/// </summary>
|
||||
public string StatusMessage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Bytes downloaded
|
||||
/// </summary>
|
||||
public long BytesDownloaded { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Total bytes to download
|
||||
/// </summary>
|
||||
public long BytesTotal { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Download speed in bytes per second
|
||||
/// </summary>
|
||||
public long BytesPerSecond { get; }
|
||||
|
||||
public SteamCmdInstallProgressEventArgs(
|
||||
SteamCmdInstallJob job,
|
||||
double progress,
|
||||
string statusMessage,
|
||||
long bytesDownloaded = 0,
|
||||
long bytesTotal = 0,
|
||||
long bytesPerSecond = 0)
|
||||
{
|
||||
Job = job;
|
||||
Progress = progress;
|
||||
StatusMessage = statusMessage;
|
||||
BytesDownloaded = bytesDownloaded;
|
||||
BytesTotal = bytesTotal;
|
||||
BytesPerSecond = bytesPerSecond;
|
||||
}
|
||||
}
|
||||
33
LANCommander.Steam/Events/SteamCmdInstallStatusEventArgs.cs
Normal file
33
LANCommander.Steam/Events/SteamCmdInstallStatusEventArgs.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using LANCommander.Steam.Enums;
|
||||
using LANCommander.Steam.Models;
|
||||
|
||||
namespace LANCommander.Steam.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for install status changes (started, completed, failed)
|
||||
/// </summary>
|
||||
public class SteamCmdInstallStatusEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The install job this status change is for
|
||||
/// </summary>
|
||||
public SteamCmdInstallJob Job { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The new status
|
||||
/// </summary>
|
||||
public SteamCmdInstallStatus Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message if the status is Failed
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; }
|
||||
|
||||
public SteamCmdInstallStatusEventArgs(SteamCmdInstallJob job, SteamCmdInstallStatus status, string? errorMessage = null)
|
||||
{
|
||||
Job = job;
|
||||
Status = status;
|
||||
ErrorMessage = errorMessage;
|
||||
}
|
||||
}
|
||||
81
LANCommander.Steam/Extensions/ServiceCollectionExtensions.cs
Normal file
81
LANCommander.Steam/Extensions/ServiceCollectionExtensions.cs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Implementations;
|
||||
using LANCommander.Steam.Options;
|
||||
using LANCommander.Steam.Services;
|
||||
|
||||
namespace LANCommander.Steam.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for registering SteamCMD services
|
||||
/// </summary>
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Add SteamCMD service with default in-memory profile store
|
||||
/// </summary>
|
||||
public static IServiceCollection AddSteamCmd(this IServiceCollection services, Action<SteamCmdOptions>? configure = null)
|
||||
{
|
||||
if (configure != null)
|
||||
{
|
||||
services.Configure(configure);
|
||||
}
|
||||
else
|
||||
{
|
||||
services.Configure<SteamCmdOptions>(_ => { });
|
||||
}
|
||||
|
||||
services.AddSingleton<ISteamCmdProfileStore, InMemorySteamCmdProfileStore>();
|
||||
services.AddScoped<ISteamCmdService, SteamCmdService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add SteamCMD service with custom profile store implementation
|
||||
/// </summary>
|
||||
public static IServiceCollection AddSteamCmd<TProfileStore>(
|
||||
this IServiceCollection services,
|
||||
Action<SteamCmdOptions>? configure = null)
|
||||
where TProfileStore : class, ISteamCmdProfileStore
|
||||
{
|
||||
if (configure != null)
|
||||
{
|
||||
services.Configure(configure);
|
||||
}
|
||||
else
|
||||
{
|
||||
services.Configure<SteamCmdOptions>(_ => { });
|
||||
}
|
||||
|
||||
services.AddSingleton<ISteamCmdProfileStore, TProfileStore>();
|
||||
services.AddScoped<ISteamCmdService, SteamCmdService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add SteamCMD service with existing profile store instance
|
||||
/// </summary>
|
||||
public static IServiceCollection AddSteamCmd(
|
||||
this IServiceCollection services,
|
||||
ISteamCmdProfileStore profileStore,
|
||||
Action<SteamCmdOptions>? configure = null)
|
||||
{
|
||||
if (configure != null)
|
||||
{
|
||||
services.Configure(configure);
|
||||
}
|
||||
else
|
||||
{
|
||||
services.Configure<SteamCmdOptions>(_ => { });
|
||||
}
|
||||
|
||||
services.AddSingleton(profileStore);
|
||||
services.AddScoped<ISteamCmdService, SteamCmdService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Models;
|
||||
|
||||
namespace LANCommander.Steam.Implementations;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory implementation of ISteamCmdProfileStore
|
||||
/// Useful for testing or simple scenarios
|
||||
/// </summary>
|
||||
public class InMemorySteamCmdProfileStore : ISteamCmdProfileStore
|
||||
{
|
||||
private readonly Dictionary<string, SteamCmdProfile> _profiles = new();
|
||||
|
||||
public Task<IEnumerable<SteamCmdProfile>> GetAllAsync()
|
||||
{
|
||||
return Task.FromResult<IEnumerable<SteamCmdProfile>>(_profiles.Values.ToList());
|
||||
}
|
||||
|
||||
public Task<SteamCmdProfile?> GetByUsernameAsync(string username)
|
||||
{
|
||||
_profiles.TryGetValue(username, out var profile);
|
||||
return Task.FromResult(profile);
|
||||
}
|
||||
|
||||
public Task SaveAsync(SteamCmdProfile profile)
|
||||
{
|
||||
_profiles[profile.Username] = profile;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DeleteAsync(string username)
|
||||
{
|
||||
_profiles.Remove(username);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>latestmajor</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="SteamWebAPI2" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Extensions\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
86
LANCommander.Steam/Models/SteamCmdInstallJob.cs
Normal file
86
LANCommander.Steam/Models/SteamCmdInstallJob.cs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using LANCommander.Steam.Enums;
|
||||
|
||||
namespace LANCommander.Steam.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an installation job in the queue
|
||||
/// </summary>
|
||||
public class SteamCmdInstallJob
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for this install job
|
||||
/// </summary>
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
/// <summary>
|
||||
/// Steam App ID to install
|
||||
/// </summary>
|
||||
public uint AppId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Installation directory
|
||||
/// </summary>
|
||||
public string InstallDirectory { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Username for Steam login (null for anonymous)
|
||||
/// </summary>
|
||||
public string? Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current status of the installation
|
||||
/// </summary>
|
||||
public SteamCmdInstallStatus Status { get; set; } = SteamCmdInstallStatus.Queued;
|
||||
|
||||
/// <summary>
|
||||
/// Progress percentage (0-100)
|
||||
/// </summary>
|
||||
public double Progress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current status message
|
||||
/// </summary>
|
||||
public string StatusMessage { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Bytes downloaded
|
||||
/// </summary>
|
||||
public long BytesDownloaded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total bytes to download
|
||||
/// </summary>
|
||||
public long BytesTotal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Download speed in bytes per second
|
||||
/// </summary>
|
||||
public long BytesPerSecond { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the job was created
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// When the job started processing
|
||||
/// </summary>
|
||||
public DateTime? StartedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the job completed
|
||||
/// </summary>
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message if the job failed
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Task completion source for awaiting the job
|
||||
/// </summary>
|
||||
internal TaskCompletionSource<SteamCmdStatus>? CompletionSource { get; set; }
|
||||
}
|
||||
17
LANCommander.Steam/Models/SteamCmdProfile.cs
Normal file
17
LANCommander.Steam/Models/SteamCmdProfile.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace LANCommander.Steam.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a SteamCMD profile with username and install directory
|
||||
/// </summary>
|
||||
public class SteamCmdProfile
|
||||
{
|
||||
/// <summary>
|
||||
/// Steam username for this profile
|
||||
/// </summary>
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Default install directory for this profile
|
||||
/// </summary>
|
||||
public string InstallDirectory { get; set; } = string.Empty;
|
||||
}
|
||||
22
LANCommander.Steam/Options/SteamCmdOptions.cs
Normal file
22
LANCommander.Steam/Options/SteamCmdOptions.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
namespace LANCommander.Steam.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for SteamCMD service
|
||||
/// </summary>
|
||||
public class SteamCmdOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to the SteamCMD executable
|
||||
/// </summary>
|
||||
public string? ExecutablePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default install directory for Steam content
|
||||
/// </summary>
|
||||
public string? DefaultInstallDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to auto-detect the SteamCMD path if not configured
|
||||
/// </summary>
|
||||
public bool AutoDetectPath { get; set; } = true;
|
||||
}
|
||||
860
LANCommander.Steam/Services/SteamCmdService.cs
Normal file
860
LANCommander.Steam/Services/SteamCmdService.cs
Normal file
|
|
@ -0,0 +1,860 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using LANCommander.Steam.Abstractions;
|
||||
using LANCommander.Steam.Enums;
|
||||
using LANCommander.Steam.Events;
|
||||
using LANCommander.Steam.Models;
|
||||
using LANCommander.Steam.Options;
|
||||
|
||||
namespace LANCommander.Steam.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for interacting with SteamCMD
|
||||
/// </summary>
|
||||
public class SteamCmdService : ISteamCmdService
|
||||
{
|
||||
private readonly ILogger<SteamCmdService> _logger;
|
||||
private readonly ISteamCmdProfileStore? _profileStore;
|
||||
private readonly SteamCmdOptions _options;
|
||||
private readonly ConcurrentDictionary<Guid, SteamCmdInstallJob> _installJobs = new();
|
||||
private readonly SemaphoreSlim _queueSemaphore = new(1, 1);
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
||||
private Task? _queueProcessorTask;
|
||||
|
||||
private string? _executablePath;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when an install job status changes
|
||||
/// </summary>
|
||||
public event EventHandler<SteamCmdInstallStatusEventArgs>? InstallStatusChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when install progress is updated
|
||||
/// </summary>
|
||||
public event EventHandler<SteamCmdInstallProgressEventArgs>? InstallProgress;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the SteamCMD executable path
|
||||
/// </summary>
|
||||
public string? ExecutablePath
|
||||
{
|
||||
get => _executablePath ?? _options.ExecutablePath;
|
||||
set => _executablePath = value;
|
||||
}
|
||||
|
||||
public SteamCmdService(
|
||||
ILogger<SteamCmdService> logger,
|
||||
IOptions<SteamCmdOptions>? options = null,
|
||||
ISteamCmdProfileStore? profileStore = null)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_options = options?.Value ?? new SteamCmdOptions();
|
||||
_profileStore = profileStore;
|
||||
|
||||
// Start the queue processor
|
||||
_queueProcessorTask = Task.Run(ProcessInstallQueueAsync, _cancellationTokenSource.Token);
|
||||
}
|
||||
public async Task<SteamCmdConnectionStatus> GetConnectionStatusAsync(string username)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsValidUsername(username))
|
||||
throw new ArgumentException("Invalid username", nameof(username));
|
||||
|
||||
// Auto-populate SteamCMD path if not configured
|
||||
if (string.IsNullOrWhiteSpace(ExecutablePath))
|
||||
{
|
||||
if (_options.AutoDetectPath)
|
||||
{
|
||||
var detectedPath = await AutoDetectSteamCmdPathAsync();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(detectedPath))
|
||||
{
|
||||
ExecutablePath = detectedPath;
|
||||
_logger.LogInformation("Auto-detected SteamCMD at: {Path}", detectedPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("SteamCMD path is not configured and could not be auto-detected");
|
||||
return SteamCmdConnectionStatus.NotInstalled;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("SteamCMD path is not configured and auto-detection is disabled");
|
||||
return SteamCmdConnectionStatus.NotInstalled;
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(ExecutablePath))
|
||||
{
|
||||
_logger.LogWarning("SteamCMD executable not found at configured path: {Path}", ExecutablePath);
|
||||
return SteamCmdConnectionStatus.NotInstalled;
|
||||
}
|
||||
|
||||
// Try to run steamcmd with +quit to check if it's working
|
||||
var result = await ExecuteSteamCmdCommandAsync("+quit");
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
// Check if we're logged in by trying to get user info
|
||||
var loginResult = await ExecuteSteamCmdCommandAsync($"+login {username} +quit", TimeSpan.FromSeconds(30));
|
||||
return loginResult.Success ? SteamCmdConnectionStatus.Authenticated : SteamCmdConnectionStatus.Unauthenticated;
|
||||
}
|
||||
|
||||
return SteamCmdConnectionStatus.NotInstalled;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error checking SteamCMD connection status");
|
||||
return SteamCmdConnectionStatus.NotInstalled;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> AutoDetectSteamCmdPathAsync()
|
||||
{
|
||||
var possiblePaths = new List<string>();
|
||||
|
||||
// Windows paths
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// Common Steam installation directories
|
||||
var steamPaths = new[]
|
||||
{
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Steam", "steamcmd.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Steam", "steamcmd.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Steam", "steamcmd.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Steam", "steamcmd.exe"),
|
||||
"C:\\Steam\\steamcmd.exe",
|
||||
"C:\\Program Files\\Steam\\steamcmd.exe",
|
||||
"C:\\Program Files (x86)\\Steam\\steamcmd.exe"
|
||||
};
|
||||
|
||||
possiblePaths.AddRange(steamPaths);
|
||||
|
||||
// Check PATH environment variable
|
||||
var pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? [];
|
||||
|
||||
foreach (var dir in pathDirs)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(dir))
|
||||
{
|
||||
possiblePaths.Add(Path.Combine(dir, "steamcmd.exe"));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Linux paths
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
possiblePaths.AddRange(new[]
|
||||
{
|
||||
"/app/Data/Steam/steamcmd.sh",
|
||||
"/usr/local/bin/steamcmd",
|
||||
"/usr/bin/steamcmd",
|
||||
"/home/steam/steamcmd/steamcmd.sh",
|
||||
"/opt/steamcmd/steamcmd.sh",
|
||||
"/var/lib/steam/steamcmd/steamcmd.sh"
|
||||
});
|
||||
|
||||
// Check PATH environment variable
|
||||
var pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(':') ?? [];
|
||||
|
||||
foreach (var dir in pathDirs)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(dir))
|
||||
{
|
||||
possiblePaths.Add(Path.Combine(dir, "steamcmd"));
|
||||
}
|
||||
}
|
||||
}
|
||||
// macOS paths
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
possiblePaths.AddRange(new[]
|
||||
{
|
||||
"/usr/local/bin/steamcmd",
|
||||
"/opt/homebrew/bin/steamcmd",
|
||||
"/Applications/Steam.app/Contents/MacOS/steamcmd"
|
||||
});
|
||||
|
||||
// Check PATH environment variable
|
||||
var pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(':') ?? [];
|
||||
|
||||
foreach (var dir in pathDirs)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(dir))
|
||||
{
|
||||
possiblePaths.Add(Path.Combine(dir, "steamcmd"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check each possible path
|
||||
foreach (var path in possiblePaths)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Verify it's actually SteamCMD by checking if it responds to --version or +quit
|
||||
var testResult = await ExecuteSteamCmdCommandAsync("+quit", TimeSpan.FromSeconds(30), path);
|
||||
|
||||
if (testResult.Success)
|
||||
{
|
||||
_logger.LogDebug("Found SteamCMD at: {Path}", path);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Failed to verify SteamCMD at: {Path}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogWarning("SteamCMD not found in common installation locations");
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public async Task<SteamCmdStatus> LoginToSteamAsync(string username, string? password = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsValidUsername(username))
|
||||
throw new ArgumentException("Invalid username", nameof(username));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ExecutablePath))
|
||||
{
|
||||
_logger.LogError("SteamCMD path is not configured");
|
||||
return SteamCmdStatus.PathNotConfigured;
|
||||
}
|
||||
|
||||
if (!File.Exists(ExecutablePath))
|
||||
{
|
||||
_logger.LogError("SteamCMD executable not found at configured path: {Path}", ExecutablePath);
|
||||
return SteamCmdStatus.ExecutableNotFound;
|
||||
}
|
||||
|
||||
var loginCommand = string.IsNullOrWhiteSpace(password)
|
||||
? $"+login {username}"
|
||||
: $"+login {username} {password}";
|
||||
|
||||
var result = await ExecuteSteamCmdCommandAsync($"{loginCommand} +quit", TimeSpan.FromSeconds(30));
|
||||
|
||||
if (result.Output.Contains("Invalid Password"))
|
||||
return SteamCmdStatus.InvalidPassword;
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.LogInformation("Successfully logged into Steam as {Username}", username);
|
||||
return SteamCmdStatus.Success;
|
||||
}
|
||||
|
||||
_logger.LogError("Failed to log into Steam: {Error}", result.ErrorOutput);
|
||||
return SteamCmdStatus.Error;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error logging into Steam");
|
||||
return SteamCmdStatus.UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SteamCmdStatus> LogoutAsync(string username)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsValidUsername(username))
|
||||
return SteamCmdStatus.InvalidUsername;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ExecutablePath))
|
||||
{
|
||||
_logger.LogError("SteamCMD path is not configured");
|
||||
return SteamCmdStatus.PathNotConfigured;
|
||||
}
|
||||
|
||||
if (!File.Exists(ExecutablePath))
|
||||
{
|
||||
_logger.LogError("SteamCMD executable not found at configured path: {Path}", ExecutablePath);
|
||||
return SteamCmdStatus.ExecutableNotFound;
|
||||
}
|
||||
|
||||
var logoutCommand = $"+logout {username}";
|
||||
|
||||
var result = await ExecuteSteamCmdCommandAsync($"{logoutCommand} +quit");
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.LogInformation("Successfully logged out of the Steam account {Username}", username);
|
||||
return SteamCmdStatus.Success;
|
||||
}
|
||||
|
||||
_logger.LogError("Failed to log out of Steam: {Error}", result.ErrorOutput);
|
||||
return SteamCmdStatus.Error;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error logging out of Steam");
|
||||
return SteamCmdStatus.UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SteamCmdInstallJob> InstallContentAsync(uint appId, string installDirectory, string? username = null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(username) && !IsValidUsername(username))
|
||||
throw new ArgumentException("Invalid username", nameof(username));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ExecutablePath))
|
||||
{
|
||||
_logger.LogError("SteamCMD path is not configured");
|
||||
throw new InvalidOperationException("SteamCMD path is not configured");
|
||||
}
|
||||
|
||||
if (!File.Exists(ExecutablePath))
|
||||
{
|
||||
_logger.LogError("SteamCMD executable not found at configured path: {Path}", ExecutablePath);
|
||||
throw new FileNotFoundException("SteamCMD executable not found", ExecutablePath);
|
||||
}
|
||||
|
||||
// Create install job
|
||||
var job = new SteamCmdInstallJob
|
||||
{
|
||||
AppId = appId,
|
||||
InstallDirectory = installDirectory,
|
||||
Username = username,
|
||||
Status = SteamCmdInstallStatus.Queued,
|
||||
StatusMessage = "Queued for installation",
|
||||
CompletionSource = new TaskCompletionSource<SteamCmdStatus>()
|
||||
};
|
||||
|
||||
_installJobs[job.Id] = job;
|
||||
|
||||
_logger.LogInformation("Queued installation job {JobId} for app {AppId} to {InstallDirectory}", job.Id, appId, installDirectory);
|
||||
|
||||
// Queue processor will pick it up automatically
|
||||
return job;
|
||||
}
|
||||
|
||||
public SteamCmdInstallJob? GetInstallJob(Guid jobId)
|
||||
{
|
||||
_installJobs.TryGetValue(jobId, out var job);
|
||||
return job;
|
||||
}
|
||||
|
||||
public IEnumerable<SteamCmdInstallJob> GetInstallJobs()
|
||||
{
|
||||
return _installJobs.Values.ToList();
|
||||
}
|
||||
|
||||
public async Task<bool> CancelInstallJobAsync(Guid jobId)
|
||||
{
|
||||
if (!_installJobs.TryGetValue(jobId, out var job))
|
||||
return false;
|
||||
|
||||
if (job.Status == SteamCmdInstallStatus.Completed || job.Status == SteamCmdInstallStatus.Failed)
|
||||
return false;
|
||||
|
||||
job.Status = SteamCmdInstallStatus.Cancelled;
|
||||
job.StatusMessage = "Cancelled by user";
|
||||
job.CompletedAt = DateTime.UtcNow;
|
||||
job.CompletionSource?.TrySetCanceled();
|
||||
|
||||
OnInstallStatusChanged(job, SteamCmdInstallStatus.Cancelled);
|
||||
|
||||
_logger.LogInformation("Cancelled install job {JobId}", jobId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<SteamCmdStatus> RemoveContentAsync(string installDirectory)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(installDirectory))
|
||||
{
|
||||
_logger.LogError("Install directory is not specified");
|
||||
return SteamCmdStatus.InstallDirectoryNotFound;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(installDirectory))
|
||||
{
|
||||
_logger.LogWarning("Install directory does not exist: {InstallDirectory}", installDirectory);
|
||||
return SteamCmdStatus.Success; // Consider it already removed
|
||||
}
|
||||
|
||||
// Remove the directory and all its contents
|
||||
Directory.Delete(installDirectory, true);
|
||||
|
||||
_logger.LogInformation("Successfully removed content from {InstallDirectory}", installDirectory);
|
||||
return SteamCmdStatus.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error removing content from {InstallDirectory}", installDirectory);
|
||||
return SteamCmdStatus.UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SteamCmdProfile>> GetProfilesAsync()
|
||||
{
|
||||
if (_profileStore == null)
|
||||
{
|
||||
throw new InvalidOperationException("Profile store is not configured. Provide an ISteamCmdProfileStore implementation.");
|
||||
}
|
||||
|
||||
return await _profileStore.GetAllAsync();
|
||||
}
|
||||
|
||||
public async Task<SteamCmdProfile?> GetProfileAsync(string username)
|
||||
{
|
||||
if (_profileStore == null)
|
||||
{
|
||||
throw new InvalidOperationException("Profile store is not configured. Provide an ISteamCmdProfileStore implementation.");
|
||||
}
|
||||
|
||||
return await _profileStore.GetByUsernameAsync(username);
|
||||
}
|
||||
|
||||
public async Task SaveProfileAsync(SteamCmdProfile profile)
|
||||
{
|
||||
if (_profileStore == null)
|
||||
{
|
||||
throw new InvalidOperationException("Profile store is not configured. Provide an ISteamCmdProfileStore implementation.");
|
||||
}
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(profile));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(profile.Username))
|
||||
{
|
||||
throw new ArgumentException("Profile username cannot be empty", nameof(profile));
|
||||
}
|
||||
|
||||
await _profileStore.SaveAsync(profile);
|
||||
_logger.LogInformation("Saved profile for username: {Username}", profile.Username);
|
||||
}
|
||||
|
||||
public async Task DeleteProfileAsync(string username)
|
||||
{
|
||||
if (_profileStore == null)
|
||||
{
|
||||
throw new InvalidOperationException("Profile store is not configured. Provide an ISteamCmdProfileStore implementation.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
throw new ArgumentException("Username cannot be empty", nameof(username));
|
||||
}
|
||||
|
||||
await _profileStore.DeleteAsync(username);
|
||||
_logger.LogInformation("Deleted profile for username: {Username}", username);
|
||||
}
|
||||
|
||||
private async Task ProcessInstallQueueAsync()
|
||||
{
|
||||
while (!_cancellationTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var queuedJob = _installJobs.Values
|
||||
.FirstOrDefault(j => j.Status == SteamCmdInstallStatus.Queued);
|
||||
|
||||
if (queuedJob == null)
|
||||
{
|
||||
await Task.Delay(1000, _cancellationTokenSource.Token);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process the job (semaphore is acquired inside ProcessInstallJobAsync)
|
||||
await ProcessInstallJobAsync(queuedJob);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Service is being disposed
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing install queue");
|
||||
await Task.Delay(5000, _cancellationTokenSource.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessInstallJobAsync(SteamCmdInstallJob job)
|
||||
{
|
||||
await _queueSemaphore.WaitAsync(_cancellationTokenSource.Token);
|
||||
|
||||
try
|
||||
{
|
||||
job.Status = SteamCmdInstallStatus.InProgress;
|
||||
job.StartedAt = DateTime.UtcNow;
|
||||
job.StatusMessage = "Starting installation...";
|
||||
OnInstallStatusChanged(job, SteamCmdInstallStatus.InProgress);
|
||||
|
||||
// Ensure install directory exists
|
||||
Directory.CreateDirectory(job.InstallDirectory);
|
||||
|
||||
var commands = new List<string>();
|
||||
|
||||
// Add login command if credentials provided
|
||||
if (!string.IsNullOrWhiteSpace(job.Username))
|
||||
{
|
||||
commands.Add($"+login {job.Username}");
|
||||
}
|
||||
else
|
||||
{
|
||||
commands.Add("+login anonymous");
|
||||
}
|
||||
|
||||
// Add install commands
|
||||
commands.Add($"+force_install_dir \"{job.InstallDirectory}\"");
|
||||
commands.Add($"+app_update {job.AppId} validate");
|
||||
commands.Add("+quit");
|
||||
|
||||
var commandString = string.Join(" ", commands);
|
||||
|
||||
var result = await ExecuteSteamCmdCommandWithProgressAsync(
|
||||
commandString,
|
||||
job,
|
||||
_cancellationTokenSource.Token);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
job.Status = SteamCmdInstallStatus.Completed;
|
||||
job.StatusMessage = "Installation completed successfully";
|
||||
job.Progress = 100;
|
||||
job.CompletedAt = DateTime.UtcNow;
|
||||
job.CompletionSource?.TrySetResult(SteamCmdStatus.Success);
|
||||
|
||||
OnInstallStatusChanged(job, SteamCmdInstallStatus.Completed);
|
||||
OnInstallProgress(job, 100, "Installation completed successfully");
|
||||
|
||||
_logger.LogInformation("Successfully installed Steam app {AppId} to {InstallDirectory}", job.AppId, job.InstallDirectory);
|
||||
}
|
||||
else
|
||||
{
|
||||
job.Status = SteamCmdInstallStatus.Failed;
|
||||
job.StatusMessage = $"Installation failed: {result.ErrorOutput}";
|
||||
job.ErrorMessage = result.ErrorOutput;
|
||||
job.CompletedAt = DateTime.UtcNow;
|
||||
job.CompletionSource?.TrySetResult(SteamCmdStatus.Error);
|
||||
|
||||
OnInstallStatusChanged(job, SteamCmdInstallStatus.Failed, result.ErrorOutput);
|
||||
|
||||
_logger.LogError("Failed to install Steam app {AppId}: {Error}", job.AppId, result.ErrorOutput);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
job.Status = SteamCmdInstallStatus.Cancelled;
|
||||
job.StatusMessage = "Installation cancelled";
|
||||
job.CompletedAt = DateTime.UtcNow;
|
||||
job.CompletionSource?.TrySetCanceled();
|
||||
|
||||
OnInstallStatusChanged(job, SteamCmdInstallStatus.Cancelled);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
job.Status = SteamCmdInstallStatus.Failed;
|
||||
job.StatusMessage = $"Installation error: {ex.Message}";
|
||||
job.ErrorMessage = ex.Message;
|
||||
job.CompletedAt = DateTime.UtcNow;
|
||||
job.CompletionSource?.TrySetException(ex);
|
||||
|
||||
OnInstallStatusChanged(job, SteamCmdInstallStatus.Failed, ex.Message);
|
||||
|
||||
_logger.LogError(ex, "Error installing Steam content for app {AppId}", job.AppId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<SteamCmdResult> ExecuteSteamCmdCommandWithProgressAsync(
|
||||
string arguments,
|
||||
SteamCmdInstallJob? job = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ExecutablePath))
|
||||
{
|
||||
throw new InvalidOperationException("SteamCMD executable path is not configured");
|
||||
}
|
||||
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = ExecutablePath,
|
||||
Arguments = arguments,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
|
||||
using var process = new Process { StartInfo = processStartInfo };
|
||||
var output = new StringBuilder();
|
||||
var error = new StringBuilder();
|
||||
|
||||
process.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
output.AppendLine(e.Data);
|
||||
_logger.LogDebug("SteamCMD Output: {Output}", e.Data);
|
||||
|
||||
// Parse progress if job is provided
|
||||
if (job != null)
|
||||
{
|
||||
ParseProgressFromOutput(e.Data, job);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
error.AppendLine(e.Data);
|
||||
_logger.LogDebug("SteamCMD Error: {Error}", e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
try
|
||||
{
|
||||
await process.WaitForExitAsync(cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogWarning("SteamCMD operation was cancelled");
|
||||
process.Kill();
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while executing SteamCMD");
|
||||
}
|
||||
|
||||
return new SteamCmdResult
|
||||
{
|
||||
Success = process.ExitCode == 0,
|
||||
ExitCode = process.ExitCode,
|
||||
Output = output.ToString(),
|
||||
ErrorOutput = error.ToString()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error executing SteamCMD command: {Arguments}", arguments);
|
||||
|
||||
return new SteamCmdResult
|
||||
{
|
||||
Success = false,
|
||||
ExitCode = -1,
|
||||
ErrorOutput = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseProgressFromOutput(string line, SteamCmdInstallJob job)
|
||||
{
|
||||
// SteamCMD progress patterns:
|
||||
// "Downloading update (X of Y MB)"
|
||||
// "Update state (X% at Y MB/s) : downloading update"
|
||||
// "Success. App 'X' fully installed."
|
||||
// "Installing update..."
|
||||
|
||||
var progressMatch = Regex.Match(line, @"Update state \(([\d.]+)%", RegexOptions.IgnoreCase);
|
||||
if (progressMatch.Success && double.TryParse(progressMatch.Groups[1].Value, out var progress))
|
||||
{
|
||||
job.Progress = Math.Min(100, Math.Max(0, progress));
|
||||
|
||||
// Try to extract download speed
|
||||
var speedMatch = Regex.Match(line, @"at ([\d.]+) (MB|KB)/s", RegexOptions.IgnoreCase);
|
||||
if (speedMatch.Success && double.TryParse(speedMatch.Groups[1].Value, out var speed))
|
||||
{
|
||||
var unit = speedMatch.Groups[2].Value.ToUpper();
|
||||
job.BytesPerSecond = (long)(speed * (unit == "MB" ? 1048576 : 1024));
|
||||
}
|
||||
|
||||
OnInstallProgress(job, job.Progress, line, job.BytesDownloaded, job.BytesTotal, job.BytesPerSecond);
|
||||
}
|
||||
|
||||
// Extract download progress (X of Y MB)
|
||||
var downloadMatch = Regex.Match(line, @"Downloading update \(([\d.]+) of ([\d.]+) (MB|KB)\)", RegexOptions.IgnoreCase);
|
||||
if (downloadMatch.Success)
|
||||
{
|
||||
if (double.TryParse(downloadMatch.Groups[1].Value, out var downloaded) &&
|
||||
double.TryParse(downloadMatch.Groups[2].Value, out var total))
|
||||
{
|
||||
var unit = downloadMatch.Groups[3].Value.ToUpper();
|
||||
var multiplier = unit == "MB" ? 1048576 : 1024;
|
||||
|
||||
job.BytesDownloaded = (long)(downloaded * multiplier);
|
||||
job.BytesTotal = (long)(total * multiplier);
|
||||
|
||||
if (job.BytesTotal > 0)
|
||||
{
|
||||
job.Progress = (job.BytesDownloaded * 100.0) / job.BytesTotal;
|
||||
}
|
||||
|
||||
OnInstallProgress(job, job.Progress, line, job.BytesDownloaded, job.BytesTotal, job.BytesPerSecond);
|
||||
}
|
||||
}
|
||||
|
||||
// Update status message
|
||||
if (!string.IsNullOrWhiteSpace(line) && !line.Contains("Update state"))
|
||||
{
|
||||
job.StatusMessage = line.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInstallStatusChanged(SteamCmdInstallJob job, SteamCmdInstallStatus status, string? errorMessage = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
InstallStatusChanged?.Invoke(this, new SteamCmdInstallStatusEventArgs(job, status, errorMessage));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error invoking InstallStatusChanged event");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInstallProgress(SteamCmdInstallJob job, double progress, string statusMessage, long bytesDownloaded = 0, long bytesTotal = 0, long bytesPerSecond = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
InstallProgress?.Invoke(this, new SteamCmdInstallProgressEventArgs(job, progress, statusMessage, bytesDownloaded, bytesTotal, bytesPerSecond));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error invoking InstallProgress event");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<SteamCmdResult> ExecuteSteamCmdCommandAsync(string arguments, TimeSpan? timeout = null, string? steamCmdPath = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var executablePath = string.IsNullOrWhiteSpace(steamCmdPath) ? ExecutablePath : steamCmdPath;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(executablePath))
|
||||
{
|
||||
throw new InvalidOperationException("SteamCMD executable path is not configured");
|
||||
}
|
||||
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = executablePath,
|
||||
Arguments = arguments,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
|
||||
using var process = new Process { StartInfo = processStartInfo };
|
||||
var output = new StringBuilder();
|
||||
var error = new StringBuilder();
|
||||
|
||||
process.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
output.AppendLine(e.Data);
|
||||
_logger.LogDebug("SteamCMD Output: {Output}", e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
error.AppendLine(e.Data);
|
||||
_logger.LogDebug("SteamCMD Error: {Error}", e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
try
|
||||
{
|
||||
if (timeout.HasValue)
|
||||
await process.WaitForExitAsync().WaitAsync(timeout.Value);
|
||||
else
|
||||
await process.WaitForExitAsync();
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
_logger.LogWarning("SteamCMD timed out");
|
||||
|
||||
process.Kill();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while executing SteamCMD");
|
||||
}
|
||||
|
||||
return new SteamCmdResult
|
||||
{
|
||||
Success = process.ExitCode == 0,
|
||||
ExitCode = process.ExitCode,
|
||||
Output = output.ToString(),
|
||||
ErrorOutput = error.ToString()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error executing SteamCMD command: {Arguments}", arguments);
|
||||
|
||||
return new SteamCmdResult
|
||||
{
|
||||
Success = false,
|
||||
ExitCode = -1,
|
||||
ErrorOutput = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidUsername(string value)
|
||||
{
|
||||
return !string.IsNullOrEmpty(value) &&
|
||||
Regex.IsMatch(value, @"^[a-zA-Z0-9_]+$");
|
||||
}
|
||||
|
||||
private class SteamCmdResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public int ExitCode { get; set; }
|
||||
public string Output { get; set; } = string.Empty;
|
||||
public string ErrorOutput { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
112
LANCommander.Steam/Services/SteamStoreService.cs
Normal file
112
LANCommander.Steam/Services/SteamStoreService.cs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Threading.Tasks;
|
||||
using HtmlAgilityPack;
|
||||
|
||||
namespace LANCommander.Steam.Services;
|
||||
|
||||
public class SteamStoreService
|
||||
{
|
||||
private readonly HttpClient HttpClient;
|
||||
|
||||
public SteamStoreService()
|
||||
{
|
||||
HttpClient = new HttpClient();
|
||||
HttpClient.BaseAddress = new Uri("https://store.steampowered.com");
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<GameSearchResult>> SearchGamesAsync(string keyword)
|
||||
{
|
||||
HtmlWeb web = new HtmlWeb();
|
||||
HtmlDocument dom = await web.LoadFromWebAsync($"https://store.steampowered.com/search/suggest?term={keyword}&f=games&cc=US");
|
||||
|
||||
var results = new List<GameSearchResult>();
|
||||
var matches = dom.DocumentNode.SelectNodes("//a[@data-ds-appid]");
|
||||
|
||||
if (matches == null || matches.Count == 0)
|
||||
return [];
|
||||
|
||||
foreach (var match in matches)
|
||||
{
|
||||
try
|
||||
{
|
||||
var appId = match.Attributes["data-ds-appid"].Value;
|
||||
var matchNameElement = match.SelectSingleNode(".//div[@class = 'match_name']");
|
||||
|
||||
appId = appId.Split(',').First();
|
||||
|
||||
if (matchNameElement != null)
|
||||
{
|
||||
results.Add(new GameSearchResult
|
||||
{
|
||||
Name = matchNameElement.InnerText,
|
||||
AppId = Convert.ToInt32(appId)
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { }
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public async Task<(bool Exists, string MimeType)> HasWebAssetAsync(int appId, WebAssetType webAssetType)
|
||||
{
|
||||
var webAssetUri = GetWebAssetUri(appId, webAssetType);
|
||||
var response = await HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, webAssetUri));
|
||||
|
||||
var exists = response.Content.Headers.ContentType.MediaType == MediaTypeNames.Image.Jpeg || response.Content.Headers.ContentType.MediaType == "image/png";
|
||||
|
||||
return (exists, response.Content.Headers.ContentType.MediaType);
|
||||
}
|
||||
|
||||
public async Task<bool> HasManualAsync(int appId)
|
||||
{
|
||||
var manualUri = GetManualUri(appId);
|
||||
var response = await HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, manualUri));
|
||||
|
||||
return response.Content.Headers.ContentType.MediaType == MediaTypeNames.Application.Pdf;
|
||||
}
|
||||
|
||||
public async Task<byte[]> DownloadManualAsync(int appId)
|
||||
{
|
||||
var manualUri = GetManualUri(appId);
|
||||
var response = await HttpClient.GetAsync(manualUri);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
await response.Content.CopyToAsync(ms);
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static Uri GetManualUri(int appId)
|
||||
{
|
||||
return new Uri($"https://store.steampowered.com/manual/{appId}");
|
||||
}
|
||||
|
||||
public static Uri GetWebAssetUri(int appId, WebAssetType type)
|
||||
{
|
||||
Dictionary<WebAssetType, string> webAssetTypeMap = new Dictionary<WebAssetType, string>()
|
||||
{
|
||||
{ WebAssetType.Capsule, "capsule_231x87.jpg" },
|
||||
{ WebAssetType.CapsuleLarge, "capsule_616x353.jpg" },
|
||||
{ WebAssetType.Header, "header.jpg" },
|
||||
{ WebAssetType.HeroCapsule, "hero_capsule.jpg" },
|
||||
{ WebAssetType.LibraryCover, "library_600x900.jpg" },
|
||||
{ WebAssetType.LibraryHeader, "library_header.jpg" },
|
||||
{ WebAssetType.LibraryHero, "library_hero.jpg" },
|
||||
{ WebAssetType.Logo, "logo.png" }
|
||||
};
|
||||
|
||||
return new Uri($"https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/{appId}/{webAssetTypeMap[type]}");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue