Compare commits

...

39 Commits

Author SHA1 Message Date
1839ec226a Update to latest SteamStorefrontAPI 2023-12-25 21:49:37 +01:00
e65d92c12c Update to .NET 8; Update SteamStorefrontAPI 2023-12-25 13:34:46 +01:00
23162d090e Merge branch 'achievement-support' of https://git.jeddunk.xyz/jeddunk/GoldbergGUI into achievement-support 2022-02-07 10:57:01 +01:00
81de24ed29 UI overhaul, achievements are now listed in the GUI 2022-02-07 10:54:21 +01:00
UrbanCMC
176479387c Added support for retrieving game achievements 2022-02-07 10:54:21 +01:00
8eb3b0d60e UI overhaul, achievements are now listed in the GUI 2022-02-07 10:51:23 +01:00
2b77f9feb0 Merge branch 'dev' into achievement-support 2022-01-30 16:50:57 +01:00
fdbd0b882e update submodule 2022-01-30 16:47:01 +01:00
fda90b360e SteamStorefrontAPI modul fix 2022-01-30 16:40:36 +01:00
UrbanCMC
9545f83833 Added support for retrieving game achievements 2022-01-30 14:49:02 +01:00
71021ee767 code cleanup; build x86 per default 2021-12-22 13:15:28 +01:00
0239ffbb27 Fixed DLC crash 2021-08-29 22:42:33 +02:00
0be8f4ad8c Fixed saving/loading app_paths.txt 2021-04-29 16:44:37 +02:00
0c1c1226fb Removed Depot ID since they can be used independently from DLCs.
Improve log verbosity.
2021-04-28 17:14:29 +02:00
fd518e4504 Fixed crashes related to DLC.
Removed depot name value.
2021-04-28 13:51:06 +02:00
e17b0a18ca Changed how DLCs are referred as.
Optional DLC settings can now be shown (loading and saving is not implemented yet)
2021-04-28 13:33:04 +02:00
adc067d8a2 Merge branch 'master' into sqlite
# Conflicts:
#	GoldbergGUI.Core/Services/GoldbergService.cs
#	GoldbergGUI.Core/Services/SteamService.cs
2021-04-08 19:01:02 +02:00
e4dfd38dd3 Improved log verbosity. 2021-04-07 14:52:50 +02:00
373876c074 Downloaded Goldberg archive will now be validated by checking its file size. 2021-04-07 14:43:39 +02:00
62abe0e212 Improved extraction. 2021-04-07 14:22:59 +02:00
653e97beab Improve verbosity of logs 2021-04-07 13:52:56 +02:00
8e62880e23 Show error message if Goldberg could not be downloaded/extracted, prompting the user to do so manually.
Try to skip getting DLC from SteamDB on error.
2021-04-06 16:55:53 +02:00
b2cdd34cf8 Fixed issue with "UNIQUE" appid in cache during initialization.
Adjusted async code to new changes.
2021-03-24 10:21:59 +01:00
c823aa15fb sqlite functions are now (mostly) async 2021-03-21 16:55:58 +01:00
3bee1a5508 minor changes 2021-03-21 16:15:17 +01:00
13eb05fff9 Fixed crash when trying to get app by name from db 2021-03-21 15:53:56 +01:00
bc19e79d69 Added SQLite support 2021-03-21 15:20:46 +01:00
19f460d12d Fixed crashes related to global settings. 2021-03-21 13:06:40 +01:00
aac466802e Revert fdc2e027 2021-03-21 13:01:32 +01:00
a4d143825c Revert 89f43166 2021-03-21 13:00:58 +01:00
89f43166c7 Removed automatically getting list of dlc when finding id or opening file
Fixed db locking issue while getting dlc
2021-03-11 14:39:25 +01:00
fdc2e0277f Replaced old cache system with DB based one (using LiteDB)
Fixed crashes related to global settings
2021-03-11 14:09:45 +01:00
6196ed84b4 Continue adding options. 2021-02-17 14:27:54 +01:00
a00ec26e68 Added custom broadcast addresses gui element 2021-02-15 21:12:57 +01:00
3eeb893bc4 Disabled overlay setting, only supported in experimental build.
Started adding more options.
2021-02-15 20:52:58 +01:00
950844a14b Removed unused code. Minor code changes. 2021-01-19 20:37:13 +01:00
1602937be3 Automatically look up DLC when looking for AppID and when opening file with no config. 2021-01-19 13:35:13 +01:00
76f2d45f45 Added UI elements for global options.
Bigger minimal size for main window.
2021-01-19 13:23:31 +01:00
508d23da5f Added Extensions.cs.
Added GoldbergGlobalConfiguration.
Code cleanup.
2021-01-15 17:46:13 +01:00
21 changed files with 1134 additions and 393 deletions

1
.gitignore vendored
View File

@ -542,3 +542,4 @@ MigrationBackup/
# End of https://www.toptal.com/developers/gitignore/api/intellij,rider,visualstudio,dotnetcore,windows # End of https://www.toptal.com/developers/gitignore/api/intellij,rider,visualstudio,dotnetcore,windows
/GoldbergGUI.Core/Utils/Secrets.cs /GoldbergGUI.Core/Utils/Secrets.cs
/README.bbcode

3
.gitmodules vendored Normal file
View File

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

View File

@ -1,18 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<FileVersion>0.1.0</FileVersion> <FileVersion>0.3.0</FileVersion>
<AssemblyVersion>0.1.0</AssemblyVersion>
<Company>Jeddunk</Company> <Company>Jeddunk</Company>
<Platforms>AnyCPU;x86;x64</Platforms>
</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.26.0" /> <PackageReference Include="SharpCompress" Version="0.35.0" />
<PackageReference Include="SteamStorefrontAPI.NETStandard" Version="1.0.0" /> <PackageReference Include="sqlite-net-pcl" Version="1.7.335" />
<PackageReference Include="SteamStorefrontAPI" Version="2.0.1.421" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,13 +1,280 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization;
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable UnusedMember.Global
namespace GoldbergGUI.Core.Models namespace GoldbergGUI.Core.Models
{ {
public class GoldbergGlobalConfiguration
{
/// <summary>
/// Name of the user
/// </summary>
public string AccountName { get; set; }
/// <summary>
/// Steam64ID of the user
/// </summary>
public long UserSteamId { get; set; }
/// <summary>
/// language to be used
/// </summary>
public string Language { get; set; }
/// <summary>
/// Custom broadcast addresses (IPv4 or domain addresses)
/// </summary>
public List<string> CustomBroadcastIps { get; set; }
}
public class GoldbergConfiguration public class GoldbergConfiguration
{ {
/// <summary>
/// App ID of the game
/// </summary>
public int AppId { get; set; } public int AppId { get; set; }
public List<SteamApp> DlcList { get; set; } /// <summary>
/// List of DLC
/// </summary>
public List<DlcApp> DlcList { get; set; }
public List<int> Depots { get; set; }
public List<Group> SubscribedGroups { get; set; }
//public List<AppPath> AppPaths { get; set; }
public List<Achievement> Achievements { get; set; }
public List<Item> Items { get; set; }
public List<Leaderboard> Leaderboards { get; set; }
public List<Stat> Stats { get; set; }
// Add controller setting here!
/// <summary>
/// Set offline mode.
/// </summary>
public bool Offline { get; set; } public bool Offline { get; set; }
/// <summary>
/// Disable networking (game is set to online, however all outgoing network connectivity will be disabled).
/// </summary>
public bool DisableNetworking { get; set; } public bool DisableNetworking { get; set; }
/// <summary>
/// Disable overlay (experimental only).
/// </summary>
public bool DisableOverlay { get; set; } public bool DisableOverlay { get; set; }
public GoldbergGlobalConfiguration OverwrittenGlobalConfiguration { get; set; }
}
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;
}
/// <summary>
/// Path to DLC (relative to Steam API DLL) (optional)
/// </summary>
public string AppPath { get; set; }
}
public class Group
{
/// <summary>
/// ID of group (https://steamcommunity.com/gid/103582791433980119/memberslistxml/?xml=1).
/// </summary>
public int GroupId { get; set; }
/// <summary>
/// Name of group.
/// </summary>
public string GroupName { get; set; }
/// <summary>
/// App ID of game associated with group (https://steamcommunity.com/games/218620/memberslistxml/?xml=1).
/// </summary>
public int AppId { get; set; }
}
public class Achievement
{
/// <summary>
/// Achievement description.
/// </summary>
[JsonPropertyName("description")]
public string Description { get; set; }
/// <summary>
/// Human readable name, as shown on webpage, game library, overlay, etc.
/// </summary>
[JsonPropertyName("displayName")]
public string DisplayName { get; set; }
/// <summary>
/// Is achievement hidden? 0 = false, else true.
/// </summary>
[JsonPropertyName("hidden")]
public int Hidden { get; set; }
/// <summary>
/// Path to icon when unlocked (colored).
/// </summary>
[JsonPropertyName("icon")]
public string Icon { get; set; }
/// <summary>
/// Path to icon when locked (grayed out).
/// </summary>
// ReSharper disable once StringLiteralTypo
[JsonPropertyName("icongray")]
public string IconGray { get; set; }
/// <summary>
/// Internal name.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }
}
public class Item
{
[JsonPropertyName("Timestamp")]
public DateTimeOffset Timestamp { get; set; }
[JsonPropertyName("modified")]
public string Modified { get; set; }
[JsonPropertyName("date_created")]
public string DateCreated { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("display_type")]
public string DisplayType { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("bundle")]
public string Bundle { get; set; }
[JsonPropertyName("description")]
public string Description { get; set; }
[JsonPropertyName("background_color")]
public string BackgroundColor { get; set; }
[JsonPropertyName("icon_url")]
public Uri IconUrl { get; set; }
[JsonPropertyName("icon_url_large")]
public Uri IconUrlLarge { get; set; }
[JsonPropertyName("name_color")]
public string NameColor { get; set; }
[JsonPropertyName("tradable")]
// [JsonConverter(typeof(PurpleParseStringConverter))]
public bool Tradable { get; set; }
[JsonPropertyName("marketable")]
// [JsonConverter(typeof(PurpleParseStringConverter))]
public bool Marketable { get; set; }
[JsonPropertyName("commodity")]
// [JsonConverter(typeof(PurpleParseStringConverter))]
public bool Commodity { get; set; }
[JsonPropertyName("drop_interval")]
// [JsonConverter(typeof(FluffyParseStringConverter))]
public long DropInterval { get; set; }
[JsonPropertyName("drop_max_per_window")]
// [JsonConverter(typeof(FluffyParseStringConverter))]
public long DropMaxPerWindow { get; set; }
// ReSharper disable once StringLiteralTypo
[JsonPropertyName("workshopid")]
// [JsonConverter(typeof(FluffyParseStringConverter))]
public long WorkshopId { get; set; }
[JsonPropertyName("tw_unique_to_own")]
// [JsonConverter(typeof(PurpleParseStringConverter))]
public bool TwUniqueToOwn { get; set; }
[JsonPropertyName("item_quality")]
// [JsonConverter(typeof(FluffyParseStringConverter))]
public long ItemQuality { get; set; }
[JsonPropertyName("tw_price")]
public string TwPrice { get; set; }
[JsonPropertyName("tw_type")]
public string TwType { get; set; }
[JsonPropertyName("tw_client_visible")]
// [JsonConverter(typeof(FluffyParseStringConverter))]
public long TwClientVisible { get; set; }
[JsonPropertyName("tw_icon_small")]
public string TwIconSmall { get; set; }
[JsonPropertyName("tw_icon_large")]
public string TwIconLarge { get; set; }
[JsonPropertyName("tw_description")]
public string TwDescription { get; set; }
[JsonPropertyName("tw_client_name")]
public string TwClientName { get; set; }
[JsonPropertyName("tw_client_type")]
public string TwClientType { get; set; }
[JsonPropertyName("tw_rarity")]
public string TwRarity { get; set; }
}
public class Leaderboard
{
public string Name { get; set; }
public SortMethod SortMethodSetting { get; set; }
public DisplayType DisplayTypeSetting { get; set; }
public enum SortMethod
{
None,
Ascending,
Descending
}
public enum DisplayType
{
None,
Numeric,
TimeSeconds,
TimeMilliseconds
}
}
public class Stat
{
public string Name { get; set; }
public StatType StatTypeSetting { get; set; }
public string Value { get; set; }
public enum StatType
{
Int,
Float,
AvgRate
}
} }
} }

View File

@ -1,7 +1,6 @@
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
@ -11,35 +10,41 @@ using GoldbergGUI.Core.Utils;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace GoldbergGUI.Core.Models namespace GoldbergGUI.Core.Models
{ {
[Table("steamapp")]
public class SteamApp public class SteamApp
{ {
private string _name; [JsonPropertyName("appid")]
private string _comparableName; [Column("appid")]
[JsonPropertyName("appid")] public int AppId { get; set; } [PrimaryKey]
public int AppId { get; set; }
/// <summary>
/// Name of Steam app
/// </summary>
[JsonPropertyName("name")] [JsonPropertyName("name")]
public string Name [Column("name")]
{ public string Name { get; set; }
get => _name;
set
{
_name = value;
_comparableName = Regex.Replace(value, Misc.SpecialCharsRegex, "").ToLower();
}
}
public bool CompareName(string value) => _comparableName.Equals(value); [Column("comparable_name")]
public string ComparableName { get; set; }
public AppType type { get; set; } /// <summary>
/// App type (Game, DLC, ...)
/// </summary>
[Column("type")]
public string AppType { get; set; }
public override string ToString() public override string ToString()
{ {
return $"{AppId}={Name}"; return $"{AppId}={Name}";
} }
[JsonPropertyName("last_modified")] public long LastModified { get; set; } [JsonPropertyName("last_modified")]
[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; }
} }
@ -67,19 +72,4 @@ 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,3 +1,6 @@
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;
@ -5,10 +8,8 @@ using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GoldbergGUI.Core.Models; using System.Windows;
using MvvmCross.Logging;
namespace GoldbergGUI.Core.Services namespace GoldbergGUI.Core.Services
{ {
@ -17,24 +18,25 @@ namespace GoldbergGUI.Core.Services
// does file copy stuff // does file copy stuff
public interface IGoldbergService public interface IGoldbergService
{ {
public Task<(string accountName, long userSteamId, string language)> Initialize(IMvxLog log); public Task<GoldbergGlobalConfiguration> Initialize(IMvxLog log);
public Task<GoldbergConfiguration> Read(string path); public Task<GoldbergConfiguration> Read(string path);
public Task Save(string path, GoldbergConfiguration configuration); public Task Save(string path, GoldbergConfiguration configuration);
public Task<(string accountName, long steamId, string language)> GetGlobalSettings(); public Task<GoldbergGlobalConfiguration> GetGlobalSettings();
public Task SetGlobalSettings(string accountName, long userSteamId, string language); 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;
private const string GoldbergUrl = "https://mr_goldberg.gitlab.io/goldberg_emulator/"; private const string DefaultAccountName = "Mr_Goldberg";
private const long DefaultSteamId = 76561197960287930;
private const string DefaultLanguage = "english"; private const string DefaultLanguage = "english";
private const string GoldbergUrl = "https://mr_goldberg.gitlab.io/goldberg_emulator/";
private readonly string _goldbergZipPath = Path.Combine(Directory.GetCurrentDirectory(), "goldberg.zip"); private readonly string _goldbergZipPath = Path.Combine(Directory.GetCurrentDirectory(), "goldberg.zip");
private readonly string _goldbergPath = Path.Combine(Directory.GetCurrentDirectory(), "goldberg"); private readonly string _goldbergPath = Path.Combine(Directory.GetCurrentDirectory(), "goldberg");
@ -46,6 +48,10 @@ namespace GoldbergGUI.Core.Services
private readonly string _userSteamIdPath = Path.Combine(GlobalSettingsPath, "settings/user_steam_id.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 _languagePath = Path.Combine(GlobalSettingsPath, "settings/language.txt");
private readonly string _customBroadcastIpsPath =
Path.Combine(GlobalSettingsPath, "settings/custom_broadcasts.txt");
// ReSharper disable StringLiteralTypo
private readonly List<string> _interfaceNames = new List<string> private readonly List<string> _interfaceNames = new List<string>
{ {
"SteamClient", "SteamClient",
@ -76,68 +82,124 @@ namespace GoldbergGUI.Core.Services
// Call Download // Call Download
// Get global settings // Get global settings
public async Task<(string accountName, long userSteamId, string language)> Initialize(IMvxLog log) public async Task<GoldbergGlobalConfiguration> Initialize(IMvxLog log)
{ {
_log = log; _log = log;
var download = await Download().ConfigureAwait(false); 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); return await GetGlobalSettings().ConfigureAwait(false);
} }
public async Task<(string accountName, long steamId, string language)> GetGlobalSettings() public async Task<GoldbergGlobalConfiguration> GetGlobalSettings()
{ {
_log.Info("Getting global settings..."); _log.Info("Getting global settings...");
var accountName = "Account name..."; var accountName = DefaultAccountName;
long steamId = -1; var steamId = DefaultSteamId;
var language = DefaultLanguage; var language = DefaultLanguage;
var customBroadcastIps = new List<string>();
if (!File.Exists(GlobalSettingsPath)) Directory.CreateDirectory(Path.Join(GlobalSettingsPath, "settings"));
await Task.Run(() => await Task.Run(() =>
{ {
if (File.Exists(_accountNamePath)) accountName = File.ReadLines(_accountNamePath).First().Trim(); if (File.Exists(_accountNamePath)) accountName = File.ReadLines(_accountNamePath).First().Trim();
if (File.Exists(_userSteamIdPath) && if (File.Exists(_userSteamIdPath) &&
!long.TryParse(File.ReadLines(_userSteamIdPath).First().Trim(), out steamId) && !long.TryParse(File.ReadLines(_userSteamIdPath).First().Trim(), out steamId) &&
steamId < 76561197960265729 && steamId > 76561202255233023) steamId < 76561197960265729 && steamId > 76561202255233023)
_log.Error("Invalid User Steam ID!"); {
if (File.Exists(_languagePath)) language = File.ReadLines(_languagePath).First().Trim(); _log.Error("Invalid User Steam ID! Using default Steam ID...");
}).ConfigureAwait(false); steamId = DefaultSteamId;
return (accountName, steamId, language);
} }
public async Task SetGlobalSettings(string accountName, long userSteamId, string language) 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,
UserSteamId = steamId,
Language = language,
CustomBroadcastIps = customBroadcastIps
};
}
public async Task SetGlobalSettings(GoldbergGlobalConfiguration c)
{
var accountName = c.AccountName;
var userSteamId = c.UserSteamId;
var language = c.Language;
var customBroadcastIps = c.CustomBroadcastIps;
_log.Info("Setting global settings..."); _log.Info("Setting global settings...");
if (accountName != null && accountName != "Account name...") // Account Name
if (!string.IsNullOrEmpty(accountName))
{ {
_log.Info("Setting account name..."); _log.Info("Setting account name...");
if (!File.Exists(_accountNamePath))
await File.Create(_accountNamePath).DisposeAsync().ConfigureAwait(false);
await File.WriteAllTextAsync(_accountNamePath, accountName).ConfigureAwait(false); await File.WriteAllTextAsync(_accountNamePath, accountName).ConfigureAwait(false);
} }
else else
{ {
_log.Info("Invalid account name! Skipping..."); _log.Info("Invalid account name! Skipping...");
await File.WriteAllTextAsync(_accountNamePath, "Goldberg").ConfigureAwait(false); if (!File.Exists(_accountNamePath))
await File.Create(_accountNamePath).DisposeAsync().ConfigureAwait(false);
await File.WriteAllTextAsync(_accountNamePath, DefaultAccountName).ConfigureAwait(false);
} }
// User SteamID
if (userSteamId >= 76561197960265729 && userSteamId <= 76561202255233023) if (userSteamId >= 76561197960265729 && userSteamId <= 76561202255233023)
{ {
_log.Info("Setting user Steam ID..."); _log.Info("Setting user Steam ID...");
if (!File.Exists(_userSteamIdPath))
await File.Create(_userSteamIdPath).DisposeAsync().ConfigureAwait(false);
await File.WriteAllTextAsync(_userSteamIdPath, userSteamId.ToString()).ConfigureAwait(false); await File.WriteAllTextAsync(_userSteamIdPath, userSteamId.ToString()).ConfigureAwait(false);
} }
else else
{ {
_log.Info("Invalid user Steam ID! Skipping..."); _log.Info("Invalid user Steam ID! Skipping...");
await Task.Run(() => File.Delete(_userSteamIdPath)).ConfigureAwait(false); if (!File.Exists(_userSteamIdPath))
await File.Create(_userSteamIdPath).DisposeAsync().ConfigureAwait(false);
await File.WriteAllTextAsync(_userSteamIdPath, DefaultSteamId.ToString()).ConfigureAwait(false);
} }
if (language != null) // Language
if (!string.IsNullOrEmpty(language))
{ {
_log.Info("Setting language..."); _log.Info("Setting language...");
if (!File.Exists(_languagePath))
await File.Create(_languagePath).DisposeAsync().ConfigureAwait(false);
await File.WriteAllTextAsync(_languagePath, language).ConfigureAwait(false); await File.WriteAllTextAsync(_languagePath, language).ConfigureAwait(false);
} }
else else
{ {
_log.Info("Invalid language! Skipping..."); _log.Info("Invalid language! Skipping...");
if (!File.Exists(_languagePath))
await File.Create(_languagePath).DisposeAsync().ConfigureAwait(false);
await File.WriteAllTextAsync(_languagePath, DefaultLanguage).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 =
customBroadcastIps.Aggregate("", (current, address) => $"{current}{address}\n");
if (!File.Exists(_customBroadcastIpsPath))
await File.Create(_customBroadcastIpsPath).DisposeAsync().ConfigureAwait(false);
await File.WriteAllTextAsync(_customBroadcastIpsPath, result).ConfigureAwait(false);
}
else
{
_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 // If first time, call GenerateInterfaces
@ -146,7 +208,8 @@ namespace GoldbergGUI.Core.Services
{ {
_log.Info("Reading configuration..."); _log.Info("Reading configuration...");
var appId = -1; var appId = -1;
var dlcList = new List<SteamApp>(); var achievementList = new List<Achievement>();
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))
{ {
@ -159,7 +222,21 @@ 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...");
@ -170,12 +247,27 @@ namespace GoldbergGUI.Core.Services
{ {
var match = expression.Match(line); var match = expression.Match(line);
if (match.Success) if (match.Success)
dlcList.Add(new SteamApp dlcList.Add(new DlcApp()
{ {
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
{ {
@ -185,6 +277,7 @@ 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")),
@ -212,6 +305,7 @@ 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...");
@ -221,52 +315,138 @@ namespace GoldbergGUI.Core.Services
} }
// create steam_appid.txt // 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 // Achievements + Images
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)
{ {
var dlcString = ""; _log.Info("Saving DLC settings...");
c.DlcList.ForEach(x => dlcString += $"{x}\n"); var dlcContent = "";
await File.WriteAllTextAsync(Path.Combine(path, "steam_settings", "DLC.txt"), dlcString) //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); .ConfigureAwait(false);
} }
else 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"))) 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"));
} }
} }
@ -275,17 +455,21 @@ 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);
} }
@ -297,11 +481,11 @@ namespace GoldbergGUI.Core.Services
return steamSettingsDirExists && steamAppIdTxtExists; return steamSettingsDirExists && steamAppIdTxtExists;
} }
private async Task<bool> Download()
{
// Get webpage // Get webpage
// Get job id, compare with local if exists, save it if false or missing // Get job id, compare with local if exists, save it if false or missing
// Get latest archive if mismatch, call Extract // Get latest archive if mismatch, call Extract
public async Task<bool> Download()
{
_log.Info("Initializing download..."); _log.Info("Initializing download...");
if (!Directory.Exists(_goldbergPath)) Directory.CreateDirectory(_goldbergPath); if (!Directory.Exists(_goldbergPath)) Directory.CreateDirectory(_goldbergPath);
var client = new HttpClient(); var client = new HttpClient();
@ -312,6 +496,8 @@ namespace GoldbergGUI.Core.Services
var jobIdPath = Path.Combine(_goldbergPath, "job_id"); var jobIdPath = Path.Combine(_goldbergPath, "job_id");
var match = regex.Match(body); var match = regex.Match(body);
if (File.Exists(jobIdPath)) if (File.Exists(jobIdPath))
{
try
{ {
_log.Info("Check if update is needed..."); _log.Info("Check if update is needed...");
var jobIdLocal = Convert.ToInt32(File.ReadLines(jobIdPath).First().Trim()); var jobIdLocal = Convert.ToInt32(File.ReadLines(jobIdPath).First().Trim());
@ -323,45 +509,106 @@ namespace GoldbergGUI.Core.Services
return false; return false;
} }
} }
catch (Exception)
{
_log.Error("An error occured, local Goldberg setup might be broken!");
}
}
_log.Info("Starting download..."); _log.Info("Starting download...");
await StartDownload(client, match.Value).ConfigureAwait(false); await StartDownload(match.Value).ConfigureAwait(false);
return true; return true;
} }
private async Task StartDownload(HttpClient client, string downloadUrl) private async Task StartDownload(string downloadUrl)
{ {
try
{
var client = new HttpClient();
_log.Debug(downloadUrl); _log.Debug(downloadUrl);
await using var fileStream = File.OpenWrite(_goldbergZipPath); await using var fileStream = File.OpenWrite(_goldbergZipPath);
//client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead) //client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead)
var task = GetFileAsync(client, downloadUrl, fileStream).ConfigureAwait(false); var httpRequestMessage = new HttpRequestMessage(HttpMethod.Head, downloadUrl);
await task; var headResponse = await client.SendAsync(httpRequestMessage).ConfigureAwait(false);
if (task.GetAwaiter().IsCompleted) 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!"); _log.Info("Download finished!");
} }
} else
private static async Task GetFileAsync(HttpClient client, string requestUri, Stream destination,
CancellationToken cancelToken = default)
{ {
var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancelToken) throw new Exception("File size does not match!");
.ConfigureAwait(false); }
await using var download = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); }).ConfigureAwait(false);
await download.CopyToAsync(destination, cancelToken).ConfigureAwait(false); }
if (destination.CanSeek) destination.Position = 0; catch (Exception e)
{
ShowErrorMessage();
_log.Error(e.ToString);
Environment.Exit(1);
}
} }
// Empty subfolder ./goldberg/ // Empty subfolder ./goldberg/
// Extract all from archive to subfolder ./goldberg/ // Extract all from archive to subfolder ./goldberg/
public async Task Extract(string archivePath) private async Task Extract(string archivePath)
{ {
var errorOccured = false;
_log.Debug("Start extraction..."); _log.Debug("Start extraction...");
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(() => await Task.Run(() =>
{ {
Directory.Delete(_goldbergPath, true); try
ZipFile.ExtractToDirectory(archivePath, _goldbergPath); {
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); }).ConfigureAwait(false);
_log.Debug("Extract done!"); }
}
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);
}
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 // https://gitlab.com/Mr_Goldberg/goldberg_emulator/-/blob/master/generate_interfaces_file.cpp
@ -436,5 +683,22 @@ 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,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.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
{ {
@ -21,23 +20,21 @@ namespace GoldbergGUI.Core.Services
public interface ISteamService public interface ISteamService
{ {
public Task Initialize(IMvxLog log); public Task Initialize(IMvxLog log);
public IEnumerable<SteamApp> GetListOfAppsByName(string name); public Task<IEnumerable<SteamApp>> GetListOfAppsByName(string name);
public SteamApp GetAppByName(string name); public Task<SteamApp> GetAppByName(string name);
public SteamApp GetAppById(int appid); public Task<SteamApp> GetAppById(int appid);
public Task<List<SteamApp>> GetListOfDlc(SteamApp steamApp, bool useSteamDb); public Task<List<Achievement>> GetListOfAchievements(SteamApp steamApp);
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 AppType SteamAppType { get; } public string SteamAppType { get; }
public HashSet<SteamApp> Cache { get; set; } = new HashSet<SteamApp>();
public SteamCache(string filename, string uri, Type apiVersion, AppType steamAppType) public SteamCache(string uri, Type apiVersion, string steamAppType)
{ {
Filename = filename;
SteamUri = uri; SteamUri = uri;
ApiVersion = apiVersion; ApiVersion = apiVersion;
SteamAppType = steamAppType; SteamAppType = steamAppType;
@ -49,32 +46,30 @@ namespace GoldbergGUI.Core.Services
public class SteamService : ISteamService public class SteamService : ISteamService
{ {
// ReSharper disable StringLiteralTypo // ReSharper disable StringLiteralTypo
private readonly Dictionary<AppType, SteamCache> _caches = private readonly Dictionary<string, SteamCache> _caches =
new Dictionary<AppType, SteamCache> new Dictionary<string, SteamCache>
{ {
{ {
AppType.Game, AppTypeGame,
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),
AppType.Game AppTypeGame
) )
}, },
{ {
AppType.DLC, AppTypeDlc,
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),
AppType.DLC AppTypeDlc
) )
} }
}; };
@ -84,147 +79,160 @@ 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)
{ {
if (type == typeof(SteamAppsV1)) return type == typeof(SteamAppsV2)
return JsonSerializer.Deserialize<SteamAppsV1>(cacheString); ? (SteamApps)JsonSerializer.Deserialize<SteamAppsV2>(cacheString)
else if (type == typeof(SteamAppsV2)) : JsonSerializer.Deserialize<SteamAppsV1>(cacheString);
return JsonSerializer.Deserialize<SteamAppsV2>(cacheString);
return null;
} }
foreach (var (k, c) in _caches)
{
_log = log; _log = log;
_log.Info($"Updating cache ({k.Value})..."); _db = new SQLiteAsyncConnection(Database);
var updateNeeded = //_db.CreateTable<SteamApp>();
DateTime.Now.Subtract(File.GetLastWriteTimeUtc(c.Filename)).TotalDays >= 1; await _db.CreateTableAsync<SteamApp>()
SteamApps steamApps; //.ContinueWith(x => _log.Debug("Table success!"))
try
{
var temp = await GetCache(updateNeeded, c.SteamUri, c.Filename)
.ConfigureAwait(false); .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);
}
try var countAsync = await _db.Table<SteamApp>().CountAsync().ConfigureAwait(false);
if (DateTime.Now.Subtract(File.GetLastWriteTimeUtc(Database)).TotalDays >= 1 || countAsync == 0)
{ {
var cacheRaw = new HashSet<SteamApp>(steamApps.AppList.Apps); foreach (var (appType, steamCache) in _caches)
{
_log.Info($"Updating cache ({appType})...");
bool haveMoreResults;
long lastAppId = 0;
var client = new HttpClient();
var cacheRaw = new HashSet<SteamApp>();
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<SteamApp>(); var cache = new HashSet<SteamApp>();
foreach (var steamApp in cacheRaw) foreach (var steamApp in cacheRaw)
{ {
steamApp.type = c.SteamAppType; steamApp.AppType = steamCache.SteamAppType;
steamApp.ComparableName = PrepareStringToCompare(steamApp.Name);
cache.Add(steamApp); cache.Add(steamApp);
} }
c.Cache = cache; await _db.InsertAllAsync(cache, "OR IGNORE").ConfigureAwait(false);
_log.Info("Loaded cache into memory!");
} }
catch (NullReferenceException e) }
}
public async Task<IEnumerable<SteamApp>> GetListOfAppsByName(string name)
{ {
Console.WriteLine(e); var query = await _db.Table<SteamApp>()
throw; .Where(x => x.AppType == AppTypeGame).ToListAsync().ConfigureAwait(false);
} var listOfAppsByName = query.Search(x => x.Name)
}
}
private async Task<string> GetCache(bool updateNeeded, string steamUri, string cachePath)
{
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<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 SteamApp GetAppByName(string name) public async Task<SteamApp> GetAppByName(string name)
{ {
_log.Info($"Trying to get app {name}"); _log.Info($"Trying to get app {name}");
var comparableName = Regex.Replace(name, Misc.SpecialCharsRegex, "").ToLower(); var comparableName = PrepareStringToCompare(name);
var app = _caches[AppType.Game].Cache.FirstOrDefault(x => x.CompareName(comparableName)); var app = await _db.Table<SteamApp>()
.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 SteamApp GetAppById(int appid) public async Task<SteamApp> GetAppById(int appid)
{ {
_log.Info($"Trying to get app with ID {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<SteamApp>().Where(x => x.AppType == AppTypeGame)
.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<SteamApp>> GetListOfDlc(SteamApp steamApp, bool useSteamDb) public async Task<List<Achievement>> GetListOfAchievements(SteamApp steamApp)
{ {
_log.Info("Get DLC"); 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}");
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 == 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)) var result = await _db.Table<SteamApp>().Where(z => z.AppType == AppTypeDlc)
?? new SteamApp {AppId = x, Name = $"Unknown DLC {x}"}; .FirstOrDefaultAsync(y => y.AppId.Equals(x)).ConfigureAwait(true)
dlcList.Add(result); ?? 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..."); _log.Info("Got DLC successfully...");
// Get DLC from SteamDB // Get DLC from SteamDB
// Get Cloudflare cookie // Get Cloudflare cookie (not implemented)
// Scrape and parse HTML page // Scrape and parse HTML page
// Add missing to DLC list // Add missing to DLC list
// ReSharper disable once InvertIf // Return current list if we don't intend to use SteamDB
if (useSteamDb) if (!useSteamDb) return dlcList;
try
{ {
var steamDbUri = new Uri($"https://steamdb.info/app/{steamApp.AppId}/dlc/"); var steamDbUri = new Uri($"https://steamdb.info/app/{steamApp.AppId}/dlc/");
var client = new HttpClient(); var client = new HttpClient();
client.DefaultRequestHeaders.UserAgent.ParseAdd(UserAgent); client.DefaultRequestHeaders.UserAgent.ParseAdd(UserAgent);
_log.Info("Get SteamDB App"); _log.Info($"Get SteamDB App {steamApp}");
var httpCall = client.GetAsync(steamDbUri); var httpCall = client.GetAsync(steamDbUri);
var response = await httpCall.ConfigureAwait(false); var response = await httpCall.ConfigureAwait(false);
_log.Debug(httpCall.Status.ToString()); _log.Debug(httpCall.Status.ToString());
@ -240,6 +248,7 @@ namespace GoldbergGUI.Core.Services
var query1 = doc.QuerySelector("#dlc"); var query1 = doc.QuerySelector("#dlc");
if (query1 != null) if (query1 != null)
{ {
_log.Info("Got list of DLC from SteamDB.");
var query2 = query1.QuerySelectorAll(".app"); var query2 = query1.QuerySelectorAll(".app");
foreach (var element in query2) foreach (var element in query2)
{ {
@ -248,7 +257,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 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)); var i = dlcList.FindIndex(x => x.AppId.Equals(dlcApp.AppId));
if (i > -1) if (i > -1)
{ {
@ -268,6 +277,11 @@ namespace GoldbergGUI.Core.Services
_log.Error("Could not get DLC from SteamDB!"); _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 else
{ {
@ -281,5 +295,10 @@ namespace GoldbergGUI.Core.Services
return dlcList; return dlcList;
} }
private static string PrepareStringToCompare(string name)
{
return Regex.Replace(name, Misc.AlphaNumOnlyRegex, "").ToLower();
}
} }
} }

View File

@ -1,8 +1,7 @@
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

@ -0,0 +1,20 @@
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace GoldbergGUI.Core.Utils
{
public static class Extensions
{
public static async Task GetFileAsync(this HttpClient client, string requestUri, Stream destination,
CancellationToken cancelToken = default)
{
var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancelToken)
.ConfigureAwait(false);
await using var download = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
await download.CopyToAsync(destination, cancelToken).ConfigureAwait(false);
if (destination.CanSeek) destination.Position = 0;
}
}
}

View File

@ -1,42 +1,25 @@
using System.Collections.ObjectModel;
namespace GoldbergGUI.Core.Utils namespace GoldbergGUI.Core.Utils
{ {
public class Misc public static class Misc
{ {
public const string SpecialCharsRegex = "[^0-9a-zA-Z]+"; public const string AlphaNumOnlyRegex = "[^0-9a-zA-Z]+";
public const string DefaultLanguageSelection = "english"; }
public static readonly ObservableCollection<string> DefaultLanguages = new ObservableCollection<string>(new[]
public class GlobalHelp
{ {
"arabic", public static string Header =>
"bulgarian", "Information\n";
"schinese",
"tchinese", public static string TextPreLink =>
"czech", "Usually these settings are saved under";
"danish",
"dutch", public static string Link => "%APPDATA%\\Goldberg SteamEmu Saves\\settings";
"english",
"finnish", public static string TextPostLink =>
"french", ", which makes these " +
"german", "available for every game that uses the Goldberg Emulator. However, if you want to set specific settings " +
"greek", "for certain games (e.g. different language), you can remove the \"Global\" checkmark next to the option " +
"hungarian", "and then change it. If you want to remove that setting, just empty the field while \"Global\" is " +
"italian", "unchecked. (Not implemented yet!)";
"japanese",
"koreana",
"norwegian",
"polish",
"portuguese",
"brazilian",
"romanian",
"russian",
"spanish",
"latam",
"swedish",
"thai",
"turkish",
"ukrainian",
"vietnamese"
});
} }
} }

View File

@ -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.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
@ -9,14 +17,6 @@ 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 Microsoft.Win32;
using MvvmCross;
using MvvmCross.Commands;
using MvvmCross.Logging;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
namespace GoldbergGUI.Core.ViewModels namespace GoldbergGUI.Core.ViewModels
{ {
@ -30,7 +30,8 @@ namespace GoldbergGUI.Core.ViewModels
private int _appId; private int _appId;
//private SteamApp _currentGame; //private SteamApp _currentGame;
private ObservableCollection<SteamApp> _dlcs; private ObservableCollection<Achievement> _achievements;
private ObservableCollection<DlcApp> _dlcs;
private string _accountName; private string _accountName;
private long _steamId; private long _steamId;
private bool _offline; private bool _offline;
@ -71,11 +72,11 @@ namespace GoldbergGUI.Core.ViewModels
SteamLanguages = new ObservableCollection<string>(_goldberg.Languages()); SteamLanguages = new ObservableCollection<string>(_goldberg.Languages());
ResetForm(); ResetForm();
await _steam.Initialize(_logProvider.GetLogFor<SteamService>()).ConfigureAwait(false); await _steam.Initialize(_logProvider.GetLogFor<SteamService>()).ConfigureAwait(false);
var (accountName, userSteamId, language) = var globalConfiguration =
await _goldberg.Initialize(_logProvider.GetLogFor<GoldbergService>()).ConfigureAwait(false); await _goldberg.Initialize(_logProvider.GetLogFor<GoldbergService>()).ConfigureAwait(false);
AccountName = accountName; AccountName = globalConfiguration.AccountName;
SteamId = userSteamId; SteamId = globalConfiguration.UserSteamId;
SelectedLanguage = language; SelectedLanguage = globalConfiguration.Language;
} }
catch (Exception e) catch (Exception e)
{ {
@ -130,7 +131,7 @@ namespace GoldbergGUI.Core.ViewModels
} }
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
public ObservableCollection<SteamApp> DLCs public ObservableCollection<DlcApp> DLCs
{ {
get => _dlcs; get => _dlcs;
set set
@ -142,6 +143,16 @@ 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;
@ -221,7 +232,15 @@ namespace GoldbergGUI.Core.ViewModels
} }
} }
public bool DllSelected => !DllPath.Contains("Path to game's steam_api(64).dll"); public bool DllSelected
{
get
{
var value = !DllPath.Contains("Path to game's steam_api(64).dll");
if (!value) _log.Warn("No DLL selected! Skipping...");
return value;
}
}
public ObservableCollection<string> SteamLanguages public ObservableCollection<string> SteamLanguages
{ {
@ -244,9 +263,6 @@ namespace GoldbergGUI.Core.ViewModels
} }
} }
public string AboutVersionText =>
FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
public string StatusText public string StatusText
{ {
get => _statusText; get => _statusText;
@ -257,6 +273,11 @@ namespace GoldbergGUI.Core.ViewModels
} }
} }
public static string AboutVersionText =>
FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
public static GlobalHelp G => new GlobalHelp();
// COMMANDS // // COMMANDS //
public IMvxCommand OpenFileCommand => new MvxAsyncCommand(OpenFile); public IMvxCommand OpenFileCommand => new MvxAsyncCommand(OpenFile);
@ -282,6 +303,7 @@ namespace GoldbergGUI.Core.ViewModels
DllPath = dialog.FileName; DllPath = dialog.FileName;
await ReadConfig().ConfigureAwait(false); await ReadConfig().ConfigureAwait(false);
if (!GoldbergApplied) await GetListOfDlc().ConfigureAwait(false);
MainWindowEnabled = true; MainWindowEnabled = true;
StatusText = "Ready."; StatusText = "Ready.";
} }
@ -310,7 +332,7 @@ namespace GoldbergGUI.Core.ViewModels
MainWindowEnabled = false; MainWindowEnabled = false;
StatusText = "Trying to find AppID..."; StatusText = "Trying to find AppID...";
var appByName = _steam.GetAppByName(_gameName); var appByName = await _steam.GetAppByName(_gameName).ConfigureAwait(false);
if (appByName != null) if (appByName != null)
{ {
GameName = appByName.Name; GameName = appByName.Name;
@ -318,7 +340,7 @@ namespace GoldbergGUI.Core.ViewModels
} }
else else
{ {
var list = _steam.GetListOfAppsByName(GameName); var list = await _steam.GetListOfAppsByName(GameName).ConfigureAwait(false);
var steamApps = list as SteamApp[] ?? list.ToArray(); var steamApps = list as SteamApp[] ?? list.ToArray();
if (steamApps.Length == 1) if (steamApps.Length == 1)
{ {
@ -338,7 +360,7 @@ namespace GoldbergGUI.Core.ViewModels
await FindIdInList(steamApps).ConfigureAwait(false); await FindIdInList(steamApps).ConfigureAwait(false);
} }
} }
await GetListOfDlc().ConfigureAwait(false);
MainWindowEnabled = true; MainWindowEnabled = true;
StatusText = "Ready."; StatusText = "Ready.";
} }
@ -353,10 +375,37 @@ namespace GoldbergGUI.Core.ViewModels
return; 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; 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()
@ -371,7 +420,7 @@ namespace GoldbergGUI.Core.ViewModels
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<SteamApp>(listOfDlc); DLCs = new MvxObservableCollection<DlcApp>(listOfDlc);
MainWindowEnabled = true; MainWindowEnabled = true;
if (DLCs.Count > 0) if (DLCs.Count > 0)
{ {
@ -389,12 +438,14 @@ namespace GoldbergGUI.Core.ViewModels
private async Task SaveConfig() private async Task SaveConfig()
{ {
_log.Info("Saving global settings..."); _log.Info("Saving global settings...");
await _goldberg.SetGlobalSettings(AccountName, SteamId, SelectedLanguage).ConfigureAwait(false); var globalConfiguration = new GoldbergGlobalConfiguration
if (!DllSelected)
{ {
_log.Error("No DLL selected!"); AccountName = AccountName,
return; UserSteamId = SteamId,
} Language = SelectedLanguage
};
await _goldberg.SetGlobalSettings(globalConfiguration).ConfigureAwait(false);
if (!DllSelected) return;
_log.Info("Saving Goldberg settings..."); _log.Info("Saving Goldberg settings...");
if (!GetDllPathDir(out var dirPath)) return; if (!GetDllPathDir(out var dirPath)) return;
@ -403,6 +454,7 @@ namespace GoldbergGUI.Core.ViewModels
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,
@ -418,12 +470,11 @@ namespace GoldbergGUI.Core.ViewModels
private async Task ResetConfig() private async Task ResetConfig()
{ {
(AccountName, SteamId, SelectedLanguage) = await _goldberg.GetGlobalSettings().ConfigureAwait(false); var globalConfiguration = await _goldberg.GetGlobalSettings().ConfigureAwait(false);
if (!DllSelected) AccountName = globalConfiguration.AccountName;
{ SteamId = globalConfiguration.UserSteamId;
_log.Error("No DLL selected!"); SelectedLanguage = globalConfiguration.Language;
return; if (!DllSelected) return;
}
_log.Info("Reset form..."); _log.Info("Reset form...");
MainWindowEnabled = false; MainWindowEnabled = false;
@ -437,11 +488,7 @@ namespace GoldbergGUI.Core.ViewModels
private async Task GenerateSteamInterfaces() private async Task GenerateSteamInterfaces()
{ {
if (!DllSelected) if (!DllSelected) return;
{
_log.Error("No DLL selected!");
return;
}
_log.Info("Generate steam_interfaces.txt..."); _log.Info("Generate steam_interfaces.txt...");
MainWindowEnabled = false; MainWindowEnabled = false;
@ -468,29 +515,25 @@ namespace GoldbergGUI.Core.ViewModels
} }
else else
{ {
var pastedDlc = new List<SteamApp>();
var result = Clipboard.GetText(); var result = Clipboard.GetText();
var expression = new Regex(@"(?<id>.*) *= *(?<name>.*)"); var expression = new Regex(@"(?<id>.*) *= *(?<name>.*)");
foreach (var line in result.Split(new[] var pastedDlc = (from line in result.Split(new[] { "\n", "\r\n" },
{ StringSplitOptions.RemoveEmptyEntries)
"\n", select expression.Match(line) into match
"\r\n" where match.Success
}, StringSplitOptions.RemoveEmptyEntries)) select new DlcApp
{
var match = expression.Match(line);
if (match.Success)
pastedDlc.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
}); }).ToList();
}
if (pastedDlc.Count > 0) if (pastedDlc.Count > 0)
{ {
DLCs.Clear(); DLCs.Clear();
DLCs = new ObservableCollection<SteamApp>(pastedDlc); DLCs = new ObservableCollection<DlcApp>(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";
StatusText = $"Successfully got {statusTextCount} from clipboard! Ready.";
} }
else else
{ {
@ -499,6 +542,22 @@ namespace GoldbergGUI.Core.ViewModels
} }
}); });
public IMvxCommand OpenGlobalSettingsFolderCommand => new MvxCommand(OpenGlobalSettingsFolder);
private void OpenGlobalSettingsFolder()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
StatusText = "Can't open folder (Windows only)! Ready.";
return;
}
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Goldberg SteamEmu Saves", "settings");
var start = Process.Start("explorer.exe", path);
start?.Dispose();
}
// OTHER METHODS // // OTHER METHODS //
private void ResetForm() private void ResetForm()
@ -506,7 +565,8 @@ 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;
DLCs = new ObservableCollection<SteamApp>(); Achievements = new ObservableCollection<Achievement>();
DLCs = new ObservableCollection<DlcApp>();
AccountName = "Account name..."; AccountName = "Account name...";
SteamId = -1; SteamId = -1;
Offline = false; Offline = false;
@ -526,7 +586,8 @@ namespace GoldbergGUI.Core.ViewModels
private void SetFormFromConfig(GoldbergConfiguration config) private void SetFormFromConfig(GoldbergConfiguration config)
{ {
AppId = config.AppId; AppId = config.AppId;
DLCs = new ObservableCollection<SteamApp>(config.DlcList); Achievements = new ObservableCollection<Achievement>(config.Achievements);
DLCs = new ObservableCollection<DlcApp>(config.DlcList);
Offline = config.Offline; Offline = config.Offline;
DisableNetworking = config.DisableNetworking; DisableNetworking = config.DisableNetworking;
DisableOverlay = config.DisableOverlay; DisableOverlay = config.DisableOverlay;
@ -536,7 +597,6 @@ namespace GoldbergGUI.Core.ViewModels
{ {
if (!DllSelected) if (!DllSelected)
{ {
_log.Error("No DLL selected!");
dirPath = null; dirPath = null;
return false; return false;
} }

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,5 +1,4 @@
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,11 +2,11 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<FileVersion>0.1.0</FileVersion> <FileVersion>0.3.0</FileVersion>
<AssemblyVersion>0.1.0</AssemblyVersion>
<Company>Jeddunk</Company> <Company>Jeddunk</Company>
<Platforms>AnyCPU;x86;x64</Platforms>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -20,4 +20,20 @@
<ProjectReference Include="..\GoldbergGUI.Core\GoldbergGUI.Core.csproj" /> <ProjectReference Include="..\GoldbergGUI.Core\GoldbergGUI.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Remove="publish\**" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Remove="publish\**" />
</ItemGroup>
<ItemGroup>
<None Remove="publish\**" />
</ItemGroup>
<ItemGroup>
<Page Remove="publish\**" />
</ItemGroup>
</Project> </Project>

View File

@ -5,6 +5,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" mc:Ignorable="d"
Title="GoldbergGUI" MinHeight="500" MinWidth="600" Background="#FFF0F0F0"> Title="GoldbergGUI" MinHeight="600" MinWidth="800" Background="#FFF0F0F0">
<Grid /> <Grid />
</views:MvxWindow> </views:MvxWindow>

View File

@ -1,10 +1,7 @@
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
{ {

View File

@ -7,6 +7,9 @@
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="*"/>
@ -29,7 +32,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"/> <RowDefinition Height="Auto" MaxHeight="0"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition/> <RowDefinition/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
@ -39,11 +42,11 @@
<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"/>
<GroupBox Header="DLC" Grid.Row="3" Padding="0,0,0,0" Margin="0,5,0,0"> <TabControl Grid.Row="3" Margin="0,5,0,0" Padding="0,0,0,0">
<GroupBox.InputBindings> <TabItem Header="DLC">
<KeyBinding Key="V" Modifiers="Control" <TabItem.InputBindings>
Command="{Binding PasteDlcCommand}"/> <KeyBinding Key="V" Modifiers="Control" Command="{Binding PasteDlcCommand}"/>
</GroupBox.InputBindings> </TabItem.InputBindings>
<Grid Margin="10,10,10,10"> <Grid Margin="10,10,10,10">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition/> <RowDefinition/>
@ -53,16 +56,57 @@
<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}}"/>
<DataGridTextColumn Header="Depot Name" Binding="{Binding DepotName}" Width="*" Visibility="{Binding Source={x:Reference ShowOptionalDlcSettings}, Path=IsChecked, Converter={StaticResource B2V}}" />-->
<DataGridTextColumn Header="App Path" Binding="{Binding AppPath}" Width="*" Visibility="{Binding Source={x:Reference ShowOptionalDlcSettings}, Path=IsChecked, Converter={StaticResource B2V}}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
<Button Content="Get _DLCs for AppID" Command="{Binding GetListOfDlcCommand}" Grid.Row="1" Width="120" HorizontalAlignment="Right" Margin="0,5,125,0" Height="20"/> <Grid Grid.Row="1">
<Button Content="_Advanced Settings..." Grid.Row="1" Width="120" HorizontalAlignment="Right" Margin="0,5,0,0" Height="20" IsEnabled="False" ToolTip="Work in progress..."/> <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>
</GroupBox> </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"/>
@ -84,11 +128,11 @@
<StackPanel Margin="5,5,5,5"> <StackPanel Margin="5,5,5,5">
<CheckBox Content="Offline" IsChecked="{Binding Offline, Mode=TwoWay}" Height="20" VerticalAlignment="Stretch" VerticalContentAlignment="Center"/> <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 Networking" IsChecked="{Binding DisableNetworking, Mode=TwoWay}" Height="20" VerticalContentAlignment="Center"/>
<CheckBox Content="Disable Overlay" IsChecked="{Binding DisableOverlay, Mode=TwoWay}" Height="20" VerticalContentAlignment="Center"/> <CheckBox Content="Disable Overlay" IsChecked="{Binding DisableOverlay, Mode=TwoWay}" Height="20" VerticalContentAlignment="Center" IsEnabled="False"/>
</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">
@ -96,26 +140,64 @@
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition/> <RowDefinition Height="Auto"/>
<RowDefinition/> <RowDefinition Height="Auto"/>
<RowDefinition/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Label Content="Account name" HorizontalAlignment="Left" Margin="0,0,10,0" /> <Label Content="Account name" HorizontalAlignment="Left" Margin="0,0,10,0" />
<TextBox Text="{Binding AccountName, Mode=TwoWay}" Height="20" Grid.Row="0" Grid.Column="1"/> <TextBox Text="{Binding AccountName, Mode=TwoWay}" Height="20" Grid.Row="0" Grid.Column="1"/>
<CheckBox Content="Global" Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right"
Margin="10,0,5,0" VerticalAlignment="Center" IsChecked="True"
IsEnabled="False"/>
<!--
IsEnabled="{Binding DllSelected, UpdateSourceTrigger=PropertyChanged}"/>
-->
<Label Content="Steam64ID" HorizontalAlignment="Left" Grid.Row="1" <Label Content="Steam64ID" HorizontalAlignment="Left" Grid.Row="1"
Grid.Column="0" Margin="0,0,10,0" /> Grid.Column="0" Margin="0,0,10,0" />
<TextBox Text="{Binding SteamId, Mode=TwoWay}" Grid.Column="1" Height="20" Grid.Row="1"/> <TextBox Text="{Binding SteamId, Mode=TwoWay}" Grid.Column="1" Height="20" Grid.Row="1"/>
<CheckBox Content="Global" Grid.Row="1" Grid.Column="2" HorizontalAlignment="Right"
Margin="10,0,5,0" VerticalAlignment="Center" IsChecked="True"
IsEnabled="False"/>
<!--
IsEnabled="{Binding DllSelected, UpdateSourceTrigger=PropertyChanged}"/>
-->
<Label Content="Language" HorizontalAlignment="Left" Grid.Row="2" <Label Content="Language" HorizontalAlignment="Left" Grid.Row="2"
Grid.Column="0" Margin="0,0,10,0" /> Grid.Column="0" Margin="0,0,10,0" />
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding SteamLanguages}" SelectedItem="{Binding SelectedLanguage}"/> <ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding SteamLanguages}" SelectedItem="{Binding SelectedLanguage}" VerticalAlignment="Center"/>
<CheckBox Content="Global" Grid.Row="2" Grid.Column="2" HorizontalAlignment="Right"
Margin="10,0,5,0" VerticalAlignment="Center" IsChecked="True"
IsEnabled="False"/>
<!--
IsEnabled="{Binding DllSelected, UpdateSourceTrigger=PropertyChanged}"/>
-->
<Label Content="Custom Broadcast Addresses:" HorizontalAlignment="Left"
Grid.ColumnSpan="2" Grid.Row="3" Grid.Column="0" Margin="0,0,10,0"/>
<CheckBox Content="Global" Grid.Row="3" Grid.Column="2" HorizontalAlignment="Right"
Margin="10,0,5,0" VerticalAlignment="Center" IsChecked="True"
IsEnabled="False"/>
<TextBox Grid.Row="4" Grid.ColumnSpan="3" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
TextWrapping="Wrap"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto" MaxHeight="120" MinHeight="120"/>
<TextBlock TextWrapping="Wrap" Grid.ColumnSpan="3" Grid.Column="0" Grid.Row="5" Margin="5,10,5,5">
<Run Text="{Binding G.Header, Mode=OneTime}" FontWeight="Bold"/><!--
--><Run Text="{Binding G.TextPreLink, Mode=OneTime}"/>
<Hyperlink Command="{Binding OpenGlobalSettingsFolderCommand}"><Run
Text="{Binding G.Link, Mode=OneTime}"/></Hyperlink><!--
--><Run Text="{Binding G.TextPostLink, Mode=OneTime}"/>
</TextBlock>
</Grid> </Grid>
</StackPanel> </StackPanel>
</TabItem> </TabItem>
<TabItem Header="About" HorizontalAlignment="Center" Height="20" VerticalAlignment="Center" Width="54"> <TabItem Header="About" HorizontalAlignment="Center" Height="20" VerticalAlignment="Center" Width="54">
<StackPanel Margin="10,20,10,10"> <StackPanel Margin="10,20,10,10">
<Label VerticalAlignment="Stretch" Content="{Binding AboutVersionText}"> <Label VerticalAlignment="Stretch" Content="{Binding AboutVersionText, Mode=OneTime}">
<Label.ContentTemplate> <Label.ContentTemplate>
<DataTemplate> <DataTemplate>
<StackPanel> <StackPanel>
@ -126,6 +208,8 @@
<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,6 +1,6 @@
using MvvmCross.Platforms.Wpf.Presenters.Attributes; using MvvmCross.Platforms.Wpf.Presenters.Attributes;
using MvvmCross.Platforms.Wpf.Views;
// ReSharper disable UnusedType.Global
namespace GoldbergGUI.WPF.Views namespace GoldbergGUI.WPF.Views
{ {
[MvxWindowPresentation(Identifier = nameof(SearchResultView), Modal = false)] [MvxWindowPresentation(Identifier = nameof(SearchResultView), Modal = false)]

View File

@ -1,26 +1,52 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 17
VisualStudioVersion = 16.0.30717.126 VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoldbergGUI.Core", "GoldbergGUI.Core\GoldbergGUI.Core.csproj", "{FB205F05-83DE-4D87-8CE2-F7DA320944FD}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GoldbergGUI.Core", "GoldbergGUI.Core\GoldbergGUI.Core.csproj", "{FB205F05-83DE-4D87-8CE2-F7DA320944FD}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoldbergGUI.WPF", "GoldbergGUI.WPF\GoldbergGUI.WPF.csproj", "{84ED15D3-725C-43B1-B8C7-51759CAABBAA}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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,8 +40,20 @@ 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.