From ff6f9997f529ef608745f10619735e642b3c4016 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 9 Nov 2023 19:38:32 -0600 Subject: [PATCH 01/93] Added game ID to manifest --- LANCommander.SDK/Models/GameManifest.cs | 1 + LANCommander/Services/GameService.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/LANCommander.SDK/Models/GameManifest.cs b/LANCommander.SDK/Models/GameManifest.cs index 21920d6..260bc6b 100644 --- a/LANCommander.SDK/Models/GameManifest.cs +++ b/LANCommander.SDK/Models/GameManifest.cs @@ -6,6 +6,7 @@ namespace LANCommander.SDK { public class GameManifest { + public Guid Id { get; set; } public string Title { get; set; } public string SortTitle { get; set; } public string Description { get; set; } diff --git a/LANCommander/Services/GameService.cs b/LANCommander/Services/GameService.cs index 179ec5a..a45c333 100644 --- a/LANCommander/Services/GameService.cs +++ b/LANCommander/Services/GameService.cs @@ -44,6 +44,7 @@ namespace LANCommander.Services var manifest = new GameManifest() { + Id = game.Id, Title = game.Title, SortTitle = game.SortTitle, Description = game.Description, From a679fae0cb75e5812a9b3bc780972fb9040352ac Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 9 Nov 2023 19:40:38 -0600 Subject: [PATCH 02/93] Relocate crucial installation logic to SDK --- .../LANCommander.PlaynitePlugin.csproj | 5 - .../LANCommanderLibraryPlugin.cs | 30 +- .../Client.cs | 83 ++-- .../ExtractionResult.cs | 2 +- .../Helpers/RetryHelper.cs | 13 +- LANCommander.SDK/LANCommander.SDK.csproj | 7 + LANCommander.SDK/LANCommander.cs | 405 ++++++++++++++++++ LANCommander.SDK/Models/AuthToken.cs | 1 + LANCommander.SDK/Models/Game.cs | 1 + .../PowerShellRuntime.cs | 24 +- .../TrackableStream.cs | 4 +- 11 files changed, 497 insertions(+), 78 deletions(-) rename LANCommander.Playnite.Extension/LANCommanderClient.cs => LANCommander.SDK/Client.cs (77%) rename {LANCommander.Playnite.Extension => LANCommander.SDK}/ExtractionResult.cs (88%) rename {LANCommander.Playnite.Extension => LANCommander.SDK}/Helpers/RetryHelper.cs (64%) create mode 100644 LANCommander.SDK/LANCommander.cs rename {LANCommander.Playnite.Extension => LANCommander.SDK}/PowerShellRuntime.cs (86%) rename {LANCommander.Playnite.Extension => LANCommander.SDK}/TrackableStream.cs (99%) diff --git a/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj b/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj index 20a9688..fe6be55 100644 --- a/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj +++ b/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj @@ -99,15 +99,10 @@ - - - - - diff --git a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs index 3f80ec0..659eafa 100644 --- a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs +++ b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs @@ -21,7 +21,7 @@ namespace LANCommander.PlaynitePlugin { public static readonly ILogger Logger = LogManager.GetLogger(); internal LANCommanderSettingsViewModel Settings { get; set; } - internal LANCommanderClient LANCommander { get; set; } + internal LANCommander.SDK.LANCommander LANCommander { get; set; } internal PowerShellRuntime PowerShellRuntime { get; set; } internal GameSaveService GameSaveService { get; set; } @@ -39,16 +39,14 @@ namespace LANCommander.PlaynitePlugin Settings = new LANCommanderSettingsViewModel(this); - LANCommander = new LANCommanderClient(Settings.ServerAddress); - LANCommander.Token = new SDK.Models.AuthToken() + LANCommander = new SDK.LANCommander(Settings.ServerAddress); + LANCommander.Client.UseToken(new SDK.Models.AuthToken() { AccessToken = Settings.AccessToken, RefreshToken = Settings.RefreshToken, - }; + }); - PowerShellRuntime = new PowerShellRuntime(); - - GameSaveService = new GameSaveService(LANCommander, PlayniteApi, PowerShellRuntime); + // GameSaveService = new GameSaveService(LANCommander, PlayniteApi, PowerShellRuntime); api.UriHandler.RegisterSource("lancommander", args => { @@ -91,7 +89,7 @@ namespace LANCommander.PlaynitePlugin public bool ValidateConnection() { - return LANCommander.ValidateToken(LANCommander.Token); + return LANCommander.Client.ValidateToken(); } public override IEnumerable GetGames(LibraryGetGamesArgs args) @@ -111,7 +109,7 @@ namespace LANCommander.PlaynitePlugin } } - var games = LANCommander + var games = LANCommander.Client .GetGames() .Where(g => g != null && g.Archives != null && g.Archives.Count() > 0); @@ -121,7 +119,7 @@ namespace LANCommander.PlaynitePlugin { Logger.Trace($"Importing/updating metadata for game \"{game.Title}\"..."); - var manifest = LANCommander.GetGameManifest(game.Id); + var manifest = LANCommander.Client.GetGameManifest(game.Id); Logger.Trace("Successfully grabbed game manifest"); var existingGame = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == game.Id.ToString() && g.PluginId == Id && g.IsInstalled); @@ -183,13 +181,13 @@ namespace LANCommander.PlaynitePlugin metadata.Features.Add(new MetadataNameProperty($"Online Multiplayer {manifest.OnlineMultiplayer.GetPlayerCount()}".Trim())); if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Icon)) - metadata.Icon = new MetadataFile(LANCommander.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Icon))); + metadata.Icon = new MetadataFile(LANCommander.Client.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Icon))); if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Cover)) - metadata.CoverImage = new MetadataFile(LANCommander.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Cover))); + metadata.CoverImage = new MetadataFile(LANCommander.Client.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Cover))); if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Background)) - metadata.BackgroundImage = new MetadataFile(LANCommander.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background))); + metadata.BackgroundImage = new MetadataFile(LANCommander.Client.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background))); gameMetadata.Add(metadata); } @@ -244,7 +242,7 @@ namespace LANCommander.PlaynitePlugin if (result.Result == true) { PowerShellRuntime.RunScript(nameChangeArgs.Games.First(), SDK.Enums.ScriptType.NameChange, $@"""{result.SelectedString}"" ""{oldName}"""); - LANCommander.ChangeAlias(result.SelectedString); + LANCommander.Client.ChangeAlias(result.SelectedString); } } }; @@ -264,7 +262,7 @@ namespace LANCommander.PlaynitePlugin if (Guid.TryParse(keyChangeArgs.Games.First().GameId, out gameId)) { // NUKIEEEE - var newKey = LANCommander.GetNewKey(gameId); + var newKey = LANCommander.Client.GetNewKey(gameId); if (String.IsNullOrEmpty(newKey)) PlayniteApi.Dialogs.ShowErrorMessage("There are no more keys available on the server.", "No Keys Available"); @@ -402,7 +400,7 @@ namespace LANCommander.PlaynitePlugin var games = PlayniteApi.Database.Games.Where(g => g.IsInstalled).ToList(); - LANCommander.ChangeAlias(result.SelectedString); + LANCommander.Client.ChangeAlias(result.SelectedString); Logger.Trace($"Running name change scripts across {games.Count} installed game(s)"); diff --git a/LANCommander.Playnite.Extension/LANCommanderClient.cs b/LANCommander.SDK/Client.cs similarity index 77% rename from LANCommander.Playnite.Extension/LANCommanderClient.cs rename to LANCommander.SDK/Client.cs index e2cdfd1..958e8ca 100644 --- a/LANCommander.Playnite.Extension/LANCommanderClient.cs +++ b/LANCommander.SDK/Client.cs @@ -1,6 +1,6 @@ using LANCommander.SDK; using LANCommander.SDK.Models; -using Playnite.SDK; +using Microsoft.Extensions.Logging; using RestSharp; using System; using System.Collections.Generic; @@ -11,19 +11,19 @@ using System.Net; using System.Net.NetworkInformation; using System.Threading.Tasks; -namespace LANCommander.PlaynitePlugin +namespace LANCommander.SDK { - internal class LANCommanderClient + public class Client { - public static readonly ILogger Logger = LogManager.GetLogger(); + private static readonly ILogger Logger; - public readonly RestClient Client; - public AuthToken Token; + private readonly RestClient ApiClient; + private AuthToken Token; - public LANCommanderClient(string baseUrl) + public Client(string baseUrl) { if (!String.IsNullOrWhiteSpace(baseUrl)) - Client = new RestClient(baseUrl); + ApiClient = new RestClient(baseUrl); } private T PostRequest(string route, object body) @@ -32,7 +32,7 @@ namespace LANCommander.PlaynitePlugin .AddJsonBody(body) .AddHeader("Authorization", $"Bearer {Token.AccessToken}"); - var response = Client.Post(request); + var response = ApiClient.Post(request); return response.Data; } @@ -42,7 +42,7 @@ namespace LANCommander.PlaynitePlugin var request = new RestRequest(route) .AddHeader("Authorization", $"Bearer {Token.AccessToken}"); - var response = Client.Get(request); + var response = ApiClient.Get(request); return response.Data; } @@ -58,7 +58,7 @@ namespace LANCommander.PlaynitePlugin client.DownloadProgressChanged += (s, e) => progressHandler(e); client.DownloadFileCompleted += (s, e) => completeHandler(e); - client.DownloadFileAsync(new Uri($"{Client.BaseUrl}{route}"), tempFile); + client.DownloadFileAsync(new Uri($"{ApiClient.BaseUrl}{route}"), tempFile); return tempFile; } @@ -72,14 +72,14 @@ namespace LANCommander.PlaynitePlugin client.Headers.Add("Authorization", $"Bearer {Token.AccessToken}"); - var ws = client.OpenRead(new Uri($"{Client.BaseUrl}{route}")); + var ws = client.OpenRead(new Uri($"{ApiClient.BaseUrl}{route}")); return new TrackableStream(ws, true, Convert.ToInt64(client.ResponseHeaders["Content-Length"])); } - public async Task AuthenticateAsync(string username, string password) + public async Task AuthenticateAsync(string username, string password) { - var response = await Client.ExecuteAsync(new RestRequest("/api/Auth", Method.POST).AddJsonBody(new AuthRequest() + var response = await ApiClient.ExecuteAsync(new RestRequest("/api/Auth", Method.POST).AddJsonBody(new AuthRequest() { UserName = username, Password = password @@ -88,7 +88,14 @@ namespace LANCommander.PlaynitePlugin switch (response.StatusCode) { case HttpStatusCode.OK: - return response.Data; + Token = new AuthToken + { + AccessToken = response.Data.AccessToken, + RefreshToken = response.Data.RefreshToken, + Expiration = response.Data.Expiration + }; + + return Token; case HttpStatusCode.Forbidden: case HttpStatusCode.BadRequest: @@ -102,7 +109,7 @@ namespace LANCommander.PlaynitePlugin public async Task RegisterAsync(string username, string password) { - var response = await Client.ExecuteAsync(new RestRequest("/api/auth/register", Method.POST).AddJsonBody(new AuthRequest() + var response = await ApiClient.ExecuteAsync(new RestRequest("/api/auth/register", Method.POST).AddJsonBody(new AuthRequest() { UserName = username, Password = password @@ -125,19 +132,19 @@ namespace LANCommander.PlaynitePlugin public async Task PingAsync() { - var response = await Client.ExecuteAsync(new RestRequest("/api/Ping", Method.GET)); + var response = await ApiClient.ExecuteAsync(new RestRequest("/api/Ping", Method.GET)); return response.StatusCode == HttpStatusCode.OK; } public AuthResponse RefreshToken(AuthToken token) { - Logger.Trace("Refreshing token..."); + Logger.LogTrace("Refreshing token..."); var request = new RestRequest("/api/Auth/Refresh") .AddJsonBody(token); - var response = Client.Post(request); + var response = ApiClient.Post(request); if (response.StatusCode != HttpStatusCode.OK) throw new WebException(response.ErrorMessage); @@ -145,13 +152,18 @@ namespace LANCommander.PlaynitePlugin return response.Data; } + public bool ValidateToken() + { + return ValidateToken(Token); + } + public bool ValidateToken(AuthToken token) { - Logger.Trace("Validating token..."); + Logger.LogTrace("Validating token..."); if (token == null) { - Logger.Trace("Token is null!"); + Logger.LogTrace("Token is null!"); return false; } @@ -160,22 +172,27 @@ namespace LANCommander.PlaynitePlugin if (String.IsNullOrEmpty(token.AccessToken) || String.IsNullOrEmpty(token.RefreshToken)) { - Logger.Trace("Token is empty!"); + Logger.LogTrace("Token is empty!"); return false; } - var response = Client.Post(request); + var response = ApiClient.Post(request); var valid = response.StatusCode == HttpStatusCode.OK; if (valid) - Logger.Trace("Token is valid!"); + Logger.LogTrace("Token is valid!"); else - Logger.Trace("Token is invalid!"); + Logger.LogTrace("Token is invalid!"); return response.StatusCode == HttpStatusCode.OK; } + public void UseToken(AuthToken token) + { + Token = token; + } + public IEnumerable GetGames() { return GetRequest>("/api/Games"); @@ -223,26 +240,26 @@ namespace LANCommander.PlaynitePlugin public GameSave UploadSave(string gameId, byte[] data) { - Logger.Trace("Uploading save..."); + Logger.LogTrace("Uploading save..."); var request = new RestRequest($"/api/Saves/Upload/{gameId}", Method.POST) .AddHeader("Authorization", $"Bearer {Token.AccessToken}"); request.AddFile(gameId, data, gameId); - var response = Client.Post(request); + var response = ApiClient.Post(request); return response.Data; } public string GetMediaUrl(Media media) { - return (new Uri(Client.BaseUrl, $"/api/Media/{media.Id}/Download?fileId={media.FileId}").ToString()); + return (new Uri(ApiClient.BaseUrl, $"/api/Media/{media.Id}/Download?fileId={media.FileId}").ToString()); } public string GetKey(Guid id) { - Logger.Trace("Requesting key allocation..."); + Logger.LogTrace("Requesting key allocation..."); var macAddress = GetMacAddress(); @@ -261,7 +278,7 @@ namespace LANCommander.PlaynitePlugin public string GetAllocatedKey(Guid id) { - Logger.Trace("Requesting allocated key..."); + Logger.LogTrace("Requesting allocated key..."); var macAddress = GetMacAddress(); @@ -283,7 +300,7 @@ namespace LANCommander.PlaynitePlugin public string GetNewKey(Guid id) { - Logger.Trace("Requesting new key allocation..."); + Logger.LogTrace("Requesting new key allocation..."); var macAddress = GetMacAddress(); @@ -305,14 +322,14 @@ namespace LANCommander.PlaynitePlugin public User GetProfile() { - Logger.Trace("Requesting player's profile..."); + Logger.LogTrace("Requesting player's profile..."); return GetRequest("/api/Profile"); } public string ChangeAlias(string alias) { - Logger.Trace("Requesting to change player alias..."); + Logger.LogTrace("Requesting to change player alias..."); var response = PostRequest("/api/Profile/ChangeAlias", alias); diff --git a/LANCommander.Playnite.Extension/ExtractionResult.cs b/LANCommander.SDK/ExtractionResult.cs similarity index 88% rename from LANCommander.Playnite.Extension/ExtractionResult.cs rename to LANCommander.SDK/ExtractionResult.cs index ee4fb2c..ac9e1d8 100644 --- a/LANCommander.Playnite.Extension/ExtractionResult.cs +++ b/LANCommander.SDK/ExtractionResult.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace LANCommander.PlaynitePlugin +namespace LANCommander.SDK { internal class ExtractionResult { diff --git a/LANCommander.Playnite.Extension/Helpers/RetryHelper.cs b/LANCommander.SDK/Helpers/RetryHelper.cs similarity index 64% rename from LANCommander.Playnite.Extension/Helpers/RetryHelper.cs rename to LANCommander.SDK/Helpers/RetryHelper.cs index 898a1d8..94389ac 100644 --- a/LANCommander.Playnite.Extension/Helpers/RetryHelper.cs +++ b/LANCommander.SDK/Helpers/RetryHelper.cs @@ -1,15 +1,12 @@ -using Playnite.SDK; +using Microsoft.Extensions.Logging; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; -namespace LANCommander.PlaynitePlugin.Helpers +namespace LANCommander.SDK.Helpers { internal static class RetryHelper { - internal static readonly ILogger Logger = LogManager.GetLogger(); + internal static readonly ILogger Logger; internal static T RetryOnException(int maxAttempts, TimeSpan delay, T @default, Func action) { @@ -19,14 +16,14 @@ namespace LANCommander.PlaynitePlugin.Helpers { try { - Logger.Trace($"Attempt #{attempts + 1}/{maxAttempts}..."); + Logger.LogTrace($"Attempt #{attempts + 1}/{maxAttempts}..."); attempts++; return action(); } catch (Exception ex) { - Logger.Error(ex, $"Attempt failed!"); + Logger.LogError(ex, $"Attempt failed!"); if (attempts >= maxAttempts) return @default; diff --git a/LANCommander.SDK/LANCommander.SDK.csproj b/LANCommander.SDK/LANCommander.SDK.csproj index 9f5c4f4..11a21d2 100644 --- a/LANCommander.SDK/LANCommander.SDK.csproj +++ b/LANCommander.SDK/LANCommander.SDK.csproj @@ -4,4 +4,11 @@ netstandard2.0 + + + + + + + diff --git a/LANCommander.SDK/LANCommander.cs b/LANCommander.SDK/LANCommander.cs new file mode 100644 index 0000000..743c727 --- /dev/null +++ b/LANCommander.SDK/LANCommander.cs @@ -0,0 +1,405 @@ +using LANCommander.SDK.Enums; +using LANCommander.SDK.Extensions; +using LANCommander.SDK.Helpers; +using LANCommander.SDK.Models; +using Microsoft.Extensions.Logging; +using SharpCompress.Common; +using SharpCompress.Readers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using YamlDotNet.RepresentationModel; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace LANCommander.SDK +{ + public class ArchiveExtractionProgressArgs : EventArgs + { + public long Position { get; set; } + public long Length { get; set; } + } + + public class ArchiveEntryExtractionProgressArgs : EventArgs + { + public IReader Reader { get; set; } + public TrackableStream Stream { get; set; } + public ReaderProgress Progress { get; set; } + public IEntry Entry { get; set; } + } + + public class LANCommander + { + public static readonly ILogger Logger; + + private const string ManifestFilename = "_manifest.yml"; + + private string DefaultInstallDirectory { get; set; } + public Client Client { get; set; } + private PowerShellRuntime PowerShellRuntime; + + public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e); + public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress; + + public delegate void OnArchiveExtractionProgressHandler(long position, long length); + public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress; + + public LANCommander(string baseUrl) + { + Client = new Client(baseUrl); + } + + /// + /// Downloads, extracts, and runs post-install scripts for the specified game + /// + /// Game to install + /// Maximum attempts in case of transmission error + /// Final install path + /// + public string InstallGame(Guid gameId, int maxAttempts = 10) + { + var game = Client.GetGame(gameId); + + Logger.LogTrace("Installing game {GameTitle} (GameId)", game.Title, game.Id); + + var result = RetryHelper.RetryOnException(maxAttempts, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () => + { + Logger.LogTrace("Attempting to download and extract game"); + + return DownloadAndExtractGame(game); + }); + + if (!result.Success && !result.Canceled) + throw new Exception("Could not extract the installer. Retry the install or check your connection"); + else if (result.Canceled) + throw new Exception("Game install was canceled"); + + GameManifest manifest = null; + + game.InstallDirectory = result.Directory; + + var writeManifestSuccess = RetryHelper.RetryOnException(maxAttempts, TimeSpan.FromSeconds(1), false, () => + { + Logger.LogTrace("Attempting to get game manifest"); + + manifest = Client.GetGameManifest(game.Id); + + WriteManifest(manifest, game.InstallDirectory); + + return true; + }); + + if (!writeManifestSuccess) + throw new Exception("Could not grab the manifest file. Retry the install or check your connection"); + + Logger.LogTrace("Saving scripts"); + + SaveScript(game, ScriptType.Install); + SaveScript(game, ScriptType.Uninstall); + SaveScript(game, ScriptType.NameChange); + SaveScript(game, ScriptType.KeyChange); + + if (game.Redistributables != null && game.Redistributables.Count() > 0) + { + Logger.LogTrace("Installing required redistributables"); + InstallRedistributables(game); + } + + try + { + PowerShellRuntime.RunScript(game, ScriptType.Install); + PowerShellRuntime.RunScript(game, ScriptType.NameChange, /* Plugin.Settings.PlayerName */ ""); + + var key = Client.GetAllocatedKey(game.Id); + + PowerShellRuntime.RunScript(game, ScriptType.KeyChange, $"\"{key}\""); + } + catch (Exception ex) + { + Logger.LogError(ex, "Could not execute post-install scripts"); + } + + // Plugin.UpdateGame(manifest, gameId) + + // Plugin.DownloadCache.Remove(gameId); + + return result.Directory; + } + + private ExtractionResult DownloadAndExtractGame(Game game, string installDirectory = "") + { + if (game == null) + { + Logger.LogTrace("Game failed to download, no game was specified"); + + throw new ArgumentNullException("No game was specified"); + } + + if (String.IsNullOrWhiteSpace(installDirectory)) + installDirectory = DefaultInstallDirectory; + + var destination = Path.Combine(installDirectory, game.Title.SanitizeFilename()); + + Logger.LogTrace("Downloading and extracting {Game} to path {Destination}", game.Title, destination); + + try + { + Directory.CreateDirectory(destination); + + using (var gameStream = Client.StreamGame(game.Id)) + using (var reader = ReaderFactory.Open(gameStream)) + { + gameStream.OnProgress += (pos, len) => + { + OnArchiveExtractionProgress?.Invoke(pos, len); + }; + + reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs e) => + { + OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs + { + Entry = e.Item, + Progress = e.ReaderProgress, + Reader = reader, + Stream = gameStream + }); + }; + + reader.WriteAllToDirectory(destination, new ExtractionOptions() + { + ExtractFullPath = true, + Overwrite = true + }); + } + } + catch (Exception ex) + { + if (false) + { + + } + else + { + Logger.LogError(ex, "Could not extract to path {Destination}", destination); + + if (Directory.Exists(destination)) + { + Logger.LogTrace("Cleaning up orphaned install files after bad install"); + + Directory.Delete(destination, true); + } + + throw new Exception("The game archive could not be extracted, is it corrupted? Please try again"); + } + } + + var extractionResult = new ExtractionResult + { + Canceled = false, + }; + + if (!extractionResult.Canceled) + { + extractionResult.Success = true; + extractionResult.Directory = destination; + + Logger.LogTrace("Game {Game} successfully downloaded and extracted to {Destination}", game.Title, destination); + } + + return extractionResult; + } + + private void InstallRedistributables(Game game) + { + foreach (var redistributable in game.Redistributables) + { + InstallRedistributable(redistributable); + } + } + + private void InstallRedistributable(Redistributable redistributable) + { + string installScriptTempFile = null; + string detectionScriptTempFile = null; + string extractTempPath = null; + + try + { + var installScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.Install); + installScriptTempFile = SaveTempScript(installScript); + + var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall); + detectionScriptTempFile = SaveTempScript(detectionScript); + + var detectionResult = PowerShellRuntime.RunScript(detectionScriptTempFile, detectionScript.RequiresAdmin); + + // Redistributable is not installed + if (detectionResult == 0) + { + if (redistributable.Archives.Count() > 0) + { + var extractionResult = DownloadAndExtractRedistributable(redistributable); + + if (extractionResult.Success) + { + extractTempPath = extractionResult.Directory; + + PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath); + } + } + else + { + PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath); + } + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Redistributable {Redistributable} failed to install", redistributable.Name); + } + finally + { + if (File.Exists(installScriptTempFile)) + File.Delete(installScriptTempFile); + + if (File.Exists(detectionScriptTempFile)) + File.Delete(detectionScriptTempFile); + + if (Directory.Exists(extractTempPath)) + Directory.Delete(extractTempPath); + } + } + + private ExtractionResult DownloadAndExtractRedistributable(Redistributable redistributable) + { + if (redistributable == null) + { + Logger.LogTrace("Redistributable failed to download! No redistributable was specified"); + throw new ArgumentNullException("No redistributable was specified"); + } + + var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename()); + + Logger.LogTrace("Downloading and extracting {Redistributable} to path {Destination}", redistributable.Name, destination); + + try + { + Directory.CreateDirectory(destination); + + using (var redistributableStream = Client.StreamRedistributable(redistributable.Id)) + using (var reader = ReaderFactory.Open(redistributableStream)) + { + redistributableStream.OnProgress += (pos, len) => + { + OnArchiveExtractionProgress?.Invoke(pos, len); + }; + + reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs e) => + { + OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs + { + Entry = e.Item, + Progress = e.ReaderProgress, + Reader = reader, + Stream = redistributableStream + }); + }; + + reader.WriteAllToDirectory(destination, new ExtractionOptions() + { + ExtractFullPath = true, + Overwrite = true + }); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Could not extract to path {Destination}", destination); + + if (Directory.Exists(destination)) + { + Logger.LogTrace("Cleaning up orphaned files after bad install"); + + Directory.Delete(destination, true); + } + + throw new Exception("The redistributable archive could not be extracted, is it corrupted? Please try again"); + } + + var extractionResult = new ExtractionResult + { + Canceled = false + }; + + if (!extractionResult.Canceled) + { + extractionResult.Success = true; + extractionResult.Directory = destination; + Logger.LogTrace("Redistributable {Redistributable} successfully downloaded and extracted to {Destination}", redistributable.Name, destination); + } + + return extractionResult; + } + + public void WriteManifest(GameManifest manifest, string installDirectory) + { + var destination = Path.Combine(installDirectory, ManifestFilename); + + Logger.LogTrace("Attempting to write manifest to path {Destination}", destination); + + var serializer = new SerializerBuilder() + .WithNamingConvention(PascalCaseNamingConvention.Instance) + .Build(); + + Logger.LogTrace("Serializing manifest"); + + var yaml = serializer.Serialize(manifest); + + Logger.LogTrace("Writing manifest file"); + + File.WriteAllText(destination, yaml); + } + + private string SaveTempScript(Script script) + { + var tempPath = Path.GetTempFileName(); + + // PowerShell will only run scripts with the .ps1 file extension + File.Move(tempPath, tempPath + ".ps1"); + + Logger.LogTrace("Writing script {Script} to {Destination}", script.Name, tempPath); + + File.WriteAllText(tempPath, script.Contents); + + return tempPath; + } + + private void SaveScript(Game game, ScriptType type) + { + var script = game.Scripts.FirstOrDefault(s => s.Type == type); + + if (script == null) + return; + + if (script.RequiresAdmin) + script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents; + + var filename = PowerShellRuntime.GetScriptFilePath(game, type); + + if (File.Exists(filename)) + File.Delete(filename); + + Logger.LogTrace("Writing {ScriptType} script to {Destination}", type, filename); + + File.WriteAllText(filename, script.Contents); + } + + public void ChangeAlias(string alias) + { + + } + } +} diff --git a/LANCommander.SDK/Models/AuthToken.cs b/LANCommander.SDK/Models/AuthToken.cs index 4f5f97b..f8728e6 100644 --- a/LANCommander.SDK/Models/AuthToken.cs +++ b/LANCommander.SDK/Models/AuthToken.cs @@ -8,5 +8,6 @@ namespace LANCommander.SDK.Models { public string AccessToken { get; set; } public string RefreshToken { get; set; } + public DateTime Expiration { get; set; } } } diff --git a/LANCommander.SDK/Models/Game.cs b/LANCommander.SDK/Models/Game.cs index d13737b..befabf4 100644 --- a/LANCommander.SDK/Models/Game.cs +++ b/LANCommander.SDK/Models/Game.cs @@ -10,6 +10,7 @@ namespace LANCommander.SDK.Models public string DirectoryName { get; set; } public string Description { get; set; } public DateTime ReleasedOn { get; set; } + public string InstallDirectory { get; set; } public virtual IEnumerable Actions { get; set; } public virtual IEnumerable Tags { get; set; } public virtual Company Publisher { get; set; } diff --git a/LANCommander.Playnite.Extension/PowerShellRuntime.cs b/LANCommander.SDK/PowerShellRuntime.cs similarity index 86% rename from LANCommander.Playnite.Extension/PowerShellRuntime.cs rename to LANCommander.SDK/PowerShellRuntime.cs index 4b17858..fc8cf54 100644 --- a/LANCommander.Playnite.Extension/PowerShellRuntime.cs +++ b/LANCommander.SDK/PowerShellRuntime.cs @@ -1,22 +1,20 @@ using LANCommander.SDK.Enums; -using Playnite.SDK; -using Playnite.SDK.Models; +using LANCommander.SDK.Models; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Management.Automation; using System.Runtime.InteropServices; -using System.Security.RightsManagement; using System.Text; using System.Threading.Tasks; -namespace LANCommander.PlaynitePlugin +namespace LANCommander.SDK { internal class PowerShellRuntime { - public static readonly ILogger Logger = LogManager.GetLogger(); + public static readonly ILogger Logger; [DllImport("kernel32.dll", SetLastError = true)] static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr); @@ -26,11 +24,11 @@ namespace LANCommander.PlaynitePlugin public void RunCommand(string command, bool asAdmin = false) { - Logger.Trace($"Executing command `{command}` | Admin: {asAdmin}"); + Logger.LogTrace($"Executing command `{command}` | Admin: {asAdmin}"); var tempScript = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".ps1"); - Logger.Trace($"Creating temp script at path {tempScript}"); + Logger.LogTrace($"Creating temp script at path {tempScript}"); File.WriteAllText(tempScript, command); @@ -41,7 +39,7 @@ namespace LANCommander.PlaynitePlugin public int RunScript(string path, bool asAdmin = false, string arguments = null, string workingDirectory = null) { - Logger.Trace($"Executing script at path {path} | Admin: {asAdmin} | Arguments: {arguments}"); + Logger.LogTrace($"Executing script at path {path} | Admin: {asAdmin} | Arguments: {arguments}"); var wow64Value = IntPtr.Zero; @@ -95,7 +93,7 @@ namespace LANCommander.PlaynitePlugin // Concatenate scripts var sb = new StringBuilder(); - Logger.Trace("Concatenating scripts..."); + Logger.LogTrace("Concatenating scripts..."); foreach (var path in paths) { @@ -103,16 +101,16 @@ namespace LANCommander.PlaynitePlugin sb.AppendLine(contents); - Logger.Trace($"Added {path}!"); + Logger.LogTrace($"Added {path}!"); } - Logger.Trace("Done concatenating!"); + Logger.LogTrace("Done concatenating!"); if (sb.Length > 0) { var scriptPath = Path.GetTempFileName(); - Logger.Trace($"Creating temp script at path {scriptPath}"); + Logger.LogTrace($"Creating temp script at path {scriptPath}"); File.WriteAllText(scriptPath, sb.ToString()); diff --git a/LANCommander.Playnite.Extension/TrackableStream.cs b/LANCommander.SDK/TrackableStream.cs similarity index 99% rename from LANCommander.Playnite.Extension/TrackableStream.cs rename to LANCommander.SDK/TrackableStream.cs index cff27f8..38f8835 100644 --- a/LANCommander.Playnite.Extension/TrackableStream.cs +++ b/LANCommander.SDK/TrackableStream.cs @@ -1,9 +1,9 @@ using System; using System.IO; -namespace LANCommander.PlaynitePlugin +namespace LANCommander.SDK { - internal class TrackableStream : MemoryStream, IDisposable + public class TrackableStream : MemoryStream, IDisposable { public delegate void OnProgressDelegate(long Position, long Length); public event OnProgressDelegate OnProgress = delegate { }; From e53709334cf04ace912ceaf8b21b20b6e1b702fe Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 9 Nov 2023 23:45:37 -0600 Subject: [PATCH 03/93] Make PowerShellRuntime static --- LANCommander.SDK/PowerShellRuntime.cs | 30 ++++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/LANCommander.SDK/PowerShellRuntime.cs b/LANCommander.SDK/PowerShellRuntime.cs index fc8cf54..c9f4514 100644 --- a/LANCommander.SDK/PowerShellRuntime.cs +++ b/LANCommander.SDK/PowerShellRuntime.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace LANCommander.SDK { - internal class PowerShellRuntime + public static class PowerShellRuntime { public static readonly ILogger Logger; @@ -22,7 +22,7 @@ namespace LANCommander.SDK [DllImport("kernel32.dll", SetLastError = true)] static extern bool Wow64RevertWow64FsRedirection(ref IntPtr ptr); - public void RunCommand(string command, bool asAdmin = false) + public static void RunCommand(string command, bool asAdmin = false) { Logger.LogTrace($"Executing command `{command}` | Admin: {asAdmin}"); @@ -37,7 +37,7 @@ namespace LANCommander.SDK File.Delete(tempScript); } - public int RunScript(string path, bool asAdmin = false, string arguments = null, string workingDirectory = null) + public static int RunScript(string path, bool asAdmin = false, string arguments = null, string workingDirectory = null) { Logger.LogTrace($"Executing script at path {path} | Admin: {asAdmin} | Arguments: {arguments}"); @@ -73,9 +73,14 @@ namespace LANCommander.SDK return process.ExitCode; } - public void RunScript(Game game, ScriptType type, string arguments = null) + public static void RunScript(Game game, ScriptType type, string arguments = null) { - var path = GetScriptFilePath(game, type); + RunScript(game.InstallDirectory, type, arguments); + } + + public static void RunScript(string installDirectory, ScriptType type, string arguments = null) + { + var path = GetScriptFilePath(installDirectory, type); if (File.Exists(path)) { @@ -88,7 +93,7 @@ namespace LANCommander.SDK } } - public void RunScriptsAsAdmin(IEnumerable paths, string arguments = null) + public static void RunScriptsAsAdmin(IEnumerable paths, string arguments = null) { // Concatenate scripts var sb = new StringBuilder(); @@ -118,14 +123,14 @@ namespace LANCommander.SDK } } - public void RunScripts(IEnumerable games, ScriptType type, string arguments = null) + public static void RunScripts(IEnumerable installDirectories, ScriptType type, string arguments = null) { List scripts = new List(); List adminScripts = new List(); - foreach (var game in games) + foreach (var installDirectory in installDirectories) { - var path = GetScriptFilePath(game, type); + var path = GetScriptFilePath(installDirectory, type); if (!File.Exists(path)) continue; @@ -147,6 +152,11 @@ namespace LANCommander.SDK } public static string GetScriptFilePath(Game game, ScriptType type) + { + return GetScriptFilePath(game.InstallDirectory, type); + } + + public static string GetScriptFilePath(string installDirectory, ScriptType type) { Dictionary filenames = new Dictionary() { { ScriptType.Install, "_install.ps1" }, @@ -157,7 +167,7 @@ namespace LANCommander.SDK var filename = filenames[type]; - return Path.Combine(game.InstallDirectory, filename); + return Path.Combine(installDirectory, filename); } } } From 39f2d4b212f41b99317b684a9272b77522f9aa7b Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 10 Nov 2023 00:29:16 -0600 Subject: [PATCH 04/93] Move methods that should be static to ManifestHelper and ScriptHelper. Move install logic to GameManager and RedistributableManager. Update InstallController and UninstallController --- .../InstallController.cs | 465 ++---------------- .../LANCommander.PlaynitePlugin.csproj | 12 - .../LANCommanderLibraryPlugin.cs | 47 +- .../Services/GameSaveService.cs | 6 +- .../UninstallController.cs | 16 +- LANCommander.SDK/EventArgs.cs | 22 + LANCommander.SDK/GameManager.cs | 205 ++++++++ LANCommander.SDK/Helpers/ManifestHelper.cs | 52 ++ LANCommander.SDK/Helpers/ScriptHelper.cs | 50 ++ LANCommander.SDK/LANCommander.cs | 405 --------------- LANCommander.SDK/RedistributableManager.cs | 164 ++++++ 11 files changed, 553 insertions(+), 891 deletions(-) create mode 100644 LANCommander.SDK/EventArgs.cs create mode 100644 LANCommander.SDK/GameManager.cs create mode 100644 LANCommander.SDK/Helpers/ManifestHelper.cs create mode 100644 LANCommander.SDK/Helpers/ScriptHelper.cs delete mode 100644 LANCommander.SDK/LANCommander.cs create mode 100644 LANCommander.SDK/RedistributableManager.cs diff --git a/LANCommander.Playnite.Extension/InstallController.cs b/LANCommander.Playnite.Extension/InstallController.cs index 084b406..3ec33f7 100644 --- a/LANCommander.Playnite.Extension/InstallController.cs +++ b/LANCommander.Playnite.Extension/InstallController.cs @@ -1,17 +1,10 @@ -using LANCommander.PlaynitePlugin.Helpers; -using LANCommander.SDK.Enums; -using LANCommander.SDK.Extensions; +using LANCommander.SDK; +using LANCommander.SDK.Helpers; using LANCommander.SDK.Models; using Playnite.SDK; using Playnite.SDK.Models; using Playnite.SDK.Plugins; -using SharpCompress.Common; -using SharpCompress.Readers; using System; -using System.IO; -using System.Linq; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; namespace LANCommander.PlaynitePlugin { @@ -20,15 +13,11 @@ namespace LANCommander.PlaynitePlugin public static readonly ILogger Logger = LogManager.GetLogger(); private LANCommanderLibraryPlugin Plugin; - private PowerShellRuntime PowerShellRuntime; - private Playnite.SDK.Models.Game PlayniteGame; public LANCommanderInstallController(LANCommanderLibraryPlugin plugin, Playnite.SDK.Models.Game game) : base(game) { Name = "Install using LANCommander"; Plugin = plugin; - PlayniteGame = game; - PowerShellRuntime = new PowerShellRuntime(); } public override void Install(InstallActionArgs args) @@ -43,450 +32,52 @@ namespace LANCommander.PlaynitePlugin } var gameId = Guid.Parse(Game.GameId); - var game = Plugin.LANCommander.GetGame(gameId); - Logger.Trace($"Installing game {game.Title} ({game.Id})..."); + string installDirectory = null; - var result = RetryHelper.RetryOnException(10, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () => - { - Logger.Trace("Attempting to download and extract game..."); - return DownloadAndExtractGame(game); - }); - - if (!result.Success && !result.Canceled) - throw new Exception("Could not extract the install archive. Retry the install or check your connection."); - else if (result.Canceled) - throw new Exception("Install was canceled"); - - var installInfo = new GameInstallationData() - { - InstallDirectory = result.Directory - }; - - PlayniteGame.InstallDirectory = result.Directory; - - SDK.GameManifest manifest = null; - - var writeManifestSuccess = RetryHelper.RetryOnException(10, TimeSpan.FromSeconds(1), false, () => - { - Logger.Trace("Attempting to get game manifest..."); - - manifest = Plugin.LANCommander.GetGameManifest(gameId); - - WriteManifest(manifest, result.Directory); - - return true; - }); - - if (!writeManifestSuccess) - throw new Exception("Could not get or write the manifest file. Retry the install or check your connection."); - - Logger.Trace("Saving scripts..."); - - SaveScript(game, result.Directory, ScriptType.Install); - SaveScript(game, result.Directory, ScriptType.Uninstall); - SaveScript(game, result.Directory, ScriptType.NameChange); - SaveScript(game, result.Directory, ScriptType.KeyChange); - - if (game.Redistributables != null && game.Redistributables.Count() > 0) - { - Logger.Trace("Installing required redistributables..."); - InstallRedistributables(game); - } - - try - { - PowerShellRuntime.RunScript(PlayniteGame, ScriptType.Install); - PowerShellRuntime.RunScript(PlayniteGame, ScriptType.NameChange, Plugin.Settings.PlayerName); - - var key = Plugin.LANCommander.GetAllocatedKey(game.Id); - - PowerShellRuntime.RunScript(PlayniteGame, ScriptType.KeyChange, $"\"{key}\""); - } - catch { } - - Plugin.UpdateGame(manifest, gameId); - - Plugin.DownloadCache.Remove(gameId); - - InvokeOnInstalled(new GameInstalledEventArgs(installInfo)); - } - - private ExtractionResult DownloadAndExtractGame(LANCommander.SDK.Models.Game game) - { - if (game == null) - { - Logger.Trace("Game failed to download! No game was specified!"); - - throw new Exception("Game failed to download!"); - } - - var destination = Path.Combine(Plugin.Settings.InstallDirectory, game.Title.SanitizeFilename()); - - Logger.Trace($"Downloading and extracting \"{game.Title}\" to path {destination}"); var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress => { - try + var gameManager = new GameManager(Plugin.LANCommanderClient); + + gameManager.OnArchiveExtractionProgress += (long pos, long len) => { - Directory.CreateDirectory(destination); - progress.ProgressMaxValue = 100; - progress.CurrentProgressValue = 0; + progress.ProgressMaxValue = len; + progress.CurrentProgressValue = pos; + }; - using (var gameStream = Plugin.LANCommander.StreamGame(game.Id)) - using (var reader = ReaderFactory.Open(gameStream)) - { - progress.ProgressMaxValue = gameStream.Length; - - gameStream.OnProgress += (pos, len) => - { - progress.CurrentProgressValue = pos; - }; - - reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs e) => - { - if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested) - { - reader.Cancel(); - progress.IsIndeterminate = true; - - reader.Dispose(); - gameStream.Dispose(); - } - }; - - reader.WriteAllToDirectory(destination, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - catch (Exception ex) + gameManager.OnArchiveEntryExtractionProgress += (object sender, ArchiveEntryExtractionProgressArgs e) => { if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested) { - Logger.Trace("User cancelled the download"); + e.Reader.Cancel(); + e.Reader.Dispose(); + e.Stream.Dispose(); - if (Directory.Exists(destination)) - { - Logger.Trace("Cleaning up orphaned install files after cancelled install..."); - - Directory.Delete(destination, true); - } + progress.IsIndeterminate = true; } - else - { - Logger.Error(ex, $"Could not extract to path {destination}"); + }; - if (Directory.Exists(destination)) - { - Logger.Trace("Cleaning up orphaned install files after bad install..."); - - Directory.Delete(destination, true); - } - - throw new Exception("The game archive could not be extracted. Please try again or fix the archive!"); - } - } + installDirectory = gameManager.Install(gameId); }, - new GlobalProgressOptions($"Downloading {game.Title}...") + new GlobalProgressOptions($"Downloading {Game.Name}...") { - IsIndeterminate = false, + IsIndeterminate = true, Cancelable = true, }); - var extractionResult = new ExtractionResult + if (!result.Canceled && result.Error == null && !String.IsNullOrWhiteSpace(installDirectory)) { - Canceled = result.Canceled - }; + var manifest = ManifestHelper.Read(installDirectory); - if (!result.Canceled) - { - extractionResult.Success = true; - extractionResult.Directory = destination; - Logger.Trace($"Game successfully downloaded and extracted to {destination}"); + Plugin.UpdateGame(manifest); + + var installInfo = new GameInstallationData + { + InstallDirectory = installDirectory, + }; + + InvokeOnInstalled(new GameInstalledEventArgs(installInfo)); } - - return extractionResult; - } - - private void InstallRedistributables(LANCommander.SDK.Models.Game game) - { - foreach (var redistributable in game.Redistributables) - { - string installScriptTempFile = null; - string detectionScriptTempFile = null; - string extractTempPath = null; - - try - { - var installScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.Install); - installScriptTempFile = SaveTempScript(installScript); - - var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall); - detectionScriptTempFile = SaveTempScript(detectionScript); - - var detectionResult = PowerShellRuntime.RunScript(detectionScriptTempFile, detectionScript.RequiresAdmin); - - // Redistributable is not installed - if (detectionResult == 0) - { - if (redistributable.Archives.Count() > 0) - { - var extractionResult = DownloadAndExtractRedistributable(redistributable); - - if (extractionResult.Success) - { - extractTempPath = extractionResult.Directory; - - PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath); - } - } - else - { - PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath); - } - } - } - catch (Exception ex) - { - Logger.Error(ex, $"Redistributable {redistributable.Name} failed to install"); - } - finally - { - if (File.Exists(installScriptTempFile)) - File.Delete(installScriptTempFile); - - if (File.Exists(detectionScriptTempFile)) - File.Delete(detectionScriptTempFile); - - if (Directory.Exists(extractTempPath)) - Directory.Delete(extractTempPath); - } - } - } - - private ExtractionResult DownloadAndExtractRedistributable(LANCommander.SDK.Models.Redistributable redistributable) - { - if (redistributable == null) - { - Logger.Trace("Redistributable failed to download! No redistributable was specified!"); - - throw new Exception("Redistributable failed to download!"); - } - - var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename()); - - Logger.Trace($"Downloading and extracting \"{redistributable.Name}\" to path {destination}"); - var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress => - { - try - { - Directory.CreateDirectory(destination); - progress.ProgressMaxValue = 100; - progress.CurrentProgressValue = 0; - - using (var redistributableStream = Plugin.LANCommander.StreamRedistributable(redistributable.Id)) - using (var reader = ReaderFactory.Open(redistributableStream)) - { - progress.ProgressMaxValue = redistributableStream.Length; - - redistributableStream.OnProgress += (pos, len) => - { - progress.CurrentProgressValue = pos; - }; - - reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs e) => - { - if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested) - { - reader.Cancel(); - progress.IsIndeterminate = true; - - reader.Dispose(); - redistributableStream.Dispose(); - } - }; - - reader.WriteAllToDirectory(destination, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - catch (Exception ex) - { - if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested) - { - Logger.Trace("User cancelled the download"); - - if (Directory.Exists(destination)) - { - Logger.Trace("Cleaning up orphaned install files after cancelled install..."); - - Directory.Delete(destination, true); - } - } - else - { - Logger.Error(ex, $"Could not extract to path {destination}"); - - if (Directory.Exists(destination)) - { - Logger.Trace("Cleaning up orphaned install files after bad install..."); - - Directory.Delete(destination, true); - } - - throw new Exception("The redistributable archive could not be extracted. Please try again or fix the archive!"); - } - } - }, - new GlobalProgressOptions($"Downloading {redistributable.Name}...") - { - IsIndeterminate = false, - Cancelable = true, - }); - - var extractionResult = new ExtractionResult - { - Canceled = result.Canceled - }; - - if (!result.Canceled) - { - extractionResult.Success = true; - extractionResult.Directory = destination; - Logger.Trace($"Redistributable successfully downloaded and extracted to {destination}"); - } - - return extractionResult; - } - - private string Download(LANCommander.SDK.Models.Game game) - { - string tempFile = String.Empty; - - if (game != null) - { - Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress => - { - progress.ProgressMaxValue = 100; - progress.CurrentProgressValue = 0; - - var destination = Plugin.LANCommander.DownloadGame(game.Id, (changed) => - { - progress.CurrentProgressValue = changed.ProgressPercentage; - }, (complete) => - { - progress.CurrentProgressValue = 100; - }); - - // Lock the thread until download is done - while (progress.CurrentProgressValue != 100) - { - - } - - tempFile = destination; - }, - new GlobalProgressOptions($"Downloading {game.Title}...") - { - IsIndeterminate = false, - Cancelable = false, - }); - - return tempFile; - } - else - throw new Exception("Game failed to download!"); - } - - private string Extract(LANCommander.SDK.Models.Game game, string archivePath) - { - var destination = Path.Combine(Plugin.Settings.InstallDirectory, game.Title.SanitizeFilename()); - - Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress => - { - Directory.CreateDirectory(destination); - - using (var fs = File.OpenRead(archivePath)) - using (var ts = new TrackableStream(fs)) - using (var reader = ReaderFactory.Open(ts)) - { - progress.ProgressMaxValue = ts.Length; - ts.OnProgress += (pos, len) => - { - progress.CurrentProgressValue = pos; - }; - - reader.WriteAllToDirectory(destination, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - }, - new GlobalProgressOptions($"Extracting {game.Title}...") - { - IsIndeterminate = false, - Cancelable = false, - }); - - return destination; - } - - private void WriteManifest(SDK.GameManifest manifest, string installDirectory) - { - var destination = Path.Combine(installDirectory, "_manifest.yml"); - - Logger.Trace($"Attempting to write manifest to path {destination}"); - - var serializer = new SerializerBuilder() - .WithNamingConvention(new PascalCaseNamingConvention()) - .Build(); - - Logger.Trace("Serializing manifest..."); - var yaml = serializer.Serialize(manifest); - - Logger.Trace("Writing manifest file..."); - File.WriteAllText(destination, yaml); - } - - private string SaveTempScript(LANCommander.SDK.Models.Script script) - { - var tempPath = Path.GetTempFileName(); - - File.Move(tempPath, tempPath + ".ps1"); - - tempPath = tempPath + ".ps1"; - - Logger.Trace($"Writing script {script.Name} to {tempPath}"); - - File.WriteAllText(tempPath, script.Contents); - - return tempPath; - } - - private void SaveScript(LANCommander.SDK.Models.Game game, string installationDirectory, ScriptType type) - { - var script = game.Scripts.FirstOrDefault(s => s.Type == type); - - if (script == null) - return; - - if (script.RequiresAdmin) - script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents; - - var filename = PowerShellRuntime.GetScriptFilePath(PlayniteGame, type); - - if (File.Exists(filename)) - File.Delete(filename); - - Logger.Trace($"Writing {type} script to {filename}"); - - File.WriteAllText(filename, script.Contents); } } } diff --git a/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj b/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj index fe6be55..bbd6a13 100644 --- a/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj +++ b/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj @@ -42,12 +42,6 @@ - - ..\packages\RestSharp.106.15.0\lib\net452\RestSharp.dll - - - ..\packages\SharpCompress.0.34.1\lib\net462\SharpCompress.dll - ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll @@ -91,12 +85,6 @@ - - ..\packages\YamlDotNet.5.4.0\lib\net45\YamlDotNet.dll - - - ..\packages\ZstdSharp.Port.0.7.2\lib\net461\ZstdSharp.dll - diff --git a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs index 659eafa..fee02ea 100644 --- a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs +++ b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs @@ -21,8 +21,7 @@ namespace LANCommander.PlaynitePlugin { public static readonly ILogger Logger = LogManager.GetLogger(); internal LANCommanderSettingsViewModel Settings { get; set; } - internal LANCommander.SDK.LANCommander LANCommander { get; set; } - internal PowerShellRuntime PowerShellRuntime { get; set; } + internal LANCommander.SDK.Client LANCommanderClient { get; set; } internal GameSaveService GameSaveService { get; set; } public override Guid Id { get; } = Guid.Parse("48e1bac7-e0a0-45d7-ba83-36f5e9e959fc"); @@ -39,8 +38,8 @@ namespace LANCommander.PlaynitePlugin Settings = new LANCommanderSettingsViewModel(this); - LANCommander = new SDK.LANCommander(Settings.ServerAddress); - LANCommander.Client.UseToken(new SDK.Models.AuthToken() + LANCommanderClient = new SDK.Client(Settings.ServerAddress); + LANCommanderClient.UseToken(new SDK.Models.AuthToken() { AccessToken = Settings.AccessToken, RefreshToken = Settings.RefreshToken, @@ -89,7 +88,7 @@ namespace LANCommander.PlaynitePlugin public bool ValidateConnection() { - return LANCommander.Client.ValidateToken(); + return LANCommanderClient.ValidateToken(); } public override IEnumerable GetGames(LibraryGetGamesArgs args) @@ -109,7 +108,7 @@ namespace LANCommander.PlaynitePlugin } } - var games = LANCommander.Client + var games = LANCommanderClient .GetGames() .Where(g => g != null && g.Archives != null && g.Archives.Count() > 0); @@ -119,7 +118,7 @@ namespace LANCommander.PlaynitePlugin { Logger.Trace($"Importing/updating metadata for game \"{game.Title}\"..."); - var manifest = LANCommander.Client.GetGameManifest(game.Id); + var manifest = LANCommanderClient.GetGameManifest(game.Id); Logger.Trace("Successfully grabbed game manifest"); var existingGame = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == game.Id.ToString() && g.PluginId == Id && g.IsInstalled); @@ -128,7 +127,7 @@ namespace LANCommander.PlaynitePlugin { Logger.Trace("Game already exists in library, updating metadata..."); - UpdateGame(manifest, game.Id); + UpdateGame(manifest); continue; } @@ -181,13 +180,13 @@ namespace LANCommander.PlaynitePlugin metadata.Features.Add(new MetadataNameProperty($"Online Multiplayer {manifest.OnlineMultiplayer.GetPlayerCount()}".Trim())); if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Icon)) - metadata.Icon = new MetadataFile(LANCommander.Client.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Icon))); + metadata.Icon = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Icon))); if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Cover)) - metadata.CoverImage = new MetadataFile(LANCommander.Client.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Cover))); + metadata.CoverImage = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Cover))); if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Background)) - metadata.BackgroundImage = new MetadataFile(LANCommander.Client.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background))); + metadata.BackgroundImage = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background))); gameMetadata.Add(metadata); } @@ -222,9 +221,9 @@ namespace LANCommander.PlaynitePlugin if (args.Games.Count == 1 && args.Games.First().IsInstalled && !String.IsNullOrWhiteSpace(args.Games.First().InstallDirectory)) { - var nameChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.NameChange); - var keyChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.KeyChange); - var installScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.Install); + var nameChangeScriptPath = LANCommander.SDK.PowerShellRuntime.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.NameChange); + var keyChangeScriptPath = LANCommander.SDK.PowerShellRuntime.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.KeyChange); + var installScriptPath = LANCommander.SDK.PowerShellRuntime.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.Install); if (File.Exists(nameChangeScriptPath)) { @@ -241,8 +240,10 @@ namespace LANCommander.PlaynitePlugin if (result.Result == true) { - PowerShellRuntime.RunScript(nameChangeArgs.Games.First(), SDK.Enums.ScriptType.NameChange, $@"""{result.SelectedString}"" ""{oldName}"""); - LANCommander.Client.ChangeAlias(result.SelectedString); + var game = nameChangeArgs.Games.First(); + + LANCommander.SDK.PowerShellRuntime.RunScript(game.InstallDirectory, SDK.Enums.ScriptType.NameChange, $@"""{result.SelectedString}"" ""{oldName}"""); + LANCommanderClient.ChangeAlias(result.SelectedString); } } }; @@ -262,12 +263,12 @@ namespace LANCommander.PlaynitePlugin if (Guid.TryParse(keyChangeArgs.Games.First().GameId, out gameId)) { // NUKIEEEE - var newKey = LANCommander.Client.GetNewKey(gameId); + var newKey = LANCommanderClient.GetNewKey(gameId); if (String.IsNullOrEmpty(newKey)) PlayniteApi.Dialogs.ShowErrorMessage("There are no more keys available on the server.", "No Keys Available"); else - PowerShellRuntime.RunScript(keyChangeArgs.Games.First(), SDK.Enums.ScriptType.KeyChange, $@"""{newKey}"""); + LANCommander.SDK.PowerShellRuntime.RunScript(keyChangeArgs.Games.First().InstallDirectory, SDK.Enums.ScriptType.KeyChange, $@"""{newKey}"""); } else { @@ -290,7 +291,7 @@ namespace LANCommander.PlaynitePlugin if (Guid.TryParse(installArgs.Games.First().GameId, out gameId)) { - PowerShellRuntime.RunScript(installArgs.Games.First(), SDK.Enums.ScriptType.Install); + LANCommander.SDK.PowerShellRuntime.RunScript(installArgs.Games.First().InstallDirectory, SDK.Enums.ScriptType.Install); } else { @@ -400,11 +401,11 @@ namespace LANCommander.PlaynitePlugin var games = PlayniteApi.Database.Games.Where(g => g.IsInstalled).ToList(); - LANCommander.Client.ChangeAlias(result.SelectedString); + LANCommanderClient.ChangeAlias(result.SelectedString); Logger.Trace($"Running name change scripts across {games.Count} installed game(s)"); - PowerShellRuntime.RunScripts(games, SDK.Enums.ScriptType.NameChange, Settings.PlayerName); + LANCommander.SDK.PowerShellRuntime.RunScripts(games.Select(g => g.InstallDirectory), SDK.Enums.ScriptType.NameChange, Settings.PlayerName); } } else @@ -439,9 +440,9 @@ namespace LANCommander.PlaynitePlugin return window; } - public void UpdateGame(SDK.GameManifest manifest, Guid gameId) + public void UpdateGame(SDK.GameManifest manifest) { - var game = PlayniteApi.Database.Games.First(g => g.GameId == gameId.ToString()); + var game = PlayniteApi.Database.Games.First(g => g.GameId == manifest.Id.ToString()); if (game == null) return; diff --git a/LANCommander.Playnite.Extension/Services/GameSaveService.cs b/LANCommander.Playnite.Extension/Services/GameSaveService.cs index a650e5c..4513443 100644 --- a/LANCommander.Playnite.Extension/Services/GameSaveService.cs +++ b/LANCommander.Playnite.Extension/Services/GameSaveService.cs @@ -17,15 +17,13 @@ namespace LANCommander.PlaynitePlugin.Services { internal class GameSaveService { - private readonly LANCommanderClient LANCommander; + private readonly LANCommander.SDK.Client LANCommander; private readonly IPlayniteAPI PlayniteApi; - private readonly PowerShellRuntime PowerShellRuntime; - internal GameSaveService(LANCommanderClient lanCommander, IPlayniteAPI playniteApi, PowerShellRuntime powerShellRuntime) + internal GameSaveService(LANCommander.SDK.Client lanCommander, IPlayniteAPI playniteApi) { LANCommander = lanCommander; PlayniteApi = playniteApi; - PowerShellRuntime = powerShellRuntime; } internal void DownloadSave(Game game) diff --git a/LANCommander.Playnite.Extension/UninstallController.cs b/LANCommander.Playnite.Extension/UninstallController.cs index 3a77ee6..93518d0 100644 --- a/LANCommander.Playnite.Extension/UninstallController.cs +++ b/LANCommander.Playnite.Extension/UninstallController.cs @@ -12,29 +12,25 @@ namespace LANCommander.PlaynitePlugin public static readonly ILogger Logger = LogManager.GetLogger(); private LANCommanderLibraryPlugin Plugin; - private PowerShellRuntime PowerShellRuntime; public LANCommanderUninstallController(LANCommanderLibraryPlugin plugin, Game game) : base(game) { Name = "Uninstall LANCommander Game"; Plugin = plugin; - PowerShellRuntime = new PowerShellRuntime(); } public override void Uninstall(UninstallActionArgs args) { try { - PowerShellRuntime.RunScript(Game, ScriptType.Uninstall); + var gameManager = new LANCommander.SDK.GameManager(Plugin.LANCommanderClient); + + gameManager.Uninstall(Game.InstallDirectory); } - catch { } + catch (Exception ex) + { - Logger.Trace("Attempting to delete install directory..."); - - if (!String.IsNullOrWhiteSpace(Game.InstallDirectory) && Directory.Exists(Game.InstallDirectory)) - Directory.Delete(Game.InstallDirectory, true); - - Logger.Trace("Deleted!"); + } InvokeOnUninstalled(new GameUninstalledEventArgs()); } diff --git a/LANCommander.SDK/EventArgs.cs b/LANCommander.SDK/EventArgs.cs new file mode 100644 index 0000000..ca85135 --- /dev/null +++ b/LANCommander.SDK/EventArgs.cs @@ -0,0 +1,22 @@ +using SharpCompress.Common; +using SharpCompress.Readers; +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK +{ + public class ArchiveExtractionProgressArgs : EventArgs + { + public long Position { get; set; } + public long Length { get; set; } + } + + public class ArchiveEntryExtractionProgressArgs : EventArgs + { + public IReader Reader { get; set; } + public TrackableStream Stream { get; set; } + public ReaderProgress Progress { get; set; } + public IEntry Entry { get; set; } + } +} diff --git a/LANCommander.SDK/GameManager.cs b/LANCommander.SDK/GameManager.cs new file mode 100644 index 0000000..c60f1b1 --- /dev/null +++ b/LANCommander.SDK/GameManager.cs @@ -0,0 +1,205 @@ +using LANCommander.SDK.Enums; +using LANCommander.SDK.Extensions; +using LANCommander.SDK.Helpers; +using LANCommander.SDK.Models; +using Microsoft.Extensions.Logging; +using SharpCompress.Common; +using SharpCompress.Readers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace LANCommander.SDK +{ + public class GameManager + { + private static readonly ILogger Logger; + private Client Client { get; set; } + private string DefaultInstallDirectory { get; set; } + + public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e); + public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress; + + public delegate void OnArchiveExtractionProgressHandler(long position, long length); + public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress; + + public GameManager(Client client) + { + Client = client; + } + + /// + /// Downloads, extracts, and runs post-install scripts for the specified game + /// + /// Game to install + /// Maximum attempts in case of transmission error + /// Final install path + /// + public string Install(Guid gameId, int maxAttempts = 10) + { + var game = Client.GetGame(gameId); + + Logger.LogTrace("Installing game {GameTitle} (GameId)", game.Title, game.Id); + + var result = RetryHelper.RetryOnException(maxAttempts, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () => + { + Logger.LogTrace("Attempting to download and extract game"); + + return DownloadAndExtract(game); + }); + + if (!result.Success && !result.Canceled) + throw new Exception("Could not extract the installer. Retry the install or check your connection"); + else if (result.Canceled) + throw new Exception("Game install was canceled"); + + GameManifest manifest = null; + + game.InstallDirectory = result.Directory; + + var writeManifestSuccess = RetryHelper.RetryOnException(maxAttempts, TimeSpan.FromSeconds(1), false, () => + { + Logger.LogTrace("Attempting to get game manifest"); + + manifest = Client.GetGameManifest(game.Id); + + ManifestHelper.Write(manifest, game.InstallDirectory); + + return true; + }); + + if (!writeManifestSuccess) + throw new Exception("Could not grab the manifest file. Retry the install or check your connection"); + + Logger.LogTrace("Saving scripts"); + + ScriptHelper.SaveScript(game, ScriptType.Install); + ScriptHelper.SaveScript(game, ScriptType.Uninstall); + ScriptHelper.SaveScript(game, ScriptType.NameChange); + ScriptHelper.SaveScript(game, ScriptType.KeyChange); + + try + { + PowerShellRuntime.RunScript(game, ScriptType.Install); + PowerShellRuntime.RunScript(game, ScriptType.NameChange, /* Plugin.Settings.PlayerName */ ""); + + var key = Client.GetAllocatedKey(game.Id); + + PowerShellRuntime.RunScript(game, ScriptType.KeyChange, $"\"{key}\""); + } + catch (Exception ex) + { + Logger.LogError(ex, "Could not execute post-install scripts"); + } + + return result.Directory; + } + + public void Uninstall(string installDirectory) + { + var manifest = ManifestHelper.Read(installDirectory); + + try + { + Logger.LogTrace("Running uninstall script"); + PowerShellRuntime.RunScript(installDirectory, ScriptType.Uninstall); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error running uninstall script"); + } + + Logger.LogTrace("Attempting to delete the install directory"); + + if (Directory.Exists(installDirectory)) + Directory.Delete(installDirectory, true); + + Logger.LogTrace("Deleted install directory {InstallDirectory}", installDirectory); + } + + private ExtractionResult DownloadAndExtract(Game game, string installDirectory = "") + { + if (game == null) + { + Logger.LogTrace("Game failed to download, no game was specified"); + + throw new ArgumentNullException("No game was specified"); + } + + if (String.IsNullOrWhiteSpace(installDirectory)) + installDirectory = DefaultInstallDirectory; + + var destination = Path.Combine(installDirectory, game.Title.SanitizeFilename()); + + Logger.LogTrace("Downloading and extracting {Game} to path {Destination}", game.Title, destination); + + try + { + Directory.CreateDirectory(destination); + + using (var gameStream = Client.StreamGame(game.Id)) + using (var reader = ReaderFactory.Open(gameStream)) + { + gameStream.OnProgress += (pos, len) => + { + OnArchiveExtractionProgress?.Invoke(pos, len); + }; + + reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs e) => + { + OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs + { + Entry = e.Item, + Progress = e.ReaderProgress, + Reader = reader, + Stream = gameStream + }); + }; + + reader.WriteAllToDirectory(destination, new ExtractionOptions() + { + ExtractFullPath = true, + Overwrite = true + }); + } + } + catch (Exception ex) + { + if (false) + { + + } + else + { + Logger.LogError(ex, "Could not extract to path {Destination}", destination); + + if (Directory.Exists(destination)) + { + Logger.LogTrace("Cleaning up orphaned install files after bad install"); + + Directory.Delete(destination, true); + } + + throw new Exception("The game archive could not be extracted, is it corrupted? Please try again"); + } + } + + var extractionResult = new ExtractionResult + { + Canceled = false, + }; + + if (!extractionResult.Canceled) + { + extractionResult.Success = true; + extractionResult.Directory = destination; + + Logger.LogTrace("Game {Game} successfully downloaded and extracted to {Destination}", game.Title, destination); + } + + return extractionResult; + } + } +} diff --git a/LANCommander.SDK/Helpers/ManifestHelper.cs b/LANCommander.SDK/Helpers/ManifestHelper.cs new file mode 100644 index 0000000..7e82586 --- /dev/null +++ b/LANCommander.SDK/Helpers/ManifestHelper.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using YamlDotNet.Serialization.NamingConventions; +using YamlDotNet.Serialization; + +namespace LANCommander.SDK.Helpers +{ + public static class ManifestHelper + { + public static readonly ILogger Logger; + + public const string ManifestFilename = "_manifest.yml"; + + public static GameManifest Read(string installDirectory) + { + var source = Path.Combine(installDirectory, ManifestFilename); + var yaml = File.ReadAllText(source); + + var deserializer = new DeserializerBuilder() + .WithNamingConvention(PascalCaseNamingConvention.Instance) + .Build(); + + Logger.LogTrace("Deserializing manifest"); + + var manifest = deserializer.Deserialize(source); + + return manifest; + } + + public static void Write(GameManifest manifest, string installDirectory) + { + var destination = Path.Combine(installDirectory, ManifestFilename); + + Logger.LogTrace("Attempting to write manifest to path {Destination}", destination); + + var serializer = new SerializerBuilder() + .WithNamingConvention(PascalCaseNamingConvention.Instance) + .Build(); + + Logger.LogTrace("Serializing manifest"); + + var yaml = serializer.Serialize(manifest); + + Logger.LogTrace("Writing manifest file"); + + File.WriteAllText(destination, yaml); + } + } +} diff --git a/LANCommander.SDK/Helpers/ScriptHelper.cs b/LANCommander.SDK/Helpers/ScriptHelper.cs new file mode 100644 index 0000000..2a6e926 --- /dev/null +++ b/LANCommander.SDK/Helpers/ScriptHelper.cs @@ -0,0 +1,50 @@ +using LANCommander.SDK.Enums; +using LANCommander.SDK.Models; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace LANCommander.SDK.Helpers +{ + public static class ScriptHelper + { + public static readonly ILogger Logger; + + public static string SaveTempScript(Script script) + { + var tempPath = Path.GetTempFileName(); + + // PowerShell will only run scripts with the .ps1 file extension + File.Move(tempPath, tempPath + ".ps1"); + + Logger.LogTrace("Writing script {Script} to {Destination}", script.Name, tempPath); + + File.WriteAllText(tempPath, script.Contents); + + return tempPath; + } + + public static void SaveScript(Game game, ScriptType type) + { + var script = game.Scripts.FirstOrDefault(s => s.Type == type); + + if (script == null) + return; + + if (script.RequiresAdmin) + script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents; + + var filename = PowerShellRuntime.GetScriptFilePath(game, type); + + if (File.Exists(filename)) + File.Delete(filename); + + Logger.LogTrace("Writing {ScriptType} script to {Destination}", type, filename); + + File.WriteAllText(filename, script.Contents); + } + } +} diff --git a/LANCommander.SDK/LANCommander.cs b/LANCommander.SDK/LANCommander.cs deleted file mode 100644 index 743c727..0000000 --- a/LANCommander.SDK/LANCommander.cs +++ /dev/null @@ -1,405 +0,0 @@ -using LANCommander.SDK.Enums; -using LANCommander.SDK.Extensions; -using LANCommander.SDK.Helpers; -using LANCommander.SDK.Models; -using Microsoft.Extensions.Logging; -using SharpCompress.Common; -using SharpCompress.Readers; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using YamlDotNet.RepresentationModel; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - -namespace LANCommander.SDK -{ - public class ArchiveExtractionProgressArgs : EventArgs - { - public long Position { get; set; } - public long Length { get; set; } - } - - public class ArchiveEntryExtractionProgressArgs : EventArgs - { - public IReader Reader { get; set; } - public TrackableStream Stream { get; set; } - public ReaderProgress Progress { get; set; } - public IEntry Entry { get; set; } - } - - public class LANCommander - { - public static readonly ILogger Logger; - - private const string ManifestFilename = "_manifest.yml"; - - private string DefaultInstallDirectory { get; set; } - public Client Client { get; set; } - private PowerShellRuntime PowerShellRuntime; - - public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e); - public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress; - - public delegate void OnArchiveExtractionProgressHandler(long position, long length); - public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress; - - public LANCommander(string baseUrl) - { - Client = new Client(baseUrl); - } - - /// - /// Downloads, extracts, and runs post-install scripts for the specified game - /// - /// Game to install - /// Maximum attempts in case of transmission error - /// Final install path - /// - public string InstallGame(Guid gameId, int maxAttempts = 10) - { - var game = Client.GetGame(gameId); - - Logger.LogTrace("Installing game {GameTitle} (GameId)", game.Title, game.Id); - - var result = RetryHelper.RetryOnException(maxAttempts, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () => - { - Logger.LogTrace("Attempting to download and extract game"); - - return DownloadAndExtractGame(game); - }); - - if (!result.Success && !result.Canceled) - throw new Exception("Could not extract the installer. Retry the install or check your connection"); - else if (result.Canceled) - throw new Exception("Game install was canceled"); - - GameManifest manifest = null; - - game.InstallDirectory = result.Directory; - - var writeManifestSuccess = RetryHelper.RetryOnException(maxAttempts, TimeSpan.FromSeconds(1), false, () => - { - Logger.LogTrace("Attempting to get game manifest"); - - manifest = Client.GetGameManifest(game.Id); - - WriteManifest(manifest, game.InstallDirectory); - - return true; - }); - - if (!writeManifestSuccess) - throw new Exception("Could not grab the manifest file. Retry the install or check your connection"); - - Logger.LogTrace("Saving scripts"); - - SaveScript(game, ScriptType.Install); - SaveScript(game, ScriptType.Uninstall); - SaveScript(game, ScriptType.NameChange); - SaveScript(game, ScriptType.KeyChange); - - if (game.Redistributables != null && game.Redistributables.Count() > 0) - { - Logger.LogTrace("Installing required redistributables"); - InstallRedistributables(game); - } - - try - { - PowerShellRuntime.RunScript(game, ScriptType.Install); - PowerShellRuntime.RunScript(game, ScriptType.NameChange, /* Plugin.Settings.PlayerName */ ""); - - var key = Client.GetAllocatedKey(game.Id); - - PowerShellRuntime.RunScript(game, ScriptType.KeyChange, $"\"{key}\""); - } - catch (Exception ex) - { - Logger.LogError(ex, "Could not execute post-install scripts"); - } - - // Plugin.UpdateGame(manifest, gameId) - - // Plugin.DownloadCache.Remove(gameId); - - return result.Directory; - } - - private ExtractionResult DownloadAndExtractGame(Game game, string installDirectory = "") - { - if (game == null) - { - Logger.LogTrace("Game failed to download, no game was specified"); - - throw new ArgumentNullException("No game was specified"); - } - - if (String.IsNullOrWhiteSpace(installDirectory)) - installDirectory = DefaultInstallDirectory; - - var destination = Path.Combine(installDirectory, game.Title.SanitizeFilename()); - - Logger.LogTrace("Downloading and extracting {Game} to path {Destination}", game.Title, destination); - - try - { - Directory.CreateDirectory(destination); - - using (var gameStream = Client.StreamGame(game.Id)) - using (var reader = ReaderFactory.Open(gameStream)) - { - gameStream.OnProgress += (pos, len) => - { - OnArchiveExtractionProgress?.Invoke(pos, len); - }; - - reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs e) => - { - OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs - { - Entry = e.Item, - Progress = e.ReaderProgress, - Reader = reader, - Stream = gameStream - }); - }; - - reader.WriteAllToDirectory(destination, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - catch (Exception ex) - { - if (false) - { - - } - else - { - Logger.LogError(ex, "Could not extract to path {Destination}", destination); - - if (Directory.Exists(destination)) - { - Logger.LogTrace("Cleaning up orphaned install files after bad install"); - - Directory.Delete(destination, true); - } - - throw new Exception("The game archive could not be extracted, is it corrupted? Please try again"); - } - } - - var extractionResult = new ExtractionResult - { - Canceled = false, - }; - - if (!extractionResult.Canceled) - { - extractionResult.Success = true; - extractionResult.Directory = destination; - - Logger.LogTrace("Game {Game} successfully downloaded and extracted to {Destination}", game.Title, destination); - } - - return extractionResult; - } - - private void InstallRedistributables(Game game) - { - foreach (var redistributable in game.Redistributables) - { - InstallRedistributable(redistributable); - } - } - - private void InstallRedistributable(Redistributable redistributable) - { - string installScriptTempFile = null; - string detectionScriptTempFile = null; - string extractTempPath = null; - - try - { - var installScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.Install); - installScriptTempFile = SaveTempScript(installScript); - - var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall); - detectionScriptTempFile = SaveTempScript(detectionScript); - - var detectionResult = PowerShellRuntime.RunScript(detectionScriptTempFile, detectionScript.RequiresAdmin); - - // Redistributable is not installed - if (detectionResult == 0) - { - if (redistributable.Archives.Count() > 0) - { - var extractionResult = DownloadAndExtractRedistributable(redistributable); - - if (extractionResult.Success) - { - extractTempPath = extractionResult.Directory; - - PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath); - } - } - else - { - PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath); - } - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Redistributable {Redistributable} failed to install", redistributable.Name); - } - finally - { - if (File.Exists(installScriptTempFile)) - File.Delete(installScriptTempFile); - - if (File.Exists(detectionScriptTempFile)) - File.Delete(detectionScriptTempFile); - - if (Directory.Exists(extractTempPath)) - Directory.Delete(extractTempPath); - } - } - - private ExtractionResult DownloadAndExtractRedistributable(Redistributable redistributable) - { - if (redistributable == null) - { - Logger.LogTrace("Redistributable failed to download! No redistributable was specified"); - throw new ArgumentNullException("No redistributable was specified"); - } - - var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename()); - - Logger.LogTrace("Downloading and extracting {Redistributable} to path {Destination}", redistributable.Name, destination); - - try - { - Directory.CreateDirectory(destination); - - using (var redistributableStream = Client.StreamRedistributable(redistributable.Id)) - using (var reader = ReaderFactory.Open(redistributableStream)) - { - redistributableStream.OnProgress += (pos, len) => - { - OnArchiveExtractionProgress?.Invoke(pos, len); - }; - - reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs e) => - { - OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs - { - Entry = e.Item, - Progress = e.ReaderProgress, - Reader = reader, - Stream = redistributableStream - }); - }; - - reader.WriteAllToDirectory(destination, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Could not extract to path {Destination}", destination); - - if (Directory.Exists(destination)) - { - Logger.LogTrace("Cleaning up orphaned files after bad install"); - - Directory.Delete(destination, true); - } - - throw new Exception("The redistributable archive could not be extracted, is it corrupted? Please try again"); - } - - var extractionResult = new ExtractionResult - { - Canceled = false - }; - - if (!extractionResult.Canceled) - { - extractionResult.Success = true; - extractionResult.Directory = destination; - Logger.LogTrace("Redistributable {Redistributable} successfully downloaded and extracted to {Destination}", redistributable.Name, destination); - } - - return extractionResult; - } - - public void WriteManifest(GameManifest manifest, string installDirectory) - { - var destination = Path.Combine(installDirectory, ManifestFilename); - - Logger.LogTrace("Attempting to write manifest to path {Destination}", destination); - - var serializer = new SerializerBuilder() - .WithNamingConvention(PascalCaseNamingConvention.Instance) - .Build(); - - Logger.LogTrace("Serializing manifest"); - - var yaml = serializer.Serialize(manifest); - - Logger.LogTrace("Writing manifest file"); - - File.WriteAllText(destination, yaml); - } - - private string SaveTempScript(Script script) - { - var tempPath = Path.GetTempFileName(); - - // PowerShell will only run scripts with the .ps1 file extension - File.Move(tempPath, tempPath + ".ps1"); - - Logger.LogTrace("Writing script {Script} to {Destination}", script.Name, tempPath); - - File.WriteAllText(tempPath, script.Contents); - - return tempPath; - } - - private void SaveScript(Game game, ScriptType type) - { - var script = game.Scripts.FirstOrDefault(s => s.Type == type); - - if (script == null) - return; - - if (script.RequiresAdmin) - script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents; - - var filename = PowerShellRuntime.GetScriptFilePath(game, type); - - if (File.Exists(filename)) - File.Delete(filename); - - Logger.LogTrace("Writing {ScriptType} script to {Destination}", type, filename); - - File.WriteAllText(filename, script.Contents); - } - - public void ChangeAlias(string alias) - { - - } - } -} diff --git a/LANCommander.SDK/RedistributableManager.cs b/LANCommander.SDK/RedistributableManager.cs new file mode 100644 index 0000000..bb4ce9f --- /dev/null +++ b/LANCommander.SDK/RedistributableManager.cs @@ -0,0 +1,164 @@ +using LANCommander.SDK.Enums; +using LANCommander.SDK.Extensions; +using LANCommander.SDK.Helpers; +using LANCommander.SDK.Models; +using Microsoft.Extensions.Logging; +using SharpCompress.Common; +using SharpCompress.Readers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace LANCommander.SDK +{ + public class RedistributableManager + { + private static readonly ILogger Logger; + private Client Client { get; set; } + + public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e); + public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress; + + public delegate void OnArchiveExtractionProgressHandler(long position, long length); + public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress; + + public RedistributableManager(Client client) + { + Client = client; + } + + public void Install(Game game) + { + foreach (var redistributable in game.Redistributables) + { + Install(redistributable); + } + } + + public void Install(Redistributable redistributable) + { + string installScriptTempFile = null; + string detectionScriptTempFile = null; + string extractTempPath = null; + + try + { + var installScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.Install); + installScriptTempFile = ScriptHelper.SaveTempScript(installScript); + + var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall); + detectionScriptTempFile = ScriptHelper.SaveTempScript(detectionScript); + + var detectionResult = PowerShellRuntime.RunScript(detectionScriptTempFile, detectionScript.RequiresAdmin); + + // Redistributable is not installed + if (detectionResult == 0) + { + if (redistributable.Archives.Count() > 0) + { + var extractionResult = DownloadAndExtract(redistributable); + + if (extractionResult.Success) + { + extractTempPath = extractionResult.Directory; + + PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath); + } + } + else + { + PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath); + } + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Redistributable {Redistributable} failed to install", redistributable.Name); + } + finally + { + if (File.Exists(installScriptTempFile)) + File.Delete(installScriptTempFile); + + if (File.Exists(detectionScriptTempFile)) + File.Delete(detectionScriptTempFile); + + if (Directory.Exists(extractTempPath)) + Directory.Delete(extractTempPath); + } + } + + private ExtractionResult DownloadAndExtract(Redistributable redistributable) + { + if (redistributable == null) + { + Logger.LogTrace("Redistributable failed to download! No redistributable was specified"); + throw new ArgumentNullException("No redistributable was specified"); + } + + var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename()); + + Logger.LogTrace("Downloading and extracting {Redistributable} to path {Destination}", redistributable.Name, destination); + + try + { + Directory.CreateDirectory(destination); + + using (var redistributableStream = Client.StreamRedistributable(redistributable.Id)) + using (var reader = ReaderFactory.Open(redistributableStream)) + { + redistributableStream.OnProgress += (pos, len) => + { + OnArchiveExtractionProgress?.Invoke(pos, len); + }; + + reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs e) => + { + OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs + { + Entry = e.Item, + Progress = e.ReaderProgress, + Reader = reader, + Stream = redistributableStream + }); + }; + + reader.WriteAllToDirectory(destination, new ExtractionOptions() + { + ExtractFullPath = true, + Overwrite = true + }); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Could not extract to path {Destination}", destination); + + if (Directory.Exists(destination)) + { + Logger.LogTrace("Cleaning up orphaned files after bad install"); + + Directory.Delete(destination, true); + } + + throw new Exception("The redistributable archive could not be extracted, is it corrupted? Please try again"); + } + + var extractionResult = new ExtractionResult + { + Canceled = false + }; + + if (!extractionResult.Canceled) + { + extractionResult.Success = true; + extractionResult.Directory = destination; + Logger.LogTrace("Redistributable {Redistributable} successfully downloaded and extracted to {Destination}", redistributable.Name, destination); + } + + return extractionResult; + } + } +} From 73b542856a62e7515468fe3f6fc02198a1a9f063 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 10 Nov 2023 01:32:30 -0600 Subject: [PATCH 05/93] Refactor GameSaveService into GameSaveManager and SaveController. Update Playnite addon authentication dialogs to use new client. --- .../LANCommander.PlaynitePlugin.csproj | 8 +- .../LANCommanderLibraryPlugin.cs | 7 +- .../SaveController.cs | 61 +++++ .../Views/Authentication.xaml.cs | 30 +-- .../Views/LANCommanderSettingsView.xaml.cs | 4 +- LANCommander.SDK/Client.cs | 22 +- .../GameSaveManager.cs | 209 ++++++++---------- LANCommander.SDK/Helpers/ManifestHelper.cs | 9 +- LANCommander.SDK/LANCommander.SDK.csproj | 4 +- 9 files changed, 201 insertions(+), 153 deletions(-) create mode 100644 LANCommander.Playnite.Extension/SaveController.cs rename LANCommander.Playnite.Extension/Services/GameSaveService.cs => LANCommander.SDK/GameSaveManager.cs (52%) diff --git a/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj b/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj index bbd6a13..e8bed07 100644 --- a/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj +++ b/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj @@ -42,6 +42,9 @@ + + ..\packages\SharpCompress.0.34.1\lib\net462\SharpCompress.dll + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll @@ -85,10 +88,13 @@ + + ..\packages\ZstdSharp.Port.0.7.2\lib\net461\ZstdSharp.dll + - + diff --git a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs index fee02ea..a7da90f 100644 --- a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs +++ b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs @@ -1,5 +1,4 @@ using LANCommander.PlaynitePlugin.Extensions; -using LANCommander.PlaynitePlugin.Services; using Playnite.SDK; using Playnite.SDK.Events; using Playnite.SDK.Models; @@ -22,7 +21,7 @@ namespace LANCommander.PlaynitePlugin public static readonly ILogger Logger = LogManager.GetLogger(); internal LANCommanderSettingsViewModel Settings { get; set; } internal LANCommander.SDK.Client LANCommanderClient { get; set; } - internal GameSaveService GameSaveService { get; set; } + internal LANCommanderSaveController SaveController { get; set; } public override Guid Id { get; } = Guid.Parse("48e1bac7-e0a0-45d7-ba83-36f5e9e959fc"); public override string Name => "LANCommander"; @@ -333,12 +332,12 @@ namespace LANCommander.PlaynitePlugin public override void OnGameStarting(OnGameStartingEventArgs args) { - GameSaveService.DownloadSave(args.Game); + SaveController.Download(args.Game); } public override void OnGameStopped(OnGameStoppedEventArgs args) { - GameSaveService.UploadSave(args.Game); + SaveController.Upload(args.Game); } public override IEnumerable GetTopPanelItems() diff --git a/LANCommander.Playnite.Extension/SaveController.cs b/LANCommander.Playnite.Extension/SaveController.cs new file mode 100644 index 0000000..557ee2e --- /dev/null +++ b/LANCommander.Playnite.Extension/SaveController.cs @@ -0,0 +1,61 @@ +using LANCommander.SDK; +using Playnite.SDK; +using Playnite.SDK.Models; +using Playnite.SDK.Plugins; + +namespace LANCommander.PlaynitePlugin +{ + public class LANCommanderSaveController : ControllerBase + { + private static readonly ILogger Logger; + + private LANCommanderLibraryPlugin Plugin; + + public LANCommanderSaveController(LANCommanderLibraryPlugin plugin, Game game) : base(game) + { + Name = "Download save using LANCommander"; + Plugin = plugin; + } + + public void Download(Game game) + { + if (game != null) + { + Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress => + { + progress.ProgressMaxValue = 100; + progress.CurrentProgressValue = 0; + + var saveManager = new GameSaveManager(Plugin.LANCommanderClient); + + saveManager.OnDownloadProgress += (downloadProgress) => + { + progress.CurrentProgressValue = downloadProgress.ProgressPercentage; + }; + + saveManager.OnDownloadComplete += (downloadComplete) => + { + progress.CurrentProgressValue = 100; + }; + + saveManager.Download(game.InstallDirectory); + + // Lock the thread until the download is done + while (progress.CurrentProgressValue != 100) { } + }, + new GlobalProgressOptions("Downloading latest save...") + { + IsIndeterminate = false, + Cancelable = false + }); + } + } + + public void Upload(Game game) + { + var saveManager = new GameSaveManager(Plugin.LANCommanderClient); + + saveManager.Upload(game.InstallDirectory); + } + } +} diff --git a/LANCommander.Playnite.Extension/Views/Authentication.xaml.cs b/LANCommander.Playnite.Extension/Views/Authentication.xaml.cs index 6b3db4f..0e6869a 100644 --- a/LANCommander.Playnite.Extension/Views/Authentication.xaml.cs +++ b/LANCommander.Playnite.Extension/Views/Authentication.xaml.cs @@ -100,24 +100,16 @@ namespace LANCommander.PlaynitePlugin.Views LoginButton.Content = "Logging in..."; })); - if (Plugin.LANCommander == null || Plugin.LANCommander.Client == null) - Plugin.LANCommander = new LANCommanderClient(Context.ServerAddress); - else - Plugin.LANCommander.Client.BaseUrl = new Uri(Context.ServerAddress); + if (Plugin.LANCommanderClient == null) + Plugin.LANCommanderClient = new LANCommander.SDK.Client(Context.ServerAddress); - var response = await Plugin.LANCommander.AuthenticateAsync(Context.UserName, Context.Password); + var response = await Plugin.LANCommanderClient.AuthenticateAsync(Context.UserName, Context.Password); Plugin.Settings.ServerAddress = Context.ServerAddress; Plugin.Settings.AccessToken = response.AccessToken; Plugin.Settings.RefreshToken = response.RefreshToken; - Plugin.LANCommander.Token = new AuthToken() - { - AccessToken = response.AccessToken, - RefreshToken = response.RefreshToken, - }; - - var profile = Plugin.LANCommander.GetProfile(); + var profile = Plugin.LANCommanderClient.GetProfile(); Plugin.Settings.PlayerName = String.IsNullOrWhiteSpace(profile.Alias) ? profile.UserName : profile.Alias; @@ -148,24 +140,16 @@ namespace LANCommander.PlaynitePlugin.Views RegisterButton.IsEnabled = false; RegisterButton.Content = "Working..."; - if (Plugin.LANCommander == null || Plugin.LANCommander.Client == null) - Plugin.LANCommander = new LANCommanderClient(Context.ServerAddress); - else - Plugin.LANCommander.Client.BaseUrl = new Uri(Context.ServerAddress); + if (Plugin.LANCommanderClient == null) + Plugin.LANCommanderClient = new LANCommander.SDK.Client(Context.ServerAddress); - var response = await Plugin.LANCommander.RegisterAsync(Context.UserName, Context.Password); + var response = await Plugin.LANCommanderClient.RegisterAsync(Context.UserName, Context.Password); Plugin.Settings.ServerAddress = Context.ServerAddress; Plugin.Settings.AccessToken = response.AccessToken; Plugin.Settings.RefreshToken = response.RefreshToken; Plugin.Settings.PlayerName = Context.UserName; - Plugin.LANCommander.Token = new AuthToken() - { - AccessToken = response.AccessToken, - RefreshToken = response.RefreshToken, - }; - Context.Password = String.Empty; Plugin.SavePluginSettings(Plugin.Settings); diff --git a/LANCommander.Playnite.Extension/Views/LANCommanderSettingsView.xaml.cs b/LANCommander.Playnite.Extension/Views/LANCommanderSettingsView.xaml.cs index 2388840..44526a0 100644 --- a/LANCommander.Playnite.Extension/Views/LANCommanderSettingsView.xaml.cs +++ b/LANCommander.Playnite.Extension/Views/LANCommanderSettingsView.xaml.cs @@ -47,7 +47,7 @@ namespace LANCommander.PlaynitePlugin RefreshToken = Settings.RefreshToken, }; - var task = Task.Run(() => Plugin.LANCommander.ValidateToken(token)) + var task = Task.Run(() => Plugin.LANCommanderClient.ValidateToken(token)) .ContinueWith(antecedent => { try @@ -90,7 +90,7 @@ namespace LANCommander.PlaynitePlugin { Plugin.Settings.AccessToken = String.Empty; Plugin.Settings.RefreshToken = String.Empty; - Plugin.LANCommander.Token = null; + Plugin.LANCommanderClient.UseToken(null); Plugin.SavePluginSettings(Plugin.Settings); diff --git a/LANCommander.SDK/Client.cs b/LANCommander.SDK/Client.cs index 958e8ca..805b0b4 100644 --- a/LANCommander.SDK/Client.cs +++ b/LANCommander.SDK/Client.cs @@ -107,7 +107,7 @@ namespace LANCommander.SDK } } - public async Task RegisterAsync(string username, string password) + public async Task RegisterAsync(string username, string password) { var response = await ApiClient.ExecuteAsync(new RestRequest("/api/auth/register", Method.POST).AddJsonBody(new AuthRequest() { @@ -118,7 +118,14 @@ namespace LANCommander.SDK switch (response.StatusCode) { case HttpStatusCode.OK: - return response.Data; + Token = new AuthToken + { + AccessToken = response.Data.AccessToken, + RefreshToken = response.Data.RefreshToken, + Expiration = response.Data.Expiration + }; + + return Token; case HttpStatusCode.BadRequest: case HttpStatusCode.Forbidden: @@ -137,7 +144,7 @@ namespace LANCommander.SDK return response.StatusCode == HttpStatusCode.OK; } - public AuthResponse RefreshToken(AuthToken token) + public AuthToken RefreshToken(AuthToken token) { Logger.LogTrace("Refreshing token..."); @@ -149,7 +156,14 @@ namespace LANCommander.SDK if (response.StatusCode != HttpStatusCode.OK) throw new WebException(response.ErrorMessage); - return response.Data; + Token = new AuthToken + { + AccessToken = response.Data.AccessToken, + RefreshToken = response.Data.RefreshToken, + Expiration = response.Data.Expiration + }; + + return Token; } public bool ValidateToken() diff --git a/LANCommander.Playnite.Extension/Services/GameSaveService.cs b/LANCommander.SDK/GameSaveManager.cs similarity index 52% rename from LANCommander.Playnite.Extension/Services/GameSaveService.cs rename to LANCommander.SDK/GameSaveManager.cs index 4513443..a7ffc1b 100644 --- a/LANCommander.Playnite.Extension/Services/GameSaveService.cs +++ b/LANCommander.SDK/GameSaveManager.cs @@ -1,64 +1,55 @@ using LANCommander.SDK; -using Playnite.SDK; -using Playnite.SDK.Models; +using LANCommander.SDK.Helpers; +using LANCommander.SDK.Models; using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; using SharpCompress.Readers; using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; +using System.Net; using System.Text; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; -namespace LANCommander.PlaynitePlugin.Services +namespace LANCommander.SDK { - internal class GameSaveService + public class GameSaveManager { - private readonly LANCommander.SDK.Client LANCommander; - private readonly IPlayniteAPI PlayniteApi; + private readonly Client Client; - internal GameSaveService(LANCommander.SDK.Client lanCommander, IPlayniteAPI playniteApi) + public delegate void OnDownloadProgressHandler(DownloadProgressChangedEventArgs e); + public event OnDownloadProgressHandler OnDownloadProgress; + + public delegate void OnDownloadCompleteHandler(AsyncCompletedEventArgs e); + public event OnDownloadCompleteHandler OnDownloadComplete; + + public GameSaveManager(Client client) { - LANCommander = lanCommander; - PlayniteApi = playniteApi; + Client = client; } - internal void DownloadSave(Game game) + public void Download(string installDirectory) { + var manifest = ManifestHelper.Read(installDirectory); + string tempFile = String.Empty; - if (game != null) + if (manifest != null) { - PlayniteApi.Dialogs.ActivateGlobalProgress(progress => + var destination = Client.DownloadLatestSave(manifest.Id, (changed) => { - progress.ProgressMaxValue = 100; - progress.CurrentProgressValue = 0; - - var destination = LANCommander.DownloadLatestSave(Guid.Parse(game.GameId), (changed) => - { - progress.CurrentProgressValue = changed.ProgressPercentage; - }, (complete) => - { - progress.CurrentProgressValue = 100; - }); - - // Lock the thread until download is done - while (progress.CurrentProgressValue != 100) - { - - } - - tempFile = destination; - }, - new GlobalProgressOptions("Downloading latest save...") + OnDownloadProgress?.Invoke(changed); + }, (complete) => { - IsIndeterminate = false, - Cancelable = false + OnDownloadComplete?.Invoke(complete); }); + tempFile = destination; + // Go into the archive and extract the files to the correct locations try { @@ -72,10 +63,6 @@ namespace LANCommander.PlaynitePlugin.Services .WithNamingConvention(new PascalCaseNamingConvention()) .Build(); - var manifestContents = File.ReadAllText(Path.Combine(tempLocation, "_manifest.yml")); - - var manifest = deserializer.Deserialize(manifestContents); - #region Move files foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File")) { @@ -84,7 +71,7 @@ namespace LANCommander.PlaynitePlugin.Services var tempSavePathFile = Path.Combine(tempSavePath, savePath.Path.Replace('/', '\\').Replace("{InstallDir}\\", "")); - var destination = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory)); + destination = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", installDirectory)); if (File.Exists(tempSavePathFile)) { @@ -105,7 +92,7 @@ namespace LANCommander.PlaynitePlugin.Services if (inInstallDir) { // Files are in the game's install directory. Move them there from the save path. - destination = file.Replace(tempSavePath, savePath.Path.Replace('/', '\\').TrimEnd('\\').Replace("{InstallDir}", game.InstallDirectory)); + destination = file.Replace(tempSavePath, savePath.Path.Replace('/', '\\').TrimEnd('\\').Replace("{InstallDir}", installDirectory)); if (File.Exists(destination)) File.Delete(destination); @@ -153,97 +140,89 @@ namespace LANCommander.PlaynitePlugin.Services } } - internal void UploadSave(Game game) + public void Upload(string installDirectory) { - var manifestPath = Path.Combine(game.InstallDirectory, "_manifest.yml"); + var manifest = ManifestHelper.Read(installDirectory); - if (File.Exists(manifestPath)) + var temp = Path.GetTempFileName(); + + if (manifest.SavePaths != null && manifest.SavePaths.Count() > 0) { - var deserializer = new DeserializerBuilder() - .WithNamingConvention(new PascalCaseNamingConvention()) - .Build(); - - var manifest = deserializer.Deserialize(File.ReadAllText(manifestPath)); - var temp = Path.GetTempFileName(); - - if (manifest.SavePaths != null && manifest.SavePaths.Count() > 0) + using (var archive = ZipArchive.Create()) { - using (var archive = ZipArchive.Create()) + archive.DeflateCompressionLevel = SharpCompress.Compressors.Deflate.CompressionLevel.BestCompression; + + #region Add files from defined paths + foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File")) { - archive.DeflateCompressionLevel = SharpCompress.Compressors.Deflate.CompressionLevel.BestCompression; + var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", installDirectory)); - #region Add files from defined paths - foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File")) + if (Directory.Exists(localPath)) { - var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory)); - - if (Directory.Exists(localPath)) - { - AddDirectoryToZip(archive, localPath, localPath, savePath.Id); - } - else if (File.Exists(localPath)) - { - archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath); - } + AddDirectoryToZip(archive, localPath, localPath, savePath.Id); } - #endregion - - #region Add files from defined paths - foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File")) + else if (File.Exists(localPath)) { - var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory)); - - if (Directory.Exists(localPath)) - { - AddDirectoryToZip(archive, localPath, localPath, savePath.Id); - } - else if (File.Exists(localPath)) - { - archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath); - } + archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath); } - #endregion + } + #endregion - #region Export registry keys - if (manifest.SavePaths.Any(sp => sp.Type == "Registry")) + #region Add files from defined paths + foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File")) + { + var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", installDirectory)); + + if (Directory.Exists(localPath)) { - List tempRegFiles = new List(); - - var exportCommand = new StringBuilder(); - - foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "Registry")) - { - var tempRegFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".reg"); - - exportCommand.AppendLine($"reg.exe export \"{savePath.Path.Replace(":\\", "\\")}\" \"{tempRegFile}\""); - tempRegFiles.Add(tempRegFile); - } - - PowerShellRuntime.RunCommand(exportCommand.ToString()); - - var exportFile = new StringBuilder(); - - foreach (var tempRegFile in tempRegFiles) - { - exportFile.AppendLine(File.ReadAllText(tempRegFile)); - File.Delete(tempRegFile); - } - - archive.AddEntry("_registry.reg", new MemoryStream(Encoding.UTF8.GetBytes(exportFile.ToString())), true); + AddDirectoryToZip(archive, localPath, localPath, savePath.Id); } - #endregion - - archive.AddEntry("_manifest.yml", manifestPath); - - using (var ms = new MemoryStream()) + else if (File.Exists(localPath)) { - archive.SaveTo(ms); - - ms.Seek(0, SeekOrigin.Begin); - - var save = LANCommander.UploadSave(game.GameId, ms.ToArray()); + archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath); } } + #endregion + + #region Export registry keys + if (manifest.SavePaths.Any(sp => sp.Type == "Registry")) + { + List tempRegFiles = new List(); + + var exportCommand = new StringBuilder(); + + foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "Registry")) + { + var tempRegFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".reg"); + + exportCommand.AppendLine($"reg.exe export \"{savePath.Path.Replace(":\\", "\\")}\" \"{tempRegFile}\""); + tempRegFiles.Add(tempRegFile); + } + + PowerShellRuntime.RunCommand(exportCommand.ToString()); + + var exportFile = new StringBuilder(); + + foreach (var tempRegFile in tempRegFiles) + { + exportFile.AppendLine(File.ReadAllText(tempRegFile)); + File.Delete(tempRegFile); + } + + archive.AddEntry("_registry.reg", new MemoryStream(Encoding.UTF8.GetBytes(exportFile.ToString())), true); + } + #endregion + + archive.AddEntry("_manifest.yml", ManifestHelper.GetPath(installDirectory)); + + using (var ms = new MemoryStream()) + { + archive.SaveTo(ms); + + ms.Seek(0, SeekOrigin.Begin); + + var save = Client.UploadSave(manifest.Id.ToString(), ms.ToArray()); + } } } } diff --git a/LANCommander.SDK/Helpers/ManifestHelper.cs b/LANCommander.SDK/Helpers/ManifestHelper.cs index 7e82586..f4e8ed7 100644 --- a/LANCommander.SDK/Helpers/ManifestHelper.cs +++ b/LANCommander.SDK/Helpers/ManifestHelper.cs @@ -16,7 +16,7 @@ namespace LANCommander.SDK.Helpers public static GameManifest Read(string installDirectory) { - var source = Path.Combine(installDirectory, ManifestFilename); + var source = GetPath(installDirectory); var yaml = File.ReadAllText(source); var deserializer = new DeserializerBuilder() @@ -32,7 +32,7 @@ namespace LANCommander.SDK.Helpers public static void Write(GameManifest manifest, string installDirectory) { - var destination = Path.Combine(installDirectory, ManifestFilename); + var destination = GetPath(installDirectory); Logger.LogTrace("Attempting to write manifest to path {Destination}", destination); @@ -48,5 +48,10 @@ namespace LANCommander.SDK.Helpers File.WriteAllText(destination, yaml); } + + public static string GetPath(string installDirectory) + { + return Path.Combine(installDirectory, ManifestFilename); + } } } diff --git a/LANCommander.SDK/LANCommander.SDK.csproj b/LANCommander.SDK/LANCommander.SDK.csproj index 11a21d2..7130409 100644 --- a/LANCommander.SDK/LANCommander.SDK.csproj +++ b/LANCommander.SDK/LANCommander.SDK.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -8,7 +8,7 @@ - + From 5237e886127e1ef3f6d4b04ba25f0bc4e7eb3329 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 10 Nov 2023 20:53:28 -0600 Subject: [PATCH 06/93] Null handling for logger --- LANCommander.SDK/Client.cs | 24 +++++++++---------- LANCommander.SDK/GameManager.cs | 28 +++++++++++----------- LANCommander.SDK/Helpers/ManifestHelper.cs | 8 +++---- LANCommander.SDK/Helpers/RetryHelper.cs | 4 ++-- LANCommander.SDK/Helpers/ScriptHelper.cs | 4 ++-- LANCommander.SDK/PowerShellRuntime.cs | 14 +++++------ LANCommander.SDK/RedistributableManager.cs | 12 +++++----- 7 files changed, 47 insertions(+), 47 deletions(-) diff --git a/LANCommander.SDK/Client.cs b/LANCommander.SDK/Client.cs index 805b0b4..740d828 100644 --- a/LANCommander.SDK/Client.cs +++ b/LANCommander.SDK/Client.cs @@ -146,7 +146,7 @@ namespace LANCommander.SDK public AuthToken RefreshToken(AuthToken token) { - Logger.LogTrace("Refreshing token..."); + Logger?.LogTrace("Refreshing token..."); var request = new RestRequest("/api/Auth/Refresh") .AddJsonBody(token); @@ -173,11 +173,11 @@ namespace LANCommander.SDK public bool ValidateToken(AuthToken token) { - Logger.LogTrace("Validating token..."); + Logger?.LogTrace("Validating token..."); if (token == null) { - Logger.LogTrace("Token is null!"); + Logger?.LogTrace("Token is null!"); return false; } @@ -186,7 +186,7 @@ namespace LANCommander.SDK if (String.IsNullOrEmpty(token.AccessToken) || String.IsNullOrEmpty(token.RefreshToken)) { - Logger.LogTrace("Token is empty!"); + Logger?.LogTrace("Token is empty!"); return false; } @@ -195,9 +195,9 @@ namespace LANCommander.SDK var valid = response.StatusCode == HttpStatusCode.OK; if (valid) - Logger.LogTrace("Token is valid!"); + Logger?.LogTrace("Token is valid!"); else - Logger.LogTrace("Token is invalid!"); + Logger?.LogTrace("Token is invalid!"); return response.StatusCode == HttpStatusCode.OK; } @@ -254,7 +254,7 @@ namespace LANCommander.SDK public GameSave UploadSave(string gameId, byte[] data) { - Logger.LogTrace("Uploading save..."); + Logger?.LogTrace("Uploading save..."); var request = new RestRequest($"/api/Saves/Upload/{gameId}", Method.POST) .AddHeader("Authorization", $"Bearer {Token.AccessToken}"); @@ -273,7 +273,7 @@ namespace LANCommander.SDK public string GetKey(Guid id) { - Logger.LogTrace("Requesting key allocation..."); + Logger?.LogTrace("Requesting key allocation..."); var macAddress = GetMacAddress(); @@ -292,7 +292,7 @@ namespace LANCommander.SDK public string GetAllocatedKey(Guid id) { - Logger.LogTrace("Requesting allocated key..."); + Logger?.LogTrace("Requesting allocated key..."); var macAddress = GetMacAddress(); @@ -314,7 +314,7 @@ namespace LANCommander.SDK public string GetNewKey(Guid id) { - Logger.LogTrace("Requesting new key allocation..."); + Logger?.LogTrace("Requesting new key allocation..."); var macAddress = GetMacAddress(); @@ -336,14 +336,14 @@ namespace LANCommander.SDK public User GetProfile() { - Logger.LogTrace("Requesting player's profile..."); + Logger?.LogTrace("Requesting player's profile..."); return GetRequest("/api/Profile"); } public string ChangeAlias(string alias) { - Logger.LogTrace("Requesting to change player alias..."); + Logger?.LogTrace("Requesting to change player alias..."); var response = PostRequest("/api/Profile/ChangeAlias", alias); diff --git a/LANCommander.SDK/GameManager.cs b/LANCommander.SDK/GameManager.cs index c60f1b1..20ccb8f 100644 --- a/LANCommander.SDK/GameManager.cs +++ b/LANCommander.SDK/GameManager.cs @@ -41,11 +41,11 @@ namespace LANCommander.SDK { var game = Client.GetGame(gameId); - Logger.LogTrace("Installing game {GameTitle} (GameId)", game.Title, game.Id); + Logger?.LogTrace("Installing game {GameTitle} (GameId)", game.Title, game.Id); var result = RetryHelper.RetryOnException(maxAttempts, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () => { - Logger.LogTrace("Attempting to download and extract game"); + Logger?.LogTrace("Attempting to download and extract game"); return DownloadAndExtract(game); }); @@ -61,7 +61,7 @@ namespace LANCommander.SDK var writeManifestSuccess = RetryHelper.RetryOnException(maxAttempts, TimeSpan.FromSeconds(1), false, () => { - Logger.LogTrace("Attempting to get game manifest"); + Logger?.LogTrace("Attempting to get game manifest"); manifest = Client.GetGameManifest(game.Id); @@ -73,7 +73,7 @@ namespace LANCommander.SDK if (!writeManifestSuccess) throw new Exception("Could not grab the manifest file. Retry the install or check your connection"); - Logger.LogTrace("Saving scripts"); + Logger?.LogTrace("Saving scripts"); ScriptHelper.SaveScript(game, ScriptType.Install); ScriptHelper.SaveScript(game, ScriptType.Uninstall); @@ -91,7 +91,7 @@ namespace LANCommander.SDK } catch (Exception ex) { - Logger.LogError(ex, "Could not execute post-install scripts"); + Logger?.LogError(ex, "Could not execute post-install scripts"); } return result.Directory; @@ -103,27 +103,27 @@ namespace LANCommander.SDK try { - Logger.LogTrace("Running uninstall script"); + Logger?.LogTrace("Running uninstall script"); PowerShellRuntime.RunScript(installDirectory, ScriptType.Uninstall); } catch (Exception ex) { - Logger.LogError(ex, "Error running uninstall script"); + Logger?.LogError(ex, "Error running uninstall script"); } - Logger.LogTrace("Attempting to delete the install directory"); + Logger?.LogTrace("Attempting to delete the install directory"); if (Directory.Exists(installDirectory)) Directory.Delete(installDirectory, true); - Logger.LogTrace("Deleted install directory {InstallDirectory}", installDirectory); + Logger?.LogTrace("Deleted install directory {InstallDirectory}", installDirectory); } private ExtractionResult DownloadAndExtract(Game game, string installDirectory = "") { if (game == null) { - Logger.LogTrace("Game failed to download, no game was specified"); + Logger?.LogTrace("Game failed to download, no game was specified"); throw new ArgumentNullException("No game was specified"); } @@ -133,7 +133,7 @@ namespace LANCommander.SDK var destination = Path.Combine(installDirectory, game.Title.SanitizeFilename()); - Logger.LogTrace("Downloading and extracting {Game} to path {Destination}", game.Title, destination); + Logger?.LogTrace("Downloading and extracting {Game} to path {Destination}", game.Title, destination); try { @@ -173,11 +173,11 @@ namespace LANCommander.SDK } else { - Logger.LogError(ex, "Could not extract to path {Destination}", destination); + Logger?.LogError(ex, "Could not extract to path {Destination}", destination); if (Directory.Exists(destination)) { - Logger.LogTrace("Cleaning up orphaned install files after bad install"); + Logger?.LogTrace("Cleaning up orphaned install files after bad install"); Directory.Delete(destination, true); } @@ -196,7 +196,7 @@ namespace LANCommander.SDK extractionResult.Success = true; extractionResult.Directory = destination; - Logger.LogTrace("Game {Game} successfully downloaded and extracted to {Destination}", game.Title, destination); + Logger?.LogTrace("Game {Game} successfully downloaded and extracted to {Destination}", game.Title, destination); } return extractionResult; diff --git a/LANCommander.SDK/Helpers/ManifestHelper.cs b/LANCommander.SDK/Helpers/ManifestHelper.cs index f4e8ed7..accda4c 100644 --- a/LANCommander.SDK/Helpers/ManifestHelper.cs +++ b/LANCommander.SDK/Helpers/ManifestHelper.cs @@ -23,7 +23,7 @@ namespace LANCommander.SDK.Helpers .WithNamingConvention(PascalCaseNamingConvention.Instance) .Build(); - Logger.LogTrace("Deserializing manifest"); + Logger?.LogTrace("Deserializing manifest"); var manifest = deserializer.Deserialize(source); @@ -34,17 +34,17 @@ namespace LANCommander.SDK.Helpers { var destination = GetPath(installDirectory); - Logger.LogTrace("Attempting to write manifest to path {Destination}", destination); + Logger?.LogTrace("Attempting to write manifest to path {Destination}", destination); var serializer = new SerializerBuilder() .WithNamingConvention(PascalCaseNamingConvention.Instance) .Build(); - Logger.LogTrace("Serializing manifest"); + Logger?.LogTrace("Serializing manifest"); var yaml = serializer.Serialize(manifest); - Logger.LogTrace("Writing manifest file"); + Logger?.LogTrace("Writing manifest file"); File.WriteAllText(destination, yaml); } diff --git a/LANCommander.SDK/Helpers/RetryHelper.cs b/LANCommander.SDK/Helpers/RetryHelper.cs index 94389ac..4a912eb 100644 --- a/LANCommander.SDK/Helpers/RetryHelper.cs +++ b/LANCommander.SDK/Helpers/RetryHelper.cs @@ -16,14 +16,14 @@ namespace LANCommander.SDK.Helpers { try { - Logger.LogTrace($"Attempt #{attempts + 1}/{maxAttempts}..."); + Logger?.LogTrace($"Attempt #{attempts + 1}/{maxAttempts}..."); attempts++; return action(); } catch (Exception ex) { - Logger.LogError(ex, $"Attempt failed!"); + Logger?.LogError(ex, $"Attempt failed!"); if (attempts >= maxAttempts) return @default; diff --git a/LANCommander.SDK/Helpers/ScriptHelper.cs b/LANCommander.SDK/Helpers/ScriptHelper.cs index 2a6e926..3232e57 100644 --- a/LANCommander.SDK/Helpers/ScriptHelper.cs +++ b/LANCommander.SDK/Helpers/ScriptHelper.cs @@ -20,7 +20,7 @@ namespace LANCommander.SDK.Helpers // PowerShell will only run scripts with the .ps1 file extension File.Move(tempPath, tempPath + ".ps1"); - Logger.LogTrace("Writing script {Script} to {Destination}", script.Name, tempPath); + Logger?.LogTrace("Writing script {Script} to {Destination}", script.Name, tempPath); File.WriteAllText(tempPath, script.Contents); @@ -42,7 +42,7 @@ namespace LANCommander.SDK.Helpers if (File.Exists(filename)) File.Delete(filename); - Logger.LogTrace("Writing {ScriptType} script to {Destination}", type, filename); + Logger?.LogTrace("Writing {ScriptType} script to {Destination}", type, filename); File.WriteAllText(filename, script.Contents); } diff --git a/LANCommander.SDK/PowerShellRuntime.cs b/LANCommander.SDK/PowerShellRuntime.cs index c9f4514..18d8f8c 100644 --- a/LANCommander.SDK/PowerShellRuntime.cs +++ b/LANCommander.SDK/PowerShellRuntime.cs @@ -24,11 +24,11 @@ namespace LANCommander.SDK public static void RunCommand(string command, bool asAdmin = false) { - Logger.LogTrace($"Executing command `{command}` | Admin: {asAdmin}"); + Logger?.LogTrace($"Executing command `{command}` | Admin: {asAdmin}"); var tempScript = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".ps1"); - Logger.LogTrace($"Creating temp script at path {tempScript}"); + Logger?.LogTrace($"Creating temp script at path {tempScript}"); File.WriteAllText(tempScript, command); @@ -39,7 +39,7 @@ namespace LANCommander.SDK public static int RunScript(string path, bool asAdmin = false, string arguments = null, string workingDirectory = null) { - Logger.LogTrace($"Executing script at path {path} | Admin: {asAdmin} | Arguments: {arguments}"); + Logger?.LogTrace($"Executing script at path {path} | Admin: {asAdmin} | Arguments: {arguments}"); var wow64Value = IntPtr.Zero; @@ -98,7 +98,7 @@ namespace LANCommander.SDK // Concatenate scripts var sb = new StringBuilder(); - Logger.LogTrace("Concatenating scripts..."); + Logger?.LogTrace("Concatenating scripts..."); foreach (var path in paths) { @@ -106,16 +106,16 @@ namespace LANCommander.SDK sb.AppendLine(contents); - Logger.LogTrace($"Added {path}!"); + Logger?.LogTrace($"Added {path}!"); } - Logger.LogTrace("Done concatenating!"); + Logger?.LogTrace("Done concatenating!"); if (sb.Length > 0) { var scriptPath = Path.GetTempFileName(); - Logger.LogTrace($"Creating temp script at path {scriptPath}"); + Logger?.LogTrace($"Creating temp script at path {scriptPath}"); File.WriteAllText(scriptPath, sb.ToString()); diff --git a/LANCommander.SDK/RedistributableManager.cs b/LANCommander.SDK/RedistributableManager.cs index bb4ce9f..7c4a371 100644 --- a/LANCommander.SDK/RedistributableManager.cs +++ b/LANCommander.SDK/RedistributableManager.cs @@ -75,7 +75,7 @@ namespace LANCommander.SDK } catch (Exception ex) { - Logger.LogError(ex, "Redistributable {Redistributable} failed to install", redistributable.Name); + Logger?.LogError(ex, "Redistributable {Redistributable} failed to install", redistributable.Name); } finally { @@ -94,13 +94,13 @@ namespace LANCommander.SDK { if (redistributable == null) { - Logger.LogTrace("Redistributable failed to download! No redistributable was specified"); + Logger?.LogTrace("Redistributable failed to download! No redistributable was specified"); throw new ArgumentNullException("No redistributable was specified"); } var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename()); - Logger.LogTrace("Downloading and extracting {Redistributable} to path {Destination}", redistributable.Name, destination); + Logger?.LogTrace("Downloading and extracting {Redistributable} to path {Destination}", redistributable.Name, destination); try { @@ -134,11 +134,11 @@ namespace LANCommander.SDK } catch (Exception ex) { - Logger.LogError(ex, "Could not extract to path {Destination}", destination); + Logger?.LogError(ex, "Could not extract to path {Destination}", destination); if (Directory.Exists(destination)) { - Logger.LogTrace("Cleaning up orphaned files after bad install"); + Logger?.LogTrace("Cleaning up orphaned files after bad install"); Directory.Delete(destination, true); } @@ -155,7 +155,7 @@ namespace LANCommander.SDK { extractionResult.Success = true; extractionResult.Directory = destination; - Logger.LogTrace("Redistributable {Redistributable} successfully downloaded and extracted to {Destination}", redistributable.Name, destination); + Logger?.LogTrace("Redistributable {Redistributable} successfully downloaded and extracted to {Destination}", redistributable.Name, destination); } return extractionResult; From 20de9d6cae004ebf35849d6206843a3a44be3758 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 10 Nov 2023 20:53:48 -0600 Subject: [PATCH 07/93] Allow injection of loggers --- LANCommander.SDK/Client.cs | 10 +++++++++- LANCommander.SDK/GameManager.cs | 8 +++++++- LANCommander.SDK/RedistributableManager.cs | 8 +++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/LANCommander.SDK/Client.cs b/LANCommander.SDK/Client.cs index 740d828..d7db648 100644 --- a/LANCommander.SDK/Client.cs +++ b/LANCommander.SDK/Client.cs @@ -15,7 +15,7 @@ namespace LANCommander.SDK { public class Client { - private static readonly ILogger Logger; + private readonly ILogger Logger; private readonly RestClient ApiClient; private AuthToken Token; @@ -26,6 +26,14 @@ namespace LANCommander.SDK ApiClient = new RestClient(baseUrl); } + public Client(string baseUrl, ILogger logger) + { + if (!String.IsNullOrWhiteSpace(baseUrl)) + ApiClient = new RestClient(baseUrl); + + Logger = logger; + } + private T PostRequest(string route, object body) { var request = new RestRequest(route) diff --git a/LANCommander.SDK/GameManager.cs b/LANCommander.SDK/GameManager.cs index 20ccb8f..7c858ab 100644 --- a/LANCommander.SDK/GameManager.cs +++ b/LANCommander.SDK/GameManager.cs @@ -15,7 +15,7 @@ namespace LANCommander.SDK { public class GameManager { - private static readonly ILogger Logger; + private readonly ILogger Logger; private Client Client { get; set; } private string DefaultInstallDirectory { get; set; } @@ -30,6 +30,12 @@ namespace LANCommander.SDK Client = client; } + public GameManager(Client client, ILogger logger) + { + Client = client; + Logger = logger; + } + /// /// Downloads, extracts, and runs post-install scripts for the specified game /// diff --git a/LANCommander.SDK/RedistributableManager.cs b/LANCommander.SDK/RedistributableManager.cs index 7c4a371..d714599 100644 --- a/LANCommander.SDK/RedistributableManager.cs +++ b/LANCommander.SDK/RedistributableManager.cs @@ -15,7 +15,7 @@ namespace LANCommander.SDK { public class RedistributableManager { - private static readonly ILogger Logger; + private readonly ILogger Logger; private Client Client { get; set; } public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e); @@ -29,6 +29,12 @@ namespace LANCommander.SDK Client = client; } + public RedistributableManager(Client client, ILogger logger) + { + Client = client; + Logger = logger; + } + public void Install(Game game) { foreach (var redistributable in game.Redistributables) From b77e7f6e5331099fdd21d903ab7cb7aca6df62cb Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 10 Nov 2023 21:36:35 -0600 Subject: [PATCH 08/93] Pass in default install directory to managers --- LANCommander.Playnite.Extension/InstallController.cs | 2 +- LANCommander.Playnite.Extension/UninstallController.cs | 2 +- LANCommander.SDK/GameManager.cs | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/LANCommander.Playnite.Extension/InstallController.cs b/LANCommander.Playnite.Extension/InstallController.cs index 3ec33f7..8eca948 100644 --- a/LANCommander.Playnite.Extension/InstallController.cs +++ b/LANCommander.Playnite.Extension/InstallController.cs @@ -37,7 +37,7 @@ namespace LANCommander.PlaynitePlugin var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress => { - var gameManager = new GameManager(Plugin.LANCommanderClient); + var gameManager = new GameManager(Plugin.LANCommanderClient, Plugin.Settings.InstallDirectory); gameManager.OnArchiveExtractionProgress += (long pos, long len) => { diff --git a/LANCommander.Playnite.Extension/UninstallController.cs b/LANCommander.Playnite.Extension/UninstallController.cs index 93518d0..eb23735 100644 --- a/LANCommander.Playnite.Extension/UninstallController.cs +++ b/LANCommander.Playnite.Extension/UninstallController.cs @@ -23,7 +23,7 @@ namespace LANCommander.PlaynitePlugin { try { - var gameManager = new LANCommander.SDK.GameManager(Plugin.LANCommanderClient); + var gameManager = new LANCommander.SDK.GameManager(Plugin.LANCommanderClient, Plugin.Settings.InstallDirectory); gameManager.Uninstall(Game.InstallDirectory); } diff --git a/LANCommander.SDK/GameManager.cs b/LANCommander.SDK/GameManager.cs index 7c858ab..f0d1e03 100644 --- a/LANCommander.SDK/GameManager.cs +++ b/LANCommander.SDK/GameManager.cs @@ -25,14 +25,16 @@ namespace LANCommander.SDK public delegate void OnArchiveExtractionProgressHandler(long position, long length); public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress; - public GameManager(Client client) + public GameManager(Client client, string defaultInstallDirectory) { Client = client; + DefaultInstallDirectory = defaultInstallDirectory; } - public GameManager(Client client, ILogger logger) + public GameManager(Client client, string defaultInstallDirectory, ILogger logger) { Client = client; + DefaultInstallDirectory = DefaultInstallDirectory; Logger = logger; } @@ -53,7 +55,7 @@ namespace LANCommander.SDK { Logger?.LogTrace("Attempting to download and extract game"); - return DownloadAndExtract(game); + return DownloadAndExtract(game, DefaultInstallDirectory); }); if (!result.Success && !result.Canceled) From 52a5f5866f005bfeb442b4918fab2bd9f8530e95 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 10 Nov 2023 21:37:02 -0600 Subject: [PATCH 09/93] Downgrade YamlDotNet --- LANCommander.Playnite.Extension/packages.config | 2 -- LANCommander.SDK/Helpers/ManifestHelper.cs | 4 ++-- LANCommander.SDK/LANCommander.SDK.csproj | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/LANCommander.Playnite.Extension/packages.config b/LANCommander.Playnite.Extension/packages.config index 48c1616..7ba8b19 100644 --- a/LANCommander.Playnite.Extension/packages.config +++ b/LANCommander.Playnite.Extension/packages.config @@ -4,7 +4,6 @@ - @@ -16,6 +15,5 @@ - \ No newline at end of file diff --git a/LANCommander.SDK/Helpers/ManifestHelper.cs b/LANCommander.SDK/Helpers/ManifestHelper.cs index accda4c..97b773c 100644 --- a/LANCommander.SDK/Helpers/ManifestHelper.cs +++ b/LANCommander.SDK/Helpers/ManifestHelper.cs @@ -20,7 +20,7 @@ namespace LANCommander.SDK.Helpers var yaml = File.ReadAllText(source); var deserializer = new DeserializerBuilder() - .WithNamingConvention(PascalCaseNamingConvention.Instance) + .WithNamingConvention(new PascalCaseNamingConvention()) .Build(); Logger?.LogTrace("Deserializing manifest"); @@ -37,7 +37,7 @@ namespace LANCommander.SDK.Helpers Logger?.LogTrace("Attempting to write manifest to path {Destination}", destination); var serializer = new SerializerBuilder() - .WithNamingConvention(PascalCaseNamingConvention.Instance) + .WithNamingConvention(new PascalCaseNamingConvention()) .Build(); Logger?.LogTrace("Serializing manifest"); diff --git a/LANCommander.SDK/LANCommander.SDK.csproj b/LANCommander.SDK/LANCommander.SDK.csproj index 7130409..e653767 100644 --- a/LANCommander.SDK/LANCommander.SDK.csproj +++ b/LANCommander.SDK/LANCommander.SDK.csproj @@ -8,7 +8,7 @@ - + From ea337dfea16e6ddc7cc59baa598caa8363a1925c Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 10 Nov 2023 21:37:27 -0600 Subject: [PATCH 10/93] Feed actual YAML contents to deserializer --- LANCommander.SDK/Helpers/ManifestHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LANCommander.SDK/Helpers/ManifestHelper.cs b/LANCommander.SDK/Helpers/ManifestHelper.cs index 97b773c..4c09f16 100644 --- a/LANCommander.SDK/Helpers/ManifestHelper.cs +++ b/LANCommander.SDK/Helpers/ManifestHelper.cs @@ -25,7 +25,7 @@ namespace LANCommander.SDK.Helpers Logger?.LogTrace("Deserializing manifest"); - var manifest = deserializer.Deserialize(source); + var manifest = deserializer.Deserialize(yaml); return manifest; } From 47bb054fd1783b0afc86093a5be031447cfe689b Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 10 Nov 2023 21:44:48 -0600 Subject: [PATCH 11/93] Restore progress bar --- LANCommander.Playnite.Extension/InstallController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LANCommander.Playnite.Extension/InstallController.cs b/LANCommander.Playnite.Extension/InstallController.cs index 8eca948..8b7e021 100644 --- a/LANCommander.Playnite.Extension/InstallController.cs +++ b/LANCommander.Playnite.Extension/InstallController.cs @@ -61,7 +61,7 @@ namespace LANCommander.PlaynitePlugin }, new GlobalProgressOptions($"Downloading {Game.Name}...") { - IsIndeterminate = true, + IsIndeterminate = false, Cancelable = true, }); From bb980cc063ccda0ebb04bd1ba15179b5d704720c Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 10 Nov 2023 21:45:09 -0600 Subject: [PATCH 12/93] Avoid exception if manifest is malformed --- LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs index a7da90f..e118e19 100644 --- a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs +++ b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs @@ -441,7 +441,7 @@ namespace LANCommander.PlaynitePlugin public void UpdateGame(SDK.GameManifest manifest) { - var game = PlayniteApi.Database.Games.First(g => g.GameId == manifest.Id.ToString()); + var game = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == manifest?.Id.ToString()); if (game == null) return; From 1ede37c0313c6192572e4b2033a84617e7963439 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Sun, 12 Nov 2023 01:04:05 -0600 Subject: [PATCH 13/93] Simplify game install cancellation. Cancels now happen silently and don't generate a dialog. --- .../InstallController.cs | 15 +++- LANCommander.SDK/EventArgs.cs | 2 - LANCommander.SDK/GameManager.cs | 74 +++++++++++++------ LANCommander.SDK/RedistributableManager.cs | 2 - 4 files changed, 62 insertions(+), 31 deletions(-) diff --git a/LANCommander.Playnite.Extension/InstallController.cs b/LANCommander.Playnite.Extension/InstallController.cs index 8b7e021..6440365 100644 --- a/LANCommander.Playnite.Extension/InstallController.cs +++ b/LANCommander.Playnite.Extension/InstallController.cs @@ -49,9 +49,7 @@ namespace LANCommander.PlaynitePlugin { if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested) { - e.Reader.Cancel(); - e.Reader.Dispose(); - e.Stream.Dispose(); + gameManager.CancelInstall(); progress.IsIndeterminate = true; } @@ -78,6 +76,17 @@ namespace LANCommander.PlaynitePlugin InvokeOnInstalled(new GameInstalledEventArgs(installInfo)); } + else if (result.Canceled) + { + var game = Plugin.PlayniteApi.Database.Games.Get(Game.Id); + + game.IsInstalling = false; + game.IsInstalled = false; + + Plugin.PlayniteApi.Database.Games.Update(game); + } + else if (result.Error != null) + throw result.Error; } } } diff --git a/LANCommander.SDK/EventArgs.cs b/LANCommander.SDK/EventArgs.cs index ca85135..c0af51a 100644 --- a/LANCommander.SDK/EventArgs.cs +++ b/LANCommander.SDK/EventArgs.cs @@ -14,8 +14,6 @@ namespace LANCommander.SDK public class ArchiveEntryExtractionProgressArgs : EventArgs { - public IReader Reader { get; set; } - public TrackableStream Stream { get; set; } public ReaderProgress Progress { get; set; } public IEntry Entry { get; set; } } diff --git a/LANCommander.SDK/GameManager.cs b/LANCommander.SDK/GameManager.cs index f0d1e03..bd99083 100644 --- a/LANCommander.SDK/GameManager.cs +++ b/LANCommander.SDK/GameManager.cs @@ -25,6 +25,9 @@ namespace LANCommander.SDK public delegate void OnArchiveExtractionProgressHandler(long position, long length); public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress; + private TrackableStream Stream; + private IReader Reader; + public GameManager(Client client, string defaultInstallDirectory) { Client = client; @@ -61,7 +64,7 @@ namespace LANCommander.SDK if (!result.Success && !result.Canceled) throw new Exception("Could not extract the installer. Retry the install or check your connection"); else if (result.Canceled) - throw new Exception("Game install was canceled"); + return ""; GameManifest manifest = null; @@ -143,41 +146,62 @@ namespace LANCommander.SDK Logger?.LogTrace("Downloading and extracting {Game} to path {Destination}", game.Title, destination); + var extractionResult = new ExtractionResult + { + Canceled = false, + }; + try { Directory.CreateDirectory(destination); - using (var gameStream = Client.StreamGame(game.Id)) - using (var reader = ReaderFactory.Open(gameStream)) + Stream = Client.StreamGame(game.Id); + Reader = ReaderFactory.Open(Stream); + + Stream.OnProgress += (pos, len) => { - gameStream.OnProgress += (pos, len) => - { - OnArchiveExtractionProgress?.Invoke(pos, len); - }; + OnArchiveExtractionProgress?.Invoke(pos, len); + }; - reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs e) => + Reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs e) => + { + OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs { - OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs - { - Entry = e.Item, - Progress = e.ReaderProgress, - Reader = reader, - Stream = gameStream - }); - }; + Entry = e.Item, + Progress = e.ReaderProgress, + }); + }; - reader.WriteAllToDirectory(destination, new ExtractionOptions() + while (Reader.MoveToNextEntry()) + { + if (Reader.Cancelled) + break; + + Reader.WriteEntryToDirectory(destination, new ExtractionOptions() { ExtractFullPath = true, - Overwrite = true + Overwrite = true, + PreserveFileTime = true, }); } + + Reader.Dispose(); + Stream.Dispose(); } catch (Exception ex) { - if (false) + if (Reader.Cancelled) { + Logger?.LogTrace("User cancelled the download"); + extractionResult.Canceled = true; + + if (Directory.Exists(destination)) + { + Logger?.LogTrace("Cleaning up orphaned files after cancelled install"); + + Directory.Delete(destination, true); + } } else { @@ -194,11 +218,6 @@ namespace LANCommander.SDK } } - var extractionResult = new ExtractionResult - { - Canceled = false, - }; - if (!extractionResult.Canceled) { extractionResult.Success = true; @@ -209,5 +228,12 @@ namespace LANCommander.SDK return extractionResult; } + + public void CancelInstall() + { + Reader?.Cancel(); + // Reader?.Dispose(); + // Stream?.Dispose(); + } } } diff --git a/LANCommander.SDK/RedistributableManager.cs b/LANCommander.SDK/RedistributableManager.cs index d714599..fcc87d8 100644 --- a/LANCommander.SDK/RedistributableManager.cs +++ b/LANCommander.SDK/RedistributableManager.cs @@ -126,8 +126,6 @@ namespace LANCommander.SDK { Entry = e.Item, Progress = e.ReaderProgress, - Reader = reader, - Stream = redistributableStream }); }; From 81e4848407118bc1e0cff8704eb41a728b60b81c Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Sun, 12 Nov 2023 01:26:51 -0600 Subject: [PATCH 14/93] Include download percentage in dialog --- LANCommander.Playnite.Extension/InstallController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/LANCommander.Playnite.Extension/InstallController.cs b/LANCommander.Playnite.Extension/InstallController.cs index 6440365..6ea34e3 100644 --- a/LANCommander.Playnite.Extension/InstallController.cs +++ b/LANCommander.Playnite.Extension/InstallController.cs @@ -41,8 +41,11 @@ namespace LANCommander.PlaynitePlugin gameManager.OnArchiveExtractionProgress += (long pos, long len) => { + var percent = Math.Ceiling((pos / (decimal)len) * 100); + progress.ProgressMaxValue = len; progress.CurrentProgressValue = pos; + progress.Text = $"Downloading {Game.Name} ({percent}%)"; }; gameManager.OnArchiveEntryExtractionProgress += (object sender, ArchiveEntryExtractionProgressArgs e) => @@ -57,7 +60,7 @@ namespace LANCommander.PlaynitePlugin installDirectory = gameManager.Install(gameId); }, - new GlobalProgressOptions($"Downloading {Game.Name}...") + new GlobalProgressOptions($"Preparing to download {Game.Name}") { IsIndeterminate = false, Cancelable = true, From 6f7c17493cc9abb84c6a1e17e346d563a5e13a19 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Sun, 12 Nov 2023 01:27:15 -0600 Subject: [PATCH 15/93] Install redistributables after game is installed --- .../InstallController.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/LANCommander.Playnite.Extension/InstallController.cs b/LANCommander.Playnite.Extension/InstallController.cs index 6ea34e3..954b9e3 100644 --- a/LANCommander.Playnite.Extension/InstallController.cs +++ b/LANCommander.Playnite.Extension/InstallController.cs @@ -5,6 +5,7 @@ using Playnite.SDK; using Playnite.SDK.Models; using Playnite.SDK.Plugins; using System; +using System.Linq; namespace LANCommander.PlaynitePlugin { @@ -66,6 +67,24 @@ namespace LANCommander.PlaynitePlugin Cancelable = true, }); + // Install any redistributables + var game = Plugin.LANCommanderClient.GetGame(gameId); + + if (game.Redistributables != null && game.Redistributables.Count() > 0) + { + Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress => + { + var redistributableManager = new RedistributableManager(Plugin.LANCommanderClient); + + redistributableManager.Install(game); + }, + new GlobalProgressOptions("Installing redistributables...") + { + IsIndeterminate = true, + Cancelable = false, + }); + } + if (!result.Canceled && result.Error == null && !String.IsNullOrWhiteSpace(installDirectory)) { var manifest = ManifestHelper.Read(installDirectory); @@ -81,12 +100,12 @@ namespace LANCommander.PlaynitePlugin } else if (result.Canceled) { - var game = Plugin.PlayniteApi.Database.Games.Get(Game.Id); + var dbGame = Plugin.PlayniteApi.Database.Games.Get(Game.Id); - game.IsInstalling = false; - game.IsInstalled = false; + dbGame.IsInstalling = false; + dbGame.IsInstalled = false; - Plugin.PlayniteApi.Database.Games.Update(game); + Plugin.PlayniteApi.Database.Games.Update(dbGame); } else if (result.Error != null) throw result.Error; From 7c97a3db5768c7ee29416674188b536a308ebd3c Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Sun, 12 Nov 2023 01:50:34 -0600 Subject: [PATCH 16/93] Include download speed in progress dialog --- .../InstallController.cs | 28 ++++++++++++++++--- .../LANCommander.PlaynitePlugin.csproj | 3 ++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/LANCommander.Playnite.Extension/InstallController.cs b/LANCommander.Playnite.Extension/InstallController.cs index 954b9e3..7c7bb86 100644 --- a/LANCommander.Playnite.Extension/InstallController.cs +++ b/LANCommander.Playnite.Extension/InstallController.cs @@ -5,6 +5,7 @@ using Playnite.SDK; using Playnite.SDK.Models; using Playnite.SDK.Plugins; using System; +using System.Diagnostics; using System.Linq; namespace LANCommander.PlaynitePlugin @@ -40,13 +41,30 @@ namespace LANCommander.PlaynitePlugin { var gameManager = new GameManager(Plugin.LANCommanderClient, Plugin.Settings.InstallDirectory); + Stopwatch stopwatch = new Stopwatch(); + + stopwatch.Start(); + + var lastTotalSize = 0d; + var speed = 0d; + gameManager.OnArchiveExtractionProgress += (long pos, long len) => { - var percent = Math.Ceiling((pos / (decimal)len) * 100); + if (stopwatch.ElapsedMilliseconds > 500) + { + var percent = Math.Ceiling((pos / (decimal)len) * 100); - progress.ProgressMaxValue = len; - progress.CurrentProgressValue = pos; - progress.Text = $"Downloading {Game.Name} ({percent}%)"; + progress.ProgressMaxValue = len; + progress.CurrentProgressValue = pos; + + speed = (double)(progress.CurrentProgressValue - lastTotalSize) / (stopwatch.ElapsedMilliseconds / 1000d); + + progress.Text = $"Downloading {Game.Name} ({percent}%) | {ByteSizeLib.ByteSize.FromBytes(speed).ToString("#.#")}/s"; + + lastTotalSize = pos; + + stopwatch.Restart(); + } }; gameManager.OnArchiveEntryExtractionProgress += (object sender, ArchiveEntryExtractionProgressArgs e) => @@ -60,6 +78,8 @@ namespace LANCommander.PlaynitePlugin }; installDirectory = gameManager.Install(gameId); + + stopwatch.Stop(); }, new GlobalProgressOptions($"Preparing to download {Game.Name}") { diff --git a/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj b/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj index e8bed07..38dff6c 100644 --- a/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj +++ b/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj @@ -34,6 +34,9 @@ ..\packages\rix0rrr.BeaconLib.1.0.2\lib\net40\BeaconLib.dll + + ..\packages\ByteSize.2.1.1\lib\net45\ByteSize.dll + ..\packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll From 227411a558fe1c512cd31e886048e93ffe75df0a Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Sun, 12 Nov 2023 01:52:55 -0600 Subject: [PATCH 17/93] Include ByteSize --- LANCommander.Playnite.Extension/packages.config | 1 + 1 file changed, 1 insertion(+) diff --git a/LANCommander.Playnite.Extension/packages.config b/LANCommander.Playnite.Extension/packages.config index 7ba8b19..5a9ca11 100644 --- a/LANCommander.Playnite.Extension/packages.config +++ b/LANCommander.Playnite.Extension/packages.config @@ -1,5 +1,6 @@  + From 5fb4fadfb4c094404bdcf818659b7d00710cab9d Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Sun, 12 Nov 2023 02:10:04 -0600 Subject: [PATCH 18/93] Fix default install directory when logger is provided --- LANCommander.SDK/GameManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LANCommander.SDK/GameManager.cs b/LANCommander.SDK/GameManager.cs index bd99083..056b563 100644 --- a/LANCommander.SDK/GameManager.cs +++ b/LANCommander.SDK/GameManager.cs @@ -37,7 +37,7 @@ namespace LANCommander.SDK public GameManager(Client client, string defaultInstallDirectory, ILogger logger) { Client = client; - DefaultInstallDirectory = DefaultInstallDirectory; + DefaultInstallDirectory = defaultInstallDirectory; Logger = logger; } From ee62bdf2a1329b943cc382bc393d767c13042ed5 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Sun, 12 Nov 2023 02:20:24 -0600 Subject: [PATCH 19/93] Updated README --- README.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1bd572a..5ab6fee 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,36 @@ Currently only Windows is supported. This may change in the future and a Docker ## FAQ ### How do I get games? -Do you have a peg leg and a parrot? There is no DRM implementation in LANCommander. The best games are either portable games or DRM-free games. Freeware, shareware, abandonware are all great available options. LANCommander is just a management/distribution system. It does not come bundled with any games. +The best games are either portable games or DRM-free games. Freeware, shareware, abandonware are all great available options. LANCommander is only a management/distribution system. It does not come bundled with any games. ### I have a pretty large LAN party planned with hundreds of players. I have some sick infrastructure and a LAN cache. What do? LANCommander communicates over HTTP(S). There is no LAN cache configuration provided, but all downloads are provided through the `/api/Games/{id}/Download` route. +### Where can I get some help? +Some documentation lives at the [Wiki](https://lancommander.app/index.php/Main_Page) including a [Getting Started](https://lancommander.app/index.php/Tutorials:Getting_Started) guide and a category for [Tutorials](https://lancommander.app/index.php/Category:Tutorials). It also contains a large library of sample configurations for [Games](https://lancommander.app/index.php/Category:Games) and [Redistributables](https://lancommander.app/index.php/Category:Redistributables). + ### How do I contribute? Hit that fork button, submit a PR, there are no hard rules right now. +If you're not a developer but still want to contribute, writing documentation in the wiki is a great way to give back to the community! + +The LANCommander dev team is currently spearheaded by one developer in their free time. If you feel compelled, [donations] are always appreciated. + +## SDK +A separate assembly called `LANCommander.SDK` has been created for use in client applications. The offical Playnite add-on utilizes this assembly to handle the authentication, download, install, and uninstall of entries from a LANCommander server. Here is a quick example of how one can authenticate to a LANCommander server and install a game to `C:\Games`: + +```csharp +var client = new LANCommander.SDK.Client(); + +await client.AuthenticateAsync("username", "password"); + +var gameManager = new LANCommander.SDK.GameManager(client, "C:\\Games"); + +var gameId = "114f653d-ea91-484b-8fe9-8e9bb58bde81"; + +gameManager.Install(gameId); +``` + ## To Do LANCommander is far from complete. The basic implementation that exists will allow you to: @@ -34,10 +56,13 @@ LANCommander is far from complete. The basic implementation that exists will all - New user registration - Local "cloud" user saves - Game patching + - Dedicated server management/administration + - Redistributable management and distribution + - IPX Beacon for emulators such as DosBox + - Game media management and automatic lookup (covers, icons, backgrounds) The following features are being considered: - - Dedicated server management/administration - Linux build - Some expansion of the dashboard with useful stats - - Built-in VPN client/server for remote LAN parties + - Built-in VPN client/server for remote LAN parties \ No newline at end of file From ae23f621c2b541a9d875a7351cdc3796a350159a Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Sun, 12 Nov 2023 02:20:47 -0600 Subject: [PATCH 20/93] Fix link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ab6fee..8ea4fd1 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Hit that fork button, submit a PR, there are no hard rules right now. If you're not a developer but still want to contribute, writing documentation in the wiki is a great way to give back to the community! -The LANCommander dev team is currently spearheaded by one developer in their free time. If you feel compelled, [donations] are always appreciated. +The LANCommander dev team is currently spearheaded by one developer in their free time. If you feel compelled, [donations](https://www.paypal.com/donate/?business=LBJW6PFMFLULA&no_recurring=0¤cy_code=USD) are always appreciated. ## SDK A separate assembly called `LANCommander.SDK` has been created for use in client applications. The offical Playnite add-on utilizes this assembly to handle the authentication, download, install, and uninstall of entries from a LANCommander server. Here is a quick example of how one can authenticate to a LANCommander server and install a game to `C:\Games`: From dc2eff497249708f37425a34126776c3149e9dfa Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Mon, 13 Nov 2023 23:07:50 -0600 Subject: [PATCH 21/93] Have manifest writer return the file path --- LANCommander.SDK/Helpers/ManifestHelper.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/LANCommander.SDK/Helpers/ManifestHelper.cs b/LANCommander.SDK/Helpers/ManifestHelper.cs index 4c09f16..9f87cef 100644 --- a/LANCommander.SDK/Helpers/ManifestHelper.cs +++ b/LANCommander.SDK/Helpers/ManifestHelper.cs @@ -30,7 +30,7 @@ namespace LANCommander.SDK.Helpers return manifest; } - public static void Write(GameManifest manifest, string installDirectory) + public static string Write(GameManifest manifest, string installDirectory) { var destination = GetPath(installDirectory); @@ -47,6 +47,8 @@ namespace LANCommander.SDK.Helpers Logger?.LogTrace("Writing manifest file"); File.WriteAllText(destination, yaml); + + return destination; } public static string GetPath(string installDirectory) From baa2b9b20692ffefdfc488f9510c6387677f370e Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Mon, 13 Nov 2023 23:09:06 -0600 Subject: [PATCH 22/93] Started adding PowerShell cmdlets useful for LANCommander scripting --- .../Cmdlets/Convert-AspectRatio.cs | 45 ++++++++++++ .../Cmdlets/ConvertTo-StringBytes.cs | 40 +++++++++++ .../Cmdlets/Edit-PatchBinary.cs | 30 ++++++++ .../Cmdlets/Get-GameManifest.cs | 19 +++++ .../Cmdlets/Get-PrimaryDisplay.cs | 18 +++++ .../Cmdlets/Write-GameManifest.cs | 24 +++++++ .../Cmdlets/Write-ReplaceContentInFile.cs | 31 ++++++++ .../LANCommander.PowerShell.csproj | 68 ++++++++++++++++++ .../LANCommander.PowerShell.psd1 | Bin 0 -> 8022 bytes .../Properties/AssemblyInfo.cs | 36 ++++++++++ LANCommander.PowerShell/packages.config | 4 ++ LANCommander.sln | 6 ++ 12 files changed, 321 insertions(+) create mode 100644 LANCommander.PowerShell/Cmdlets/Convert-AspectRatio.cs create mode 100644 LANCommander.PowerShell/Cmdlets/ConvertTo-StringBytes.cs create mode 100644 LANCommander.PowerShell/Cmdlets/Edit-PatchBinary.cs create mode 100644 LANCommander.PowerShell/Cmdlets/Get-GameManifest.cs create mode 100644 LANCommander.PowerShell/Cmdlets/Get-PrimaryDisplay.cs create mode 100644 LANCommander.PowerShell/Cmdlets/Write-GameManifest.cs create mode 100644 LANCommander.PowerShell/Cmdlets/Write-ReplaceContentInFile.cs create mode 100644 LANCommander.PowerShell/LANCommander.PowerShell.csproj create mode 100644 LANCommander.PowerShell/LANCommander.PowerShell.psd1 create mode 100644 LANCommander.PowerShell/Properties/AssemblyInfo.cs create mode 100644 LANCommander.PowerShell/packages.config diff --git a/LANCommander.PowerShell/Cmdlets/Convert-AspectRatio.cs b/LANCommander.PowerShell/Cmdlets/Convert-AspectRatio.cs new file mode 100644 index 0000000..5bb019d --- /dev/null +++ b/LANCommander.PowerShell/Cmdlets/Convert-AspectRatio.cs @@ -0,0 +1,45 @@ +using System; +using System.Management.Automation; + +namespace LANCommander.PowerShell.Cmdlets +{ + public class DisplayResolution + { + public int Width { get; set; } + public int Height { get; set; } + } + + [Cmdlet(VerbsData.Convert, "AspectRatio")] + [OutputType(typeof(string))] + public class ConvertAspectRatioCmdlet : PSCmdlet + { + [Parameter(Mandatory = true, Position = 0)] + public int Width { get; set; } + + [Parameter(Mandatory = true, Position = 1)] + public int Height { get; set; } + + [Parameter(Mandatory = true, Position = 2)] + public double AspectRatio { get; set; } + + protected override void ProcessRecord() + { + var resolution = new DisplayResolution(); + + // Display is wider, pillar box + if ((Width / Height) < AspectRatio) + { + resolution.Width = (int)Math.Ceiling(Height * AspectRatio); + resolution.Height = Height; + } + // Letterbox + else + { + resolution.Width = Width; + resolution.Height = (int)Math.Ceiling(Width * (1 / AspectRatio)); + } + + WriteObject(resolution); + } + } +} diff --git a/LANCommander.PowerShell/Cmdlets/ConvertTo-StringBytes.cs b/LANCommander.PowerShell/Cmdlets/ConvertTo-StringBytes.cs new file mode 100644 index 0000000..87b3c09 --- /dev/null +++ b/LANCommander.PowerShell/Cmdlets/ConvertTo-StringBytes.cs @@ -0,0 +1,40 @@ +using LANCommander.SDK; +using LANCommander.SDK.Helpers; +using System.Management.Automation; + +namespace LANCommander.PowerShell.Cmdlets +{ + [Cmdlet(VerbsData.ConvertTo, "StringBytes")] + [OutputType(typeof(byte[]))] + public class ConvertToStringBytesCmdlet : PSCmdlet + { + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + public string Input { get; set; } + + [Parameter] + public bool Utf16 { get; set; } = false; + + [Parameter] + public bool BigEndian { get; set; } = false; + + [Parameter] + public int MaxLength { get; set; } = 0; + + protected override void ProcessRecord() + { + byte[] output; + + if (MaxLength > 0 && Input.Length > MaxLength) + Input = Input.Substring(0, MaxLength); + + if (Utf16 && BigEndian) + output = System.Text.Encoding.BigEndianUnicode.GetBytes(Input); + else if (Utf16) + output = System.Text.Encoding.Unicode.GetBytes(Input); + else + output = System.Text.Encoding.ASCII.GetBytes(Input); + + WriteObject(output); + } + } +} diff --git a/LANCommander.PowerShell/Cmdlets/Edit-PatchBinary.cs b/LANCommander.PowerShell/Cmdlets/Edit-PatchBinary.cs new file mode 100644 index 0000000..0e73580 --- /dev/null +++ b/LANCommander.PowerShell/Cmdlets/Edit-PatchBinary.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; +using System.Management.Automation; + +namespace LANCommander.PowerShell.Cmdlets +{ + [Cmdlet(VerbsData.Edit, "PatchBinary")] + [OutputType(typeof(string))] + public class EditPatchBinaryCmdlet : PSCmdlet + { + [Parameter(Mandatory = true, Position = 0)] + public long Offset { get; set; } + + [Parameter(Mandatory = true, Position = 1)] + public byte[] Data { get; set; } + + [Parameter(Mandatory = true, Position = 2)] + public string FilePath { get; set; } + + protected override void ProcessRecord() + { + using (var writer = File.OpenWrite(FilePath)) + { + writer.Seek(Offset, SeekOrigin.Begin); + + writer.Write(Data, 0, Data.Length); + } + } + } +} diff --git a/LANCommander.PowerShell/Cmdlets/Get-GameManifest.cs b/LANCommander.PowerShell/Cmdlets/Get-GameManifest.cs new file mode 100644 index 0000000..b012bd5 --- /dev/null +++ b/LANCommander.PowerShell/Cmdlets/Get-GameManifest.cs @@ -0,0 +1,19 @@ +using LANCommander.SDK; +using LANCommander.SDK.Helpers; +using System.Management.Automation; + +namespace LANCommander.PowerShell.Cmdlets +{ + [Cmdlet(VerbsCommon.Get, "GameManifest")] + [OutputType(typeof(GameManifest))] + public class GetGameManifestCmdlet : PSCmdlet + { + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + public string Path { get; set; } + + protected override void ProcessRecord() + { + WriteObject(ManifestHelper.Read(Path)); + } + } +} diff --git a/LANCommander.PowerShell/Cmdlets/Get-PrimaryDisplay.cs b/LANCommander.PowerShell/Cmdlets/Get-PrimaryDisplay.cs new file mode 100644 index 0000000..0b53547 --- /dev/null +++ b/LANCommander.PowerShell/Cmdlets/Get-PrimaryDisplay.cs @@ -0,0 +1,18 @@ +using System.Linq; +using System.Management.Automation; +using System.Windows.Forms; + +namespace LANCommander.PowerShell.Cmdlets +{ + [Cmdlet(VerbsCommon.Get, "PrimaryDisplay")] + [OutputType(typeof(string))] + public class GetPrimaryDisplayCmdlet : PSCmdlet + { + protected override void ProcessRecord() + { + var screens = Screen.AllScreens; + + WriteObject(screens.First(s => s.Primary)); + } + } +} diff --git a/LANCommander.PowerShell/Cmdlets/Write-GameManifest.cs b/LANCommander.PowerShell/Cmdlets/Write-GameManifest.cs new file mode 100644 index 0000000..6d9ad0c --- /dev/null +++ b/LANCommander.PowerShell/Cmdlets/Write-GameManifest.cs @@ -0,0 +1,24 @@ +using LANCommander.SDK; +using LANCommander.SDK.Helpers; +using System.Management.Automation; + +namespace LANCommander.PowerShell.Cmdlets +{ + [Cmdlet(VerbsCommunications.Write, "GameManifest")] + [OutputType(typeof(string))] + public class WriteGameManifestCmdlet : PSCmdlet + { + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + public string Path { get; set; } + + [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + public GameManifest Manifest { get; set; } + + protected override void ProcessRecord() + { + var destination = ManifestHelper.Write(Manifest, Path); + + WriteObject(destination); + } + } +} diff --git a/LANCommander.PowerShell/Cmdlets/Write-ReplaceContentInFile.cs b/LANCommander.PowerShell/Cmdlets/Write-ReplaceContentInFile.cs new file mode 100644 index 0000000..32cda9c --- /dev/null +++ b/LANCommander.PowerShell/Cmdlets/Write-ReplaceContentInFile.cs @@ -0,0 +1,31 @@ +using System.IO; +using System.Management.Automation; +using System.Text.RegularExpressions; + +namespace LANCommander.PowerShell.Cmdlets +{ + + [Cmdlet(VerbsCommunications.Write, "ReplaceContentInFile")] + [OutputType(typeof(string))] + public class ReplaceContentInFileCmdlet : PSCmdlet + { + [Parameter(Mandatory = true, Position = 0)] + public string Pattern { get; set; } + + [Parameter(Mandatory = true, Position = 1)] + public string Substitution { get; set; } + + [Parameter(Mandatory = true, Position = 2)] + public string FilePath { get; set; } + + protected override void ProcessRecord() + { + var contents = File.ReadAllText(FilePath); + var regex = new Regex(Pattern, RegexOptions.Multiline); + + var result = regex.Replace(contents, Substitution); + + WriteObject(result); + } + } +} diff --git a/LANCommander.PowerShell/LANCommander.PowerShell.csproj b/LANCommander.PowerShell/LANCommander.PowerShell.csproj new file mode 100644 index 0000000..515ee5d --- /dev/null +++ b/LANCommander.PowerShell/LANCommander.PowerShell.csproj @@ -0,0 +1,68 @@ + + + + + Debug + AnyCPU + {807943BF-0C7D-4ED3-8393-CFEE64E3138C} + Library + Properties + LANCommander.PowerShell + LANCommander.PowerShell + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + ..\packages\PowerShellStandard.Library.5.1.1\lib\net452\System.Management.Automation.dll + + + + + + + + + + + + + + + + + + + + + + + + + + {4c2a71fd-a30b-4d62-888a-4ef843d8e506} + LANCommander.SDK + + + + \ No newline at end of file diff --git a/LANCommander.PowerShell/LANCommander.PowerShell.psd1 b/LANCommander.PowerShell/LANCommander.PowerShell.psd1 new file mode 100644 index 0000000000000000000000000000000000000000..52b16e5c1381d6e249d47a7eb3b44108dc4b9778 GIT binary patch literal 8022 zcmd^^TTdKE5QY0WQvSopJb(8a|dH5_Wg(5r;Ql* zPx^X4jc>~1mcwpng%@E%Z-=v4UCo(>)v%{$7xDfe{Hp7T{+4I^`L0j(d=$sDwM$P3 zif|O(h3|CT69#sErt4I9CkxCSYc=Ms>F>Ti5B2}6@Iar3rP=KMB>WMdIFf_V(Jq59 ziF*z+ZJ6XodOM0(-f4$?L|^Yrb8YGCRI@vJl4QEjw=-#>i2nx~QPuHk#IvutV~xt~ z@x8`hSJ$i3X41LK`6_DrRCv%;q0hNwIf_pUJ?qb5Ox8AhWnA-X8Ok-;{=v-BzUzT@ zndlEqx}EpJLD&*TG-GH^XErKke40cJ4ulTf){QR$dp-#(;cjNlov^9r=hCxr>P$QH z9ZvtQD{RDy1MLq3LC}v+u#(#+$&MUP!^>dnnw$^Bsjlq&Fnle9>)~G9>ruR0*RyZq z-D8cgx9hrF)910qb@cg0(yfV^<`b@|me7I0ICqsjn30b*TvflxcQ9W?x1%Vr?er$< zp}XL|`5G^@)==}!8qGYd;+O~lUUw{s@pWR2c@67fmlS!LEd!eQl}r~a()3-e;W41N zB3>2JCjL^C5aFj&A%REJfR4IicP__fe4nC+dsea?n(0UObtI9!Z)+!*4+|$+d#u%s zjUEH@SdnvaNwb>vHOX^~&$Tw(9}2}#bCF~!sp_%*6i35_3ymQXS0j0$6-Kff>mnao z!m|ePZc!QPR<4gKxjcz+xt140j(1sB`*f(E&DKoF{wX`$rZkJ#Vli$p3Lio9*bL8J zRpqc14t2M!Hx@%K^mZCvUz<^@nsIm;yYVO(?wo|_2e4kFUj=Q7%sKG3+pGZRd^IX^CDAzPb zAmWe3>J??#-LN0yW6~6NAM)ORpRP@kMRd9+R#2T0(_tbtE%jw5atDqOqt7FE3e^=a zqCR`!OO5+EJW|%?t|c9Ib-#WC?KwZHNLr0J>SIaD32+)7Sj9|~xA;a~2j_=#wHHy_ zY9eRPvDiCSrGj5rY$qR~t5M{ON~$fQ^s;8(4(C!dF5^E{rWh;Emm@(F?+z8yCsB@incqg+Qe&Dwn8)Sp zyNH>^bdWr8F>mqxuY-9zdMG`-)z8+O=C-{wLEJ}xtO>Mrhb6@l3yYKs1FJ5Ji$8$2h}+c6`jo&ek?Z!f$R zM&hhl5T;VM<2}{LN=EW32L4&>N5<*wxt-^b^%zTKkN%Lw{C~ksrIb@_VWg-)+`qjH9%MGn(8%VUS8EE zw%W=xS#O68q2sIsiRyO%$^X>`oeyfq^q*^SQQg0aY~!rVPI+#sf4s1|Ry~U4@WosY z-;(vgK%ak81pi$xv|i~d?HB`cE!OM4TG?%?`q+@KTb(nGwYSyhH;aZIk2ht>POLVm z^Qq`<)kSgb#z;$qv|M8KN`CSSS2#)QsAIK0i)dMY+MDu|BP?f$v|D|QO7C+t<*MZ7 zNVa3_@2MXoyC8Et3Tmc7?4B+5cO^9^c5W#UP^Jrn?<3r4e27vy*?}FEZ`ftgOR>wI(z2#jQUdr^O{_z36)tO z{Yv2-MUK5`l#gE0-iJbU9Iet8TPHErS=DnEHB?_!xL%2fWMumdhqI5w@7%6v37_LE zlYDL&p1RjgN6?{`Z&KbzNODgdHt@gNji2t5jrBkXeN(W zOij|`rM|LX*#RF(>%5E};V0kel+tJT^=fRe*4W7}W=BHk=T%jd^s?jdo5oQ&HfzrM z8z1*^IH?2a;LMjSR)fR1fp>wvx%Y2IxieeyI{4^!sn#qbaY92hBI=~7zgi_X*|T{y z7`I%i6x^2Y^&ER{NoL}`?V9^#HKwpXpQhR6$eN-+^Ex1S7Bw-^?^2^!=^OWVE-}?} z8x(c3@f+sO%gtDRZRm%#^5}`QZNFLYWT5&wbsv5PX?+70DR(j!b3Rl_6L)@`MG$mC zw}CI)=`7!xaS(N#i9509upa7O*25M0tq$*S{Kn3zlpC^a`c=C;qpG9#m%l&$4TZ`X AjsO4v literal 0 HcmV?d00001 diff --git a/LANCommander.PowerShell/Properties/AssemblyInfo.cs b/LANCommander.PowerShell/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..87468b6 --- /dev/null +++ b/LANCommander.PowerShell/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("LANCommander.PowerShell")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("LANCommander.PowerShell")] +[assembly: AssemblyCopyright("Copyright © 2023")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("807943bf-0c7d-4ed3-8393-cfee64e3138c")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/LANCommander.PowerShell/packages.config b/LANCommander.PowerShell/packages.config new file mode 100644 index 0000000..411c02f --- /dev/null +++ b/LANCommander.PowerShell/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/LANCommander.sln b/LANCommander.sln index 529f8cc..5f6fef2 100644 --- a/LANCommander.sln +++ b/LANCommander.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LANCommander.SDK", "LANComm EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LANCommander.PCGamingWiki", "LANCommander.PCGamingWiki\LANCommander.PCGamingWiki.csproj", "{2436B817-4475-4E70-9BB2-E1E7866DB79F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LANCommander.PowerShell", "LANCommander.PowerShell\LANCommander.PowerShell.csproj", "{807943BF-0C7D-4ED3-8393-CFEE64E3138C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {2436B817-4475-4E70-9BB2-E1E7866DB79F}.Debug|Any CPU.Build.0 = Debug|Any CPU {2436B817-4475-4E70-9BB2-E1E7866DB79F}.Release|Any CPU.ActiveCfg = Release|Any CPU {2436B817-4475-4E70-9BB2-E1E7866DB79F}.Release|Any CPU.Build.0 = Release|Any CPU + {807943BF-0C7D-4ED3-8393-CFEE64E3138C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {807943BF-0C7D-4ED3-8393-CFEE64E3138C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {807943BF-0C7D-4ED3-8393-CFEE64E3138C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {807943BF-0C7D-4ED3-8393-CFEE64E3138C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 29dcebb70f128e8c42a492865fb0e2057357a66f Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Wed, 15 Nov 2023 22:38:20 -0600 Subject: [PATCH 23/93] New PowerShell runtime with the ability to use variables --- LANCommander.SDK/Helpers/ScriptHelper.cs | 13 +- LANCommander.SDK/LANCommander.SDK.csproj | 1 + .../PowerShell/PowerShellArgument.cs | 20 +++ .../PowerShell/PowerShellFactory.cs | 14 ++ .../PowerShell/PowerShellScript.cs | 169 ++++++++++++++++++ .../PowerShell/PowerShellVariable.cs | 20 +++ 6 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 LANCommander.SDK/PowerShell/PowerShellArgument.cs create mode 100644 LANCommander.SDK/PowerShell/PowerShellFactory.cs create mode 100644 LANCommander.SDK/PowerShell/PowerShellScript.cs create mode 100644 LANCommander.SDK/PowerShell/PowerShellVariable.cs diff --git a/LANCommander.SDK/Helpers/ScriptHelper.cs b/LANCommander.SDK/Helpers/ScriptHelper.cs index 3232e57..dd27f7e 100644 --- a/LANCommander.SDK/Helpers/ScriptHelper.cs +++ b/LANCommander.SDK/Helpers/ScriptHelper.cs @@ -14,15 +14,24 @@ namespace LANCommander.SDK.Helpers public static readonly ILogger Logger; public static string SaveTempScript(Script script) + { + var tempPath = SaveTempScript(script.Contents); + + Logger?.LogTrace("Wrote script {Script} to {Destination}", script.Name, tempPath); + + return tempPath; + } + + public static string SaveTempScript(string contents) { var tempPath = Path.GetTempFileName(); // PowerShell will only run scripts with the .ps1 file extension File.Move(tempPath, tempPath + ".ps1"); - Logger?.LogTrace("Writing script {Script} to {Destination}", script.Name, tempPath); + tempPath = tempPath + ".ps1"; - File.WriteAllText(tempPath, script.Contents); + File.WriteAllText(tempPath, contents); return tempPath; } diff --git a/LANCommander.SDK/LANCommander.SDK.csproj b/LANCommander.SDK/LANCommander.SDK.csproj index e653767..2e097cf 100644 --- a/LANCommander.SDK/LANCommander.SDK.csproj +++ b/LANCommander.SDK/LANCommander.SDK.csproj @@ -6,6 +6,7 @@ + diff --git a/LANCommander.SDK/PowerShell/PowerShellArgument.cs b/LANCommander.SDK/PowerShell/PowerShellArgument.cs new file mode 100644 index 0000000..173e861 --- /dev/null +++ b/LANCommander.SDK/PowerShell/PowerShellArgument.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.PowerShell +{ + public class PowerShellArgument + { + public string Name { get; set; } + public object Value { get; set; } + public Type Type { get; set; } + + public PowerShellArgument(string name, object value, Type type) + { + Name = name; + Value = value; + Type = type; + } + } +} diff --git a/LANCommander.SDK/PowerShell/PowerShellFactory.cs b/LANCommander.SDK/PowerShell/PowerShellFactory.cs new file mode 100644 index 0000000..ace2d78 --- /dev/null +++ b/LANCommander.SDK/PowerShell/PowerShellFactory.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.PowerShell +{ + public static class PowerShellFactory + { + public static PowerShellScript RunScript() + { + return new PowerShellScript(); + } + } +} diff --git a/LANCommander.SDK/PowerShell/PowerShellScript.cs b/LANCommander.SDK/PowerShell/PowerShellScript.cs new file mode 100644 index 0000000..6f300f9 --- /dev/null +++ b/LANCommander.SDK/PowerShell/PowerShellScript.cs @@ -0,0 +1,169 @@ +using LANCommander.SDK.Helpers; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace LANCommander.SDK.PowerShell +{ + public class PowerShellScript + { + private string Contents { get; set; } = ""; + private string WorkingDirectory { get; set; } = ""; + private bool AsAdmin { get; set; } = false; + private bool ShellExecute { get; set; } = false; + private bool IgnoreWow64 { get; set; } = false; + private ICollection Variables { get; set; } + private Dictionary Arguments { get; set; } + private Process Process { get; set; } + + public PowerShellScript() + { + Variables = new List(); + Arguments = new Dictionary(); + Process = new Process(); + + Process.StartInfo.FileName = "powershell.exe"; + Process.StartInfo.RedirectStandardOutput = false; + + AddArgument("ExecutionPolicy", "Unrestricted"); + } + + public PowerShellScript UseFile(string path) + { + Contents = File.ReadAllText(path); + + return this; + } + + public PowerShellScript UseInline(string contents) + { + Contents = contents; + + return this; + } + + public PowerShellScript UseWorkingDirectory(string path) + { + WorkingDirectory = path; + + return this; + } + + public PowerShellScript UseShellExecute() + { + ShellExecute = true; + + return this; + } + + public PowerShellScript AddVariable(string name, T value) + { + Variables.Add(new PowerShellVariable(name, value, typeof(T))); + + return this; + } + + public PowerShellScript AddArgument(string name, T value) + { + Arguments.Add(name, $"\"{value}\""); + + return this; + } + + public PowerShellScript AddArgument(string name, int value) + { + Arguments[name] = value.ToString(); + + return this; + } + + public PowerShellScript AddArgument(string name, long value) + { + Arguments[name] = value.ToString(); + + return this; + } + + public PowerShellScript RunAsAdmin() + { + AsAdmin = true; + + Process.StartInfo.Verb = "runas"; + Process.StartInfo.UseShellExecute = true; + + return this; + } + + public PowerShellScript IgnoreWow64Redirection() + { + IgnoreWow64 = true; + + return this; + } + + public int Execute() + { + var scriptBuilder = new StringBuilder(); + + var wow64Value = IntPtr.Zero; + + foreach (var variable in Variables) + { + scriptBuilder.AppendLine($"${variable.Name} = Convert-FromSerializedBase64 \"{Serialize(variable.Value)}\""); + } + + scriptBuilder.AppendLine(Contents); + + var path = ScriptHelper.SaveTempScript(scriptBuilder.ToString()); + + AddArgument("File", path); + + if (IgnoreWow64) + Wow64DisableWow64FsRedirection(ref wow64Value); + + Process.StartInfo.Arguments = String.Join(" ", Arguments.Select((name, value) => + { + return $"-{name} {value}"; + })); + + if (!String.IsNullOrEmpty(WorkingDirectory)) + Process.StartInfo.WorkingDirectory = WorkingDirectory; + + if (ShellExecute) + Process.StartInfo.UseShellExecute = true; + + if (AsAdmin) + { + Process.StartInfo.Verb = "runas"; + Process.StartInfo.UseShellExecute = true; + } + + Process.Start(); + Process.WaitForExit(); + + if (IgnoreWow64) + Wow64RevertWow64FsRedirection(ref wow64Value); + + if (File.Exists(path)) + File.Delete(path); + + return Process.ExitCode; + } + + public static string Serialize(T input) + { + // Use the PowerShell serializer to generate XML for our input. Then convert to base64 so we can put it on one line. + return Convert.ToBase64String(Encoding.UTF8.GetBytes(System.Management.Automation.PSSerializer.Serialize(input))); + } + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool Wow64RevertWow64FsRedirection(ref IntPtr ptr); + } +} diff --git a/LANCommander.SDK/PowerShell/PowerShellVariable.cs b/LANCommander.SDK/PowerShell/PowerShellVariable.cs new file mode 100644 index 0000000..a7f78b8 --- /dev/null +++ b/LANCommander.SDK/PowerShell/PowerShellVariable.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LANCommander.SDK.PowerShell +{ + public class PowerShellVariable + { + public string Name { get; set; } + public object Value { get; set; } + public Type Type { get; set; } + + public PowerShellVariable(string name, object value, Type type) + { + Name = name; + Value = value; + Type = type; + } + } +} From bf2c9ea45a66d927e407afc78eec906ee76c5812 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Wed, 15 Nov 2023 23:42:54 -0600 Subject: [PATCH 24/93] Move script path helpers to ScriptHelper. Execute post-install scripts in Playnite extension. --- .../InstallController.cs | 36 +++++++++++++++++++ LANCommander.SDK/GameManager.cs | 14 -------- LANCommander.SDK/Helpers/ScriptHelper.cs | 19 ++++++++++ 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/LANCommander.Playnite.Extension/InstallController.cs b/LANCommander.Playnite.Extension/InstallController.cs index 7c7bb86..1be1388 100644 --- a/LANCommander.Playnite.Extension/InstallController.cs +++ b/LANCommander.Playnite.Extension/InstallController.cs @@ -1,6 +1,7 @@ using LANCommander.SDK; using LANCommander.SDK.Helpers; using LANCommander.SDK.Models; +using LANCommander.SDK.PowerShell; using Playnite.SDK; using Playnite.SDK.Models; using Playnite.SDK.Plugins; @@ -116,6 +117,9 @@ namespace LANCommander.PlaynitePlugin InstallDirectory = installDirectory, }; + RunInstallScript(installDirectory); + RunNameChangeScript(installDirectory); + InvokeOnInstalled(new GameInstalledEventArgs(installInfo)); } else if (result.Canceled) @@ -130,5 +134,37 @@ namespace LANCommander.PlaynitePlugin else if (result.Error != null) throw result.Error; } + + private int RunInstallScript(string installDirectory) + { + var manifest = ManifestHelper.Read(installDirectory); + var script = new PowerShellScript(); + + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory); + script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress); + + script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install)); + + return script.Execute(); + } + + private int RunNameChangeScript(string installDirectory) + { + var manifest = ManifestHelper.Read(installDirectory); + var script = new PowerShellScript(); + + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory); + script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress); + script.AddVariable("OldPlayerAlias", ""); + script.AddVariable("NewPlayerAlias", Plugin.Settings.PlayerName); + + script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange)); + + return script.Execute(); + } } } diff --git a/LANCommander.SDK/GameManager.cs b/LANCommander.SDK/GameManager.cs index 056b563..dc2d733 100644 --- a/LANCommander.SDK/GameManager.cs +++ b/LANCommander.SDK/GameManager.cs @@ -91,20 +91,6 @@ namespace LANCommander.SDK ScriptHelper.SaveScript(game, ScriptType.NameChange); ScriptHelper.SaveScript(game, ScriptType.KeyChange); - try - { - PowerShellRuntime.RunScript(game, ScriptType.Install); - PowerShellRuntime.RunScript(game, ScriptType.NameChange, /* Plugin.Settings.PlayerName */ ""); - - var key = Client.GetAllocatedKey(game.Id); - - PowerShellRuntime.RunScript(game, ScriptType.KeyChange, $"\"{key}\""); - } - catch (Exception ex) - { - Logger?.LogError(ex, "Could not execute post-install scripts"); - } - return result.Directory; } diff --git a/LANCommander.SDK/Helpers/ScriptHelper.cs b/LANCommander.SDK/Helpers/ScriptHelper.cs index dd27f7e..352cb96 100644 --- a/LANCommander.SDK/Helpers/ScriptHelper.cs +++ b/LANCommander.SDK/Helpers/ScriptHelper.cs @@ -55,5 +55,24 @@ namespace LANCommander.SDK.Helpers File.WriteAllText(filename, script.Contents); } + + public static string GetScriptFilePath(Game game, ScriptType type) + { + return GetScriptFilePath(game.InstallDirectory, type); + } + + public static string GetScriptFilePath(string installDirectory, ScriptType type) + { + Dictionary filenames = new Dictionary() { + { ScriptType.Install, "_install.ps1" }, + { ScriptType.Uninstall, "_uninstall.ps1" }, + { ScriptType.NameChange, "_changename.ps1" }, + { ScriptType.KeyChange, "_changekey.ps1" } + }; + + var filename = filenames[type]; + + return Path.Combine(installDirectory, filename); + } } } From cb9f31a00afadc7e22cdf2795fafa4d1e004e514 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Wed, 15 Nov 2023 23:53:26 -0600 Subject: [PATCH 25/93] Use new PowerShell in redistributable installs --- LANCommander.SDK/RedistributableManager.cs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/LANCommander.SDK/RedistributableManager.cs b/LANCommander.SDK/RedistributableManager.cs index fcc87d8..699b5af 100644 --- a/LANCommander.SDK/RedistributableManager.cs +++ b/LANCommander.SDK/RedistributableManager.cs @@ -2,6 +2,7 @@ using LANCommander.SDK.Extensions; using LANCommander.SDK.Helpers; using LANCommander.SDK.Models; +using LANCommander.SDK.PowerShell; using Microsoft.Extensions.Logging; using SharpCompress.Common; using SharpCompress.Readers; @@ -57,7 +58,7 @@ namespace LANCommander.SDK var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall); detectionScriptTempFile = ScriptHelper.SaveTempScript(detectionScript); - var detectionResult = PowerShellRuntime.RunScript(detectionScriptTempFile, detectionScript.RequiresAdmin); + var detectionResult = RunScript(detectionScriptTempFile, redistributable); // Redistributable is not installed if (detectionResult == 0) @@ -70,12 +71,12 @@ namespace LANCommander.SDK { extractTempPath = extractionResult.Directory; - PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath); + RunScript(installScriptTempFile, redistributable, installScript.RequiresAdmin, extractTempPath); } } else { - PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath); + RunScript(installScriptTempFile, redistributable, installScript.RequiresAdmin, extractTempPath); } } } @@ -164,5 +165,20 @@ namespace LANCommander.SDK return extractionResult; } + + private int RunScript(string path, Redistributable redistributable, bool requiresAdmin = false, string workingDirectory = "") + { + var script = new PowerShellScript(); + + script.AddVariable("Redistributable", redistributable); + + script.UseWorkingDirectory(workingDirectory); + script.UseFile(path); + + if (requiresAdmin) + script.RunAsAdmin(); + + return script.Execute(); + } } } From 97f459eaff467e2fb32074343d922b9b1a7637a7 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Wed, 15 Nov 2023 23:58:55 -0600 Subject: [PATCH 26/93] Move uninstall script execution to Playnite addon --- .../UninstallController.cs | 30 ++++++++++++++++++- LANCommander.SDK/GameManager.cs | 1 - 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/LANCommander.Playnite.Extension/UninstallController.cs b/LANCommander.Playnite.Extension/UninstallController.cs index eb23735..350b37a 100644 --- a/LANCommander.Playnite.Extension/UninstallController.cs +++ b/LANCommander.Playnite.Extension/UninstallController.cs @@ -1,4 +1,6 @@ using LANCommander.SDK.Enums; +using LANCommander.SDK.Helpers; +using LANCommander.SDK.PowerShell; using Playnite.SDK; using Playnite.SDK.Models; using Playnite.SDK.Plugins; @@ -25,11 +27,37 @@ namespace LANCommander.PlaynitePlugin { var gameManager = new LANCommander.SDK.GameManager(Plugin.LANCommanderClient, Plugin.Settings.InstallDirectory); + try + { + var scriptPath = ScriptHelper.GetScriptFilePath(Game.InstallDirectory, SDK.Enums.ScriptType.Uninstall); + + if (!String.IsNullOrEmpty(scriptPath)) + { + var manifest = ManifestHelper.Read(Game.InstallDirectory); + var script = new PowerShellScript(); + + var key = Plugin.LANCommanderClient.GetAllocatedKey(manifest.Id); + + script.AddVariable("InstallDirectory", Game.InstallDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory); + script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress); + + script.UseFile(scriptPath); + + script.Execute(); + } + } + catch (Exception ex) + { + Logger.Error(ex, "There was an error running the uninstall script"); + } + gameManager.Uninstall(Game.InstallDirectory); } catch (Exception ex) { - + Logger.Error(ex, "There was an error uninstalling the game"); } InvokeOnUninstalled(new GameUninstalledEventArgs()); diff --git a/LANCommander.SDK/GameManager.cs b/LANCommander.SDK/GameManager.cs index dc2d733..110c627 100644 --- a/LANCommander.SDK/GameManager.cs +++ b/LANCommander.SDK/GameManager.cs @@ -101,7 +101,6 @@ namespace LANCommander.SDK try { Logger?.LogTrace("Running uninstall script"); - PowerShellRuntime.RunScript(installDirectory, ScriptType.Uninstall); } catch (Exception ex) { From eb05364542385e28649aa5feaffdf6aeae21df15 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Wed, 15 Nov 2023 23:59:12 -0600 Subject: [PATCH 27/93] Fix reference to static method in ScriptHelper --- LANCommander.SDK/Helpers/ScriptHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LANCommander.SDK/Helpers/ScriptHelper.cs b/LANCommander.SDK/Helpers/ScriptHelper.cs index 352cb96..3fde3f8 100644 --- a/LANCommander.SDK/Helpers/ScriptHelper.cs +++ b/LANCommander.SDK/Helpers/ScriptHelper.cs @@ -46,7 +46,7 @@ namespace LANCommander.SDK.Helpers if (script.RequiresAdmin) script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents; - var filename = PowerShellRuntime.GetScriptFilePath(game, type); + var filename = GetScriptFilePath(game, type); if (File.Exists(filename)) File.Delete(filename); From 16dc60b90a76bf340ec6e15d301f01c22832b759 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 00:40:27 -0600 Subject: [PATCH 28/93] Run key change script in InstallController --- .../InstallController.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/LANCommander.Playnite.Extension/InstallController.cs b/LANCommander.Playnite.Extension/InstallController.cs index 1be1388..bd27e7b 100644 --- a/LANCommander.Playnite.Extension/InstallController.cs +++ b/LANCommander.Playnite.Extension/InstallController.cs @@ -119,6 +119,7 @@ namespace LANCommander.PlaynitePlugin RunInstallScript(installDirectory); RunNameChangeScript(installDirectory); + RunKeyChangeScript(installDirectory); InvokeOnInstalled(new GameInstalledEventArgs(installInfo)); } @@ -166,5 +167,23 @@ namespace LANCommander.PlaynitePlugin return script.Execute(); } + + private int RunKeyChangeScript(string installDirectory) + { + var manifest = ManifestHelper.Read(installDirectory); + var script = new PowerShellScript(); + + var key = Plugin.LANCommanderClient.GetAllocatedKey(manifest.Id); + + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory); + script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress); + script.AddVariable("AllocatedKey", key); + + script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange)); + + return script.Execute(); + } } } From 8d85aca0a781740f1283e2dbba09231f4ea10f49 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 00:46:20 -0600 Subject: [PATCH 29/93] Fix plugin menu items to use new script execution --- .../LANCommanderLibraryPlugin.cs | 84 ++++++++++++++++--- 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs index e118e19..491de78 100644 --- a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs +++ b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs @@ -1,4 +1,6 @@ using LANCommander.PlaynitePlugin.Extensions; +using LANCommander.SDK.Helpers; +using LANCommander.SDK.PowerShell; using Playnite.SDK; using Playnite.SDK.Events; using Playnite.SDK.Models; @@ -220,9 +222,9 @@ namespace LANCommander.PlaynitePlugin if (args.Games.Count == 1 && args.Games.First().IsInstalled && !String.IsNullOrWhiteSpace(args.Games.First().InstallDirectory)) { - var nameChangeScriptPath = LANCommander.SDK.PowerShellRuntime.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.NameChange); - var keyChangeScriptPath = LANCommander.SDK.PowerShellRuntime.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.KeyChange); - var installScriptPath = LANCommander.SDK.PowerShellRuntime.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.Install); + var nameChangeScriptPath = ScriptHelper.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.NameChange); + var keyChangeScriptPath = ScriptHelper.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.KeyChange); + var installScriptPath = ScriptHelper.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.Install); if (File.Exists(nameChangeScriptPath)) { @@ -241,7 +243,8 @@ namespace LANCommander.PlaynitePlugin { var game = nameChangeArgs.Games.First(); - LANCommander.SDK.PowerShellRuntime.RunScript(game.InstallDirectory, SDK.Enums.ScriptType.NameChange, $@"""{result.SelectedString}"" ""{oldName}"""); + RunNameChangeScript(game.InstallDirectory, oldName, result.SelectedString); + LANCommanderClient.ChangeAlias(result.SelectedString); } } @@ -267,7 +270,7 @@ namespace LANCommander.PlaynitePlugin if (String.IsNullOrEmpty(newKey)) PlayniteApi.Dialogs.ShowErrorMessage("There are no more keys available on the server.", "No Keys Available"); else - LANCommander.SDK.PowerShellRuntime.RunScript(keyChangeArgs.Games.First().InstallDirectory, SDK.Enums.ScriptType.KeyChange, $@"""{newKey}"""); + RunKeyChangeScript(keyChangeArgs.Games.First().InstallDirectory, newKey); } else { @@ -289,13 +292,9 @@ namespace LANCommander.PlaynitePlugin Guid gameId; if (Guid.TryParse(installArgs.Games.First().GameId, out gameId)) - { - LANCommander.SDK.PowerShellRuntime.RunScript(installArgs.Games.First().InstallDirectory, SDK.Enums.ScriptType.Install); - } + RunInstallScript(installArgs.Games.First().InstallDirectory); else - { PlayniteApi.Dialogs.ShowErrorMessage("This game could not be found on the server. Your game may be corrupted."); - } } }; } @@ -391,6 +390,8 @@ namespace LANCommander.PlaynitePlugin } else { + var oldName = Settings.PlayerName; + Settings.PlayerName = result.SelectedString; Logger.Trace($"New player name of \"{Settings.PlayerName}\" has been set!"); @@ -404,7 +405,17 @@ namespace LANCommander.PlaynitePlugin Logger.Trace($"Running name change scripts across {games.Count} installed game(s)"); - LANCommander.SDK.PowerShellRuntime.RunScripts(games.Select(g => g.InstallDirectory), SDK.Enums.ScriptType.NameChange, Settings.PlayerName); + foreach (var game in games) + { + var script = new PowerShellScript(); + + script.AddVariable("OldName", oldName); + script.AddVariable("NewName", Settings.PlayerName); + + script.UseFile(ScriptHelper.GetScriptFilePath(game.InstallDirectory, SDK.Enums.ScriptType.NameChange)); + + script.Execute(); + } } } else @@ -523,5 +534,56 @@ namespace LANCommander.PlaynitePlugin PlayniteApi.Database.Games.Update(game); } + + private int RunInstallScript(string installDirectory) + { + var manifest = ManifestHelper.Read(installDirectory); + var script = new PowerShellScript(); + + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", Settings.InstallDirectory); + script.AddVariable("ServerAddress", Settings.ServerAddress); + + script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install)); + + return script.Execute(); + } + + private int RunNameChangeScript(string installDirectory, string oldPlayerAlias, string newPlayerAlias) + { + var manifest = ManifestHelper.Read(installDirectory); + var script = new PowerShellScript(); + + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", Settings.InstallDirectory); + script.AddVariable("ServerAddress", Settings.ServerAddress); + script.AddVariable("OldPlayerAlias", oldPlayerAlias); + script.AddVariable("NewPlayerAlias", newPlayerAlias); + + script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange)); + + return script.Execute(); + } + + private int RunKeyChangeScript(string installDirectory, string key = "") + { + var manifest = ManifestHelper.Read(installDirectory); + var script = new PowerShellScript(); + + if (String.IsNullOrEmpty(key)) + key = LANCommanderClient.GetAllocatedKey(manifest.Id); + + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", Settings.InstallDirectory); + script.AddVariable("ServerAddress", Settings.ServerAddress); + script.AddVariable("AllocatedKey", key); + + script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange)); + + return script.Execute(); + } } } From 03828bea6059eacd4c7ee298a0d4447602bddda5 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 00:46:42 -0600 Subject: [PATCH 30/93] Remove unused code from uninstall --- LANCommander.SDK/GameManager.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/LANCommander.SDK/GameManager.cs b/LANCommander.SDK/GameManager.cs index 110c627..a9163cc 100644 --- a/LANCommander.SDK/GameManager.cs +++ b/LANCommander.SDK/GameManager.cs @@ -96,16 +96,6 @@ namespace LANCommander.SDK public void Uninstall(string installDirectory) { - var manifest = ManifestHelper.Read(installDirectory); - - try - { - Logger?.LogTrace("Running uninstall script"); - } - catch (Exception ex) - { - Logger?.LogError(ex, "Error running uninstall script"); - } Logger?.LogTrace("Attempting to delete the install directory"); From 35f6dadf9cfa821c47fcf841b643ebd1a0333213 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 00:47:01 -0600 Subject: [PATCH 31/93] Convert game save registry export commands to inline scripts --- LANCommander.SDK/GameSaveManager.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/LANCommander.SDK/GameSaveManager.cs b/LANCommander.SDK/GameSaveManager.cs index a7ffc1b..caf53de 100644 --- a/LANCommander.SDK/GameSaveManager.cs +++ b/LANCommander.SDK/GameSaveManager.cs @@ -1,6 +1,7 @@ using LANCommander.SDK; using LANCommander.SDK.Helpers; using LANCommander.SDK.Models; +using LANCommander.SDK.PowerShell; using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; @@ -126,7 +127,14 @@ namespace LANCommander.SDK { var registryImportFileContents = File.ReadAllText(registryImportFilePath); - PowerShellRuntime.RunCommand($"regedit.exe /s \"{registryImportFilePath}\"", registryImportFileContents.Contains("HKEY_LOCAL_MACHINE")); + var script = new PowerShellScript(); + + script.UseInline($"regedit.exe /s \"{registryImportFilePath}\""); + + if (registryImportFileContents.Contains("HKEY_LOCAL_MACHINE")) + script.RunAsAdmin(); + + script.Execute(); } #endregion @@ -199,7 +207,11 @@ namespace LANCommander.SDK tempRegFiles.Add(tempRegFile); } - PowerShellRuntime.RunCommand(exportCommand.ToString()); + var script = new PowerShellScript(); + + script.UseInline(exportCommand.ToString()); + + script.Execute(); var exportFile = new StringBuilder(); From 86211a7500fbc0cc6f17d033d903b23b71d1c756 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 00:47:13 -0600 Subject: [PATCH 32/93] Remove unused PowerShell code --- .../PowerShell/PowerShellFactory.cs | 14 -- LANCommander.SDK/PowerShellRuntime.cs | 173 ------------------ 2 files changed, 187 deletions(-) delete mode 100644 LANCommander.SDK/PowerShell/PowerShellFactory.cs delete mode 100644 LANCommander.SDK/PowerShellRuntime.cs diff --git a/LANCommander.SDK/PowerShell/PowerShellFactory.cs b/LANCommander.SDK/PowerShell/PowerShellFactory.cs deleted file mode 100644 index ace2d78..0000000 --- a/LANCommander.SDK/PowerShell/PowerShellFactory.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace LANCommander.SDK.PowerShell -{ - public static class PowerShellFactory - { - public static PowerShellScript RunScript() - { - return new PowerShellScript(); - } - } -} diff --git a/LANCommander.SDK/PowerShellRuntime.cs b/LANCommander.SDK/PowerShellRuntime.cs deleted file mode 100644 index 18d8f8c..0000000 --- a/LANCommander.SDK/PowerShellRuntime.cs +++ /dev/null @@ -1,173 +0,0 @@ -using LANCommander.SDK.Enums; -using LANCommander.SDK.Models; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; - -namespace LANCommander.SDK -{ - public static class PowerShellRuntime - { - public static readonly ILogger Logger; - - [DllImport("kernel32.dll", SetLastError = true)] - static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr); - - [DllImport("kernel32.dll", SetLastError = true)] - static extern bool Wow64RevertWow64FsRedirection(ref IntPtr ptr); - - public static void RunCommand(string command, bool asAdmin = false) - { - Logger?.LogTrace($"Executing command `{command}` | Admin: {asAdmin}"); - - var tempScript = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".ps1"); - - Logger?.LogTrace($"Creating temp script at path {tempScript}"); - - File.WriteAllText(tempScript, command); - - RunScript(tempScript, asAdmin); - - File.Delete(tempScript); - } - - public static int RunScript(string path, bool asAdmin = false, string arguments = null, string workingDirectory = null) - { - Logger?.LogTrace($"Executing script at path {path} | Admin: {asAdmin} | Arguments: {arguments}"); - - var wow64Value = IntPtr.Zero; - - // Disable Wow64 redirection so we can hit areas of the registry absolutely - Wow64DisableWow64FsRedirection(ref wow64Value); - - var process = new Process(); - - process.StartInfo.FileName = "powershell.exe"; - process.StartInfo.Arguments = $@"-ExecutionPolicy Unrestricted -File ""{path}"""; - process.StartInfo.UseShellExecute = false; - process.StartInfo.RedirectStandardOutput = false; - - if (arguments != null) - process.StartInfo.Arguments += " " + arguments; - - if (workingDirectory != null) - process.StartInfo.WorkingDirectory = workingDirectory; - - if (asAdmin) - { - process.StartInfo.Verb = "runas"; - process.StartInfo.UseShellExecute = true; - } - - process.Start(); - process.WaitForExit(); - - Wow64RevertWow64FsRedirection(ref wow64Value); - - return process.ExitCode; - } - - public static void RunScript(Game game, ScriptType type, string arguments = null) - { - RunScript(game.InstallDirectory, type, arguments); - } - - public static void RunScript(string installDirectory, ScriptType type, string arguments = null) - { - var path = GetScriptFilePath(installDirectory, type); - - if (File.Exists(path)) - { - var contents = File.ReadAllText(path); - - if (contents.StartsWith("# Requires Admin")) - RunScript(path, true, arguments); - else - RunScript(path, false, arguments); - } - } - - public static void RunScriptsAsAdmin(IEnumerable paths, string arguments = null) - { - // Concatenate scripts - var sb = new StringBuilder(); - - Logger?.LogTrace("Concatenating scripts..."); - - foreach (var path in paths) - { - var contents = File.ReadAllText(path); - - sb.AppendLine(contents); - - Logger?.LogTrace($"Added {path}!"); - } - - Logger?.LogTrace("Done concatenating!"); - - if (sb.Length > 0) - { - var scriptPath = Path.GetTempFileName(); - - Logger?.LogTrace($"Creating temp script at path {scriptPath}"); - - File.WriteAllText(scriptPath, sb.ToString()); - - RunScript(scriptPath, true, arguments); - } - } - - public static void RunScripts(IEnumerable installDirectories, ScriptType type, string arguments = null) - { - List scripts = new List(); - List adminScripts = new List(); - - foreach (var installDirectory in installDirectories) - { - var path = GetScriptFilePath(installDirectory, type); - - if (!File.Exists(path)) - continue; - - var contents = File.ReadAllText(path); - - if (contents.StartsWith("# Requires Admin")) - adminScripts.Add(path); - else - scripts.Add(path); - } - - RunScriptsAsAdmin(adminScripts, arguments); - - foreach (var script in scripts) - { - RunScript(script, false, arguments); - } - } - - public static string GetScriptFilePath(Game game, ScriptType type) - { - return GetScriptFilePath(game.InstallDirectory, type); - } - - public static string GetScriptFilePath(string installDirectory, ScriptType type) - { - Dictionary filenames = new Dictionary() { - { ScriptType.Install, "_install.ps1" }, - { ScriptType.Uninstall, "_uninstall.ps1" }, - { ScriptType.NameChange, "_changename.ps1" }, - { ScriptType.KeyChange, "_changekey.ps1" } - }; - - var filename = filenames[type]; - - return Path.Combine(installDirectory, filename); - } - } -} From c71cf9fedd79366d7b2409476e201c6c114be5a9 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 01:01:17 -0600 Subject: [PATCH 33/93] Cmdlet for installing game --- .../Cmdlets/Install-Game.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 LANCommander.PowerShell/Cmdlets/Install-Game.cs diff --git a/LANCommander.PowerShell/Cmdlets/Install-Game.cs b/LANCommander.PowerShell/Cmdlets/Install-Game.cs new file mode 100644 index 0000000..a68037a --- /dev/null +++ b/LANCommander.PowerShell/Cmdlets/Install-Game.cs @@ -0,0 +1,51 @@ +using LANCommander.SDK; +using System; +using System.Diagnostics; +using System.Linq; +using System.Management.Automation; +using System.Windows.Forms; + +namespace LANCommander.PowerShell.Cmdlets +{ + [Cmdlet(VerbsLifecycle.Install, "PrimaryDisplay")] + [OutputType(typeof(string))] + public class InstallGameCmdlet : PSCmdlet + { + [Parameter(Mandatory = true)] + public Client Client { get; set; } + + [Parameter(Mandatory = true)] + public Guid Id { get; set; } + + [Parameter(Mandatory = false)] + public string InstallDirectory { get; set; } = "C:\\Games"; + + protected override void ProcessRecord() + { + var gameManager = new GameManager(Client, InstallDirectory); + var game = Client.GetGame(Id); + + var progress = new ProgressRecord(1, $"Installing {game.Title}", "Progress:"); + + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + + gameManager.OnArchiveExtractionProgress += (long position, long length) => + { + // Only update a max of every 500ms + if (stopwatch.ElapsedMilliseconds > 500) + { + progress.PercentComplete = (int)Math.Ceiling((position / (decimal)length) * 100); + + WriteProgress(progress); + + stopwatch.Restart(); + } + }; + + gameManager.Install(Id); + + stopwatch.Stop(); + } + } +} From 839e9b49351fd9d8ffc13e4420561fcd112c579e Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 01:44:21 -0600 Subject: [PATCH 34/93] Run scripts after cmdlet install --- .../Cmdlets/Install-Game.cs | 59 ++++++++++++++++++- .../LANCommander.PowerShell.csproj | 1 + 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/LANCommander.PowerShell/Cmdlets/Install-Game.cs b/LANCommander.PowerShell/Cmdlets/Install-Game.cs index a68037a..ffd4308 100644 --- a/LANCommander.PowerShell/Cmdlets/Install-Game.cs +++ b/LANCommander.PowerShell/Cmdlets/Install-Game.cs @@ -1,4 +1,6 @@ using LANCommander.SDK; +using LANCommander.SDK.Helpers; +using LANCommander.SDK.PowerShell; using System; using System.Diagnostics; using System.Linq; @@ -43,9 +45,64 @@ namespace LANCommander.PowerShell.Cmdlets } }; - gameManager.Install(Id); + var installDirectory = gameManager.Install(Id); stopwatch.Stop(); + + RunInstallScript(installDirectory); + RunNameChangeScript(installDirectory); + RunKeyChangeScript(installDirectory); + } + + private int RunInstallScript(string installDirectory) + { + var manifest = ManifestHelper.Read(installDirectory); + var script = new PowerShellScript(); + + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", InstallDirectory); + script.AddVariable("ServerAddress", Client.BaseUrl); + + script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install)); + + return script.Execute(); + } + + private int RunNameChangeScript(string installDirectory) + { + var user = Client.GetProfile(); + var manifest = ManifestHelper.Read(installDirectory); + var script = new PowerShellScript(); + + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", InstallDirectory); + script.AddVariable("ServerAddress", Client.BaseUrl); + script.AddVariable("OldPlayerAlias", ""); + script.AddVariable("NewPlayerAlias", user.UserName); + + script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange)); + + return script.Execute(); + } + + private int RunKeyChangeScript(string installDirectory) + { + var manifest = ManifestHelper.Read(installDirectory); + var script = new PowerShellScript(); + + var key = Client.GetAllocatedKey(manifest.Id); + + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", InstallDirectory); + script.AddVariable("ServerAddress", Client.BaseUrl); + script.AddVariable("AllocatedKey", key); + + script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange)); + + return script.Execute(); } } } diff --git a/LANCommander.PowerShell/LANCommander.PowerShell.csproj b/LANCommander.PowerShell/LANCommander.PowerShell.csproj index 515ee5d..aaa8ee5 100644 --- a/LANCommander.PowerShell/LANCommander.PowerShell.csproj +++ b/LANCommander.PowerShell/LANCommander.PowerShell.csproj @@ -46,6 +46,7 @@ + From 26f03f61fc86123b7452eb8590849c0eaca47bd8 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 01:44:35 -0600 Subject: [PATCH 35/93] Add public base URL to LC client --- LANCommander.SDK/Client.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/LANCommander.SDK/Client.cs b/LANCommander.SDK/Client.cs index d7db648..4f8bdaa 100644 --- a/LANCommander.SDK/Client.cs +++ b/LANCommander.SDK/Client.cs @@ -18,18 +18,24 @@ namespace LANCommander.SDK private readonly ILogger Logger; private readonly RestClient ApiClient; - private AuthToken Token; + private AuthToken Token; + + public string BaseUrl; public Client(string baseUrl) { - if (!String.IsNullOrWhiteSpace(baseUrl)) - ApiClient = new RestClient(baseUrl); + BaseUrl = baseUrl; + + if (!String.IsNullOrWhiteSpace(BaseUrl)) + ApiClient = new RestClient(BaseUrl); } public Client(string baseUrl, ILogger logger) { - if (!String.IsNullOrWhiteSpace(baseUrl)) - ApiClient = new RestClient(baseUrl); + BaseUrl = baseUrl; + + if (!String.IsNullOrWhiteSpace(BaseUrl)) + ApiClient = new RestClient(BaseUrl); Logger = logger; } From 8bd422249e645c76b2b9ce14c5bf41cc7d9e69f8 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 12:04:26 -0600 Subject: [PATCH 36/93] Fix Install-Game cmdlet name --- LANCommander.PowerShell/Cmdlets/Install-Game.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LANCommander.PowerShell/Cmdlets/Install-Game.cs b/LANCommander.PowerShell/Cmdlets/Install-Game.cs index ffd4308..fe5ce74 100644 --- a/LANCommander.PowerShell/Cmdlets/Install-Game.cs +++ b/LANCommander.PowerShell/Cmdlets/Install-Game.cs @@ -9,7 +9,7 @@ using System.Windows.Forms; namespace LANCommander.PowerShell.Cmdlets { - [Cmdlet(VerbsLifecycle.Install, "PrimaryDisplay")] + [Cmdlet(VerbsLifecycle.Install, "Game")] [OutputType(typeof(string))] public class InstallGameCmdlet : PSCmdlet { From 49c4b10cf935843ffee0bfcf518d0a91460157a9 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 12:04:41 -0600 Subject: [PATCH 37/93] Don't get key when uninstalling --- LANCommander.Playnite.Extension/UninstallController.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/LANCommander.Playnite.Extension/UninstallController.cs b/LANCommander.Playnite.Extension/UninstallController.cs index 350b37a..8185bc4 100644 --- a/LANCommander.Playnite.Extension/UninstallController.cs +++ b/LANCommander.Playnite.Extension/UninstallController.cs @@ -36,8 +36,6 @@ namespace LANCommander.PlaynitePlugin var manifest = ManifestHelper.Read(Game.InstallDirectory); var script = new PowerShellScript(); - var key = Plugin.LANCommanderClient.GetAllocatedKey(manifest.Id); - script.AddVariable("InstallDirectory", Game.InstallDirectory); script.AddVariable("GameManifest", manifest); script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory); From f7fa7aa9f326faf71873c865003d83a59386d13a Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 12:04:55 -0600 Subject: [PATCH 38/93] Add cmdlet for uninstalling a game --- .../Cmdlets/Uninstall-Game.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs diff --git a/LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs b/LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs new file mode 100644 index 0000000..0d15815 --- /dev/null +++ b/LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs @@ -0,0 +1,42 @@ +using LANCommander.SDK; +using LANCommander.SDK.Helpers; +using LANCommander.SDK.PowerShell; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Windows.Forms; + +namespace LANCommander.PowerShell.Cmdlets +{ + [Cmdlet(VerbsLifecycle.Uninstall, "Game")] + [OutputType(typeof(string))] + public class UninstallGameCmdlet : PSCmdlet + { + [Parameter(Mandatory = false)] + public string InstallDirectory { get; set; } = "C:\\Games"; + + protected override void ProcessRecord() + { + var scriptPath = ScriptHelper.GetScriptFilePath(InstallDirectory, SDK.Enums.ScriptType.Uninstall); + + if (!String.IsNullOrEmpty(scriptPath) && File.Exists(scriptPath)) + { + var manifest = ManifestHelper.Read(InstallDirectory); + var script = new PowerShellScript(); + + script.AddVariable("InstallDirectory", InstallDirectory); + script.AddVariable("GameManifest", manifest); + + script.UseFile(scriptPath); + + script.Execute(); + } + + var gameManager = new GameManager(null, InstallDirectory); + + gameManager.Uninstall(InstallDirectory); + } + } +} From 986fb87db1251deb0e26ec62b036d6f21f039568 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 12:06:18 -0600 Subject: [PATCH 39/93] Make install directory mandatory when uninstalling --- LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs b/LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs index 0d15815..5746de8 100644 --- a/LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs +++ b/LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs @@ -14,8 +14,8 @@ namespace LANCommander.PowerShell.Cmdlets [OutputType(typeof(string))] public class UninstallGameCmdlet : PSCmdlet { - [Parameter(Mandatory = false)] - public string InstallDirectory { get; set; } = "C:\\Games"; + [Parameter(Mandatory = true)] + public string InstallDirectory { get; set; } protected override void ProcessRecord() { From a9f3b7a39d56bdde06801ffffa2285f52716431e Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 14:06:40 -0600 Subject: [PATCH 40/93] Only execute scripts if they exists on the disk --- .../InstallController.cs | 72 +++++++++++------- .../LANCommanderLibraryPlugin.cs | 73 ++++++++++++------- .../UninstallController.cs | 2 +- 3 files changed, 95 insertions(+), 52 deletions(-) diff --git a/LANCommander.Playnite.Extension/InstallController.cs b/LANCommander.Playnite.Extension/InstallController.cs index bd27e7b..c762c86 100644 --- a/LANCommander.Playnite.Extension/InstallController.cs +++ b/LANCommander.Playnite.Extension/InstallController.cs @@ -7,6 +7,7 @@ using Playnite.SDK.Models; using Playnite.SDK.Plugins; using System; using System.Diagnostics; +using System.IO; using System.Linq; namespace LANCommander.PlaynitePlugin @@ -139,51 +140,72 @@ namespace LANCommander.PlaynitePlugin private int RunInstallScript(string installDirectory) { var manifest = ManifestHelper.Read(installDirectory); - var script = new PowerShellScript(); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install); - script.AddVariable("InstallDirectory", installDirectory); - script.AddVariable("GameManifest", manifest); - script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory); - script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress); + if (File.Exists(path)) + { + var script = new PowerShellScript(); - script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install)); + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory); + script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress); - return script.Execute(); + script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install)); + + return script.Execute(); + } + + return 0; } private int RunNameChangeScript(string installDirectory) { var manifest = ManifestHelper.Read(installDirectory); - var script = new PowerShellScript(); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange); - script.AddVariable("InstallDirectory", installDirectory); - script.AddVariable("GameManifest", manifest); - script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory); - script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress); - script.AddVariable("OldPlayerAlias", ""); - script.AddVariable("NewPlayerAlias", Plugin.Settings.PlayerName); + if (File.Exists(path)) + { + var script = new PowerShellScript(); - script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange)); + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory); + script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress); + script.AddVariable("OldPlayerAlias", ""); + script.AddVariable("NewPlayerAlias", Plugin.Settings.PlayerName); - return script.Execute(); + script.UseFile(path); + + return script.Execute(); + } + + return 0; } private int RunKeyChangeScript(string installDirectory) { var manifest = ManifestHelper.Read(installDirectory); - var script = new PowerShellScript(); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange); - var key = Plugin.LANCommanderClient.GetAllocatedKey(manifest.Id); + if (File.Exists(path)) + { + var script = new PowerShellScript(); - script.AddVariable("InstallDirectory", installDirectory); - script.AddVariable("GameManifest", manifest); - script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory); - script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress); - script.AddVariable("AllocatedKey", key); + var key = Plugin.LANCommanderClient.GetAllocatedKey(manifest.Id); - script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange)); + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory); + script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress); + script.AddVariable("AllocatedKey", key); - return script.Execute(); + script.UseFile(path); + + return script.Execute(); + } + + return 0; } } } diff --git a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs index 491de78..21926a0 100644 --- a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs +++ b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs @@ -538,52 +538,73 @@ namespace LANCommander.PlaynitePlugin private int RunInstallScript(string installDirectory) { var manifest = ManifestHelper.Read(installDirectory); - var script = new PowerShellScript(); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install); - script.AddVariable("InstallDirectory", installDirectory); - script.AddVariable("GameManifest", manifest); - script.AddVariable("DefaultInstallDirectory", Settings.InstallDirectory); - script.AddVariable("ServerAddress", Settings.ServerAddress); + if (File.Exists(path)) + { + var script = new PowerShellScript(); - script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install)); + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", Settings.InstallDirectory); + script.AddVariable("ServerAddress", Settings.ServerAddress); - return script.Execute(); + script.UseFile(path); + + return script.Execute(); + } + + return 0; } private int RunNameChangeScript(string installDirectory, string oldPlayerAlias, string newPlayerAlias) { var manifest = ManifestHelper.Read(installDirectory); - var script = new PowerShellScript(); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange); - script.AddVariable("InstallDirectory", installDirectory); - script.AddVariable("GameManifest", manifest); - script.AddVariable("DefaultInstallDirectory", Settings.InstallDirectory); - script.AddVariable("ServerAddress", Settings.ServerAddress); - script.AddVariable("OldPlayerAlias", oldPlayerAlias); - script.AddVariable("NewPlayerAlias", newPlayerAlias); + if (File.Exists(path)) + { + var script = new PowerShellScript(); - script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange)); + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", Settings.InstallDirectory); + script.AddVariable("ServerAddress", Settings.ServerAddress); + script.AddVariable("OldPlayerAlias", oldPlayerAlias); + script.AddVariable("NewPlayerAlias", newPlayerAlias); - return script.Execute(); + script.UseFile(path); + + return script.Execute(); + } + + return 0; } private int RunKeyChangeScript(string installDirectory, string key = "") { var manifest = ManifestHelper.Read(installDirectory); - var script = new PowerShellScript(); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange); - if (String.IsNullOrEmpty(key)) - key = LANCommanderClient.GetAllocatedKey(manifest.Id); + if (File.Exists(path)) + { + var script = new PowerShellScript(); - script.AddVariable("InstallDirectory", installDirectory); - script.AddVariable("GameManifest", manifest); - script.AddVariable("DefaultInstallDirectory", Settings.InstallDirectory); - script.AddVariable("ServerAddress", Settings.ServerAddress); - script.AddVariable("AllocatedKey", key); + if (String.IsNullOrEmpty(key)) + key = LANCommanderClient.GetAllocatedKey(manifest.Id); - script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange)); + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", Settings.InstallDirectory); + script.AddVariable("ServerAddress", Settings.ServerAddress); + script.AddVariable("AllocatedKey", key); - return script.Execute(); + script.UseFile(path); + + return script.Execute(); + } + + return 0; } } } diff --git a/LANCommander.Playnite.Extension/UninstallController.cs b/LANCommander.Playnite.Extension/UninstallController.cs index 8185bc4..df9788f 100644 --- a/LANCommander.Playnite.Extension/UninstallController.cs +++ b/LANCommander.Playnite.Extension/UninstallController.cs @@ -31,7 +31,7 @@ namespace LANCommander.PlaynitePlugin { var scriptPath = ScriptHelper.GetScriptFilePath(Game.InstallDirectory, SDK.Enums.ScriptType.Uninstall); - if (!String.IsNullOrEmpty(scriptPath)) + if (!String.IsNullOrEmpty(scriptPath) && File.Exists(scriptPath)) { var manifest = ManifestHelper.Read(Game.InstallDirectory); var script = new PowerShellScript(); From 1b72d9002a07478bf288ee76fe5b35afb3f75af0 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 14:07:38 -0600 Subject: [PATCH 41/93] Switch PowerShell library to 4.6.2. Fix missing includes in project. --- LANCommander.PowerShell/LANCommander.PowerShell.csproj | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/LANCommander.PowerShell/LANCommander.PowerShell.csproj b/LANCommander.PowerShell/LANCommander.PowerShell.csproj index aaa8ee5..9f7d67d 100644 --- a/LANCommander.PowerShell/LANCommander.PowerShell.csproj +++ b/LANCommander.PowerShell/LANCommander.PowerShell.csproj @@ -9,9 +9,10 @@ Properties LANCommander.PowerShell LANCommander.PowerShell - v4.7.2 + v4.6.2 512 true + true @@ -46,6 +47,7 @@ + @@ -56,7 +58,9 @@ - + + PreserveNewest + From 2282d9b01396da00eb01989540867021356f86bb Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 14:08:35 -0600 Subject: [PATCH 42/93] Include PowerShell project in Playnite extension --- .../LANCommander.PlaynitePlugin.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj b/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj index 38dff6c..597df0b 100644 --- a/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj +++ b/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj @@ -12,6 +12,7 @@ v4.6.2 512 true + true true @@ -140,6 +141,10 @@ + + {807943bf-0c7d-4ed3-8393-cfee64e3138c} + LANCommander.PowerShell + {4c2a71fd-a30b-4d62-888a-4ef843d8e506} LANCommander.SDK From 196feedded04b43be93423851d61ef0784e2664d Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 14:34:00 -0600 Subject: [PATCH 43/93] Load PowerShell module on script execution. Add cmdlet to deserialize/decode variables. --- .../Cmdlets/ConvertFrom-SerializedBase64.cs | 24 +++++++++++++++++++ .../LANCommander.PowerShell.csproj | 1 + .../PowerShell/PowerShellScript.cs | 17 ++++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 LANCommander.PowerShell/Cmdlets/ConvertFrom-SerializedBase64.cs diff --git a/LANCommander.PowerShell/Cmdlets/ConvertFrom-SerializedBase64.cs b/LANCommander.PowerShell/Cmdlets/ConvertFrom-SerializedBase64.cs new file mode 100644 index 0000000..055aeee --- /dev/null +++ b/LANCommander.PowerShell/Cmdlets/ConvertFrom-SerializedBase64.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace LANCommander.PowerShell.Cmdlets +{ + [Cmdlet(VerbsData.ConvertFrom, "SerializedBase64")] + [OutputType(typeof(object))] + public class ConvertFromSerializedBase64Cmdlet : PSCmdlet + { + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + public string Input { get; set; } + + protected override void ProcessRecord() + { + var xml = Encoding.UTF8.GetString(Convert.FromBase64String(Input)); + + WriteObject(PSSerializer.Deserialize(xml)); + } + } +} diff --git a/LANCommander.PowerShell/LANCommander.PowerShell.csproj b/LANCommander.PowerShell/LANCommander.PowerShell.csproj index 9f7d67d..2357de7 100644 --- a/LANCommander.PowerShell/LANCommander.PowerShell.csproj +++ b/LANCommander.PowerShell/LANCommander.PowerShell.csproj @@ -46,6 +46,7 @@ + diff --git a/LANCommander.SDK/PowerShell/PowerShellScript.cs b/LANCommander.SDK/PowerShell/PowerShellScript.cs index 6f300f9..1d86f51 100644 --- a/LANCommander.SDK/PowerShell/PowerShellScript.cs +++ b/LANCommander.SDK/PowerShell/PowerShellScript.cs @@ -18,18 +18,21 @@ namespace LANCommander.SDK.PowerShell private bool IgnoreWow64 { get; set; } = false; private ICollection Variables { get; set; } private Dictionary Arguments { get; set; } + private List Modules { get; set; } private Process Process { get; set; } public PowerShellScript() { Variables = new List(); Arguments = new Dictionary(); + Modules = new List(); Process = new Process(); Process.StartInfo.FileName = "powershell.exe"; Process.StartInfo.RedirectStandardOutput = false; AddArgument("ExecutionPolicy", "Unrestricted"); + AddModule(Path.Combine(Environment.CurrentDirectory, "LANCommander.PowerShell.psd1")); } public PowerShellScript UseFile(string path) @@ -88,6 +91,13 @@ namespace LANCommander.SDK.PowerShell return this; } + public PowerShellScript AddModule(string path) + { + Modules.Add(path); + + return this; + } + public PowerShellScript RunAsAdmin() { AsAdmin = true; @@ -111,9 +121,14 @@ namespace LANCommander.SDK.PowerShell var wow64Value = IntPtr.Zero; + foreach (var module in Modules) + { + scriptBuilder.AppendLine($"Import-Module \"{module}\""); + } + foreach (var variable in Variables) { - scriptBuilder.AppendLine($"${variable.Name} = Convert-FromSerializedBase64 \"{Serialize(variable.Value)}\""); + scriptBuilder.AppendLine($"${variable.Name} = ConvertFrom-SerializedBase64 \"{Serialize(variable.Value)}\""); } scriptBuilder.AppendLine(Contents); From ed1b4973d3108c6e640419df9ace0ebdf7e31365 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 14:43:11 -0600 Subject: [PATCH 44/93] Fix cmdlet export specified by module manifest --- .../LANCommander.PowerShell.psd1 | Bin 8022 -> 8022 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/LANCommander.PowerShell/LANCommander.PowerShell.psd1 b/LANCommander.PowerShell/LANCommander.PowerShell.psd1 index 52b16e5c1381d6e249d47a7eb3b44108dc4b9778..7237c50e66600f386871344947ec57b24a69d06c 100644 GIT binary patch delta 16 Ycmca+cg=1?k;vp9B0`&6L@sdw07EthHvj+t delta 16 Xcmca+cg=1?k;r5oaiPsEBA2)TJL?8c From 8abc2fa15e0c608fe3c8ca568278bad256f712ff Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 15:20:50 -0600 Subject: [PATCH 45/93] Add cmdlet for encoding serialized objects for PS --- .../Cmdlets/ConvertFrom-SerializedBase64.cs | 2 +- .../Cmdlets/ConvertTo-SerializedBase64.cs | 24 +++++++++++++++++++ .../LANCommander.PowerShell.csproj | 1 + 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 LANCommander.PowerShell/Cmdlets/ConvertTo-SerializedBase64.cs diff --git a/LANCommander.PowerShell/Cmdlets/ConvertFrom-SerializedBase64.cs b/LANCommander.PowerShell/Cmdlets/ConvertFrom-SerializedBase64.cs index 055aeee..d600b38 100644 --- a/LANCommander.PowerShell/Cmdlets/ConvertFrom-SerializedBase64.cs +++ b/LANCommander.PowerShell/Cmdlets/ConvertFrom-SerializedBase64.cs @@ -9,7 +9,7 @@ namespace LANCommander.PowerShell.Cmdlets { [Cmdlet(VerbsData.ConvertFrom, "SerializedBase64")] [OutputType(typeof(object))] - public class ConvertFromSerializedBase64Cmdlet : PSCmdlet + public class ConvertFromSerializedBase64Cmdlet : Cmdlet { [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string Input { get; set; } diff --git a/LANCommander.PowerShell/Cmdlets/ConvertTo-SerializedBase64.cs b/LANCommander.PowerShell/Cmdlets/ConvertTo-SerializedBase64.cs new file mode 100644 index 0000000..fe92654 --- /dev/null +++ b/LANCommander.PowerShell/Cmdlets/ConvertTo-SerializedBase64.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace LANCommander.PowerShell.Cmdlets +{ + [Cmdlet(VerbsData.ConvertTo, "SerializedBase64")] + [OutputType(typeof(object))] + public class ConvertToSerializedBase64Cmdlet : Cmdlet + { + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + public object Input { get; set; } + + protected override void ProcessRecord() + { + var output = Convert.ToBase64String(Encoding.UTF8.GetBytes(PSSerializer.Serialize(Input))); + + WriteObject(output); + } + } +} diff --git a/LANCommander.PowerShell/LANCommander.PowerShell.csproj b/LANCommander.PowerShell/LANCommander.PowerShell.csproj index 2357de7..b59a2f6 100644 --- a/LANCommander.PowerShell/LANCommander.PowerShell.csproj +++ b/LANCommander.PowerShell/LANCommander.PowerShell.csproj @@ -46,6 +46,7 @@ + From 982227cf1fd3618127feeaea135783da111365e6 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 15:21:50 -0600 Subject: [PATCH 46/93] Change to inherit from Cmdlet --- LANCommander.PowerShell/Cmdlets/Convert-AspectRatio.cs | 2 +- LANCommander.PowerShell/Cmdlets/ConvertTo-StringBytes.cs | 2 +- LANCommander.PowerShell/Cmdlets/Edit-PatchBinary.cs | 2 +- LANCommander.PowerShell/Cmdlets/Get-GameManifest.cs | 2 +- LANCommander.PowerShell/Cmdlets/Get-PrimaryDisplay.cs | 2 +- LANCommander.PowerShell/Cmdlets/Install-Game.cs | 2 +- LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs | 2 +- LANCommander.PowerShell/Cmdlets/Write-GameManifest.cs | 2 +- LANCommander.PowerShell/Cmdlets/Write-ReplaceContentInFile.cs | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/LANCommander.PowerShell/Cmdlets/Convert-AspectRatio.cs b/LANCommander.PowerShell/Cmdlets/Convert-AspectRatio.cs index 5bb019d..a31949a 100644 --- a/LANCommander.PowerShell/Cmdlets/Convert-AspectRatio.cs +++ b/LANCommander.PowerShell/Cmdlets/Convert-AspectRatio.cs @@ -11,7 +11,7 @@ namespace LANCommander.PowerShell.Cmdlets [Cmdlet(VerbsData.Convert, "AspectRatio")] [OutputType(typeof(string))] - public class ConvertAspectRatioCmdlet : PSCmdlet + public class ConvertAspectRatioCmdlet : Cmdlet { [Parameter(Mandatory = true, Position = 0)] public int Width { get; set; } diff --git a/LANCommander.PowerShell/Cmdlets/ConvertTo-StringBytes.cs b/LANCommander.PowerShell/Cmdlets/ConvertTo-StringBytes.cs index 87b3c09..dced3b9 100644 --- a/LANCommander.PowerShell/Cmdlets/ConvertTo-StringBytes.cs +++ b/LANCommander.PowerShell/Cmdlets/ConvertTo-StringBytes.cs @@ -6,7 +6,7 @@ namespace LANCommander.PowerShell.Cmdlets { [Cmdlet(VerbsData.ConvertTo, "StringBytes")] [OutputType(typeof(byte[]))] - public class ConvertToStringBytesCmdlet : PSCmdlet + public class ConvertToStringBytesCmdlet : Cmdlet { [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string Input { get; set; } diff --git a/LANCommander.PowerShell/Cmdlets/Edit-PatchBinary.cs b/LANCommander.PowerShell/Cmdlets/Edit-PatchBinary.cs index 0e73580..95e2e46 100644 --- a/LANCommander.PowerShell/Cmdlets/Edit-PatchBinary.cs +++ b/LANCommander.PowerShell/Cmdlets/Edit-PatchBinary.cs @@ -6,7 +6,7 @@ namespace LANCommander.PowerShell.Cmdlets { [Cmdlet(VerbsData.Edit, "PatchBinary")] [OutputType(typeof(string))] - public class EditPatchBinaryCmdlet : PSCmdlet + public class EditPatchBinaryCmdlet : Cmdlet { [Parameter(Mandatory = true, Position = 0)] public long Offset { get; set; } diff --git a/LANCommander.PowerShell/Cmdlets/Get-GameManifest.cs b/LANCommander.PowerShell/Cmdlets/Get-GameManifest.cs index b012bd5..d11ea2b 100644 --- a/LANCommander.PowerShell/Cmdlets/Get-GameManifest.cs +++ b/LANCommander.PowerShell/Cmdlets/Get-GameManifest.cs @@ -6,7 +6,7 @@ namespace LANCommander.PowerShell.Cmdlets { [Cmdlet(VerbsCommon.Get, "GameManifest")] [OutputType(typeof(GameManifest))] - public class GetGameManifestCmdlet : PSCmdlet + public class GetGameManifestCmdlet : Cmdlet { [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string Path { get; set; } diff --git a/LANCommander.PowerShell/Cmdlets/Get-PrimaryDisplay.cs b/LANCommander.PowerShell/Cmdlets/Get-PrimaryDisplay.cs index 0b53547..93c838e 100644 --- a/LANCommander.PowerShell/Cmdlets/Get-PrimaryDisplay.cs +++ b/LANCommander.PowerShell/Cmdlets/Get-PrimaryDisplay.cs @@ -6,7 +6,7 @@ namespace LANCommander.PowerShell.Cmdlets { [Cmdlet(VerbsCommon.Get, "PrimaryDisplay")] [OutputType(typeof(string))] - public class GetPrimaryDisplayCmdlet : PSCmdlet + public class GetPrimaryDisplayCmdlet : Cmdlet { protected override void ProcessRecord() { diff --git a/LANCommander.PowerShell/Cmdlets/Install-Game.cs b/LANCommander.PowerShell/Cmdlets/Install-Game.cs index fe5ce74..84d8b5c 100644 --- a/LANCommander.PowerShell/Cmdlets/Install-Game.cs +++ b/LANCommander.PowerShell/Cmdlets/Install-Game.cs @@ -11,7 +11,7 @@ namespace LANCommander.PowerShell.Cmdlets { [Cmdlet(VerbsLifecycle.Install, "Game")] [OutputType(typeof(string))] - public class InstallGameCmdlet : PSCmdlet + public class InstallGameCmdlet : Cmdlet { [Parameter(Mandatory = true)] public Client Client { get; set; } diff --git a/LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs b/LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs index 5746de8..27b3dc4 100644 --- a/LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs +++ b/LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs @@ -12,7 +12,7 @@ namespace LANCommander.PowerShell.Cmdlets { [Cmdlet(VerbsLifecycle.Uninstall, "Game")] [OutputType(typeof(string))] - public class UninstallGameCmdlet : PSCmdlet + public class UninstallGameCmdlet : Cmdlet { [Parameter(Mandatory = true)] public string InstallDirectory { get; set; } diff --git a/LANCommander.PowerShell/Cmdlets/Write-GameManifest.cs b/LANCommander.PowerShell/Cmdlets/Write-GameManifest.cs index 6d9ad0c..28ff74d 100644 --- a/LANCommander.PowerShell/Cmdlets/Write-GameManifest.cs +++ b/LANCommander.PowerShell/Cmdlets/Write-GameManifest.cs @@ -6,7 +6,7 @@ namespace LANCommander.PowerShell.Cmdlets { [Cmdlet(VerbsCommunications.Write, "GameManifest")] [OutputType(typeof(string))] - public class WriteGameManifestCmdlet : PSCmdlet + public class WriteGameManifestCmdlet : Cmdlet { [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string Path { get; set; } diff --git a/LANCommander.PowerShell/Cmdlets/Write-ReplaceContentInFile.cs b/LANCommander.PowerShell/Cmdlets/Write-ReplaceContentInFile.cs index 32cda9c..3a96f72 100644 --- a/LANCommander.PowerShell/Cmdlets/Write-ReplaceContentInFile.cs +++ b/LANCommander.PowerShell/Cmdlets/Write-ReplaceContentInFile.cs @@ -7,7 +7,7 @@ namespace LANCommander.PowerShell.Cmdlets [Cmdlet(VerbsCommunications.Write, "ReplaceContentInFile")] [OutputType(typeof(string))] - public class ReplaceContentInFileCmdlet : PSCmdlet + public class ReplaceContentInFileCmdlet : Cmdlet { [Parameter(Mandatory = true, Position = 0)] public string Pattern { get; set; } From e723a5345be26b313b66710d22d3b0df60306742 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 15:22:20 -0600 Subject: [PATCH 47/93] Scaffold tests for cmdlets --- LANCommander.PowerShell.Tests/Cmdlets.cs | 36 +++++++++ .../LANCommander.PowerShell.Tests.csproj | 75 +++++++++++++++++++ .../Properties/AssemblyInfo.cs | 20 +++++ LANCommander.PowerShell.Tests/packages.config | 5 ++ LANCommander.sln | 6 ++ 5 files changed, 142 insertions(+) create mode 100644 LANCommander.PowerShell.Tests/Cmdlets.cs create mode 100644 LANCommander.PowerShell.Tests/LANCommander.PowerShell.Tests.csproj create mode 100644 LANCommander.PowerShell.Tests/Properties/AssemblyInfo.cs create mode 100644 LANCommander.PowerShell.Tests/packages.config diff --git a/LANCommander.PowerShell.Tests/Cmdlets.cs b/LANCommander.PowerShell.Tests/Cmdlets.cs new file mode 100644 index 0000000..062dc71 --- /dev/null +++ b/LANCommander.PowerShell.Tests/Cmdlets.cs @@ -0,0 +1,36 @@ +using LANCommander.PowerShell.Cmdlets; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Linq; + +namespace LANCommander.PowerShell.Tests +{ + [TestClass] + public class CmdletTests + { + [TestMethod] + public void ConvertToSerializedBase64ShouldBeDeserializable() + { + var testPhrase = "Hello world! This should be deserializable back to its original form."; + + var encodingCmdlet = new ConvertToSerializedBase64Cmdlet() + { + Input = testPhrase + }; + + var encodingResults = encodingCmdlet.Invoke().OfType().ToList(); + + Assert.AreEqual(1, encodingResults.Count); + + var decodingCmdlet = new ConvertFromSerializedBase64Cmdlet() + { + Input = encodingResults.First() + }; + + var decodingResults = decodingCmdlet.Invoke().OfType().ToList(); + + Assert.AreEqual(1, encodingResults.Count); + Assert.AreEqual(testPhrase, decodingResults.First()); + } + } +} diff --git a/LANCommander.PowerShell.Tests/LANCommander.PowerShell.Tests.csproj b/LANCommander.PowerShell.Tests/LANCommander.PowerShell.Tests.csproj new file mode 100644 index 0000000..af5df94 --- /dev/null +++ b/LANCommander.PowerShell.Tests/LANCommander.PowerShell.Tests.csproj @@ -0,0 +1,75 @@ + + + + + + Debug + AnyCPU + {D7069A13-F0AA-4CBF-9013-4276F130A6DD} + Library + Properties + LANCommander.PowerShell.Tests + LANCommander.PowerShell.Tests + v4.6.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + + + + + + + + + + + + + {807943bf-0c7d-4ed3-8393-cfee64e3138c} + LANCommander.PowerShell + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/LANCommander.PowerShell.Tests/Properties/AssemblyInfo.cs b/LANCommander.PowerShell.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..82c8b70 --- /dev/null +++ b/LANCommander.PowerShell.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("LANCommander.PowerShell.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("LANCommander.PowerShell.Tests")] +[assembly: AssemblyCopyright("Copyright © 2023")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("d7069a13-f0aa-4cbf-9013-4276f130a6dd")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/LANCommander.PowerShell.Tests/packages.config b/LANCommander.PowerShell.Tests/packages.config new file mode 100644 index 0000000..e47cc4d --- /dev/null +++ b/LANCommander.PowerShell.Tests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/LANCommander.sln b/LANCommander.sln index 5f6fef2..f77170c 100644 --- a/LANCommander.sln +++ b/LANCommander.sln @@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LANCommander.PCGamingWiki", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LANCommander.PowerShell", "LANCommander.PowerShell\LANCommander.PowerShell.csproj", "{807943BF-0C7D-4ED3-8393-CFEE64E3138C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LANCommander.PowerShell.Tests", "LANCommander.PowerShell.Tests\LANCommander.PowerShell.Tests.csproj", "{D7069A13-F0AA-4CBF-9013-4276F130A6DD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +41,10 @@ Global {807943BF-0C7D-4ED3-8393-CFEE64E3138C}.Debug|Any CPU.Build.0 = Debug|Any CPU {807943BF-0C7D-4ED3-8393-CFEE64E3138C}.Release|Any CPU.ActiveCfg = Release|Any CPU {807943BF-0C7D-4ED3-8393-CFEE64E3138C}.Release|Any CPU.Build.0 = Release|Any CPU + {D7069A13-F0AA-4CBF-9013-4276F130A6DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7069A13-F0AA-4CBF-9013-4276F130A6DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7069A13-F0AA-4CBF-9013-4276F130A6DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7069A13-F0AA-4CBF-9013-4276F130A6DD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 7649e63195b675e0849a3b64642eea5c6ac61c92 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 15:36:45 -0600 Subject: [PATCH 48/93] Add test for Convert-AspectRatio --- LANCommander.PowerShell.Tests/Cmdlets.cs | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/LANCommander.PowerShell.Tests/Cmdlets.cs b/LANCommander.PowerShell.Tests/Cmdlets.cs index 062dc71..b56e654 100644 --- a/LANCommander.PowerShell.Tests/Cmdlets.cs +++ b/LANCommander.PowerShell.Tests/Cmdlets.cs @@ -32,5 +32,33 @@ namespace LANCommander.PowerShell.Tests Assert.AreEqual(1, encodingResults.Count); Assert.AreEqual(testPhrase, decodingResults.First()); } + + [TestMethod] + [DataRow(640, 480, 640, 360, 16, 9)] + [DataRow(1024, 768, 1024, 576, 16, 9)] + [DataRow(1600, 1200, 1600, 900, 16, 9)] + [DataRow(1920, 1080, 1440, 1080, 4, 3)] + [DataRow(1366, 1024, 1024, 768, 4, 3)] + [DataRow(854, 480, 640, 480, 4, 3)] + public void ConvertAspectRatioShouldReturnCorrectBounds(int x1, int y1, int x2, int y2, int ratioX, int ratioY) + { + var aspectRatio = (double)ratioX / (double)ratioY; + + var cmdlet = new ConvertAspectRatioCmdlet() + { + AspectRatio = aspectRatio, + Width = x1, + Height = y1 + }; + + var output = cmdlet.Invoke().OfType().ToList(); + + Assert.AreEqual(1, output.Count); + + var bounds = output.First(); + + Assert.AreEqual(x2, bounds.Width); + Assert.AreEqual(y2, bounds.Height); + } } } From 2cb7013120a9fd0e1f6d6a0e0e1caaae21860f7e Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 16 Nov 2023 15:42:34 -0600 Subject: [PATCH 49/93] Write final install directory as output for Install-Game. Only process scripts that exist --- .../Cmdlets/Install-Game.cs | 74 ++++++++++++------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/LANCommander.PowerShell/Cmdlets/Install-Game.cs b/LANCommander.PowerShell/Cmdlets/Install-Game.cs index 84d8b5c..c24b5ed 100644 --- a/LANCommander.PowerShell/Cmdlets/Install-Game.cs +++ b/LANCommander.PowerShell/Cmdlets/Install-Game.cs @@ -3,6 +3,7 @@ using LANCommander.SDK.Helpers; using LANCommander.SDK.PowerShell; using System; using System.Diagnostics; +using System.IO; using System.Linq; using System.Management.Automation; using System.Windows.Forms; @@ -52,57 +53,80 @@ namespace LANCommander.PowerShell.Cmdlets RunInstallScript(installDirectory); RunNameChangeScript(installDirectory); RunKeyChangeScript(installDirectory); + + WriteObject(installDirectory); } private int RunInstallScript(string installDirectory) { var manifest = ManifestHelper.Read(installDirectory); - var script = new PowerShellScript(); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install); - script.AddVariable("InstallDirectory", installDirectory); - script.AddVariable("GameManifest", manifest); - script.AddVariable("DefaultInstallDirectory", InstallDirectory); - script.AddVariable("ServerAddress", Client.BaseUrl); + if (File.Exists(path)) + { + var script = new PowerShellScript(); - script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install)); + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", InstallDirectory); + script.AddVariable("ServerAddress", Client.BaseUrl); - return script.Execute(); + script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install)); + + return script.Execute(); + } + + return 0; } private int RunNameChangeScript(string installDirectory) { var user = Client.GetProfile(); var manifest = ManifestHelper.Read(installDirectory); - var script = new PowerShellScript(); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange); + + if (File.Exists(path)) + { + var script = new PowerShellScript(); - script.AddVariable("InstallDirectory", installDirectory); - script.AddVariable("GameManifest", manifest); - script.AddVariable("DefaultInstallDirectory", InstallDirectory); - script.AddVariable("ServerAddress", Client.BaseUrl); - script.AddVariable("OldPlayerAlias", ""); - script.AddVariable("NewPlayerAlias", user.UserName); + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", InstallDirectory); + script.AddVariable("ServerAddress", Client.BaseUrl); + script.AddVariable("OldPlayerAlias", ""); + script.AddVariable("NewPlayerAlias", user.UserName); - script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange)); + script.UseFile(path); - return script.Execute(); + return script.Execute(); + } + + return 0; } private int RunKeyChangeScript(string installDirectory) { var manifest = ManifestHelper.Read(installDirectory); - var script = new PowerShellScript(); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange); - var key = Client.GetAllocatedKey(manifest.Id); + if (File.Exists(path)) + { + var script = new PowerShellScript(); - script.AddVariable("InstallDirectory", installDirectory); - script.AddVariable("GameManifest", manifest); - script.AddVariable("DefaultInstallDirectory", InstallDirectory); - script.AddVariable("ServerAddress", Client.BaseUrl); - script.AddVariable("AllocatedKey", key); + var key = Client.GetAllocatedKey(manifest.Id); - script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange)); + script.AddVariable("InstallDirectory", installDirectory); + script.AddVariable("GameManifest", manifest); + script.AddVariable("DefaultInstallDirectory", InstallDirectory); + script.AddVariable("ServerAddress", Client.BaseUrl); + script.AddVariable("AllocatedKey", key); - return script.Execute(); + script.UseFile(path); + + return script.Execute(); + } + + return 0; } } } From aed9935b16a1b35c1c48549b04ef9aefeea4e5a3 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 17 Nov 2023 00:46:05 -0600 Subject: [PATCH 50/93] Updated default snippets --- .../Snippets/Examples/Copy Directory.ps1 | 2 +- .../Snippets/Examples/Create Directory.ps1 | 2 +- .../Snippets/Examples/Patch Binary.ps1 | 2 +- .../Snippets/Examples/Remove Directory.ps1 | 2 +- .../Snippets/Examples/Rename File.ps1 | 2 +- .../Examples/Replace Content In File.ps1 | 2 -- .../Examples/Separate ASCII Bytes.ps1 | 2 -- .../Examples/Set Compatibility Mode.ps1 | 2 +- .../Examples/String to ASCII Bytes.ps1 | 2 -- .../Snippets/Examples/Trim String.ps1 | 4 +-- .../Snippets/Examples/Write to File.ps1 | 2 +- .../Functions/Convert-AspectRatio.ps1 | 2 ++ .../Functions/ConvertTo-StringBytes.ps1 | 2 ++ .../Snippets/Functions/Edit-PatchBinary.ps1 | 1 + .../Snippets/Functions/Get-43Resolution.ps1 | 14 --------- .../Snippets/Functions/Get-AsciiBytes.ps1 | 30 ------------------- .../Snippets/Functions/Get-GameManifest.ps1 | 1 + .../Snippets/Functions/Get-PrimaryDisplay.ps1 | 2 ++ .../Snippets/Functions/Patch-Binary.ps1 | 11 ------- .../Functions/Separate-AsciiBytes.ps1 | 12 -------- .../Functions/Write-ReplaceContentInFile.ps1 | 7 ++--- .../Snippets/Variables/Allocated Key.ps1 | 1 + .../Variables/Default Install Directory.ps1 | 1 + LANCommander/Snippets/Variables/Display.ps1 | 3 -- .../Variables/Game Install Directory.ps1 | 1 + .../Snippets/Variables/Game Manifest.ps1 | 1 + .../Snippets/Variables/InstallDir.ps1 | 1 - .../{NewName.ps1 => New Player Alias.ps1} | 0 .../{OldName.ps1 => Old Player Alias.ps1} | 0 .../Snippets/Variables/Server Address.ps1 | 1 + 30 files changed, 24 insertions(+), 91 deletions(-) delete mode 100644 LANCommander/Snippets/Examples/Replace Content In File.ps1 delete mode 100644 LANCommander/Snippets/Examples/Separate ASCII Bytes.ps1 delete mode 100644 LANCommander/Snippets/Examples/String to ASCII Bytes.ps1 create mode 100644 LANCommander/Snippets/Functions/Convert-AspectRatio.ps1 create mode 100644 LANCommander/Snippets/Functions/ConvertTo-StringBytes.ps1 create mode 100644 LANCommander/Snippets/Functions/Edit-PatchBinary.ps1 delete mode 100644 LANCommander/Snippets/Functions/Get-43Resolution.ps1 delete mode 100644 LANCommander/Snippets/Functions/Get-AsciiBytes.ps1 create mode 100644 LANCommander/Snippets/Functions/Get-GameManifest.ps1 create mode 100644 LANCommander/Snippets/Functions/Get-PrimaryDisplay.ps1 delete mode 100644 LANCommander/Snippets/Functions/Patch-Binary.ps1 delete mode 100644 LANCommander/Snippets/Functions/Separate-AsciiBytes.ps1 create mode 100644 LANCommander/Snippets/Variables/Allocated Key.ps1 create mode 100644 LANCommander/Snippets/Variables/Default Install Directory.ps1 delete mode 100644 LANCommander/Snippets/Variables/Display.ps1 create mode 100644 LANCommander/Snippets/Variables/Game Install Directory.ps1 create mode 100644 LANCommander/Snippets/Variables/Game Manifest.ps1 delete mode 100644 LANCommander/Snippets/Variables/InstallDir.ps1 rename LANCommander/Snippets/Variables/{NewName.ps1 => New Player Alias.ps1} (100%) rename LANCommander/Snippets/Variables/{OldName.ps1 => Old Player Alias.ps1} (100%) create mode 100644 LANCommander/Snippets/Variables/Server Address.ps1 diff --git a/LANCommander/Snippets/Examples/Copy Directory.ps1 b/LANCommander/Snippets/Examples/Copy Directory.ps1 index 26c82d5..02c366f 100644 --- a/LANCommander/Snippets/Examples/Copy Directory.ps1 +++ b/LANCommander/Snippets/Examples/Copy Directory.ps1 @@ -1 +1 @@ -Copy-Item -Path "$InstallDir\" -Destination "$InstallDir\" -Recurse \ No newline at end of file +Copy-Item -Path "$InstallDirectory\" -Destination "$InstallDirectory\" -Recurse \ No newline at end of file diff --git a/LANCommander/Snippets/Examples/Create Directory.ps1 b/LANCommander/Snippets/Examples/Create Directory.ps1 index b5d5d4c..b65e50d 100644 --- a/LANCommander/Snippets/Examples/Create Directory.ps1 +++ b/LANCommander/Snippets/Examples/Create Directory.ps1 @@ -1 +1 @@ -New-Item -ItemType Directory -Force -Path "$InstallDir\" \ No newline at end of file +New-Item -ItemType Directory -Force -Path "$InstallDirectory\" \ No newline at end of file diff --git a/LANCommander/Snippets/Examples/Patch Binary.ps1 b/LANCommander/Snippets/Examples/Patch Binary.ps1 index 5019bf2..b3568ad 100644 --- a/LANCommander/Snippets/Examples/Patch Binary.ps1 +++ b/LANCommander/Snippets/Examples/Patch Binary.ps1 @@ -1,2 +1,2 @@ # Writes byte[] to a file at an offset -Patch-Binary -FilePath "$InstallDir\" -Offset 0x00 -Data $bytes \ No newline at end of file +Patch-Binary -FilePath "$InstallDirectory\" -Offset 0x00 -Data $bytes \ No newline at end of file diff --git a/LANCommander/Snippets/Examples/Remove Directory.ps1 b/LANCommander/Snippets/Examples/Remove Directory.ps1 index 5df8909..8425c7b 100644 --- a/LANCommander/Snippets/Examples/Remove Directory.ps1 +++ b/LANCommander/Snippets/Examples/Remove Directory.ps1 @@ -1 +1 @@ -Remove-Item "$InstallDir\" -Recurse -ErrorAction Ignore \ No newline at end of file +Remove-Item "$InstallDirectory\" -Recurse -ErrorAction Ignore \ No newline at end of file diff --git a/LANCommander/Snippets/Examples/Rename File.ps1 b/LANCommander/Snippets/Examples/Rename File.ps1 index b315277..9042ba1 100644 --- a/LANCommander/Snippets/Examples/Rename File.ps1 +++ b/LANCommander/Snippets/Examples/Rename File.ps1 @@ -1 +1 @@ -Rename-Item -Path "$InstallDir\" -NewName "$InstallDir\" \ No newline at end of file +Rename-Item -Path "$InstallDirectory\" -NewName "$InstallDirectory\" \ No newline at end of file diff --git a/LANCommander/Snippets/Examples/Replace Content In File.ps1 b/LANCommander/Snippets/Examples/Replace Content In File.ps1 deleted file mode 100644 index c28c8bf..0000000 --- a/LANCommander/Snippets/Examples/Replace Content In File.ps1 +++ /dev/null @@ -1,2 +0,0 @@ -# Use regex to replace text within a file. Quotes are escaped by double quoting ("") -Write-ReplaceContentInFile -Regex '^game.setPlayerName "(.+)"' -Replacement "game.setPlayerName ""$NewName""" -FilePath "$InstallDir\" \ No newline at end of file diff --git a/LANCommander/Snippets/Examples/Separate ASCII Bytes.ps1 b/LANCommander/Snippets/Examples/Separate ASCII Bytes.ps1 deleted file mode 100644 index 2ea805a..0000000 --- a/LANCommander/Snippets/Examples/Separate ASCII Bytes.ps1 +++ /dev/null @@ -1,2 +0,0 @@ -# Takes an input byte[] and separates it with 0x00 between each character -$bytes = Separate-AsciiBytes -Data $bytes \ No newline at end of file diff --git a/LANCommander/Snippets/Examples/Set Compatibility Mode.ps1 b/LANCommander/Snippets/Examples/Set Compatibility Mode.ps1 index b9e130d..0ebf256 100644 --- a/LANCommander/Snippets/Examples/Set Compatibility Mode.ps1 +++ b/LANCommander/Snippets/Examples/Set Compatibility Mode.ps1 @@ -9,4 +9,4 @@ # WIN7RTM # WIN8RTM # See: https://ss64.com/nt/syntax-compatibility.html -New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers" -Name "$InstallDir\" -Value "~ WINXPSP2 HIGHDPIAWARE" -Force \ No newline at end of file +New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers" -Name "$InstallDirectory\" -Value "~ WINXPSP2 HIGHDPIAWARE" -Force \ No newline at end of file diff --git a/LANCommander/Snippets/Examples/String to ASCII Bytes.ps1 b/LANCommander/Snippets/Examples/String to ASCII Bytes.ps1 deleted file mode 100644 index d40c156..0000000 --- a/LANCommander/Snippets/Examples/String to ASCII Bytes.ps1 +++ /dev/null @@ -1,2 +0,0 @@ -# Convert an input string to ASCII-encoded byte[]. Shorter strings will pad out to 12 bytes, longer strings will be trimmed. -$bytes = Get-AsciiBytes -InputString "Hello world!" -MaxLength 12 \ No newline at end of file diff --git a/LANCommander/Snippets/Examples/Trim String.ps1 b/LANCommander/Snippets/Examples/Trim String.ps1 index b4d7b94..2a72e13 100644 --- a/LANCommander/Snippets/Examples/Trim String.ps1 +++ b/LANCommander/Snippets/Examples/Trim String.ps1 @@ -1,4 +1,4 @@ # Trim a string down to a specified amount of characters -if ($NewName.Length -gt 10) { - $NewName = $NewName.Substring(0, 10); +if ($NewPlayerAlias.Length -gt 10) { + $NewPlayerAlias = $NewPlayerAlias.Substring(0, 10); } \ No newline at end of file diff --git a/LANCommander/Snippets/Examples/Write to File.ps1 b/LANCommander/Snippets/Examples/Write to File.ps1 index e2e2bbf..8dbb621 100644 --- a/LANCommander/Snippets/Examples/Write to File.ps1 +++ b/LANCommander/Snippets/Examples/Write to File.ps1 @@ -1,2 +1,2 @@ # Write contents of a string to a file -Set-Content "$InstallDir\" "Hello world!" \ No newline at end of file +Set-Content "$InstallDirectory\" "Hello world!" \ No newline at end of file diff --git a/LANCommander/Snippets/Functions/Convert-AspectRatio.ps1 b/LANCommander/Snippets/Functions/Convert-AspectRatio.ps1 new file mode 100644 index 0000000..5faeb50 --- /dev/null +++ b/LANCommander/Snippets/Functions/Convert-AspectRatio.ps1 @@ -0,0 +1,2 @@ +# Bounds accessible via $Resolution.Height, $Resolution.Width +$Resolution = Convert-AspectRatio -Width 1280 -Height 800 -AspectRatio (4 / 3) \ No newline at end of file diff --git a/LANCommander/Snippets/Functions/ConvertTo-StringBytes.ps1 b/LANCommander/Snippets/Functions/ConvertTo-StringBytes.ps1 new file mode 100644 index 0000000..18e775b --- /dev/null +++ b/LANCommander/Snippets/Functions/ConvertTo-StringBytes.ps1 @@ -0,0 +1,2 @@ +# Converts a string to a UTF16-encoded byte array. This looks like ASCII characters separated by 0x00 in most cases. +$bytes = ConvertTo-StringBytes -Input "Hello World!" -Utf16 1 -MaxLength 12 \ No newline at end of file diff --git a/LANCommander/Snippets/Functions/Edit-PatchBinary.ps1 b/LANCommander/Snippets/Functions/Edit-PatchBinary.ps1 new file mode 100644 index 0000000..e35312d --- /dev/null +++ b/LANCommander/Snippets/Functions/Edit-PatchBinary.ps1 @@ -0,0 +1 @@ +Edit-PatchBinary -FilePath "$InstallDirectory\" -Offset 0x00 -Data $bytes \ No newline at end of file diff --git a/LANCommander/Snippets/Functions/Get-43Resolution.ps1 b/LANCommander/Snippets/Functions/Get-43Resolution.ps1 deleted file mode 100644 index 7c45514..0000000 --- a/LANCommander/Snippets/Functions/Get-43Resolution.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -function Get-43Resolution([int]$Width, [int]$Height) { - $ratio = 4 / 3 - - if (($Width -gt $Height) -or ($Width -eq $Height)) { - return @{ Width = [math]::Round($ratio * $Height); Height = $Height } - } - - if ($Width -lt $Height) { - return @{ Width = $Width; Height = [math]::Round($Width / $ratio) } - } -} - -# Accessible via $Resolution.Height, $Resolution.Width -$Resolution = Get-43Resolution -Width 1280 -Height 800 \ No newline at end of file diff --git a/LANCommander/Snippets/Functions/Get-AsciiBytes.ps1 b/LANCommander/Snippets/Functions/Get-AsciiBytes.ps1 deleted file mode 100644 index 9696eb2..0000000 --- a/LANCommander/Snippets/Functions/Get-AsciiBytes.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -function Get-AsciiBytes([string]$InputString, [int]$MaxLength) -{ - if ($InputString.Length -gt $MaxLength) - { - $InputString = $InputString.Substring(0, $MaxLength) - } - - $bytes = [System.Text.Encoding]::ASCII.GetBytes($InputString) - $array = @() - $count = 0 - - $extraPadding = $MaxLength - $bytes.Length - - foreach ($byte in $bytes) - { - if ($count -lt $MaxLength) - { - $array += $byte - $count++ - } - } - - # Pad the end with 0x00 to meet our max length - for ($i = $count; $i -lt $MaxLength; $i++) - { - $array += 0x00 - } - - return $array -} \ No newline at end of file diff --git a/LANCommander/Snippets/Functions/Get-GameManifest.ps1 b/LANCommander/Snippets/Functions/Get-GameManifest.ps1 new file mode 100644 index 0000000..5fa659b --- /dev/null +++ b/LANCommander/Snippets/Functions/Get-GameManifest.ps1 @@ -0,0 +1 @@ +$manifest = Get-GameManifest "C:\\Games\\" \ No newline at end of file diff --git a/LANCommander/Snippets/Functions/Get-PrimaryDisplay.ps1 b/LANCommander/Snippets/Functions/Get-PrimaryDisplay.ps1 new file mode 100644 index 0000000..db37f87 --- /dev/null +++ b/LANCommander/Snippets/Functions/Get-PrimaryDisplay.ps1 @@ -0,0 +1,2 @@ +# Bounds are accessible by $Resolution.Width and $Resolution.Height +$Resolution = Get-PrimaryDisplay \ No newline at end of file diff --git a/LANCommander/Snippets/Functions/Patch-Binary.ps1 b/LANCommander/Snippets/Functions/Patch-Binary.ps1 deleted file mode 100644 index 80be124..0000000 --- a/LANCommander/Snippets/Functions/Patch-Binary.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -function Patch-Binary([byte[]]$Data, [int]$Offset, [string]$FilePath) -{ - $bytes = [System.IO.File]::ReadAllBytes($FilePath) - - for ($i = 0; $i -lt $Data.Length; $i++) - { - $bytes[$Offset + $i] = $Data[$i] - } - - [System.IO.File]::WriteAllBytes($FilePath, $bytes) -} \ No newline at end of file diff --git a/LANCommander/Snippets/Functions/Separate-AsciiBytes.ps1 b/LANCommander/Snippets/Functions/Separate-AsciiBytes.ps1 deleted file mode 100644 index d8c86f1..0000000 --- a/LANCommander/Snippets/Functions/Separate-AsciiBytes.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -function Separate-AsciiBytes([byte[]]$Data) -{ - $array = @() - - foreach ($byte in $Data) - { - $array += $byte - $array += 0x00 - } - - return $array -} \ No newline at end of file diff --git a/LANCommander/Snippets/Functions/Write-ReplaceContentInFile.ps1 b/LANCommander/Snippets/Functions/Write-ReplaceContentInFile.ps1 index b50e3b3..c28c8bf 100644 --- a/LANCommander/Snippets/Functions/Write-ReplaceContentInFile.ps1 +++ b/LANCommander/Snippets/Functions/Write-ReplaceContentInFile.ps1 @@ -1,5 +1,2 @@ -function Write-ReplaceContentInFile([string]$Regex, [string]$Replacement, [string]$FilePath) -{ - $content = (Get-Content $FilePath) -replace $Regex, $Replacement - [IO.File]::WriteAllLines($FilePath, $content) -} \ No newline at end of file +# Use regex to replace text within a file. Quotes are escaped by double quoting ("") +Write-ReplaceContentInFile -Regex '^game.setPlayerName "(.+)"' -Replacement "game.setPlayerName ""$NewName""" -FilePath "$InstallDir\" \ No newline at end of file diff --git a/LANCommander/Snippets/Variables/Allocated Key.ps1 b/LANCommander/Snippets/Variables/Allocated Key.ps1 new file mode 100644 index 0000000..0fb4572 --- /dev/null +++ b/LANCommander/Snippets/Variables/Allocated Key.ps1 @@ -0,0 +1 @@ +$AllocatedKey \ No newline at end of file diff --git a/LANCommander/Snippets/Variables/Default Install Directory.ps1 b/LANCommander/Snippets/Variables/Default Install Directory.ps1 new file mode 100644 index 0000000..3f6de2c --- /dev/null +++ b/LANCommander/Snippets/Variables/Default Install Directory.ps1 @@ -0,0 +1 @@ +$DefaultInstallDirectory \ No newline at end of file diff --git a/LANCommander/Snippets/Variables/Display.ps1 b/LANCommander/Snippets/Variables/Display.ps1 deleted file mode 100644 index eca1e0e..0000000 --- a/LANCommander/Snippets/Variables/Display.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -# Accessible via $Display.Width and $Display.Height -Add-Type -AssemblyName System.Windows.Forms -$Display = ([System.Windows.Forms.Screen]::AllScreens | Where-Object Primary).Bounds \ No newline at end of file diff --git a/LANCommander/Snippets/Variables/Game Install Directory.ps1 b/LANCommander/Snippets/Variables/Game Install Directory.ps1 new file mode 100644 index 0000000..ac9ce15 --- /dev/null +++ b/LANCommander/Snippets/Variables/Game Install Directory.ps1 @@ -0,0 +1 @@ +$OldPlayerAlias \ No newline at end of file diff --git a/LANCommander/Snippets/Variables/Game Manifest.ps1 b/LANCommander/Snippets/Variables/Game Manifest.ps1 new file mode 100644 index 0000000..d839b38 --- /dev/null +++ b/LANCommander/Snippets/Variables/Game Manifest.ps1 @@ -0,0 +1 @@ +$GameManifest \ No newline at end of file diff --git a/LANCommander/Snippets/Variables/InstallDir.ps1 b/LANCommander/Snippets/Variables/InstallDir.ps1 deleted file mode 100644 index 262b772..0000000 --- a/LANCommander/Snippets/Variables/InstallDir.ps1 +++ /dev/null @@ -1 +0,0 @@ -$InstallDir = $PSScriptRoot \ No newline at end of file diff --git a/LANCommander/Snippets/Variables/NewName.ps1 b/LANCommander/Snippets/Variables/New Player Alias.ps1 similarity index 100% rename from LANCommander/Snippets/Variables/NewName.ps1 rename to LANCommander/Snippets/Variables/New Player Alias.ps1 diff --git a/LANCommander/Snippets/Variables/OldName.ps1 b/LANCommander/Snippets/Variables/Old Player Alias.ps1 similarity index 100% rename from LANCommander/Snippets/Variables/OldName.ps1 rename to LANCommander/Snippets/Variables/Old Player Alias.ps1 diff --git a/LANCommander/Snippets/Variables/Server Address.ps1 b/LANCommander/Snippets/Variables/Server Address.ps1 new file mode 100644 index 0000000..e244bae --- /dev/null +++ b/LANCommander/Snippets/Variables/Server Address.ps1 @@ -0,0 +1 @@ +$ServerAddress \ No newline at end of file From 35fbdd008a1bc27ece1883aa6df267f13108f7b5 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 17 Nov 2023 00:54:38 -0600 Subject: [PATCH 51/93] Add migration to delete old snippets --- ...64657_DeleteDeprecatedSnippets.Designer.cs | 1691 +++++++++++++++++ ...20231117064657_DeleteDeprecatedSnippets.cs | 43 + 2 files changed, 1734 insertions(+) create mode 100644 LANCommander/Migrations/20231117064657_DeleteDeprecatedSnippets.Designer.cs create mode 100644 LANCommander/Migrations/20231117064657_DeleteDeprecatedSnippets.cs diff --git a/LANCommander/Migrations/20231117064657_DeleteDeprecatedSnippets.Designer.cs b/LANCommander/Migrations/20231117064657_DeleteDeprecatedSnippets.Designer.cs new file mode 100644 index 0000000..f143973 --- /dev/null +++ b/LANCommander/Migrations/20231117064657_DeleteDeprecatedSnippets.Designer.cs @@ -0,0 +1,1691 @@ +// +using System; +using LANCommander.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace LANCommander.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20231117064657_DeleteDeprecatedSnippets")] + partial class DeleteDeprecatedSnippets + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.13") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true); + + modelBuilder.Entity("CategoryGame", b => + { + b.Property("CategoriesId") + .HasColumnType("TEXT"); + + b.Property("GamesId") + .HasColumnType("TEXT"); + + b.HasKey("CategoriesId", "GamesId"); + + b.HasIndex("GamesId"); + + b.ToTable("CategoryGame"); + }); + + modelBuilder.Entity("GameDeveloper", b => + { + b.Property("DeveloperId") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.HasKey("DeveloperId", "GameId"); + + b.HasIndex("GameId"); + + b.ToTable("GameDeveloper"); + }); + + modelBuilder.Entity("GameGenre", b => + { + b.Property("GamesId") + .HasColumnType("TEXT"); + + b.Property("GenresId") + .HasColumnType("TEXT"); + + b.HasKey("GamesId", "GenresId"); + + b.HasIndex("GenresId"); + + b.ToTable("GameGenre"); + }); + + modelBuilder.Entity("GamePublisher", b => + { + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("PublisherId") + .HasColumnType("TEXT"); + + b.HasKey("GameId", "PublisherId"); + + b.HasIndex("PublisherId"); + + b.ToTable("GamePublisher"); + }); + + modelBuilder.Entity("GameRedistributable", b => + { + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("RedistributableId") + .HasColumnType("TEXT"); + + b.HasKey("GameId", "RedistributableId"); + + b.HasIndex("RedistributableId"); + + b.ToTable("GameRedistributable"); + }); + + modelBuilder.Entity("GameTag", b => + { + b.Property("GamesId") + .HasColumnType("TEXT"); + + b.Property("TagsId") + .HasColumnType("TEXT"); + + b.HasKey("GamesId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("GameTag"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Action", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Arguments") + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("PrimaryAction") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("WorkingDirectory") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Actions"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Archive", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Changelog") + .HasColumnType("TEXT"); + + b.Property("CompressedSize") + .HasColumnType("INTEGER"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("LastVersionId") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RedistributableId") + .HasColumnType("TEXT"); + + b.Property("UncompressedSize") + .HasColumnType("INTEGER"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("LastVersionId"); + + b.HasIndex("RedistributableId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Archive"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ParentId") + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ParentId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Company", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Companies"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Game", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("DirectoryName") + .HasColumnType("TEXT"); + + b.Property("IGDBId") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ReleasedOn") + .HasColumnType("TEXT"); + + b.Property("Singleplayer") + .HasColumnType("INTEGER"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("ValidKeyRegex") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Games"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.GameSave", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.HasIndex("UserId"); + + b.ToTable("GameSaves"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Genre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Genres"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Key", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AllocationMethod") + .HasColumnType("INTEGER"); + + b.Property("ClaimedByComputerName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("ClaimedByIpv4Address") + .HasMaxLength(15) + .HasColumnType("TEXT"); + + b.Property("ClaimedByMacAddress") + .HasMaxLength(17) + .HasColumnType("TEXT"); + + b.Property("ClaimedByUserId") + .HasColumnType("TEXT"); + + b.Property("ClaimedOn") + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClaimedByUserId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Keys"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Media", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("FileId") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("SourceUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Media"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.MultiplayerMode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("GameId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MaxPlayers") + .HasColumnType("INTEGER"); + + b.Property("MinPlayers") + .HasColumnType("INTEGER"); + + b.Property("NetworkProtocol") + .HasColumnType("INTEGER"); + + b.Property("Spectators") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("MultiplayerModes"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Redistributable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Redistributables"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("LANCommander.Data.Models.SavePath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("SavePaths"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Script", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Contents") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RedistributableId") + .HasColumnType("TEXT"); + + b.Property("RequiresAdmin") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("RedistributableId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Scripts"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Arguments") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Autostart") + .HasColumnType("INTEGER"); + + b.Property("AutostartDelay") + .HasColumnType("INTEGER"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OnStartScriptPath") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OnStopScriptPath") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("UseShellExecute") + .HasColumnType("INTEGER"); + + b.Property("WorkingDirectory") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.ServerConsole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Host") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ServerId1") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ServerId"); + + b.HasIndex("ServerId1"); + + b.HasIndex("UpdatedById"); + + b.ToTable("ServerConsoles"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.ServerHttpPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("LocalPath") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ServerId") + .HasColumnType("TEXT"); + + b.Property("ServerId1") + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ServerId"); + + b.HasIndex("ServerId1"); + + b.HasIndex("UpdatedById"); + + b.ToTable("ServerHttpPath"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("Alias") + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("ApprovedOn") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("RefreshToken") + .HasColumnType("TEXT"); + + b.Property("RefreshTokenExpiration") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("CategoryGame", b => + { + b.HasOne("LANCommander.Data.Models.Category", null) + .WithMany() + .HasForeignKey("CategoriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Game", null) + .WithMany() + .HasForeignKey("GamesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GameDeveloper", b => + { + b.HasOne("LANCommander.Data.Models.Company", null) + .WithMany() + .HasForeignKey("DeveloperId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Game", null) + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GameGenre", b => + { + b.HasOne("LANCommander.Data.Models.Game", null) + .WithMany() + .HasForeignKey("GamesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GamePublisher", b => + { + b.HasOne("LANCommander.Data.Models.Game", null) + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Company", null) + .WithMany() + .HasForeignKey("PublisherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GameRedistributable", b => + { + b.HasOne("LANCommander.Data.Models.Game", null) + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Redistributable", null) + .WithMany() + .HasForeignKey("RedistributableId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GameTag", b => + { + b.HasOne("LANCommander.Data.Models.Game", null) + .WithMany() + .HasForeignKey("GamesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Action", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("Actions") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Archive", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("Archives") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("LANCommander.Data.Models.Archive", "LastVersion") + .WithMany() + .HasForeignKey("LastVersionId"); + + b.HasOne("LANCommander.Data.Models.Redistributable", "Redistributable") + .WithMany("Archives") + .HasForeignKey("RedistributableId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("LastVersion"); + + b.Navigation("Redistributable"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Category", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Category", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Parent"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Company", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Game", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.GameSave", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("GameSaves") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.HasOne("LANCommander.Data.Models.User", "User") + .WithMany("GameSaves") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Genre", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Key", b => + { + b.HasOne("LANCommander.Data.Models.User", "ClaimedByUser") + .WithMany() + .HasForeignKey("ClaimedByUserId"); + + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("Keys") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("ClaimedByUser"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Media", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("Media") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.MultiplayerMode", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("MultiplayerModes") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Redistributable", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.SavePath", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("SavePaths") + .HasForeignKey("GameId"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Script", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("Scripts") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("LANCommander.Data.Models.Redistributable", "Redistributable") + .WithMany("Scripts") + .HasForeignKey("RedistributableId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("Redistributable"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Server", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("Servers") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.ServerConsole", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Server", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Server", null) + .WithMany("ServerConsoles") + .HasForeignKey("ServerId1"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Server"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.ServerHttpPath", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Server", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Server", null) + .WithMany("HttpPaths") + .HasForeignKey("ServerId1"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Server"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Tag", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("LANCommander.Data.Models.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("LANCommander.Data.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("LANCommander.Data.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("LANCommander.Data.Models.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("LANCommander.Data.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Category", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Game", b => + { + b.Navigation("Actions"); + + b.Navigation("Archives"); + + b.Navigation("GameSaves"); + + b.Navigation("Keys"); + + b.Navigation("Media"); + + b.Navigation("MultiplayerModes"); + + b.Navigation("SavePaths"); + + b.Navigation("Scripts"); + + b.Navigation("Servers"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Redistributable", b => + { + b.Navigation("Archives"); + + b.Navigation("Scripts"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Server", b => + { + b.Navigation("HttpPaths"); + + b.Navigation("ServerConsoles"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.User", b => + { + b.Navigation("GameSaves"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LANCommander/Migrations/20231117064657_DeleteDeprecatedSnippets.cs b/LANCommander/Migrations/20231117064657_DeleteDeprecatedSnippets.cs new file mode 100644 index 0000000..cc3554c --- /dev/null +++ b/LANCommander/Migrations/20231117064657_DeleteDeprecatedSnippets.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LANCommander.Migrations +{ + /// + public partial class DeleteDeprecatedSnippets : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + string[] snippetsToRemove = new string[] + { + "Examples\\Replace Content In File.ps1", + "Examples\\Separate ASCII Bytes.ps1", + "Examples\\String to ASCII Bytes.ps1", + "Functions\\Get-43Resolution.ps1", + "Functions\\Get-AsciiBytes.ps1", + "Functions\\Patch-Binary.ps1", + "Functions\\Separate-AsciiBytes.ps1", + "Variables\\Display.ps1", + "Variables\\InstallDir.ps1", + "Variables\\NewName.ps1", + "Variables\\OldName.ps1", + }; + + foreach (var snippet in snippetsToRemove) + { + var path = Path.Combine("Snippets", snippet); + + if (File.Exists(path)) + File.Delete(path); + } + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} From 14600f5d709f5cafbb5bb4130e828179d057d0db Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 17 Nov 2023 01:16:45 -0600 Subject: [PATCH 52/93] Alter migration to try to automatically convert scripts to new variables where arguments may be missing --- .../Migrations/20231117064657_DeleteDeprecatedSnippets.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/LANCommander/Migrations/20231117064657_DeleteDeprecatedSnippets.cs b/LANCommander/Migrations/20231117064657_DeleteDeprecatedSnippets.cs index cc3554c..fd076f9 100644 --- a/LANCommander/Migrations/20231117064657_DeleteDeprecatedSnippets.cs +++ b/LANCommander/Migrations/20231117064657_DeleteDeprecatedSnippets.cs @@ -32,6 +32,14 @@ namespace LANCommander.Migrations if (File.Exists(path)) File.Delete(path); } + + migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$NewName = $args[0]' || char(13) || char(10), '')"); + migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$OldName = \"\"' || char(13) || char(10) || 'if ($args[1]) {' || char(13) || char(10) || char(9) || '$OldName = $args[1]' || char(13) || char(10) || '}' || char(13) || char(10), '')"); + migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$InstallDir = $PSScriptRoot' || char(13) || char(10), '')"); + migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$InstallDir', '$InstallDirectory')"); + migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$NewName', '$NewPlayerAlias')"); + migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$OldName', '$OldPlayerAlias')"); + } /// From 5324723cee5a59a705300ccd7e8c7142a92e83a2 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Fri, 17 Nov 2023 02:28:46 -0600 Subject: [PATCH 53/93] Start tracking play sessions --- .../LANCommanderLibraryPlugin.cs | 18 +- LANCommander.SDK/Client.cs | 24 + .../Controllers/Api/PlaySessionsController.cs | 53 + LANCommander/Data/DatabaseContext.cs | 14 + LANCommander/Data/Models/Game.cs | 1 + LANCommander/Data/Models/PlaySession.cs | 27 + LANCommander/Data/Models/User.cs | 3 + ...20231117081347_AddPlaySessions.Designer.cs | 1769 +++++++++++++++++ .../20231117081347_AddPlaySessions.cs | 82 + .../DatabaseContextModelSnapshot.cs | 78 + LANCommander/Program.cs | 1 + LANCommander/Services/PlaySessionService.cs | 41 + 12 files changed, 2109 insertions(+), 2 deletions(-) create mode 100644 LANCommander/Controllers/Api/PlaySessionsController.cs create mode 100644 LANCommander/Data/Models/PlaySession.cs create mode 100644 LANCommander/Migrations/20231117081347_AddPlaySessions.Designer.cs create mode 100644 LANCommander/Migrations/20231117081347_AddPlaySessions.cs create mode 100644 LANCommander/Services/PlaySessionService.cs diff --git a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs index 21926a0..5d069e5 100644 --- a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs +++ b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs @@ -331,12 +331,26 @@ namespace LANCommander.PlaynitePlugin public override void OnGameStarting(OnGameStartingEventArgs args) { - SaveController.Download(args.Game); + if (args.Game.PluginId == Id) + { + SaveController.Download(args.Game); + + var gameId = Guid.Parse(args.Game.GameId); + + LANCommanderClient.StartPlaySession(gameId); + } } public override void OnGameStopped(OnGameStoppedEventArgs args) { - SaveController.Upload(args.Game); + if (args.Game.PluginId == Id) + { + SaveController.Upload(args.Game); + + var gameId = Guid.Parse(args.Game.GameId); + + LANCommanderClient.EndPlaySession(gameId); + } } public override IEnumerable GetTopPanelItems() diff --git a/LANCommander.SDK/Client.cs b/LANCommander.SDK/Client.cs index 4f8bdaa..68c09c0 100644 --- a/LANCommander.SDK/Client.cs +++ b/LANCommander.SDK/Client.cs @@ -51,6 +51,16 @@ namespace LANCommander.SDK return response.Data; } + private T PostRequest(string route) + { + var request = new RestRequest(route) + .AddHeader("Authorization", $"Bearer {Token.AccessToken}"); + + var response = ApiClient.Post(request); + + return response.Data; + } + private T GetRequest(string route) { var request = new RestRequest(route) @@ -364,6 +374,20 @@ namespace LANCommander.SDK return alias; } + public void StartPlaySession(Guid gameId) + { + Logger?.LogTrace("Starting a game session..."); + + PostRequest($"/api/PlaySession/Start/{gameId}"); + } + + public void EndPlaySession(Guid gameId) + { + Logger?.LogTrace("Ending a game session..."); + + PostRequest($"/api/PlaySession/End/{gameId}"); + } + private string GetMacAddress() { return NetworkInterface.GetAllNetworkInterfaces() diff --git a/LANCommander/Controllers/Api/PlaySessionsController.cs b/LANCommander/Controllers/Api/PlaySessionsController.cs new file mode 100644 index 0000000..b8631a3 --- /dev/null +++ b/LANCommander/Controllers/Api/PlaySessionsController.cs @@ -0,0 +1,53 @@ +using LANCommander.Data.Models; +using LANCommander.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; + +namespace LANCommander.Controllers.Api +{ + [Authorize(AuthenticationSchemes = "Bearer")] + [Route("api/[controller]")] + [ApiController] + public class PlaySessionsController : ControllerBase + { + private readonly PlaySessionService PlaySessionService; + private readonly GameService GameService; + private readonly UserManager UserManager; + + public PlaySessionsController(PlaySessionService playSessionService, GameService gameService, UserManager userManager) + { + PlaySessionService = playSessionService; + GameService = gameService; + UserManager = userManager; + } + + [HttpPost("Start/{id}")] + public async Task Start(Guid id) + { + var user = await UserManager.FindByNameAsync(User.Identity.Name); + var game = await GameService.Get(id); + + if (game == null || user == null) + return BadRequest(); + + await PlaySessionService.StartSession(game.Id, user.Id); + + return Ok(); + } + + [HttpPost("End/{id}")] + public async Task End(Guid id) + { + var user = await UserManager.FindByNameAsync(User.Identity.Name); + var game = await GameService.Get(id); + + if (game == null || user == null) + return BadRequest(); + + await PlaySessionService.EndSession(game.Id, user.Id); + + return Ok(); + } + } +} diff --git a/LANCommander/Data/DatabaseContext.cs b/LANCommander/Data/DatabaseContext.cs index d5cc256..4c8de21 100644 --- a/LANCommander/Data/DatabaseContext.cs +++ b/LANCommander/Data/DatabaseContext.cs @@ -107,6 +107,18 @@ namespace LANCommander.Data .IsRequired(true) .OnDelete(DeleteBehavior.NoAction); + builder.Entity() + .HasMany(u => u.PlaySessions) + .WithOne(ps => ps.User) + .IsRequired(true) + .OnDelete(DeleteBehavior.Cascade); + + builder.Entity() + .HasMany(g => g.PlaySessions) + .WithOne(ps => ps.Game) + .IsRequired(true) + .OnDelete(DeleteBehavior.NoAction); + builder.Entity() .HasOne(s => s.Game) .WithMany(g => g.Servers) @@ -152,6 +164,8 @@ namespace LANCommander.Data public DbSet? GameSaves { get; set; } + public DbSet? PlaySessions { get; set; } + public DbSet? Servers { get; set; } public DbSet? ServerConsoles { get; set; } diff --git a/LANCommander/Data/Models/Game.cs b/LANCommander/Data/Models/Game.cs index 7c86d47..54a285c 100644 --- a/LANCommander/Data/Models/Game.cs +++ b/LANCommander/Data/Models/Game.cs @@ -31,6 +31,7 @@ namespace LANCommander.Data.Models public virtual ICollection? Archives { get; set; } public virtual ICollection