diff --git a/GoldbergGUI.Core/GoldbergGUI.Core.csproj b/GoldbergGUI.Core/GoldbergGUI.Core.csproj
index 865a61b..72f32a9 100644
--- a/GoldbergGUI.Core/GoldbergGUI.Core.csproj
+++ b/GoldbergGUI.Core/GoldbergGUI.Core.csproj
@@ -1,32 +1,28 @@
-
- netcoreapp3.1
- 0.2.0
- Jeddunk
- AnyCPU;x86;x64
-
+
+ net8.0
+ 0.3.0
+ Jeddunk
+ AnyCPU;x86;x64
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
- ..\..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_32\PresentationCore\v4.0_4.0.0.0__31bf3856ad364e35\PresentationCore.dll
-
-
- ..\..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_MSIL\PresentationFramework\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.dll
-
-
+
+
+ ..\..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_32\PresentationCore\v4.0_4.0.0.0__31bf3856ad364e35\PresentationCore.dll
+
+
+ ..\..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_MSIL\PresentationFramework\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.dll
+
+
diff --git a/GoldbergGUI.Core/Services/GoldbergService.cs b/GoldbergGUI.Core/Services/GoldbergService.cs
index bbe0be1..6b7bcfa 100644
--- a/GoldbergGUI.Core/Services/GoldbergService.cs
+++ b/GoldbergGUI.Core/Services/GoldbergService.cs
@@ -208,6 +208,7 @@ namespace GoldbergGUI.Core.Services
{
_log.Info("Reading configuration...");
var appId = -1;
+ var achievementList = new List();
var dlcList = new List();
var steamAppidTxt = Path.Combine(path, "steam_appid.txt");
if (File.Exists(steamAppidTxt))
@@ -221,6 +222,19 @@ namespace GoldbergGUI.Core.Services
_log.Info(@"""steam_appid.txt"" missing! Skipping...");
}
+ var achievementJson = Path.Combine(path, "steam_settings", "achievements.json");
+ if (File.Exists(achievementJson))
+ {
+ _log.Info("Getting achievements...");
+ var json = await File.ReadAllTextAsync(achievementJson)
+ .ConfigureAwait(false);
+ achievementList = System.Text.Json.JsonSerializer.Deserialize>(json);
+ }
+ else
+ {
+ _log.Info(@"""steam_settings/achievements.json"" missing! Skipping...");
+ }
+
var dlcTxt = Path.Combine(path, "steam_settings", "DLC.txt");
var appPathTxt = Path.Combine(path, "steam_settings", "app_paths.txt");
if (File.Exists(dlcTxt))
@@ -263,6 +277,7 @@ namespace GoldbergGUI.Core.Services
return new GoldbergConfiguration
{
AppId = appId,
+ Achievements = achievementList,
DlcList = dlcList,
Offline = File.Exists(Path.Combine(path, "steam_settings", "offline.txt")),
DisableNetworking = File.Exists(Path.Combine(path, "steam_settings", "disable_networking.txt")),
@@ -303,6 +318,53 @@ namespace GoldbergGUI.Core.Services
await File.WriteAllTextAsync(Path.Combine(path, "steam_appid.txt"), c.AppId.ToString())
.ConfigureAwait(false);
+ // 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)
{
@@ -621,5 +683,22 @@ namespace GoldbergGUI.Core.Services
return success;
}
+
+ private async Task DownloadImageAsync(string imageFolder, string imageUrl)
+ {
+ var fileName = Path.GetFileName(imageUrl);
+ var targetPath = Path.Combine(imageFolder, fileName);
+ if (File.Exists(targetPath))
+ {
+ return;
+ }
+ else if (imageUrl.StartsWith("images/"))
+ {
+ _log.Warn($"Previously downloaded image '{imageUrl}' is now missing!");
+ }
+
+ var wc = new System.Net.WebClient();
+ await wc.DownloadFileTaskAsync(new Uri(imageUrl, UriKind.Absolute), targetPath);
+ }
}
}
\ No newline at end of file
diff --git a/GoldbergGUI.Core/Services/SteamService.cs b/GoldbergGUI.Core/Services/SteamService.cs
index 5e79f4d..74c369b 100644
--- a/GoldbergGUI.Core/Services/SteamService.cs
+++ b/GoldbergGUI.Core/Services/SteamService.cs
@@ -23,6 +23,7 @@ namespace GoldbergGUI.Core.Services
public Task> GetListOfAppsByName(string name);
public Task GetAppByName(string name);
public Task GetAppById(int appid);
+ public Task> GetListOfAchievements(SteamApp steamApp);
public Task> GetListOfDlc(SteamApp steamApp, bool useSteamDb);
}
@@ -81,6 +82,7 @@ namespace GoldbergGUI.Core.Services
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;
@@ -168,6 +170,32 @@ namespace GoldbergGUI.Core.Services
return app;
}
+ public async Task> GetListOfAchievements(SteamApp steamApp)
+ {
+ var achievementList = new List();
+ if (steamApp == null)
+ {
+ return achievementList;
+ }
+
+ _log.Info($"Getting achievements for App {steamApp}");
+
+ var client = new HttpClient();
+ client.DefaultRequestHeaders.UserAgent.ParseAdd(UserAgent);
+ var apiUrl = $"{GameSchemaUrl}?key={Secrets.SteamWebApiKey()}&appid={steamApp.AppId}&l=en";
+
+ var response = await client.GetAsync(apiUrl);
+ var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+
+ var jsonResponse = JsonDocument.Parse(responseBody);
+ var achievementData = jsonResponse.RootElement.GetProperty("game")
+ .GetProperty("availableGameStats")
+ .GetProperty("achievements");
+
+ achievementList = JsonSerializer.Deserialize>(achievementData.GetRawText());
+ return achievementList;
+ }
+
public async Task> GetListOfDlc(SteamApp steamApp, bool useSteamDb)
{
var dlcList = new List();
diff --git a/GoldbergGUI.Core/ViewModels/MainViewModel.cs b/GoldbergGUI.Core/ViewModels/MainViewModel.cs
index 6f57aae..54580c7 100644
--- a/GoldbergGUI.Core/ViewModels/MainViewModel.cs
+++ b/GoldbergGUI.Core/ViewModels/MainViewModel.cs
@@ -30,6 +30,7 @@ namespace GoldbergGUI.Core.ViewModels
private int _appId;
//private SteamApp _currentGame;
+ private ObservableCollection _achievements;
private ObservableCollection _dlcs;
private string _accountName;
private long _steamId;
@@ -142,6 +143,16 @@ namespace GoldbergGUI.Core.ViewModels
}
}
+ public ObservableCollection Achievements
+ {
+ get => _achievements;
+ set
+ {
+ _achievements = value;
+ RaisePropertyChanged(() => Achievements);
+ }
+ }
+
public string AccountName
{
get => _accountName;
@@ -368,6 +379,33 @@ namespace GoldbergGUI.Core.ViewModels
if (steamApp != null) GameName = steamApp.Name;
}
+ public IMvxCommand GetListOfAchievementsCommand => new MvxAsyncCommand(GetListOfAchievements);
+
+ private async Task GetListOfAchievements()
+ {
+ if (AppId <= 0)
+ {
+ _log.Error("Invalid Steam App!");
+ return;
+ }
+
+ MainWindowEnabled = false;
+ StatusText = "Trying to get list of achievements...";
+ var listOfAchievements = await _steam.GetListOfAchievements(new SteamApp { AppId = AppId, Name = GameName });
+ Achievements = new MvxObservableCollection(listOfAchievements);
+ MainWindowEnabled = true;
+
+ if (Achievements.Count > 0)
+ {
+ var empty = Achievements.Count == 1 ? "" : "s";
+ StatusText = $"Successfully got {Achievements.Count} achievement{empty}! Ready.";
+ }
+ else
+ {
+ StatusText = "No achievements found! Ready.";
+ }
+ }
+
public IMvxCommand GetListOfDlcCommand => new MvxAsyncCommand(GetListOfDlc);
private async Task GetListOfDlc()
@@ -416,6 +454,7 @@ namespace GoldbergGUI.Core.ViewModels
await _goldberg.Save(dirPath, new GoldbergConfiguration
{
AppId = AppId,
+ Achievements = Achievements.ToList(),
DlcList = DLCs.ToList(),
Offline = Offline,
DisableNetworking = DisableNetworking,
@@ -526,6 +565,7 @@ namespace GoldbergGUI.Core.ViewModels
DllPath = "Path to game's steam_api(64).dll...";
GameName = "Game name...";
AppId = -1;
+ Achievements = new ObservableCollection();
DLCs = new ObservableCollection();
AccountName = "Account name...";
SteamId = -1;
@@ -546,6 +586,7 @@ namespace GoldbergGUI.Core.ViewModels
private void SetFormFromConfig(GoldbergConfiguration config)
{
AppId = config.AppId;
+ Achievements = new ObservableCollection(config.Achievements);
DLCs = new ObservableCollection(config.DlcList);
Offline = config.Offline;
DisableNetworking = config.DisableNetworking;
diff --git a/GoldbergGUI.WPF/GoldbergGUI.WPF.csproj b/GoldbergGUI.WPF/GoldbergGUI.WPF.csproj
index d0e7dcb..6e1a7aa 100644
--- a/GoldbergGUI.WPF/GoldbergGUI.WPF.csproj
+++ b/GoldbergGUI.WPF/GoldbergGUI.WPF.csproj
@@ -2,9 +2,9 @@
WinExe
- netcoreapp3.1
+ net8.0-windows
true
- 0.2.0
+ 0.3.0
Jeddunk
AnyCPU;x86;x64
diff --git a/GoldbergGUI.WPF/Views/MainView.xaml b/GoldbergGUI.WPF/Views/MainView.xaml
index 818b49c..83b115d 100644
--- a/GoldbergGUI.WPF/Views/MainView.xaml
+++ b/GoldbergGUI.WPF/Views/MainView.xaml
@@ -32,7 +32,7 @@
-
+
@@ -42,33 +42,71 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -117,7 +155,7 @@
-
-
-
+
+
diff --git a/GoldbergGUI.sln b/GoldbergGUI.sln
index bda7b63..91e54f5 100644
--- a/GoldbergGUI.sln
+++ b/GoldbergGUI.sln
@@ -7,7 +7,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GoldbergGUI.Core", "Goldber
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GoldbergGUI.WPF", "GoldbergGUI.WPF\GoldbergGUI.WPF.csproj", "{84ED15D3-725C-43B1-B8C7-51759CAABBAA}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamStorefrontAPI", "SteamStorefrontAPI\SteamStorefrontAPI\SteamStorefrontAPI.csproj", "{42D17FA4-C45C-4CC1-BA9C-80B3FA1C006D}"
+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
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -43,18 +47,6 @@ Global
{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
- {42D17FA4-C45C-4CC1-BA9C-80B3FA1C006D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {42D17FA4-C45C-4CC1-BA9C-80B3FA1C006D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {42D17FA4-C45C-4CC1-BA9C-80B3FA1C006D}.Debug|x64.ActiveCfg = Debug|Any CPU
- {42D17FA4-C45C-4CC1-BA9C-80B3FA1C006D}.Debug|x64.Build.0 = Debug|Any CPU
- {42D17FA4-C45C-4CC1-BA9C-80B3FA1C006D}.Debug|x86.ActiveCfg = Debug|Any CPU
- {42D17FA4-C45C-4CC1-BA9C-80B3FA1C006D}.Debug|x86.Build.0 = Debug|Any CPU
- {42D17FA4-C45C-4CC1-BA9C-80B3FA1C006D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {42D17FA4-C45C-4CC1-BA9C-80B3FA1C006D}.Release|Any CPU.Build.0 = Release|Any CPU
- {42D17FA4-C45C-4CC1-BA9C-80B3FA1C006D}.Release|x64.ActiveCfg = Release|Any CPU
- {42D17FA4-C45C-4CC1-BA9C-80B3FA1C006D}.Release|x64.Build.0 = Release|Any CPU
- {42D17FA4-C45C-4CC1-BA9C-80B3FA1C006D}.Release|x86.ActiveCfg = Release|Any CPU
- {42D17FA4-C45C-4CC1-BA9C-80B3FA1C006D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/README.md b/README.md
index dcb2c00..a1f86c2 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ While the most used options are available right now, I am planning to support al
* Subscribed Groups
* Mods (Steam Workshop)
* Inventory and Items
-* Achievements
+* ~~Achievements~~
* Stats, Leaderboards
* 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.
+### Contributors
+
+* [UrbanCMC](https://github.com/UrbanCMC/) - Implementation of achievements
+
+### Dependencies
+
+* AngleSharp
+* MvvmCross
+* NinjaNye
+* Serilog
+* SharpCompress
+* sqlite-net-pcl
+* My fork of SteamStorefrontAPI
+
## License
GoldbergGUI is licensed under the GNU General Public License v3.0.
-
-Dependencies will be listed ASAP.
diff --git a/SteamStorefrontAPI b/SteamStorefrontAPI
deleted file mode 160000
index 2b984cd..0000000
--- a/SteamStorefrontAPI
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 2b984cd5a11802e6106e0e1202b90fe5da7735fb