-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e52eee1
commit bdccad6
Showing
14 changed files
with
992 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
using System.IO.Compression; | ||
using LauncherGamePlugin; | ||
using LauncherGamePlugin.Interfaces; | ||
using RemoteDownloaderPlugin.Utils; | ||
|
||
namespace RemoteDownloaderPlugin.Game; | ||
|
||
public class GameDownload : ProgressStatus | ||
{ | ||
private IEntry _entry; | ||
private int _lastSecond = 0; | ||
private readonly CancellationTokenSource _cts = new(); | ||
private bool _doneDownloading = false; | ||
public long TotalSize { get; private set; } | ||
public string Version { get; private set; } | ||
public GameType Type { get; private set; } | ||
public string BaseFileName { get; private set; } | ||
|
||
public GameDownload(IEntry entry) | ||
{ | ||
_entry = entry; | ||
} | ||
|
||
private void OnProgressUpdate(object? obj, float progress) | ||
{ | ||
if (_doneDownloading || _lastSecond == DateTime.Now.Second) // Only make the UI respond once a second | ||
return; | ||
|
||
_lastSecond = DateTime.Now.Second; | ||
|
||
progress *= 100; | ||
Line1 = $"Downloading: {progress:0}%"; | ||
Percentage = progress; | ||
InvokeOnUpdate(); | ||
} | ||
|
||
public async Task Download(IApp app) | ||
{ | ||
_doneDownloading = false; | ||
|
||
if (_entry is EmuEntry emuEntry) | ||
await DownloadEmu(app, emuEntry); | ||
else if (_entry is PcEntry pcEntry) | ||
await DownloadPc(app, pcEntry); | ||
else | ||
throw new Exception("Download failed: Unknown type"); | ||
} | ||
|
||
private async Task DownloadEmu(IApp app, EmuEntry entry) | ||
{ | ||
Type = GameType.Emu; | ||
if (entry.Files.Count(x => x.Type == "base") != 1) | ||
{ | ||
throw new Exception("Multiple base images, impossible download"); | ||
} | ||
|
||
var basePath = Path.Join(app.GameDir, "Remote", entry.Emu); | ||
string baseGamePath = null; | ||
var extraFilesPath = Path.Join(app.GameDir, "Remote", entry.Emu, entry.GameId); | ||
Directory.CreateDirectory(basePath); | ||
Directory.CreateDirectory(extraFilesPath); | ||
|
||
using HttpClient client = new(); | ||
|
||
for (int i = 0; i < entry.Files.Count; i++) | ||
{ | ||
Progress<float> localProcess = new(); | ||
localProcess.ProgressChanged += (sender, f) => | ||
{ | ||
var part = (float)1 / entry.Files.Count; | ||
var add = (float)i / entry.Files.Count; | ||
OnProgressUpdate(null, part * f + add); | ||
}; | ||
|
||
var fileEntry = entry.Files[i]; | ||
var destPath = Path.Join(fileEntry.Type == "base" ? basePath : extraFilesPath, fileEntry.Name); | ||
|
||
if (fileEntry.Type == "base") | ||
{ | ||
baseGamePath = destPath; | ||
BaseFileName = fileEntry.Name; | ||
} | ||
|
||
var fs = new FileStream(destPath, FileMode.Create); | ||
|
||
try | ||
{ | ||
await client.DownloadAsync(fileEntry.Url, fs, localProcess, _cts.Token); | ||
} | ||
catch (TaskCanceledException e) | ||
{ | ||
await Task.Run(() => fs.Dispose()); | ||
|
||
if (baseGamePath != null && File.Exists(baseGamePath)) | ||
{ | ||
File.Delete(baseGamePath); | ||
} | ||
|
||
Directory.Delete(extraFilesPath, true); | ||
|
||
throw; | ||
} | ||
|
||
Line1 = "Saving file..."; | ||
InvokeOnUpdate(); | ||
await Task.Run(() => fs.Dispose()); | ||
} | ||
|
||
TotalSize = (await Task.Run(() => LauncherGamePlugin.Utils.DirSize(new(extraFilesPath)))) + (new FileInfo(baseGamePath!)).Length; | ||
Version = entry.Files.Last(x => x.Type is "base" or "update").Version; | ||
} | ||
|
||
private async Task DownloadPc(IApp app, PcEntry entry) | ||
{ | ||
Type = GameType.Pc; | ||
var basePath = Path.Join(app.GameDir, "Remote", "Pc", entry.GameId); | ||
Directory.CreateDirectory(basePath); | ||
var zipFilePath = Path.Join(basePath, "__game__.zip"); | ||
|
||
using HttpClient client = new(); | ||
var fs = new FileStream(zipFilePath, FileMode.Create); | ||
|
||
Progress<float> progress = new(); | ||
progress.ProgressChanged += OnProgressUpdate; | ||
|
||
try | ||
{ | ||
await client.DownloadAsync(entry.Url, fs, progress, _cts.Token); | ||
} | ||
catch (TaskCanceledException e) | ||
{ | ||
await Task.Run(() => fs.Dispose()); | ||
|
||
Directory.Delete(basePath); | ||
|
||
throw; | ||
} | ||
|
||
|
||
Percentage = 100; | ||
Line1 = "Saving..."; | ||
InvokeOnUpdate(); | ||
await Task.Run(() => fs.Dispose()); | ||
Line1 = "Unzipping..."; | ||
InvokeOnUpdate(); | ||
await Task.Run(() => ZipFile.ExtractToDirectory(zipFilePath, basePath)); | ||
File.Delete(zipFilePath); | ||
|
||
if (_cts.IsCancellationRequested) | ||
{ | ||
Directory.Delete(basePath); | ||
} | ||
|
||
TotalSize = await Task.Run(() => LauncherGamePlugin.Utils.DirSize(new(basePath))); | ||
Version = entry.Version; | ||
} | ||
|
||
public void Stop() | ||
{ | ||
if (_doneDownloading) | ||
return; | ||
|
||
_cts.Cancel(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
using LauncherGamePlugin; | ||
using LauncherGamePlugin.Enums; | ||
using LauncherGamePlugin.Forms; | ||
using LauncherGamePlugin.Interfaces; | ||
using LauncherGamePlugin.Launcher; | ||
|
||
namespace RemoteDownloaderPlugin.Game; | ||
|
||
public class InstalledGame : IGame | ||
{ | ||
public string InternalName => Game.Id; | ||
public string Name => Game.Name; | ||
public bool IsRunning { get; set; } = false; | ||
public IGameSource Source => _plugin; | ||
public long? Size => Game.GameSize; | ||
public bool HasImage(ImageType type) | ||
=> ImageTypeToUri(type) != null; | ||
|
||
public Task<byte[]?> GetImage(ImageType type) | ||
{ | ||
Uri? url = ImageTypeToUri(type); | ||
|
||
if (url == null) | ||
return Task.FromResult<byte[]?>(null); | ||
|
||
return Storage.Cache($"{Game.Id}_{type}", () => Storage.ImageDownload(url)); | ||
} | ||
|
||
public InstalledStatus InstalledStatus => InstalledStatus.Installed; | ||
|
||
public Platform EstimatedGamePlatform => (_type == GameType.Emu) | ||
? LauncherGamePlugin.Utils.GuessPlatformBasedOnString(_plugin.Storage.Data.EmuProfiles.FirstOrDefault(x => x.Platform == _emuGame!.Emu)?.ExecPath) | ||
: LauncherGamePlugin.Utils.GuessPlatformBasedOnString(_pcLaunchDetails!.LaunchExec); | ||
|
||
public ProgressStatus? ProgressStatus => null; | ||
public event Action? OnUpdate; | ||
|
||
public void InvokeOnUpdate() | ||
=> OnUpdate?.Invoke(); | ||
|
||
public IInstalledGame Game { get; } | ||
private Plugin _plugin; | ||
private PcLaunchDetails? _pcLaunchDetails; | ||
private InstalledEmuGame? _emuGame; | ||
private GameType _type; | ||
|
||
public InstalledGame(IInstalledGame game, Plugin plugin) | ||
{ | ||
Game = game; | ||
_plugin = plugin; | ||
_type = game is InstalledEmuGame ? GameType.Emu : GameType.Pc; | ||
_pcLaunchDetails = null; | ||
|
||
if (_type == GameType.Pc) | ||
{ | ||
var fullPath = Path.Join(plugin.App.GameDir, "Remote", "Pc", Game.Id, "game.json"); | ||
_pcLaunchDetails = PcLaunchDetails.GetFromPath(fullPath); | ||
} | ||
else | ||
{ | ||
_emuGame = (game as InstalledEmuGame)!; | ||
} | ||
} | ||
|
||
public void Play() | ||
{ | ||
try | ||
{ | ||
if (_type == GameType.Emu) | ||
{ | ||
var emuProfile = _plugin.Storage.Data.EmuProfiles.FirstOrDefault(x => x.Platform == _emuGame!.Emu); | ||
|
||
if (emuProfile == null) | ||
{ | ||
throw new Exception($"No '{_emuGame!.Emu}' emulation profile exists"); | ||
} | ||
|
||
var baseGamePath = Path.Join(_plugin.App.GameDir, "Remote", _emuGame!.Emu, _emuGame.BaseFilename); | ||
|
||
LaunchParams args = new(emuProfile.ExecPath, | ||
emuProfile.CliArgs.Replace("{EXEC}", $"\"{baseGamePath}\""), emuProfile.WorkingDirectory, this, | ||
EstimatedGamePlatform); | ||
_plugin.App.Launch(args); | ||
} | ||
else | ||
{ | ||
var execPath = Path.Join(_plugin.App.GameDir, "Remote", "Pc", Game.Id, _pcLaunchDetails!.LaunchExec); | ||
var workingDir = Path.Join(_plugin.App.GameDir, "Remote", "Pc", Game.Id, _pcLaunchDetails!.WorkingDir); | ||
LaunchParams args = new(execPath, _pcLaunchDetails.LaunchArgs, Path.GetDirectoryName(workingDir)!, this, | ||
EstimatedGamePlatform); | ||
_plugin.App.Launch(args); | ||
} | ||
} | ||
catch (Exception e) | ||
{ | ||
_plugin.App.ShowDismissibleTextPrompt($"Game Failed to launch: {e.Message}"); | ||
} | ||
} | ||
|
||
public void Delete() | ||
{ | ||
if (_type == GameType.Emu) | ||
{ | ||
var baseGamePath = Path.Join(_plugin.App.GameDir, "Remote", _emuGame!.Emu, _emuGame.BaseFilename); | ||
var extraDir = Path.Join(_plugin.App.GameDir, "Remote", _emuGame!.Emu, Game.Id); | ||
var success = false; | ||
|
||
try | ||
{ | ||
File.Delete(baseGamePath); | ||
Directory.Delete(extraDir, true); | ||
success = true; | ||
} | ||
catch {} | ||
|
||
var game = _plugin.Storage.Data.EmuGames.Find(x => x.Id == Game.Id); | ||
_plugin.Storage.Data.EmuGames.Remove(game!); | ||
|
||
if (!success) | ||
{ | ||
_plugin.App.ShowTextPrompt("Failed to delete files. Game has been unlinked"); | ||
} | ||
} | ||
else | ||
{ | ||
var path = Path.Join(_plugin.App.GameDir, "Remote", "Pc", Game.Id); | ||
var success = false; | ||
|
||
try | ||
{ | ||
Directory.Delete(path, true); | ||
success = true; | ||
} | ||
catch {} | ||
|
||
var game = _plugin.Storage.Data.PcGames.Find(x => x.Id == Game.Id); | ||
_plugin.Storage.Data.PcGames.Remove(game!); | ||
|
||
if (!success) | ||
{ | ||
_plugin.App.ShowTextPrompt("Failed to delete files. Game has been unlinked"); | ||
} | ||
} | ||
|
||
_plugin.App.ReloadGames(); | ||
_plugin.Storage.Save(); | ||
} | ||
|
||
public void OpenInFileManager() | ||
{ | ||
if (_type == GameType.Emu) | ||
{ | ||
LauncherGamePlugin.Utils.OpenFolderWithHighlightedFile(Path.Join(_plugin.App.GameDir, "Remote", _emuGame!.Emu, _emuGame.BaseFilename)); | ||
} | ||
else | ||
{ | ||
LauncherGamePlugin.Utils.OpenFolder(Path.Join(_plugin.App.GameDir, "Remote", "Pc", Game.Id)); | ||
} | ||
} | ||
|
||
private Uri? ImageTypeToUri(ImageType type) | ||
=> type switch | ||
{ | ||
ImageType.Background => Game.Images.Background, | ||
ImageType.VerticalCover => Game.Images.VerticalCover, | ||
ImageType.HorizontalCover => Game.Images.HorizontalCover, | ||
ImageType.Logo => Game.Images.Logo, | ||
ImageType.Icon => Game.Images.Icon, | ||
_ => null | ||
}; | ||
} |
Oops, something went wrong.