Compare commits
203 Commits
Author | SHA1 | Date |
---|---|---|
Pat Hartl | 5ef16fc4cc | |
Pat Hartl | db8d3e4bf6 | |
Pat Hartl | f2462c0d20 | |
Pat Hartl | b87fe92c63 | |
Pat Hartl | c735556281 | |
Pat Hartl | d4bcde9d28 | |
Pat Hartl | 920b6b26f7 | |
Pat Hartl | d31fccc9f3 | |
Pat Hartl | c48d5b5d59 | |
Pat Hartl | dfb9f51acd | |
Pat Hartl | 03b0d9da93 | |
Pat Hartl | e2883322e6 | |
Pat Hartl | 50eff6784f | |
Pat Hartl | 171f82df3e | |
Pat Hartl | 8b7be9157e | |
Pat Hartl | 96c6d58486 | |
Pat Hartl | fd6e3e56a4 | |
Pat Hartl | d4fe8db48d | |
Pat Hartl | 281353b86c | |
Pat Hartl | ba46970406 | |
Pat Hartl | 6fb77adb63 | |
Pat Hartl | b38f2b6cef | |
Pat Hartl | 7568688c97 | |
Pat Hartl | a8c62151f3 | |
Pat Hartl | 32e135de7b | |
Pat Hartl | c37095d4c4 | |
Pat Hartl | a716bafc4d | |
Pat Hartl | e4531321b1 | |
Pat Hartl | 78168e94a5 | |
Pat Hartl | 82779bcc72 | |
Pat Hartl | 7b625b2f60 | |
Pat Hartl | 74d8790ee2 | |
Pat Hartl | 745cf0cce6 | |
Pat Hartl | 7c22aaa139 | |
Pat Hartl | eb73885991 | |
Pat Hartl | ce80dfa51f | |
Pat Hartl | 349001d8f6 | |
Pat Hartl | d705b34f84 | |
Pat Hartl | 0246ee017c | |
Pat Hartl | 818160d658 | |
Pat Hartl | 035c98cd18 | |
Pat Hartl | d3e13aee9e | |
Pat Hartl | ce402cf5c1 | |
Pat Hartl | 3dbee36886 | |
Pat Hartl | 7793c9a1e8 | |
Pat Hartl | c4c25ad85b | |
Pat Hartl | 1d2f82fdef | |
Pat Hartl | a533e9ad8c | |
Pat Hartl | c3a5edbe46 | |
Pat Hartl | 8cc97f9bdb | |
Pat Hartl | 875b7b7caa | |
Pat Hartl | 6cc947b47e | |
Pat Hartl | a450ac4a18 | |
Pat Hartl | ffa24dbecc | |
Pat Hartl | d0d6701380 | |
Pat Hartl | ab67092c2f | |
Pat Hartl | d97e1f48b3 | |
Pat Hartl | 50badc981b | |
Pat Hartl | b863080842 | |
Pat Hartl | 8b3f2c6cde | |
Pat Hartl | 70674f900e | |
Pat Hartl | f21bf4801e | |
Pat Hartl | ead2c9c3f1 | |
Pat Hartl | fe0bdf31f6 | |
Pat Hartl | 282a1f7c36 | |
Pat Hartl | 5e3384b4fd | |
Pat Hartl | bc30cc911a | |
Pat Hartl | 737f2bec84 | |
Pat Hartl | a47b77cc5c | |
Pat Hartl | f19ef09ff8 | |
Pat Hartl | 8a408f3a18 | |
Pat Hartl | 96fd12a72f | |
Pat Hartl | 1f870366a4 | |
Pat Hartl | 2a80964fe1 | |
Pat Hartl | 6a3c55f669 | |
Pat Hartl | 1f03860360 | |
Pat Hartl | 314a785fee | |
Pat Hartl | 09d23bcb78 | |
Pat Hartl | 6b005eb384 | |
Pat Hartl | 39dd24e0ba | |
Pat Hartl | 5324723cee | |
Pat Hartl | aff2e991ed | |
Pat Hartl | 14600f5d70 | |
Pat Hartl | 35fbdd008a | |
Pat Hartl | aed9935b16 | |
Pat Hartl | 2cb7013120 | |
Pat Hartl | 7649e63195 | |
Pat Hartl | e723a5345b | |
Pat Hartl | 982227cf1f | |
Pat Hartl | 8abc2fa15e | |
Pat Hartl | ed1b4973d3 | |
Pat Hartl | 196feedded | |
Pat Hartl | 2282d9b013 | |
Pat Hartl | 1b72d9002a | |
Pat Hartl | a9f3b7a39d | |
Pat Hartl | 986fb87db1 | |
Pat Hartl | f7fa7aa9f3 | |
Pat Hartl | 49c4b10cf9 | |
Pat Hartl | 8bd422249e | |
Pat Hartl | 26f03f61fc | |
Pat Hartl | 839e9b4935 | |
Pat Hartl | c71cf9fedd | |
Pat Hartl | 86211a7500 | |
Pat Hartl | 35f6dadf9c | |
Pat Hartl | 03828bea60 | |
Pat Hartl | 8d85aca0a7 | |
Pat Hartl | 16dc60b90a | |
Pat Hartl | eb05364542 | |
Pat Hartl | 97f459eaff | |
Pat Hartl | cb9f31a00a | |
Pat Hartl | bf2c9ea45a | |
Pat Hartl | 29dcebb70f | |
Pat Hartl | baa2b9b206 | |
Pat Hartl | dc2eff4972 | |
Pat Hartl | ae23f621c2 | |
Pat Hartl | ee62bdf2a1 | |
Pat Hartl | 5fb4fadfb4 | |
Pat Hartl | 1a0cff3914 | |
Pat Hartl | 227411a558 | |
Pat Hartl | 7c97a3db57 | |
Pat Hartl | 6f7c17493c | |
Pat Hartl | 81e4848407 | |
Pat Hartl | 1ede37c031 | |
Pat Hartl | bb980cc063 | |
Pat Hartl | 47bb054fd1 | |
Pat Hartl | ea337dfea1 | |
Pat Hartl | 52a5f5866f | |
Pat Hartl | b77e7f6e53 | |
Pat Hartl | 20de9d6cae | |
Pat Hartl | 5237e88612 | |
Pat Hartl | 73b542856a | |
Pat Hartl | 39f2d4b212 | |
Pat Hartl | e53709334c | |
Pat Hartl | a679fae0cb | |
Pat Hartl | ff6f9997f5 | |
Pat Hartl | 5d5e137e18 | |
Pat Hartl | 81f8d55694 | |
Pat Hartl | c4793daf07 | |
Pat Hartl | 5c4f81cf80 | |
Pat Hartl | d111be9828 | |
Pat Hartl | 63ae3a4f2a | |
Pat Hartl | 4b40445bac | |
Pat Hartl | 25de79eeed | |
Pat Hartl | 16ba48ed6c | |
Pat Hartl | 508f5c18fb | |
Pat Hartl | 53276542e8 | |
Pat Hartl | aec4342188 | |
Pat Hartl | 2d86ff2518 | |
Pat Hartl | 4393c8fdff | |
Pat Hartl | bd15ceaa5b | |
Pat Hartl | 03626a75d0 | |
Pat Hartl | 78fb812a74 | |
Pat Hartl | 4fb11c1dd7 | |
Pat Hartl | d6eff92835 | |
Pat Hartl | 3f3d5b718b | |
Pat Hartl | 1689cab3b3 | |
Pat Hartl | f275d3478b | |
Pat Hartl | f0c8296b6e | |
Pat Hartl | 37f9027a80 | |
Pat Hartl | 14a92bdc3e | |
Pat Hartl | 739453c8bc | |
Pat Hartl | 82886221fc | |
Pat Hartl | 0fc8d756b3 | |
Pat Hartl | 262e8cd468 | |
Pat Hartl | d51eab151a | |
Pat Hartl | 499b0c910a | |
Pat Hartl | 8c61a7e3b5 | |
Pat Hartl | fd3f6c24b1 | |
Pat Hartl | fa57cc21ad | |
Pat Hartl | 5a99f58f81 | |
Pat Hartl | b23df9b2ad | |
Pat Hartl | ff9ec5a17b | |
Pat Hartl | 06188d5800 | |
Pat Hartl | 5e47d8fa3d | |
Pat Hartl | 4dfed69a91 | |
Pat Hartl | b4405d3034 | |
Pat Hartl | e4cf2aabfe | |
Pat Hartl | af265e9d34 | |
Pat Hartl | 9e1b8ad7e3 | |
Pat Hartl | 9327511245 | |
Pat Hartl | e3a08bc9c3 | |
Pat Hartl | a3b1dabbb1 | |
Pat Hartl | 2287ac9922 | |
Pat Hartl | 539ddb2f2e | |
Pat Hartl | a00d0b3b42 | |
Pat Hartl | 80bd7dc66c | |
Pat Hartl | 4b7e72b343 | |
Pat Hartl | 04b8d45af4 | |
Pat Hartl | 3d86a617a9 | |
Pat Hartl | 05116a65fb | |
Pat Hartl | c477782ca8 | |
Pat Hartl | ae14ceb306 | |
Pat Hartl | 35684c0a54 | |
Pat Hartl | 8f853fab56 | |
Pat Hartl | 07a41aeaf5 | |
Pat Hartl | f94e5f63a8 | |
Pat Hartl | b32451d216 | |
Pat Hartl | e00aa069fa | |
Pat Hartl | f376056eeb | |
Pat Hartl | 35ca8391c6 | |
Pat Hartl | 54b7fec96b | |
Pat Hartl | ebcb943e36 | |
Pat Hartl | 0c7b6f3b56 |
|
@ -352,3 +352,5 @@ Upload/
|
|||
LANCommander/Icon/
|
||||
LANCommander/Settings.yml
|
||||
LANCommander/Saves/
|
||||
LANCommander/Media/
|
||||
LANCommander/Uploads/
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
using LANCommander.PlaynitePlugin.Helpers;
|
||||
using LANCommander.SDK.Enums;
|
||||
using LANCommander.SDK.Extensions;
|
||||
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 SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
{
|
||||
|
@ -20,15 +17,11 @@ namespace LANCommander.PlaynitePlugin
|
|||
public static readonly ILogger Logger = LogManager.GetLogger();
|
||||
|
||||
private LANCommanderLibraryPlugin Plugin;
|
||||
private PowerShellRuntime PowerShellRuntime;
|
||||
private Playnite.SDK.Models.Game PlayniteGame;
|
||||
|
||||
public LANCommanderInstallController(LANCommanderLibraryPlugin plugin, Playnite.SDK.Models.Game game) : base(game)
|
||||
{
|
||||
Name = "Install using LANCommander";
|
||||
Plugin = plugin;
|
||||
PlayniteGame = game;
|
||||
PowerShellRuntime = new PowerShellRuntime();
|
||||
}
|
||||
|
||||
public override void Install(InstallActionArgs args)
|
||||
|
@ -43,238 +36,176 @@ namespace LANCommander.PlaynitePlugin
|
|||
}
|
||||
|
||||
var gameId = Guid.Parse(Game.GameId);
|
||||
var game = Plugin.LANCommander.GetGame(gameId);
|
||||
|
||||
Logger.Trace($"Installing game {game.Title} ({game.Id})...");
|
||||
string installDirectory = null;
|
||||
|
||||
var installDirectory = RetryHelper.RetryOnException(10, TimeSpan.FromMilliseconds(500), "", () =>
|
||||
var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
{
|
||||
Logger.Trace("Attempting to download and extract game...");
|
||||
return DownloadAndExtract(game);
|
||||
});
|
||||
var gameManager = new GameManager(Plugin.LANCommanderClient, Plugin.Settings.InstallDirectory);
|
||||
|
||||
if (installDirectory == "")
|
||||
throw new Exception("Could not extract the install archive. Retry the install or check your connection.");
|
||||
Stopwatch stopwatch = new Stopwatch();
|
||||
|
||||
var installInfo = new GameInstallationData()
|
||||
stopwatch.Start();
|
||||
|
||||
var lastTotalSize = 0d;
|
||||
var speed = 0d;
|
||||
|
||||
gameManager.OnArchiveExtractionProgress += (long pos, long len) =>
|
||||
{
|
||||
InstallDirectory = installDirectory
|
||||
if (stopwatch.ElapsedMilliseconds > 500)
|
||||
{
|
||||
var percent = Math.Ceiling((pos / (decimal)len) * 100);
|
||||
|
||||
progress.ProgressMaxValue = len;
|
||||
progress.CurrentProgressValue = pos;
|
||||
|
||||
speed = (double)(progress.CurrentProgressValue - lastTotalSize) / (stopwatch.ElapsedMilliseconds / 1000d);
|
||||
|
||||
progress.Text = $"Downloading {Game.Name} ({percent}%) | {ByteSizeLib.ByteSize.FromBytes(speed).ToString("#.#")}/s";
|
||||
|
||||
lastTotalSize = pos;
|
||||
|
||||
stopwatch.Restart();
|
||||
}
|
||||
};
|
||||
|
||||
PlayniteGame.InstallDirectory = installDirectory;
|
||||
|
||||
SDK.GameManifest manifest = null;
|
||||
|
||||
var writeManifestSuccess = RetryHelper.RetryOnException(10, TimeSpan.FromSeconds(1), false, () =>
|
||||
gameManager.OnArchiveEntryExtractionProgress += (object sender, ArchiveEntryExtractionProgressArgs e) =>
|
||||
{
|
||||
Logger.Trace("Attempting to get game manifest...");
|
||||
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
|
||||
{
|
||||
gameManager.CancelInstall();
|
||||
|
||||
manifest = Plugin.LANCommander.GetGameManifest(gameId);
|
||||
progress.IsIndeterminate = true;
|
||||
}
|
||||
};
|
||||
|
||||
WriteManifest(manifest, installDirectory);
|
||||
installDirectory = gameManager.Install(gameId);
|
||||
|
||||
return true;
|
||||
stopwatch.Stop();
|
||||
},
|
||||
new GlobalProgressOptions($"Preparing to download {Game.Name}")
|
||||
{
|
||||
IsIndeterminate = false,
|
||||
Cancelable = true,
|
||||
});
|
||||
|
||||
if (!writeManifestSuccess)
|
||||
throw new Exception("Could not get or write the manifest file. Retry the install or check your connection.");
|
||||
// Install any redistributables
|
||||
var game = Plugin.LANCommanderClient.GetGame(gameId);
|
||||
|
||||
Logger.Trace("Saving scripts...");
|
||||
|
||||
SaveScript(game, installDirectory, ScriptType.Install);
|
||||
SaveScript(game, installDirectory, ScriptType.Uninstall);
|
||||
SaveScript(game, installDirectory, ScriptType.NameChange);
|
||||
SaveScript(game, installDirectory, ScriptType.KeyChange);
|
||||
|
||||
try
|
||||
if (game.Redistributables != null && game.Redistributables.Count() > 0)
|
||||
{
|
||||
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.Install);
|
||||
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.NameChange, Plugin.Settings.PlayerName);
|
||||
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
{
|
||||
var redistributableManager = new RedistributableManager(Plugin.LANCommanderClient);
|
||||
|
||||
var key = Plugin.LANCommander.GetAllocatedKey(game.Id);
|
||||
|
||||
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.KeyChange, $"\"{key}\"");
|
||||
redistributableManager.Install(game);
|
||||
},
|
||||
new GlobalProgressOptions("Installing redistributables...")
|
||||
{
|
||||
IsIndeterminate = true,
|
||||
Cancelable = false,
|
||||
});
|
||||
}
|
||||
catch { }
|
||||
|
||||
Plugin.UpdateGame(manifest, gameId);
|
||||
if (!result.Canceled && result.Error == null && !String.IsNullOrWhiteSpace(installDirectory))
|
||||
{
|
||||
var manifest = ManifestHelper.Read(installDirectory);
|
||||
|
||||
Plugin.DownloadCache.Remove(gameId);
|
||||
Plugin.UpdateGame(manifest);
|
||||
|
||||
var installInfo = new GameInstallationData
|
||||
{
|
||||
InstallDirectory = installDirectory,
|
||||
};
|
||||
|
||||
RunInstallScript(installDirectory);
|
||||
RunNameChangeScript(installDirectory);
|
||||
RunKeyChangeScript(installDirectory);
|
||||
|
||||
InvokeOnInstalled(new GameInstalledEventArgs(installInfo));
|
||||
}
|
||||
|
||||
private string DownloadAndExtract(LANCommander.SDK.Models.Game game)
|
||||
else if (result.Canceled)
|
||||
{
|
||||
if (game == null)
|
||||
{
|
||||
Logger.Trace("Game failed to download! No game was specified!");
|
||||
var dbGame = Plugin.PlayniteApi.Database.Games.Get(Game.Id);
|
||||
|
||||
throw new Exception("Game failed to download!");
|
||||
dbGame.IsInstalling = false;
|
||||
dbGame.IsInstalled = false;
|
||||
|
||||
Plugin.PlayniteApi.Database.Games.Update(dbGame);
|
||||
}
|
||||
else if (result.Error != null)
|
||||
throw result.Error;
|
||||
}
|
||||
|
||||
var destination = Path.Combine(Plugin.Settings.InstallDirectory, game.Title.SanitizeFilename());
|
||||
|
||||
Logger.Trace($"Downloading and extracting \"{game.Title}\" to path {destination}");
|
||||
|
||||
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
private int RunInstallScript(string installDirectory)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(destination);
|
||||
progress.ProgressMaxValue = 100;
|
||||
progress.CurrentProgressValue = 0;
|
||||
var manifest = ManifestHelper.Read(installDirectory);
|
||||
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install);
|
||||
|
||||
using (var gameStream = Plugin.LANCommander.StreamGame(game.Id))
|
||||
using (var reader = ReaderFactory.Open(gameStream))
|
||||
if (File.Exists(path))
|
||||
{
|
||||
progress.ProgressMaxValue = gameStream.Length;
|
||||
var script = new PowerShellScript();
|
||||
|
||||
gameStream.OnProgress += (pos, len) =>
|
||||
{
|
||||
progress.CurrentProgressValue = pos;
|
||||
};
|
||||
script.AddVariable("InstallDirectory", installDirectory);
|
||||
script.AddVariable("GameManifest", manifest);
|
||||
script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory);
|
||||
script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress);
|
||||
|
||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, $"Could not extract to path {destination}");
|
||||
script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.Install));
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger.Trace("Cleaning up orphaned install files after bad install...");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
return script.Execute();
|
||||
}
|
||||
|
||||
throw new Exception("The game archive could not be extracted. Please try again or fix the archive!");
|
||||
}
|
||||
},
|
||||
new GlobalProgressOptions($"Downloading {game.Title}...")
|
||||
{
|
||||
IsIndeterminate = false,
|
||||
Cancelable = false,
|
||||
});
|
||||
|
||||
Logger.Trace($"Game successfully downloaded and extracted to {destination}");
|
||||
|
||||
return destination;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private string Download(LANCommander.SDK.Models.Game game)
|
||||
private int RunNameChangeScript(string installDirectory)
|
||||
{
|
||||
string tempFile = String.Empty;
|
||||
var manifest = ManifestHelper.Read(installDirectory);
|
||||
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.NameChange);
|
||||
|
||||
if (game != null)
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
{
|
||||
progress.ProgressMaxValue = 100;
|
||||
progress.CurrentProgressValue = 0;
|
||||
var script = new PowerShellScript();
|
||||
|
||||
var destination = Plugin.LANCommander.DownloadGame(game.Id, (changed) =>
|
||||
{
|
||||
progress.CurrentProgressValue = changed.ProgressPercentage;
|
||||
}, (complete) =>
|
||||
{
|
||||
progress.CurrentProgressValue = 100;
|
||||
});
|
||||
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);
|
||||
|
||||
// Lock the thread until download is done
|
||||
while (progress.CurrentProgressValue != 100)
|
||||
{
|
||||
script.UseFile(path);
|
||||
|
||||
return script.Execute();
|
||||
}
|
||||
|
||||
tempFile = destination;
|
||||
},
|
||||
new GlobalProgressOptions($"Downloading {game.Title}...")
|
||||
{
|
||||
IsIndeterminate = false,
|
||||
Cancelable = false,
|
||||
});
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
else
|
||||
throw new Exception("Game failed to download!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private string Extract(LANCommander.SDK.Models.Game game, string archivePath)
|
||||
private int RunKeyChangeScript(string installDirectory)
|
||||
{
|
||||
var destination = Path.Combine(Plugin.Settings.InstallDirectory, game.Title.SanitizeFilename());
|
||||
var manifest = ManifestHelper.Read(installDirectory);
|
||||
var path = ScriptHelper.GetScriptFilePath(installDirectory, SDK.Enums.ScriptType.KeyChange);
|
||||
|
||||
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(destination);
|
||||
var script = new PowerShellScript();
|
||||
|
||||
using (var fs = File.OpenRead(archivePath))
|
||||
using (var ts = new TrackableStream(fs))
|
||||
using (var reader = ReaderFactory.Open(ts))
|
||||
{
|
||||
progress.ProgressMaxValue = ts.Length;
|
||||
ts.OnProgress += (pos, len) =>
|
||||
{
|
||||
progress.CurrentProgressValue = pos;
|
||||
};
|
||||
var key = Plugin.LANCommanderClient.GetAllocatedKey(manifest.Id);
|
||||
|
||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
},
|
||||
new GlobalProgressOptions($"Extracting {game.Title}...")
|
||||
{
|
||||
IsIndeterminate = false,
|
||||
Cancelable = false,
|
||||
});
|
||||
script.AddVariable("InstallDirectory", installDirectory);
|
||||
script.AddVariable("GameManifest", manifest);
|
||||
script.AddVariable("DefaultInstallDirectory", Plugin.Settings.InstallDirectory);
|
||||
script.AddVariable("ServerAddress", Plugin.Settings.ServerAddress);
|
||||
script.AddVariable("AllocatedKey", key);
|
||||
|
||||
return destination;
|
||||
script.UseFile(path);
|
||||
|
||||
return script.Execute();
|
||||
}
|
||||
|
||||
private void WriteManifest(SDK.GameManifest manifest, string installDirectory)
|
||||
{
|
||||
var destination = Path.Combine(installDirectory, "_manifest.yml");
|
||||
|
||||
Logger.Trace($"Attempting to write manifest to path {destination}");
|
||||
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(new PascalCaseNamingConvention())
|
||||
.Build();
|
||||
|
||||
Logger.Trace("Serializing manifest...");
|
||||
var yaml = serializer.Serialize(manifest);
|
||||
|
||||
Logger.Trace("Writing manifest file...");
|
||||
File.WriteAllText(destination, yaml);
|
||||
}
|
||||
|
||||
private void SaveScript(LANCommander.SDK.Models.Game game, string installationDirectory, ScriptType type)
|
||||
{
|
||||
var script = game.Scripts.FirstOrDefault(s => s.Type == type);
|
||||
|
||||
if (script == null)
|
||||
return;
|
||||
|
||||
if (script.RequiresAdmin)
|
||||
script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents;
|
||||
|
||||
var filename = PowerShellRuntime.GetScriptFilePath(PlayniteGame, type);
|
||||
|
||||
if (File.Exists(filename))
|
||||
File.Delete(filename);
|
||||
|
||||
Logger.Trace($"Writing {type} script to {filename}");
|
||||
|
||||
File.WriteAllText(filename, script.Contents);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
@ -34,19 +35,28 @@
|
|||
<Reference Include="BeaconLib, Version=1.0.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\rix0rrr.BeaconLib.1.0.2\lib\net40\BeaconLib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
|
||||
<Reference Include="ByteSize, Version=2.1.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ByteSize.2.1.1\lib\net45\ByteSize.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Playnite.SDK, Version=6.9.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\PlayniteSDK.6.9.0\lib\net462\Playnite.SDK.dll</HintPath>
|
||||
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.8.0.0\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Extensions.Logging.Abstractions.8.0.0\lib\net462\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Playnite.SDK, Version=6.10.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\PlayniteSDK.6.10.0\lib\net462\Playnite.SDK.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="RestSharp, Version=106.15.0.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\RestSharp.106.15.0\lib\net452\RestSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SharpCompress, Version=0.33.0.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpCompress.0.33.0\lib\net462\SharpCompress.dll</HintPath>
|
||||
<Reference Include="SharpCompress, Version=0.34.2.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpCompress.0.34.2\lib\net462\SharpCompress.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
|
@ -67,14 +77,14 @@
|
|||
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Text.Encoding.CodePages, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Text.Encoding.CodePages.7.0.0\lib\net462\System.Text.Encoding.CodePages.dll</HintPath>
|
||||
<Reference Include="System.Text.Encoding.CodePages, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Text.Encoding.CodePages.8.0.0\lib\net462\System.Text.Encoding.CodePages.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Text.Encodings.Web, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Text.Encodings.Web.7.0.0\lib\net462\System.Text.Encodings.Web.dll</HintPath>
|
||||
<Reference Include="System.Text.Encodings.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Text.Encodings.Web.8.0.0\lib\net462\System.Text.Encodings.Web.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Text.Json, Version=7.0.0.3, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Text.Json.7.0.3\lib\net462\System.Text.Json.dll</HintPath>
|
||||
<Reference Include="System.Text.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Text.Json.8.0.0\lib\net462\System.Text.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||
|
@ -93,18 +103,18 @@
|
|||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="YamlDotNet, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\YamlDotNet.5.4.0\lib\net45\YamlDotNet.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="ZstdSharp, Version=0.7.2.0, Culture=neutral, PublicKeyToken=8d151af33a4ad5cf, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ZstdSharp.Port.0.7.2\lib\net461\ZstdSharp.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="TrackableStream.cs" />
|
||||
<Compile Include="Extensions\MultiplayerInfoExtensions.cs" />
|
||||
<Compile Include="Helpers\RetryHelper.cs" />
|
||||
<Compile Include="PowerShellRuntime.cs" />
|
||||
<Compile Include="Services\GameSaveService.cs" />
|
||||
<Compile Include="PlayniteLogger.cs" />
|
||||
<Compile Include="SaveController.cs" />
|
||||
<Compile Include="UninstallController.cs" />
|
||||
<Compile Include="InstallController.cs" />
|
||||
<Compile Include="LANCommanderClient.cs" />
|
||||
<Compile Include="LANCommanderLibraryClient.cs" />
|
||||
<Compile Include="LANCommanderLibraryPlugin.cs" />
|
||||
<Compile Include="ViewModels\LANCommanderSettingsViewModel.cs" />
|
||||
<Compile Include="Views\LANCommanderSettingsView.xaml.cs">
|
||||
|
@ -145,6 +155,10 @@
|
|||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LANCommander.PowerShell\LANCommander.PowerShell.csproj">
|
||||
<Project>{807943bf-0c7d-4ed3-8393-cfee64e3138c}</Project>
|
||||
<Name>LANCommander.PowerShell</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\LANCommander.SDK\LANCommander.SDK.csproj">
|
||||
<Project>{4c2a71fd-a30b-4d62-888a-4ef843d8e506}</Project>
|
||||
<Name>LANCommander.SDK</Name>
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
using Playnite.SDK;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
{
|
||||
public class LANCommanderLibraryClient : LibraryClient
|
||||
{
|
||||
public override bool IsInstalled => true;
|
||||
|
||||
public override void Open()
|
||||
{
|
||||
|
||||
System.Diagnostics.Process.Start("https://localhost:7087");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using LANCommander.PlaynitePlugin.Extensions;
|
||||
using LANCommander.PlaynitePlugin.Services;
|
||||
using LANCommander.SDK.Helpers;
|
||||
using LANCommander.SDK.PowerShell;
|
||||
using Playnite.SDK;
|
||||
using Playnite.SDK.Events;
|
||||
using Playnite.SDK.Models;
|
||||
|
@ -9,6 +10,7 @@ using System.Collections.Generic;
|
|||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
@ -20,13 +22,11 @@ namespace LANCommander.PlaynitePlugin
|
|||
{
|
||||
public static readonly ILogger Logger = LogManager.GetLogger();
|
||||
internal LANCommanderSettingsViewModel Settings { get; set; }
|
||||
internal LANCommanderClient LANCommander { get; set; }
|
||||
internal PowerShellRuntime PowerShellRuntime { get; set; }
|
||||
internal GameSaveService GameSaveService { get; set; }
|
||||
internal LANCommander.SDK.Client LANCommanderClient { get; set; }
|
||||
internal LANCommanderSaveController SaveController { get; set; }
|
||||
|
||||
public override Guid Id { get; } = Guid.Parse("48e1bac7-e0a0-45d7-ba83-36f5e9e959fc");
|
||||
public override string Name => "LANCommander";
|
||||
public override LibraryClient Client { get; } = new LANCommanderLibraryClient();
|
||||
|
||||
internal Dictionary<Guid, string> DownloadCache = new Dictionary<Guid, string>();
|
||||
|
||||
|
@ -39,16 +39,14 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
Settings = new LANCommanderSettingsViewModel(this);
|
||||
|
||||
LANCommander = new LANCommanderClient(Settings.ServerAddress);
|
||||
LANCommander.Token = new SDK.Models.AuthToken()
|
||||
LANCommanderClient = new SDK.Client(Settings.ServerAddress, new PlayniteLogger(Logger));
|
||||
LANCommanderClient.UseToken(new SDK.Models.AuthToken()
|
||||
{
|
||||
AccessToken = Settings.AccessToken,
|
||||
RefreshToken = Settings.RefreshToken,
|
||||
};
|
||||
});
|
||||
|
||||
PowerShellRuntime = new PowerShellRuntime();
|
||||
|
||||
GameSaveService = new GameSaveService(LANCommander, PlayniteApi, PowerShellRuntime);
|
||||
// GameSaveService = new GameSaveService(LANCommander, PlayniteApi, PowerShellRuntime);
|
||||
|
||||
api.UriHandler.RegisterSource("lancommander", args =>
|
||||
{
|
||||
|
@ -74,38 +72,46 @@ namespace LANCommander.PlaynitePlugin
|
|||
if (Guid.TryParse(args.Arguments[1], out gameId))
|
||||
PlayniteApi.StartGame(gameId);
|
||||
break;
|
||||
|
||||
case "connect":
|
||||
if (args.Arguments.Length == 1)
|
||||
{
|
||||
ShowAuthenticationWindow();
|
||||
break;
|
||||
}
|
||||
|
||||
ShowAuthenticationWindow(HttpUtility.UrlDecode(args.Arguments[1]));
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnApplicationStarted(OnApplicationStartedEventArgs args)
|
||||
{
|
||||
if (LANCommander.Token == null || LANCommander.Client == null || !LANCommander.ValidateToken(LANCommander.Token))
|
||||
{
|
||||
Logger.Trace("No valid authentication token exists. Showing auth window...");
|
||||
ShowAuthenticationWindow();
|
||||
}
|
||||
}
|
||||
|
||||
public bool ValidateConnection()
|
||||
{
|
||||
return LANCommander.ValidateToken(LANCommander.Token);
|
||||
return LANCommanderClient.ValidateToken();
|
||||
}
|
||||
|
||||
public override IEnumerable<GameMetadata> GetGames(LibraryGetGamesArgs args)
|
||||
{
|
||||
var gameMetadata = new List<GameMetadata>();
|
||||
|
||||
while (!ValidateConnection())
|
||||
if (!ValidateConnection())
|
||||
{
|
||||
Logger.Trace("Authentication invalid, showing auth window...");
|
||||
ShowAuthenticationWindow();
|
||||
|
||||
if (!ValidateConnection())
|
||||
{
|
||||
Logger.Trace("User cancelled authentication.");
|
||||
|
||||
throw new Exception("You must set up a valid connection to a LANCommander server.");
|
||||
}
|
||||
}
|
||||
|
||||
var games = LANCommander
|
||||
var games = LANCommanderClient
|
||||
.GetGames()
|
||||
.Where(g => g.Archives != null && g.Archives.Count() > 0);
|
||||
.Where(g => g != null && g.Archives != null && g.Archives.Count() > 0);
|
||||
|
||||
foreach (var game in games)
|
||||
{
|
||||
|
@ -113,7 +119,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
{
|
||||
Logger.Trace($"Importing/updating metadata for game \"{game.Title}\"...");
|
||||
|
||||
var manifest = LANCommander.GetGameManifest(game.Id);
|
||||
var manifest = LANCommanderClient.GetGameManifest(game.Id);
|
||||
Logger.Trace("Successfully grabbed game manifest");
|
||||
|
||||
var existingGame = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == game.Id.ToString() && g.PluginId == Id && g.IsInstalled);
|
||||
|
@ -122,7 +128,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
{
|
||||
Logger.Trace("Game already exists in library, updating metadata...");
|
||||
|
||||
UpdateGame(manifest, game.Id);
|
||||
UpdateGame(manifest);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
@ -138,7 +144,6 @@ namespace LANCommander.PlaynitePlugin
|
|||
GameId = game.Id.ToString(),
|
||||
ReleaseDate = new ReleaseDate(manifest.ReleasedOn),
|
||||
//Version = game.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault().Version,
|
||||
Icon = new MetadataFile($"{Settings.ServerAddress}{manifest.Icon}"),
|
||||
GameActions = game.Actions.OrderBy(a => a.SortOrder).Select(a => new PN.SDK.Models.GameAction()
|
||||
{
|
||||
Name = a.Name,
|
||||
|
@ -175,6 +180,15 @@ namespace LANCommander.PlaynitePlugin
|
|||
if (manifest.OnlineMultiplayer != null)
|
||||
metadata.Features.Add(new MetadataNameProperty($"Online Multiplayer {manifest.OnlineMultiplayer.GetPlayerCount()}".Trim()));
|
||||
|
||||
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Icon))
|
||||
metadata.Icon = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Icon)));
|
||||
|
||||
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Cover))
|
||||
metadata.CoverImage = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Cover)));
|
||||
|
||||
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Background))
|
||||
metadata.BackgroundImage = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background)));
|
||||
|
||||
gameMetadata.Add(metadata);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -208,9 +222,9 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
if (args.Games.Count == 1 && args.Games.First().IsInstalled && !String.IsNullOrWhiteSpace(args.Games.First().InstallDirectory))
|
||||
{
|
||||
var nameChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.NameChange);
|
||||
var keyChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.KeyChange);
|
||||
var installScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.Install);
|
||||
var nameChangeScriptPath = 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))
|
||||
{
|
||||
|
@ -226,7 +240,13 @@ namespace LANCommander.PlaynitePlugin
|
|||
var result = PlayniteApi.Dialogs.SelectString("Enter your player name", "Change Player Name", Settings.PlayerName);
|
||||
|
||||
if (result.Result == true)
|
||||
PowerShellRuntime.RunScript(nameChangeArgs.Games.First(), SDK.Enums.ScriptType.NameChange, $@"""{result.SelectedString}"" ""{oldName}""");
|
||||
{
|
||||
var game = nameChangeArgs.Games.First();
|
||||
|
||||
RunNameChangeScript(game.InstallDirectory, oldName, result.SelectedString);
|
||||
|
||||
LANCommanderClient.ChangeAlias(result.SelectedString);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -245,12 +265,12 @@ namespace LANCommander.PlaynitePlugin
|
|||
if (Guid.TryParse(keyChangeArgs.Games.First().GameId, out gameId))
|
||||
{
|
||||
// NUKIEEEE
|
||||
var newKey = LANCommander.GetNewKey(gameId);
|
||||
var newKey = LANCommanderClient.GetNewKey(gameId);
|
||||
|
||||
if (String.IsNullOrEmpty(newKey))
|
||||
PlayniteApi.Dialogs.ShowErrorMessage("There are no more keys available on the server.", "No Keys Available");
|
||||
else
|
||||
PowerShellRuntime.RunScript(keyChangeArgs.Games.First(), SDK.Enums.ScriptType.KeyChange, $@"""{newKey}""");
|
||||
RunKeyChangeScript(keyChangeArgs.Games.First().InstallDirectory, newKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -272,14 +292,10 @@ namespace LANCommander.PlaynitePlugin
|
|||
Guid gameId;
|
||||
|
||||
if (Guid.TryParse(installArgs.Games.First().GameId, out gameId))
|
||||
{
|
||||
PowerShellRuntime.RunScript(installArgs.Games.First(), 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.");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -315,12 +331,42 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
public override void OnGameStarting(OnGameStartingEventArgs args)
|
||||
{
|
||||
GameSaveService.DownloadSave(args.Game);
|
||||
if (args.Game.PluginId == Id)
|
||||
{
|
||||
var gameId = Guid.Parse(args.Game.GameId);
|
||||
|
||||
LANCommanderClient.StartPlaySession(gameId);
|
||||
|
||||
try
|
||||
{
|
||||
SaveController = new LANCommanderSaveController(this, args.Game);
|
||||
SaveController.Download(args.Game);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.Error(ex, "Could not download save");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnGameStopped(OnGameStoppedEventArgs args)
|
||||
{
|
||||
GameSaveService.UploadSave(args.Game);
|
||||
if (args.Game.PluginId == Id)
|
||||
{
|
||||
var gameId = Guid.Parse(args.Game.GameId);
|
||||
|
||||
LANCommanderClient.EndPlaySession(gameId);
|
||||
|
||||
try
|
||||
{
|
||||
SaveController = new LANCommanderSaveController(this, args.Game);
|
||||
SaveController.Upload(args.Game);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.Error(ex, "Could not upload save");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<TopPanelItem> GetTopPanelItems()
|
||||
|
@ -374,6 +420,8 @@ namespace LANCommander.PlaynitePlugin
|
|||
}
|
||||
else
|
||||
{
|
||||
var oldName = Settings.PlayerName;
|
||||
|
||||
Settings.PlayerName = result.SelectedString;
|
||||
|
||||
Logger.Trace($"New player name of \"{Settings.PlayerName}\" has been set!");
|
||||
|
@ -383,16 +431,28 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
var games = PlayniteApi.Database.Games.Where(g => g.IsInstalled).ToList();
|
||||
|
||||
LANCommanderClient.ChangeAlias(result.SelectedString);
|
||||
|
||||
Logger.Trace($"Running name change scripts across {games.Count} installed game(s)");
|
||||
|
||||
PowerShellRuntime.RunScripts(games, SDK.Enums.ScriptType.NameChange, Settings.PlayerName);
|
||||
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
|
||||
Logger.Trace("Name change was cancelled");
|
||||
}
|
||||
|
||||
public Window ShowAuthenticationWindow()
|
||||
public Window ShowAuthenticationWindow(string serverAddress = null, EventHandler onClose = null)
|
||||
{
|
||||
Window window = null;
|
||||
Application.Current.Dispatcher.Invoke((Action)delegate
|
||||
|
@ -408,21 +468,25 @@ namespace LANCommander.PlaynitePlugin
|
|||
window.Content = new Views.Authentication(this);
|
||||
window.DataContext = new ViewModels.Authentication()
|
||||
{
|
||||
ServerAddress = Settings?.ServerAddress
|
||||
ServerAddress = serverAddress ?? Settings?.ServerAddress
|
||||
};
|
||||
|
||||
window.Owner = PlayniteApi.Dialogs.GetCurrentAppWindow();
|
||||
window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
window.ResizeMode = ResizeMode.NoResize;
|
||||
|
||||
if (onClose != null)
|
||||
window.Closed += onClose;
|
||||
|
||||
window.ShowDialog();
|
||||
});
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
public void UpdateGame(SDK.GameManifest manifest, Guid gameId)
|
||||
public void UpdateGame(SDK.GameManifest manifest)
|
||||
{
|
||||
var game = PlayniteApi.Database.Games.First(g => g.GameId == gameId.ToString());
|
||||
var game = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == manifest?.Id.ToString());
|
||||
|
||||
if (game == null)
|
||||
return;
|
||||
|
@ -432,8 +496,6 @@ namespace LANCommander.PlaynitePlugin
|
|||
else
|
||||
game.GameActions.Clear();
|
||||
|
||||
game.Icon = $"{Settings.ServerAddress}{manifest.Icon}";
|
||||
|
||||
if (manifest.Actions == null)
|
||||
throw new Exception("The game has no actions defined.");
|
||||
|
||||
|
@ -506,5 +568,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
{
|
||||
public sealed class PlayniteLogger : ILogger
|
||||
{
|
||||
private readonly Playnite.SDK.ILogger Logger;
|
||||
|
||||
public PlayniteLogger(Playnite.SDK.ILogger logger) {
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
switch (logLevel)
|
||||
{
|
||||
case LogLevel.Trace:
|
||||
Logger?.Trace(formatter.Invoke(state, exception));
|
||||
break;
|
||||
|
||||
case LogLevel.Debug:
|
||||
Logger?.Debug(formatter.Invoke(state, exception));
|
||||
break;
|
||||
|
||||
case LogLevel.Information:
|
||||
Logger.Info(formatter.Invoke(state, exception));
|
||||
break;
|
||||
|
||||
case LogLevel.Warning:
|
||||
Logger.Warn(formatter.Invoke(state, exception));
|
||||
break;
|
||||
|
||||
case LogLevel.Error:
|
||||
case LogLevel.Critical:
|
||||
Logger.Error(formatter.Invoke(state, exception));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
using Playnite.SDK;
|
||||
using Playnite.SDK.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.RightsManagement;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
{
|
||||
internal class PowerShellRuntime
|
||||
{
|
||||
public static readonly ILogger Logger = LogManager.GetLogger();
|
||||
|
||||
[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 void RunCommand(string command, bool asAdmin = false)
|
||||
{
|
||||
Logger.Trace($"Executing command `{command}` | Admin: {asAdmin}");
|
||||
|
||||
var tempScript = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".ps1");
|
||||
|
||||
Logger.Trace($"Creating temp script at path {tempScript}");
|
||||
|
||||
File.WriteAllText(tempScript, command);
|
||||
|
||||
RunScript(tempScript, asAdmin);
|
||||
|
||||
File.Delete(tempScript);
|
||||
}
|
||||
|
||||
public void RunScript(string path, bool asAdmin = false, string arguments = null)
|
||||
{
|
||||
Logger.Trace($"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 (asAdmin)
|
||||
{
|
||||
process.StartInfo.Verb = "runas";
|
||||
process.StartInfo.UseShellExecute = true;
|
||||
}
|
||||
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
|
||||
Wow64RevertWow64FsRedirection(ref wow64Value);
|
||||
}
|
||||
|
||||
public void RunScript(Game game, ScriptType type, string arguments = null)
|
||||
{
|
||||
var path = GetScriptFilePath(game, 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 void RunScriptsAsAdmin(IEnumerable<string> paths, string arguments = null)
|
||||
{
|
||||
// Concatenate scripts
|
||||
var sb = new StringBuilder();
|
||||
|
||||
Logger.Trace("Concatenating scripts...");
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
var contents = File.ReadAllText(path);
|
||||
|
||||
sb.AppendLine(contents);
|
||||
|
||||
Logger.Trace($"Added {path}!");
|
||||
}
|
||||
|
||||
Logger.Trace("Done concatenating!");
|
||||
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
var scriptPath = Path.GetTempFileName();
|
||||
|
||||
Logger.Trace($"Creating temp script at path {scriptPath}");
|
||||
|
||||
File.WriteAllText(scriptPath, sb.ToString());
|
||||
|
||||
RunScript(scriptPath, true, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
public void RunScripts(IEnumerable<Game> games, ScriptType type, string arguments = null)
|
||||
{
|
||||
List<string> scripts = new List<string>();
|
||||
List<string> adminScripts = new List<string>();
|
||||
|
||||
foreach (var game in games)
|
||||
{
|
||||
var path = GetScriptFilePath(game, 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)
|
||||
{
|
||||
Dictionary<ScriptType, string> filenames = new Dictionary<ScriptType, string>() {
|
||||
{ ScriptType.Install, "_install.ps1" },
|
||||
{ ScriptType.Uninstall, "_uninstall.ps1" },
|
||||
{ ScriptType.NameChange, "_changename.ps1" },
|
||||
{ ScriptType.KeyChange, "_changekey.ps1" }
|
||||
};
|
||||
|
||||
var filename = filenames[type];
|
||||
|
||||
return Path.Combine(game.InstallDirectory, filename);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
using LANCommander.SDK;
|
||||
using Playnite.SDK;
|
||||
using Playnite.SDK.Models;
|
||||
using Playnite.SDK.Plugins;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
{
|
||||
public class LANCommanderSaveController : ControllerBase
|
||||
{
|
||||
private static readonly ILogger Logger;
|
||||
|
||||
private LANCommanderLibraryPlugin Plugin;
|
||||
|
||||
public LANCommanderSaveController(LANCommanderLibraryPlugin plugin, Game game) : base(game)
|
||||
{
|
||||
Name = "Download save using LANCommander";
|
||||
Plugin = plugin;
|
||||
}
|
||||
|
||||
public void Download(Game game)
|
||||
{
|
||||
if (game != null)
|
||||
{
|
||||
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
{
|
||||
progress.ProgressMaxValue = 100;
|
||||
progress.CurrentProgressValue = 0;
|
||||
|
||||
var saveManager = new GameSaveManager(Plugin.LANCommanderClient);
|
||||
|
||||
saveManager.OnDownloadProgress += (downloadProgress) =>
|
||||
{
|
||||
progress.CurrentProgressValue = downloadProgress.ProgressPercentage;
|
||||
};
|
||||
|
||||
saveManager.OnDownloadComplete += (downloadComplete) =>
|
||||
{
|
||||
progress.CurrentProgressValue = 100;
|
||||
};
|
||||
|
||||
saveManager.Download(game.InstallDirectory);
|
||||
|
||||
// Lock the thread until the download is done
|
||||
while (progress.CurrentProgressValue != 100) { }
|
||||
},
|
||||
new GlobalProgressOptions("Downloading latest save...")
|
||||
{
|
||||
IsIndeterminate = false,
|
||||
Cancelable = false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Upload(Game game)
|
||||
{
|
||||
var saveManager = new GameSaveManager(Plugin.LANCommanderClient);
|
||||
|
||||
saveManager.Upload(game.InstallDirectory);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,289 +0,0 @@
|
|||
using LANCommander.SDK;
|
||||
using Playnite.SDK;
|
||||
using Playnite.SDK.Models;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.Zip;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin.Services
|
||||
{
|
||||
internal class GameSaveService
|
||||
{
|
||||
private readonly LANCommanderClient LANCommander;
|
||||
private readonly IPlayniteAPI PlayniteApi;
|
||||
private readonly PowerShellRuntime PowerShellRuntime;
|
||||
|
||||
internal GameSaveService(LANCommanderClient lanCommander, IPlayniteAPI playniteApi, PowerShellRuntime powerShellRuntime)
|
||||
{
|
||||
LANCommander = lanCommander;
|
||||
PlayniteApi = playniteApi;
|
||||
PowerShellRuntime = powerShellRuntime;
|
||||
}
|
||||
|
||||
internal void DownloadSave(Game game)
|
||||
{
|
||||
string tempFile = String.Empty;
|
||||
|
||||
if (game != null)
|
||||
{
|
||||
PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
{
|
||||
progress.ProgressMaxValue = 100;
|
||||
progress.CurrentProgressValue = 0;
|
||||
|
||||
var destination = LANCommander.DownloadLatestSave(Guid.Parse(game.GameId), (changed) =>
|
||||
{
|
||||
progress.CurrentProgressValue = changed.ProgressPercentage;
|
||||
}, (complete) =>
|
||||
{
|
||||
progress.CurrentProgressValue = 100;
|
||||
});
|
||||
|
||||
// Lock the thread until download is done
|
||||
while (progress.CurrentProgressValue != 100)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
tempFile = destination;
|
||||
},
|
||||
new GlobalProgressOptions("Downloading latest save...")
|
||||
{
|
||||
IsIndeterminate = false,
|
||||
Cancelable = false
|
||||
});
|
||||
|
||||
// Go into the archive and extract the files to the correct locations
|
||||
try
|
||||
{
|
||||
var tempLocation = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
|
||||
Directory.CreateDirectory(tempLocation);
|
||||
|
||||
ExtractFilesFromZip(tempFile, tempLocation);
|
||||
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(new PascalCaseNamingConvention())
|
||||
.Build();
|
||||
|
||||
var manifestContents = File.ReadAllText(Path.Combine(tempLocation, "_manifest.yml"));
|
||||
|
||||
var manifest = deserializer.Deserialize<GameManifest>(manifestContents);
|
||||
|
||||
#region Move files
|
||||
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
|
||||
{
|
||||
bool inInstallDir = savePath.Path.StartsWith("{InstallDir}");
|
||||
string tempSavePath = Path.Combine(tempLocation, savePath.Id.ToString());
|
||||
|
||||
var tempSavePathFile = Path.Combine(tempSavePath, savePath.Path.Replace('/', '\\').Replace("{InstallDir}\\", ""));
|
||||
|
||||
var destination = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory));
|
||||
|
||||
if (File.Exists(tempSavePathFile))
|
||||
{
|
||||
// Is file, move file
|
||||
if (File.Exists(destination))
|
||||
File.Delete(destination);
|
||||
|
||||
File.Move(tempSavePathFile, destination);
|
||||
}
|
||||
else if (Directory.Exists(tempSavePath))
|
||||
{
|
||||
var files = Directory.GetFiles(tempSavePath, "*", SearchOption.AllDirectories);
|
||||
|
||||
if (inInstallDir)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (inInstallDir)
|
||||
{
|
||||
// Files are in the game's install directory. Move them there from the save path.
|
||||
destination = file.Replace(tempSavePath, savePath.Path.Replace('/', '\\').TrimEnd('\\').Replace("{InstallDir}", game.InstallDirectory));
|
||||
|
||||
if (File.Exists(destination))
|
||||
File.Delete(destination);
|
||||
|
||||
File.Move(file, destination);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Specified path is probably an absolute path, maybe with environment variables.
|
||||
destination = Environment.ExpandEnvironmentVariables(file.Replace(tempSavePathFile, savePath.Path.Replace('/', '\\')));
|
||||
|
||||
if (File.Exists(destination))
|
||||
File.Delete(destination);
|
||||
|
||||
File.Move(file, destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Handle registry importing
|
||||
var registryImportFilePath = Path.Combine(tempLocation, "_registry.reg");
|
||||
|
||||
if (File.Exists(registryImportFilePath))
|
||||
{
|
||||
var registryImportFileContents = File.ReadAllText(registryImportFilePath);
|
||||
|
||||
PowerShellRuntime.RunCommand($"regedit.exe /s \"{registryImportFilePath}\"", registryImportFileContents.Contains("HKEY_LOCAL_MACHINE"));
|
||||
}
|
||||
#endregion
|
||||
|
||||
// Clean up temp files
|
||||
Directory.Delete(tempLocation, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void UploadSave(Game game)
|
||||
{
|
||||
var manifestPath = Path.Combine(game.InstallDirectory, "_manifest.yml");
|
||||
|
||||
if (File.Exists(manifestPath))
|
||||
{
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(new PascalCaseNamingConvention())
|
||||
.Build();
|
||||
|
||||
var manifest = deserializer.Deserialize<GameManifest>(File.ReadAllText(manifestPath));
|
||||
var temp = Path.GetTempFileName();
|
||||
|
||||
if (manifest.SavePaths != null && manifest.SavePaths.Count() > 0)
|
||||
{
|
||||
using (var archive = ZipArchive.Create())
|
||||
{
|
||||
archive.DeflateCompressionLevel = SharpCompress.Compressors.Deflate.CompressionLevel.BestCompression;
|
||||
|
||||
#region Add files from defined paths
|
||||
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
|
||||
{
|
||||
var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory));
|
||||
|
||||
if (Directory.Exists(localPath))
|
||||
{
|
||||
AddDirectoryToZip(archive, localPath, localPath, savePath.Id);
|
||||
}
|
||||
else if (File.Exists(localPath))
|
||||
{
|
||||
archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Add files from defined paths
|
||||
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
|
||||
{
|
||||
var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory));
|
||||
|
||||
if (Directory.Exists(localPath))
|
||||
{
|
||||
AddDirectoryToZip(archive, localPath, localPath, savePath.Id);
|
||||
}
|
||||
else if (File.Exists(localPath))
|
||||
{
|
||||
archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Export registry keys
|
||||
if (manifest.SavePaths.Any(sp => sp.Type == "Registry"))
|
||||
{
|
||||
List<string> tempRegFiles = new List<string>();
|
||||
|
||||
var exportCommand = new StringBuilder();
|
||||
|
||||
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "Registry"))
|
||||
{
|
||||
var tempRegFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".reg");
|
||||
|
||||
exportCommand.AppendLine($"reg.exe export \"{savePath.Path.Replace(":\\", "\\")}\" \"{tempRegFile}\"");
|
||||
tempRegFiles.Add(tempRegFile);
|
||||
}
|
||||
|
||||
PowerShellRuntime.RunCommand(exportCommand.ToString());
|
||||
|
||||
var exportFile = new StringBuilder();
|
||||
|
||||
foreach (var tempRegFile in tempRegFiles)
|
||||
{
|
||||
exportFile.AppendLine(File.ReadAllText(tempRegFile));
|
||||
File.Delete(tempRegFile);
|
||||
}
|
||||
|
||||
archive.AddEntry("_registry.reg", new MemoryStream(Encoding.UTF8.GetBytes(exportFile.ToString())), true);
|
||||
}
|
||||
#endregion
|
||||
|
||||
archive.AddEntry("_manifest.yml", manifestPath);
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
archive.SaveTo(ms);
|
||||
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var save = LANCommander.UploadSave(game.GameId, ms.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDirectoryToZip(ZipArchive zipArchive, string path, string workingDirectory, Guid pathId)
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(path))
|
||||
{
|
||||
// Oh man is this a hack. We should be removing only the working directory from the start,
|
||||
// but we're making the assumption that the working dir put in actually prefixes the path.
|
||||
// Also wtf, that Path.Combine is stripping the pathId out?
|
||||
zipArchive.AddEntry(Path.Combine(pathId.ToString(), path.Substring(workingDirectory.Length), Path.GetFileName(file)), file);
|
||||
}
|
||||
|
||||
foreach (var child in Directory.GetDirectories(path))
|
||||
{
|
||||
// See above
|
||||
//ZipEntry entry = new ZipEntry(Path.Combine(pathId.ToString(), path.Substring(workingDirectory.Length), Path.GetFileName(path)));
|
||||
|
||||
//zipStream.PutNextEntry(entry);
|
||||
//zipStream.CloseEntry();
|
||||
|
||||
AddDirectoryToZip(zipArchive, child, workingDirectory, pathId);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtractFilesFromZip(string zipPath, string destination)
|
||||
{
|
||||
using (var fs = File.OpenRead(zipPath))
|
||||
using (var ts = new TrackableStream(fs))
|
||||
using (var reader = ReaderFactory.Open(ts))
|
||||
{
|
||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
@ -12,29 +14,49 @@ namespace LANCommander.PlaynitePlugin
|
|||
public static readonly ILogger Logger = LogManager.GetLogger();
|
||||
|
||||
private LANCommanderLibraryPlugin Plugin;
|
||||
private PowerShellRuntime PowerShellRuntime;
|
||||
|
||||
public LANCommanderUninstallController(LANCommanderLibraryPlugin plugin, Game game) : base(game)
|
||||
{
|
||||
Name = "Uninstall LANCommander Game";
|
||||
Plugin = plugin;
|
||||
PowerShellRuntime = new PowerShellRuntime();
|
||||
}
|
||||
|
||||
public override void Uninstall(UninstallActionArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
PowerShellRuntime.RunScript(Game, ScriptType.Uninstall);
|
||||
var gameManager = new LANCommander.SDK.GameManager(Plugin.LANCommanderClient, 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");
|
||||
}
|
||||
catch { }
|
||||
|
||||
Logger.Trace("Attempting to delete install directory...");
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(Game.InstallDirectory) && Directory.Exists(Game.InstallDirectory))
|
||||
Directory.Delete(Game.InstallDirectory, true);
|
||||
|
||||
Logger.Trace("Deleted!");
|
||||
gameManager.Uninstall(Game.InstallDirectory);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "There was an error uninstalling the game");
|
||||
}
|
||||
|
||||
InvokeOnUninstalled(new GameUninstalledEventArgs());
|
||||
}
|
||||
|
|
|
@ -40,6 +40,9 @@ namespace LANCommander.PlaynitePlugin.Views
|
|||
{
|
||||
var beacon = beacons.First();
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(beacon.Data) && Uri.TryCreate(beacon.Data, UriKind.Absolute, out var beaconUri))
|
||||
Context.ServerAddress = beaconUri.ToString();
|
||||
else
|
||||
Context.ServerAddress = $"http://{beacon.Address.Address}:{beacon.Address.Port}";
|
||||
|
||||
this.ServerAddress.Text = Context.ServerAddress;
|
||||
|
@ -97,23 +100,20 @@ namespace LANCommander.PlaynitePlugin.Views
|
|||
LoginButton.Content = "Logging in...";
|
||||
}));
|
||||
|
||||
if (Plugin.LANCommander == null || Plugin.LANCommander.Client == null)
|
||||
Plugin.LANCommander = new LANCommanderClient(Context.ServerAddress);
|
||||
if (Plugin.LANCommanderClient == null)
|
||||
Plugin.LANCommanderClient = new LANCommander.SDK.Client(Context.ServerAddress);
|
||||
else
|
||||
Plugin.LANCommander.Client.BaseUrl = new Uri(Context.ServerAddress);
|
||||
Plugin.LANCommanderClient.UseServerAddress(Context.ServerAddress);
|
||||
|
||||
var response = await Plugin.LANCommander.AuthenticateAsync(Context.UserName, Context.Password);
|
||||
var response = await Plugin.LANCommanderClient.AuthenticateAsync(Context.UserName, Context.Password);
|
||||
|
||||
Plugin.Settings.ServerAddress = Context.ServerAddress;
|
||||
Plugin.Settings.AccessToken = response.AccessToken;
|
||||
Plugin.Settings.RefreshToken = response.RefreshToken;
|
||||
Plugin.Settings.PlayerName = Context.UserName;
|
||||
|
||||
Plugin.LANCommander.Token = new AuthToken()
|
||||
{
|
||||
AccessToken = response.AccessToken,
|
||||
RefreshToken = response.RefreshToken,
|
||||
};
|
||||
var profile = Plugin.LANCommanderClient.GetProfile();
|
||||
|
||||
Plugin.Settings.PlayerName = String.IsNullOrWhiteSpace(profile.Alias) ? profile.UserName : profile.Alias;
|
||||
|
||||
// Probably unneeded, but why not be more secure?
|
||||
Context.Password = String.Empty;
|
||||
|
@ -124,6 +124,8 @@ namespace LANCommander.PlaynitePlugin.Views
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, ex.Message);
|
||||
|
||||
Plugin.PlayniteApi.Dialogs.ShowErrorMessage(ex.Message);
|
||||
|
||||
LoginButton.Dispatcher.Invoke(new System.Action(() =>
|
||||
|
@ -142,24 +144,16 @@ namespace LANCommander.PlaynitePlugin.Views
|
|||
RegisterButton.IsEnabled = false;
|
||||
RegisterButton.Content = "Working...";
|
||||
|
||||
if (Plugin.LANCommander == null || Plugin.LANCommander.Client == null)
|
||||
Plugin.LANCommander = new LANCommanderClient(Context.ServerAddress);
|
||||
else
|
||||
Plugin.LANCommander.Client.BaseUrl = new Uri(Context.ServerAddress);
|
||||
if (Plugin.LANCommanderClient == null)
|
||||
Plugin.LANCommanderClient = new LANCommander.SDK.Client(Context.ServerAddress);
|
||||
|
||||
var response = await Plugin.LANCommander.RegisterAsync(Context.UserName, Context.Password);
|
||||
var response = await Plugin.LANCommanderClient.RegisterAsync(Context.UserName, Context.Password);
|
||||
|
||||
Plugin.Settings.ServerAddress = Context.ServerAddress;
|
||||
Plugin.Settings.AccessToken = response.AccessToken;
|
||||
Plugin.Settings.RefreshToken = response.RefreshToken;
|
||||
Plugin.Settings.PlayerName = Context.UserName;
|
||||
|
||||
Plugin.LANCommander.Token = new AuthToken()
|
||||
{
|
||||
AccessToken = response.AccessToken,
|
||||
RefreshToken = response.RefreshToken,
|
||||
};
|
||||
|
||||
Context.Password = String.Empty;
|
||||
|
||||
Plugin.SavePluginSettings(Plugin.Settings);
|
||||
|
|
|
@ -15,29 +15,44 @@
|
|||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="100" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="75" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" Content="Install Directory" />
|
||||
<TextBox Name="PART_InstallDirectory" Grid.Column="2" Text="{Binding InstallDirectory}" />
|
||||
<Button Grid.Column="4" Content="Browse" Click="SelectInstallDirectory_Click" VerticalAlignment="Center" Grid.ColumnSpan="2" />
|
||||
</Grid>
|
||||
|
||||
<Grid Height="40" Grid.Row="1">
|
||||
<Grid Grid.Row="1" Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="75" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Column="0" Content="Server Address" />
|
||||
<TextBox Name="PART_ServerAddress" Grid.Column="2" Text="{Binding ServerAddress}" IsEnabled="false" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="2" Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Name="PART_AuthenticationButton" Content="Authenticate" Grid.Column="0" Click="AuthenticateButton_Click" VerticalAlignment="Center" />
|
||||
<Label Name="PART_AuthenticateLabel" Margin="20,0,0,0" VerticalAlignment="Center" Grid.Column="1" />
|
||||
<Button Name="PART_DisconnectButton" Content="Disconnect" Grid.Column="0" Click="DisconnectButton_Click" VerticalAlignment="Center" />
|
||||
<Label Name="PART_AuthenticateLabel" Margin="20,0,0,0" VerticalAlignment="Center" Grid.Column="3" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -39,6 +39,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
PART_AuthenticateLabel.Content = "Checking authentication status...";
|
||||
PART_AuthenticationButton.IsEnabled = false;
|
||||
PART_InstallDirectory.Text = Settings.InstallDirectory;
|
||||
PART_ServerAddress.Text = Settings.ServerAddress;
|
||||
|
||||
var token = new AuthToken()
|
||||
{
|
||||
|
@ -46,7 +47,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
RefreshToken = Settings.RefreshToken,
|
||||
};
|
||||
|
||||
var task = Task.Run(() => Plugin.LANCommander.ValidateToken(token))
|
||||
var task = Task.Run(() => Plugin.LANCommanderClient.ValidateToken(token))
|
||||
.ContinueWith(antecedent =>
|
||||
{
|
||||
try
|
||||
|
@ -57,11 +58,17 @@ namespace LANCommander.PlaynitePlugin
|
|||
{
|
||||
PART_AuthenticateLabel.Content = "Authentication failed!";
|
||||
PART_AuthenticationButton.IsEnabled = true;
|
||||
PART_AuthenticationButton.Visibility = Visibility.Visible;
|
||||
PART_DisconnectButton.IsEnabled = false;
|
||||
PART_DisconnectButton.Visibility = Visibility.Hidden;
|
||||
}
|
||||
else
|
||||
{
|
||||
PART_AuthenticateLabel.Content = "Connection established!";
|
||||
PART_AuthenticationButton.IsEnabled = false;
|
||||
PART_AuthenticationButton.Visibility = Visibility.Hidden;
|
||||
PART_DisconnectButton.IsEnabled = true;
|
||||
PART_DisconnectButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
@ -74,9 +81,22 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
private void AuthenticateButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var authWindow = Plugin.ShowAuthenticationWindow();
|
||||
var authWindow = Plugin.ShowAuthenticationWindow(Settings.ServerAddress, AuthWindow_Closed);
|
||||
}
|
||||
|
||||
authWindow.Closed += AuthWindow_Closed;
|
||||
private void DisconnectButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Plugin.Settings.AccessToken = String.Empty;
|
||||
Plugin.Settings.RefreshToken = String.Empty;
|
||||
Plugin.LANCommanderClient.UseToken(null);
|
||||
|
||||
Plugin.SavePluginSettings(Plugin.Settings);
|
||||
|
||||
PART_AuthenticateLabel.Content = "Not Authenticated";
|
||||
PART_AuthenticationButton.IsEnabled = true;
|
||||
PART_AuthenticationButton.Visibility = Visibility.Visible;
|
||||
PART_DisconnectButton.IsEnabled = false;
|
||||
PART_DisconnectButton.Visibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
private void AuthWindow_Closed(object sender, EventArgs e)
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
AddonId: LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc
|
||||
Packages:
|
||||
- Version: 0.1.2
|
||||
RequiredApiVersion: 6.0.0
|
||||
ReleaseDate: 2023-09-15
|
||||
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.1.2/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc-v0.1.2.pext
|
||||
Changelog:
|
||||
- Initial submission to Playnite addon database
|
||||
- Version: 0.1.3
|
||||
RequiredApiVersion: 6.0.0
|
||||
ReleaseDate: 2023-09-21
|
||||
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.1.3/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_v0.1.3.pext
|
||||
Changelog:
|
||||
- Added the ability to cancel installs
|
||||
- Version: 0.2.0
|
||||
RequiredApiVersion: 6.0.0
|
||||
ReleaseDate: 2023-10-28
|
||||
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.2.0/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_0_2_0.pext
|
||||
Changelog:
|
||||
- Player name is now persisted across machines and is stored as the user's profile name
|
||||
- Added support for servers to bundle redistributables with games
|
||||
- Added support for opening authentication window using Playnite API
|
||||
- Version: 0.2.1
|
||||
RequiredApiVersion: 6.0.0
|
||||
ReleaseDate: 2023-11-03
|
||||
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.2.1/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_0_2_1.pext
|
||||
Changelog:
|
||||
- LANCommander servers can now provide game art to Playnite clients
|
||||
- Added server address to addon settings
|
||||
- Added disconnect button to addon settings
|
||||
- Version: 0.2.2
|
||||
RequiredApiVersion: 6.0.0
|
||||
ReleaseDate: 2023-11-20
|
||||
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.2.2/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_0_2_2.pext
|
||||
Changelog:
|
||||
- _manifest.yml files in the game's install directory now includes the game's ID
|
||||
- Installation progress dialog now shows the download percentage and transfer speed
|
||||
- Full game download will be skipped if the game files already exist, see full release notes for more details
|
||||
- Play sessions are now recorded to the server with user ID, start time, and end time
|
||||
- Connection status now updates correctly when authenticating through addon settings
|
||||
- Version: 0.3.0
|
||||
RequiredApiVersion: 6.0.0
|
||||
ReleaseDate: 2023-12-03
|
||||
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.3.0/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_0_3_0.pext
|
||||
Changelog:
|
||||
- Save paths now support regex patterns (experimental)
|
||||
- Fixed redistributable archive downloading
|
||||
- Fixed redistributable scripts not being able to run as admin if marked as such
|
||||
- Fixed game save downloading
|
||||
- Fixed addon loading of YamlDotNet library
|
|
@ -1,20 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Bcl.AsyncInterfaces" version="7.0.0" targetFramework="net462" />
|
||||
<package id="NuGet.CommandLine" version="6.7.0" targetFramework="net462" developmentDependency="true" />
|
||||
<package id="PlayniteSDK" version="6.9.0" targetFramework="net462" />
|
||||
<package id="ByteSize" version="2.1.1" targetFramework="net462" />
|
||||
<package id="Microsoft.Bcl.AsyncInterfaces" version="8.0.0" targetFramework="net462" />
|
||||
<package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="8.0.0" targetFramework="net462" />
|
||||
<package id="Microsoft.Extensions.Logging.Abstractions" version="8.0.0" targetFramework="net462" />
|
||||
<package id="NuGet.CommandLine" version="6.8.0" targetFramework="net462" developmentDependency="true" />
|
||||
<package id="PlayniteSDK" version="6.10.0" targetFramework="net462" />
|
||||
<package id="PowerShellStandard.Library" version="5.1.1" targetFramework="net462" />
|
||||
<package id="RestSharp" version="106.15.0" targetFramework="net462" />
|
||||
<package id="rix0rrr.BeaconLib" version="1.0.2" targetFramework="net462" />
|
||||
<package id="SharpCompress" version="0.33.0" targetFramework="net462" />
|
||||
<package id="SharpCompress" version="0.34.2" targetFramework="net462" />
|
||||
<package id="System.Buffers" version="4.5.1" targetFramework="net462" />
|
||||
<package id="System.Memory" version="4.5.5" targetFramework="net462" />
|
||||
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net462" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net462" />
|
||||
<package id="System.Text.Encoding.CodePages" version="7.0.0" targetFramework="net462" />
|
||||
<package id="System.Text.Encodings.Web" version="7.0.0" targetFramework="net462" />
|
||||
<package id="System.Text.Json" version="7.0.3" targetFramework="net462" />
|
||||
<package id="System.Text.Encoding.CodePages" version="8.0.0" targetFramework="net462" />
|
||||
<package id="System.Text.Encodings.Web" version="8.0.0" targetFramework="net462" />
|
||||
<package id="System.Text.Json" version="8.0.0" targetFramework="net462" />
|
||||
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net462" />
|
||||
<package id="System.ValueTuple" version="4.5.0" targetFramework="net462" />
|
||||
<package id="YamlDotNet" version="5.4.0" targetFramework="net462" />
|
||||
<package id="ZstdSharp.Port" version="0.7.2" targetFramework="net462" />
|
||||
</packages>
|
|
@ -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<string>().ToList();
|
||||
|
||||
Assert.AreEqual(1, encodingResults.Count);
|
||||
|
||||
var decodingCmdlet = new ConvertFromSerializedBase64Cmdlet()
|
||||
{
|
||||
Input = encodingResults.First()
|
||||
};
|
||||
|
||||
var decodingResults = decodingCmdlet.Invoke().OfType<string>().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<DisplayResolution>().ToList();
|
||||
|
||||
Assert.AreEqual(1, output.Count);
|
||||
|
||||
var bounds = output.First();
|
||||
|
||||
Assert.AreEqual(x2, bounds.Width);
|
||||
Assert.AreEqual(y2, bounds.Height);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{D7069A13-F0AA-4CBF-9013-4276F130A6DD}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>LANCommander.PowerShell.Tests</RootNamespace>
|
||||
<AssemblyName>LANCommander.PowerShell.Tests</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
|
||||
<IsCodedUITest>False</IsCodedUITest>
|
||||
<TestProjectType>UnitTest</TestProjectType>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=5.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.5.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="RestSharp, Version=106.15.0.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\RestSharp.106.15.0\lib\net452\RestSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SharpCompress, Version=0.34.2.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpCompress.0.34.2\lib\net462\SharpCompress.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
|
||||
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Text.Encoding.CodePages, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Text.Encoding.CodePages.8.0.0\lib\net462\System.Text.Encoding.CodePages.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="YamlDotNet, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\YamlDotNet.5.4.0\lib\net45\YamlDotNet.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ZstdSharp, Version=0.7.4.0, Culture=neutral, PublicKeyToken=8d151af33a4ad5cf, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ZstdSharp.Port.0.7.4\lib\net462\ZstdSharp.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Cmdlets.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LANCommander.PowerShell\LANCommander.PowerShell.csproj">
|
||||
<Project>{807943bf-0c7d-4ed3-8393-cfee64e3138c}</Project>
|
||||
<Name>LANCommander.PowerShell</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\LANCommander.SDK\LANCommander.SDK.csproj">
|
||||
<Project>{4c2a71fd-a30b-4d62-888a-4ef843d8e506}</Project>
|
||||
<Name>LANCommander.SDK</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>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}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.props'))" />
|
||||
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.targets')" />
|
||||
</Project>
|
|
@ -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")]
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Bcl.AsyncInterfaces" version="5.0.0" targetFramework="net462" />
|
||||
<package id="MSTest.TestAdapter" version="2.2.10" targetFramework="net462" />
|
||||
<package id="MSTest.TestFramework" version="2.2.10" targetFramework="net462" />
|
||||
<package id="RestSharp" version="106.15.0" targetFramework="net462" />
|
||||
<package id="SharpCompress" version="0.34.2" targetFramework="net462" />
|
||||
<package id="System.Buffers" version="4.5.1" targetFramework="net462" />
|
||||
<package id="System.Memory" version="4.5.5" targetFramework="net462" />
|
||||
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net462" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net462" />
|
||||
<package id="System.Text.Encoding.CodePages" version="8.0.0" targetFramework="net462" />
|
||||
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net462" />
|
||||
<package id="YamlDotNet" version="5.4.0" targetFramework="net462" />
|
||||
<package id="ZstdSharp.Port" version="0.7.4" targetFramework="net462" />
|
||||
</packages>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
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;
|
||||
|
||||
[Parameter]
|
||||
public int MinLength { get; set; } = 0;
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
byte[] output;
|
||||
|
||||
if (MaxLength > 0 && Input.Length > MaxLength)
|
||||
Input = Input.Substring(0, MaxLength);
|
||||
|
||||
if (MinLength > 0 && MinLength < MaxLength)
|
||||
Input = Input.PadRight(MinLength, '\0');
|
||||
else if (MinLength > 0)
|
||||
Input = Input.PadRight(MaxLength, '\0');
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
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()
|
||||
{
|
||||
if (File.Exists(FilePath))
|
||||
{
|
||||
var contents = File.ReadAllText(FilePath);
|
||||
var regex = new Regex(Pattern, RegexOptions.Multiline);
|
||||
|
||||
contents = regex.Replace(contents, Substitution);
|
||||
|
||||
File.WriteAllText(FilePath, contents);
|
||||
|
||||
WriteObject(contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{807943BF-0C7D-4ED3-8393-CFEE64E3138C}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>LANCommander.PowerShell</RootNamespace>
|
||||
<AssemblyName>LANCommander.PowerShell</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\PowerShellStandard.Library.5.1.1\lib\net452\System.Management.Automation.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Cmdlets\ConvertTo-SerializedBase64.cs" />
|
||||
<Compile Include="Cmdlets\ConvertFrom-SerializedBase64.cs" />
|
||||
<Compile Include="Cmdlets\Edit-PatchBinary.cs" />
|
||||
<Compile Include="Cmdlets\Uninstall-Game.cs" />
|
||||
<Compile Include="Cmdlets\Install-Game.cs" />
|
||||
<Compile Include="Cmdlets\Write-ReplaceContentInFile.cs" />
|
||||
<Compile Include="Cmdlets\ConvertTo-StringBytes.cs" />
|
||||
<Compile Include="Cmdlets\Get-PrimaryDisplay.cs" />
|
||||
<Compile Include="Cmdlets\Convert-AspectRatio.cs" />
|
||||
<Compile Include="Cmdlets\Get-GameManifest.cs" />
|
||||
<Compile Include="Cmdlets\Write-GameManifest.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="LANCommander.PowerShell.psd1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LANCommander.SDK\LANCommander.SDK.csproj">
|
||||
<Project>{4c2a71fd-a30b-4d62-888a-4ef843d8e506}</Project>
|
||||
<Name>LANCommander.SDK</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
Binary file not shown.
|
@ -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")]
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="PowerShellStandard.Library" version="5.1.1" targetFramework="net472" />
|
||||
</packages>
|
|
@ -1,6 +1,6 @@
|
|||
using LANCommander.SDK;
|
||||
using LANCommander.SDK.Models;
|
||||
using Playnite.SDK;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RestSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -11,19 +11,33 @@ using System.Net;
|
|||
using System.Net.NetworkInformation;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
internal class LANCommanderClient
|
||||
public class Client
|
||||
{
|
||||
public static readonly ILogger Logger = LogManager.GetLogger();
|
||||
private readonly ILogger Logger;
|
||||
|
||||
public readonly RestClient Client;
|
||||
public AuthToken Token;
|
||||
private RestClient ApiClient;
|
||||
private AuthToken Token;
|
||||
|
||||
public LANCommanderClient(string baseUrl)
|
||||
public string BaseUrl;
|
||||
|
||||
public Client(string baseUrl)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(baseUrl))
|
||||
Client = new RestClient(baseUrl);
|
||||
BaseUrl = baseUrl;
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(BaseUrl))
|
||||
ApiClient = new RestClient(BaseUrl);
|
||||
}
|
||||
|
||||
public Client(string baseUrl, ILogger logger)
|
||||
{
|
||||
BaseUrl = baseUrl;
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(BaseUrl))
|
||||
ApiClient = new RestClient(BaseUrl);
|
||||
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
private T PostRequest<T>(string route, object body)
|
||||
|
@ -32,7 +46,17 @@ namespace LANCommander.PlaynitePlugin
|
|||
.AddJsonBody(body)
|
||||
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
||||
|
||||
var response = Client.Post<T>(request);
|
||||
var response = ApiClient.Post<T>(request);
|
||||
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
private T PostRequest<T>(string route)
|
||||
{
|
||||
var request = new RestRequest(route)
|
||||
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
||||
|
||||
var response = ApiClient.Post<T>(request);
|
||||
|
||||
return response.Data;
|
||||
}
|
||||
|
@ -42,7 +66,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
var request = new RestRequest(route)
|
||||
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
||||
|
||||
var response = Client.Get<T>(request);
|
||||
var response = ApiClient.Get<T>(request);
|
||||
|
||||
return response.Data;
|
||||
}
|
||||
|
@ -58,7 +82,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
client.DownloadProgressChanged += (s, e) => progressHandler(e);
|
||||
client.DownloadFileCompleted += (s, e) => completeHandler(e);
|
||||
|
||||
client.DownloadFileAsync(new Uri($"{Client.BaseUrl}{route}"), tempFile);
|
||||
client.DownloadFileAsync(new Uri($"{ApiClient.BaseUrl}{route}"), tempFile);
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
|
@ -72,14 +96,14 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
client.Headers.Add("Authorization", $"Bearer {Token.AccessToken}");
|
||||
|
||||
var ws = client.OpenRead(new Uri($"{Client.BaseUrl}{route}"));
|
||||
var ws = client.OpenRead(new Uri($"{ApiClient.BaseUrl}{route}"));
|
||||
|
||||
return new TrackableStream(ws, true, Convert.ToInt64(client.ResponseHeaders["Content-Length"]));
|
||||
}
|
||||
|
||||
public async Task<AuthResponse> AuthenticateAsync(string username, string password)
|
||||
public async Task<AuthToken> AuthenticateAsync(string username, string password)
|
||||
{
|
||||
var response = await Client.ExecuteAsync<AuthResponse>(new RestRequest("/api/Auth", Method.POST).AddJsonBody(new AuthRequest()
|
||||
var response = await ApiClient.ExecuteAsync<AuthResponse>(new RestRequest("/api/Auth", Method.POST).AddJsonBody(new AuthRequest()
|
||||
{
|
||||
UserName = username,
|
||||
Password = password
|
||||
|
@ -88,7 +112,14 @@ namespace LANCommander.PlaynitePlugin
|
|||
switch (response.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.OK:
|
||||
return response.Data;
|
||||
Token = new AuthToken
|
||||
{
|
||||
AccessToken = response.Data.AccessToken,
|
||||
RefreshToken = response.Data.RefreshToken,
|
||||
Expiration = response.Data.Expiration
|
||||
};
|
||||
|
||||
return Token;
|
||||
|
||||
case HttpStatusCode.Forbidden:
|
||||
case HttpStatusCode.BadRequest:
|
||||
|
@ -100,9 +131,9 @@ namespace LANCommander.PlaynitePlugin
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<AuthResponse> RegisterAsync(string username, string password)
|
||||
public async Task<AuthToken> RegisterAsync(string username, string password)
|
||||
{
|
||||
var response = await Client.ExecuteAsync<AuthResponse>(new RestRequest("/api/auth/register", Method.POST).AddJsonBody(new AuthRequest()
|
||||
var response = await ApiClient.ExecuteAsync<AuthResponse>(new RestRequest("/api/auth/register", Method.POST).AddJsonBody(new AuthRequest()
|
||||
{
|
||||
UserName = username,
|
||||
Password = password
|
||||
|
@ -111,7 +142,14 @@ namespace LANCommander.PlaynitePlugin
|
|||
switch (response.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.OK:
|
||||
return response.Data;
|
||||
Token = new AuthToken
|
||||
{
|
||||
AccessToken = response.Data.AccessToken,
|
||||
RefreshToken = response.Data.RefreshToken,
|
||||
Expiration = response.Data.Expiration
|
||||
};
|
||||
|
||||
return Token;
|
||||
|
||||
case HttpStatusCode.BadRequest:
|
||||
case HttpStatusCode.Forbidden:
|
||||
|
@ -125,33 +163,45 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
public async Task<bool> PingAsync()
|
||||
{
|
||||
var response = await Client.ExecuteAsync(new RestRequest("/api/Ping", Method.GET));
|
||||
var response = await ApiClient.ExecuteAsync(new RestRequest("/api/Ping", Method.GET));
|
||||
|
||||
return response.StatusCode == HttpStatusCode.OK;
|
||||
}
|
||||
|
||||
public AuthResponse RefreshToken(AuthToken token)
|
||||
public AuthToken RefreshToken(AuthToken token)
|
||||
{
|
||||
Logger.Trace("Refreshing token...");
|
||||
Logger?.LogTrace("Refreshing token...");
|
||||
|
||||
var request = new RestRequest("/api/Auth/Refresh")
|
||||
.AddJsonBody(token);
|
||||
|
||||
var response = Client.Post<AuthResponse>(request);
|
||||
var response = ApiClient.Post<AuthResponse>(request);
|
||||
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
throw new WebException(response.ErrorMessage);
|
||||
|
||||
return response.Data;
|
||||
Token = new AuthToken
|
||||
{
|
||||
AccessToken = response.Data.AccessToken,
|
||||
RefreshToken = response.Data.RefreshToken,
|
||||
Expiration = response.Data.Expiration
|
||||
};
|
||||
|
||||
return Token;
|
||||
}
|
||||
|
||||
public bool ValidateToken()
|
||||
{
|
||||
return ValidateToken(Token);
|
||||
}
|
||||
|
||||
public bool ValidateToken(AuthToken token)
|
||||
{
|
||||
Logger.Trace("Validating token...");
|
||||
Logger?.LogTrace("Validating token...");
|
||||
|
||||
if (token == null)
|
||||
{
|
||||
Logger.Trace("Token is null!");
|
||||
Logger?.LogTrace("Token is null!");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -160,22 +210,33 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
if (String.IsNullOrEmpty(token.AccessToken) || String.IsNullOrEmpty(token.RefreshToken))
|
||||
{
|
||||
Logger.Trace("Token is empty!");
|
||||
Logger?.LogTrace("Token is empty!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var response = Client.Post(request);
|
||||
var response = ApiClient.Post(request);
|
||||
|
||||
var valid = response.StatusCode == HttpStatusCode.OK;
|
||||
|
||||
if (valid)
|
||||
Logger.Trace("Token is valid!");
|
||||
Logger?.LogTrace("Token is valid!");
|
||||
else
|
||||
Logger.Trace("Token is invalid!");
|
||||
Logger?.LogTrace("Token is invalid!");
|
||||
|
||||
return response.StatusCode == HttpStatusCode.OK;
|
||||
}
|
||||
|
||||
public void UseToken(AuthToken token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
|
||||
public void UseServerAddress(string address)
|
||||
{
|
||||
BaseUrl = address;
|
||||
ApiClient = new RestClient(BaseUrl);
|
||||
}
|
||||
|
||||
public IEnumerable<Game> GetGames()
|
||||
{
|
||||
return GetRequest<IEnumerable<Game>>("/api/Games");
|
||||
|
@ -206,6 +267,11 @@ namespace LANCommander.PlaynitePlugin
|
|||
return DownloadRequest($"/api/Archives/Download/{id}", progressHandler, completeHandler);
|
||||
}
|
||||
|
||||
public TrackableStream StreamRedistributable(Guid id)
|
||||
{
|
||||
return StreamRequest($"/api/Redistributables/{id}/Download");
|
||||
}
|
||||
|
||||
public string DownloadSave(Guid id, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
|
||||
{
|
||||
return DownloadRequest($"/api/Saves/Download/{id}", progressHandler, completeHandler);
|
||||
|
@ -218,21 +284,26 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
public GameSave UploadSave(string gameId, byte[] data)
|
||||
{
|
||||
Logger.Trace("Uploading save...");
|
||||
Logger?.LogTrace("Uploading save...");
|
||||
|
||||
var request = new RestRequest($"/api/Saves/Upload/{gameId}", Method.POST)
|
||||
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
||||
|
||||
request.AddFile(gameId, data, gameId);
|
||||
|
||||
var response = Client.Post<GameSave>(request);
|
||||
var response = ApiClient.Post<GameSave>(request);
|
||||
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public string GetMediaUrl(Media media)
|
||||
{
|
||||
return (new Uri(ApiClient.BaseUrl, $"/api/Media/{media.Id}/Download?fileId={media.FileId}").ToString());
|
||||
}
|
||||
|
||||
public string GetKey(Guid id)
|
||||
{
|
||||
Logger.Trace("Requesting key allocation...");
|
||||
Logger?.LogTrace("Requesting key allocation...");
|
||||
|
||||
var macAddress = GetMacAddress();
|
||||
|
||||
|
@ -251,7 +322,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
public string GetAllocatedKey(Guid id)
|
||||
{
|
||||
Logger.Trace("Requesting allocated key...");
|
||||
Logger?.LogTrace("Requesting allocated key...");
|
||||
|
||||
var macAddress = GetMacAddress();
|
||||
|
||||
|
@ -273,7 +344,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
public string GetNewKey(Guid id)
|
||||
{
|
||||
Logger.Trace("Requesting new key allocation...");
|
||||
Logger?.LogTrace("Requesting new key allocation...");
|
||||
|
||||
var macAddress = GetMacAddress();
|
||||
|
||||
|
@ -293,6 +364,36 @@ namespace LANCommander.PlaynitePlugin
|
|||
return response.Value;
|
||||
}
|
||||
|
||||
public User GetProfile()
|
||||
{
|
||||
Logger?.LogTrace("Requesting player's profile...");
|
||||
|
||||
return GetRequest<User>("/api/Profile");
|
||||
}
|
||||
|
||||
public string ChangeAlias(string alias)
|
||||
{
|
||||
Logger?.LogTrace("Requesting to change player alias...");
|
||||
|
||||
var response = PostRequest<object>("/api/Profile/ChangeAlias", alias);
|
||||
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void StartPlaySession(Guid gameId)
|
||||
{
|
||||
Logger?.LogTrace("Starting a game session...");
|
||||
|
||||
PostRequest<object>($"/api/PlaySessions/Start/{gameId}");
|
||||
}
|
||||
|
||||
public void EndPlaySession(Guid gameId)
|
||||
{
|
||||
Logger?.LogTrace("Ending a game session...");
|
||||
|
||||
PostRequest<object>($"/api/PlaySessions/End/{gameId}");
|
||||
}
|
||||
|
||||
private string GetMacAddress()
|
||||
{
|
||||
return NetworkInterface.GetAllNetworkInterfaces()
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK.Enums
|
||||
{
|
||||
public enum MediaType
|
||||
{
|
||||
Icon,
|
||||
Cover,
|
||||
Background
|
||||
}
|
||||
}
|
|
@ -5,6 +5,9 @@
|
|||
Install,
|
||||
Uninstall,
|
||||
NameChange,
|
||||
KeyChange
|
||||
KeyChange,
|
||||
SaveUpload,
|
||||
SaveDownload,
|
||||
DetectInstall
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
public class ArchiveExtractionProgressArgs : EventArgs
|
||||
{
|
||||
public long Position { get; set; }
|
||||
public long Length { get; set; }
|
||||
}
|
||||
|
||||
public class ArchiveEntryExtractionProgressArgs : EventArgs
|
||||
{
|
||||
public ReaderProgress Progress { get; set; }
|
||||
public IEntry Entry { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
internal class ExtractionResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public bool Canceled { get; set; }
|
||||
public string Directory { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
using LANCommander.SDK.Extensions;
|
||||
using LANCommander.SDK.Helpers;
|
||||
using LANCommander.SDK.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
public class GameManager
|
||||
{
|
||||
private readonly ILogger Logger;
|
||||
private Client Client { get; set; }
|
||||
private string DefaultInstallDirectory { get; set; }
|
||||
|
||||
public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e);
|
||||
public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress;
|
||||
|
||||
public delegate void OnArchiveExtractionProgressHandler(long position, long length);
|
||||
public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress;
|
||||
|
||||
private TrackableStream Stream;
|
||||
private IReader Reader;
|
||||
|
||||
public GameManager(Client client, string defaultInstallDirectory)
|
||||
{
|
||||
Client = client;
|
||||
DefaultInstallDirectory = defaultInstallDirectory;
|
||||
}
|
||||
|
||||
public GameManager(Client client, string defaultInstallDirectory, ILogger logger)
|
||||
{
|
||||
Client = client;
|
||||
DefaultInstallDirectory = defaultInstallDirectory;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads, extracts, and runs post-install scripts for the specified game
|
||||
/// </summary>
|
||||
/// <param name="game">Game to install</param>
|
||||
/// <param name="maxAttempts">Maximum attempts in case of transmission error</param>
|
||||
/// <returns>Final install path</returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public string Install(Guid gameId, int maxAttempts = 10)
|
||||
{
|
||||
GameManifest manifest = null;
|
||||
|
||||
var game = Client.GetGame(gameId);
|
||||
|
||||
var destination = Path.Combine(DefaultInstallDirectory, game.Title.SanitizeFilename());
|
||||
|
||||
try
|
||||
{
|
||||
if (ManifestHelper.Exists(destination))
|
||||
manifest = ManifestHelper.Read(destination);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogTrace(ex, "Error reading manifest before install");
|
||||
}
|
||||
|
||||
if (manifest == null || manifest.Id != gameId)
|
||||
{
|
||||
Logger?.LogTrace("Installing game {GameTitle} ({GameId})", game.Title, game.Id);
|
||||
|
||||
var result = RetryHelper.RetryOnException<ExtractionResult>(maxAttempts, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () =>
|
||||
{
|
||||
Logger?.LogTrace("Attempting to download and extract game");
|
||||
|
||||
return DownloadAndExtract(game, destination);
|
||||
});
|
||||
|
||||
if (!result.Success && !result.Canceled)
|
||||
throw new Exception("Could not extract the installer. Retry the install or check your connection");
|
||||
else if (result.Canceled)
|
||||
return "";
|
||||
|
||||
game.InstallDirectory = result.Directory;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger?.LogTrace("Game {GameTitle} ({GameId}) is already installed to {InstallDirectory}", game.Title, game.Id, destination);
|
||||
|
||||
game.InstallDirectory = destination;
|
||||
}
|
||||
|
||||
var writeManifestSuccess = RetryHelper.RetryOnException(maxAttempts, TimeSpan.FromSeconds(1), false, () =>
|
||||
{
|
||||
Logger?.LogTrace("Attempting to get game manifest");
|
||||
|
||||
manifest = Client.GetGameManifest(game.Id);
|
||||
|
||||
ManifestHelper.Write(manifest, game.InstallDirectory);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!writeManifestSuccess)
|
||||
throw new Exception("Could not grab the manifest file. Retry the install or check your connection");
|
||||
|
||||
Logger?.LogTrace("Saving scripts");
|
||||
|
||||
ScriptHelper.SaveScript(game, ScriptType.Install);
|
||||
ScriptHelper.SaveScript(game, ScriptType.Uninstall);
|
||||
ScriptHelper.SaveScript(game, ScriptType.NameChange);
|
||||
ScriptHelper.SaveScript(game, ScriptType.KeyChange);
|
||||
|
||||
return game.InstallDirectory;
|
||||
}
|
||||
|
||||
public void Uninstall(string installDirectory)
|
||||
{
|
||||
|
||||
Logger?.LogTrace("Attempting to delete the install directory");
|
||||
|
||||
if (Directory.Exists(installDirectory))
|
||||
Directory.Delete(installDirectory, true);
|
||||
|
||||
Logger?.LogTrace("Deleted install directory {InstallDirectory}", installDirectory);
|
||||
}
|
||||
|
||||
private ExtractionResult DownloadAndExtract(Game game, string destination)
|
||||
{
|
||||
if (game == null)
|
||||
{
|
||||
Logger?.LogTrace("Game failed to download, no game was specified");
|
||||
|
||||
throw new ArgumentNullException("No game was specified");
|
||||
}
|
||||
|
||||
Logger?.LogTrace("Downloading and extracting {Game} to path {Destination}", game.Title, destination);
|
||||
|
||||
var extractionResult = new ExtractionResult
|
||||
{
|
||||
Canceled = false,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(destination);
|
||||
|
||||
Stream = Client.StreamGame(game.Id);
|
||||
Reader = ReaderFactory.Open(Stream);
|
||||
|
||||
Stream.OnProgress += (pos, len) =>
|
||||
{
|
||||
OnArchiveExtractionProgress?.Invoke(pos, len);
|
||||
};
|
||||
|
||||
Reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
|
||||
{
|
||||
OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs
|
||||
{
|
||||
Entry = e.Item,
|
||||
Progress = e.ReaderProgress,
|
||||
});
|
||||
};
|
||||
|
||||
while (Reader.MoveToNextEntry())
|
||||
{
|
||||
if (Reader.Cancelled)
|
||||
break;
|
||||
|
||||
Reader.WriteEntryToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true,
|
||||
PreserveFileTime = true,
|
||||
});
|
||||
}
|
||||
|
||||
Reader.Dispose();
|
||||
Stream.Dispose();
|
||||
}
|
||||
catch (ReaderCancelledException ex)
|
||||
{
|
||||
Logger?.LogTrace("User cancelled the download");
|
||||
|
||||
extractionResult.Canceled = true;
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger?.LogTrace("Cleaning up orphaned files after cancelled install");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, "Could not extract to path {Destination}", destination);
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger?.LogTrace("Cleaning up orphaned install files after bad install");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
}
|
||||
|
||||
throw new Exception("The game archive could not be extracted, is it corrupted? Please try again");
|
||||
}
|
||||
|
||||
if (!extractionResult.Canceled)
|
||||
{
|
||||
extractionResult.Success = true;
|
||||
extractionResult.Directory = destination;
|
||||
|
||||
Logger?.LogTrace("Game {Game} successfully downloaded and extracted to {Destination}", game.Title, destination);
|
||||
}
|
||||
|
||||
return extractionResult;
|
||||
}
|
||||
|
||||
public void CancelInstall()
|
||||
{
|
||||
Reader?.Cancel();
|
||||
// Reader?.Dispose();
|
||||
// Stream?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
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;
|
||||
using SharpCompress.Readers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
public class GameSaveManager
|
||||
{
|
||||
private readonly Client Client;
|
||||
|
||||
public delegate void OnDownloadProgressHandler(DownloadProgressChangedEventArgs e);
|
||||
public event OnDownloadProgressHandler OnDownloadProgress;
|
||||
|
||||
public delegate void OnDownloadCompleteHandler(AsyncCompletedEventArgs e);
|
||||
public event OnDownloadCompleteHandler OnDownloadComplete;
|
||||
|
||||
public GameSaveManager(Client client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
|
||||
public void Download(string installDirectory)
|
||||
{
|
||||
var manifest = ManifestHelper.Read(installDirectory);
|
||||
|
||||
string tempFile = String.Empty;
|
||||
|
||||
if (manifest != null)
|
||||
{
|
||||
var destination = Client.DownloadLatestSave(manifest.Id, (changed) =>
|
||||
{
|
||||
OnDownloadProgress?.Invoke(changed);
|
||||
}, (complete) =>
|
||||
{
|
||||
OnDownloadComplete?.Invoke(complete);
|
||||
});
|
||||
|
||||
tempFile = destination;
|
||||
|
||||
// Go into the archive and extract the files to the correct locations
|
||||
try
|
||||
{
|
||||
var tempLocation = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
|
||||
Directory.CreateDirectory(tempLocation);
|
||||
|
||||
ExtractFilesFromZip(tempFile, tempLocation);
|
||||
|
||||
#region Move files
|
||||
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
|
||||
{
|
||||
bool inInstallDir = savePath.Path.StartsWith("{InstallDir}");
|
||||
string tempSavePath = Path.Combine(tempLocation, savePath.Id.ToString());
|
||||
|
||||
foreach (var entry in savePath.Entries)
|
||||
{
|
||||
var tempSavePathFile = Path.Combine(tempSavePath, entry.ArchivePath);
|
||||
|
||||
destination = Environment.ExpandEnvironmentVariables(entry.ActualPath).Replace("{InstallDir}", installDirectory);
|
||||
|
||||
if (File.Exists(tempSavePathFile))
|
||||
{
|
||||
if (File.Exists(destination))
|
||||
File.Delete(destination);
|
||||
|
||||
File.Move(tempSavePathFile, destination);
|
||||
}
|
||||
else if (Directory.Exists(tempSavePath))
|
||||
{
|
||||
var files = Directory.GetFiles(tempSavePath, "*", SearchOption.AllDirectories);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (inInstallDir)
|
||||
{
|
||||
// Files are in the game's install directory. Move them there from the save path.
|
||||
destination = file.Replace(tempSavePath, savePath.Path.Replace('/', Path.DirectorySeparatorChar).TrimEnd(Path.DirectorySeparatorChar).Replace("{InstallDir}", installDirectory));
|
||||
|
||||
if (File.Exists(destination))
|
||||
File.Delete(destination);
|
||||
|
||||
File.Move(file, destination);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Specified path is probably an absolute path, maybe with environment variables.
|
||||
destination = Environment.ExpandEnvironmentVariables(file.Replace(tempSavePathFile, savePath.Path.Replace('/', Path.DirectorySeparatorChar)));
|
||||
|
||||
if (File.Exists(destination))
|
||||
File.Delete(destination);
|
||||
|
||||
File.Move(file, destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Handle registry importing
|
||||
var registryImportFilePath = Path.Combine(tempLocation, "_registry.reg");
|
||||
|
||||
if (File.Exists(registryImportFilePath))
|
||||
{
|
||||
var registryImportFileContents = File.ReadAllText(registryImportFilePath);
|
||||
|
||||
var script = new PowerShellScript();
|
||||
|
||||
script.UseInline($"regedit.exe /s \"{registryImportFilePath}\"");
|
||||
|
||||
if (registryImportFileContents.Contains("HKEY_LOCAL_MACHINE"))
|
||||
script.RunAsAdmin();
|
||||
|
||||
script.Execute();
|
||||
}
|
||||
#endregion
|
||||
|
||||
// Clean up temp files
|
||||
Directory.Delete(tempLocation, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Upload(string installDirectory)
|
||||
{
|
||||
var manifest = ManifestHelper.Read(installDirectory);
|
||||
|
||||
var temp = Path.GetTempFileName();
|
||||
|
||||
if (manifest.SavePaths != null && manifest.SavePaths.Count() > 0)
|
||||
{
|
||||
using (var archive = ZipArchive.Create())
|
||||
{
|
||||
archive.DeflateCompressionLevel = SharpCompress.Compressors.Deflate.CompressionLevel.BestCompression;
|
||||
|
||||
#region Add files from defined paths
|
||||
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
|
||||
{
|
||||
IEnumerable<string> localPaths;
|
||||
|
||||
if (savePath.IsRegex)
|
||||
{
|
||||
var regex = new Regex(Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", installDirectory)));
|
||||
|
||||
localPaths = Directory.GetFiles(installDirectory, "*", SearchOption.AllDirectories)
|
||||
.Where(p => regex.IsMatch(p))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
localPaths = new string[] { savePath.Path };
|
||||
|
||||
var entries = new List<SavePathEntry>();
|
||||
|
||||
foreach (var localPath in localPaths)
|
||||
{
|
||||
var actualPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', Path.DirectorySeparatorChar).Replace("{InstallDir}", installDirectory));
|
||||
var relativePath = actualPath.Replace(installDirectory + Path.DirectorySeparatorChar, "");
|
||||
|
||||
if (Directory.Exists(actualPath))
|
||||
{
|
||||
AddDirectoryToZip(archive, relativePath, actualPath, savePath.Id);
|
||||
}
|
||||
else if (File.Exists(actualPath))
|
||||
{
|
||||
archive.AddEntry(Path.Combine(savePath.Id.ToString(), relativePath), actualPath);
|
||||
}
|
||||
|
||||
entries.Add(new SavePathEntry
|
||||
{
|
||||
ArchivePath = relativePath,
|
||||
ActualPath = actualPath.Replace(installDirectory, "{InstallDir}")
|
||||
});
|
||||
|
||||
savePath.Entries = entries;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Export registry keys
|
||||
if (manifest.SavePaths.Any(sp => sp.Type == "Registry"))
|
||||
{
|
||||
List<string> tempRegFiles = new List<string>();
|
||||
|
||||
var exportCommand = new StringBuilder();
|
||||
|
||||
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "Registry"))
|
||||
{
|
||||
var tempRegFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".reg");
|
||||
|
||||
exportCommand.AppendLine($"reg.exe export \"{savePath.Path.Replace(":\\", "\\")}\" \"{tempRegFile}\"");
|
||||
tempRegFiles.Add(tempRegFile);
|
||||
}
|
||||
|
||||
var script = new PowerShellScript();
|
||||
|
||||
script.UseInline(exportCommand.ToString());
|
||||
|
||||
script.Execute();
|
||||
|
||||
var exportFile = new StringBuilder();
|
||||
|
||||
foreach (var tempRegFile in tempRegFiles)
|
||||
{
|
||||
exportFile.AppendLine(File.ReadAllText(tempRegFile));
|
||||
File.Delete(tempRegFile);
|
||||
}
|
||||
|
||||
archive.AddEntry("_registry.reg", new MemoryStream(Encoding.UTF8.GetBytes(exportFile.ToString())), true);
|
||||
}
|
||||
#endregion
|
||||
|
||||
var tempManifest = Path.GetTempFileName();
|
||||
|
||||
File.WriteAllText(tempManifest, ManifestHelper.Serialize(manifest));
|
||||
|
||||
archive.AddEntry("_manifest.yml", tempManifest);
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
archive.SaveTo(ms);
|
||||
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var save = Client.UploadSave(manifest.Id.ToString(), ms.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDirectoryToZip(ZipArchive zipArchive, string path, string workingDirectory, Guid pathId)
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(path))
|
||||
{
|
||||
// Oh man is this a hack. We should be removing only the working directory from the start,
|
||||
// but we're making the assumption that the working dir put in actually prefixes the path.
|
||||
// Also wtf, that Path.Combine is stripping the pathId out?
|
||||
zipArchive.AddEntry(Path.Combine(pathId.ToString(), path.Substring(workingDirectory.Length), Path.GetFileName(file)), file);
|
||||
}
|
||||
|
||||
foreach (var child in Directory.GetDirectories(path))
|
||||
{
|
||||
// See above
|
||||
//ZipEntry entry = new ZipEntry(Path.Combine(pathId.ToString(), path.Substring(workingDirectory.Length), Path.GetFileName(path)));
|
||||
|
||||
//zipStream.PutNextEntry(entry);
|
||||
//zipStream.CloseEntry();
|
||||
|
||||
AddDirectoryToZip(zipArchive, child, workingDirectory, pathId);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtractFilesFromZip(string zipPath, string destination)
|
||||
{
|
||||
using (var fs = File.OpenRead(zipPath))
|
||||
using (var ts = new TrackableStream(fs))
|
||||
using (var reader = ReaderFactory.Open(ts))
|
||||
{
|
||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace LANCommander.SDK.Helpers
|
||||
{
|
||||
public static class ManifestHelper
|
||||
{
|
||||
public static readonly ILogger Logger;
|
||||
|
||||
public const string ManifestFilename = "_manifest.yml";
|
||||
|
||||
public static bool Exists(string installDirectory)
|
||||
{
|
||||
var path = GetPath(installDirectory);
|
||||
|
||||
return File.Exists(path);
|
||||
}
|
||||
|
||||
public static GameManifest Read(string installDirectory)
|
||||
{
|
||||
var source = GetPath(installDirectory);
|
||||
var yaml = File.ReadAllText(source);
|
||||
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(new PascalCaseNamingConvention())
|
||||
.Build();
|
||||
|
||||
Logger?.LogTrace("Deserializing manifest");
|
||||
|
||||
var manifest = deserializer.Deserialize<GameManifest>(yaml);
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
public static string Write(GameManifest manifest, string installDirectory)
|
||||
{
|
||||
var destination = GetPath(installDirectory);
|
||||
|
||||
Logger?.LogTrace("Attempting to write manifest to path {Destination}", destination);
|
||||
|
||||
var yaml = Serialize(manifest);
|
||||
|
||||
Logger?.LogTrace("Writing manifest file");
|
||||
|
||||
File.WriteAllText(destination, yaml);
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
public static string Serialize(GameManifest manifest)
|
||||
{
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(new PascalCaseNamingConvention())
|
||||
.Build();
|
||||
|
||||
Logger?.LogTrace("Serializing manifest");
|
||||
|
||||
var yaml = serializer.Serialize(manifest);
|
||||
|
||||
return yaml;
|
||||
}
|
||||
|
||||
public static string GetPath(string installDirectory)
|
||||
{
|
||||
return Path.Combine(installDirectory, ManifestFilename);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,12 @@
|
|||
using Playnite.SDK;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin.Helpers
|
||||
namespace LANCommander.SDK.Helpers
|
||||
{
|
||||
internal static class RetryHelper
|
||||
{
|
||||
internal static readonly ILogger Logger = LogManager.GetLogger();
|
||||
internal static readonly ILogger Logger;
|
||||
|
||||
internal static T RetryOnException<T>(int maxAttempts, TimeSpan delay, T @default, Func<T> action)
|
||||
{
|
||||
|
@ -19,14 +16,14 @@ namespace LANCommander.PlaynitePlugin.Helpers
|
|||
{
|
||||
try
|
||||
{
|
||||
Logger.Trace($"Attempt #{attempts + 1}/{maxAttempts}...");
|
||||
Logger?.LogTrace($"Attempt #{attempts + 1}/{maxAttempts}...");
|
||||
|
||||
attempts++;
|
||||
return action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, $"Attempt failed!");
|
||||
Logger?.LogError(ex, $"Attempt failed!");
|
||||
|
||||
if (attempts >= maxAttempts)
|
||||
return @default;
|
|
@ -0,0 +1,78 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
using LANCommander.SDK.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK.Helpers
|
||||
{
|
||||
public static class ScriptHelper
|
||||
{
|
||||
public static readonly ILogger Logger;
|
||||
|
||||
public static string SaveTempScript(Script script)
|
||||
{
|
||||
var tempPath = 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");
|
||||
|
||||
tempPath = tempPath + ".ps1";
|
||||
|
||||
File.WriteAllText(tempPath, contents);
|
||||
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
public static void SaveScript(Game game, ScriptType type)
|
||||
{
|
||||
var script = game.Scripts.FirstOrDefault(s => s.Type == type);
|
||||
|
||||
if (script == null)
|
||||
return;
|
||||
|
||||
if (script.RequiresAdmin)
|
||||
script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents;
|
||||
|
||||
var filename = GetScriptFilePath(game, type);
|
||||
|
||||
if (File.Exists(filename))
|
||||
File.Delete(filename);
|
||||
|
||||
Logger?.LogTrace("Writing {ScriptType} script to {Destination}", type, filename);
|
||||
|
||||
File.WriteAllText(filename, script.Contents);
|
||||
}
|
||||
|
||||
public static string GetScriptFilePath(Game game, ScriptType type)
|
||||
{
|
||||
return GetScriptFilePath(game.InstallDirectory, type);
|
||||
}
|
||||
|
||||
public static string GetScriptFilePath(string installDirectory, ScriptType type)
|
||||
{
|
||||
Dictionary<ScriptType, string> filenames = new Dictionary<ScriptType, string>() {
|
||||
{ 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="PowerShellStandard.Library" Version="5.1.1" />
|
||||
<PackageReference Include="RestSharp" Version="106.15.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.34.2" />
|
||||
<PackageReference Include="YamlDotNet" Version="5.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -8,5 +8,6 @@ namespace LANCommander.SDK.Models
|
|||
{
|
||||
public string AccessToken { get; set; }
|
||||
public string RefreshToken { get; set; }
|
||||
public DateTime Expiration { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,14 @@ namespace LANCommander.SDK.Models
|
|||
public string DirectoryName { get; set; }
|
||||
public string Description { get; set; }
|
||||
public DateTime ReleasedOn { get; set; }
|
||||
public string InstallDirectory { get; set; }
|
||||
public virtual IEnumerable<Action> Actions { get; set; }
|
||||
public virtual IEnumerable<Tag> Tags { get; set; }
|
||||
public virtual Company Publisher { get; set; }
|
||||
public virtual Company Developer { get; set; }
|
||||
public virtual IEnumerable<Archive> Archives { get; set; }
|
||||
public virtual IEnumerable<Script> Scripts { get; set; }
|
||||
public virtual IEnumerable<Media> Media { get; set; }
|
||||
public virtual IEnumerable<Redistributable> Redistributables { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace LANCommander.SDK
|
|||
{
|
||||
public class GameManifest
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string SortTitle { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
@ -15,7 +16,6 @@ namespace LANCommander.SDK
|
|||
public IEnumerable<string> Publishers { get; set; }
|
||||
public IEnumerable<string> Developers { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string Icon { get; set; }
|
||||
public IEnumerable<GameAction> Actions { get; set; }
|
||||
public bool Singleplayer { get; set; }
|
||||
public MultiplayerInfo LocalMultiplayer { get; set; }
|
||||
|
@ -47,5 +47,13 @@ namespace LANCommander.SDK
|
|||
public Guid Id { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Path { get; set; }
|
||||
public bool IsRegex { get; set; }
|
||||
public IEnumerable<SavePathEntry> Entries { get; set; }
|
||||
}
|
||||
|
||||
public class SavePathEntry
|
||||
{
|
||||
public string ArchivePath { get; set; }
|
||||
public string ActualPath { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK.Models
|
||||
{
|
||||
public class Media : BaseModel
|
||||
{
|
||||
public Guid FileId { get; set; }
|
||||
public MediaType Type { get; set; }
|
||||
public string SourceUrl { get; set; }
|
||||
public string MimeType { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LANCommander.SDK.Models
|
||||
{
|
||||
public class Redistributable : BaseModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public DateTime ReleasedOn { get; set; }
|
||||
public virtual IEnumerable<Archive> Archives { get; set; }
|
||||
public virtual IEnumerable<Script> Scripts { get; set; }
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ namespace LANCommander.SDK.Models
|
|||
{
|
||||
public SavePathType Type { get; set; }
|
||||
public string Path { get; set; }
|
||||
public bool IsRegex { get; set; }
|
||||
public virtual Game Game { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,6 @@ namespace LANCommander.SDK.Models
|
|||
{
|
||||
public Guid Id { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string Alias { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
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<PowerShellVariable> Variables { get; set; }
|
||||
private Dictionary<string, string> Arguments { get; set; }
|
||||
private List<string> Modules { get; set; }
|
||||
private Process Process { get; set; }
|
||||
|
||||
public PowerShellScript()
|
||||
{
|
||||
Variables = new List<PowerShellVariable>();
|
||||
Arguments = new Dictionary<string, string>();
|
||||
Modules = new List<string>();
|
||||
Process = new Process();
|
||||
|
||||
Process.StartInfo.FileName = "powershell.exe";
|
||||
Process.StartInfo.RedirectStandardOutput = false;
|
||||
|
||||
AddArgument("ExecutionPolicy", "Unrestricted");
|
||||
|
||||
var moduleManifests = Directory.EnumerateFiles(Environment.CurrentDirectory, "LANCommander.PowerShell.psd1", SearchOption.AllDirectories);
|
||||
|
||||
if (moduleManifests.Any())
|
||||
AddModule(moduleManifests.First());
|
||||
|
||||
IgnoreWow64Redirection();
|
||||
}
|
||||
|
||||
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<T>(string name, T value)
|
||||
{
|
||||
Variables.Add(new PowerShellVariable(name, value, typeof(T)));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public PowerShellScript AddArgument<T>(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;
|
||||
|
||||
if (Contents.StartsWith("# Requires Admin"))
|
||||
RunAsAdmin();
|
||||
|
||||
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);
|
||||
|
||||
foreach (var argument in Arguments)
|
||||
{
|
||||
Process.StartInfo.Arguments += $" -{argument.Key} {argument.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>(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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
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;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
public class RedistributableManager
|
||||
{
|
||||
private readonly ILogger Logger;
|
||||
private Client Client { get; set; }
|
||||
|
||||
public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e);
|
||||
public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress;
|
||||
|
||||
public delegate void OnArchiveExtractionProgressHandler(long position, long length);
|
||||
public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress;
|
||||
|
||||
public RedistributableManager(Client client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
|
||||
public RedistributableManager(Client client, ILogger logger)
|
||||
{
|
||||
Client = client;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public void Install(Game game)
|
||||
{
|
||||
foreach (var redistributable in game.Redistributables)
|
||||
{
|
||||
Install(redistributable);
|
||||
}
|
||||
}
|
||||
|
||||
public void Install(Redistributable redistributable)
|
||||
{
|
||||
string installScriptTempFile = null;
|
||||
string detectionScriptTempFile = null;
|
||||
string extractTempPath = null;
|
||||
|
||||
try
|
||||
{
|
||||
var installScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.Install);
|
||||
installScriptTempFile = ScriptHelper.SaveTempScript(installScript);
|
||||
|
||||
var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall);
|
||||
detectionScriptTempFile = ScriptHelper.SaveTempScript(detectionScript);
|
||||
|
||||
var detectionResult = RunScript(detectionScriptTempFile, redistributable, detectionScript.RequiresAdmin);
|
||||
|
||||
// Redistributable is not installed
|
||||
if (detectionResult == 0)
|
||||
{
|
||||
if (redistributable.Archives.Count() > 0)
|
||||
{
|
||||
var extractionResult = DownloadAndExtract(redistributable);
|
||||
|
||||
if (extractionResult.Success)
|
||||
{
|
||||
extractTempPath = extractionResult.Directory;
|
||||
|
||||
RunScript(installScriptTempFile, redistributable, installScript.RequiresAdmin, extractTempPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RunScript(installScriptTempFile, redistributable, installScript.RequiresAdmin, extractTempPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, "Redistributable {Redistributable} failed to install", redistributable.Name);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(installScriptTempFile))
|
||||
File.Delete(installScriptTempFile);
|
||||
|
||||
if (File.Exists(detectionScriptTempFile))
|
||||
File.Delete(detectionScriptTempFile);
|
||||
|
||||
if (Directory.Exists(extractTempPath))
|
||||
Directory.Delete(extractTempPath);
|
||||
}
|
||||
}
|
||||
|
||||
private ExtractionResult DownloadAndExtract(Redistributable redistributable)
|
||||
{
|
||||
if (redistributable == null)
|
||||
{
|
||||
Logger?.LogTrace("Redistributable failed to download! No redistributable was specified");
|
||||
throw new ArgumentNullException("No redistributable was specified");
|
||||
}
|
||||
|
||||
var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename());
|
||||
|
||||
Logger?.LogTrace("Downloading and extracting {Redistributable} to path {Destination}", redistributable.Name, destination);
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(destination);
|
||||
|
||||
using (var redistributableStream = Client.StreamRedistributable(redistributable.Id))
|
||||
using (var reader = ReaderFactory.Open(redistributableStream))
|
||||
{
|
||||
redistributableStream.OnProgress += (pos, len) =>
|
||||
{
|
||||
OnArchiveExtractionProgress?.Invoke(pos, len);
|
||||
};
|
||||
|
||||
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
|
||||
{
|
||||
OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs
|
||||
{
|
||||
Entry = e.Item,
|
||||
Progress = e.ReaderProgress,
|
||||
});
|
||||
};
|
||||
|
||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, "Could not extract to path {Destination}", destination);
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger?.LogTrace("Cleaning up orphaned files after bad install");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
}
|
||||
|
||||
throw new Exception("The redistributable archive could not be extracted, is it corrupted? Please try again");
|
||||
}
|
||||
|
||||
var extractionResult = new ExtractionResult
|
||||
{
|
||||
Canceled = false
|
||||
};
|
||||
|
||||
if (!extractionResult.Canceled)
|
||||
{
|
||||
extractionResult.Success = true;
|
||||
extractionResult.Directory = destination;
|
||||
Logger?.LogTrace("Redistributable {Redistributable} successfully downloaded and extracted to {Destination}", redistributable.Name, destination);
|
||||
}
|
||||
|
||||
return extractionResult;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
internal class TrackableStream : MemoryStream, IDisposable
|
||||
public class TrackableStream : MemoryStream, IDisposable
|
||||
{
|
||||
public delegate void OnProgressDelegate(long Position, long Length);
|
||||
public event OnProgressDelegate OnProgress = delegate { };
|
|
@ -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
|
||||
|
|
|
@ -15,29 +15,20 @@ namespace LANCommander.Areas.Identity.Pages.Account
|
|||
{
|
||||
public class LogoutModel : PageModel
|
||||
{
|
||||
private readonly SignInManager<User> _signInManager;
|
||||
private readonly ILogger<LogoutModel> _logger;
|
||||
private readonly SignInManager<User> SignInManager;
|
||||
private readonly ILogger<LogoutModel> Logger;
|
||||
|
||||
public LogoutModel(SignInManager<User> signInManager, ILogger<LogoutModel> logger)
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
_logger = logger;
|
||||
SignInManager = signInManager;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost(string returnUrl = null)
|
||||
public async Task<IActionResult> OnGet()
|
||||
{
|
||||
await _signInManager.SignOutAsync();
|
||||
_logger.LogInformation("User logged out.");
|
||||
if (returnUrl != null)
|
||||
{
|
||||
return LocalRedirect(returnUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This needs to be a redirect so that the browser performs a new
|
||||
// request and the identity for the user gets updated.
|
||||
return RedirectToPage();
|
||||
}
|
||||
await SignInManager.SignOutAsync();
|
||||
|
||||
return LocalRedirect("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<Space Direction="DirectionVHType.Vertical" Style="width: 100%">
|
||||
<SpaceItem>
|
||||
<Table TItem="Archive" DataSource="@Game.Archives.OrderByDescending(a => a.CreatedOn)" HidePagination="true" Responsive>
|
||||
<Table TItem="Archive" DataSource="@Archives" HidePagination="true" Responsive>
|
||||
<PropertyColumn Property="a => a.Version" />
|
||||
<PropertyColumn Property="a => a.CompressedSize">
|
||||
@ByteSizeLib.ByteSize.FromBytes(context.CompressedSize)
|
||||
|
@ -19,11 +19,11 @@
|
|||
<PropertyColumn Property="a => a.CreatedBy">
|
||||
@context.CreatedBy?.UserName
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="a => a.CreatedOn" Format="MM/dd/yyyy hh:mm tt" />
|
||||
<PropertyColumn Property="a => a.CreatedOn" Format="MM/dd/yyyy hh:mm tt" DefaultSortOrder="@SortDirection.Descending" />
|
||||
<ActionColumn Title="">
|
||||
<Space Style="display: flex; justify-content: end">
|
||||
<SpaceItem>
|
||||
<a href="/Download/Game/@context.Id" target="_blank" class="ant-btn ant-btn-text ant-btn-icon-only">
|
||||
<a href="/Download/Archive/@context.Id" target="_blank" class="ant-btn ant-btn-text ant-btn-icon-only">
|
||||
<Icon Type="@IconType.Outline.Download" />
|
||||
</a>
|
||||
</SpaceItem>
|
||||
|
@ -46,12 +46,14 @@
|
|||
</SpaceItem>
|
||||
</Space>
|
||||
|
||||
<ArchiveUploader @ref="Uploader" OnArchiveUploaded="AddArchive" />
|
||||
<ArchiveUploader @ref="Uploader" GameId="GameId" RedistributableId="RedistributableId" OnArchiveUploaded="LoadData" />
|
||||
|
||||
@code {
|
||||
[Parameter] public Game Game { get; set; }
|
||||
[Parameter] public Guid GameId { get; set; }
|
||||
[Parameter] public Guid RedistributableId { get; set; }
|
||||
|
||||
ICollection<Archive> Archives { get; set; }
|
||||
|
||||
Archive Archive;
|
||||
ArchiveUploader Uploader;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
|
@ -63,10 +65,10 @@
|
|||
|
||||
private async Task LoadData()
|
||||
{
|
||||
Game.Archives = await ArchiveService.Get(a => a.GameId == Game.Id).OrderByDescending(a => a.CreatedOn).ToListAsync();
|
||||
|
||||
if (Game.Archives == null)
|
||||
Game.Archives = new List<Archive>();
|
||||
if (GameId != Guid.Empty)
|
||||
Archives = await ArchiveService.Get(a => a.GameId == GameId).ToListAsync();
|
||||
else if (RedistributableId != Guid.Empty)
|
||||
Archives = await ArchiveService.Get(a => a.RedistributableId == RedistributableId).ToListAsync();
|
||||
}
|
||||
|
||||
private async Task Download(Archive archive)
|
||||
|
@ -78,27 +80,7 @@
|
|||
|
||||
private async Task UploadArchive()
|
||||
{
|
||||
Archive = new Archive()
|
||||
{
|
||||
GameId = Game.Id,
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
|
||||
await Uploader.Open(Archive);
|
||||
}
|
||||
|
||||
private async Task AddArchive(Archive archive)
|
||||
{
|
||||
var lastArchive = Game.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault();
|
||||
|
||||
Archive = await ArchiveService.Add(archive);
|
||||
|
||||
await LoadData();
|
||||
|
||||
var settings = SettingService.GetSettings();
|
||||
|
||||
if (lastArchive != null && settings.Archives.EnablePatching)
|
||||
BackgroundJob.Enqueue<PatchArchiveBackgroundJob>(x => x.Execute(lastArchive.Id, Archive.Id));
|
||||
await Uploader.Open();
|
||||
}
|
||||
|
||||
private async Task Delete(Archive archive)
|
||||
|
@ -107,6 +89,8 @@
|
|||
{
|
||||
await ArchiveService.Delete(archive);
|
||||
|
||||
await LoadData();
|
||||
|
||||
await MessageService.Success("Archive deleted!");
|
||||
}
|
||||
catch (Exception ex)
|
|
@ -2,6 +2,7 @@
|
|||
@using System.Diagnostics;
|
||||
@using Hangfire;
|
||||
@using LANCommander.Jobs.Background;
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
@inject HttpClient HttpClient
|
||||
@inject NavigationManager Navigator
|
||||
@inject ArchiveService ArchiveService
|
||||
|
@ -62,7 +63,9 @@
|
|||
</Modal>
|
||||
|
||||
@code {
|
||||
[Parameter] public EventCallback<Archive> OnArchiveUploaded { get; set; }
|
||||
[Parameter] public Guid GameId { get; set; }
|
||||
[Parameter] public Guid RedistributableId { get; set; }
|
||||
[Parameter] public EventCallback<Guid> OnArchiveUploaded { get; set; }
|
||||
|
||||
Archive Archive;
|
||||
|
||||
|
@ -113,9 +116,21 @@
|
|||
File = args.File;
|
||||
}
|
||||
|
||||
public async Task Open(Archive archive)
|
||||
public async Task Open(Guid? archiveId = null)
|
||||
{
|
||||
Archive = archive;
|
||||
if (archiveId.HasValue && archiveId != Guid.Empty)
|
||||
{
|
||||
Archive = await ArchiveService.Get(archiveId.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Archive = new Archive();
|
||||
|
||||
if (GameId != Guid.Empty)
|
||||
Archive.GameId = GameId;
|
||||
else if (RedistributableId != Guid.Empty)
|
||||
Archive.RedistributableId = RedistributableId;
|
||||
}
|
||||
|
||||
Visible = true;
|
||||
|
||||
|
@ -128,8 +143,8 @@
|
|||
{
|
||||
if (FileInput != null)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(archive.ObjectKey) && archive.ObjectKey != Guid.Empty.ToString())
|
||||
await JS.InvokeVoidAsync("Uploader.Init", "FileInput", archive.ObjectKey.ToString());
|
||||
if (!String.IsNullOrWhiteSpace(Archive.ObjectKey) && Archive.ObjectKey != Guid.Empty.ToString())
|
||||
await JS.InvokeVoidAsync("Uploader.Init", "FileInput", Archive.ObjectKey.ToString());
|
||||
else
|
||||
await JS.InvokeVoidAsync("Uploader.Init", "FileInput", "");
|
||||
|
||||
|
@ -163,12 +178,32 @@
|
|||
Archive.ObjectKey = objectKey.ToString();
|
||||
Archive.CompressedSize = File.Size;
|
||||
|
||||
if (Archive.Id != Guid.Empty)
|
||||
Archive = await ArchiveService.Update(Archive);
|
||||
else
|
||||
Archive = await ArchiveService.Add(Archive);
|
||||
|
||||
Visible = false;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
Archive? lastArchive = null;
|
||||
|
||||
var settings = SettingService.GetSettings();
|
||||
|
||||
if (settings.Archives.EnablePatching)
|
||||
{
|
||||
if (Archive.GameId != Guid.Empty)
|
||||
lastArchive = await ArchiveService.Get(a => a.Id != Archive.Id && a.GameId == Archive.GameId).OrderByDescending(a => a.CreatedOn).FirstOrDefaultAsync();
|
||||
else if (Archive.RedistributableId != Guid.Empty)
|
||||
lastArchive = await ArchiveService.Get(a => a.Id != Archive.Id && a.RedistributableId == Archive.RedistributableId).OrderByDescending(a => a.CreatedOn).FirstOrDefaultAsync();
|
||||
|
||||
if (lastArchive != null && settings.Archives.EnablePatching)
|
||||
BackgroundJob.Enqueue<PatchArchiveBackgroundJob>(x => x.Execute(lastArchive.Id, Archive.Id));
|
||||
}
|
||||
|
||||
if (OnArchiveUploaded.HasDelegate)
|
||||
await OnArchiveUploaded.InvokeAsync(Archive);
|
||||
await OnArchiveUploaded.InvokeAsync(Archive.Id);
|
||||
|
||||
await MessageService.Success("Archive uploaded!");
|
||||
}
|
|
@ -238,6 +238,8 @@
|
|||
}
|
||||
|
||||
async Task<HashSet<FileManagerDirectory>> GetArchiveDirectoriesAsync(Guid archiveId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var entries = await ArchiveService.GetContents(archiveId);
|
||||
var directories = new HashSet<FileManagerDirectory>();
|
||||
|
@ -258,6 +260,17 @@
|
|||
root
|
||||
};
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
MessageService.Error("Could not open archive! Is it missing?");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageService.Error("An unknown error occurred trying to open the archive");
|
||||
}
|
||||
|
||||
return new HashSet<FileManagerDirectory>();
|
||||
}
|
||||
|
||||
string GetEntryName(IFileManagerEntry entry)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<div class="image-picker">
|
||||
<div class="image-picker-images">
|
||||
@foreach (var image in Images)
|
||||
{
|
||||
<div class="image-picker-image" style="width: @(Size)px; max-height: @(Size)px">
|
||||
<input type="radio" id="image-picker-image-@image.Key" checked="@(Value == image.Key)" name="SelectedResult" @onchange="@(() => SelectionChanged(image.Key))" />
|
||||
<label for="image-picker-image-@image.Key"></label>
|
||||
<img src="@image.Value" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public double Size { get; set; }
|
||||
[Parameter] public string Value { get; set; }
|
||||
[Parameter] public EventCallback<string> ValueChanged { get; set; }
|
||||
[Parameter] public Dictionary<string, string> Images { get; set; }
|
||||
|
||||
async Task SelectionChanged(string key)
|
||||
{
|
||||
Value = key;
|
||||
|
||||
if (ValueChanged.HasDelegate)
|
||||
await ValueChanged.InvokeAsync(key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
@inherits FeedbackComponent<MediaGrabberOptions, MediaGrabberResult>
|
||||
@using LANCommander.Data.Enums;
|
||||
@using LANCommander.Models;
|
||||
@inject IMediaGrabberService MediaGrabberService
|
||||
|
||||
<GridRow Justify="space-between">
|
||||
<GridCol Span="6">
|
||||
<Search @bind-Value="Search" OnSearch="(x) => GetResults(Type, x)" DefaultValue="@Search" />
|
||||
</GridCol>
|
||||
<GridCol Span="12"></GridCol>
|
||||
<GridCol Span="6">
|
||||
<Slider TValue="double" @bind-Value="Size" DefaultValue="200" Min="50" Max="400" />
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
|
||||
@foreach (var group in Results)
|
||||
{
|
||||
<div class="media-grabber-group">
|
||||
<h2>@group.First().Group</h2>
|
||||
|
||||
<ImagePicker Size="Size" Images="@group.ToDictionary(r => r.Id, r => r.ThumbnailUrl)" ValueChanged="OnImageSelected" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public string Search { get; set; }
|
||||
[Parameter] public MediaType Type { get; set; }
|
||||
|
||||
MediaGrabberResult Media { get; set; }
|
||||
|
||||
double Size { get; set; } = 200;
|
||||
|
||||
IEnumerable<IEnumerable<MediaGrabberResult>> Results = new List<List<MediaGrabberResult>>();
|
||||
Dictionary<string, string> Images { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
protected override async Task OnFirstAfterRenderAsync()
|
||||
{
|
||||
Type = Options.Type;
|
||||
Search = Options.Search;
|
||||
|
||||
await GetResults(Type, Search);
|
||||
}
|
||||
|
||||
private async Task GetResults(MediaType type, string search)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
Results = (await MediaGrabberService.SearchAsync(type, search)).GroupBy(r => r.Group);
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnImageSelected(string key)
|
||||
{
|
||||
Media = Results.SelectMany(g => g).FirstOrDefault(r => r.Id == key);
|
||||
}
|
||||
|
||||
public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args)
|
||||
{
|
||||
await base.OkCancelRefWithResult!.OnOk(Media);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
@inject IMessageService MessageService
|
||||
|
||||
<Modal Visible="Visible" OkText="@("Insert")" OnOk="Parse" OnCancel="Close" Width="800" Title="Paste Export File Contents">
|
||||
<StandaloneCodeEditor @ref="Editor" ConstructionOptions="EditorConstructionOptions" />
|
||||
</Modal>
|
||||
|
||||
@code {
|
||||
[Parameter] public EventCallback<string> OnParsed { get; set; }
|
||||
|
||||
bool Visible = false;
|
||||
string Contents = "";
|
||||
|
||||
StandaloneCodeEditor? Editor;
|
||||
|
||||
private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
|
||||
{
|
||||
return new StandaloneEditorConstructionOptions
|
||||
{
|
||||
AutomaticLayout = true,
|
||||
Language = "ini",
|
||||
Value = Contents,
|
||||
Theme = "vs-dark",
|
||||
};
|
||||
}
|
||||
|
||||
private async Task Parse()
|
||||
{
|
||||
Contents = await Editor.GetValue();
|
||||
|
||||
var parser = new RegParserDotNet.RegParser();
|
||||
var lines = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
var keys = parser.Parse(Contents);
|
||||
|
||||
foreach (var key in keys)
|
||||
{
|
||||
switch (key.Type)
|
||||
{
|
||||
case RegParserDotNet.RegistryValueType.REG_KEY:
|
||||
if (lines.Count > 0)
|
||||
lines.Add("");
|
||||
|
||||
lines.Add($"New-Item -Path \"registry::\\{key.Path}\"");
|
||||
break;
|
||||
|
||||
case RegParserDotNet.RegistryValueType.REG_SZ:
|
||||
lines.Add($"New-ItemProperty -Path \"registry::\\{key.Path}\" -Name \"{key.Property}\" -Value \"{(string)key.Value}\" -Force");
|
||||
break;
|
||||
|
||||
case RegParserDotNet.RegistryValueType.REG_DWORD:
|
||||
lines.Add($"New-ItemProperty -Path \"registry::\\{key.Path}\" -Name \"{key.Property}\" -Value {(int)key.Value} -Force");
|
||||
break;
|
||||
|
||||
case RegParserDotNet.RegistryValueType.REG_BINARY:
|
||||
var bytes = key.Value as byte[];
|
||||
var convertedBytes = String.Join("\\\n", bytes.Chunk(32).Select(c => String.Join(", ", c.Select(b => "0x" + b.ToString("X2")))));
|
||||
lines.Add($"New-ItemProperty -Path \"registry::\\{key.Path}\" -Name \"{key.Property}\" -PropertyType Binary -Value [byte[]]({convertedBytes}) -Force");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (OnParsed.HasDelegate)
|
||||
await OnParsed.InvokeAsync(String.Join('\n', lines));
|
||||
|
||||
Close();
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageService.Error(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Open()
|
||||
{
|
||||
Visible = true;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Visible = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
@using LANCommander.Components.FileManagerComponents;
|
||||
@using LANCommander.Data.Enums;
|
||||
@using LANCommander.Extensions;
|
||||
@using LANCommander.Models
|
||||
@using LANCommander.Services
|
||||
@using System.IO.Compression;
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
@inject ScriptService ScriptService
|
||||
@inject ModalService ModalService
|
||||
@inject IMessageService MessageService
|
||||
|
||||
|
||||
|
||||
<Space Direction="DirectionVHType.Vertical" Size="@("large")" Style="width: 100%">
|
||||
<SpaceItem>
|
||||
<Table TItem="Script" DataSource="@Scripts" HidePagination="true" Responsive>
|
||||
<PropertyColumn Property="s => s.Type">@context.Type.GetDisplayName()</PropertyColumn>
|
||||
<PropertyColumn Property="s => s.CreatedBy">
|
||||
@context.CreatedBy?.UserName
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="s => s.CreatedOn" Format="MM/dd/yyyy hh:mm tt" />
|
||||
<ActionColumn Title="">
|
||||
<Space Style="display: flex; justify-content: end">
|
||||
<SpaceItem>
|
||||
<Button OnClick="() => Edit(context.Id)" Icon="@IconType.Outline.Edit" Type="@ButtonType.Text" />
|
||||
</SpaceItem>
|
||||
<SpaceItem>
|
||||
<Popconfirm OnConfirm="() => Delete(context)" Title="Are you sure you want to delete this script?">
|
||||
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
|
||||
</Popconfirm>
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
</ActionColumn>
|
||||
</Table>
|
||||
</SpaceItem>
|
||||
|
||||
<SpaceItem>
|
||||
<GridRow Justify="end">
|
||||
<GridCol>
|
||||
<Button OnClick="() => Edit()" Type="@ButtonType.Primary">Add Script</Button>
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
|
||||
<style>
|
||||
.monaco-editor-container {
|
||||
height: 600px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
[Parameter] public Guid GameId { get; set; }
|
||||
[Parameter] public Guid RedistributableId { get; set; }
|
||||
[Parameter] public Guid ArchiveId { get; set; }
|
||||
[Parameter] public IEnumerable<ScriptType> AllowedTypes { get; set; }
|
||||
|
||||
ICollection<Script> Scripts { get; set; } = new List<Script>();
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
if (GameId != Guid.Empty)
|
||||
Scripts = await ScriptService.Get(s => s.GameId == GameId).ToListAsync();
|
||||
else if (RedistributableId != Guid.Empty)
|
||||
Scripts = await ScriptService.Get(s => s.RedistributableId == RedistributableId).ToListAsync();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async void Edit(Guid? scriptId = null)
|
||||
{
|
||||
var modalOptions = new ModalOptions()
|
||||
{
|
||||
Title = scriptId == null ? "Add Script" : "Edit Script",
|
||||
Maximizable = false,
|
||||
DefaultMaximized = true,
|
||||
Closable = true,
|
||||
OkText = "Save"
|
||||
};
|
||||
|
||||
var options = new ScriptEditorOptions()
|
||||
{
|
||||
ScriptId = scriptId ?? default,
|
||||
AllowedTypes = AllowedTypes,
|
||||
ArchiveId = ArchiveId,
|
||||
GameId = GameId,
|
||||
RedistributableId = RedistributableId
|
||||
};
|
||||
|
||||
var modalRef = await ModalService.CreateModalAsync<ScriptEditorDialog, ScriptEditorOptions, Script>(modalOptions, options);
|
||||
|
||||
modalRef.OnOk = async (script) =>
|
||||
{
|
||||
await LoadData();
|
||||
};
|
||||
}
|
||||
|
||||
private async void Delete(Script script = null)
|
||||
{
|
||||
if (script != null)
|
||||
await ScriptService.Delete(script);
|
||||
|
||||
await MessageService.Success("Script deleted!");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
@using LANCommander.Components.FileManagerComponents;
|
||||
@using LANCommander.Extensions;
|
||||
@using LANCommander.Data.Enums;
|
||||
@using LANCommander.Models;
|
||||
@inherits FeedbackComponent<ScriptEditorOptions, Script>
|
||||
@inject ScriptService ScriptService
|
||||
@inject ModalService ModalService
|
||||
@inject IMessageService MessageService
|
||||
|
||||
<Form Model="@Script" Layout="@FormLayout.Vertical">
|
||||
<FormItem>
|
||||
@foreach (var group in Snippets.Select(s => s.Group).Distinct())
|
||||
{
|
||||
<Dropdown>
|
||||
<Overlay>
|
||||
<Menu>
|
||||
@foreach (var snippet in Snippets.Where(s => s.Group == group))
|
||||
{
|
||||
<MenuItem OnClick="() => InsertSnippet(snippet)">
|
||||
@snippet.Name
|
||||
</MenuItem>
|
||||
}
|
||||
</Menu>
|
||||
</Overlay>
|
||||
|
||||
<ChildContent>
|
||||
<Button Type="@ButtonType.Primary">@group</Button>
|
||||
</ChildContent>
|
||||
</Dropdown>
|
||||
}
|
||||
|
||||
@if (Options.ArchiveId != Guid.Empty)
|
||||
{
|
||||
<Button Icon="@IconType.Outline.FolderOpen" OnClick="BrowseForPath" Type="@ButtonType.Text">Browse</Button>
|
||||
}
|
||||
|
||||
<Button Icon="@IconType.Outline.Build" OnClick="() => RegToPowerShell.Open()" Type="@ButtonType.Text">Import .reg</Button>
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<StandaloneCodeEditor @ref="Editor" Id="@("editor-" + Id.ToString())" ConstructionOptions="EditorConstructionOptions" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem Label="Name">
|
||||
<Input @bind-Value="@context.Name" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem Label="Type">
|
||||
<Select @bind-Value="context.Type" TItem="ScriptType" TItemValue="ScriptType" DataSource="Enum.GetValues<ScriptType>().Where(st => Options.AllowedTypes == null || Options.AllowedTypes.Contains(st))">
|
||||
<LabelTemplate Context="Value">@Value.GetDisplayName()</LabelTemplate>
|
||||
<ItemTemplate Context="Value">@Value.GetDisplayName()</ItemTemplate>
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Checkbox @bind-Checked="context.RequiresAdmin">Requires Admin</Checkbox>
|
||||
</FormItem>
|
||||
|
||||
<FormItem Label="Description">
|
||||
<TextArea @bind-Value="context.Description" MaxLength=500 ShowCount />
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<RegToPowerShell @ref="RegToPowerShell" OnParsed="(text) => InsertText(text)" />
|
||||
|
||||
@code {
|
||||
Guid Id = Guid.NewGuid();
|
||||
Script Script;
|
||||
StandaloneCodeEditor? Editor;
|
||||
RegToPowerShell RegToPowerShell;
|
||||
IEnumerable<Snippet> Snippets { get; set; }
|
||||
|
||||
private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
|
||||
{
|
||||
return new StandaloneEditorConstructionOptions
|
||||
{
|
||||
AutomaticLayout = true,
|
||||
Language = "powershell",
|
||||
Value = Script.Contents,
|
||||
Theme = "vs-dark",
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Options.ScriptId != Guid.Empty)
|
||||
Script = await ScriptService.Get(Options.ScriptId);
|
||||
else if (Options.GameId != Guid.Empty)
|
||||
Script = new Script()
|
||||
{
|
||||
GameId = Options.GameId
|
||||
};
|
||||
else if (Options.RedistributableId != Guid.Empty)
|
||||
Script = new Script()
|
||||
{
|
||||
RedistributableId = Options.RedistributableId
|
||||
};
|
||||
|
||||
Snippets = ScriptService.GetSnippets();
|
||||
}
|
||||
|
||||
public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args)
|
||||
{
|
||||
await Save();
|
||||
|
||||
Editor.Dispose();
|
||||
|
||||
await base.OkCancelRefWithResult!.OnOk(Script);
|
||||
}
|
||||
|
||||
public override async Task CancelAsync(ModalClosingEventArgs args)
|
||||
{
|
||||
Editor.Dispose();
|
||||
|
||||
await base.CancelAsync(args);
|
||||
}
|
||||
|
||||
private async void BrowseForPath()
|
||||
{
|
||||
|
||||
var modalOptions = new ModalOptions()
|
||||
{
|
||||
Title = "Choose Reference",
|
||||
Maximizable = false,
|
||||
DefaultMaximized = true,
|
||||
Closable = true,
|
||||
OkText = "Insert File Path"
|
||||
};
|
||||
|
||||
var browserOptions = new FilePickerOptions()
|
||||
{
|
||||
ArchiveId = Options.ArchiveId,
|
||||
Select = true,
|
||||
Multiple = false
|
||||
};
|
||||
|
||||
var modalRef = await ModalService.CreateModalAsync<FilePickerDialog, FilePickerOptions, IEnumerable<IFileManagerEntry>>(modalOptions, browserOptions);
|
||||
|
||||
modalRef.OnOk = (results) =>
|
||||
{
|
||||
var path = results.FirstOrDefault().Path;
|
||||
|
||||
InsertText($"$InstallDir\\{path.Replace('/', '\\')}");
|
||||
|
||||
StateHasChanged();
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
}
|
||||
|
||||
private async Task InsertText(string text)
|
||||
{
|
||||
var line = await Editor.GetPosition();
|
||||
var range = new BlazorMonaco.Range(line.LineNumber, 1, line.LineNumber, 1);
|
||||
|
||||
var currentSelections = await Editor.GetSelections();
|
||||
|
||||
await Editor.ExecuteEdits("ScriptEditor", new List<IdentifiedSingleEditOperation>()
|
||||
{
|
||||
new IdentifiedSingleEditOperation
|
||||
{
|
||||
Range = range,
|
||||
Text = text,
|
||||
ForceMoveMarkers = true
|
||||
}
|
||||
}, currentSelections);
|
||||
}
|
||||
|
||||
private async Task InsertSnippet(Snippet snippet)
|
||||
{
|
||||
await InsertText(snippet.Content);
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = await Editor.GetValue();
|
||||
|
||||
Script.Contents = value;
|
||||
|
||||
if (Script.Id == Guid.Empty)
|
||||
Script = await ScriptService.Add(Script);
|
||||
else
|
||||
Script = await ScriptService.Update(Script);
|
||||
|
||||
MessageService.Success("Script saved!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageService.Error("Script could not be saved!");
|
||||
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<Select Mode="tags" TItem="Guid" TItemValue="Guid" @bind-Values="@SelectedValues" OnSelectedItemsChanged="OnSelectedItemsChanged" EnableSearch>
|
||||
<SelectOptions>
|
||||
@foreach (var entity in Entities)
|
||||
@foreach (var entity in Entities.OrderBy(OptionLabelSelector))
|
||||
{
|
||||
<SelectOption TItemValue="Guid" TItem="Guid" Value="@entity.Id" Label="@OptionLabelSelector.Invoke(entity)" />
|
||||
}
|
||||
|
@ -45,7 +45,7 @@
|
|||
}
|
||||
|
||||
if (ValuesChanged.HasDelegate)
|
||||
await ValuesChanged.InvokeAsync();
|
||||
await ValuesChanged.InvokeAsync(Values);
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
@typeparam TItem where TItem : BaseModel
|
||||
|
||||
<Transfer DataSource="TransferItems" TargetKeys="TargetKeys" OnChange="OnChange" Titles="new string[] { LeftTitle, RightTitle }" />
|
||||
|
||||
@code {
|
||||
[Parameter] public string LeftTitle { get; set; } = "";
|
||||
[Parameter] public string RightTitle { get; set; } = "";
|
||||
[Parameter] public Func<TItem, string> TitleSelector { get; set; }
|
||||
[Parameter] public IEnumerable<TItem> DataSource { get; set; }
|
||||
[Parameter] public ICollection<TItem> Values { get; set; } = new List<TItem>();
|
||||
[Parameter] public EventCallback<ICollection<TItem>> ValuesChanged { get; set; }
|
||||
|
||||
IEnumerable<TransferItem> TransferItems { get; set; } = new List<TransferItem>();
|
||||
List<string> TargetKeys { get; set; } = new List<string>();
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
TransferItems = DataSource.Select(i => new TransferItem()
|
||||
{
|
||||
Key = i.Id.ToString(),
|
||||
Title = TitleSelector.Invoke(i)
|
||||
});
|
||||
|
||||
if (Values != null)
|
||||
TargetKeys = Values.Select(i => i.Id.ToString()).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
async Task OnChange(TransferChangeArgs e)
|
||||
{
|
||||
Values = DataSource.Where(i => e.TargetKeys.Contains(i.Id.ToString())).ToList();
|
||||
|
||||
if (ValuesChanged.HasDelegate)
|
||||
await ValuesChanged.InvokeAsync(Values);
|
||||
}
|
||||
}
|
|
@ -42,8 +42,6 @@ namespace LANCommander.Controllers.Api
|
|||
{
|
||||
var manifest = await GameService.GetManifest(id);
|
||||
|
||||
manifest.Icon = Url.Action(nameof(GetIcon), new { id = id });
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
|
@ -67,21 +65,5 @@ namespace LANCommander.Controllers.Api
|
|||
|
||||
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", $"{game.Title.SanitizeFilename()}.zip");
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("{id}/Icon.png")]
|
||||
public async Task<IActionResult> GetIcon(Guid id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var game = await GameService.Get(id);
|
||||
|
||||
return File(GameService.GetIcon(game), "image/png");
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
using LANCommander.Data;
|
||||
using LANCommander.Data.Models;
|
||||
using LANCommander.Extensions;
|
||||
using LANCommander.Models;
|
||||
using LANCommander.SDK;
|
||||
using LANCommander.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LANCommander.Controllers.Api
|
||||
{
|
||||
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class MediaController : ControllerBase
|
||||
{
|
||||
private readonly MediaService MediaService;
|
||||
private readonly LANCommanderSettings Settings = SettingService.GetSettings();
|
||||
|
||||
public MediaController(MediaService mediaService)
|
||||
{
|
||||
|
||||
MediaService = mediaService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<Media>> Get()
|
||||
{
|
||||
return await MediaService.Get();
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<Media> Get(Guid id)
|
||||
{
|
||||
return await MediaService.Get(id);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("{id}/Download")]
|
||||
public async Task<IActionResult> Download(Guid id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var media = await MediaService.Get(id);
|
||||
|
||||
var fs = System.IO.File.OpenRead(MediaService.GetImagePath(media));
|
||||
|
||||
return File(fs, media.MimeType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
using LANCommander.Data.Models;
|
||||
using LANCommander.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LANCommander.Controllers.Api
|
||||
{
|
||||
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class PlaySessionsController : ControllerBase
|
||||
{
|
||||
private readonly PlaySessionService PlaySessionService;
|
||||
private readonly GameService GameService;
|
||||
private readonly UserManager<User> UserManager;
|
||||
|
||||
public PlaySessionsController(PlaySessionService playSessionService, GameService gameService, UserManager<User> userManager)
|
||||
{
|
||||
PlaySessionService = playSessionService;
|
||||
GameService = gameService;
|
||||
UserManager = userManager;
|
||||
}
|
||||
|
||||
[HttpPost("Start/{id}")]
|
||||
public async Task<IActionResult> Start(Guid id)
|
||||
{
|
||||
var user = await UserManager.FindByNameAsync(User.Identity.Name);
|
||||
var game = await GameService.Get(id);
|
||||
|
||||
if (game == null || user == null)
|
||||
return BadRequest();
|
||||
|
||||
await PlaySessionService.StartSession(game.Id, user.Id);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost("End/{id}")]
|
||||
public async Task<IActionResult> End(Guid id)
|
||||
{
|
||||
var user = await UserManager.FindByNameAsync(User.Identity.Name);
|
||||
var game = await GameService.Get(id);
|
||||
|
||||
if (game == null || user == null)
|
||||
return BadRequest();
|
||||
|
||||
await PlaySessionService.EndSession(game.Id, user.Id);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
using LANCommander.Data.Models;
|
||||
using LANCommander.Models;
|
||||
using LANCommander.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using NLog;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.Controllers.Api
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||
[ApiController]
|
||||
public class ProfileController : ControllerBase
|
||||
{
|
||||
protected readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly UserManager<User> UserManager;
|
||||
|
||||
public ProfileController(UserManager<User> userManager)
|
||||
{
|
||||
UserManager = userManager;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Get()
|
||||
{
|
||||
if (User != null && User.Identity != null && User.Identity.IsAuthenticated)
|
||||
{
|
||||
var user = await UserManager.FindByNameAsync(User.Identity.Name);
|
||||
|
||||
return Ok(user);
|
||||
}
|
||||
else
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> ChangeAlias(string alias)
|
||||
{
|
||||
if (User != null && User.Identity != null && User.Identity.IsAuthenticated)
|
||||
{
|
||||
var user = await UserManager.FindByNameAsync(User.Identity.Name);
|
||||
|
||||
user.Alias = alias;
|
||||
|
||||
await UserManager.UpdateAsync(user);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
else
|
||||
return Unauthorized();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using LANCommander.Data.Models;
|
||||
using LANCommander.Extensions;
|
||||
using LANCommander.Models;
|
||||
using LANCommander.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LANCommander.Controllers.Api
|
||||
{
|
||||
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class RedistributableController : ControllerBase
|
||||
{
|
||||
private readonly RedistributableService RedistributableService;
|
||||
private readonly LANCommanderSettings Settings = SettingService.GetSettings();
|
||||
|
||||
public RedistributableController(RedistributableService redistributableService)
|
||||
{
|
||||
|
||||
RedistributableService = redistributableService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<Redistributable>> Get()
|
||||
{
|
||||
return await RedistributableService.Get();
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<Redistributable> Get(Guid id)
|
||||
{
|
||||
return await RedistributableService.Get(id);
|
||||
}
|
||||
|
||||
[HttpGet("{id}/Download")]
|
||||
public async Task<IActionResult> Download(Guid id)
|
||||
{
|
||||
var redistributable = await RedistributableService.Get(id);
|
||||
|
||||
if (redistributable == null)
|
||||
return NotFound();
|
||||
|
||||
if (redistributable.Archives == null || redistributable.Archives.Count == 0)
|
||||
return NotFound();
|
||||
|
||||
var archive = redistributable.Archives.OrderByDescending(a => a.CreatedOn).First();
|
||||
|
||||
var filename = Path.Combine(Settings.Archives.StoragePath, archive.ObjectKey);
|
||||
|
||||
if (!System.IO.File.Exists(filename))
|
||||
return NotFound();
|
||||
|
||||
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", $"{redistributable.Name.SanitizeFilename()}.zip");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,5 +48,11 @@ namespace LANCommander.Controllers.Api
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("Media")]
|
||||
public async Task Media(IFormFile file)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace LANCommander.Controllers
|
|||
ArchiveService = archiveService;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Game(Guid id)
|
||||
public async Task<IActionResult> Archive(Guid id)
|
||||
{
|
||||
var archive = await ArchiveService.Get(id);
|
||||
|
||||
|
@ -31,7 +31,14 @@ namespace LANCommander.Controllers
|
|||
if (!System.IO.File.Exists(filename))
|
||||
return NotFound();
|
||||
|
||||
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", $"{archive.Game.Title.SanitizeFilename()}.zip");
|
||||
string name = "";
|
||||
|
||||
if (archive.GameId != null && archive.GameId != Guid.Empty)
|
||||
name = $"{archive.Game.Title.SanitizeFilename()}.zip";
|
||||
else if (archive.RedistributableId != null && archive.RedistributableId != Guid.Empty)
|
||||
name = $"{archive.Redistributable.Name.SanitizeFilename()}.zip";
|
||||
|
||||
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
using LANCommander.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LANCommander.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
public class SavesController : Controller
|
||||
{
|
||||
private readonly GameSaveService GameSaveService;
|
||||
|
||||
public SavesController(GameSaveService gameSaveService)
|
||||
{
|
||||
GameSaveService = gameSaveService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Download(Guid id)
|
||||
{
|
||||
var save = await GameSaveService.Get(id);
|
||||
|
||||
if (User == null || User.Identity?.Name != save.User?.UserName)
|
||||
return Unauthorized();
|
||||
|
||||
if (save == null)
|
||||
return NotFound();
|
||||
|
||||
var filename = GameSaveService.GetSavePath(save);
|
||||
|
||||
if (!System.IO.File.Exists(filename))
|
||||
return NotFound();
|
||||
|
||||
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/zip", $"{save.User?.UserName} - {(save.Game == null ? "Unknown" : save.Game?.Title)} - {save.CreatedOn.ToString("MM-dd-yyyy.hh-mm")}.zip");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,14 +20,44 @@ namespace LANCommander.Controllers
|
|||
if (server == null)
|
||||
return NotFound();
|
||||
|
||||
path = path.Replace('/', Path.DirectorySeparatorChar);
|
||||
|
||||
var filename = Path.Combine(server.HTTPRootPath, path);
|
||||
|
||||
if (!System.IO.File.Exists(filename))
|
||||
if (server.HttpPaths == null || server.HttpPaths.Count == 0)
|
||||
return NotFound();
|
||||
|
||||
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", Path.GetFileName(filename));
|
||||
// Sanitize
|
||||
if (path == null)
|
||||
path = "/";
|
||||
|
||||
path = path.Trim('/');
|
||||
path = path + "/";
|
||||
|
||||
var httpPath = server.HttpPaths.FirstOrDefault(hp => path.StartsWith(hp.Path.TrimStart('/')));
|
||||
|
||||
// Check to see if there's a root path defined if nothing else matches
|
||||
if (httpPath == null)
|
||||
httpPath = server.HttpPaths.FirstOrDefault(hp => hp.Path == "/");
|
||||
|
||||
if (httpPath == null)
|
||||
return Forbid();
|
||||
|
||||
var relativePath = path.Substring(httpPath.Path.TrimStart('/').Length).Replace('/', Path.DirectorySeparatorChar).TrimStart('\\');
|
||||
|
||||
var localPath = Path.Combine(httpPath.LocalPath, relativePath).TrimEnd('\\');
|
||||
var attrs = System.IO.File.GetAttributes(localPath);
|
||||
|
||||
if ((attrs & FileAttributes.Directory) == FileAttributes.Directory)
|
||||
{
|
||||
if (!System.IO.Directory.Exists(localPath))
|
||||
return NotFound();
|
||||
|
||||
return Json(Directory.GetFileSystemEntries(localPath).Select(fse => fse.Substring(localPath.Length)));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!System.IO.File.Exists(localPath))
|
||||
return NotFound();
|
||||
|
||||
return File(new FileStream(localPath, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", Path.GetFileName(localPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,26 @@ namespace LANCommander.Data
|
|||
{
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
builder.ConfigureBaseRelationships<Data.Models.Action>();
|
||||
builder.ConfigureBaseRelationships<Archive>();
|
||||
builder.ConfigureBaseRelationships<Category>();
|
||||
builder.ConfigureBaseRelationships<Collection>();
|
||||
builder.ConfigureBaseRelationships<Company>();
|
||||
builder.ConfigureBaseRelationships<Game>();
|
||||
builder.ConfigureBaseRelationships<GameSave>();
|
||||
builder.ConfigureBaseRelationships<Genre>();
|
||||
builder.ConfigureBaseRelationships<Key>();
|
||||
builder.ConfigureBaseRelationships<Media>();
|
||||
builder.ConfigureBaseRelationships<MultiplayerMode>();
|
||||
builder.ConfigureBaseRelationships<PlaySession>();
|
||||
builder.ConfigureBaseRelationships<Redistributable>();
|
||||
builder.ConfigureBaseRelationships<SavePath>();
|
||||
builder.ConfigureBaseRelationships<Script>();
|
||||
builder.ConfigureBaseRelationships<Server>();
|
||||
builder.ConfigureBaseRelationships<ServerConsole>();
|
||||
builder.ConfigureBaseRelationships<ServerHttpPath>();
|
||||
builder.ConfigureBaseRelationships<Tag>();
|
||||
|
||||
builder.Entity<Genre>()
|
||||
.HasMany(g => g.Games)
|
||||
.WithMany(g => g.Genres);
|
||||
|
@ -34,16 +54,17 @@ namespace LANCommander.Data
|
|||
.HasMany(t => t.Games)
|
||||
.WithMany(g => g.Tags);
|
||||
|
||||
#region Game Relationships
|
||||
builder.Entity<Game>()
|
||||
.HasMany(g => g.Archives)
|
||||
.WithOne(g => g.Game)
|
||||
.IsRequired(true)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Game>()
|
||||
.HasMany(g => g.Scripts)
|
||||
.WithOne(s => s.Game)
|
||||
.IsRequired(true)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Game>()
|
||||
|
@ -63,6 +84,28 @@ namespace LANCommander.Data
|
|||
.IsRequired(true)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Game>()
|
||||
.HasMany(g => g.Media)
|
||||
.WithOne(m => m.Game)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Game>()
|
||||
.HasMany(g => g.SavePaths)
|
||||
.WithOne(p => p.Game)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Game>()
|
||||
.HasMany(g => g.PlaySessions)
|
||||
.WithOne(ps => ps.Game)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
builder.Entity<Game>()
|
||||
.HasMany(g => g.GameSaves)
|
||||
.WithOne(gs => gs.Game)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
builder.Entity<Game>()
|
||||
.HasMany(g => g.Developers)
|
||||
.WithMany(c => c.DevelopedGames)
|
||||
|
@ -81,29 +124,85 @@ namespace LANCommander.Data
|
|||
g => g.HasOne<Game>().WithMany().HasForeignKey("GameId")
|
||||
);
|
||||
|
||||
builder.Entity<Game>()
|
||||
.HasMany(g => g.Redistributables)
|
||||
.WithMany(r => r.Games)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"GameRedistributable",
|
||||
gr => gr.HasOne<Redistributable>().WithMany().HasForeignKey("RedistributableId"),
|
||||
gr => gr.HasOne<Game>().WithMany().HasForeignKey("GameId")
|
||||
);
|
||||
#endregion
|
||||
|
||||
#region User Relationships
|
||||
builder.Entity<User>()
|
||||
.HasMany(u => u.GameSaves)
|
||||
.WithOne(gs => gs.User)
|
||||
.IsRequired(true)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Game>()
|
||||
.HasMany(g => g.GameSaves)
|
||||
.WithOne(gs => gs.Game)
|
||||
builder.Entity<User>()
|
||||
.HasMany(u => u.PlaySessions)
|
||||
.WithOne(ps => ps.User)
|
||||
.IsRequired(true)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
#endregion
|
||||
|
||||
#region Server Relationships
|
||||
builder.Entity<Server>()
|
||||
.HasOne(s => s.Game)
|
||||
.WithMany(g => g.Servers)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
builder.Entity<Server>()
|
||||
.HasMany<ServerConsole>()
|
||||
.WithOne(sl => sl.Server)
|
||||
.IsRequired(true)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Server>()
|
||||
.HasMany<ServerHttpPath>()
|
||||
.WithOne(s => s.Server)
|
||||
.IsRequired(true)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
#endregion
|
||||
|
||||
#region Redistributable Relationships
|
||||
builder.Entity<Redistributable>()
|
||||
.HasMany(r => r.Archives)
|
||||
.WithOne(a => a.Redistributable)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Redistributable>()
|
||||
.HasMany(r => r.Scripts)
|
||||
.WithOne(s => s.Redistributable)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
#endregion
|
||||
|
||||
#region Collection Relationships
|
||||
builder.Entity<Collection>()
|
||||
.HasMany(c => c.Games)
|
||||
.WithMany(g => g.Collections)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"CollectionGame",
|
||||
cg => cg.HasOne<Game>().WithMany().HasForeignKey("GameId"),
|
||||
cg => cg.HasOne<Collection>().WithMany().HasForeignKey("CollectionId")
|
||||
);
|
||||
#endregion
|
||||
|
||||
#region Role Relationships
|
||||
builder.Entity<Role>()
|
||||
.HasMany(r => r.Collections)
|
||||
.WithMany(c => c.Roles)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"RoleCollection",
|
||||
rc => rc.HasOne<Collection>().WithMany().HasForeignKey("CollectionId"),
|
||||
rc => rc.HasOne<Role>().WithMany().HasForeignKey("RoleId")
|
||||
);
|
||||
#endregion
|
||||
}
|
||||
|
||||
public DbSet<Game>? Games { get; set; }
|
||||
|
@ -120,8 +219,14 @@ namespace LANCommander.Data
|
|||
|
||||
public DbSet<GameSave>? GameSaves { get; set; }
|
||||
|
||||
public DbSet<PlaySession>? PlaySessions { get; set; }
|
||||
|
||||
public DbSet<Server>? Servers { get; set; }
|
||||
|
||||
public DbSet<ServerConsole>? ServerConsoles { get; set; }
|
||||
|
||||
public DbSet<Redistributable>? Redistributables { get; set; }
|
||||
|
||||
public DbSet<Media>? Media { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace LANCommander.Data.Enums
|
||||
{
|
||||
public enum MediaType
|
||||
{
|
||||
Icon,
|
||||
Cover,
|
||||
Background
|
||||
}
|
||||
}
|
|
@ -13,6 +13,8 @@ namespace LANCommander.Data.Enums
|
|||
[Display(Name = "Save Upload")]
|
||||
SaveUpload,
|
||||
[Display(Name = "Save Download")]
|
||||
SaveDownload
|
||||
SaveDownload,
|
||||
[Display(Name = "Detect Install")]
|
||||
DetectInstall
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
using LANCommander.Data.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LANCommander.Data
|
||||
{
|
||||
public static class ModelBuilderExtensions
|
||||
{
|
||||
public static void ConfigureBaseRelationships<T>(this ModelBuilder modelBuilder) where T : BaseModel
|
||||
{
|
||||
modelBuilder.Entity<T>()
|
||||
.HasOne(x => x.CreatedBy)
|
||||
.WithMany()
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
modelBuilder.Entity<T>()
|
||||
.HasOne(x => x.UpdatedBy)
|
||||
.WithMany()
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,12 +13,18 @@ namespace LANCommander.Data.Models
|
|||
[Required]
|
||||
public string Version { get; set; }
|
||||
|
||||
public Guid GameId { get; set; }
|
||||
public Guid? GameId { get; set; }
|
||||
[JsonIgnore]
|
||||
[ForeignKey(nameof(GameId))]
|
||||
[InverseProperty("Archives")]
|
||||
public virtual Game? Game { get; set; }
|
||||
|
||||
public Guid? RedistributableId { get; set; }
|
||||
[JsonIgnore]
|
||||
[ForeignKey(nameof(RedistributableId))]
|
||||
[InverseProperty("Archives")]
|
||||
public virtual Redistributable? Redistributable { get; set; }
|
||||
|
||||
[Display(Name = "Last Version")]
|
||||
public virtual Archive? LastVersion { get; set; }
|
||||
|
||||
|
|
|
@ -10,10 +10,13 @@ namespace LANCommander.Data.Models
|
|||
|
||||
[Display(Name = "Created On")]
|
||||
public DateTime CreatedOn { get; set; }
|
||||
|
||||
[Display(Name = "Created By")]
|
||||
public virtual User? CreatedBy { get; set; }
|
||||
|
||||
[Display(Name = "Updated On")]
|
||||
public DateTime UpdatedOn { get; set; }
|
||||
|
||||
[Display(Name = "Updated By")]
|
||||
public virtual User? UpdatedBy { get; set; }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LANCommander.Data.Models
|
||||
{
|
||||
[Table("Collections")]
|
||||
public class Collection : BaseModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
[JsonIgnore]
|
||||
public virtual ICollection<Game> Games { get; set; }
|
||||
[JsonIgnore]
|
||||
public virtual ICollection<Role> Roles { get; set; }
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@ namespace LANCommander.Data.Models
|
|||
public string Title { get; set; }
|
||||
[Display(Name = "Sort Title")]
|
||||
public string? SortTitle { get; set; }
|
||||
public string? Icon { get; set; }
|
||||
[Display(Name = "Directory Name")]
|
||||
public string? DirectoryName { get; set; }
|
||||
public string? Description { get; set; }
|
||||
|
@ -32,10 +31,14 @@ namespace LANCommander.Data.Models
|
|||
public virtual ICollection<Archive>? Archives { get; set; }
|
||||
public virtual ICollection<Script>? Scripts { get; set; }
|
||||
public virtual ICollection<GameSave>? GameSaves { get; set; }
|
||||
public virtual ICollection<PlaySession>? PlaySessions { get; set; }
|
||||
public virtual ICollection<SavePath>? SavePaths { get; set; }
|
||||
public virtual ICollection<Server>? Servers { get; set; }
|
||||
public virtual ICollection<Redistributable>? Redistributables { get; set; }
|
||||
public virtual ICollection<Media>? Media { get; set; }
|
||||
|
||||
public string? ValidKeyRegex { get; set; }
|
||||
public virtual ICollection<Key>? Keys { get; set; }
|
||||
public virtual ICollection<Collection> Collections { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace LANCommander.Data.Models
|
|||
[Table("GameSaves")]
|
||||
public class GameSave : BaseModel
|
||||
{
|
||||
public Guid GameId { get; set; }
|
||||
public Guid? GameId { get; set; }
|
||||
[JsonIgnore]
|
||||
[ForeignKey(nameof(GameId))]
|
||||
[InverseProperty("GameSaves")]
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
using LANCommander.Data.Enums;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LANCommander.Data.Models
|
||||
{
|
||||
[Table("Media")]
|
||||
public class Media : BaseModel
|
||||
{
|
||||
public Guid FileId { get; set; }
|
||||
public MediaType Type { get; set; }
|
||||
|
||||
[MaxLength(2048)]
|
||||
public string SourceUrl { get; set; }
|
||||
|
||||
[MaxLength(255)]
|
||||
public string MimeType { get; set; }
|
||||
|
||||
public Guid GameId { get; set; }
|
||||
[JsonIgnore]
|
||||
[ForeignKey(nameof(GameId))]
|
||||
[InverseProperty("Media")]
|
||||
public virtual Game? Game { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LANCommander.Data.Models
|
||||
{
|
||||
[Table("PlaySessions")]
|
||||
public class PlaySession : BaseModel
|
||||
{
|
||||
public Guid? GameId { get; set; }
|
||||
[JsonIgnore]
|
||||
[ForeignKey(nameof(GameId))]
|
||||
[InverseProperty("PlaySessions")]
|
||||
public virtual Game? Game { get; set; }
|
||||
|
||||
public Guid UserId { get; set; }
|
||||
[ForeignKey(nameof(UserId))]
|
||||
[InverseProperty("PlaySessions")]
|
||||
public virtual User? User { get; set; }
|
||||
|
||||
[Display(Name = "Start")]
|
||||
public DateTime? Start { get; set; }
|
||||
|
||||
[Display(Name = "End")]
|
||||
public DateTime? End { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace LANCommander.Data.Models
|
||||
{
|
||||
[Table("Redistributables")]
|
||||
public class Redistributable : BaseModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public virtual ICollection<Archive>? Archives { get; set; }
|
||||
public virtual ICollection<Script>? Scripts { get; set; }
|
||||
public virtual ICollection<Game>? Games { get; set; }
|
||||
}
|
||||
}
|
|
@ -6,5 +6,6 @@ namespace LANCommander.Data.Models
|
|||
[Table("Roles")]
|
||||
public class Role : IdentityRole<Guid>
|
||||
{
|
||||
public virtual ICollection<Collection> Collections { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace LANCommander.Data.Models
|
|||
{
|
||||
public SavePathType Type { get; set; }
|
||||
public string Path { get; set; }
|
||||
public bool IsRegex { get; set; }
|
||||
|
||||
public Guid? GameId { get; set; }
|
||||
[JsonIgnore]
|
||||
|
|
|
@ -18,5 +18,11 @@ namespace LANCommander.Data.Models
|
|||
[ForeignKey(nameof(GameId))]
|
||||
[InverseProperty("Scripts")]
|
||||
public virtual Game? Game { get; set; }
|
||||
|
||||
public Guid? RedistributableId { get; set; }
|
||||
[JsonIgnore]
|
||||
[ForeignKey(nameof(RedistributableId))]
|
||||
[InverseProperty("Scripts")]
|
||||
public virtual Redistributable? Redistributable { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,6 @@ namespace LANCommander.Data.Models
|
|||
public bool Autostart { get; set; }
|
||||
public int AutostartDelay { get; set; }
|
||||
|
||||
public bool EnableHTTP { get; set; }
|
||||
public string HTTPRootPath { get; set; }
|
||||
|
||||
public Guid? GameId { get; set; }
|
||||
[JsonIgnore]
|
||||
[ForeignKey(nameof(GameId))]
|
||||
|
@ -27,5 +24,6 @@ namespace LANCommander.Data.Models
|
|||
public virtual Game? Game { get; set; }
|
||||
|
||||
public virtual ICollection<ServerConsole>? ServerConsoles { get; set; }
|
||||
public virtual ICollection<ServerHttpPath>? HttpPaths { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LANCommander.Data.Models
|
||||
{
|
||||
public class ServerHttpPath : BaseModel
|
||||
{
|
||||
public string LocalPath { get; set; }
|
||||
public string Path { get; set; }
|
||||
|
||||
public Guid ServerId { get; set; }
|
||||
[JsonIgnore]
|
||||
[ForeignKey(nameof(ServerId))]
|
||||
[InverseProperty("HttpPaths")]
|
||||
public virtual Server Server { get; set; }
|
||||
}
|
||||
}
|
|
@ -46,12 +46,17 @@ namespace LANCommander.Data.Models
|
|||
[JsonIgnore]
|
||||
public virtual ICollection<GameSave>? GameSaves { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual ICollection<PlaySession>? PlaySessions { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool Approved { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public DateTime? ApprovedOn { get; set; }
|
||||
|
||||
public string? Alias { get; set; }
|
||||
|
||||
public string GetGameSaveUploadPath()
|
||||
{
|
||||
var settings = SettingService.GetSettings();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>aspnet-LANCommander-C1F79CFA-9767-4AD7-BD5A-2549F8328A2D</UserSecretsId>
|
||||
|
@ -22,25 +22,26 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AntDesign" Version="0.15.5" />
|
||||
<PackageReference Include="AntDesign.Charts" Version="0.3.1" />
|
||||
<PackageReference Include="AntDesign" Version="0.16.1" />
|
||||
<PackageReference Include="AntDesign.Charts" Version="0.3.2" />
|
||||
<PackageReference Include="Blazor-ApexCharts" Version="1.0.1" />
|
||||
<PackageReference Include="BlazorMonaco" Version="3.1.0" />
|
||||
<PackageReference Include="ByteSize" Version="2.1.1" />
|
||||
<PackageReference Include="CoreRCON" Version="5.0.5" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.5" />
|
||||
<PackageReference Include="Hangfire.Core" Version="1.8.5" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="0.5.1" />
|
||||
<PackageReference Include="craftersmine.SteamGridDB.Net" Version="1.1.5" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.6" />
|
||||
<PackageReference Include="Hangfire.Core" Version="1.8.6" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="0.6.0" />
|
||||
<PackageReference Include="IGDB" Version="2.3.2" />
|
||||
<PackageReference Include="IPXRelayDotNet" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.11">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
@ -49,14 +50,15 @@
|
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.10" />
|
||||
<PackageReference Include="NLog" Version="5.2.4" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.4" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.0" />
|
||||
<PackageReference Include="NLog" Version="5.2.5" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.5" />
|
||||
<PackageReference Include="RegParserDotNet" Version="1.0.4" />
|
||||
<PackageReference Include="rix0rrr.BeaconLib" Version="1.0.2" />
|
||||
<PackageReference Include="swashbuckle" Version="5.6.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
|
||||
<PackageReference Include="XtermBlazor" Version="1.9.0" />
|
||||
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="8.0.0" />
|
||||
<PackageReference Include="XtermBlazor" Version="1.10.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="13.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -64,7 +66,6 @@
|
|||
<Folder Include="bin\Debug\net6.0\" />
|
||||
<Folder Include="Data\Migrations\" />
|
||||
<Folder Include="Migrations\" />
|
||||
<Folder Include="Pages\Games\Archives\" />
|
||||
<Folder Include="Scripts\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -86,6 +87,81 @@
|
|||
<None Update="favicon.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Examples\Copy Directory.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Examples\Create Directory.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Examples\Create Registry Path.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Examples\Get User Registry Virtual Store.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Examples\Patch Binary.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Examples\Remove Directory.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Examples\Rename File.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Examples\Sanitize Filename.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Examples\Set Compatibility Mode.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Examples\Set Registry Key.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Examples\Trim String.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Examples\Write to File.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Functions\Convert-AspectRatio.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Functions\ConvertTo-StringBytes.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Functions\Edit-PatchBinary.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Functions\Get-GameManifest.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Functions\Get-PrimaryDisplay.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Functions\Write-ReplaceContentInFile.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Variables\Allocated Key.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Variables\Default Install Directory.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Variables\Game Install Directory.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Variables\Game Manifest.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Variables\New Player Alias.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Variables\Old Player Alias.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Snippets\Variables\Server Address.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,28 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LANCommander.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPlayerAlias : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Alias",
|
||||
table: "AspNetUsers",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Alias",
|
||||
table: "AspNetUsers");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue