Skip to content

Commit 9088180

Browse files
EzioTheDeadPoetJanuarySnowMaelstrom8
authored
Release 4.0.5.0 (#2858)
* minor fixed for modlist-report cli usage and make it automatic after compilation * added tickbox option to auto-generate report * another another othergames fix * experimental speedup maybe * image cache optimization on gallery * some fixes for failing gallery images * Update Game.cs * Update GameRegistry.cs * reverse exclude filter for contains mods formatting * retry fix * update CHANGELOG.md * update workflow version for NuGet releases --------- Co-authored-by: JanuarySnow <bobfordiscord12@gmail.com> Co-authored-by: Maelstrom <Inolongerfearpoison@gmail.com>
1 parent cf2fb32 commit 9088180

27 files changed

+1008
-159
lines changed

.github/workflows/tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
branches: [ main ]
88

99
env:
10-
VERSION: 4.0.2.0
10+
VERSION: 4.0.5.0
1111

1212
jobs:
1313
build:

CHANGELOG.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
### Changelog
22

3-
### Version - 4.0.4.1 - 9/10/2025
3+
#### Version - 4.0.5.0 - 11/04/2025
4+
* Added checkbox for automatically generating a modlist-report for analyzing the metadata of the resulting modlist file ([@JanuarySnow](https://www.github.com/JanuarySnow))
5+
* Added option to inverse the mod search filter in the gallery ([@JanuarySnow](https://www.github.com/JanuarySnow))
6+
* Added game MetaData for Dragon's Dogma 2 ([@Maelstrom8](https://github.com/Maelstrom8))
7+
* Optimized Performance ([@JanuarySnow](https://www.github.com/JanuarySnow))
8+
* Fixed an installation issue when using other Games as sources ([@JanuarySnow](https://www.github.com/JanuarySnow))
9+
* Fixed the retry button deleting stuff it shouldn't, causing unfinished installations being reported as completed ([@JanuarySnow](https://www.github.com/JanuarySnow))
10+
11+
#### Version - 4.0.4.1 - 9/10/2025
412
* Hotfix for the game file sourcing now suddenly requiring other games to be installed on modlists that don't actually use file sourcing from other games ([@JanuarySnow](https://www.github.com/JanuarySnow))
513

614
#### Version - 4.0.4.0 - 9/7/2025
715
* Fixed the Nexus OAuth token never being refreshed upon starting installation or during downloads
816
* This could lead to issues after having not logged back into Nexus for 6 hours after your initial login
917
* Fixed an issue with the Google Drive downloader not being able to download files when not prompted with a harmful file warning
10-
* Fixed Wabbajack not properly sourcing files from other installed games (for example when using Skyrim SE DLC on Skyrim VR modlists) ([@JanuarySnow](https://www.github.com/JanuarySnow)])
18+
* Fixed Wabbajack not properly sourcing files from other installed games (for example, when using Skyrim SE DLC on Skyrim VR modlists) ([@JanuarySnow](https://www.github.com/JanuarySnow)])
1119
* Fixed Wabbajack crashing when navigating the 'Has Mod(s)' filter in the gallery with arrow keys
1220

1321
#### Version - 4.0.3.0 - 7/18/2025

Wabbajack.App.Wpf/App.xaml.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
using Wabbajack.Services.OSIntegrated;
3434
using Wabbajack.UserIntervention;
3535
using Wabbajack.Util;
36+
using Wabbajack.Common;
3637
using Ext = Wabbajack.Common.Ext;
3738

3839
namespace Wabbajack;
@@ -43,9 +44,11 @@ namespace Wabbajack;
4344
public partial class App
4445
{
4546
private IHost _host;
47+
private TimerResolution? _timerRes;
4648

4749
private void OnStartup(object sender, StartupEventArgs e)
4850
{
51+
_timerRes = new TimerResolution(1);
4952
if (IsAdmin())
5053
{
5154
var messageBox = MessageBox.Show("Don't run Wabbajack as Admin!", "Error", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK, MessageBoxOptions.DefaultDesktopOnly);
@@ -191,6 +194,7 @@ private bool GrantFullControlOverDir(AbsolutePath path)
191194

192195
protected override void OnExit(ExitEventArgs e)
193196
{
197+
_timerRes?.Dispose();
194198
base.OnExit(e);
195199
}
196200

Wabbajack.App.Wpf/Settings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,5 @@ public class GalleryFilterSettings
104104
public bool IncludeUnofficial { get; set; }
105105
public bool OnlyInstalled { get; set; }
106106
public string Search { get; set; }
107+
public bool ExcludeMods { get; set; }
107108
}

Wabbajack.App.Wpf/Util/ImageCacheManager.cs

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,50 +25,57 @@ public class ImageCacheManager
2525

2626
private async Task SaveImage(Hash hash, MemoryStream ms)
2727
{
28-
var path = _imageCachePath.Combine(hash.ToHex());
29-
await using var fs = new FileStream(path.ToString(), FileMode.Create, FileAccess.Write);
30-
ms.WriteTo(fs);
28+
var path = PathFor(hash);
29+
ms.Position = 0;
30+
await using var fs = new FileStream(path.ToString(), FileMode.Create, FileAccess.Write, FileShare.Read);
31+
await ms.CopyToAsync(fs);
3132
}
32-
private async Task<(bool, MemoryStream)> LoadImage(Hash hash)
33+
34+
private async Task<(bool ok, MemoryStream stream)> LoadImage(Hash hash)
3335
{
34-
MemoryStream imageStream = null;
35-
var path = _imageCachePath.Combine(hash.ToHex());
36-
if (!path.FileExists())
37-
{
38-
return (false, imageStream);
39-
}
36+
var path = PathFor(hash);
37+
if (!path.FileExists()) return (false, null);
4038

41-
imageStream = new MemoryStream();
42-
await using var fs = new FileStream(path.ToString(), FileMode.Open, FileAccess.Read);
43-
await fs.CopyToAsync(imageStream);
44-
return (true, imageStream);
39+
var ms = new MemoryStream();
40+
await using var fs = new FileStream(path.ToString(), FileMode.Open, FileAccess.Read, FileShare.Read);
41+
await fs.CopyToAsync(ms);
42+
return (true, ms);
4543
}
4644

45+
private AbsolutePath PathFor(Hash hash) => _imageCachePath.Combine(hash.ToHex());
46+
4747
public ImageCacheManager(ILogger<ImageCacheManager> logger, Services.OSIntegrated.Configuration configuration)
4848
{
4949
_logger = logger;
5050
_configuration = configuration;
5151
_imageCachePath = _configuration.ImageCacheLocation;
5252
_imageCachePath.CreateDirectory();
53-
53+
5454
RxApp.TaskpoolScheduler.ScheduleRecurringAction(_pollInterval, () =>
5555
{
56-
foreach (var (hash, cachedImage) in _cachedImages)
56+
foreach (var (hash, cached) in _cachedImages)
5757
{
58-
if (!cachedImage.IsExpired()) continue;
59-
58+
if (!cached.IsExpired()) continue;
59+
6060
try
6161
{
6262
_cachedImages.TryRemove(hash, out _);
63-
File.Delete(_configuration.ImageCacheLocation.Combine(hash).ToString());
63+
var path = PathFor(hash);
64+
if (path.FileExists())
65+
{
66+
try { File.Delete(path.ToString()); }
67+
catch (IOException) { }
68+
catch (UnauthorizedAccessException) { }
69+
}
6470
}
6571
catch (Exception ex)
6672
{
67-
_logger.LogError("Failed to delete cached image {b64}", hash);
73+
_logger.LogError(ex, "Failed to delete cached image {hashHex}", hash.ToHex());
6874
}
6975
}
7076
});
71-
77+
78+
7279
}
7380

7481
public async Task<bool> Add(string url, BitmapImage img)
@@ -105,6 +112,6 @@ public class CachedImage(BitmapImage image)
105112
private readonly TimeSpan _cacheDuration = TimeSpan.FromMinutes(5);
106113

107114
public BitmapImage Image { get; } = image;
108-
109-
public bool IsExpired() => _cachedAt - DateTime.Now > _cacheDuration;
115+
116+
public bool IsExpired() => DateTime.Now - _cachedAt > _cacheDuration;
110117
}

Wabbajack.App.Wpf/Util/UIUtils.cs

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
using Wabbajack.DTOs;
2020
using Exception = System.Exception;
2121
using SharpImage = SixLabors.ImageSharp.Image;
22+
using SixLabors.ImageSharp.Processing;
23+
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
2224

2325
namespace Wabbajack;
2426

@@ -34,6 +36,7 @@ public static BitmapImage BitmapImageFromStream(Stream stream)
3436
img.StreamSource = stream;
3537
img.EndInit();
3638
img.Freeze();
39+
stream.Position = 0;
3740
return img;
3841
}
3942

@@ -104,40 +107,86 @@ public static AbsolutePath OpenFileDialog(string filter, string initialDirectory
104107
return default;
105108
}
106109

107-
public static IObservable<BitmapImage> DownloadBitmapImage(this IObservable<string> obs, Action<Exception> exceptionHandler,
108-
LoadingLock loadingLock, HttpClient client, ImageCacheManager icm)
110+
public static IObservable<BitmapImage> DownloadBitmapImage(
111+
this IObservable<string> obs,
112+
Action<Exception> exceptionHandler,
113+
LoadingLock loadingLock,
114+
HttpClient client,
115+
ImageCacheManager icm)
109116
{
117+
const int MaxConcurrent = 8;
118+
110119
return obs
111120
.ObserveOn(RxApp.TaskpoolScheduler)
112-
.SelectTask(async url =>
121+
.Select(url => Observable.FromAsync(async () =>
113122
{
114123
using var ll = loadingLock.WithLoading();
115124
try
116125
{
117126
var (cached, cachedImg) = await icm.Get(url);
118127
if (cached) return cachedImg;
119128

120-
await using var stream = await client.GetStreamAsync(url);
129+
await using var net = await client.GetStreamAsync(url);
121130

122-
using var pngStream = new MemoryStream();
123-
using (var sharpImg = await SharpImage.LoadAsync(stream))
131+
using var sharpImg = await SixLabors.ImageSharp.Image.LoadAsync<SixLabors.ImageSharp.PixelFormats.Bgra32>(net);
132+
const int targetPx = 512;
133+
if (sharpImg.Width > targetPx || sharpImg.Height > targetPx)
124134
{
125-
await sharpImg.SaveAsPngAsync(pngStream);
135+
var scale = Math.Min((float)targetPx / sharpImg.Width, (float)targetPx / sharpImg.Height);
136+
var nw = (int)(sharpImg.Width * scale);
137+
var nh = (int)(sharpImg.Height * scale);
138+
sharpImg.Mutate(x => x.Resize(nw, nh));
126139
}
127140

141+
using var pngStream = new MemoryStream(capacity: 64 * 1024);
142+
var fastPng = new SixLabors.ImageSharp.Formats.Png.PngEncoder
143+
{
144+
CompressionLevel = SixLabors.ImageSharp.Formats.Png.PngCompressionLevel.NoCompression,
145+
FilterMethod = SixLabors.ImageSharp.Formats.Png.PngFilterMethod.None,
146+
BitDepth = SixLabors.ImageSharp.Formats.Png.PngBitDepth.Bit8,
147+
ColorType = SixLabors.ImageSharp.Formats.Png.PngColorType.RgbWithAlpha
148+
};
149+
try
150+
{
151+
await sharpImg.SaveAsPngAsync(pngStream, fastPng);
152+
}
153+
catch (IndexOutOfRangeException)
154+
{
155+
// SME banner failed to load, buggy metadata in log, so this crap removes it
156+
sharpImg.Metadata.IccProfile = null;
157+
sharpImg.Metadata.ExifProfile = null;
158+
sharpImg.Metadata.XmpProfile = null;
159+
foreach (var f in sharpImg.Frames)
160+
{
161+
f.Metadata.IccProfile = null;
162+
f.Metadata.ExifProfile = null;
163+
f.Metadata.XmpProfile = null;
164+
}
165+
166+
pngStream.SetLength(0);
167+
pngStream.Position = 0;
168+
await sharpImg.SaveAsPngAsync(pngStream, fastPng);
169+
}
170+
171+
pngStream.Position = 0;
172+
128173
var img = BitmapImageFromStream(pngStream);
174+
129175
await icm.Add(url, img);
176+
130177
return img;
131178
}
132179
catch (Exception ex)
133180
{
134181
exceptionHandler(ex);
135182
return default;
136183
}
137-
})
184+
}))
185+
.Merge(MaxConcurrent) // limit concurrency
138186
.ObserveOnGuiThread();
139187
}
140188

189+
141190
/// <summary>
142191
/// Format bytes to a greater unit
143192
/// </summary>

Wabbajack.App.Wpf/ViewModels/Compiler/CompilerSettingsVM.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public CompilerSettingsVM(CompilerSettings cs)
4040
AlwaysEnabled = cs.AlwaysEnabled.ToHashSet();
4141
Version = cs.Version?.ToString() ?? "";
4242
Description = cs.Description;
43+
AutoGenerateReport = cs.AutoGenerateReport;
4344
}
4445

4546
[Reactive] public bool ModlistIsNSFW { get; set; }
@@ -48,6 +49,8 @@ public CompilerSettingsVM(CompilerSettings cs)
4849
[Reactive] public Game Game { get; set; }
4950
[Reactive] public AbsolutePath OutputFile { get; set; }
5051

52+
[Reactive] public bool AutoGenerateReport { get; set; } = false;
53+
5154
[Reactive] public AbsolutePath ModListImage { get; set; }
5255
[Reactive] public bool UseGamePaths { get; set; }
5356

@@ -133,7 +136,8 @@ public CompilerSettings ToCompilerSettings()
133136
Ignore = Ignore.ToArray(),
134137
AlwaysEnabled = AlwaysEnabled.ToArray(),
135138
Version = System.Version.Parse(Version),
136-
Description = Description
139+
Description = Description,
140+
AutoGenerateReport = AutoGenerateReport
137141
};
138142
}
139143
public AbsolutePath CompilerSettingsPath

0 commit comments

Comments
 (0)