diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..bd149f0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "SteamStorefrontAPI"] + path = SteamStorefrontAPI + url = https://git.jeddunk.xyz/jeddunk/SteamStorefrontAPI.git diff --git a/GoldbergGUI.Core/GoldbergGUI.Core.csproj b/GoldbergGUI.Core/GoldbergGUI.Core.csproj index 79fab83..72f32a9 100644 --- a/GoldbergGUI.Core/GoldbergGUI.Core.csproj +++ b/GoldbergGUI.Core/GoldbergGUI.Core.csproj @@ -1,27 +1,28 @@  - - netcoreapp3.1 - 0.1.0 - 0.1.0 - Jeddunk - + + net8.0 + 0.3.0 + Jeddunk + AnyCPU;x86;x64 + - - - - - - - + + + + + + + + - - - ..\..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_32\PresentationCore\v4.0_4.0.0.0__31bf3856ad364e35\PresentationCore.dll - - - ..\..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_MSIL\PresentationFramework\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.dll - - + + + ..\..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_32\PresentationCore\v4.0_4.0.0.0__31bf3856ad364e35\PresentationCore.dll + + + ..\..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_MSIL\PresentationFramework\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.dll + + diff --git a/GoldbergGUI.Core/Models/GoldbergModel.cs b/GoldbergGUI.Core/Models/GoldbergModel.cs index 58e0f89..d6a40fe 100644 --- a/GoldbergGUI.Core/Models/GoldbergModel.cs +++ b/GoldbergGUI.Core/Models/GoldbergModel.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable UnusedMember.Global namespace GoldbergGUI.Core.Models { @@ -32,22 +34,22 @@ namespace GoldbergGUI.Core.Models /// /// List of DLC /// - public List DlcList { get; set; } - - public List Depots { get; set; } - + public List DlcList { get; set; } + + public List Depots { get; set; } + public List SubscribedGroups { get; set; } - - public List AppPaths { get; set; } - + + //public List AppPaths { get; set; } + public List Achievements { get; set; } - + public List Items { get; set; } - + public List Leaderboards { get; set; } - + public List Stats { get; set; } - + // Add controller setting here! /// /// Set offline mode. @@ -61,24 +63,28 @@ namespace GoldbergGUI.Core.Models /// Disable overlay (experimental only). /// public bool DisableOverlay { get; set; } - + public GoldbergGlobalConfiguration OverwrittenGlobalConfiguration { get; set; } } - public class Depot + public class DlcApp : SteamApp { + public DlcApp() { } + + public DlcApp(SteamApp steamApp) + { + AppId = steamApp.AppId; + Name = steamApp.Name; + ComparableName = steamApp.ComparableName; + AppType = steamApp.AppType; + LastModified = steamApp.LastModified; + PriceChangeNumber = steamApp.PriceChangeNumber; + } + /// - /// ID of Depot. + /// Path to DLC (relative to Steam API DLL) (optional) /// - public int DepotId { get; set; } - /// - /// Name of Depot. - /// - public string Name { get; set; } - /// - /// Associated DLC App ID, can be null (e.g. if Depot is for base game). - /// - public int DlcAppId { get; set; } + public string AppPath { get; set; } } public class Group @@ -97,51 +103,46 @@ namespace GoldbergGUI.Core.Models public int AppId { get; set; } } - public class AppPath - { - public int AppId { get; set; } - public string Path { get; set; } - } - public class Achievement { /// /// Achievement description. /// [JsonPropertyName("description")] - public string Description { get; set; } + public string Description { get; set; } /// - /// Human readable name, as shown on webpage, game libary, overlay, etc. + /// Human readable name, as shown on webpage, game library, overlay, etc. /// [JsonPropertyName("displayName")] - public string DisplayName { get; set; } + public string DisplayName { get; set; } /// /// Is achievement hidden? 0 = false, else true. /// [JsonPropertyName("hidden")] - public int Hidden { get; set; } - + public int Hidden { get; set; } + /// /// Path to icon when unlocked (colored). /// [JsonPropertyName("icon")] - public string Icon { get; set; } + public string Icon { get; set; } /// /// Path to icon when locked (grayed out). /// + // ReSharper disable once StringLiteralTypo [JsonPropertyName("icongray")] - public string IconGray { get; set; } + public string IconGray { get; set; } /// /// Internal name. /// [JsonPropertyName("name")] - public string Name { get; set; } + public string Name { get; set; } } - + public class Item { [JsonPropertyName("Timestamp")] @@ -200,9 +201,10 @@ namespace GoldbergGUI.Core.Models // [JsonConverter(typeof(FluffyParseStringConverter))] public long DropMaxPerWindow { get; set; } + // ReSharper disable once StringLiteralTypo [JsonPropertyName("workshopid")] // [JsonConverter(typeof(FluffyParseStringConverter))] - public long Workshopid { get; set; } + public long WorkshopId { get; set; } [JsonPropertyName("tw_unique_to_own")] // [JsonConverter(typeof(PurpleParseStringConverter))] diff --git a/GoldbergGUI.Core/Models/SteamAppModel.cs b/GoldbergGUI.Core/Models/SteamAppModel.cs index 6fbf277..4329b75 100644 --- a/GoldbergGUI.Core/Models/SteamAppModel.cs +++ b/GoldbergGUI.Core/Models/SteamAppModel.cs @@ -1,7 +1,6 @@ +using SQLite; using System.Collections.Generic; using System.Text.Json.Serialization; -using System.Text.RegularExpressions; -using GoldbergGUI.Core.Utils; // ReSharper disable UnusedMember.Global // ReSharper disable ClassNeverInstantiated.Global @@ -11,44 +10,41 @@ using GoldbergGUI.Core.Utils; // ReSharper disable InconsistentNaming namespace GoldbergGUI.Core.Models { + [Table("steamapp")] public class SteamApp { - private string _name; - private string _comparableName; - [JsonPropertyName("appid")] public int AppId { get; set; } + [JsonPropertyName("appid")] + [Column("appid")] + [PrimaryKey] + public int AppId { get; set; } /// /// Name of Steam app /// [JsonPropertyName("name")] - public string Name - { - get => _name; - set - { - _name = value; - _comparableName = Regex.Replace(value, Misc.AlphaNumOnlyRegex, "").ToLower(); - } - } - - /// - /// Trimmed and cleaned name of Steam app, used for comparisons. - /// - public bool CompareName(string value) => _comparableName.Equals(value); + [Column("name")] + public string Name { get; set; } + + [Column("comparable_name")] + public string ComparableName { get; set; } /// /// App type (Game, DLC, ...) /// - public AppType type { get; set; } + [Column("type")] + public string AppType { get; set; } public override string ToString() { return $"{AppId}={Name}"; } - [JsonPropertyName("last_modified")] public long LastModified { get; set; } + [JsonPropertyName("last_modified")] + [Ignore] + public long LastModified { get; set; } [JsonPropertyName("price_change_number")] + [Ignore] public long PriceChangeNumber { get; set; } } @@ -76,19 +72,4 @@ namespace GoldbergGUI.Core.Models { [JsonPropertyName("response")] public override AppList AppList { get; set; } } - - public class AppType - { - private AppType(string value) => Value = value; - - public string Value { get; } - - public static AppType Game { get; } = new AppType("game"); - public static AppType DLC { get; } = new AppType("dlc"); - public static AppType Music { get; } = new AppType("music"); - public static AppType Demo { get; } = new AppType("demo"); - public static AppType Ad { get; } = new AppType("advertising"); - public static AppType Mod { get; } = new AppType("mod"); - public static AppType Video { get; } = new AppType("video"); - } } \ No newline at end of file diff --git a/GoldbergGUI.Core/Services/GoldbergService.cs b/GoldbergGUI.Core/Services/GoldbergService.cs index 299ba96..6b7bcfa 100644 --- a/GoldbergGUI.Core/Services/GoldbergService.cs +++ b/GoldbergGUI.Core/Services/GoldbergService.cs @@ -1,3 +1,6 @@ +using GoldbergGUI.Core.Models; +using GoldbergGUI.Core.Utils; +using MvvmCross.Logging; using System; using System.Collections.Generic; using System.IO; @@ -6,9 +9,7 @@ using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; using System.Threading.Tasks; -using GoldbergGUI.Core.Models; -using GoldbergGUI.Core.Utils; -using MvvmCross.Logging; +using System.Windows; namespace GoldbergGUI.Core.Services { @@ -23,13 +24,12 @@ namespace GoldbergGUI.Core.Services public Task GetGlobalSettings(); public Task SetGlobalSettings(GoldbergGlobalConfiguration configuration); public bool GoldbergApplied(string path); - // public Task Download(); - // public Task Extract(string archivePath); public Task GenerateInterfacesFile(string filePath); public List Languages(); } // ReSharper disable once UnusedType.Global + // ReSharper disable once ClassNeverInstantiated.Global public class GoldbergService : IGoldbergService { private IMvxLog _log; @@ -47,7 +47,9 @@ namespace GoldbergGUI.Core.Services private readonly string _accountNamePath = Path.Combine(GlobalSettingsPath, "settings/account_name.txt"); private readonly string _userSteamIdPath = Path.Combine(GlobalSettingsPath, "settings/user_steam_id.txt"); private readonly string _languagePath = Path.Combine(GlobalSettingsPath, "settings/language.txt"); - private readonly string _customBroadcastIpsPath = Path.Combine(GlobalSettingsPath, "settings/custom_broadcasts.txt"); + + private readonly string _customBroadcastIpsPath = + Path.Combine(GlobalSettingsPath, "settings/custom_broadcasts.txt"); // ReSharper disable StringLiteralTypo private readonly List _interfaceNames = new List @@ -85,7 +87,11 @@ namespace GoldbergGUI.Core.Services _log = log; var download = await Download().ConfigureAwait(false); - if (download) await Extract(_goldbergZipPath).ConfigureAwait(false); + if (download) + { + await Extract(_goldbergZipPath).ConfigureAwait(false); + } + return await GetGlobalSettings().ConfigureAwait(false); } @@ -104,13 +110,16 @@ namespace GoldbergGUI.Core.Services !long.TryParse(File.ReadLines(_userSteamIdPath).First().Trim(), out steamId) && steamId < 76561197960265729 && steamId > 76561202255233023) { - _log.Error("Invalid User Steam ID!"); + _log.Error("Invalid User Steam ID! Using default Steam ID..."); + steamId = DefaultSteamId; } + if (File.Exists(_languagePath)) language = File.ReadLines(_languagePath).First().Trim(); if (File.Exists(_customBroadcastIpsPath)) customBroadcastIps.AddRange( File.ReadLines(_customBroadcastIpsPath).Select(line => line.Trim())); }).ConfigureAwait(false); + _log.Info("Got global settings."); return new GoldbergGlobalConfiguration { AccountName = accountName, @@ -142,21 +151,23 @@ namespace GoldbergGUI.Core.Services await File.Create(_accountNamePath).DisposeAsync().ConfigureAwait(false); await File.WriteAllTextAsync(_accountNamePath, DefaultAccountName).ConfigureAwait(false); } + // User SteamID if (userSteamId >= 76561197960265729 && userSteamId <= 76561202255233023) { _log.Info("Setting user Steam ID..."); - if (!File.Exists(_userSteamIdPath)) + if (!File.Exists(_userSteamIdPath)) await File.Create(_userSteamIdPath).DisposeAsync().ConfigureAwait(false); await File.WriteAllTextAsync(_userSteamIdPath, userSteamId.ToString()).ConfigureAwait(false); } else { _log.Info("Invalid user Steam ID! Skipping..."); - if (!File.Exists(_userSteamIdPath)) + if (!File.Exists(_userSteamIdPath)) await File.Create(_userSteamIdPath).DisposeAsync().ConfigureAwait(false); await File.WriteAllTextAsync(_userSteamIdPath, DefaultSteamId.ToString()).ConfigureAwait(false); } + // Language if (!string.IsNullOrEmpty(language)) { @@ -172,11 +183,12 @@ namespace GoldbergGUI.Core.Services await File.Create(_languagePath).DisposeAsync().ConfigureAwait(false); await File.WriteAllTextAsync(_languagePath, DefaultLanguage).ConfigureAwait(false); } + // Custom Broadcast IPs if (customBroadcastIps != null && customBroadcastIps.Count > 0) { _log.Info("Setting custom broadcast IPs..."); - var result = + var result = customBroadcastIps.Aggregate("", (current, address) => $"{current}{address}\n"); if (!File.Exists(_customBroadcastIpsPath)) await File.Create(_customBroadcastIpsPath).DisposeAsync().ConfigureAwait(false); @@ -187,6 +199,7 @@ namespace GoldbergGUI.Core.Services _log.Info("Empty list of custom broadcast IPs! Skipping..."); await Task.Run(() => File.Delete(_customBroadcastIpsPath)).ConfigureAwait(false); } + _log.Info("Setting global configuration finished."); } // If first time, call GenerateInterfaces @@ -195,7 +208,8 @@ namespace GoldbergGUI.Core.Services { _log.Info("Reading configuration..."); var appId = -1; - var dlcList = new List(); + var achievementList = new List(); + var dlcList = new List(); var steamAppidTxt = Path.Combine(path, "steam_appid.txt"); if (File.Exists(steamAppidTxt)) { @@ -208,7 +222,21 @@ namespace GoldbergGUI.Core.Services _log.Info(@"""steam_appid.txt"" missing! Skipping..."); } + var achievementJson = Path.Combine(path, "steam_settings", "achievements.json"); + if (File.Exists(achievementJson)) + { + _log.Info("Getting achievements..."); + var json = await File.ReadAllTextAsync(achievementJson) + .ConfigureAwait(false); + achievementList = System.Text.Json.JsonSerializer.Deserialize>(json); + } + else + { + _log.Info(@"""steam_settings/achievements.json"" missing! Skipping..."); + } + var dlcTxt = Path.Combine(path, "steam_settings", "DLC.txt"); + var appPathTxt = Path.Combine(path, "steam_settings", "app_paths.txt"); if (File.Exists(dlcTxt)) { _log.Info("Getting DLCs..."); @@ -219,12 +247,27 @@ namespace GoldbergGUI.Core.Services { var match = expression.Match(line); if (match.Success) - dlcList.Add(new SteamApp + dlcList.Add(new DlcApp() { AppId = Convert.ToInt32(match.Groups["id"].Value), Name = match.Groups["name"].Value }); } + + // ReSharper disable once InvertIf + if (File.Exists(appPathTxt)) + { + var appPathAllLinesAsync = await File.ReadAllLinesAsync(appPathTxt).ConfigureAwait(false); + var appPathExpression = new Regex(@"(?.*) *= *(?.*)"); + foreach (var line in appPathAllLinesAsync) + { + var match = appPathExpression.Match(line); + if (!match.Success) continue; + var i = dlcList.FindIndex(x => + x.AppId.Equals(Convert.ToInt32(match.Groups["id"].Value))); + dlcList[i].AppPath = match.Groups["appPath"].Value; + } + } } else { @@ -234,6 +277,7 @@ namespace GoldbergGUI.Core.Services return new GoldbergConfiguration { AppId = appId, + Achievements = achievementList, DlcList = dlcList, Offline = File.Exists(Path.Combine(path, "steam_settings", "offline.txt")), DisableNetworking = File.Exists(Path.Combine(path, "steam_settings", "disable_networking.txt")), @@ -261,6 +305,7 @@ namespace GoldbergGUI.Core.Services { CopyDllFiles(path, x64Name); } + _log.Info("DLL setup finished!"); // Create steam_settings folder if missing _log.Info("Saving settings..."); @@ -270,52 +315,138 @@ namespace GoldbergGUI.Core.Services } // create steam_appid.txt - await File.WriteAllTextAsync(Path.Combine(path, "steam_appid.txt"), c.AppId.ToString()).ConfigureAwait(false); + await File.WriteAllTextAsync(Path.Combine(path, "steam_appid.txt"), c.AppId.ToString()) + .ConfigureAwait(false); - // DLC - if (c.DlcList.Count > 0) + // Achievements + Images + if (c.Achievements.Count > 0) { - var dlcString = ""; - c.DlcList.ForEach(x => dlcString += $"{x}\n"); - await File.WriteAllTextAsync(Path.Combine(path, "steam_settings", "DLC.txt"), dlcString) + _log.Info("Downloading images..."); + var imagePath = Path.Combine(path, "steam_settings", "images"); + Directory.CreateDirectory(imagePath); + + foreach (var achievement in c.Achievements) + { + await DownloadImageAsync(imagePath, achievement.Icon); + await DownloadImageAsync(imagePath, achievement.IconGray); + + // Update achievement list to point to local images instead + achievement.Icon = $"images/{Path.GetFileName(achievement.Icon)}"; + achievement.IconGray = $"images/{Path.GetFileName(achievement.IconGray)}"; + } + + _log.Info("Saving achievements..."); + + var achievementJson = System.Text.Json.JsonSerializer.Serialize( + c.Achievements, + new System.Text.Json.JsonSerializerOptions + { + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + WriteIndented = true + }); + await File.WriteAllTextAsync(Path.Combine(path, "steam_settings", "achievements.json"), achievementJson) .ConfigureAwait(false); + + _log.Info("Finished saving achievements."); } else { + _log.Info("No achievements set! Removing achievement files..."); + var imagePath = Path.Combine(path, "steam_settings", "images"); + if (Directory.Exists(imagePath)) + { + Directory.Delete(imagePath); + } + var achievementPath = Path.Combine(path, "steam_settings", "achievements"); + if (File.Exists(achievementPath)) + { + File.Delete(achievementPath); + } + _log.Info("Removed achievement files."); + } + + // DLC + App path + if (c.DlcList.Count > 0) + { + _log.Info("Saving DLC settings..."); + var dlcContent = ""; + //var depotContent = ""; + var appPathContent = ""; + c.DlcList.ForEach(x => + { + dlcContent += $"{x}\n"; + //depotContent += $"{x.DepotId}\n"; + if (!string.IsNullOrEmpty(x.AppPath)) + appPathContent += $"{x.AppId}={x.AppPath}\n"; + }); + await File.WriteAllTextAsync(Path.Combine(path, "steam_settings", "DLC.txt"), dlcContent) + .ConfigureAwait(false); + + /*if (!string.IsNullOrEmpty(depotContent)) + { + await File.WriteAllTextAsync(Path.Combine(path, "steam_settings", "depots.txt"), depotContent) + .ConfigureAwait(false); + }*/ + + + if (!string.IsNullOrEmpty(appPathContent)) + { + await File.WriteAllTextAsync(Path.Combine(path, "steam_settings", "app_paths.txt"), appPathContent) + .ConfigureAwait(false); + } + else + { + if (File.Exists(Path.Combine(path, "steam_settings", "app_paths.txt"))) + File.Delete(Path.Combine(path, "steam_settings", "app_paths.txt")); + } + _log.Info("Saved DLC settings."); + } + else + { + _log.Info("No DLC set! Removing DLC configuration files..."); if (File.Exists(Path.Combine(path, "steam_settings", "DLC.txt"))) File.Delete(Path.Combine(path, "steam_settings", "DLC.txt")); + if (File.Exists(Path.Combine(path, "steam_settings", "app_paths.txt"))) + File.Delete(Path.Combine(path, "steam_settings", "app_paths.txt")); + _log.Info("Removed DLC configuration files."); } // Offline if (c.Offline) { + _log.Info("Create offline.txt"); await File.Create(Path.Combine(path, "steam_settings", "offline.txt")).DisposeAsync() .ConfigureAwait(false); } else { + _log.Info("Delete offline.txt if it exists"); File.Delete(Path.Combine(path, "steam_settings", "offline.txt")); } // Disable Networking if (c.DisableNetworking) { + _log.Info("Create disable_networking.txt"); await File.Create(Path.Combine(path, "steam_settings", "disable_networking.txt")).DisposeAsync() .ConfigureAwait(false); } else { + _log.Info("Delete disable_networking.txt if it exists"); File.Delete(Path.Combine(path, "steam_settings", "disable_networking.txt")); } // Disable Overlay if (c.DisableOverlay) { + _log.Info("Create disable_overlay.txt"); await File.Create(Path.Combine(path, "steam_settings", "disable_overlay.txt")).DisposeAsync() .ConfigureAwait(false); } else { + _log.Info("Delete disable_overlay.txt if it exists"); File.Delete(Path.Combine(path, "steam_settings", "disable_overlay.txt")); } } @@ -324,17 +455,21 @@ namespace GoldbergGUI.Core.Services { var steamApiDll = Path.Combine(path, $"{name}.dll"); var originalDll = Path.Combine(path, $"{name}_o.dll"); - var guiBackup = Path.Combine(path, $"{name}.dll.GOLDBERGGUIBACKUP"); + var guiBackup = Path.Combine(path, $".{name}.dll.GOLDBERGGUIBACKUP"); var goldbergDll = Path.Combine(_goldbergPath, $"{name}.dll"); if (!File.Exists(originalDll)) + { + _log.Info("Back up original Steam API DLL..."); File.Move(steamApiDll, originalDll); + } else { File.Move(steamApiDll, guiBackup, true); File.SetAttributes(guiBackup, FileAttributes.Hidden); } + _log.Info("Copy Goldberg DLL to target path..."); File.Copy(goldbergDll, steamApiDll); } @@ -362,16 +497,24 @@ namespace GoldbergGUI.Core.Services var match = regex.Match(body); if (File.Exists(jobIdPath)) { - _log.Info("Check if update is needed..."); - var jobIdLocal = Convert.ToInt32(File.ReadLines(jobIdPath).First().Trim()); - var jobIdRemote = Convert.ToInt32(match.Groups["jobid"].Value); - _log.Debug($"job_id: local {jobIdLocal}; remote {jobIdRemote}"); - if (jobIdLocal.Equals(jobIdRemote)) + try { - _log.Info("Latest Goldberg emulator is already available! Skipping..."); - return false; + _log.Info("Check if update is needed..."); + var jobIdLocal = Convert.ToInt32(File.ReadLines(jobIdPath).First().Trim()); + var jobIdRemote = Convert.ToInt32(match.Groups["jobid"].Value); + _log.Debug($"job_id: local {jobIdLocal}; remote {jobIdRemote}"); + if (jobIdLocal.Equals(jobIdRemote)) + { + _log.Info("Latest Goldberg emulator is already available! Skipping..."); + return false; + } + } + catch (Exception) + { + _log.Error("An error occured, local Goldberg setup might be broken!"); } } + _log.Info("Starting download..."); await StartDownload(match.Value).ConfigureAwait(false); return true; @@ -379,15 +522,36 @@ namespace GoldbergGUI.Core.Services private async Task StartDownload(string downloadUrl) { - var client = new HttpClient(); - _log.Debug(downloadUrl); - await using var fileStream = File.OpenWrite(_goldbergZipPath); - //client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead) - var task = client.GetFileAsync(downloadUrl, fileStream).ConfigureAwait(false); - await task; - if (task.GetAwaiter().IsCompleted) + try { - _log.Info("Download finished!"); + var client = new HttpClient(); + _log.Debug(downloadUrl); + await using var fileStream = File.OpenWrite(_goldbergZipPath); + //client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead) + var httpRequestMessage = new HttpRequestMessage(HttpMethod.Head, downloadUrl); + var headResponse = await client.SendAsync(httpRequestMessage).ConfigureAwait(false); + var contentLength = headResponse.Content.Headers.ContentLength; + await client.GetFileAsync(downloadUrl, fileStream).ContinueWith(async t => + { + // ReSharper disable once AccessToDisposedClosure + await fileStream.DisposeAsync().ConfigureAwait(false); + var fileLength = new FileInfo(_goldbergZipPath).Length; + // Environment.Exit(128); + if (contentLength == fileLength) + { + _log.Info("Download finished!"); + } + else + { + throw new Exception("File size does not match!"); + } + }).ConfigureAwait(false); + } + catch (Exception e) + { + ShowErrorMessage(); + _log.Error(e.ToString); + Environment.Exit(1); } } @@ -395,13 +559,56 @@ namespace GoldbergGUI.Core.Services // Extract all from archive to subfolder ./goldberg/ private async Task Extract(string archivePath) { + var errorOccured = false; _log.Debug("Start extraction..."); - await Task.Run(() => + Directory.Delete(_goldbergPath, true); + Directory.CreateDirectory(_goldbergPath); + using (var archive = await Task.Run(() => ZipFile.OpenRead(archivePath)).ConfigureAwait(false)) + { + foreach (var entry in archive.Entries) + { + await Task.Run(() => + { + try + { + var fullPath = Path.Combine(_goldbergPath, entry.FullName); + if (string.IsNullOrEmpty(entry.Name)) + { + Directory.CreateDirectory(fullPath); + } + else + { + entry.ExtractToFile(fullPath, true); + } + } + catch (Exception e) + { + errorOccured = true; + _log.Error($"Error while trying to extract {entry.FullName}"); + _log.Error(e.ToString); + } + }).ConfigureAwait(false); + } + } + + if (errorOccured) + { + ShowErrorMessage(); + _log.Warn("Error occured while extraction! Please setup Goldberg manually"); + } + _log.Info("Extraction was successful!"); + } + + private void ShowErrorMessage() + { + if (Directory.Exists(_goldbergPath)) { Directory.Delete(_goldbergPath, true); - ZipFile.ExtractToDirectory(archivePath, _goldbergPath); - }).ConfigureAwait(false); - _log.Debug("Extraction done!"); + } + + Directory.CreateDirectory(_goldbergPath); + MessageBox.Show("Could not setup Goldberg Emulator!\n" + + "Please download it manually and extract its content into the \"goldberg\" subfolder!"); } // https://gitlab.com/Mr_Goldberg/goldberg_emulator/-/blob/master/generate_interfaces_file.cpp @@ -476,5 +683,22 @@ namespace GoldbergGUI.Core.Services return success; } + + private async Task DownloadImageAsync(string imageFolder, string imageUrl) + { + var fileName = Path.GetFileName(imageUrl); + var targetPath = Path.Combine(imageFolder, fileName); + if (File.Exists(targetPath)) + { + return; + } + else if (imageUrl.StartsWith("images/")) + { + _log.Warn($"Previously downloaded image '{imageUrl}' is now missing!"); + } + + var wc = new System.Net.WebClient(); + await wc.DownloadFileTaskAsync(new Uri(imageUrl, UriKind.Absolute), targetPath); + } } } \ No newline at end of file diff --git a/GoldbergGUI.Core/Services/SteamService.cs b/GoldbergGUI.Core/Services/SteamService.cs index 300961e..74c369b 100644 --- a/GoldbergGUI.Core/Services/SteamService.cs +++ b/GoldbergGUI.Core/Services/SteamService.cs @@ -1,19 +1,18 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Text.Json; -using System.Text.RegularExpressions; -using System.Threading.Tasks; using AngleSharp.Dom; using AngleSharp.Html.Parser; using GoldbergGUI.Core.Models; using GoldbergGUI.Core.Utils; using MvvmCross.Logging; using NinjaNye.SearchExtensions; +using SQLite; using SteamStorefrontAPI; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace GoldbergGUI.Core.Services { @@ -21,23 +20,21 @@ namespace GoldbergGUI.Core.Services public interface ISteamService { public Task Initialize(IMvxLog log); - public IEnumerable GetListOfAppsByName(string name); - public SteamApp GetAppByName(string name); - public SteamApp GetAppById(int appid); - public Task> GetListOfDlc(SteamApp steamApp, bool useSteamDb); + public Task> GetListOfAppsByName(string name); + public Task GetAppByName(string name); + public Task GetAppById(int appid); + public Task> GetListOfAchievements(SteamApp steamApp); + public Task> GetListOfDlc(SteamApp steamApp, bool useSteamDb); } class SteamCache { - public string Filename { get; } public string SteamUri { get; } public Type ApiVersion { get; } - public AppType SteamAppType { get; } - public HashSet Cache { get; set; } = new HashSet(); + public string SteamAppType { get; } - public SteamCache(string filename, string uri, Type apiVersion, AppType steamAppType) + public SteamCache(string uri, Type apiVersion, string steamAppType) { - Filename = filename; SteamUri = uri; ApiVersion = apiVersion; SteamAppType = steamAppType; @@ -49,32 +46,30 @@ namespace GoldbergGUI.Core.Services public class SteamService : ISteamService { // ReSharper disable StringLiteralTypo - private readonly Dictionary _caches = - new Dictionary + private readonly Dictionary _caches = + new Dictionary { { - AppType.Game, + AppTypeGame, new SteamCache( - "steamapps_games.json", "https://api.steampowered.com/IStoreService/GetAppList/v1/" + "?max_results=50000" + "&include_games=1" + "&key=" + Secrets.SteamWebApiKey(), typeof(SteamAppsV1), - AppType.Game + AppTypeGame ) }, { - AppType.DLC, + AppTypeDlc, new SteamCache( - "steamapps_dlc.json", "https://api.steampowered.com/IStoreService/GetAppList/v1/" + "?max_results=50000" + "&include_games=0" + "&include_dlc=1" + "&key=" + Secrets.SteamWebApiKey(), typeof(SteamAppsV1), - AppType.DLC + AppTypeDlc ) } }; @@ -84,147 +79,160 @@ namespace GoldbergGUI.Core.Services private const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/87.0.4280.88 Safari/537.36"; + private const string AppTypeGame = "game"; + private const string AppTypeDlc = "dlc"; + private const string Database = "steamapps.cache"; + private const string GameSchemaUrl = "https://api.steampowered.com/ISteamUserStats/GetSchemaForGame/v2/"; private IMvxLog _log; + private SQLiteAsyncConnection _db; + public async Task Initialize(IMvxLog log) { - //var (path, uri, jsonType, appType) = _caches[0]; static SteamApps DeserializeSteamApps(Type type, string cacheString) { - if (type == typeof(SteamAppsV1)) - return JsonSerializer.Deserialize(cacheString); - else if (type == typeof(SteamAppsV2)) - return JsonSerializer.Deserialize(cacheString); - return null; + return type == typeof(SteamAppsV2) + ? (SteamApps)JsonSerializer.Deserialize(cacheString) + : JsonSerializer.Deserialize(cacheString); } - foreach (var (k, c) in _caches) - { - _log = log; - _log.Info($"Updating cache ({k.Value})..."); - var updateNeeded = - DateTime.Now.Subtract(File.GetLastWriteTimeUtc(c.Filename)).TotalDays >= 1; - SteamApps steamApps; - try - { - var temp = await GetCache(updateNeeded, c.SteamUri, c.Filename) - .ConfigureAwait(false); - steamApps = DeserializeSteamApps(c.ApiVersion, temp); - } - catch (JsonException) - { - _log.Error("Local cache broken, forcing update..."); - var temp = await GetCache(true, c.SteamUri, c.Filename).ConfigureAwait(false); - steamApps = DeserializeSteamApps(c.ApiVersion, temp); - } + _log = log; + _db = new SQLiteAsyncConnection(Database); + //_db.CreateTable(); + await _db.CreateTableAsync() + //.ContinueWith(x => _log.Debug("Table success!")) + .ConfigureAwait(false); - try + var countAsync = await _db.Table().CountAsync().ConfigureAwait(false); + if (DateTime.Now.Subtract(File.GetLastWriteTimeUtc(Database)).TotalDays >= 1 || countAsync == 0) + { + foreach (var (appType, steamCache) in _caches) { - var cacheRaw = new HashSet(steamApps.AppList.Apps); + _log.Info($"Updating cache ({appType})..."); + bool haveMoreResults; + long lastAppId = 0; + var client = new HttpClient(); + var cacheRaw = new HashSet(); + do + { + var response = lastAppId > 0 + ? await client.GetAsync($"{steamCache.SteamUri}&last_appid={lastAppId}") + .ConfigureAwait(false) + : await client.GetAsync(steamCache.SteamUri).ConfigureAwait(false); + var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var steamApps = DeserializeSteamApps(steamCache.ApiVersion, responseBody); + foreach (var appListApp in steamApps.AppList.Apps) cacheRaw.Add(appListApp); + haveMoreResults = steamApps.AppList.HaveMoreResults; + lastAppId = steamApps.AppList.LastAppid; + } while (haveMoreResults); + var cache = new HashSet(); foreach (var steamApp in cacheRaw) { - steamApp.type = c.SteamAppType; + steamApp.AppType = steamCache.SteamAppType; + steamApp.ComparableName = PrepareStringToCompare(steamApp.Name); cache.Add(steamApp); } - c.Cache = cache; - - _log.Info("Loaded cache into memory!"); - } - catch (NullReferenceException e) - { - Console.WriteLine(e); - throw; + await _db.InsertAllAsync(cache, "OR IGNORE").ConfigureAwait(false); } } } - private async Task GetCache(bool updateNeeded, string steamUri, string cachePath) + public async Task> GetListOfAppsByName(string name) { - string cacheString; - if (updateNeeded) - { - _log.Info("Getting content from API..."); - var client = new HttpClient(); - var response = await client.GetAsync(steamUri).ConfigureAwait(false); - var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - - _log.Info("Got content from API successfully. Writing to file..."); - await File.WriteAllTextAsync(cachePath, responseBody, Encoding.UTF8).ConfigureAwait(false); - - _log.Info("Cache written to file successfully."); - cacheString = responseBody; - } - else - { - _log.Info("Cache already up to date!"); - cacheString = await File.ReadAllTextAsync(cachePath).ConfigureAwait(false); - } - - return cacheString; - } - - public IEnumerable GetListOfAppsByName(string name) - { - var listOfAppsByName = _caches[AppType.Game].Cache.Search(x => x.Name) + var query = await _db.Table() + .Where(x => x.AppType == AppTypeGame).ToListAsync().ConfigureAwait(false); + var listOfAppsByName = query.Search(x => x.Name) .SetCulture(StringComparison.OrdinalIgnoreCase) .ContainingAll(name.Split(' ')); return listOfAppsByName; } - public SteamApp GetAppByName(string name) + public async Task GetAppByName(string name) { _log.Info($"Trying to get app {name}"); - var comparableName = Regex.Replace(name, Misc.AlphaNumOnlyRegex, "").ToLower(); - var app = _caches[AppType.Game].Cache.FirstOrDefault(x => x.CompareName(comparableName)); + var comparableName = PrepareStringToCompare(name); + var app = await _db.Table() + .FirstOrDefaultAsync(x => x.AppType == AppTypeGame && x.ComparableName.Equals(comparableName)) + .ConfigureAwait(false); if (app != null) _log.Info($"Successfully got app {app}"); return app; } - public SteamApp GetAppById(int appid) + public async Task GetAppById(int appid) { _log.Info($"Trying to get app with ID {appid}"); - var app = _caches[AppType.Game].Cache.FirstOrDefault(x => x.AppId.Equals(appid)); + var app = await _db.Table().Where(x => x.AppType == AppTypeGame) + .FirstOrDefaultAsync(x => x.AppId.Equals(appid)).ConfigureAwait(false); if (app != null) _log.Info($"Successfully got app {app}"); return app; } - public async Task> GetListOfDlc(SteamApp steamApp, bool useSteamDb) + public async Task> GetListOfAchievements(SteamApp steamApp) { - _log.Info("Get DLC"); - var dlcList = new List(); + var achievementList = new List(); + if (steamApp == null) + { + return achievementList; + } + + _log.Info($"Getting achievements for App {steamApp}"); + + var client = new HttpClient(); + client.DefaultRequestHeaders.UserAgent.ParseAdd(UserAgent); + var apiUrl = $"{GameSchemaUrl}?key={Secrets.SteamWebApiKey()}&appid={steamApp.AppId}&l=en"; + + var response = await client.GetAsync(apiUrl); + var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + var jsonResponse = JsonDocument.Parse(responseBody); + var achievementData = jsonResponse.RootElement.GetProperty("game") + .GetProperty("availableGameStats") + .GetProperty("achievements"); + + achievementList = JsonSerializer.Deserialize>(achievementData.GetRawText()); + return achievementList; + } + + public async Task> GetListOfDlc(SteamApp steamApp, bool useSteamDb) + { + var dlcList = new List(); if (steamApp != null) { + _log.Info($"Get DLC for App {steamApp}"); var task = AppDetails.GetAsync(steamApp.AppId); var steamAppDetails = await task.ConfigureAwait(true); - if (steamAppDetails.Type == AppType.Game.Value) + if (steamAppDetails.Type == AppTypeGame) { - steamAppDetails.DLC.ForEach(x => + steamAppDetails.DLC.ForEach(async x => { - var result = _caches[AppType.DLC].Cache.FirstOrDefault(y => y.AppId.Equals(x)) - ?? new SteamApp {AppId = x, Name = $"Unknown DLC {x}"}; - dlcList.Add(result); + var result = await _db.Table().Where(z => z.AppType == AppTypeDlc) + .FirstOrDefaultAsync(y => y.AppId.Equals(x)).ConfigureAwait(true) + ?? new SteamApp() { AppId = x, Name = $"Unknown DLC {x}", ComparableName = $"unknownDlc{x}", AppType = AppTypeDlc }; + dlcList.Add(new DlcApp(result)); + _log.Debug($"{result.AppId}={result.Name}"); }); - dlcList.ForEach(x => _log.Debug($"{x.AppId}={x.Name}")); _log.Info("Got DLC successfully..."); // Get DLC from SteamDB - // Get Cloudflare cookie + // Get Cloudflare cookie (not implemented) // Scrape and parse HTML page // Add missing to DLC list - - // ReSharper disable once InvertIf - if (useSteamDb) + + // Return current list if we don't intend to use SteamDB + if (!useSteamDb) return dlcList; + + try { var steamDbUri = new Uri($"https://steamdb.info/app/{steamApp.AppId}/dlc/"); var client = new HttpClient(); client.DefaultRequestHeaders.UserAgent.ParseAdd(UserAgent); - _log.Info("Get SteamDB App"); + _log.Info($"Get SteamDB App {steamApp}"); var httpCall = client.GetAsync(steamDbUri); var response = await httpCall.ConfigureAwait(false); _log.Debug(httpCall.Status.ToString()); @@ -240,6 +248,7 @@ namespace GoldbergGUI.Core.Services var query1 = doc.QuerySelector("#dlc"); if (query1 != null) { + _log.Info("Got list of DLC from SteamDB."); var query2 = query1.QuerySelectorAll(".app"); foreach (var element in query2) { @@ -248,7 +257,7 @@ namespace GoldbergGUI.Core.Services var dlcName = query3 != null ? query3[1].Text().Replace("\n", "").Trim() : $"Unknown DLC {dlcId}"; - var dlcApp = new SteamApp {AppId = Convert.ToInt32(dlcId), Name = dlcName}; + var dlcApp = new DlcApp { AppId = Convert.ToInt32(dlcId), Name = dlcName }; var i = dlcList.FindIndex(x => x.AppId.Equals(dlcApp.AppId)); if (i > -1) { @@ -268,6 +277,11 @@ namespace GoldbergGUI.Core.Services _log.Error("Could not get DLC from SteamDB!"); } } + catch (Exception e) + { + _log.Error("Could not get DLC from SteamDB! Skipping..."); + _log.Error(e.ToString); + } } else { @@ -281,5 +295,10 @@ namespace GoldbergGUI.Core.Services return dlcList; } + + private static string PrepareStringToCompare(string name) + { + return Regex.Replace(name, Misc.AlphaNumOnlyRegex, "").ToLower(); + } } } \ No newline at end of file diff --git a/GoldbergGUI.Core/Utils/CustomMvxAppStart.cs b/GoldbergGUI.Core/Utils/CustomMvxAppStart.cs index d5fcc3a..8fa735e 100644 --- a/GoldbergGUI.Core/Utils/CustomMvxAppStart.cs +++ b/GoldbergGUI.Core/Utils/CustomMvxAppStart.cs @@ -1,8 +1,7 @@ -using System; -using System.Threading.Tasks; using MvvmCross.Exceptions; using MvvmCross.Navigation; using MvvmCross.ViewModels; +using System.Threading.Tasks; namespace GoldbergGUI.Core.Utils { diff --git a/GoldbergGUI.Core/Utils/Misc.cs b/GoldbergGUI.Core/Utils/Misc.cs index 6bf7df2..9e315d3 100644 --- a/GoldbergGUI.Core/Utils/Misc.cs +++ b/GoldbergGUI.Core/Utils/Misc.cs @@ -7,15 +7,15 @@ namespace GoldbergGUI.Core.Utils public class GlobalHelp { - public static string Header => + public static string Header => "Information\n"; - public static string TextPreLink => + public static string TextPreLink => "Usually these settings are saved under"; public static string Link => "%APPDATA%\\Goldberg SteamEmu Saves\\settings"; - public static string TextPostLink => + public static string TextPostLink => ", which makes these " + "available for every game that uses the Goldberg Emulator. However, if you want to set specific settings " + "for certain games (e.g. different language), you can remove the \"Global\" checkmark next to the option " + diff --git a/GoldbergGUI.Core/ViewModels/MainViewModel.cs b/GoldbergGUI.Core/ViewModels/MainViewModel.cs index ef36c1d..54580c7 100644 --- a/GoldbergGUI.Core/ViewModels/MainViewModel.cs +++ b/GoldbergGUI.Core/ViewModels/MainViewModel.cs @@ -1,4 +1,12 @@ -using System; +using GoldbergGUI.Core.Models; +using GoldbergGUI.Core.Services; +using GoldbergGUI.Core.Utils; +using Microsoft.Win32; +using MvvmCross.Commands; +using MvvmCross.Logging; +using MvvmCross.Navigation; +using MvvmCross.ViewModels; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; @@ -9,14 +17,6 @@ using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; -using GoldbergGUI.Core.Models; -using GoldbergGUI.Core.Services; -using GoldbergGUI.Core.Utils; -using Microsoft.Win32; -using MvvmCross.Commands; -using MvvmCross.Logging; -using MvvmCross.Navigation; -using MvvmCross.ViewModels; namespace GoldbergGUI.Core.ViewModels { @@ -30,7 +30,8 @@ namespace GoldbergGUI.Core.ViewModels private int _appId; //private SteamApp _currentGame; - private ObservableCollection _dlcs; + private ObservableCollection _achievements; + private ObservableCollection _dlcs; private string _accountName; private long _steamId; private bool _offline; @@ -130,7 +131,7 @@ namespace GoldbergGUI.Core.ViewModels } // ReSharper disable once InconsistentNaming - public ObservableCollection DLCs + public ObservableCollection DLCs { get => _dlcs; set @@ -142,6 +143,16 @@ namespace GoldbergGUI.Core.ViewModels } } + public ObservableCollection Achievements + { + get => _achievements; + set + { + _achievements = value; + RaisePropertyChanged(() => Achievements); + } + } + public string AccountName { get => _accountName; @@ -264,7 +275,7 @@ namespace GoldbergGUI.Core.ViewModels public static string AboutVersionText => FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion; - + public static GlobalHelp G => new GlobalHelp(); // COMMANDS // @@ -321,7 +332,7 @@ namespace GoldbergGUI.Core.ViewModels MainWindowEnabled = false; StatusText = "Trying to find AppID..."; - var appByName = _steam.GetAppByName(_gameName); + var appByName = await _steam.GetAppByName(_gameName).ConfigureAwait(false); if (appByName != null) { GameName = appByName.Name; @@ -329,7 +340,7 @@ namespace GoldbergGUI.Core.ViewModels } else { - var list = _steam.GetListOfAppsByName(GameName); + var list = await _steam.GetListOfAppsByName(GameName).ConfigureAwait(false); var steamApps = list as SteamApp[] ?? list.ToArray(); if (steamApps.Length == 1) { @@ -364,10 +375,37 @@ namespace GoldbergGUI.Core.ViewModels return; } - var steamApp = await Task.Run(() => _steam.GetAppById(AppId)).ConfigureAwait(false); + var steamApp = await _steam.GetAppById(AppId).ConfigureAwait(false); if (steamApp != null) GameName = steamApp.Name; } + public IMvxCommand GetListOfAchievementsCommand => new MvxAsyncCommand(GetListOfAchievements); + + private async Task GetListOfAchievements() + { + if (AppId <= 0) + { + _log.Error("Invalid Steam App!"); + return; + } + + MainWindowEnabled = false; + StatusText = "Trying to get list of achievements..."; + var listOfAchievements = await _steam.GetListOfAchievements(new SteamApp { AppId = AppId, Name = GameName }); + Achievements = new MvxObservableCollection(listOfAchievements); + MainWindowEnabled = true; + + if (Achievements.Count > 0) + { + var empty = Achievements.Count == 1 ? "" : "s"; + StatusText = $"Successfully got {Achievements.Count} achievement{empty}! Ready."; + } + else + { + StatusText = "No achievements found! Ready."; + } + } + public IMvxCommand GetListOfDlcCommand => new MvxAsyncCommand(GetListOfDlc); private async Task GetListOfDlc() @@ -380,9 +418,9 @@ namespace GoldbergGUI.Core.ViewModels MainWindowEnabled = false; StatusText = "Trying to get list of DLCs..."; - var listOfDlc = await _steam.GetListOfDlc(new SteamApp {AppId = AppId, Name = GameName}, true) + var listOfDlc = await _steam.GetListOfDlc(new SteamApp { AppId = AppId, Name = GameName }, true) .ConfigureAwait(false); - DLCs = new MvxObservableCollection(listOfDlc); + DLCs = new MvxObservableCollection(listOfDlc); MainWindowEnabled = true; if (DLCs.Count > 0) { @@ -402,8 +440,8 @@ namespace GoldbergGUI.Core.ViewModels _log.Info("Saving global settings..."); var globalConfiguration = new GoldbergGlobalConfiguration { - AccountName = AccountName, - UserSteamId = SteamId, + AccountName = AccountName, + UserSteamId = SteamId, Language = SelectedLanguage }; await _goldberg.SetGlobalSettings(globalConfiguration).ConfigureAwait(false); @@ -414,13 +452,14 @@ namespace GoldbergGUI.Core.ViewModels MainWindowEnabled = false; StatusText = "Saving..."; await _goldberg.Save(dirPath, new GoldbergConfiguration - { - AppId = AppId, - DlcList = DLCs.ToList(), - Offline = Offline, - DisableNetworking = DisableNetworking, - DisableOverlay = DisableOverlay - } + { + AppId = AppId, + Achievements = Achievements.ToList(), + DlcList = DLCs.ToList(), + Offline = Offline, + DisableNetworking = DisableNetworking, + DisableOverlay = DisableOverlay + } ).ConfigureAwait(false); GoldbergApplied = _goldberg.GoldbergApplied(dirPath); MainWindowEnabled = true; @@ -478,17 +517,19 @@ namespace GoldbergGUI.Core.ViewModels { var result = Clipboard.GetText(); var expression = new Regex(@"(?.*) *= *(?.*)"); - var pastedDlc = (from line in result.Split(new[] {"\n", "\r\n"}, - StringSplitOptions.RemoveEmptyEntries) select expression.Match(line) into match - where match.Success select new SteamApp - { - AppId = Convert.ToInt32(match.Groups["id"].Value), - Name = match.Groups["name"].Value - }).ToList(); + var pastedDlc = (from line in result.Split(new[] { "\n", "\r\n" }, + StringSplitOptions.RemoveEmptyEntries) + select expression.Match(line) into match + where match.Success + select new DlcApp + { + AppId = Convert.ToInt32(match.Groups["id"].Value), + Name = match.Groups["name"].Value + }).ToList(); if (pastedDlc.Count > 0) { DLCs.Clear(); - DLCs = new ObservableCollection(pastedDlc); + DLCs = new ObservableCollection(pastedDlc); //var empty = DLCs.Count == 1 ? "" : "s"; //StatusText = $"Successfully got {DLCs.Count} DLC{empty} from clipboard! Ready."; var statusTextCount = DLCs.Count == 1 ? "one DLC" : $"{DLCs.Count} DLCs"; @@ -524,7 +565,8 @@ namespace GoldbergGUI.Core.ViewModels DllPath = "Path to game's steam_api(64).dll..."; GameName = "Game name..."; AppId = -1; - DLCs = new ObservableCollection(); + Achievements = new ObservableCollection(); + DLCs = new ObservableCollection(); AccountName = "Account name..."; SteamId = -1; Offline = false; @@ -544,7 +586,8 @@ namespace GoldbergGUI.Core.ViewModels private void SetFormFromConfig(GoldbergConfiguration config) { AppId = config.AppId; - DLCs = new ObservableCollection(config.DlcList); + Achievements = new ObservableCollection(config.Achievements); + DLCs = new ObservableCollection(config.DlcList); Offline = config.Offline; DisableNetworking = config.DisableNetworking; DisableOverlay = config.DisableOverlay; diff --git a/GoldbergGUI.Core/ViewModels/SearchResultViewModel.cs b/GoldbergGUI.Core/ViewModels/SearchResultViewModel.cs index dd46194..35f4dc2 100644 --- a/GoldbergGUI.Core/ViewModels/SearchResultViewModel.cs +++ b/GoldbergGUI.Core/ViewModels/SearchResultViewModel.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; -using System.Threading.Tasks; using GoldbergGUI.Core.Models; using MvvmCross.Commands; using MvvmCross.Logging; using MvvmCross.Navigation; using MvvmCross.ViewModels; +using System.Collections.Generic; +using System.Threading.Tasks; namespace GoldbergGUI.Core.ViewModels { @@ -14,7 +14,7 @@ namespace GoldbergGUI.Core.ViewModels private readonly IMvxLog _log; private IEnumerable _apps; - public SearchResultViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : + public SearchResultViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : base(logProvider, navigationService) { _log = logProvider.GetLogFor(typeof(SearchResultViewModel)); @@ -25,7 +25,7 @@ namespace GoldbergGUI.Core.ViewModels { Apps = parameter; } - + public IEnumerable Apps { get => _apps; @@ -35,7 +35,7 @@ namespace GoldbergGUI.Core.ViewModels RaisePropertyChanged(() => Apps); } } - + public SteamApp Selected { get; diff --git a/GoldbergGUI.WPF/App.xaml.cs b/GoldbergGUI.WPF/App.xaml.cs index 7b2e5c1..5d14782 100644 --- a/GoldbergGUI.WPF/App.xaml.cs +++ b/GoldbergGUI.WPF/App.xaml.cs @@ -1,5 +1,4 @@ using MvvmCross.Core; -using MvvmCross.Platforms.Wpf.Core; using MvvmCross.Platforms.Wpf.Views; namespace GoldbergGUI.WPF diff --git a/GoldbergGUI.WPF/AssemblyInfo.cs b/GoldbergGUI.WPF/AssemblyInfo.cs index 4a05c7d..4f943de 100644 --- a/GoldbergGUI.WPF/AssemblyInfo.cs +++ b/GoldbergGUI.WPF/AssemblyInfo.cs @@ -2,9 +2,9 @@ using System.Windows; [assembly: ThemeInfo( ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located - //(used if a resource is not found in the page, - // or application resource dictionaries) + //(used if a resource is not found in the page, + // or application resource dictionaries) ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located - //(used if a resource is not found in the page, - // app, or any theme specific resource dictionaries) + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) )] \ No newline at end of file diff --git a/GoldbergGUI.WPF/GoldbergGUI.WPF.csproj b/GoldbergGUI.WPF/GoldbergGUI.WPF.csproj index eb4287b..6e1a7aa 100644 --- a/GoldbergGUI.WPF/GoldbergGUI.WPF.csproj +++ b/GoldbergGUI.WPF/GoldbergGUI.WPF.csproj @@ -2,11 +2,11 @@ WinExe - netcoreapp3.1 + net8.0-windows true - 0.1.0 - 0.1.0 + 0.3.0 Jeddunk + AnyCPU;x86;x64 diff --git a/GoldbergGUI.WPF/Setup.cs b/GoldbergGUI.WPF/Setup.cs index e11f828..c9f6d78 100644 --- a/GoldbergGUI.WPF/Setup.cs +++ b/GoldbergGUI.WPF/Setup.cs @@ -1,20 +1,17 @@ -using System; -using System.IO; -using System.Windows.Controls; -using System.Windows.Threading; using MvvmCross.Logging; using MvvmCross.Platforms.Wpf.Core; using Serilog; +using System.IO; namespace GoldbergGUI.WPF { public class Setup : MvxWpfSetup { public override MvxLogProviderType GetDefaultLogProviderType() => MvxLogProviderType.Serilog; - + protected override IMvxLogProvider CreateLogProvider() { - var logPath = Path.Combine(Directory.GetCurrentDirectory(),"goldberg_.log"); + var logPath = Path.Combine(Directory.GetCurrentDirectory(), "goldberg_.log"); Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Console() diff --git a/GoldbergGUI.WPF/Views/MainView.xaml b/GoldbergGUI.WPF/Views/MainView.xaml index b8d16ce..83b115d 100644 --- a/GoldbergGUI.WPF/Views/MainView.xaml +++ b/GoldbergGUI.WPF/Views/MainView.xaml @@ -7,6 +7,9 @@ xmlns:viewmodel="clr-namespace:GoldbergGUI.Core.ViewModels;assembly=GoldbergGUI.Core" mc:Ignorable="d" d:DesignHeight="500" d:DesignWidth="400" d:DataContext="{d:DesignInstance Type=viewmodel:MainViewModel }"> + + + @@ -29,7 +32,7 @@ - + @@ -39,30 +42,71 @@