diff --git a/LANCommander.Playnite.Extension/InstallController.cs b/LANCommander.Playnite.Extension/InstallController.cs index 7c7bb86..c762c86 100644 --- a/LANCommander.Playnite.Extension/InstallController.cs +++ b/LANCommander.Playnite.Extension/InstallController.cs @@ -1,11 +1,13 @@ 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; using System; using System.Diagnostics; +using System.IO; using System.Linq; namespace LANCommander.PlaynitePlugin @@ -116,6 +118,10 @@ namespace LANCommander.PlaynitePlugin InstallDirectory = installDirectory, }; + RunInstallScript(installDirectory); + RunNameChangeScript(installDirectory); + RunKeyChangeScript(installDirectory); + InvokeOnInstalled(new GameInstalledEventArgs(installInfo)); } else if (result.Canceled) @@ -130,5 +136,76 @@ namespace LANCommander.PlaynitePlugin else if (result.Error != null) throw result.Error; } + + private int RunInstallScript(string installDirectory) + { + var manifest = ManifestHelper.Read(installDirectory); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install); + + 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.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install)); + + return script.Execute(); + } + + return 0; + } + + private int RunNameChangeScript(string installDirectory) + { + var manifest = ManifestHelper.Read(installDirectory); + 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", Plugin.Settings.InstallDirectory); + script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress); + script.AddVariable("OldPlayerAlias", ""); + script.AddVariable("NewPlayerAlias", Plugin.Settings.PlayerName); + + script.UseFile(path); + + return script.Execute(); + } + + return 0; + } + + private int RunKeyChangeScript(string installDirectory) + { + var manifest = ManifestHelper.Read(installDirectory); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange); + + if (File.Exists(path)) + { + 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(path); + + return script.Execute(); + } + + return 0; + } } } 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 diff --git a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs index e118e19..21926a0 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,77 @@ namespace LANCommander.PlaynitePlugin PlayniteApi.Database.Games.Update(game); } + + private int RunInstallScript(string installDirectory) + { + var manifest = ManifestHelper.Read(installDirectory); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install); + + 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.UseFile(path); + + return script.Execute(); + } + + return 0; + } + + private int RunNameChangeScript(string installDirectory, string oldPlayerAlias, string newPlayerAlias) + { + var manifest = ManifestHelper.Read(installDirectory); + 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", Settings.InstallDirectory); + script.AddVariable("ServerAddress", Settings.ServerAddress); + script.AddVariable("OldPlayerAlias", oldPlayerAlias); + script.AddVariable("NewPlayerAlias", newPlayerAlias); + + script.UseFile(path); + + return script.Execute(); + } + + return 0; + } + + private int RunKeyChangeScript(string installDirectory, string key = "") + { + var manifest = ManifestHelper.Read(installDirectory); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange); + + if (File.Exists(path)) + { + 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(path); + + return script.Execute(); + } + + return 0; + } } } diff --git a/LANCommander.Playnite.Extension/UninstallController.cs b/LANCommander.Playnite.Extension/UninstallController.cs index eb23735..df9788f 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,35 @@ 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) && File.Exists(scriptPath)) + { + var manifest = ManifestHelper.Read(Game.InstallDirectory); + var script = new PowerShellScript(); + + 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.PowerShell.Tests/Cmdlets.cs b/LANCommander.PowerShell.Tests/Cmdlets.cs new file mode 100644 index 0000000..b56e654 --- /dev/null +++ b/LANCommander.PowerShell.Tests/Cmdlets.cs @@ -0,0 +1,64 @@ +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()); + } + + [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); + } + } +} 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.PowerShell/Cmdlets/Convert-AspectRatio.cs b/LANCommander.PowerShell/Cmdlets/Convert-AspectRatio.cs new file mode 100644 index 0000000..a31949a --- /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 : Cmdlet + { + [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/ConvertFrom-SerializedBase64.cs b/LANCommander.PowerShell/Cmdlets/ConvertFrom-SerializedBase64.cs new file mode 100644 index 0000000..d600b38 --- /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 : Cmdlet + { + [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/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/Cmdlets/ConvertTo-StringBytes.cs b/LANCommander.PowerShell/Cmdlets/ConvertTo-StringBytes.cs new file mode 100644 index 0000000..dced3b9 --- /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 : Cmdlet + { + [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..95e2e46 --- /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 : Cmdlet + { + [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..d11ea2b --- /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 : Cmdlet + { + [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..93c838e --- /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 : Cmdlet + { + protected override void ProcessRecord() + { + var screens = Screen.AllScreens; + + WriteObject(screens.First(s => s.Primary)); + } + } +} diff --git a/LANCommander.PowerShell/Cmdlets/Install-Game.cs b/LANCommander.PowerShell/Cmdlets/Install-Game.cs new file mode 100644 index 0000000..c24b5ed --- /dev/null +++ b/LANCommander.PowerShell/Cmdlets/Install-Game.cs @@ -0,0 +1,132 @@ +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.Install, "Game")] + [OutputType(typeof(string))] + public class InstallGameCmdlet : Cmdlet + { + [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(); + } + }; + + var installDirectory = gameManager.Install(Id); + + stopwatch.Stop(); + + RunInstallScript(installDirectory); + RunNameChangeScript(installDirectory); + RunKeyChangeScript(installDirectory); + + WriteObject(installDirectory); + } + + private int RunInstallScript(string installDirectory) + { + var manifest = ManifestHelper.Read(installDirectory); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install); + + 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.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 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.UseFile(path); + + return script.Execute(); + } + + return 0; + } + + private int RunKeyChangeScript(string installDirectory) + { + var manifest = ManifestHelper.Read(installDirectory); + var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange); + + if (File.Exists(path)) + { + 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(path); + + return script.Execute(); + } + + return 0; + } + } +} diff --git a/LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs b/LANCommander.PowerShell/Cmdlets/Uninstall-Game.cs new file mode 100644 index 0000000..27b3dc4 --- /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 : Cmdlet + { + [Parameter(Mandatory = true)] + public string InstallDirectory { get; set; } + + 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); + } + } +} diff --git a/LANCommander.PowerShell/Cmdlets/Write-GameManifest.cs b/LANCommander.PowerShell/Cmdlets/Write-GameManifest.cs new file mode 100644 index 0000000..28ff74d --- /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 : Cmdlet + { + [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..3a96f72 --- /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 : Cmdlet + { + [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..b59a2f6 --- /dev/null +++ b/LANCommander.PowerShell/LANCommander.PowerShell.csproj @@ -0,0 +1,75 @@ + + + + + Debug + AnyCPU + {807943BF-0C7D-4ED3-8393-CFEE64E3138C} + Library + Properties + LANCommander.PowerShell + LANCommander.PowerShell + v4.6.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 + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + {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 0000000..7237c50 Binary files /dev/null and b/LANCommander.PowerShell/LANCommander.PowerShell.psd1 differ 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.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; } diff --git a/LANCommander.SDK/GameManager.cs b/LANCommander.SDK/GameManager.cs index 056b563..a9163cc 100644 --- a/LANCommander.SDK/GameManager.cs +++ b/LANCommander.SDK/GameManager.cs @@ -91,36 +91,11 @@ 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; } 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"); 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(); 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) diff --git a/LANCommander.SDK/Helpers/ScriptHelper.cs b/LANCommander.SDK/Helpers/ScriptHelper.cs index 3232e57..3fde3f8 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; } @@ -37,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); @@ -46,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); + } } } 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/PowerShellScript.cs b/LANCommander.SDK/PowerShell/PowerShellScript.cs new file mode 100644 index 0000000..1d86f51 --- /dev/null +++ b/LANCommander.SDK/PowerShell/PowerShellScript.cs @@ -0,0 +1,184 @@ +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 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) + { + 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 AddModule(string path) + { + Modules.Add(path); + + 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 module in Modules) + { + scriptBuilder.AppendLine($"Import-Module \"{module}\""); + } + + foreach (var variable in Variables) + { + scriptBuilder.AppendLine($"${variable.Name} = ConvertFrom-SerializedBase64 \"{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; + } + } +} 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); - } - } -} 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(); + } } } diff --git a/LANCommander.sln b/LANCommander.sln index 529f8cc..f77170c 100644 --- a/LANCommander.sln +++ b/LANCommander.sln @@ -11,6 +11,10 @@ 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 +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 @@ -33,6 +37,14 @@ 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 + {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 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..fd076f9 --- /dev/null +++ b/LANCommander/Migrations/20231117064657_DeleteDeprecatedSnippets.cs @@ -0,0 +1,51 @@ +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); + } + + 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')"); + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} 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