Compare commits

..

No commits in common. "master" and "0.1.2" have entirely different histories.

17 changed files with 307 additions and 563 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "SteamStorefrontAPI"]
path = SteamStorefrontAPI
url = https://git.jeddunk.xyz/jeddunk/SteamStorefrontAPI.git

View File

@ -1,28 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<FileVersion>0.3.0</FileVersion> <FileVersion>0.1.0</FileVersion>
<Company>Jeddunk</Company> <AssemblyVersion>0.1.0</AssemblyVersion>
<Platforms>AnyCPU;x86;x64</Platforms> <Company>Jeddunk</Company>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AngleSharp" Version="0.14.0" /> <PackageReference Include="AngleSharp" Version="0.14.0" />
<PackageReference Include="MvvmCross" Version="7.1.2" /> <PackageReference Include="MvvmCross" Version="7.1.2" />
<PackageReference Include="NinjaNye.SearchExtensions" Version="3.0.1" /> <PackageReference Include="NinjaNye.SearchExtensions" Version="3.0.1" />
<PackageReference Include="SharpCompress" Version="0.35.0" /> <PackageReference Include="SharpCompress" Version="0.26.0" />
<PackageReference Include="sqlite-net-pcl" Version="1.7.335" /> <PackageReference Include="SteamStorefrontAPI.NETStandard" Version="1.0.0" />
<PackageReference Include="SteamStorefrontAPI" Version="2.0.1.421" /> </ItemGroup>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"> <Reference Include="PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<HintPath>..\..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_32\PresentationCore\v4.0_4.0.0.0__31bf3856ad364e35\PresentationCore.dll</HintPath> <HintPath>..\..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_32\PresentationCore\v4.0_4.0.0.0__31bf3856ad364e35\PresentationCore.dll</HintPath>
</Reference> </Reference>
<Reference Include="PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"> <Reference Include="PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<HintPath>..\..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_MSIL\PresentationFramework\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.dll</HintPath> <HintPath>..\..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_MSIL\PresentationFramework\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,8 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable UnusedMember.Global
namespace GoldbergGUI.Core.Models namespace GoldbergGUI.Core.Models
{ {
@ -34,13 +32,13 @@ namespace GoldbergGUI.Core.Models
/// <summary> /// <summary>
/// List of DLC /// List of DLC
/// </summary> /// </summary>
public List<DlcApp> DlcList { get; set; } public List<SteamApp> DlcList { get; set; }
public List<int> Depots { get; set; } public List<Depot> Depots { get; set; }
public List<Group> SubscribedGroups { get; set; } public List<Group> SubscribedGroups { get; set; }
//public List<AppPath> AppPaths { get; set; } public List<AppPath> AppPaths { get; set; }
public List<Achievement> Achievements { get; set; } public List<Achievement> Achievements { get; set; }
@ -67,24 +65,20 @@ namespace GoldbergGUI.Core.Models
public GoldbergGlobalConfiguration OverwrittenGlobalConfiguration { get; set; } public GoldbergGlobalConfiguration OverwrittenGlobalConfiguration { get; set; }
} }
public class DlcApp : SteamApp public class Depot
{ {
public DlcApp() { }
public DlcApp(SteamApp steamApp)
{
AppId = steamApp.AppId;
Name = steamApp.Name;
ComparableName = steamApp.ComparableName;
AppType = steamApp.AppType;
LastModified = steamApp.LastModified;
PriceChangeNumber = steamApp.PriceChangeNumber;
}
/// <summary> /// <summary>
/// Path to DLC (relative to Steam API DLL) (optional) /// ID of Depot.
/// </summary> /// </summary>
public string AppPath { get; set; } public int DepotId { get; set; }
/// <summary>
/// Name of Depot.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Associated DLC App ID, can be null (e.g. if Depot is for base game).
/// </summary>
public int DlcAppId { get; set; }
} }
public class Group public class Group
@ -103,6 +97,12 @@ namespace GoldbergGUI.Core.Models
public int AppId { get; set; } public int AppId { get; set; }
} }
public class AppPath
{
public int AppId { get; set; }
public string Path { get; set; }
}
public class Achievement public class Achievement
{ {
/// <summary> /// <summary>
@ -112,7 +112,7 @@ namespace GoldbergGUI.Core.Models
public string Description { get; set; } public string Description { get; set; }
/// <summary> /// <summary>
/// Human readable name, as shown on webpage, game library, overlay, etc. /// Human readable name, as shown on webpage, game libary, overlay, etc.
/// </summary> /// </summary>
[JsonPropertyName("displayName")] [JsonPropertyName("displayName")]
public string DisplayName { get; set; } public string DisplayName { get; set; }
@ -132,7 +132,6 @@ namespace GoldbergGUI.Core.Models
/// <summary> /// <summary>
/// Path to icon when locked (grayed out). /// Path to icon when locked (grayed out).
/// </summary> /// </summary>
// ReSharper disable once StringLiteralTypo
[JsonPropertyName("icongray")] [JsonPropertyName("icongray")]
public string IconGray { get; set; } public string IconGray { get; set; }
@ -201,10 +200,9 @@ namespace GoldbergGUI.Core.Models
// [JsonConverter(typeof(FluffyParseStringConverter))] // [JsonConverter(typeof(FluffyParseStringConverter))]
public long DropMaxPerWindow { get; set; } public long DropMaxPerWindow { get; set; }
// ReSharper disable once StringLiteralTypo
[JsonPropertyName("workshopid")] [JsonPropertyName("workshopid")]
// [JsonConverter(typeof(FluffyParseStringConverter))] // [JsonConverter(typeof(FluffyParseStringConverter))]
public long WorkshopId { get; set; } public long Workshopid { get; set; }
[JsonPropertyName("tw_unique_to_own")] [JsonPropertyName("tw_unique_to_own")]
// [JsonConverter(typeof(PurpleParseStringConverter))] // [JsonConverter(typeof(PurpleParseStringConverter))]

View File

@ -1,6 +1,7 @@
using SQLite;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using GoldbergGUI.Core.Utils;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable ClassNeverInstantiated.Global // ReSharper disable ClassNeverInstantiated.Global
@ -10,41 +11,44 @@ using System.Text.Json.Serialization;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace GoldbergGUI.Core.Models namespace GoldbergGUI.Core.Models
{ {
[Table("steamapp")]
public class SteamApp public class SteamApp
{ {
[JsonPropertyName("appid")] private string _name;
[Column("appid")] private string _comparableName;
[PrimaryKey] [JsonPropertyName("appid")] public int AppId { get; set; }
public int AppId { get; set; }
/// <summary> /// <summary>
/// Name of Steam app /// Name of Steam app
/// </summary> /// </summary>
[JsonPropertyName("name")] [JsonPropertyName("name")]
[Column("name")] public string Name
public string Name { get; set; } {
get => _name;
set
{
_name = value;
_comparableName = Regex.Replace(value, Misc.AlphaNumOnlyRegex, "").ToLower();
}
}
[Column("comparable_name")] /// <summary>
public string ComparableName { get; set; } /// Trimmed and cleaned name of Steam app, used for comparisons.
/// </summary>
public bool CompareName(string value) => _comparableName.Equals(value);
/// <summary> /// <summary>
/// App type (Game, DLC, ...) /// App type (Game, DLC, ...)
/// </summary> /// </summary>
[Column("type")] public AppType type { get; set; }
public string AppType { get; set; }
public override string ToString() public override string ToString()
{ {
return $"{AppId}={Name}"; return $"{AppId}={Name}";
} }
[JsonPropertyName("last_modified")] [JsonPropertyName("last_modified")] public long LastModified { get; set; }
[Ignore]
public long LastModified { get; set; }
[JsonPropertyName("price_change_number")] [JsonPropertyName("price_change_number")]
[Ignore]
public long PriceChangeNumber { get; set; } public long PriceChangeNumber { get; set; }
} }
@ -72,4 +76,19 @@ namespace GoldbergGUI.Core.Models
{ {
[JsonPropertyName("response")] public override AppList AppList { get; set; } [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");
}
} }

View File

@ -1,6 +1,3 @@
using GoldbergGUI.Core.Models;
using GoldbergGUI.Core.Utils;
using MvvmCross.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -10,6 +7,9 @@ using System.Net.Http;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using GoldbergGUI.Core.Models;
using GoldbergGUI.Core.Utils;
using MvvmCross.Logging;
namespace GoldbergGUI.Core.Services namespace GoldbergGUI.Core.Services
{ {
@ -23,13 +23,16 @@ namespace GoldbergGUI.Core.Services
public Task Save(string path, GoldbergConfiguration configuration); public Task Save(string path, GoldbergConfiguration configuration);
public Task<GoldbergGlobalConfiguration> GetGlobalSettings(); public Task<GoldbergGlobalConfiguration> GetGlobalSettings();
public Task SetGlobalSettings(GoldbergGlobalConfiguration configuration); public Task SetGlobalSettings(GoldbergGlobalConfiguration configuration);
public bool GoldbergApplied(string path); public bool GoldbergApplied(string path);
// public Task<bool> Download();
// public Task Extract(string archivePath);
public Task GenerateInterfacesFile(string filePath); public Task GenerateInterfacesFile(string filePath);
public List<string> Languages(); public List<string> Languages();
} }
// ReSharper disable once UnusedType.Global // ReSharper disable once UnusedType.Global
// ReSharper disable once ClassNeverInstantiated.Global
public class GoldbergService : IGoldbergService public class GoldbergService : IGoldbergService
{ {
private IMvxLog _log; private IMvxLog _log;
@ -208,8 +211,7 @@ namespace GoldbergGUI.Core.Services
{ {
_log.Info("Reading configuration..."); _log.Info("Reading configuration...");
var appId = -1; var appId = -1;
var achievementList = new List<Achievement>(); var dlcList = new List<SteamApp>();
var dlcList = new List<DlcApp>();
var steamAppidTxt = Path.Combine(path, "steam_appid.txt"); var steamAppidTxt = Path.Combine(path, "steam_appid.txt");
if (File.Exists(steamAppidTxt)) if (File.Exists(steamAppidTxt))
{ {
@ -222,21 +224,7 @@ namespace GoldbergGUI.Core.Services
_log.Info(@"""steam_appid.txt"" missing! Skipping..."); _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<List<Achievement>>(json);
}
else
{
_log.Info(@"""steam_settings/achievements.json"" missing! Skipping...");
}
var dlcTxt = Path.Combine(path, "steam_settings", "DLC.txt"); var dlcTxt = Path.Combine(path, "steam_settings", "DLC.txt");
var appPathTxt = Path.Combine(path, "steam_settings", "app_paths.txt");
if (File.Exists(dlcTxt)) if (File.Exists(dlcTxt))
{ {
_log.Info("Getting DLCs..."); _log.Info("Getting DLCs...");
@ -247,27 +235,12 @@ namespace GoldbergGUI.Core.Services
{ {
var match = expression.Match(line); var match = expression.Match(line);
if (match.Success) if (match.Success)
dlcList.Add(new DlcApp() dlcList.Add(new SteamApp
{ {
AppId = Convert.ToInt32(match.Groups["id"].Value), AppId = Convert.ToInt32(match.Groups["id"].Value),
Name = match.Groups["name"].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(@"(?<id>.*) *= *(?<appPath>.*)");
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 else
{ {
@ -277,7 +250,6 @@ namespace GoldbergGUI.Core.Services
return new GoldbergConfiguration return new GoldbergConfiguration
{ {
AppId = appId, AppId = appId,
Achievements = achievementList,
DlcList = dlcList, DlcList = dlcList,
Offline = File.Exists(Path.Combine(path, "steam_settings", "offline.txt")), Offline = File.Exists(Path.Combine(path, "steam_settings", "offline.txt")),
DisableNetworking = File.Exists(Path.Combine(path, "steam_settings", "disable_networking.txt")), DisableNetworking = File.Exists(Path.Combine(path, "steam_settings", "disable_networking.txt")),
@ -305,7 +277,6 @@ namespace GoldbergGUI.Core.Services
{ {
CopyDllFiles(path, x64Name); CopyDllFiles(path, x64Name);
} }
_log.Info("DLL setup finished!");
// Create steam_settings folder if missing // Create steam_settings folder if missing
_log.Info("Saving settings..."); _log.Info("Saving settings...");
@ -318,135 +289,50 @@ namespace GoldbergGUI.Core.Services
await File.WriteAllTextAsync(Path.Combine(path, "steam_appid.txt"), c.AppId.ToString()) await File.WriteAllTextAsync(Path.Combine(path, "steam_appid.txt"), c.AppId.ToString())
.ConfigureAwait(false); .ConfigureAwait(false);
// Achievements + Images // DLC
if (c.Achievements.Count > 0)
{
_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) if (c.DlcList.Count > 0)
{ {
_log.Info("Saving DLC settings..."); var dlcString = "";
var dlcContent = ""; c.DlcList.ForEach(x => dlcString += $"{x}\n");
//var depotContent = ""; await File.WriteAllTextAsync(Path.Combine(path, "steam_settings", "DLC.txt"), dlcString)
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); .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 else
{ {
_log.Info("No DLC set! Removing DLC configuration files...");
if (File.Exists(Path.Combine(path, "steam_settings", "DLC.txt"))) if (File.Exists(Path.Combine(path, "steam_settings", "DLC.txt")))
File.Delete(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 // Offline
if (c.Offline) if (c.Offline)
{ {
_log.Info("Create offline.txt");
await File.Create(Path.Combine(path, "steam_settings", "offline.txt")).DisposeAsync() await File.Create(Path.Combine(path, "steam_settings", "offline.txt")).DisposeAsync()
.ConfigureAwait(false); .ConfigureAwait(false);
} }
else else
{ {
_log.Info("Delete offline.txt if it exists");
File.Delete(Path.Combine(path, "steam_settings", "offline.txt")); File.Delete(Path.Combine(path, "steam_settings", "offline.txt"));
} }
// Disable Networking // Disable Networking
if (c.DisableNetworking) if (c.DisableNetworking)
{ {
_log.Info("Create disable_networking.txt");
await File.Create(Path.Combine(path, "steam_settings", "disable_networking.txt")).DisposeAsync() await File.Create(Path.Combine(path, "steam_settings", "disable_networking.txt")).DisposeAsync()
.ConfigureAwait(false); .ConfigureAwait(false);
} }
else else
{ {
_log.Info("Delete disable_networking.txt if it exists");
File.Delete(Path.Combine(path, "steam_settings", "disable_networking.txt")); File.Delete(Path.Combine(path, "steam_settings", "disable_networking.txt"));
} }
// Disable Overlay // Disable Overlay
if (c.DisableOverlay) if (c.DisableOverlay)
{ {
_log.Info("Create disable_overlay.txt");
await File.Create(Path.Combine(path, "steam_settings", "disable_overlay.txt")).DisposeAsync() await File.Create(Path.Combine(path, "steam_settings", "disable_overlay.txt")).DisposeAsync()
.ConfigureAwait(false); .ConfigureAwait(false);
} }
else else
{ {
_log.Info("Delete disable_overlay.txt if it exists");
File.Delete(Path.Combine(path, "steam_settings", "disable_overlay.txt")); File.Delete(Path.Combine(path, "steam_settings", "disable_overlay.txt"));
} }
} }
@ -455,21 +341,17 @@ namespace GoldbergGUI.Core.Services
{ {
var steamApiDll = Path.Combine(path, $"{name}.dll"); var steamApiDll = Path.Combine(path, $"{name}.dll");
var originalDll = Path.Combine(path, $"{name}_o.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"); var goldbergDll = Path.Combine(_goldbergPath, $"{name}.dll");
if (!File.Exists(originalDll)) if (!File.Exists(originalDll))
{
_log.Info("Back up original Steam API DLL...");
File.Move(steamApiDll, originalDll); File.Move(steamApiDll, originalDll);
}
else else
{ {
File.Move(steamApiDll, guiBackup, true); File.Move(steamApiDll, guiBackup, true);
File.SetAttributes(guiBackup, FileAttributes.Hidden); File.SetAttributes(guiBackup, FileAttributes.Hidden);
} }
_log.Info("Copy Goldberg DLL to target path...");
File.Copy(goldbergDll, steamApiDll); File.Copy(goldbergDll, steamApiDll);
} }
@ -533,7 +415,6 @@ namespace GoldbergGUI.Core.Services
var contentLength = headResponse.Content.Headers.ContentLength; var contentLength = headResponse.Content.Headers.ContentLength;
await client.GetFileAsync(downloadUrl, fileStream).ContinueWith(async t => await client.GetFileAsync(downloadUrl, fileStream).ContinueWith(async t =>
{ {
// ReSharper disable once AccessToDisposedClosure
await fileStream.DisposeAsync().ConfigureAwait(false); await fileStream.DisposeAsync().ConfigureAwait(false);
var fileLength = new FileInfo(_goldbergZipPath).Length; var fileLength = new FileInfo(_goldbergZipPath).Length;
// Environment.Exit(128); // Environment.Exit(128);
@ -683,22 +564,5 @@ namespace GoldbergGUI.Core.Services
return success; 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);
}
} }
} }

View File

@ -1,18 +1,19 @@
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.Dom;
using AngleSharp.Html.Parser; using AngleSharp.Html.Parser;
using GoldbergGUI.Core.Models; using GoldbergGUI.Core.Models;
using GoldbergGUI.Core.Utils; using GoldbergGUI.Core.Utils;
using MvvmCross.Logging; using MvvmCross.Logging;
using NinjaNye.SearchExtensions; using NinjaNye.SearchExtensions;
using SQLite;
using SteamStorefrontAPI; 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 namespace GoldbergGUI.Core.Services
{ {
@ -20,21 +21,23 @@ namespace GoldbergGUI.Core.Services
public interface ISteamService public interface ISteamService
{ {
public Task Initialize(IMvxLog log); public Task Initialize(IMvxLog log);
public Task<IEnumerable<SteamApp>> GetListOfAppsByName(string name); public IEnumerable<SteamApp> GetListOfAppsByName(string name);
public Task<SteamApp> GetAppByName(string name); public SteamApp GetAppByName(string name);
public Task<SteamApp> GetAppById(int appid); public SteamApp GetAppById(int appid);
public Task<List<Achievement>> GetListOfAchievements(SteamApp steamApp); public Task<List<SteamApp>> GetListOfDlc(SteamApp steamApp, bool useSteamDb);
public Task<List<DlcApp>> GetListOfDlc(SteamApp steamApp, bool useSteamDb);
} }
class SteamCache class SteamCache
{ {
public string Filename { get; }
public string SteamUri { get; } public string SteamUri { get; }
public Type ApiVersion { get; } public Type ApiVersion { get; }
public string SteamAppType { get; } public AppType SteamAppType { get; }
public HashSet<SteamApp> Cache { get; set; } = new HashSet<SteamApp>();
public SteamCache(string uri, Type apiVersion, string steamAppType) public SteamCache(string filename, string uri, Type apiVersion, AppType steamAppType)
{ {
Filename = filename;
SteamUri = uri; SteamUri = uri;
ApiVersion = apiVersion; ApiVersion = apiVersion;
SteamAppType = steamAppType; SteamAppType = steamAppType;
@ -46,30 +49,32 @@ namespace GoldbergGUI.Core.Services
public class SteamService : ISteamService public class SteamService : ISteamService
{ {
// ReSharper disable StringLiteralTypo // ReSharper disable StringLiteralTypo
private readonly Dictionary<string, SteamCache> _caches = private readonly Dictionary<AppType, SteamCache> _caches =
new Dictionary<string, SteamCache> new Dictionary<AppType, SteamCache>
{ {
{ {
AppTypeGame, AppType.Game,
new SteamCache( new SteamCache(
"steamapps_games.json",
"https://api.steampowered.com/IStoreService/GetAppList/v1/" + "https://api.steampowered.com/IStoreService/GetAppList/v1/" +
"?max_results=50000" + "?max_results=50000" +
"&include_games=1" + "&include_games=1" +
"&key=" + Secrets.SteamWebApiKey(), "&key=" + Secrets.SteamWebApiKey(),
typeof(SteamAppsV1), typeof(SteamAppsV1),
AppTypeGame AppType.Game
) )
}, },
{ {
AppTypeDlc, AppType.DLC,
new SteamCache( new SteamCache(
"steamapps_dlc.json",
"https://api.steampowered.com/IStoreService/GetAppList/v1/" + "https://api.steampowered.com/IStoreService/GetAppList/v1/" +
"?max_results=50000" + "?max_results=50000" +
"&include_games=0" + "&include_games=0" +
"&include_dlc=1" + "&include_dlc=1" +
"&key=" + Secrets.SteamWebApiKey(), "&key=" + Secrets.SteamWebApiKey(),
typeof(SteamAppsV1), typeof(SteamAppsV1),
AppTypeDlc AppType.DLC
) )
} }
}; };
@ -79,146 +84,135 @@ namespace GoldbergGUI.Core.Services
private const string UserAgent = private const string UserAgent =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/87.0.4280.88 Safari/537.36"; "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 IMvxLog _log;
private SQLiteAsyncConnection _db;
public async Task Initialize(IMvxLog log) public async Task Initialize(IMvxLog log)
{ {
//var (path, uri, jsonType, appType) = _caches[0];
static SteamApps DeserializeSteamApps(Type type, string cacheString) static SteamApps DeserializeSteamApps(Type type, string cacheString)
{ {
return type == typeof(SteamAppsV2) if (type == typeof(SteamAppsV1))
? (SteamApps)JsonSerializer.Deserialize<SteamAppsV2>(cacheString) return JsonSerializer.Deserialize<SteamAppsV1>(cacheString);
: JsonSerializer.Deserialize<SteamAppsV1>(cacheString); else if (type == typeof(SteamAppsV2))
return JsonSerializer.Deserialize<SteamAppsV2>(cacheString);
return null;
} }
_log = log; foreach (var (k, c) in _caches)
_db = new SQLiteAsyncConnection(Database);
//_db.CreateTable<SteamApp>();
await _db.CreateTableAsync<SteamApp>()
//.ContinueWith(x => _log.Debug("Table success!"))
.ConfigureAwait(false);
var countAsync = await _db.Table<SteamApp>().CountAsync().ConfigureAwait(false);
if (DateTime.Now.Subtract(File.GetLastWriteTimeUtc(Database)).TotalDays >= 1 || countAsync == 0)
{ {
foreach (var (appType, steamCache) in _caches) _log = log;
_log.Info($"Updating cache ({k.Value})...");
var updateNeeded =
DateTime.Now.Subtract(File.GetLastWriteTimeUtc(c.Filename)).TotalDays >= 1;
SteamApps steamApps;
try
{ {
_log.Info($"Updating cache ({appType})..."); var temp = await GetCache(updateNeeded, c.SteamUri, c.Filename)
bool haveMoreResults; .ConfigureAwait(false);
long lastAppId = 0; steamApps = DeserializeSteamApps(c.ApiVersion, temp);
var client = new HttpClient(); }
var cacheRaw = new HashSet<SteamApp>(); catch (JsonException)
do {
{ _log.Error("Local cache broken, forcing update...");
var response = lastAppId > 0 var temp = await GetCache(true, c.SteamUri, c.Filename).ConfigureAwait(false);
? await client.GetAsync($"{steamCache.SteamUri}&last_appid={lastAppId}") steamApps = DeserializeSteamApps(c.ApiVersion, temp);
.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);
try
{
var cacheRaw = new HashSet<SteamApp>(steamApps.AppList.Apps);
var cache = new HashSet<SteamApp>(); var cache = new HashSet<SteamApp>();
foreach (var steamApp in cacheRaw) foreach (var steamApp in cacheRaw)
{ {
steamApp.AppType = steamCache.SteamAppType; steamApp.type = c.SteamAppType;
steamApp.ComparableName = PrepareStringToCompare(steamApp.Name);
cache.Add(steamApp); cache.Add(steamApp);
} }
await _db.InsertAllAsync(cache, "OR IGNORE").ConfigureAwait(false); c.Cache = cache;
_log.Info("Loaded cache into memory!");
}
catch (NullReferenceException e)
{
Console.WriteLine(e);
throw;
} }
} }
} }
public async Task<IEnumerable<SteamApp>> GetListOfAppsByName(string name) private async Task<string> GetCache(bool updateNeeded, string steamUri, string cachePath)
{ {
var query = await _db.Table<SteamApp>() string cacheString;
.Where(x => x.AppType == AppTypeGame).ToListAsync().ConfigureAwait(false); if (updateNeeded)
var listOfAppsByName = query.Search(x => x.Name) {
_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<SteamApp> GetListOfAppsByName(string name)
{
var listOfAppsByName = _caches[AppType.Game].Cache.Search(x => x.Name)
.SetCulture(StringComparison.OrdinalIgnoreCase) .SetCulture(StringComparison.OrdinalIgnoreCase)
.ContainingAll(name.Split(' ')); .ContainingAll(name.Split(' '));
return listOfAppsByName; return listOfAppsByName;
} }
public async Task<SteamApp> GetAppByName(string name) public SteamApp GetAppByName(string name)
{ {
_log.Info($"Trying to get app {name}"); _log.Info($"Trying to get app {name}");
var comparableName = PrepareStringToCompare(name); var comparableName = Regex.Replace(name, Misc.AlphaNumOnlyRegex, "").ToLower();
var app = await _db.Table<SteamApp>() var app = _caches[AppType.Game].Cache.FirstOrDefault(x => x.CompareName(comparableName));
.FirstOrDefaultAsync(x => x.AppType == AppTypeGame && x.ComparableName.Equals(comparableName))
.ConfigureAwait(false);
if (app != null) _log.Info($"Successfully got app {app}"); if (app != null) _log.Info($"Successfully got app {app}");
return app; return app;
} }
public async Task<SteamApp> GetAppById(int appid) public SteamApp GetAppById(int appid)
{ {
_log.Info($"Trying to get app with ID {appid}"); _log.Info($"Trying to get app with ID {appid}");
var app = await _db.Table<SteamApp>().Where(x => x.AppType == AppTypeGame) var app = _caches[AppType.Game].Cache.FirstOrDefault(x => x.AppId.Equals(appid));
.FirstOrDefaultAsync(x => x.AppId.Equals(appid)).ConfigureAwait(false);
if (app != null) _log.Info($"Successfully got app {app}"); if (app != null) _log.Info($"Successfully got app {app}");
return app; return app;
} }
public async Task<List<Achievement>> GetListOfAchievements(SteamApp steamApp) public async Task<List<SteamApp>> GetListOfDlc(SteamApp steamApp, bool useSteamDb)
{ {
var achievementList = new List<Achievement>(); var dlcList = new List<SteamApp>();
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<List<Achievement>>(achievementData.GetRawText());
return achievementList;
}
public async Task<List<DlcApp>> GetListOfDlc(SteamApp steamApp, bool useSteamDb)
{
var dlcList = new List<DlcApp>();
if (steamApp != null) if (steamApp != null)
{ {
_log.Info($"Get DLC for App {steamApp}"); _log.Info($"Get DLC for App {steamApp}");
var task = AppDetails.GetAsync(steamApp.AppId); var task = AppDetails.GetAsync(steamApp.AppId);
var steamAppDetails = await task.ConfigureAwait(true); var steamAppDetails = await task.ConfigureAwait(true);
if (steamAppDetails.Type == AppTypeGame) if (steamAppDetails.Type == AppType.Game.Value)
{ {
steamAppDetails.DLC.ForEach(async x => steamAppDetails.DLC.ForEach(x =>
{ {
var result = await _db.Table<SteamApp>().Where(z => z.AppType == AppTypeDlc) var result = _caches[AppType.DLC].Cache.FirstOrDefault(y => y.AppId.Equals(x))
.FirstOrDefaultAsync(y => y.AppId.Equals(x)).ConfigureAwait(true) ?? new SteamApp {AppId = x, Name = $"Unknown DLC {x}"};
?? new SteamApp() { AppId = x, Name = $"Unknown DLC {x}", ComparableName = $"unknownDlc{x}", AppType = AppTypeDlc }; dlcList.Add(result);
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..."); _log.Info("Got DLC successfully...");
// Get DLC from SteamDB // Get DLC from SteamDB
// Get Cloudflare cookie (not implemented) // Get Cloudflare cookie
// Scrape and parse HTML page // Scrape and parse HTML page
// Add missing to DLC list // Add missing to DLC list
@ -257,7 +251,7 @@ namespace GoldbergGUI.Core.Services
var dlcName = query3 != null var dlcName = query3 != null
? query3[1].Text().Replace("\n", "").Trim() ? query3[1].Text().Replace("\n", "").Trim()
: $"Unknown DLC {dlcId}"; : $"Unknown DLC {dlcId}";
var dlcApp = new DlcApp { AppId = Convert.ToInt32(dlcId), Name = dlcName }; var dlcApp = new SteamApp {AppId = Convert.ToInt32(dlcId), Name = dlcName};
var i = dlcList.FindIndex(x => x.AppId.Equals(dlcApp.AppId)); var i = dlcList.FindIndex(x => x.AppId.Equals(dlcApp.AppId));
if (i > -1) if (i > -1)
{ {
@ -295,10 +289,5 @@ namespace GoldbergGUI.Core.Services
return dlcList; return dlcList;
} }
private static string PrepareStringToCompare(string name)
{
return Regex.Replace(name, Misc.AlphaNumOnlyRegex, "").ToLower();
}
} }
} }

View File

@ -1,7 +1,8 @@
using System;
using System.Threading.Tasks;
using MvvmCross.Exceptions; using MvvmCross.Exceptions;
using MvvmCross.Navigation; using MvvmCross.Navigation;
using MvvmCross.ViewModels; using MvvmCross.ViewModels;
using System.Threading.Tasks;
namespace GoldbergGUI.Core.Utils namespace GoldbergGUI.Core.Utils
{ {

View File

@ -1,12 +1,4 @@
using GoldbergGUI.Core.Models; using System;
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.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
@ -17,6 +9,14 @@ using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; 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 namespace GoldbergGUI.Core.ViewModels
{ {
@ -30,8 +30,7 @@ namespace GoldbergGUI.Core.ViewModels
private int _appId; private int _appId;
//private SteamApp _currentGame; //private SteamApp _currentGame;
private ObservableCollection<Achievement> _achievements; private ObservableCollection<SteamApp> _dlcs;
private ObservableCollection<DlcApp> _dlcs;
private string _accountName; private string _accountName;
private long _steamId; private long _steamId;
private bool _offline; private bool _offline;
@ -131,7 +130,7 @@ namespace GoldbergGUI.Core.ViewModels
} }
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
public ObservableCollection<DlcApp> DLCs public ObservableCollection<SteamApp> DLCs
{ {
get => _dlcs; get => _dlcs;
set set
@ -143,16 +142,6 @@ namespace GoldbergGUI.Core.ViewModels
} }
} }
public ObservableCollection<Achievement> Achievements
{
get => _achievements;
set
{
_achievements = value;
RaisePropertyChanged(() => Achievements);
}
}
public string AccountName public string AccountName
{ {
get => _accountName; get => _accountName;
@ -332,7 +321,7 @@ namespace GoldbergGUI.Core.ViewModels
MainWindowEnabled = false; MainWindowEnabled = false;
StatusText = "Trying to find AppID..."; StatusText = "Trying to find AppID...";
var appByName = await _steam.GetAppByName(_gameName).ConfigureAwait(false); var appByName = _steam.GetAppByName(_gameName);
if (appByName != null) if (appByName != null)
{ {
GameName = appByName.Name; GameName = appByName.Name;
@ -340,7 +329,7 @@ namespace GoldbergGUI.Core.ViewModels
} }
else else
{ {
var list = await _steam.GetListOfAppsByName(GameName).ConfigureAwait(false); var list = _steam.GetListOfAppsByName(GameName);
var steamApps = list as SteamApp[] ?? list.ToArray(); var steamApps = list as SteamApp[] ?? list.ToArray();
if (steamApps.Length == 1) if (steamApps.Length == 1)
{ {
@ -375,37 +364,10 @@ namespace GoldbergGUI.Core.ViewModels
return; return;
} }
var steamApp = await _steam.GetAppById(AppId).ConfigureAwait(false); var steamApp = await Task.Run(() => _steam.GetAppById(AppId)).ConfigureAwait(false);
if (steamApp != null) GameName = steamApp.Name; 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<Achievement>(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); public IMvxCommand GetListOfDlcCommand => new MvxAsyncCommand(GetListOfDlc);
private async Task GetListOfDlc() private async Task GetListOfDlc()
@ -418,9 +380,9 @@ namespace GoldbergGUI.Core.ViewModels
MainWindowEnabled = false; MainWindowEnabled = false;
StatusText = "Trying to get list of DLCs..."; 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); .ConfigureAwait(false);
DLCs = new MvxObservableCollection<DlcApp>(listOfDlc); DLCs = new MvxObservableCollection<SteamApp>(listOfDlc);
MainWindowEnabled = true; MainWindowEnabled = true;
if (DLCs.Count > 0) if (DLCs.Count > 0)
{ {
@ -452,14 +414,13 @@ namespace GoldbergGUI.Core.ViewModels
MainWindowEnabled = false; MainWindowEnabled = false;
StatusText = "Saving..."; StatusText = "Saving...";
await _goldberg.Save(dirPath, new GoldbergConfiguration await _goldberg.Save(dirPath, new GoldbergConfiguration
{ {
AppId = AppId, AppId = AppId,
Achievements = Achievements.ToList(), DlcList = DLCs.ToList(),
DlcList = DLCs.ToList(), Offline = Offline,
Offline = Offline, DisableNetworking = DisableNetworking,
DisableNetworking = DisableNetworking, DisableOverlay = DisableOverlay
DisableOverlay = DisableOverlay }
}
).ConfigureAwait(false); ).ConfigureAwait(false);
GoldbergApplied = _goldberg.GoldbergApplied(dirPath); GoldbergApplied = _goldberg.GoldbergApplied(dirPath);
MainWindowEnabled = true; MainWindowEnabled = true;
@ -517,19 +478,17 @@ namespace GoldbergGUI.Core.ViewModels
{ {
var result = Clipboard.GetText(); var result = Clipboard.GetText();
var expression = new Regex(@"(?<id>.*) *= *(?<name>.*)"); var expression = new Regex(@"(?<id>.*) *= *(?<name>.*)");
var pastedDlc = (from line in result.Split(new[] { "\n", "\r\n" }, var pastedDlc = (from line in result.Split(new[] {"\n", "\r\n"},
StringSplitOptions.RemoveEmptyEntries) StringSplitOptions.RemoveEmptyEntries) select expression.Match(line) into match
select expression.Match(line) into match where match.Success select new SteamApp
where match.Success {
select new DlcApp AppId = Convert.ToInt32(match.Groups["id"].Value),
{ Name = match.Groups["name"].Value
AppId = Convert.ToInt32(match.Groups["id"].Value), }).ToList();
Name = match.Groups["name"].Value
}).ToList();
if (pastedDlc.Count > 0) if (pastedDlc.Count > 0)
{ {
DLCs.Clear(); DLCs.Clear();
DLCs = new ObservableCollection<DlcApp>(pastedDlc); DLCs = new ObservableCollection<SteamApp>(pastedDlc);
//var empty = DLCs.Count == 1 ? "" : "s"; //var empty = DLCs.Count == 1 ? "" : "s";
//StatusText = $"Successfully got {DLCs.Count} DLC{empty} from clipboard! Ready."; //StatusText = $"Successfully got {DLCs.Count} DLC{empty} from clipboard! Ready.";
var statusTextCount = DLCs.Count == 1 ? "one DLC" : $"{DLCs.Count} DLCs"; var statusTextCount = DLCs.Count == 1 ? "one DLC" : $"{DLCs.Count} DLCs";
@ -565,8 +524,7 @@ namespace GoldbergGUI.Core.ViewModels
DllPath = "Path to game's steam_api(64).dll..."; DllPath = "Path to game's steam_api(64).dll...";
GameName = "Game name..."; GameName = "Game name...";
AppId = -1; AppId = -1;
Achievements = new ObservableCollection<Achievement>(); DLCs = new ObservableCollection<SteamApp>();
DLCs = new ObservableCollection<DlcApp>();
AccountName = "Account name..."; AccountName = "Account name...";
SteamId = -1; SteamId = -1;
Offline = false; Offline = false;
@ -586,8 +544,7 @@ namespace GoldbergGUI.Core.ViewModels
private void SetFormFromConfig(GoldbergConfiguration config) private void SetFormFromConfig(GoldbergConfiguration config)
{ {
AppId = config.AppId; AppId = config.AppId;
Achievements = new ObservableCollection<Achievement>(config.Achievements); DLCs = new ObservableCollection<SteamApp>(config.DlcList);
DLCs = new ObservableCollection<DlcApp>(config.DlcList);
Offline = config.Offline; Offline = config.Offline;
DisableNetworking = config.DisableNetworking; DisableNetworking = config.DisableNetworking;
DisableOverlay = config.DisableOverlay; DisableOverlay = config.DisableOverlay;

View File

@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using GoldbergGUI.Core.Models; using GoldbergGUI.Core.Models;
using MvvmCross.Commands; using MvvmCross.Commands;
using MvvmCross.Logging; using MvvmCross.Logging;
using MvvmCross.Navigation; using MvvmCross.Navigation;
using MvvmCross.ViewModels; using MvvmCross.ViewModels;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GoldbergGUI.Core.ViewModels namespace GoldbergGUI.Core.ViewModels
{ {

View File

@ -1,4 +1,5 @@
using MvvmCross.Core; using MvvmCross.Core;
using MvvmCross.Platforms.Wpf.Core;
using MvvmCross.Platforms.Wpf.Views; using MvvmCross.Platforms.Wpf.Views;
namespace GoldbergGUI.WPF namespace GoldbergGUI.WPF

View File

@ -2,9 +2,9 @@ using System.Windows;
[assembly: ThemeInfo( [assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page, //(used if a resource is not found in the page,
// or application resource dictionaries) // or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page, //(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries) // app, or any theme specific resource dictionaries)
)] )]

View File

@ -2,11 +2,11 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<FileVersion>0.3.0</FileVersion> <FileVersion>0.1.0</FileVersion>
<AssemblyVersion>0.1.0</AssemblyVersion>
<Company>Jeddunk</Company> <Company>Jeddunk</Company>
<Platforms>AnyCPU;x86;x64</Platforms>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,7 +1,10 @@
using System;
using System.IO;
using System.Windows.Controls;
using System.Windows.Threading;
using MvvmCross.Logging; using MvvmCross.Logging;
using MvvmCross.Platforms.Wpf.Core; using MvvmCross.Platforms.Wpf.Core;
using Serilog; using Serilog;
using System.IO;
namespace GoldbergGUI.WPF namespace GoldbergGUI.WPF
{ {
@ -11,7 +14,7 @@ namespace GoldbergGUI.WPF
protected override IMvxLogProvider CreateLogProvider() protected override IMvxLogProvider CreateLogProvider()
{ {
var logPath = Path.Combine(Directory.GetCurrentDirectory(), "goldberg_.log"); var logPath = Path.Combine(Directory.GetCurrentDirectory(),"goldberg_.log");
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() .MinimumLevel.Debug()
.WriteTo.Console() .WriteTo.Console()

View File

@ -7,9 +7,6 @@
xmlns:viewmodel="clr-namespace:GoldbergGUI.Core.ViewModels;assembly=GoldbergGUI.Core" xmlns:viewmodel="clr-namespace:GoldbergGUI.Core.ViewModels;assembly=GoldbergGUI.Core"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="400" d:DataContext="{d:DesignInstance Type=viewmodel:MainViewModel }"> d:DesignHeight="500" d:DesignWidth="400" d:DataContext="{d:DesignInstance Type=viewmodel:MainViewModel }">
<views:MvxWpfView.Resources>
<BooleanToVisibilityConverter x:Key="B2V" />
</views:MvxWpfView.Resources>
<Grid Margin="0,0,0,0"> <Grid Margin="0,0,0,0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
@ -32,7 +29,7 @@
<Grid Margin="10,20,10,10"> <Grid Margin="10,20,10,10">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" MaxHeight="0"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition/> <RowDefinition/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
@ -42,71 +39,30 @@
<TextBox Text="{Binding GameName, Mode=TwoWay}" TextWrapping="Wrap" VerticalAlignment="Center" Padding="1,0,0,0" Grid.Row="2" Margin="0,5,215,5" Height="20"/> <TextBox Text="{Binding GameName, Mode=TwoWay}" TextWrapping="Wrap" VerticalAlignment="Center" Padding="1,0,0,0" Grid.Row="2" Margin="0,5,215,5" Height="20"/>
<Button Content="_Find ID..." Command="{Binding FindIdCommand}" Width="80" Grid.Row="2" Margin="0,5,130,5" HorizontalAlignment="Right" Height="20"/> <Button Content="_Find ID..." Command="{Binding FindIdCommand}" Width="80" Grid.Row="2" Margin="0,5,130,5" HorizontalAlignment="Right" Height="20"/>
<TextBox Text="{Binding AppId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextWrapping="Wrap" HorizontalAlignment="Right" VerticalAlignment="Center" Padding="1,0,0,0" Grid.Row="2" Width="125" Margin="0,5,0,5" Height="20"/> <TextBox Text="{Binding AppId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextWrapping="Wrap" HorizontalAlignment="Right" VerticalAlignment="Center" Padding="1,0,0,0" Grid.Row="2" Width="125" Margin="0,5,0,5" Height="20"/>
<TabControl Grid.Row="3" Margin="0,5,0,0" Padding="0,0,0,0"> <GroupBox Header="DLC" Grid.Row="3" Padding="0,0,0,0" Margin="0,5,0,0">
<TabItem Header="DLC"> <GroupBox.InputBindings>
<TabItem.InputBindings> <KeyBinding Key="V" Modifiers="Control"
<KeyBinding Key="V" Modifiers="Control" Command="{Binding PasteDlcCommand}"/> Command="{Binding PasteDlcCommand}"/>
</TabItem.InputBindings> </GroupBox.InputBindings>
<Grid Margin="10,10,10,10"> <Grid Margin="10,10,10,10">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition/> <RowDefinition/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<DataGrid Margin="0,0,0,5" ItemsSource="{Binding DLCs, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectionMode="Extended" SelectionUnit="FullRow" HeadersVisibility="Column" AutoGenerateColumns="False" CanUserResizeColumns="False"> <DataGrid Margin="0,0,0,5" ItemsSource="{Binding DLCs, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectionMode="Extended" SelectionUnit="FullRow" HeadersVisibility="Column" AutoGenerateColumns="False" CanUserResizeColumns="False">
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="App ID" Binding="{Binding AppId}" Width="80" /> <DataGridTextColumn Header="App ID" Binding="{Binding AppId}" Width="80" />
<DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="*" /> <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="*" />
<!--<DataGridTextColumn Header="Depot ID" Binding="{Binding DepotId}" Width="80" Visibility="{Binding Source={x:Reference ShowOptionalDlcSettings}, Path=IsChecked, Converter={StaticResource B2V}}"/> </DataGrid.Columns>
<DataGridTextColumn Header="Depot Name" Binding="{Binding DepotName}" Width="*" Visibility="{Binding Source={x:Reference ShowOptionalDlcSettings}, Path=IsChecked, Converter={StaticResource B2V}}" />--> </DataGrid>
<DataGridTextColumn Header="App Path" Binding="{Binding AppPath}" Width="*" Visibility="{Binding Source={x:Reference ShowOptionalDlcSettings}, Path=IsChecked, Converter={StaticResource B2V}}" /> <Button Content="Get _DLCs for AppID" Command="{Binding GetListOfDlcCommand}" Grid.Row="1" Width="120" HorizontalAlignment="Right" Margin="0,5,125,0" Height="20"/>
</DataGrid.Columns> <Button Content="_Advanced Settings..." Grid.Row="1" Width="120" HorizontalAlignment="Right" Margin="0,5,0,0" Height="20" IsEnabled="False" ToolTip="Work in progress..."/>
</DataGrid> </Grid>
<Grid Grid.Row="1"> </GroupBox>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="ShowOptionalDlcSettings" Margin="0,5,0,0" Content="Show optional settings"/>
<Button Grid.Column="2" Content="Get _DLCs for AppID" Command="{Binding GetListOfDlcCommand}" HorizontalAlignment="Right" Margin="0,5,0,0" Height="20" Width="117"/>
</Grid>
</Grid>
</TabItem>
<TabItem Header="Achievements" IsEnabled="{Binding DllSelected, UpdateSourceTrigger=PropertyChanged}">
<Grid Margin="10,10,10,10">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid Margin="0,0,0,5" ItemsSource="{Binding Achievements, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" SelectionMode="Extended" SelectionUnit="FullRow" HeadersVisibility="Column" AutoGenerateColumns="False" CanUserResizeColumns="True" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding DisplayName}" Width="*"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}" Width="*"/>
<DataGridCheckBoxColumn Header="Hidden" Binding="{Binding Hidden}" Width="60" CanUserResize="False"/>
</DataGrid.Columns>
</DataGrid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Row="1" Grid.Column="1" Content="Get _Achievements for AppID" Command="{Binding GetListOfAchievementsCommand}" Margin="0,5,0,0" HorizontalAlignment="Right" Width="165" />
</Grid>
</Grid>
</TabItem>
<TabItem Header="Misc" IsEnabled="{Binding DllSelected, UpdateSourceTrigger=PropertyChanged}">
<StackPanel Margin="10,10,10,10">
<Button Content="_Generate steam__interfaces.txt" IsEnabled="{Binding SteamInterfacesTxtExists, UpdateSourceTrigger=PropertyChanged}" Command="{Binding GenerateSteamInterfacesCommand}" Height="20" Margin="0,0,0,5" />
<CheckBox Content="Offline" IsChecked="{Binding Offline, Mode=TwoWay}" Height="20" VerticalAlignment="Stretch" VerticalContentAlignment="Center"/>
<CheckBox Content="Disable Networking" IsChecked="{Binding DisableNetworking, Mode=TwoWay}" Height="20" VerticalContentAlignment="Center"/>
<CheckBox Content="Disable Overlay" IsChecked="{Binding DisableOverlay, Mode=TwoWay}" Height="20" VerticalContentAlignment="Center" IsEnabled="False"/>
</StackPanel>
</TabItem>
</TabControl>
</Grid> </Grid>
</TabItem> </TabItem>
<!-- Advanced --> <!-- Advanced -->
<!--<TabItem Header="Advanced" IsEnabled="{Binding DllSelected, UpdateSourceTrigger=PropertyChanged}"> <TabItem Header="Advanced" IsEnabled="{Binding DllSelected, UpdateSourceTrigger=PropertyChanged}">
<Grid HorizontalAlignment="Stretch" Margin="10,20,10,10" > <Grid HorizontalAlignment="Stretch" Margin="10,20,10,10" >
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
@ -132,7 +88,7 @@
</StackPanel> </StackPanel>
</GroupBox> </GroupBox>
</Grid> </Grid>
</TabItem>--> </TabItem>
<!-- Settings --> <!-- Settings -->
<TabItem Header="Global Settings"> <TabItem Header="Global Settings">
<StackPanel Margin="10,20,10,10"> <StackPanel Margin="10,20,10,10">
@ -155,7 +111,7 @@
<CheckBox Content="Global" Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" <CheckBox Content="Global" Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right"
Margin="10,0,5,0" VerticalAlignment="Center" IsChecked="True" Margin="10,0,5,0" VerticalAlignment="Center" IsChecked="True"
IsEnabled="False"/> IsEnabled="False"/>
<!-- <!--
IsEnabled="{Binding DllSelected, UpdateSourceTrigger=PropertyChanged}"/> IsEnabled="{Binding DllSelected, UpdateSourceTrigger=PropertyChanged}"/>
--> -->
<Label Content="Steam64ID" HorizontalAlignment="Left" Grid.Row="1" <Label Content="Steam64ID" HorizontalAlignment="Left" Grid.Row="1"
@ -164,7 +120,7 @@
<CheckBox Content="Global" Grid.Row="1" Grid.Column="2" HorizontalAlignment="Right" <CheckBox Content="Global" Grid.Row="1" Grid.Column="2" HorizontalAlignment="Right"
Margin="10,0,5,0" VerticalAlignment="Center" IsChecked="True" Margin="10,0,5,0" VerticalAlignment="Center" IsChecked="True"
IsEnabled="False"/> IsEnabled="False"/>
<!-- <!--
IsEnabled="{Binding DllSelected, UpdateSourceTrigger=PropertyChanged}"/> IsEnabled="{Binding DllSelected, UpdateSourceTrigger=PropertyChanged}"/>
--> -->
<Label Content="Language" HorizontalAlignment="Left" Grid.Row="2" <Label Content="Language" HorizontalAlignment="Left" Grid.Row="2"
@ -173,7 +129,7 @@
<CheckBox Content="Global" Grid.Row="2" Grid.Column="2" HorizontalAlignment="Right" <CheckBox Content="Global" Grid.Row="2" Grid.Column="2" HorizontalAlignment="Right"
Margin="10,0,5,0" VerticalAlignment="Center" IsChecked="True" Margin="10,0,5,0" VerticalAlignment="Center" IsChecked="True"
IsEnabled="False"/> IsEnabled="False"/>
<!-- <!--
IsEnabled="{Binding DllSelected, UpdateSourceTrigger=PropertyChanged}"/> IsEnabled="{Binding DllSelected, UpdateSourceTrigger=PropertyChanged}"/>
--> -->
<Label Content="Custom Broadcast Addresses:" HorizontalAlignment="Left" <Label Content="Custom Broadcast Addresses:" HorizontalAlignment="Left"
@ -208,8 +164,6 @@
<TextBlock Text="Developed by Jeddunk" /> <TextBlock Text="Developed by Jeddunk" />
<TextBlock Text="Licensed under GNU GPLv3" /> <TextBlock Text="Licensed under GNU GPLv3" />
<TextBlock Text="Goldberg Emulator is owned by Mr. Goldberg and licensed under GNU LGPLv3" Margin="0,10,0,0"/> <TextBlock Text="Goldberg Emulator is owned by Mr. Goldberg and licensed under GNU LGPLv3" Margin="0,10,0,0"/>
<TextBlock Text="Contributors:" Margin="0,10,0,0" FontWeight="Bold"/>
<TextBlock Text="UrbanCMC" Margin="0,5,0,0"/>
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
</Label.ContentTemplate> </Label.ContentTemplate>

View File

@ -1,52 +1,26 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 16
VisualStudioVersion = 17.0.32112.339 VisualStudioVersion = 16.0.30717.126
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GoldbergGUI.Core", "GoldbergGUI.Core\GoldbergGUI.Core.csproj", "{FB205F05-83DE-4D87-8CE2-F7DA320944FD}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoldbergGUI.Core", "GoldbergGUI.Core\GoldbergGUI.Core.csproj", "{FB205F05-83DE-4D87-8CE2-F7DA320944FD}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GoldbergGUI.WPF", "GoldbergGUI.WPF\GoldbergGUI.WPF.csproj", "{84ED15D3-725C-43B1-B8C7-51759CAABBAA}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoldbergGUI.WPF", "GoldbergGUI.WPF\GoldbergGUI.WPF.csproj", "{84ED15D3-725C-43B1-B8C7-51759CAABBAA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4E7DA860-D7FD-4090-B7EC-6DA3974DC845}"
ProjectSection(SolutionItems) = preProject
COPYING = COPYING
README.md = README.md
EndProjectSection
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Debug|Any CPU.Build.0 = Debug|Any CPU {FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Debug|x64.ActiveCfg = Debug|x64
{FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Debug|x64.Build.0 = Debug|x64
{FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Debug|x86.ActiveCfg = Debug|x86
{FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Debug|x86.Build.0 = Debug|x86
{FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Release|Any CPU.ActiveCfg = Release|Any CPU {FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Release|Any CPU.Build.0 = Release|Any CPU {FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Release|Any CPU.Build.0 = Release|Any CPU
{FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Release|x64.ActiveCfg = Release|x64
{FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Release|x64.Build.0 = Release|x64
{FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Release|x86.ActiveCfg = Release|x86
{FB205F05-83DE-4D87-8CE2-F7DA320944FD}.Release|x86.Build.0 = Release|x86
{84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Debug|Any CPU.Build.0 = Debug|Any CPU {84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Debug|x64.ActiveCfg = Debug|Any CPU
{84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Debug|x64.Build.0 = Debug|Any CPU
{84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Debug|x86.ActiveCfg = Debug|x86
{84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Debug|x86.Build.0 = Debug|x86
{84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Release|Any CPU.ActiveCfg = Release|Any CPU {84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Release|Any CPU.Build.0 = Release|Any CPU {84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Release|Any CPU.Build.0 = Release|Any CPU
{84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Release|x64.ActiveCfg = Release|Any CPU
{84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Release|x64.Build.0 = Release|Any CPU
{84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Release|x86.ActiveCfg = Release|x86
{84ED15D3-725C-43B1-B8C7-51759CAABBAA}.Release|x86.Build.0 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -30,7 +30,7 @@ While the most used options are available right now, I am planning to support al
* Subscribed Groups * Subscribed Groups
* Mods (Steam Workshop) * Mods (Steam Workshop)
* Inventory and Items * Inventory and Items
* ~~Achievements~~ * Achievements
* Stats, Leaderboards * Stats, Leaderboards
* Controller (Steam Input) * Controller (Steam Input)
@ -40,20 +40,8 @@ Apart from those, I'm also always looking into improving the user experience of
Goldberg Emulator is owned by Mr. Goldberg and licensed under the GNU Lesser General Public License v3.0. Goldberg Emulator is owned by Mr. Goldberg and licensed under the GNU Lesser General Public License v3.0.
### Contributors
* [UrbanCMC](https://github.com/UrbanCMC/) - Implementation of achievements
### Dependencies
* AngleSharp
* MvvmCross
* NinjaNye
* Serilog
* SharpCompress
* sqlite-net-pcl
* My fork of SteamStorefrontAPI
## License ## License
GoldbergGUI is licensed under the GNU General Public License v3.0. GoldbergGUI is licensed under the GNU General Public License v3.0.
Dependencies will be listed ASAP.