Skip to content

Commit 7473f18

Browse files
authored
Release 1.16.0
Release 1.16.0
2 parents 158e536 + 12d459f commit 7473f18

18 files changed

+330
-46
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# GameVault App Changelog
22

3+
## 1.16.0
4+
Recommended Gamevault Server Version: `v14.1.0`
5+
### Changes
6+
7+
- Added auto Cloud Save Redirects
8+
- Added support for custom Cloud Save Manifests
9+
- Setting to deactivate the primary cloud save manifest in order to use only the user-defined manifests
10+
- Library can now filter for Developers and Publishers
11+
- You can now uninstall community themes from the settings
12+
- Improved the game metadata search UI
13+
- Bug fix: Startup crash when gamevault tries to set the windows jumplist
14+
315
## 1.15.0
416
Recommended Gamevault Server Version: `v14.0.0`
517
### Changes

gamevault/App.xaml.cs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -187,24 +187,28 @@ private void InitJumpList()
187187
}
188188
public void SetJumpListGames()
189189
{
190-
var lastGames = InstallViewModel.Instance.InstalledGames.Take(5).ToArray();
191-
foreach (var game in lastGames)
190+
try
192191
{
193-
if (!jumpList.JumpItems.OfType<JumpTask>().Any(jt => jt.Title == game.Key.Title))
192+
var lastGames = InstallViewModel.Instance.InstalledGames.Take(5).ToArray();
193+
foreach (var game in lastGames)
194194
{
195-
JumpTask gameTask = new JumpTask()
195+
if (!jumpList.JumpItems.OfType<JumpTask>().Any(jt => jt.Title == game.Key.Title))
196196
{
197-
Title = game.Key.Title,
198-
CustomCategory = "Last Played",
199-
ApplicationPath = Process.GetCurrentProcess().MainModule.FileName,
200-
IconResourcePath = Preferences.Get(AppConfigKey.Executable, $"{game.Value}\\gamevault-exec"),
201-
Arguments = $"start --gameid={game.Key.ID}"
202-
};
203-
jumpList.JumpItems.Add(gameTask);
197+
JumpTask gameTask = new JumpTask()
198+
{
199+
Title = game.Key.Title,
200+
CustomCategory = "Last Played",
201+
ApplicationPath = Process.GetCurrentProcess()?.MainModule?.FileName,
202+
IconResourcePath = Preferences.Get(AppConfigKey.Executable, $"{game.Value}\\gamevault-exec"),
203+
Arguments = $"start --gameid={game.Key.ID}"
204+
};
205+
jumpList.JumpItems.Add(gameTask);
206+
}
204207
}
205-
}
206208

207-
jumpList.Apply();
209+
jumpList.Apply();
210+
}
211+
catch { }
208212
}
209213
private void RestoreTheme()
210214
{
@@ -213,7 +217,7 @@ private void RestoreTheme()
213217
string currentThemeString = Preferences.Get(AppConfigKey.Theme, AppFilePath.UserFile, true);
214218
if (currentThemeString != string.Empty)
215219
{
216-
ThemeItem currentTheme = JsonSerializer.Deserialize<ThemeItem>(currentThemeString);
220+
ThemeItem currentTheme = JsonSerializer.Deserialize<ThemeItem>(currentThemeString)!;
217221

218222
if (App.Current.Resources.MergedDictionaries[0].Source.OriginalString != currentTheme.Path)
219223
{
@@ -302,6 +306,10 @@ private void CreateDirectories()
302306
{
303307
Directory.CreateDirectory(AppFilePath.ThemesLoadDir);
304308
}
309+
if (!Directory.Exists(AppFilePath.CloudSaveConfigDir))
310+
{
311+
Directory.CreateDirectory(AppFilePath.CloudSaveConfigDir);
312+
}
305313
}
306314
public bool IsWindowActiveAndControlInFocus(MainControl control)
307315
{

gamevault/AssemblyInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//(used if a resource is not found in the page,
1212
// app, or any theme specific resource dictionaries)
1313
)]
14-
[assembly: AssemblyVersion("1.15.0.0")]
14+
[assembly: AssemblyVersion("1.16.0.0")]
1515
[assembly: AssemblyCopyright("© Phalcode™. All Rights Reserved.")]
1616
#if DEBUG
1717
[assembly: XmlnsDefinition("debug-mode", "Namespace")]

gamevault/Helper/AnalyticsHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ public Dictionary<string, string> PrepareSettingsForAnalytics()
289289
var trimmedObject = SettingsViewModel.Instance.GetType()
290290
.GetProperties()
291291
.Where(prop => !propertiesToExclude.Contains(prop.Name))
292-
.ToDictionary(prop => prop.Name, prop => prop.GetValue(SettingsViewModel.Instance).ToString());
292+
.ToDictionary(prop => prop.Name, prop => prop.GetValue(SettingsViewModel.Instance)?.ToString());
293293

294294
trimmedObject.Add("HasLicence", (SettingsViewModel.Instance.License?.IsActive() == true).ToString());
295295
return trimmedObject;

gamevault/Helper/Integrations/SaveGameHelper.cs

Lines changed: 89 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
using System.Windows.Media.Imaging;
1717
using Windows.Gaming.Input;
1818
using Windows.Gaming.Preview.GamesEnumeration;
19+
using YamlDotNet.Serialization.NamingConventions;
20+
using YamlDotNet.Serialization;
21+
using ImageMagick.Drawing;
1922

2023
namespace gamevault.Helper.Integrations
2124
{
@@ -88,10 +91,11 @@ internal async Task<bool> RestoreBackup(int gameId, string installationDir)
8891
throw new Exception("no savegame extracted");
8992

9093
extractFolder = Path.GetDirectoryName(Path.GetDirectoryName(mappingFile[0]));
94+
PrepareConfigFile(installationDir, Path.Combine(AppFilePath.CloudSaveConfigDir, "config.yaml"));
9195
Process process = new Process();
9296
ProcessShepherd.Instance.AddProcess(process);
9397
process.StartInfo = CreateProcessHeader();
94-
process.StartInfo.Arguments = $"restore --force --path \"{extractFolder}\"";
98+
process.StartInfo.Arguments = $"--config {AppFilePath.CloudSaveConfigDir} restore --force --path \"{extractFolder}\"";
9599
process.Start();
96100
process.WaitForExit();
97101
ProcessShepherd.Instance.RemoveProcess(process);
@@ -172,7 +176,12 @@ internal async Task<bool> BackupSaveGame(int gameId)
172176
if (gameMetadataTitle != "" && installationDir != "")
173177
{
174178
string title = await SearchForLudusaviGameTitle(gameMetadataTitle);
175-
string tempFolder = await CreateBackup(title);
179+
if (string.IsNullOrEmpty(title))
180+
return false;
181+
string tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
182+
Directory.CreateDirectory(tempFolder);
183+
PrepareConfigFile(installedGame?.Value!, Path.Combine(AppFilePath.CloudSaveConfigDir, "config.yaml"));
184+
await CreateBackup(title, tempFolder);
176185
string archive = Path.Combine(tempFolder, "backup.zip");
177186
if (Directory.GetFiles(tempFolder, "mapping.yaml", SearchOption.AllDirectories).Length == 0)
178187
{
@@ -187,6 +196,69 @@ internal async Task<bool> BackupSaveGame(int gameId)
187196
}
188197
return false;
189198
}
199+
public void PrepareConfigFile(string installationPath, string yamlPath)
200+
{
201+
string userFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
202+
var redirects = new Dictionary<string, object>
203+
{
204+
{ "redirects", new List<Dictionary<string, object>>
205+
{
206+
new Dictionary<string, object>
207+
{
208+
{ "kind", "bidirectional" },
209+
{ "source", userFolder },
210+
{ "target", "G:\\gamevault\\currentuser" }
211+
},
212+
new Dictionary<string, object>
213+
{
214+
{ "kind", "bidirectional" },
215+
{ "source", installationPath },
216+
{ "target", "G:\\gamevault\\installation" }
217+
}
218+
}
219+
}
220+
};
221+
222+
// Simulating SettingsViewModel.Instance.CustomCloudSaveManifests
223+
var customLudusaviManifests = SettingsViewModel.Instance.CustomCloudSaveManifests.Where(m => !string.IsNullOrWhiteSpace(m.Uri));
224+
225+
Dictionary<string, object> yamlData;
226+
227+
if (customLudusaviManifests.Any()) // If manifests exist, merge with redirects
228+
{
229+
var manifest = new Dictionary<string, object>
230+
{
231+
{ "enable", SettingsViewModel.Instance.UsePrimaryCloudSaveManifest },
232+
{ "secondary", new List<Dictionary<string, object>>() }
233+
};
234+
235+
foreach (LudusaviManifestEntry entry in customLudusaviManifests)
236+
{
237+
((List<Dictionary<string, object>>)manifest["secondary"]).Add(new Dictionary<string, object>
238+
{
239+
{ Uri.IsWellFormedUriString(entry.Uri, UriKind.Absolute) ? "url" : "path", entry.Uri },
240+
{ "enable", true }
241+
});
242+
}
243+
244+
yamlData = new Dictionary<string, object>
245+
{
246+
{ "manifest", manifest },
247+
{ "redirects", redirects["redirects"] } // Merge redirects
248+
};
249+
}
250+
else
251+
{
252+
yamlData = redirects; // Only redirects if no manifests
253+
}
254+
255+
var serializer = new SerializerBuilder()
256+
.WithNamingConvention(UnderscoredNamingConvention.Instance)
257+
.Build();
258+
259+
string result = serializer.Serialize(yamlData);
260+
File.WriteAllText(yamlPath, result);
261+
}
190262
internal async Task<string> SearchForLudusaviGameTitle(string title)
191263
{
192264
return await Task.Run<string>(() =>
@@ -223,21 +295,19 @@ internal async Task<string> SearchForLudusaviGameTitle(string title)
223295
return "";
224296
});
225297
}
226-
private async Task<string> CreateBackup(string lunusaviTitle)
298+
private async Task CreateBackup(string lunusaviTitle, string tempFolder)
227299
{
228-
return await Task.Run<string>(() =>
229-
{
230-
string tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
300+
await Task.Run(() =>
301+
{
302+
Process process = new Process();
303+
ProcessShepherd.Instance.AddProcess(process);
304+
process.StartInfo = CreateProcessHeader();
305+
process.StartInfo.Arguments = $"--config {AppFilePath.CloudSaveConfigDir} backup --force --format \"zip\" --path \"{tempFolder}\" \"{lunusaviTitle}\"";
306+
process.Start();
307+
process.WaitForExit();
308+
ProcessShepherd.Instance.RemoveProcess(process);
231309

232-
Process process = new Process();
233-
ProcessShepherd.Instance.AddProcess(process);
234-
process.StartInfo = CreateProcessHeader();
235-
process.StartInfo.Arguments = $"backup --force --format \"zip\" --path \"{tempFolder}\" \"{lunusaviTitle}\"";
236-
process.Start();
237-
process.WaitForExit();
238-
ProcessShepherd.Instance.RemoveProcess(process);
239-
return tempFolder;
240-
});
310+
});
241311
}
242312
private async Task<bool> UploadSavegame(string saveFilePath, int gameId, string installationDir)
243313
{
@@ -279,4 +349,8 @@ private ProcessStartInfo CreateProcessHeader(bool redirectConsole = false)
279349
return info;
280350
}
281351
}
352+
public class LudusaviManifestEntry
353+
{
354+
public string Uri { get; set; }
355+
}
282356
}

gamevault/Helper/LoginManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public async Task StartupLogin()
8484
try
8585
{
8686
WebHelper.SetCredentials(Preferences.Get(AppConfigKey.Username, AppFilePath.UserFile), Preferences.Get(AppConfigKey.Password, AppFilePath.UserFile, true));
87-
string result = WebHelper.GetRequest(@$"{SettingsViewModel.Instance.ServerUrl}/api/users/me");
87+
string result = WebHelper.GetRequest(@$"{SettingsViewModel.Instance.ServerUrl}/api/users/me", 5000);
8888
return JsonSerializer.Deserialize<User>(result);
8989
}
9090
catch (Exception ex)

gamevault/Helper/Web/WebExceptionHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ private static string GetServerMessage(WebException ex)
1717
string errMessage = string.Empty;
1818
try
1919
{
20-
var resp = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
20+
var resp = new StreamReader(ex.Response?.GetResponseStream()).ReadToEnd();
2121
JsonObject obj = JsonNode.Parse(resp).AsObject();
2222
errMessage = obj["message"].ToString().Replace("\n", " ").Replace("\r", "");
2323
}

gamevault/Helper/Web/WebHelper.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ internal static void OverrideCredentials(string username, string password)
3535
Preferences.Set(AppConfigKey.Username, m_UserName, AppFilePath.UserFile);
3636
Preferences.Set(AppConfigKey.Password, m_Password, AppFilePath.UserFile, true);
3737
}
38-
internal static string GetRequest(string uri)
38+
internal static string GetRequest(string uri, int timeout = 0)
3939
{
4040
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
41-
#if DEBUG
42-
//request.Timeout = 3000;
43-
#endif
41+
if (timeout != 0)
42+
{
43+
request.Timeout = 5000;
44+
}
45+
4446
request.UserAgent = $"GameVault/{SettingsViewModel.Instance.Version}";
4547
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
4648
var authenticationString = $"{m_UserName}:{m_Password}";

gamevault/Models/AppInfo.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ public enum AppConfigKey
5050
DevModeEnabled,
5151
DevTargetPhalcodeTestBackend,
5252
//
53-
InstallationId
53+
InstallationId,
54+
CustomCloudSaveManifests,
55+
UsePrimaryCloudSaveManifest
56+
5457
}
5558
public static class AppFilePath
5659
{
@@ -63,6 +66,7 @@ public static class AppFilePath
6366
internal static string UserFile = $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}/GameVault/config/user";
6467
internal static string IgnoreList = $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}/GameVault/cache/ignorelist";
6568
internal static string ErrorLog = $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}/GameVault/errorlog";
69+
internal static string CloudSaveConfigDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GameVault", "config", "cloudsave");
6670

6771

6872
internal static void InitDebugPaths()
@@ -76,6 +80,7 @@ internal static void InitDebugPaths()
7680
UserFile = $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}/GameVault/debug/config/user";
7781
IgnoreList = $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}/GameVault/debug/cache/ignorelist";
7882
ErrorLog = $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}/GameVault/debug/errorlog";
83+
CloudSaveConfigDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GameVault", "debug", "config", "cloudsave");
7984
}
8085
}
8186
public static class Globals

gamevault/UserControls/GameViewUserControl.xaml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,17 @@
529529
</ItemsControl.ItemsPanel>
530530
<ItemsControl.ItemTemplate>
531531
<DataTemplate>
532-
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" FontSize="10"/>
532+
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" FontSize="10" Cursor="Hand" MouseLeftButtonUp="Developer_Clicked">
533+
<TextBlock.Style>
534+
<Style TargetType="TextBlock">
535+
<Style.Triggers>
536+
<Trigger Property="IsMouseOver" Value="True">
537+
<Setter Property="Foreground" Value="{DynamicResource MahApps.Brushes.Accent}"/>
538+
</Trigger>
539+
</Style.Triggers>
540+
</Style>
541+
</TextBlock.Style>
542+
</TextBlock>
533543
</DataTemplate>
534544
</ItemsControl.ItemTemplate>
535545
</ItemsControl>
@@ -561,7 +571,17 @@
561571
</ItemsControl.ItemsPanel>
562572
<ItemsControl.ItemTemplate>
563573
<DataTemplate>
564-
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" FontSize="10"/>
574+
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" FontSize="10" Cursor="Hand" MouseLeftButtonUp="Publisher_Clicked">
575+
<TextBlock.Style>
576+
<Style TargetType="TextBlock">
577+
<Style.Triggers>
578+
<Trigger Property="IsMouseOver" Value="True">
579+
<Setter Property="Foreground" Value="{DynamicResource MahApps.Brushes.Accent}"/>
580+
</Trigger>
581+
</Style.Triggers>
582+
</Style>
583+
</TextBlock.Style>
584+
</TextBlock>
565585
</DataTemplate>
566586
</ItemsControl.ItemTemplate>
567587
</ItemsControl>

0 commit comments

Comments
 (0)