Move methods that should be static to ManifestHelper and ScriptHelper. Move install logic to GameManager and RedistributableManager. Update InstallController and UninstallController
parent
e53709334c
commit
39f2d4b212
|
@ -1,17 +1,10 @@
|
|||
using LANCommander.PlaynitePlugin.Helpers;
|
||||
using LANCommander.SDK.Enums;
|
||||
using LANCommander.SDK.Extensions;
|
||||
using LANCommander.SDK;
|
||||
using LANCommander.SDK.Helpers;
|
||||
using LANCommander.SDK.Models;
|
||||
using Playnite.SDK;
|
||||
using Playnite.SDK.Models;
|
||||
using Playnite.SDK.Plugins;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
{
|
||||
|
@ -20,15 +13,11 @@ namespace LANCommander.PlaynitePlugin
|
|||
public static readonly ILogger Logger = LogManager.GetLogger();
|
||||
|
||||
private LANCommanderLibraryPlugin Plugin;
|
||||
private PowerShellRuntime PowerShellRuntime;
|
||||
private Playnite.SDK.Models.Game PlayniteGame;
|
||||
|
||||
public LANCommanderInstallController(LANCommanderLibraryPlugin plugin, Playnite.SDK.Models.Game game) : base(game)
|
||||
{
|
||||
Name = "Install using LANCommander";
|
||||
Plugin = plugin;
|
||||
PlayniteGame = game;
|
||||
PowerShellRuntime = new PowerShellRuntime();
|
||||
}
|
||||
|
||||
public override void Install(InstallActionArgs args)
|
||||
|
@ -43,450 +32,52 @@ namespace LANCommander.PlaynitePlugin
|
|||
}
|
||||
|
||||
var gameId = Guid.Parse(Game.GameId);
|
||||
var game = Plugin.LANCommander.GetGame(gameId);
|
||||
|
||||
Logger.Trace($"Installing game {game.Title} ({game.Id})...");
|
||||
string installDirectory = null;
|
||||
|
||||
var result = RetryHelper.RetryOnException<ExtractionResult>(10, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () =>
|
||||
{
|
||||
Logger.Trace("Attempting to download and extract game...");
|
||||
return DownloadAndExtractGame(game);
|
||||
});
|
||||
|
||||
if (!result.Success && !result.Canceled)
|
||||
throw new Exception("Could not extract the install archive. Retry the install or check your connection.");
|
||||
else if (result.Canceled)
|
||||
throw new Exception("Install was canceled");
|
||||
|
||||
var installInfo = new GameInstallationData()
|
||||
{
|
||||
InstallDirectory = result.Directory
|
||||
};
|
||||
|
||||
PlayniteGame.InstallDirectory = result.Directory;
|
||||
|
||||
SDK.GameManifest manifest = null;
|
||||
|
||||
var writeManifestSuccess = RetryHelper.RetryOnException(10, TimeSpan.FromSeconds(1), false, () =>
|
||||
{
|
||||
Logger.Trace("Attempting to get game manifest...");
|
||||
|
||||
manifest = Plugin.LANCommander.GetGameManifest(gameId);
|
||||
|
||||
WriteManifest(manifest, result.Directory);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!writeManifestSuccess)
|
||||
throw new Exception("Could not get or write the manifest file. Retry the install or check your connection.");
|
||||
|
||||
Logger.Trace("Saving scripts...");
|
||||
|
||||
SaveScript(game, result.Directory, ScriptType.Install);
|
||||
SaveScript(game, result.Directory, ScriptType.Uninstall);
|
||||
SaveScript(game, result.Directory, ScriptType.NameChange);
|
||||
SaveScript(game, result.Directory, ScriptType.KeyChange);
|
||||
|
||||
if (game.Redistributables != null && game.Redistributables.Count() > 0)
|
||||
{
|
||||
Logger.Trace("Installing required redistributables...");
|
||||
InstallRedistributables(game);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.Install);
|
||||
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.NameChange, Plugin.Settings.PlayerName);
|
||||
|
||||
var key = Plugin.LANCommander.GetAllocatedKey(game.Id);
|
||||
|
||||
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.KeyChange, $"\"{key}\"");
|
||||
}
|
||||
catch { }
|
||||
|
||||
Plugin.UpdateGame(manifest, gameId);
|
||||
|
||||
Plugin.DownloadCache.Remove(gameId);
|
||||
|
||||
InvokeOnInstalled(new GameInstalledEventArgs(installInfo));
|
||||
}
|
||||
|
||||
private ExtractionResult DownloadAndExtractGame(LANCommander.SDK.Models.Game game)
|
||||
{
|
||||
if (game == null)
|
||||
{
|
||||
Logger.Trace("Game failed to download! No game was specified!");
|
||||
|
||||
throw new Exception("Game failed to download!");
|
||||
}
|
||||
|
||||
var destination = Path.Combine(Plugin.Settings.InstallDirectory, game.Title.SanitizeFilename());
|
||||
|
||||
Logger.Trace($"Downloading and extracting \"{game.Title}\" to path {destination}");
|
||||
var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
{
|
||||
try
|
||||
var gameManager = new GameManager(Plugin.LANCommanderClient);
|
||||
|
||||
gameManager.OnArchiveExtractionProgress += (long pos, long len) =>
|
||||
{
|
||||
Directory.CreateDirectory(destination);
|
||||
progress.ProgressMaxValue = 100;
|
||||
progress.CurrentProgressValue = 0;
|
||||
progress.ProgressMaxValue = len;
|
||||
progress.CurrentProgressValue = pos;
|
||||
};
|
||||
|
||||
using (var gameStream = Plugin.LANCommander.StreamGame(game.Id))
|
||||
using (var reader = ReaderFactory.Open(gameStream))
|
||||
{
|
||||
progress.ProgressMaxValue = gameStream.Length;
|
||||
|
||||
gameStream.OnProgress += (pos, len) =>
|
||||
{
|
||||
progress.CurrentProgressValue = pos;
|
||||
};
|
||||
|
||||
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
|
||||
{
|
||||
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
|
||||
{
|
||||
reader.Cancel();
|
||||
progress.IsIndeterminate = true;
|
||||
|
||||
reader.Dispose();
|
||||
gameStream.Dispose();
|
||||
}
|
||||
};
|
||||
|
||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
gameManager.OnArchiveEntryExtractionProgress += (object sender, ArchiveEntryExtractionProgressArgs e) =>
|
||||
{
|
||||
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
|
||||
{
|
||||
Logger.Trace("User cancelled the download");
|
||||
e.Reader.Cancel();
|
||||
e.Reader.Dispose();
|
||||
e.Stream.Dispose();
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger.Trace("Cleaning up orphaned install files after cancelled install...");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
}
|
||||
progress.IsIndeterminate = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error(ex, $"Could not extract to path {destination}");
|
||||
};
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger.Trace("Cleaning up orphaned install files after bad install...");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
}
|
||||
|
||||
throw new Exception("The game archive could not be extracted. Please try again or fix the archive!");
|
||||
}
|
||||
}
|
||||
installDirectory = gameManager.Install(gameId);
|
||||
},
|
||||
new GlobalProgressOptions($"Downloading {game.Title}...")
|
||||
new GlobalProgressOptions($"Downloading {Game.Name}...")
|
||||
{
|
||||
IsIndeterminate = false,
|
||||
IsIndeterminate = true,
|
||||
Cancelable = true,
|
||||
});
|
||||
|
||||
var extractionResult = new ExtractionResult
|
||||
if (!result.Canceled && result.Error == null && !String.IsNullOrWhiteSpace(installDirectory))
|
||||
{
|
||||
Canceled = result.Canceled
|
||||
};
|
||||
var manifest = ManifestHelper.Read(installDirectory);
|
||||
|
||||
if (!result.Canceled)
|
||||
{
|
||||
extractionResult.Success = true;
|
||||
extractionResult.Directory = destination;
|
||||
Logger.Trace($"Game successfully downloaded and extracted to {destination}");
|
||||
Plugin.UpdateGame(manifest);
|
||||
|
||||
var installInfo = new GameInstallationData
|
||||
{
|
||||
InstallDirectory = installDirectory,
|
||||
};
|
||||
|
||||
InvokeOnInstalled(new GameInstalledEventArgs(installInfo));
|
||||
}
|
||||
|
||||
return extractionResult;
|
||||
}
|
||||
|
||||
private void InstallRedistributables(LANCommander.SDK.Models.Game game)
|
||||
{
|
||||
foreach (var redistributable in game.Redistributables)
|
||||
{
|
||||
string installScriptTempFile = null;
|
||||
string detectionScriptTempFile = null;
|
||||
string extractTempPath = null;
|
||||
|
||||
try
|
||||
{
|
||||
var installScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.Install);
|
||||
installScriptTempFile = SaveTempScript(installScript);
|
||||
|
||||
var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall);
|
||||
detectionScriptTempFile = SaveTempScript(detectionScript);
|
||||
|
||||
var detectionResult = PowerShellRuntime.RunScript(detectionScriptTempFile, detectionScript.RequiresAdmin);
|
||||
|
||||
// Redistributable is not installed
|
||||
if (detectionResult == 0)
|
||||
{
|
||||
if (redistributable.Archives.Count() > 0)
|
||||
{
|
||||
var extractionResult = DownloadAndExtractRedistributable(redistributable);
|
||||
|
||||
if (extractionResult.Success)
|
||||
{
|
||||
extractTempPath = extractionResult.Directory;
|
||||
|
||||
PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, $"Redistributable {redistributable.Name} failed to install");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(installScriptTempFile))
|
||||
File.Delete(installScriptTempFile);
|
||||
|
||||
if (File.Exists(detectionScriptTempFile))
|
||||
File.Delete(detectionScriptTempFile);
|
||||
|
||||
if (Directory.Exists(extractTempPath))
|
||||
Directory.Delete(extractTempPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ExtractionResult DownloadAndExtractRedistributable(LANCommander.SDK.Models.Redistributable redistributable)
|
||||
{
|
||||
if (redistributable == null)
|
||||
{
|
||||
Logger.Trace("Redistributable failed to download! No redistributable was specified!");
|
||||
|
||||
throw new Exception("Redistributable failed to download!");
|
||||
}
|
||||
|
||||
var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename());
|
||||
|
||||
Logger.Trace($"Downloading and extracting \"{redistributable.Name}\" to path {destination}");
|
||||
var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(destination);
|
||||
progress.ProgressMaxValue = 100;
|
||||
progress.CurrentProgressValue = 0;
|
||||
|
||||
using (var redistributableStream = Plugin.LANCommander.StreamRedistributable(redistributable.Id))
|
||||
using (var reader = ReaderFactory.Open(redistributableStream))
|
||||
{
|
||||
progress.ProgressMaxValue = redistributableStream.Length;
|
||||
|
||||
redistributableStream.OnProgress += (pos, len) =>
|
||||
{
|
||||
progress.CurrentProgressValue = pos;
|
||||
};
|
||||
|
||||
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
|
||||
{
|
||||
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
|
||||
{
|
||||
reader.Cancel();
|
||||
progress.IsIndeterminate = true;
|
||||
|
||||
reader.Dispose();
|
||||
redistributableStream.Dispose();
|
||||
}
|
||||
};
|
||||
|
||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
|
||||
{
|
||||
Logger.Trace("User cancelled the download");
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger.Trace("Cleaning up orphaned install files after cancelled install...");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error(ex, $"Could not extract to path {destination}");
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger.Trace("Cleaning up orphaned install files after bad install...");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
}
|
||||
|
||||
throw new Exception("The redistributable archive could not be extracted. Please try again or fix the archive!");
|
||||
}
|
||||
}
|
||||
},
|
||||
new GlobalProgressOptions($"Downloading {redistributable.Name}...")
|
||||
{
|
||||
IsIndeterminate = false,
|
||||
Cancelable = true,
|
||||
});
|
||||
|
||||
var extractionResult = new ExtractionResult
|
||||
{
|
||||
Canceled = result.Canceled
|
||||
};
|
||||
|
||||
if (!result.Canceled)
|
||||
{
|
||||
extractionResult.Success = true;
|
||||
extractionResult.Directory = destination;
|
||||
Logger.Trace($"Redistributable successfully downloaded and extracted to {destination}");
|
||||
}
|
||||
|
||||
return extractionResult;
|
||||
}
|
||||
|
||||
private string Download(LANCommander.SDK.Models.Game game)
|
||||
{
|
||||
string tempFile = String.Empty;
|
||||
|
||||
if (game != null)
|
||||
{
|
||||
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
{
|
||||
progress.ProgressMaxValue = 100;
|
||||
progress.CurrentProgressValue = 0;
|
||||
|
||||
var destination = Plugin.LANCommander.DownloadGame(game.Id, (changed) =>
|
||||
{
|
||||
progress.CurrentProgressValue = changed.ProgressPercentage;
|
||||
}, (complete) =>
|
||||
{
|
||||
progress.CurrentProgressValue = 100;
|
||||
});
|
||||
|
||||
// Lock the thread until download is done
|
||||
while (progress.CurrentProgressValue != 100)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
tempFile = destination;
|
||||
},
|
||||
new GlobalProgressOptions($"Downloading {game.Title}...")
|
||||
{
|
||||
IsIndeterminate = false,
|
||||
Cancelable = false,
|
||||
});
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
else
|
||||
throw new Exception("Game failed to download!");
|
||||
}
|
||||
|
||||
private string Extract(LANCommander.SDK.Models.Game game, string archivePath)
|
||||
{
|
||||
var destination = Path.Combine(Plugin.Settings.InstallDirectory, game.Title.SanitizeFilename());
|
||||
|
||||
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
{
|
||||
Directory.CreateDirectory(destination);
|
||||
|
||||
using (var fs = File.OpenRead(archivePath))
|
||||
using (var ts = new TrackableStream(fs))
|
||||
using (var reader = ReaderFactory.Open(ts))
|
||||
{
|
||||
progress.ProgressMaxValue = ts.Length;
|
||||
ts.OnProgress += (pos, len) =>
|
||||
{
|
||||
progress.CurrentProgressValue = pos;
|
||||
};
|
||||
|
||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
},
|
||||
new GlobalProgressOptions($"Extracting {game.Title}...")
|
||||
{
|
||||
IsIndeterminate = false,
|
||||
Cancelable = false,
|
||||
});
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private void WriteManifest(SDK.GameManifest manifest, string installDirectory)
|
||||
{
|
||||
var destination = Path.Combine(installDirectory, "_manifest.yml");
|
||||
|
||||
Logger.Trace($"Attempting to write manifest to path {destination}");
|
||||
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(new PascalCaseNamingConvention())
|
||||
.Build();
|
||||
|
||||
Logger.Trace("Serializing manifest...");
|
||||
var yaml = serializer.Serialize(manifest);
|
||||
|
||||
Logger.Trace("Writing manifest file...");
|
||||
File.WriteAllText(destination, yaml);
|
||||
}
|
||||
|
||||
private string SaveTempScript(LANCommander.SDK.Models.Script script)
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
|
||||
File.Move(tempPath, tempPath + ".ps1");
|
||||
|
||||
tempPath = tempPath + ".ps1";
|
||||
|
||||
Logger.Trace($"Writing script {script.Name} to {tempPath}");
|
||||
|
||||
File.WriteAllText(tempPath, script.Contents);
|
||||
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
private void SaveScript(LANCommander.SDK.Models.Game game, string installationDirectory, ScriptType type)
|
||||
{
|
||||
var script = game.Scripts.FirstOrDefault(s => s.Type == type);
|
||||
|
||||
if (script == null)
|
||||
return;
|
||||
|
||||
if (script.RequiresAdmin)
|
||||
script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents;
|
||||
|
||||
var filename = PowerShellRuntime.GetScriptFilePath(PlayniteGame, type);
|
||||
|
||||
if (File.Exists(filename))
|
||||
File.Delete(filename);
|
||||
|
||||
Logger.Trace($"Writing {type} script to {filename}");
|
||||
|
||||
File.WriteAllText(filename, script.Contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,12 +42,6 @@
|
|||
</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.34.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpCompress.0.34.1\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>
|
||||
|
@ -91,12 +85,6 @@
|
|||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<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>
|
||||
</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="Extensions\MultiplayerInfoExtensions.cs" />
|
||||
|
|
|
@ -21,8 +21,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
{
|
||||
public static readonly ILogger Logger = LogManager.GetLogger();
|
||||
internal LANCommanderSettingsViewModel Settings { get; set; }
|
||||
internal LANCommander.SDK.LANCommander LANCommander { get; set; }
|
||||
internal PowerShellRuntime PowerShellRuntime { get; set; }
|
||||
internal LANCommander.SDK.Client LANCommanderClient { get; set; }
|
||||
internal GameSaveService GameSaveService { get; set; }
|
||||
|
||||
public override Guid Id { get; } = Guid.Parse("48e1bac7-e0a0-45d7-ba83-36f5e9e959fc");
|
||||
|
@ -39,8 +38,8 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
Settings = new LANCommanderSettingsViewModel(this);
|
||||
|
||||
LANCommander = new SDK.LANCommander(Settings.ServerAddress);
|
||||
LANCommander.Client.UseToken(new SDK.Models.AuthToken()
|
||||
LANCommanderClient = new SDK.Client(Settings.ServerAddress);
|
||||
LANCommanderClient.UseToken(new SDK.Models.AuthToken()
|
||||
{
|
||||
AccessToken = Settings.AccessToken,
|
||||
RefreshToken = Settings.RefreshToken,
|
||||
|
@ -89,7 +88,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
public bool ValidateConnection()
|
||||
{
|
||||
return LANCommander.Client.ValidateToken();
|
||||
return LANCommanderClient.ValidateToken();
|
||||
}
|
||||
|
||||
public override IEnumerable<GameMetadata> GetGames(LibraryGetGamesArgs args)
|
||||
|
@ -109,7 +108,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
}
|
||||
}
|
||||
|
||||
var games = LANCommander.Client
|
||||
var games = LANCommanderClient
|
||||
.GetGames()
|
||||
.Where(g => g != null && g.Archives != null && g.Archives.Count() > 0);
|
||||
|
||||
|
@ -119,7 +118,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
{
|
||||
Logger.Trace($"Importing/updating metadata for game \"{game.Title}\"...");
|
||||
|
||||
var manifest = LANCommander.Client.GetGameManifest(game.Id);
|
||||
var manifest = LANCommanderClient.GetGameManifest(game.Id);
|
||||
Logger.Trace("Successfully grabbed game manifest");
|
||||
|
||||
var existingGame = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == game.Id.ToString() && g.PluginId == Id && g.IsInstalled);
|
||||
|
@ -128,7 +127,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
{
|
||||
Logger.Trace("Game already exists in library, updating metadata...");
|
||||
|
||||
UpdateGame(manifest, game.Id);
|
||||
UpdateGame(manifest);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
@ -181,13 +180,13 @@ namespace LANCommander.PlaynitePlugin
|
|||
metadata.Features.Add(new MetadataNameProperty($"Online Multiplayer {manifest.OnlineMultiplayer.GetPlayerCount()}".Trim()));
|
||||
|
||||
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Icon))
|
||||
metadata.Icon = new MetadataFile(LANCommander.Client.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Icon)));
|
||||
metadata.Icon = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Icon)));
|
||||
|
||||
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Cover))
|
||||
metadata.CoverImage = new MetadataFile(LANCommander.Client.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Cover)));
|
||||
metadata.CoverImage = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Cover)));
|
||||
|
||||
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Background))
|
||||
metadata.BackgroundImage = new MetadataFile(LANCommander.Client.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background)));
|
||||
metadata.BackgroundImage = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background)));
|
||||
|
||||
gameMetadata.Add(metadata);
|
||||
}
|
||||
|
@ -222,9 +221,9 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
if (args.Games.Count == 1 && args.Games.First().IsInstalled && !String.IsNullOrWhiteSpace(args.Games.First().InstallDirectory))
|
||||
{
|
||||
var nameChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.NameChange);
|
||||
var keyChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.KeyChange);
|
||||
var installScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.Install);
|
||||
var nameChangeScriptPath = LANCommander.SDK.PowerShellRuntime.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.NameChange);
|
||||
var keyChangeScriptPath = LANCommander.SDK.PowerShellRuntime.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.KeyChange);
|
||||
var installScriptPath = LANCommander.SDK.PowerShellRuntime.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.Install);
|
||||
|
||||
if (File.Exists(nameChangeScriptPath))
|
||||
{
|
||||
|
@ -241,8 +240,10 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
if (result.Result == true)
|
||||
{
|
||||
PowerShellRuntime.RunScript(nameChangeArgs.Games.First(), SDK.Enums.ScriptType.NameChange, $@"""{result.SelectedString}"" ""{oldName}""");
|
||||
LANCommander.Client.ChangeAlias(result.SelectedString);
|
||||
var game = nameChangeArgs.Games.First();
|
||||
|
||||
LANCommander.SDK.PowerShellRuntime.RunScript(game.InstallDirectory, SDK.Enums.ScriptType.NameChange, $@"""{result.SelectedString}"" ""{oldName}""");
|
||||
LANCommanderClient.ChangeAlias(result.SelectedString);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -262,12 +263,12 @@ namespace LANCommander.PlaynitePlugin
|
|||
if (Guid.TryParse(keyChangeArgs.Games.First().GameId, out gameId))
|
||||
{
|
||||
// NUKIEEEE
|
||||
var newKey = LANCommander.Client.GetNewKey(gameId);
|
||||
var newKey = LANCommanderClient.GetNewKey(gameId);
|
||||
|
||||
if (String.IsNullOrEmpty(newKey))
|
||||
PlayniteApi.Dialogs.ShowErrorMessage("There are no more keys available on the server.", "No Keys Available");
|
||||
else
|
||||
PowerShellRuntime.RunScript(keyChangeArgs.Games.First(), SDK.Enums.ScriptType.KeyChange, $@"""{newKey}""");
|
||||
LANCommander.SDK.PowerShellRuntime.RunScript(keyChangeArgs.Games.First().InstallDirectory, SDK.Enums.ScriptType.KeyChange, $@"""{newKey}""");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -290,7 +291,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
if (Guid.TryParse(installArgs.Games.First().GameId, out gameId))
|
||||
{
|
||||
PowerShellRuntime.RunScript(installArgs.Games.First(), SDK.Enums.ScriptType.Install);
|
||||
LANCommander.SDK.PowerShellRuntime.RunScript(installArgs.Games.First().InstallDirectory, SDK.Enums.ScriptType.Install);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -400,11 +401,11 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
var games = PlayniteApi.Database.Games.Where(g => g.IsInstalled).ToList();
|
||||
|
||||
LANCommander.Client.ChangeAlias(result.SelectedString);
|
||||
LANCommanderClient.ChangeAlias(result.SelectedString);
|
||||
|
||||
Logger.Trace($"Running name change scripts across {games.Count} installed game(s)");
|
||||
|
||||
PowerShellRuntime.RunScripts(games, SDK.Enums.ScriptType.NameChange, Settings.PlayerName);
|
||||
LANCommander.SDK.PowerShellRuntime.RunScripts(games.Select(g => g.InstallDirectory), SDK.Enums.ScriptType.NameChange, Settings.PlayerName);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -439,9 +440,9 @@ namespace LANCommander.PlaynitePlugin
|
|||
return window;
|
||||
}
|
||||
|
||||
public void UpdateGame(SDK.GameManifest manifest, Guid gameId)
|
||||
public void UpdateGame(SDK.GameManifest manifest)
|
||||
{
|
||||
var game = PlayniteApi.Database.Games.First(g => g.GameId == gameId.ToString());
|
||||
var game = PlayniteApi.Database.Games.First(g => g.GameId == manifest.Id.ToString());
|
||||
|
||||
if (game == null)
|
||||
return;
|
||||
|
|
|
@ -17,15 +17,13 @@ namespace LANCommander.PlaynitePlugin.Services
|
|||
{
|
||||
internal class GameSaveService
|
||||
{
|
||||
private readonly LANCommanderClient LANCommander;
|
||||
private readonly LANCommander.SDK.Client LANCommander;
|
||||
private readonly IPlayniteAPI PlayniteApi;
|
||||
private readonly PowerShellRuntime PowerShellRuntime;
|
||||
|
||||
internal GameSaveService(LANCommanderClient lanCommander, IPlayniteAPI playniteApi, PowerShellRuntime powerShellRuntime)
|
||||
internal GameSaveService(LANCommander.SDK.Client lanCommander, IPlayniteAPI playniteApi)
|
||||
{
|
||||
LANCommander = lanCommander;
|
||||
PlayniteApi = playniteApi;
|
||||
PowerShellRuntime = powerShellRuntime;
|
||||
}
|
||||
|
||||
internal void DownloadSave(Game game)
|
||||
|
|
|
@ -12,29 +12,25 @@ namespace LANCommander.PlaynitePlugin
|
|||
public static readonly ILogger Logger = LogManager.GetLogger();
|
||||
|
||||
private LANCommanderLibraryPlugin Plugin;
|
||||
private PowerShellRuntime PowerShellRuntime;
|
||||
|
||||
public LANCommanderUninstallController(LANCommanderLibraryPlugin plugin, Game game) : base(game)
|
||||
{
|
||||
Name = "Uninstall LANCommander Game";
|
||||
Plugin = plugin;
|
||||
PowerShellRuntime = new PowerShellRuntime();
|
||||
}
|
||||
|
||||
public override void Uninstall(UninstallActionArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
PowerShellRuntime.RunScript(Game, ScriptType.Uninstall);
|
||||
var gameManager = new LANCommander.SDK.GameManager(Plugin.LANCommanderClient);
|
||||
|
||||
gameManager.Uninstall(Game.InstallDirectory);
|
||||
}
|
||||
catch { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Logger.Trace("Attempting to delete install directory...");
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(Game.InstallDirectory) && Directory.Exists(Game.InstallDirectory))
|
||||
Directory.Delete(Game.InstallDirectory, true);
|
||||
|
||||
Logger.Trace("Deleted!");
|
||||
}
|
||||
|
||||
InvokeOnUninstalled(new GameUninstalledEventArgs());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
public class ArchiveExtractionProgressArgs : EventArgs
|
||||
{
|
||||
public long Position { get; set; }
|
||||
public long Length { get; set; }
|
||||
}
|
||||
|
||||
public class ArchiveEntryExtractionProgressArgs : EventArgs
|
||||
{
|
||||
public IReader Reader { get; set; }
|
||||
public TrackableStream Stream { get; set; }
|
||||
public ReaderProgress Progress { get; set; }
|
||||
public IEntry Entry { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
using LANCommander.SDK.Extensions;
|
||||
using LANCommander.SDK.Helpers;
|
||||
using LANCommander.SDK.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
public class GameManager
|
||||
{
|
||||
private static readonly ILogger Logger;
|
||||
private Client Client { get; set; }
|
||||
private string DefaultInstallDirectory { get; set; }
|
||||
|
||||
public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e);
|
||||
public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress;
|
||||
|
||||
public delegate void OnArchiveExtractionProgressHandler(long position, long length);
|
||||
public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress;
|
||||
|
||||
public GameManager(Client client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var game = Client.GetGame(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);
|
||||
});
|
||||
|
||||
if (!result.Success && !result.Canceled)
|
||||
throw new Exception("Could not extract the installer. Retry the install or check your connection");
|
||||
else if (result.Canceled)
|
||||
throw new Exception("Game install was canceled");
|
||||
|
||||
GameManifest manifest = null;
|
||||
|
||||
game.InstallDirectory = result.Directory;
|
||||
|
||||
var writeManifestSuccess = RetryHelper.RetryOnException(maxAttempts, TimeSpan.FromSeconds(1), false, () =>
|
||||
{
|
||||
Logger.LogTrace("Attempting to get game manifest");
|
||||
|
||||
manifest = Client.GetGameManifest(game.Id);
|
||||
|
||||
ManifestHelper.Write(manifest, game.InstallDirectory);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!writeManifestSuccess)
|
||||
throw new Exception("Could not grab the manifest file. Retry the install or check your connection");
|
||||
|
||||
Logger.LogTrace("Saving scripts");
|
||||
|
||||
ScriptHelper.SaveScript(game, ScriptType.Install);
|
||||
ScriptHelper.SaveScript(game, ScriptType.Uninstall);
|
||||
ScriptHelper.SaveScript(game, ScriptType.NameChange);
|
||||
ScriptHelper.SaveScript(game, ScriptType.KeyChange);
|
||||
|
||||
try
|
||||
{
|
||||
PowerShellRuntime.RunScript(game, ScriptType.Install);
|
||||
PowerShellRuntime.RunScript(game, ScriptType.NameChange, /* Plugin.Settings.PlayerName */ "");
|
||||
|
||||
var key = Client.GetAllocatedKey(game.Id);
|
||||
|
||||
PowerShellRuntime.RunScript(game, ScriptType.KeyChange, $"\"{key}\"");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Could not execute post-install scripts");
|
||||
}
|
||||
|
||||
return result.Directory;
|
||||
}
|
||||
|
||||
public void Uninstall(string installDirectory)
|
||||
{
|
||||
var manifest = ManifestHelper.Read(installDirectory);
|
||||
|
||||
try
|
||||
{
|
||||
Logger.LogTrace("Running uninstall script");
|
||||
PowerShellRuntime.RunScript(installDirectory, ScriptType.Uninstall);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error running uninstall script");
|
||||
}
|
||||
|
||||
Logger.LogTrace("Attempting to delete the install directory");
|
||||
|
||||
if (Directory.Exists(installDirectory))
|
||||
Directory.Delete(installDirectory, true);
|
||||
|
||||
Logger.LogTrace("Deleted install directory {InstallDirectory}", installDirectory);
|
||||
}
|
||||
|
||||
private ExtractionResult DownloadAndExtract(Game game, string installDirectory = "")
|
||||
{
|
||||
if (game == null)
|
||||
{
|
||||
Logger.LogTrace("Game failed to download, no game was specified");
|
||||
|
||||
throw new ArgumentNullException("No game was specified");
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(installDirectory))
|
||||
installDirectory = DefaultInstallDirectory;
|
||||
|
||||
var destination = Path.Combine(installDirectory, game.Title.SanitizeFilename());
|
||||
|
||||
Logger.LogTrace("Downloading and extracting {Game} to path {Destination}", game.Title, destination);
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(destination);
|
||||
|
||||
using (var gameStream = Client.StreamGame(game.Id))
|
||||
using (var reader = ReaderFactory.Open(gameStream))
|
||||
{
|
||||
gameStream.OnProgress += (pos, len) =>
|
||||
{
|
||||
OnArchiveExtractionProgress?.Invoke(pos, len);
|
||||
};
|
||||
|
||||
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
|
||||
{
|
||||
OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs
|
||||
{
|
||||
Entry = e.Item,
|
||||
Progress = e.ReaderProgress,
|
||||
Reader = reader,
|
||||
Stream = gameStream
|
||||
});
|
||||
};
|
||||
|
||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (false)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError(ex, "Could not extract to path {Destination}", destination);
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger.LogTrace("Cleaning up orphaned install files after bad install");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
}
|
||||
|
||||
throw new Exception("The game archive could not be extracted, is it corrupted? Please try again");
|
||||
}
|
||||
}
|
||||
|
||||
var extractionResult = new ExtractionResult
|
||||
{
|
||||
Canceled = false,
|
||||
};
|
||||
|
||||
if (!extractionResult.Canceled)
|
||||
{
|
||||
extractionResult.Success = true;
|
||||
extractionResult.Directory = destination;
|
||||
|
||||
Logger.LogTrace("Game {Game} successfully downloaded and extracted to {Destination}", game.Title, destination);
|
||||
}
|
||||
|
||||
return extractionResult;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace LANCommander.SDK.Helpers
|
||||
{
|
||||
public static class ManifestHelper
|
||||
{
|
||||
public static readonly ILogger Logger;
|
||||
|
||||
public const string ManifestFilename = "_manifest.yml";
|
||||
|
||||
public static GameManifest Read(string installDirectory)
|
||||
{
|
||||
var source = Path.Combine(installDirectory, ManifestFilename);
|
||||
var yaml = File.ReadAllText(source);
|
||||
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
|
||||
Logger.LogTrace("Deserializing manifest");
|
||||
|
||||
var manifest = deserializer.Deserialize<GameManifest>(source);
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
public static void Write(GameManifest manifest, string installDirectory)
|
||||
{
|
||||
var destination = Path.Combine(installDirectory, ManifestFilename);
|
||||
|
||||
Logger.LogTrace("Attempting to write manifest to path {Destination}", destination);
|
||||
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
|
||||
Logger.LogTrace("Serializing manifest");
|
||||
|
||||
var yaml = serializer.Serialize(manifest);
|
||||
|
||||
Logger.LogTrace("Writing manifest file");
|
||||
|
||||
File.WriteAllText(destination, yaml);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
using LANCommander.SDK.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK.Helpers
|
||||
{
|
||||
public static class ScriptHelper
|
||||
{
|
||||
public static readonly ILogger Logger;
|
||||
|
||||
public static string SaveTempScript(Script script)
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
|
||||
// PowerShell will only run scripts with the .ps1 file extension
|
||||
File.Move(tempPath, tempPath + ".ps1");
|
||||
|
||||
Logger.LogTrace("Writing script {Script} to {Destination}", script.Name, tempPath);
|
||||
|
||||
File.WriteAllText(tempPath, script.Contents);
|
||||
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
public static void SaveScript(Game game, ScriptType type)
|
||||
{
|
||||
var script = game.Scripts.FirstOrDefault(s => s.Type == type);
|
||||
|
||||
if (script == null)
|
||||
return;
|
||||
|
||||
if (script.RequiresAdmin)
|
||||
script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents;
|
||||
|
||||
var filename = PowerShellRuntime.GetScriptFilePath(game, type);
|
||||
|
||||
if (File.Exists(filename))
|
||||
File.Delete(filename);
|
||||
|
||||
Logger.LogTrace("Writing {ScriptType} script to {Destination}", type, filename);
|
||||
|
||||
File.WriteAllText(filename, script.Contents);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,405 +0,0 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
using LANCommander.SDK.Extensions;
|
||||
using LANCommander.SDK.Helpers;
|
||||
using LANCommander.SDK.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
public class ArchiveExtractionProgressArgs : EventArgs
|
||||
{
|
||||
public long Position { get; set; }
|
||||
public long Length { get; set; }
|
||||
}
|
||||
|
||||
public class ArchiveEntryExtractionProgressArgs : EventArgs
|
||||
{
|
||||
public IReader Reader { get; set; }
|
||||
public TrackableStream Stream { get; set; }
|
||||
public ReaderProgress Progress { get; set; }
|
||||
public IEntry Entry { get; set; }
|
||||
}
|
||||
|
||||
public class LANCommander
|
||||
{
|
||||
public static readonly ILogger Logger;
|
||||
|
||||
private const string ManifestFilename = "_manifest.yml";
|
||||
|
||||
private string DefaultInstallDirectory { get; set; }
|
||||
public Client Client { get; set; }
|
||||
private PowerShellRuntime PowerShellRuntime;
|
||||
|
||||
public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e);
|
||||
public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress;
|
||||
|
||||
public delegate void OnArchiveExtractionProgressHandler(long position, long length);
|
||||
public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress;
|
||||
|
||||
public LANCommander(string baseUrl)
|
||||
{
|
||||
Client = new Client(baseUrl);
|
||||
}
|
||||
|
||||
/// <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 InstallGame(Guid gameId, int maxAttempts = 10)
|
||||
{
|
||||
var game = Client.GetGame(gameId);
|
||||
|
||||
Logger.LogTrace("Installing game {GameTitle} (GameId)", game.Title, game.Id);
|
||||
|
||||
var result = RetryHelper.RetryOnException<ExtractionResult>(maxAttempts, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () =>
|
||||
{
|
||||
Logger.LogTrace("Attempting to download and extract game");
|
||||
|
||||
return DownloadAndExtractGame(game);
|
||||
});
|
||||
|
||||
if (!result.Success && !result.Canceled)
|
||||
throw new Exception("Could not extract the installer. Retry the install or check your connection");
|
||||
else if (result.Canceled)
|
||||
throw new Exception("Game install was canceled");
|
||||
|
||||
GameManifest manifest = null;
|
||||
|
||||
game.InstallDirectory = result.Directory;
|
||||
|
||||
var writeManifestSuccess = RetryHelper.RetryOnException(maxAttempts, TimeSpan.FromSeconds(1), false, () =>
|
||||
{
|
||||
Logger.LogTrace("Attempting to get game manifest");
|
||||
|
||||
manifest = Client.GetGameManifest(game.Id);
|
||||
|
||||
WriteManifest(manifest, game.InstallDirectory);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!writeManifestSuccess)
|
||||
throw new Exception("Could not grab the manifest file. Retry the install or check your connection");
|
||||
|
||||
Logger.LogTrace("Saving scripts");
|
||||
|
||||
SaveScript(game, ScriptType.Install);
|
||||
SaveScript(game, ScriptType.Uninstall);
|
||||
SaveScript(game, ScriptType.NameChange);
|
||||
SaveScript(game, ScriptType.KeyChange);
|
||||
|
||||
if (game.Redistributables != null && game.Redistributables.Count() > 0)
|
||||
{
|
||||
Logger.LogTrace("Installing required redistributables");
|
||||
InstallRedistributables(game);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
PowerShellRuntime.RunScript(game, ScriptType.Install);
|
||||
PowerShellRuntime.RunScript(game, ScriptType.NameChange, /* Plugin.Settings.PlayerName */ "");
|
||||
|
||||
var key = Client.GetAllocatedKey(game.Id);
|
||||
|
||||
PowerShellRuntime.RunScript(game, ScriptType.KeyChange, $"\"{key}\"");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Could not execute post-install scripts");
|
||||
}
|
||||
|
||||
// Plugin.UpdateGame(manifest, gameId)
|
||||
|
||||
// Plugin.DownloadCache.Remove(gameId);
|
||||
|
||||
return result.Directory;
|
||||
}
|
||||
|
||||
private ExtractionResult DownloadAndExtractGame(Game game, string installDirectory = "")
|
||||
{
|
||||
if (game == null)
|
||||
{
|
||||
Logger.LogTrace("Game failed to download, no game was specified");
|
||||
|
||||
throw new ArgumentNullException("No game was specified");
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(installDirectory))
|
||||
installDirectory = DefaultInstallDirectory;
|
||||
|
||||
var destination = Path.Combine(installDirectory, game.Title.SanitizeFilename());
|
||||
|
||||
Logger.LogTrace("Downloading and extracting {Game} to path {Destination}", game.Title, destination);
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(destination);
|
||||
|
||||
using (var gameStream = Client.StreamGame(game.Id))
|
||||
using (var reader = ReaderFactory.Open(gameStream))
|
||||
{
|
||||
gameStream.OnProgress += (pos, len) =>
|
||||
{
|
||||
OnArchiveExtractionProgress?.Invoke(pos, len);
|
||||
};
|
||||
|
||||
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
|
||||
{
|
||||
OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs
|
||||
{
|
||||
Entry = e.Item,
|
||||
Progress = e.ReaderProgress,
|
||||
Reader = reader,
|
||||
Stream = gameStream
|
||||
});
|
||||
};
|
||||
|
||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (false)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError(ex, "Could not extract to path {Destination}", destination);
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger.LogTrace("Cleaning up orphaned install files after bad install");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
}
|
||||
|
||||
throw new Exception("The game archive could not be extracted, is it corrupted? Please try again");
|
||||
}
|
||||
}
|
||||
|
||||
var extractionResult = new ExtractionResult
|
||||
{
|
||||
Canceled = false,
|
||||
};
|
||||
|
||||
if (!extractionResult.Canceled)
|
||||
{
|
||||
extractionResult.Success = true;
|
||||
extractionResult.Directory = destination;
|
||||
|
||||
Logger.LogTrace("Game {Game} successfully downloaded and extracted to {Destination}", game.Title, destination);
|
||||
}
|
||||
|
||||
return extractionResult;
|
||||
}
|
||||
|
||||
private void InstallRedistributables(Game game)
|
||||
{
|
||||
foreach (var redistributable in game.Redistributables)
|
||||
{
|
||||
InstallRedistributable(redistributable);
|
||||
}
|
||||
}
|
||||
|
||||
private void InstallRedistributable(Redistributable redistributable)
|
||||
{
|
||||
string installScriptTempFile = null;
|
||||
string detectionScriptTempFile = null;
|
||||
string extractTempPath = null;
|
||||
|
||||
try
|
||||
{
|
||||
var installScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.Install);
|
||||
installScriptTempFile = SaveTempScript(installScript);
|
||||
|
||||
var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall);
|
||||
detectionScriptTempFile = SaveTempScript(detectionScript);
|
||||
|
||||
var detectionResult = PowerShellRuntime.RunScript(detectionScriptTempFile, detectionScript.RequiresAdmin);
|
||||
|
||||
// Redistributable is not installed
|
||||
if (detectionResult == 0)
|
||||
{
|
||||
if (redistributable.Archives.Count() > 0)
|
||||
{
|
||||
var extractionResult = DownloadAndExtractRedistributable(redistributable);
|
||||
|
||||
if (extractionResult.Success)
|
||||
{
|
||||
extractTempPath = extractionResult.Directory;
|
||||
|
||||
PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Redistributable {Redistributable} failed to install", redistributable.Name);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(installScriptTempFile))
|
||||
File.Delete(installScriptTempFile);
|
||||
|
||||
if (File.Exists(detectionScriptTempFile))
|
||||
File.Delete(detectionScriptTempFile);
|
||||
|
||||
if (Directory.Exists(extractTempPath))
|
||||
Directory.Delete(extractTempPath);
|
||||
}
|
||||
}
|
||||
|
||||
private ExtractionResult DownloadAndExtractRedistributable(Redistributable redistributable)
|
||||
{
|
||||
if (redistributable == null)
|
||||
{
|
||||
Logger.LogTrace("Redistributable failed to download! No redistributable was specified");
|
||||
throw new ArgumentNullException("No redistributable was specified");
|
||||
}
|
||||
|
||||
var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename());
|
||||
|
||||
Logger.LogTrace("Downloading and extracting {Redistributable} to path {Destination}", redistributable.Name, destination);
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(destination);
|
||||
|
||||
using (var redistributableStream = Client.StreamRedistributable(redistributable.Id))
|
||||
using (var reader = ReaderFactory.Open(redistributableStream))
|
||||
{
|
||||
redistributableStream.OnProgress += (pos, len) =>
|
||||
{
|
||||
OnArchiveExtractionProgress?.Invoke(pos, len);
|
||||
};
|
||||
|
||||
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
|
||||
{
|
||||
OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs
|
||||
{
|
||||
Entry = e.Item,
|
||||
Progress = e.ReaderProgress,
|
||||
Reader = reader,
|
||||
Stream = redistributableStream
|
||||
});
|
||||
};
|
||||
|
||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Could not extract to path {Destination}", destination);
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger.LogTrace("Cleaning up orphaned files after bad install");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
}
|
||||
|
||||
throw new Exception("The redistributable archive could not be extracted, is it corrupted? Please try again");
|
||||
}
|
||||
|
||||
var extractionResult = new ExtractionResult
|
||||
{
|
||||
Canceled = false
|
||||
};
|
||||
|
||||
if (!extractionResult.Canceled)
|
||||
{
|
||||
extractionResult.Success = true;
|
||||
extractionResult.Directory = destination;
|
||||
Logger.LogTrace("Redistributable {Redistributable} successfully downloaded and extracted to {Destination}", redistributable.Name, destination);
|
||||
}
|
||||
|
||||
return extractionResult;
|
||||
}
|
||||
|
||||
public void WriteManifest(GameManifest manifest, string installDirectory)
|
||||
{
|
||||
var destination = Path.Combine(installDirectory, ManifestFilename);
|
||||
|
||||
Logger.LogTrace("Attempting to write manifest to path {Destination}", destination);
|
||||
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
|
||||
Logger.LogTrace("Serializing manifest");
|
||||
|
||||
var yaml = serializer.Serialize(manifest);
|
||||
|
||||
Logger.LogTrace("Writing manifest file");
|
||||
|
||||
File.WriteAllText(destination, yaml);
|
||||
}
|
||||
|
||||
private string SaveTempScript(Script script)
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
|
||||
// PowerShell will only run scripts with the .ps1 file extension
|
||||
File.Move(tempPath, tempPath + ".ps1");
|
||||
|
||||
Logger.LogTrace("Writing script {Script} to {Destination}", script.Name, tempPath);
|
||||
|
||||
File.WriteAllText(tempPath, script.Contents);
|
||||
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
private void SaveScript(Game game, ScriptType type)
|
||||
{
|
||||
var script = game.Scripts.FirstOrDefault(s => s.Type == type);
|
||||
|
||||
if (script == null)
|
||||
return;
|
||||
|
||||
if (script.RequiresAdmin)
|
||||
script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents;
|
||||
|
||||
var filename = PowerShellRuntime.GetScriptFilePath(game, type);
|
||||
|
||||
if (File.Exists(filename))
|
||||
File.Delete(filename);
|
||||
|
||||
Logger.LogTrace("Writing {ScriptType} script to {Destination}", type, filename);
|
||||
|
||||
File.WriteAllText(filename, script.Contents);
|
||||
}
|
||||
|
||||
public void ChangeAlias(string alias)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
using LANCommander.SDK.Enums;
|
||||
using LANCommander.SDK.Extensions;
|
||||
using LANCommander.SDK.Helpers;
|
||||
using LANCommander.SDK.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LANCommander.SDK
|
||||
{
|
||||
public class RedistributableManager
|
||||
{
|
||||
private static readonly ILogger Logger;
|
||||
private Client Client { get; set; }
|
||||
|
||||
public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e);
|
||||
public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress;
|
||||
|
||||
public delegate void OnArchiveExtractionProgressHandler(long position, long length);
|
||||
public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress;
|
||||
|
||||
public RedistributableManager(Client client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
|
||||
public void Install(Game game)
|
||||
{
|
||||
foreach (var redistributable in game.Redistributables)
|
||||
{
|
||||
Install(redistributable);
|
||||
}
|
||||
}
|
||||
|
||||
public void Install(Redistributable redistributable)
|
||||
{
|
||||
string installScriptTempFile = null;
|
||||
string detectionScriptTempFile = null;
|
||||
string extractTempPath = null;
|
||||
|
||||
try
|
||||
{
|
||||
var installScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.Install);
|
||||
installScriptTempFile = ScriptHelper.SaveTempScript(installScript);
|
||||
|
||||
var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall);
|
||||
detectionScriptTempFile = ScriptHelper.SaveTempScript(detectionScript);
|
||||
|
||||
var detectionResult = PowerShellRuntime.RunScript(detectionScriptTempFile, detectionScript.RequiresAdmin);
|
||||
|
||||
// Redistributable is not installed
|
||||
if (detectionResult == 0)
|
||||
{
|
||||
if (redistributable.Archives.Count() > 0)
|
||||
{
|
||||
var extractionResult = DownloadAndExtract(redistributable);
|
||||
|
||||
if (extractionResult.Success)
|
||||
{
|
||||
extractTempPath = extractionResult.Directory;
|
||||
|
||||
PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Redistributable {Redistributable} failed to install", redistributable.Name);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(installScriptTempFile))
|
||||
File.Delete(installScriptTempFile);
|
||||
|
||||
if (File.Exists(detectionScriptTempFile))
|
||||
File.Delete(detectionScriptTempFile);
|
||||
|
||||
if (Directory.Exists(extractTempPath))
|
||||
Directory.Delete(extractTempPath);
|
||||
}
|
||||
}
|
||||
|
||||
private ExtractionResult DownloadAndExtract(Redistributable redistributable)
|
||||
{
|
||||
if (redistributable == null)
|
||||
{
|
||||
Logger.LogTrace("Redistributable failed to download! No redistributable was specified");
|
||||
throw new ArgumentNullException("No redistributable was specified");
|
||||
}
|
||||
|
||||
var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename());
|
||||
|
||||
Logger.LogTrace("Downloading and extracting {Redistributable} to path {Destination}", redistributable.Name, destination);
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(destination);
|
||||
|
||||
using (var redistributableStream = Client.StreamRedistributable(redistributable.Id))
|
||||
using (var reader = ReaderFactory.Open(redistributableStream))
|
||||
{
|
||||
redistributableStream.OnProgress += (pos, len) =>
|
||||
{
|
||||
OnArchiveExtractionProgress?.Invoke(pos, len);
|
||||
};
|
||||
|
||||
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
|
||||
{
|
||||
OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs
|
||||
{
|
||||
Entry = e.Item,
|
||||
Progress = e.ReaderProgress,
|
||||
Reader = reader,
|
||||
Stream = redistributableStream
|
||||
});
|
||||
};
|
||||
|
||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Could not extract to path {Destination}", destination);
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger.LogTrace("Cleaning up orphaned files after bad install");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
}
|
||||
|
||||
throw new Exception("The redistributable archive could not be extracted, is it corrupted? Please try again");
|
||||
}
|
||||
|
||||
var extractionResult = new ExtractionResult
|
||||
{
|
||||
Canceled = false
|
||||
};
|
||||
|
||||
if (!extractionResult.Canceled)
|
||||
{
|
||||
extractionResult.Success = true;
|
||||
extractionResult.Directory = destination;
|
||||
Logger.LogTrace("Redistributable {Redistributable} successfully downloaded and extracted to {Destination}", redistributable.Name, destination);
|
||||
}
|
||||
|
||||
return extractionResult;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue