Merge pull request #701 from Noggog/wabbajack-lib-nullability

Wabbajack.Lib Nullability Enabled
This commit is contained in:
Timothy Baldridge
2020-04-12 14:23:08 -06:00
committed by GitHub
111 changed files with 802 additions and 782 deletions

View File

@ -184,4 +184,10 @@ dotnet_diagnostic.CS8609.severity = error
dotnet_diagnostic.CS8714.severity = error dotnet_diagnostic.CS8714.severity = error
# CS8605: Unboxing a possibly null value. # CS8605: Unboxing a possibly null value.
dotnet_diagnostic.CS8605.severity = error dotnet_diagnostic.CS8605.severity = error
# CS8613: Nullability of reference types in return type doesn't match implicitly implemented member.
dotnet_diagnostic.CS8613.severity = error
# CS8632: The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
dotnet_diagnostic.CS8632.severity = error

View File

@ -53,14 +53,14 @@ namespace Wabbajack.BuildServer.Test
public async Task CanNotifyOfInis() public async Task CanNotifyOfInis()
{ {
var archive = var archive =
new Archive new Archive(
{ new NexusDownloader.State
State = new NexusDownloader.State
{ {
Game = Game.SkyrimSpecialEdition, Game = Game.SkyrimSpecialEdition,
ModID = long.MaxValue >> 3, ModID = long.MaxValue >> 3,
FileID = long.MaxValue >> 3, FileID = long.MaxValue >> 3,
}, })
{
Name = Guid.NewGuid().ToString() Name = Guid.NewGuid().ToString()
}; };
Assert.True(await AuthorAPI.UploadPackagedInis(new[] {archive})); Assert.True(await AuthorAPI.UploadPackagedInis(new[] {archive}));

View File

@ -122,19 +122,14 @@ namespace Wabbajack.BuildServer.Test
var modListData = new ModList var modListData = new ModList();
{ modListData.Archives.Add(
Archives = new List<Archive> new Archive(new HTTPDownloader.State(MakeURL("test_archive.txt")))
{ {
new Archive Hash = await test_archive_path.FileHashAsync(),
{ Name = "test_archive",
Hash = await test_archive_path.FileHashAsync(), Size = test_archive_path.Size,
Name = "test_archive", });
Size = test_archive_path.Size,
State = new HTTPDownloader.State {Url = MakeURL("test_archive.txt")}
}
}
};
var modListPath = "test_modlist.wabbajack".RelativeTo(Fixture.ServerPublicFolder); var modListPath = "test_modlist.wabbajack".RelativeTo(Fixture.ServerPublicFolder);

View File

@ -42,13 +42,9 @@ namespace Wabbajack.BuildServer.Test
{ {
Payload = new IndexJob Payload = new IndexJob
{ {
Archive = new Archive Archive = new Archive(new HTTPDownloader.State(MakeURL("old_file_data.random")))
{ {
Name = "Oldfile", Name = "Oldfile",
State = new HTTPDownloader.State
{
Url = MakeURL("old_file_data.random"),
}
} }
} }
}); });
@ -57,13 +53,9 @@ namespace Wabbajack.BuildServer.Test
{ {
Payload = new IndexJob Payload = new IndexJob
{ {
Archive = new Archive Archive = new Archive(new HTTPDownloader.State(MakeURL("new_file_data.random")))
{ {
Name = "Newfile", Name = "Newfile",
State = new HTTPDownloader.State
{
Url = MakeURL("new_file_data.random"),
}
} }
} }
}); });
@ -126,6 +118,5 @@ namespace Wabbajack.BuildServer.Test
Assert.True($"{oldDataHash.ToHex()}_{newDataHash.ToHex()}".RelativeTo(Fixture.ServerUpdatesFolder).IsFile); Assert.True($"{oldDataHash.ToHex()}_{newDataHash.ToHex()}".RelativeTo(Fixture.ServerUpdatesFolder).IsFile);
} }
} }
} }

View File

@ -102,10 +102,9 @@ namespace Wabbajack.BuildServer.Controllers
Priority = Job.JobPriority.Low, Priority = Job.JobPriority.Low,
Payload = new IndexJob Payload = new IndexJob
{ {
Archive = new Archive Archive = new Archive(data)
{ {
Name = entry.Name, Name = entry.Name,
State = data
} }
} }
}); });

View File

@ -22,7 +22,7 @@ namespace Wabbajack.BuildServer.Controllers
public async Task<long> EnqueueJob(string JobName) public async Task<long> EnqueueJob(string JobName)
{ {
var jobtype = AJobPayload.NameToType[JobName]; var jobtype = AJobPayload.NameToType[JobName];
var job = new Job{Priority = Job.JobPriority.High, Payload = (AJobPayload)jobtype.GetConstructor(new Type[0]).Invoke(new object?[0])}; var job = new Job{Priority = Job.JobPriority.High, Payload = (AJobPayload)jobtype.GetConstructor(new Type[0]).Invoke(new object[0])};
await SQL.EnqueueJob(job); await SQL.EnqueueJob(job);
return job.Id; return job.Id;
} }

View File

@ -175,15 +175,19 @@ namespace Wabbajack.BuildServer.Controllers
var allMods = await api.GetModFiles(state.Game, state.ModID); var allMods = await api.GetModFiles(state.Game, state.ModID);
var archive = allMods.files.Where(m => !string.IsNullOrEmpty(m.category_name)) var archive = allMods.files.Where(m => !string.IsNullOrEmpty(m.category_name))
.OrderBy(s => Math.Abs((long)s.size - origSize)) .OrderBy(s => Math.Abs((long)s.size - origSize))
.Select(s => new Archive { .Select(s =>
Name = s.file_name, new Archive(
Size = (long)s.size, new NexusDownloader.State
State = new NexusDownloader.State {
Game = state.Game,
ModID = state.ModID,
FileID = s.file_id
})
{ {
Game = state.Game, Name = s.file_name,
ModID = state.ModID, Size = (long)s.size,
FileID = s.file_id })
}}).FirstOrDefault(); .FirstOrDefault();
if (archive == null) if (archive == null)
{ {

View File

@ -21,13 +21,12 @@ namespace Wabbajack.BuildServer.Models.Jobs
{ {
Utils.Log($"Indexing game files"); Utils.Log($"Indexing game files");
var states = GameRegistry.Games.Values var states = GameRegistry.Games.Values
.Where(game => game.GameLocation() != null && game.MainExecutable != null) .Where(game => game.TryGetGameLocation() != null && game.MainExecutable != null)
.SelectMany(game => game.GameLocation().Value.EnumerateFiles() .SelectMany(game => game.GameLocation().EnumerateFiles()
.Select(file => new GameFileSourceDownloader.State .Select(file => new GameFileSourceDownloader.State(game.InstalledVersion)
{ {
Game = game.Game, Game = game.Game,
GameVersion = game.InstalledVersion, GameFile = file.RelativeTo(game.GameLocation()),
GameFile = file.RelativeTo(game.GameLocation().Value),
})) }))
.ToList(); .ToList();
@ -41,7 +40,7 @@ namespace Wabbajack.BuildServer.Models.Jobs
await states.PMap(queue, async state => await states.PMap(queue, async state =>
{ {
var path = state.Game.MetaData().GameLocation().Value.Combine(state.GameFile); var path = state.Game.MetaData().GameLocation().Combine(state.GameFile);
Utils.Log($"Hashing Game file {path}"); Utils.Log($"Hashing Game file {path}");
try try
{ {
@ -55,16 +54,15 @@ namespace Wabbajack.BuildServer.Models.Jobs
var with_hash = states.Where(state => state.Hash != default).ToList(); var with_hash = states.Where(state => state.Hash != default).ToList();
Utils.Log($"Inserting {with_hash.Count} jobs."); Utils.Log($"Inserting {with_hash.Count} jobs.");
var jobs = states.Select(state => new IndexJob {Archive = new Archive {Name = state.GameFile.FileName.ToString(), State = state}}) var jobs = states.Select(state => new IndexJob {Archive = new Archive(state) { Name = state.GameFile.FileName.ToString()}})
.Select(j => new Job {Payload = j, RequiresNexus = j.UsesNexus}) .Select(j => new Job {Payload = j, RequiresNexus = j.UsesNexus})
.ToList(); .ToList();
foreach (var job in jobs) foreach (var job in jobs)
await sql.EnqueueJob(job); await sql.EnqueueJob(job);
return JobResult.Success(); return JobResult.Success();
} }
} }
protected override IEnumerable<object> PrimaryKey => new object[0]; protected override IEnumerable<object> PrimaryKey => new object[0];

View File

@ -39,13 +39,9 @@ namespace Wabbajack.BuildServer.Models.Jobs
{ {
Payload = new IndexJob Payload = new IndexJob
{ {
Archive = new Archive Archive = new Archive(new MegaDownloader.State(url.ToString()))
{ {
Name = Guid.NewGuid() + ".7z", Name = Guid.NewGuid() + ".7z",
State = new MegaDownloader.State
{
Url = url.ToString()
}
} }
} }
}) })

View File

@ -57,15 +57,11 @@ namespace Wabbajack.BuildServer.Models.Jobs
Priority = Job.JobPriority.High, Priority = Job.JobPriority.High,
Payload = new IndexJob Payload = new IndexJob
{ {
Archive = new Archive Archive = new Archive(new HTTPDownloader.State(file.Uri))
{ {
Name = file.MungedName, Name = file.MungedName,
Size = file.Size, Size = file.Size,
Hash = file.Hash, Hash = file.Hash,
State = new HTTPDownloader.State
{
Url = file.Uri
}
} }
} }
}); });

View File

@ -524,9 +524,8 @@ namespace Wabbajack.BuildServer.Model.Models
var result = await conn.QueryFirstOrDefaultAsync<string>(@"SELECT JsonState FROM dbo.DownloadStates var result = await conn.QueryFirstOrDefaultAsync<string>(@"SELECT JsonState FROM dbo.DownloadStates
WHERE Hash = @hash AND PrimaryKey like 'NexusDownloader+State|%'", WHERE Hash = @hash AND PrimaryKey like 'NexusDownloader+State|%'",
new {Hash = (long)startingHash}); new {Hash = (long)startingHash});
return result == null ? null : new Archive return result == null ? null : new Archive(result.FromJsonString<AbstractDownloadState>())
{ {
State = result.FromJsonString<AbstractDownloadState>(),
Hash = startingHash Hash = startingHash
}; };
} }
@ -536,9 +535,8 @@ namespace Wabbajack.BuildServer.Model.Models
await using var conn = await Open(); await using var conn = await Open();
var result = await conn.QueryFirstOrDefaultAsync<(long Hash, string State)>(@"SELECT Hash, JsonState FROM dbo.DownloadStates WHERE PrimaryKey = @PrimaryKey", var result = await conn.QueryFirstOrDefaultAsync<(long Hash, string State)>(@"SELECT Hash, JsonState FROM dbo.DownloadStates WHERE PrimaryKey = @PrimaryKey",
new {PrimaryKey = primaryKey}); new {PrimaryKey = primaryKey});
return result == default ? null : new Archive return result == default ? null : new Archive(result.State.FromJsonString<AbstractDownloadState>())
{ {
State = result.State.FromJsonString<AbstractDownloadState>(),
Hash = Hash.FromLong(result.Hash) Hash = Hash.FromLong(result.Hash)
}; };
} }

View File

@ -318,7 +318,7 @@ namespace Wabbajack.CLI.Verbs
private static async Task<string> GetTextFileFromModlist(AbsolutePath archive, ModList modlist, RelativePath sourceID) private static async Task<string> GetTextFileFromModlist(AbsolutePath archive, ModList modlist, RelativePath sourceID)
{ {
var installer = new MO2Installer(archive, modlist, default, default, null); var installer = new MO2Installer(archive, modlist, default, default, parameters: null!);
byte[] bytes = await installer.LoadBytesFromPath(sourceID); byte[] bytes = await installer.LoadBytesFromPath(sourceID);
return Encoding.Default.GetString(bytes); return Encoding.Default.GetString(bytes);
} }
@ -328,9 +328,9 @@ namespace Wabbajack.CLI.Verbs
return header.Trim().Replace(" ", "").Replace(".", ""); return header.Trim().Replace(" ", "").Replace(".", "");
} }
private static string GetModName(Archive a) private static string? GetModName(Archive a)
{ {
var result = a.Name; string? result = a.Name;
if (a.State is IMetaState metaState) if (a.State is IMetaState metaState)
{ {

View File

@ -9,7 +9,7 @@ namespace Wabbajack.CLI.Verbs
public class DeleteFile : AVerb public class DeleteFile : AVerb
{ {
[Option('n', "name", Required = true, HelpText = @"Full name (as returned by my-files) of the file")] [Option('n', "name", Required = true, HelpText = @"Full name (as returned by my-files) of the file")]
public string? Name { get; set; } public string Name { get; set; } = null!;
protected override async Task<ExitCode> Run() protected override async Task<ExitCode> Run()
{ {

View File

@ -35,7 +35,7 @@ namespace Wabbajack.CLI.Verbs
new[] {state} new[] {state}
.PMap(queue, async s => .PMap(queue, async s =>
{ {
await s.Download(new Archive {Name = Path.GetFileName(Output)}, (AbsolutePath)Output); await s.Download(new Archive(state: null!) {Name = Path.GetFileName(Output)}, (AbsolutePath)Output);
}).Wait(); }).Wait();
File.WriteAllLines(Output + ".meta", state.GetMetaIni()); File.WriteAllLines(Output + ".meta", state.GetMetaIni());

View File

@ -21,6 +21,9 @@ namespace Wabbajack
return ret; return ret;
} }
/// <summary>
/// Adds the given values to the dictionary. If a key already exists, it will throw an exception
/// </summary>
public static void Add<K, V>(this IDictionary<K, V> dict, IEnumerable<KeyValuePair<K, V>> vals) public static void Add<K, V>(this IDictionary<K, V> dict, IEnumerable<KeyValuePair<K, V>> vals)
where K : notnull where K : notnull
{ {
@ -30,6 +33,9 @@ namespace Wabbajack
} }
} }
/// <summary>
/// Adds the given values to the dictionary. If a key already exists, it will be replaced
/// </summary>
public static void Set<K, V>(this IDictionary<K, V> dict, IEnumerable<KeyValuePair<K, V>> vals) public static void Set<K, V>(this IDictionary<K, V> dict, IEnumerable<KeyValuePair<K, V>> vals)
where K : notnull where K : notnull
{ {
@ -38,5 +44,15 @@ namespace Wabbajack
dict[val.Key] = val.Value; dict[val.Key] = val.Value;
} }
} }
/// <summary>
/// Clears the dictionary and adds the given values
/// </summary>
public static void SetTo<K, V>(this IDictionary<K, V> dict, IEnumerable<KeyValuePair<K, V>> vals)
where K : notnull
{
dict.Clear();
dict.Set(vals);
}
} }
} }

View File

@ -33,5 +33,32 @@ namespace Wabbajack
yield return next; yield return next;
foreach (var itm in coll) yield return itm; foreach (var itm in coll) yield return itm;
} }
/// <summary>
/// Converts and filters a nullable enumerable to a non-nullable enumerable
/// </summary>
public static IEnumerable<T> NotNull<T>(this IEnumerable<T?> e)
where T : class
{
// Filter out nulls
return e.Where(e => e != null)
// Cast to non nullable type
.Select(e => e!);
}
/// <summary>
/// Selects items that are castable to the desired type
/// </summary>
/// <typeparam name="T">Type of the original enumerable to cast from</typeparam>
/// <typeparam name="R">Type to attempt casting to</typeparam>
/// <param name="e">Enumerable to process</param>
/// <returns>Enumerable with only objects that were castable</returns>
public static IEnumerable<R> WhereCastable<T, R>(this IEnumerable<T> e)
where T : class
where R : T
{
return e.Where(e => e is R)
.Select(e => (R)e);
}
} }
} }

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Wabbajack.Common
{
public static class ListExt
{
public static void SetTo<T>(this List<T> list, IEnumerable<T> rhs)
{
list.Clear();
list.AddRange(rhs);
}
}
}

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Alphaleonis.Win32.Filesystem; using Alphaleonis.Win32.Filesystem;
using Microsoft.Win32; using Microsoft.Win32;
@ -85,23 +86,44 @@ namespace Wabbajack.Common
{ {
get get
{ {
AbsolutePath? gameLoc = GameLocation(); if (!TryGetGameLocation(out var gameLoc))
if (gameLoc == null)
throw new GameNotInstalledException(this); throw new GameNotInstalledException(this);
if (MainExecutable == null) if (MainExecutable == null)
throw new NotImplementedException(); throw new NotImplementedException();
return FileVersionInfo.GetVersionInfo((string)gameLoc.Value.Combine(MainExecutable)).ProductVersion; return FileVersionInfo.GetVersionInfo((string)gameLoc.Combine(MainExecutable)).ProductVersion;
} }
} }
public bool IsInstalled => GameLocation() != null; public bool IsInstalled => TryGetGameLocation() != null;
public string? MainExecutable { get; internal set; } public string? MainExecutable { get; internal set; }
public AbsolutePath? GameLocation() public AbsolutePath? TryGetGameLocation()
{ {
return Consts.TestMode ? AbsolutePath.GetCurrentDirectory() : StoreHandler.Instance.GetGamePath(Game); return Consts.TestMode ? AbsolutePath.GetCurrentDirectory() : StoreHandler.Instance.TryGetGamePath(Game);
}
public bool TryGetGameLocation(out AbsolutePath path)
{
var ret = TryGetGameLocation();
if (ret != null)
{
path = ret.Value;
return true;
}
else
{
path = default;
return false;
}
}
public AbsolutePath GameLocation()
{
var ret = TryGetGameLocation();
if (ret == null) throw new ArgumentNullException();
return ret.Value;
} }
} }
@ -156,16 +178,24 @@ namespace Wabbajack.Common
} }
/// <summary> /// <summary>
/// Tries to parse game data from an arbitrary string. Tries first via parsing as a game Enum, then by Nexus name, /// Parse game data from an arbitrary string. Tries first via parsing as a game Enum, then by Nexus name.
/// <param nambe="someName"></param> /// <param nambe="someName">Name to query</param>
/// <returns></returns> /// <returns>GameMetaData found</returns>
public static GameMetaData? GetByFuzzyName(string someName) /// <exception cref="ArgumentNullException">If string could not be translated to a game</exception>
public static GameMetaData GetByFuzzyName(string someName)
{
return TryGetByFuzzyName(someName) ?? throw new ArgumentNullException($"{someName} could not be translated to a game");
}
/// <summary>
/// Tries to parse game data from an arbitrary string. Tries first via parsing as a game Enum, then by Nexus name.
/// <param nambe="someName">Name to query</param>
/// <returns>GameMetaData if found, otherwise null</returns>
public static GameMetaData? TryGetByFuzzyName(string someName)
{ {
if (Enum.TryParse(typeof(Game), someName, true, out var metadata)) return ((Game)metadata!).MetaData(); if (Enum.TryParse(typeof(Game), someName, true, out var metadata)) return ((Game)metadata!).MetaData();
GameMetaData? result = null; GameMetaData? result = GetByNexusName(someName);
result = GetByNexusName(someName);
if (result != null) return result; if (result != null) return result;
result = GetByMO2ArchiveName(someName); result = GetByMO2ArchiveName(someName);
@ -174,6 +204,12 @@ namespace Wabbajack.Common
return int.TryParse(someName, out int id) ? GetBySteamID(id) : null; return int.TryParse(someName, out int id) ? GetBySteamID(id) : null;
} }
public static bool TryGetByFuzzyName(string someName, [MaybeNullWhen(false)] out GameMetaData gameMetaData)
{
gameMetaData = TryGetByFuzzyName(someName);
return gameMetaData != null;
}
public static IReadOnlyDictionary<Game, GameMetaData> Games = new Dictionary<Game, GameMetaData> public static IReadOnlyDictionary<Game, GameMetaData> Games = new Dictionary<Game, GameMetaData>
{ {
{ {

View File

@ -11,7 +11,7 @@ namespace Wabbajack.Common.Http
{ {
public class Client public class Client
{ {
public List<(string, string)> Headers = new List<(string, string)>(); public List<(string, string?)> Headers = new List<(string, string?)>();
public List<Cookie> Cookies = new List<Cookie>(); public List<Cookie> Cookies = new List<Cookie>();
public async Task<HttpResponseMessage> GetAsync(string url, HttpCompletionOption responseHeadersRead = HttpCompletionOption.ResponseHeadersRead) public async Task<HttpResponseMessage> GetAsync(string url, HttpCompletionOption responseHeadersRead = HttpCompletionOption.ResponseHeadersRead)
{ {

View File

@ -62,16 +62,12 @@ namespace Wabbajack.Common
return JsonConvert.SerializeObject(obj, JsonSettings); return JsonConvert.SerializeObject(obj, JsonSettings);
} }
public static T FromJson<T>(this AbsolutePath filename, public static T FromJson<T>(this AbsolutePath filename)
TypeNameHandling handling = TypeNameHandling.All,
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full)
{ {
return JsonConvert.DeserializeObject<T>(filename.ReadAllText(), JsonSettings)!; return JsonConvert.DeserializeObject<T>(filename.ReadAllText(), JsonSettings)!;
} }
public static T FromJsonString<T>(this string data, public static T FromJsonString<T>(this string data)
TypeNameHandling handling = TypeNameHandling.Objects,
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full)
{ {
return JsonConvert.DeserializeObject<T>(data, JsonSettings)!; return JsonConvert.DeserializeObject<T>(data, JsonSettings)!;
} }
@ -230,8 +226,7 @@ namespace Wabbajack.Common
return (Game)i; return (Game)i;
} }
GameMetaData? game = GameRegistry.GetByFuzzyName(str); if (!GameRegistry.TryGetByFuzzyName(str, out var game))
if (game == null)
{ {
throw new ArgumentException($"Could not convert {str} to a Game type."); throw new ArgumentException($"Could not convert {str} to a Game type.");
} }

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Security; using System.Security;
using DynamicData; using DynamicData;
using Microsoft.Win32; using Microsoft.Win32;
#nullable enable
namespace Wabbajack.Common.StoreHandlers namespace Wabbajack.Common.StoreHandlers
{ {
@ -21,9 +22,14 @@ namespace Wabbajack.Common.StoreHandlers
public class SteamWorkshopItem public class SteamWorkshopItem
{ {
public SteamGame? Game; public readonly SteamGame Game;
public int ItemID; public int ItemID;
public int Size; public int Size;
public SteamWorkshopItem(SteamGame game)
{
Game = game;
}
} }
public class SteamHandler : AStoreHandler public class SteamHandler : AStoreHandler
@ -202,14 +208,14 @@ namespace Wabbajack.Common.StoreHandlers
var bracketStart = 0; var bracketStart = 0;
var bracketEnd = 0; var bracketEnd = 0;
SteamWorkshopItem? currentItem = new SteamWorkshopItem(); SteamWorkshopItem? currentItem = new SteamWorkshopItem(game);
lines.Do(l => lines.Do(l =>
{ {
if (end) if (end)
return; return;
if (currentItem == null) if (currentItem == null)
currentItem = new SteamWorkshopItem(); currentItem = new SteamWorkshopItem(game);
var currentLine = lines.IndexOf(l); var currentLine = lines.IndexOf(l);
if (l.ContainsCaseInsensitive("\"appid\"") && !foundAppID) if (l.ContainsCaseInsensitive("\"appid\"") && !foundAppID)
@ -271,7 +277,6 @@ namespace Wabbajack.Common.StoreHandlers
bracketStart = 0; bracketStart = 0;
bracketEnd = 0; bracketEnd = 0;
currentItem.Game = game;
game.WorkshopItems.Add(currentItem); game.WorkshopItems.Add(currentItem);
Utils.Log($"Found Steam Workshop item {currentItem.ItemID}"); Utils.Log($"Found Steam Workshop item {currentItem.ItemID}");

View File

@ -52,7 +52,7 @@ namespace Wabbajack.Common.StoreHandlers
} }
} }
public AbsolutePath? GetGamePath(Game game) public AbsolutePath? TryGetGamePath(Game game)
{ {
return StoreGames.FirstOrDefault(g => g.Game == game)?.Path; return StoreGames.FirstOrDefault(g => g.Game == game)?.Path;
} }

View File

@ -49,7 +49,7 @@ namespace Wabbajack.Common
private readonly BehaviorSubject<(int DesiredCPUs, int CurrentCPUs)> _cpuCountSubj = new BehaviorSubject<(int DesiredCPUs, int CurrentCPUs)>((0, 0)); private readonly BehaviorSubject<(int DesiredCPUs, int CurrentCPUs)> _cpuCountSubj = new BehaviorSubject<(int DesiredCPUs, int CurrentCPUs)>((0, 0));
public IObservable<(int CurrentCPUs, int DesiredCPUs)> CurrentCpuCount => _cpuCountSubj; public IObservable<(int CurrentCPUs, int DesiredCPUs)> CurrentCpuCount => _cpuCountSubj;
private readonly Subject<IObservable<int>> _activeNumThreadsObservable = new Subject<IObservable<int>>(); private readonly Subject<IObservable<int>?> _activeNumThreadsObservable = new Subject<IObservable<int>?>();
public static TimeSpan PollMS = TimeSpan.FromMilliseconds(200); public static TimeSpan PollMS = TimeSpan.FromMilliseconds(200);
@ -66,7 +66,7 @@ namespace Wabbajack.Common
/// Creates a WorkQueue whos number of threads is determined by the given observable /// Creates a WorkQueue whos number of threads is determined by the given observable
/// </summary> /// </summary>
/// <param name="numThreads">Driving observable that determines how many threads should be actively pulling jobs from the queue</param> /// <param name="numThreads">Driving observable that determines how many threads should be actively pulling jobs from the queue</param>
public WorkQueue(IObservable<int> numThreads) public WorkQueue(IObservable<int>? numThreads)
{ {
// Hook onto the number of active threads subject, and subscribe to it for changes // Hook onto the number of active threads subject, and subscribe to it for changes
_activeNumThreadsObservable _activeNumThreadsObservable
@ -86,7 +86,7 @@ namespace Wabbajack.Common
/// Sets the driving observable that determines how many threads should be actively pulling jobs from the queue /// Sets the driving observable that determines how many threads should be actively pulling jobs from the queue
/// </summary> /// </summary>
/// <param name="numThreads">Driving observable that determines how many threads should be actively pulling jobs from the queue</param> /// <param name="numThreads">Driving observable that determines how many threads should be actively pulling jobs from the queue</param>
public void SetActiveThreadsObservable(IObservable<int> numThreads) public void SetActiveThreadsObservable(IObservable<int>? numThreads)
{ {
_activeNumThreadsObservable.OnNext(numThreads); _activeNumThreadsObservable.OnNext(numThreads);
} }

View File

@ -15,9 +15,9 @@ namespace Wabbajack.Lib
{ {
public WorkQueue Queue { get; } = new WorkQueue(); public WorkQueue Queue { get; } = new WorkQueue();
public Context VFS { get; private set; } public Context VFS { get; }
protected StatusUpdateTracker UpdateTracker { get; private set; } protected StatusUpdateTracker UpdateTracker { get; }
private Subject<Percent> _percentCompleted { get; } = new Subject<Percent>(); private Subject<Percent> _percentCompleted { get; } = new Subject<Percent>();
@ -42,7 +42,6 @@ namespace Wabbajack.Lib
private Subject<bool> _isRunning { get; } = new Subject<bool>(); private Subject<bool> _isRunning { get; } = new Subject<bool>();
public IObservable<bool> IsRunning => _isRunning; public IObservable<bool> IsRunning => _isRunning;
private int _configured;
private int _started; private int _started;
private readonly CancellationTokenSource _cancel = new CancellationTokenSource(); private readonly CancellationTokenSource _cancel = new CancellationTokenSource();
@ -53,21 +52,16 @@ namespace Wabbajack.Lib
public BehaviorSubject<byte> MaxCores = new BehaviorSubject<byte>(byte.MaxValue); public BehaviorSubject<byte> MaxCores = new BehaviorSubject<byte>(byte.MaxValue);
public BehaviorSubject<Percent> TargetUsagePercent = new BehaviorSubject<Percent>(Percent.One); public BehaviorSubject<Percent> TargetUsagePercent = new BehaviorSubject<Percent>(Percent.One);
protected void ConfigureProcessor(int steps, IObservable<int> numThreads = null) public ABatchProcessor(int steps)
{ {
if (1 == Interlocked.CompareExchange(ref _configured, 1, 1))
{
throw new InvalidDataException("Can't configure a processor twice");
}
Queue.SetActiveThreadsObservable(numThreads);
UpdateTracker = new StatusUpdateTracker(steps); UpdateTracker = new StatusUpdateTracker(steps);
VFS = new Context(Queue) { UpdateTracker = UpdateTracker };
Queue.Status.Subscribe(_queueStatus) Queue.Status.Subscribe(_queueStatus)
.DisposeWith(_subs); .DisposeWith(_subs);
Queue.LogMessages.Subscribe(_logMessages) Queue.LogMessages.Subscribe(_logMessages)
.DisposeWith(_subs); .DisposeWith(_subs);
UpdateTracker.Progress.Subscribe(_percentCompleted); UpdateTracker.Progress.Subscribe(_percentCompleted);
UpdateTracker.StepName.Subscribe(_textStatus); UpdateTracker.StepName.Subscribe(_textStatus);
VFS = new Context(Queue) { UpdateTracker = UpdateTracker };
} }
/// <summary> /// <summary>

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
@ -19,10 +19,10 @@ namespace Wabbajack.Lib
{ {
public abstract class ACompiler : ABatchProcessor public abstract class ACompiler : ABatchProcessor
{ {
public string ModListName, ModListAuthor, ModListDescription, ModListWebsite; public string? ModListName, ModListAuthor, ModListDescription, ModListWebsite;
public AbsolutePath ModListImage, ModListReadme; public AbsolutePath ModListImage, ModListReadme;
public bool ReadmeIsWebsite; public bool ReadmeIsWebsite;
protected Version WabbajackVersion; protected Version? WabbajackVersion;
public abstract AbsolutePath VFSCacheName { get; } public abstract AbsolutePath VFSCacheName { get; }
//protected string VFSCacheName => Path.Combine(Consts.LocalAppDataPath, $"vfs_compile_cache.bin"); //protected string VFSCacheName => Path.Combine(Consts.LocalAppDataPath, $"vfs_compile_cache.bin");
@ -42,14 +42,19 @@ namespace Wabbajack.Lib
public bool IgnoreMissingFiles { get; set; } public bool IgnoreMissingFiles { get; set; }
public ICollection<Archive> SelectedArchives = new List<Archive>(); public List<Archive> SelectedArchives { get; protected set; } = new List<Archive>();
public List<Directive> InstallDirectives = new List<Directive>(); public List<Directive> InstallDirectives { get; protected set; } = new List<Directive>();
public List<RawSourceFile> AllFiles = new List<RawSourceFile>(); public List<RawSourceFile> AllFiles { get; protected set; } = new List<RawSourceFile>();
public ModList ModList = new ModList(); public ModList ModList = new ModList();
public List<IndexedArchive> IndexedArchives = new List<IndexedArchive>(); public List<IndexedArchive> IndexedArchives = new List<IndexedArchive>();
public Dictionary<Hash, IEnumerable<VirtualFile>> IndexedFiles = new Dictionary<Hash, IEnumerable<VirtualFile>>(); public Dictionary<Hash, IEnumerable<VirtualFile>> IndexedFiles = new Dictionary<Hash, IEnumerable<VirtualFile>>();
public ACompiler(int steps)
: base(steps)
{
}
public static void Info(string msg) public static void Info(string msg)
{ {
Utils.Log(msg); Utils.Log(msg);
@ -216,7 +221,7 @@ namespace Wabbajack.Lib
.GroupBy(f => f.File.Hash) .GroupBy(f => f.File.Hash)
.ToDictionary(f => f.Key, f => f.First()); .ToDictionary(f => f.Key, f => f.First());
SelectedArchives = await hashes.PMap(Queue, hash => ResolveArchive(hash, archives)); SelectedArchives.SetTo(await hashes.PMap(Queue, hash => ResolveArchive(hash, archives)));
} }
public async Task<Archive> ResolveArchive(Hash hash, IDictionary<Hash, IndexedArchive> archives) public async Task<Archive> ResolveArchive(Hash hash, IDictionary<Hash, IndexedArchive> archives)
@ -226,8 +231,7 @@ namespace Wabbajack.Lib
return await ResolveArchive(found); return await ResolveArchive(found);
} }
Error($"No match found for Archive sha: {hash.ToBase64()} this shouldn't happen"); throw new ArgumentException($"No match found for Archive sha: {hash.ToBase64()} this shouldn't happen");
return null;
} }
public async Task<Archive> ResolveArchive(IndexedArchive archive) public async Task<Archive> ResolveArchive(IndexedArchive archive)
@ -238,10 +242,7 @@ namespace Wabbajack.Lib
Error( Error(
$"No download metadata found for {archive.Name}, please use MO2 to query info or add a .meta file and try again."); $"No download metadata found for {archive.Name}, please use MO2 to query info or add a .meta file and try again.");
var result = new Archive var result = new Archive(await DownloadDispatcher.ResolveArchive(archive.IniData));
{
State = await DownloadDispatcher.ResolveArchive(archive.IniData)
};
if (result.State == null) if (result.State == null)
Error($"{archive.Name} could not be handled by any of the downloaders"); Error($"{archive.Name} could not be handled by any of the downloaders");

View File

@ -27,11 +27,12 @@ namespace Wabbajack.Lib
public AbsolutePath ModListArchive { get; private set; } public AbsolutePath ModListArchive { get; private set; }
public ModList ModList { get; private set; } public ModList ModList { get; private set; }
public Dictionary<Hash, AbsolutePath> HashedArchives { get; set; } public Dictionary<Hash, AbsolutePath> HashedArchives { get; } = new Dictionary<Hash, AbsolutePath>();
public SystemParameters SystemParameters { get; set; } public SystemParameters? SystemParameters { get; set; }
public AInstaller(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters parameters) public AInstaller(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters? parameters, int steps)
: base(steps)
{ {
ModList = modList; ModList = modList;
ModListArchive = archive; ModListArchive = archive;
@ -166,6 +167,10 @@ namespace Wabbajack.Lib
.PDoIndexed(queue, async (idx, group) => .PDoIndexed(queue, async (idx, group) =>
{ {
Utils.Status("Installing files", Percent.FactoryPutInRange(idx, vFiles.Count)); Utils.Status("Installing files", Percent.FactoryPutInRange(idx, vFiles.Count));
if (group.Key == null)
{
throw new ArgumentNullException("FromFile was null");
}
var firstDest = OutputFolder.Combine(group.First().To); var firstDest = OutputFolder.Combine(group.First().To);
await CopyFile(group.Key.StagedPath, firstDest, true); await CopyFile(group.Key.StagedPath, firstDest, true);
@ -173,7 +178,6 @@ namespace Wabbajack.Lib
{ {
await CopyFile(firstDest, OutputFolder.Combine(copy.To), false); await CopyFile(firstDest, OutputFolder.Combine(copy.To), false);
} }
}); });
Status("Unstaging files"); Status("Unstaging files");
@ -278,11 +282,11 @@ namespace Wabbajack.Lib
var hashResults = await DownloadFolder.EnumerateFiles() var hashResults = await DownloadFolder.EnumerateFiles()
.Where(e => e.Extension != Consts.HashFileExtension) .Where(e => e.Extension != Consts.HashFileExtension)
.PMap(Queue, async e => (await e.FileHashCachedAsync(), e)); .PMap(Queue, async e => (await e.FileHashCachedAsync(), e));
HashedArchives = hashResults HashedArchives.SetTo(hashResults
.OrderByDescending(e => e.Item2.LastModified) .OrderByDescending(e => e.Item2.LastModified)
.GroupBy(e => e.Item1) .GroupBy(e => e.Item1)
.Select(e => e.First()) .Select(e => e.First())
.ToDictionary(e => e.Item1, e => e.Item2); .Select(e => new KeyValuePair<Hash, AbsolutePath>(e.Item1, e.Item2)));
} }
/// <summary> /// <summary>
@ -391,8 +395,13 @@ namespace Wabbajack.Lib
return await path.FileHashAsync() == d.Hash ? d : null; return await path.FileHashAsync() == d.Hash ? d : null;
})) }))
.Where(d => d != null) .Do(d =>
.Do(d => indexed.Remove(d.To)); {
if (d != null)
{
indexed.Remove(d.To);
}
});
UpdateTracker.NextStep("Updating ModList"); UpdateTracker.NextStep("Updating ModList");
Utils.Log($"Optimized {ModList.Directives.Count} directives to {indexed.Count} required"); Utils.Log($"Optimized {ModList.Directives.Count} directives to {indexed.Count} required");

View File

@ -14,7 +14,7 @@ namespace Wabbajack.Lib
return client; return client;
} }
public static async Task<Archive> GetModUpgrade(Hash hash) public static async Task<Archive?> GetModUpgrade(Hash hash)
{ {
using var response = await GetClient() using var response = await GetClient()
.GetAsync($"{Consts.WabbajackBuildServerUri}alternative/{hash.ToHex()}"); .GetAsync($"{Consts.WabbajackBuildServerUri}alternative/{hash.ToHex()}");
@ -33,7 +33,7 @@ namespace Wabbajack.Lib
/// </summary> /// </summary>
/// <param name="hash"></param> /// <param name="hash"></param>
/// <returns></returns> /// <returns></returns>
public static async Task<string> GetModIni(Hash hash) public static async Task<string?> GetModIni(Hash hash)
{ {
var client = new Common.Http.Client(); var client = new Common.Http.Client();
try try

View File

@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps
_compiler = compiler; _compiler = compiler;
} }
public abstract ValueTask<Directive> Run(RawSourceFile source); public abstract ValueTask<Directive?> Run(RawSourceFile source);
public abstract IState GetState(); public abstract IState GetState();
} }
} }

View File

@ -51,7 +51,7 @@ namespace Wabbajack.Lib.CompilationSteps
return new State(); return new State();
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!Consts.SupportedBSAs.Contains(source.Path.Extension)) return null; if (!Consts.SupportedBSAs.Contains(source.Path.Extension)) return null;
@ -79,12 +79,12 @@ namespace Wabbajack.Lib.CompilationSteps
CreateBSA directive; CreateBSA directive;
using (var bsa = BSADispatch.OpenRead(source.AbsolutePath)) using (var bsa = BSADispatch.OpenRead(source.AbsolutePath))
{ {
directive = new CreateBSA directive = new CreateBSA(
state: bsa.State,
items: bsa.Files.Select(f => f.State).ToList())
{ {
To = source.Path, To = source.Path,
TempID = (RelativePath)id, TempID = (RelativePath)id,
State = bsa.State,
FileStates = bsa.Files.Select(f => f.State).ToList()
}; };
} }

View File

@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps
{ {
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!_compiler.IndexedFiles.TryGetValue(source.Hash, out var found)) return null; if (!_compiler.IndexedFiles.TryGetValue(source.Hash, out var found)) return null;
var result = source.EvolveTo<FromArchive>(); var result = source.EvolveTo<FromArchive>();

View File

@ -10,7 +10,7 @@ namespace Wabbajack.Lib.CompilationSteps
{ {
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
var result = source.EvolveTo<NoMatch>(); var result = source.EvolveTo<NoMatch>();
result.Reason = "No Match in Stack"; result.Reason = "No Match in Stack";

View File

@ -4,7 +4,7 @@ namespace Wabbajack.Lib.CompilationSteps
{ {
public interface ICompilationStep public interface ICompilationStep
{ {
ValueTask<Directive> Run(RawSourceFile source); ValueTask<Directive?> Run(RawSourceFile source);
IState GetState(); IState GetState();
} }

View File

@ -25,7 +25,7 @@ namespace Wabbajack.Lib.CompilationSteps
.ToList(); .ToList();
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!source.AbsolutePath.InFolder(_mo2Compiler.MO2ModsFolder)) return null; if (!source.AbsolutePath.InFolder(_mo2Compiler.MO2ModsFolder)) return null;
if (_allEnabledMods.Any(mod => source.AbsolutePath.InFolder(mod))) if (_allEnabledMods.Any(mod => source.AbsolutePath.InFolder(mod)))

View File

@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
_reason = $"Ignored because path ends with {postfix}"; _reason = $"Ignored because path ends with {postfix}";
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!((string)source.Path).EndsWith(_postfix)) return null; if (!((string)source.Path).EndsWith(_postfix)) return null;
var result = source.EvolveTo<IgnoredDirectly>(); var result = source.EvolveTo<IgnoredDirectly>();
@ -30,17 +30,13 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IgnoreEndsWith")] [JsonObject("IgnoreEndsWith")]
public class State : IState public class State : IState
{ {
public string Postfix { get; set; }
public State(string postfix) public State(string postfix)
{ {
Postfix = postfix; Postfix = postfix;
} }
public State()
{
}
public string Postfix { get; set; }
public ICompilationStep CreateStep(ACompiler compiler) public ICompilationStep CreateStep(ACompiler compiler)
{ {
return new IgnoreEndsWith(compiler, Postfix); return new IgnoreEndsWith(compiler, Postfix);

View File

@ -13,7 +13,7 @@ namespace Wabbajack.Lib.CompilationSteps
_startDir = Consts.GameFolderFilesDir + "\\"; _startDir = Consts.GameFolderFilesDir + "\\";
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!((string)source.Path).StartsWith(_startDir)) return null; if (!((string)source.Path).StartsWith(_startDir)) return null;
var i = source.EvolveTo<IgnoredDirectly>(); var i = source.EvolveTo<IgnoredDirectly>();

View File

@ -15,7 +15,7 @@ namespace Wabbajack.Lib.CompilationSteps
_gameFolder = compiler.GamePath; _gameFolder = compiler.GamePath;
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (_gameFolderFilesExists) if (_gameFolderFilesExists)
{ {

View File

@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
_reason = $"Ignored because path contains {_pattern}"; _reason = $"Ignored because path contains {_pattern}";
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!((string)source.Path).Contains(_pattern)) return null; if (!((string)source.Path).Contains(_pattern)) return null;
var result = source.EvolveTo<IgnoredDirectly>(); var result = source.EvolveTo<IgnoredDirectly>();
@ -30,17 +30,13 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IgnorePathContains")] [JsonObject("IgnorePathContains")]
public class State : IState public class State : IState
{ {
public State() public string Pattern { get; set; }
{
}
public State(string pattern) public State(string pattern)
{ {
Pattern = pattern; Pattern = pattern;
} }
public string Pattern { get; set; }
public ICompilationStep CreateStep(ACompiler compiler) public ICompilationStep CreateStep(ACompiler compiler)
{ {
return new IgnorePathContains(compiler, Pattern); return new IgnorePathContains(compiler, Pattern);

View File

@ -17,7 +17,7 @@ namespace Wabbajack.Lib.CompilationSteps
_regex = new Regex(pattern); _regex = new Regex(pattern);
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!_regex.IsMatch((string)source.Path)) return null; if (!_regex.IsMatch((string)source.Path)) return null;
var result = source.EvolveTo<IgnoredDirectly>(); var result = source.EvolveTo<IgnoredDirectly>();
@ -33,17 +33,13 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IgnorePattern")] [JsonObject("IgnorePattern")]
public class State : IState public class State : IState
{ {
public State() public string Pattern { get; set; }
{
}
public State(string pattern) public State(string pattern)
{ {
Pattern = pattern; Pattern = pattern;
} }
public string Pattern { get; set; }
public ICompilationStep CreateStep(ACompiler compiler) public ICompilationStep CreateStep(ACompiler compiler)
{ {
return new IgnoreRegex(compiler, Pattern); return new IgnoreRegex(compiler, Pattern);

View File

@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
_reason = string.Format("Ignored because path starts with {0}", _prefix); _reason = string.Format("Ignored because path starts with {0}", _prefix);
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!((string)source.Path).StartsWith(_prefix)) if (!((string)source.Path).StartsWith(_prefix))
{ {
@ -35,17 +35,13 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IgnoreStartsWith")] [JsonObject("IgnoreStartsWith")]
public class State : IState public class State : IState
{ {
public State() public string Prefix { get; set; }
{
}
public State(string prefix) public State(string prefix)
{ {
Prefix = prefix; Prefix = prefix;
} }
public string Prefix { get; set; }
public ICompilationStep CreateStep(ACompiler compiler) public ICompilationStep CreateStep(ACompiler compiler)
{ {
return new IgnoreStartsWith(compiler, Prefix); return new IgnoreStartsWith(compiler, Prefix);

View File

@ -19,7 +19,7 @@ namespace Wabbajack.Lib.CompilationSteps
}; };
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!_cruftFiles.Any(f => source.Path.StartsWith(f))) return null; if (!_cruftFiles.Any(f => source.Path.StartsWith(f))) return null;
var result = source.EvolveTo<IgnoredDirectly>(); var result = source.EvolveTo<IgnoredDirectly>();

View File

@ -10,7 +10,7 @@ namespace Wabbajack.Lib.CompilationSteps
{ {
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
var inline = source.EvolveTo<InlineFile>(); var inline = source.EvolveTo<InlineFile>();
inline.SourceDataID = await _compiler.IncludeFile(await source.AbsolutePath.ReadAllBytesAsync()); inline.SourceDataID = await _compiler.IncludeFile(await source.AbsolutePath.ReadAllBytesAsync());

View File

@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps
{ {
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!Consts.ConfigFileExtensions.Contains(source.Path.Extension)) return null; if (!Consts.ConfigFileExtensions.Contains(source.Path.Extension)) return null;
var result = source.EvolveTo<InlineFile>(); var result = source.EvolveTo<InlineFile>();

View File

@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps
{ {
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (source.AbsolutePath.Extension != Consts.ESP && if (source.AbsolutePath.Extension != Consts.ESP &&
source.AbsolutePath.Extension != Consts.ESM) return null; source.AbsolutePath.Extension != Consts.ESM) return null;

View File

@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
_prefix = Consts.LOOTFolderFilesDir + "\\"; _prefix = Consts.LOOTFolderFilesDir + "\\";
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!source.Path.StartsWith(_prefix)) return null; if (!source.Path.StartsWith(_prefix)) return null;
var result = source.EvolveTo<InlineFile>(); var result = source.EvolveTo<InlineFile>();

View File

@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps
{ {
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!source.Path.StartsWith("mods\\") || source.Path.FileName != Consts.MetaIni) return null; if (!source.Path.StartsWith("mods\\") || source.Path.FileName != Consts.MetaIni) return null;
var e = source.EvolveTo<InlineFile>(); var e = source.EvolveTo<InlineFile>();

View File

@ -23,7 +23,7 @@ namespace Wabbajack.Lib.CompilationSteps
.ToList(); .ToList();
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!source.AbsolutePath.InFolder(_modProfilesFolder)) return null; if (!source.AbsolutePath.InFolder(_modProfilesFolder)) return null;
if (_profiles.Any(profile => source.AbsolutePath.InFolder(profile))) return null; if (_profiles.Any(profile => source.AbsolutePath.InFolder(profile))) return null;

View File

@ -12,11 +12,11 @@ namespace Wabbajack.Lib.CompilationSteps
public class IncludePatches : ACompilationStep public class IncludePatches : ACompilationStep
{ {
private readonly Dictionary<RelativePath, IGrouping<RelativePath, VirtualFile>> _indexed; private readonly Dictionary<RelativePath, IGrouping<RelativePath, VirtualFile>> _indexed;
private VirtualFile _bsa; private VirtualFile? _bsa;
private Dictionary<RelativePath, VirtualFile> _indexedByName; private Dictionary<RelativePath, VirtualFile> _indexedByName;
private MO2Compiler _mo2Compiler; private MO2Compiler _mo2Compiler;
public IncludePatches(ACompiler compiler, VirtualFile constructingFromBSA = null) : base(compiler) public IncludePatches(ACompiler compiler, VirtualFile? constructingFromBSA = null) : base(compiler)
{ {
_bsa = constructingFromBSA; _bsa = constructingFromBSA;
_mo2Compiler = (MO2Compiler)compiler; _mo2Compiler = (MO2Compiler)compiler;
@ -30,9 +30,8 @@ namespace Wabbajack.Lib.CompilationSteps
.ToDictionary(f => f.FullPath.FileName); .ToDictionary(f => f.FullPath.FileName);
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
var name = source.File.Name.FileName; var name = source.File.Name.FileName;
RelativePath nameWithoutExt = name; RelativePath nameWithoutExt = name;
if (name.Extension == Consts.MOHIDDEN) if (name.Extension == Consts.MOHIDDEN)
@ -41,7 +40,7 @@ namespace Wabbajack.Lib.CompilationSteps
if (!_indexed.TryGetValue(name, out var choices)) if (!_indexed.TryGetValue(name, out var choices))
_indexed.TryGetValue(nameWithoutExt, out choices); _indexed.TryGetValue(nameWithoutExt, out choices);
dynamic modIni = null; dynamic? modIni = null;
if (source.AbsolutePath.InFolder(_mo2Compiler.MO2ModsFolder)) if (source.AbsolutePath.InFolder(_mo2Compiler.MO2ModsFolder))
{ {
if (_bsa == null) if (_bsa == null)
@ -55,7 +54,7 @@ namespace Wabbajack.Lib.CompilationSteps
var installationFile = (RelativePath)modIni?.General?.installationFile; var installationFile = (RelativePath)modIni?.General?.installationFile;
VirtualFile found = null; VirtualFile? found = null;
// Find based on exact file name + ext // Find based on exact file name + ext
if (choices != null) if (choices != null)

View File

@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
{ {
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
var files = new HashSet<AbsolutePath> var files = new HashSet<AbsolutePath>
{ {

View File

@ -16,7 +16,7 @@ namespace Wabbajack.Lib.CompilationSteps
_regex = new Regex(pattern); _regex = new Regex(pattern);
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!_regex.IsMatch((string)source.Path)) return null; if (!_regex.IsMatch((string)source.Path)) return null;
@ -33,17 +33,13 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IncludeRegex")] [JsonObject("IncludeRegex")]
public class State : IState public class State : IState
{ {
public State() public string Pattern { get; set; }
{
}
public State(string pattern) public State(string pattern)
{ {
Pattern = pattern; Pattern = pattern;
} }
public string Pattern { get; set; }
public ICompilationStep CreateStep(ACompiler compiler) public ICompilationStep CreateStep(ACompiler compiler)
{ {
return new IncludeRegex(compiler, Pattern); return new IncludeRegex(compiler, Pattern);

View File

@ -18,7 +18,7 @@ namespace Wabbajack.Lib.CompilationSteps
_game = steamGame; _game = steamGame;
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!_regex.IsMatch((string)source.Path)) if (!_regex.IsMatch((string)source.Path))
return null; return null;
@ -31,7 +31,7 @@ namespace Wabbajack.Lib.CompilationSteps
if (id == 0) if (id == 0)
return null; return null;
SteamWorkshopItem item = null; SteamWorkshopItem? item = null;
_game.WorkshopItems.Where(i => i.ItemID == id).Do(i => item = i); _game.WorkshopItems.Where(i => i.ItemID == id).Do(i => item = i);
if (item == null) if (item == null)
return null; return null;

View File

@ -15,7 +15,7 @@ namespace Wabbajack.Lib.CompilationSteps
_mo2Compiler = (MO2Compiler) compiler; _mo2Compiler = (MO2Compiler) compiler;
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
return Consts.ConfigFileExtensions.Contains(source.Path.Extension) ? await RemapFile(source) : null; return Consts.ConfigFileExtensions.Contains(source.Path.Extension) ? await RemapFile(source) : null;
} }
@ -25,7 +25,7 @@ namespace Wabbajack.Lib.CompilationSteps
return new State(); return new State();
} }
private async Task<Directive> RemapFile(RawSourceFile source) private async Task<Directive?> RemapFile(RawSourceFile source)
{ {
var data = await source.AbsolutePath.ReadAllTextAsync(); var data = await source.AbsolutePath.ReadAllTextAsync();
var originalData = data; var originalData = data;

View File

@ -26,7 +26,7 @@ namespace Wabbajack.Lib.CompilationSteps
}).Select(kv => $"mods\\{kv.Key}\\"); }).Select(kv => $"mods\\{kv.Key}\\");
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!source.Path.StartsWith(Consts.MO2ModFolderName)) return null; if (!source.Path.StartsWith(Consts.MO2ModFolderName)) return null;
foreach (var modpath in _includeDirectly) foreach (var modpath in _includeDirectly)
@ -48,17 +48,13 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IncludeTaggedMods")] [JsonObject("IncludeTaggedMods")]
public class State : IState public class State : IState
{ {
public State() public string Tag { get; set; }
{
}
public State(string tag) public State(string tag)
{ {
Tag = tag; Tag = tag;
} }
public string Tag { get; set; }
public ICompilationStep CreateStep(ACompiler compiler) public ICompilationStep CreateStep(ACompiler compiler)
{ {
return new IncludeTaggedMods(compiler, Tag); return new IncludeTaggedMods(compiler, Tag);

View File

@ -19,7 +19,7 @@ namespace Wabbajack.Lib.CompilationSteps
_correctProfiles = _mo2Compiler.SelectedProfiles.Select(p => _mo2Compiler.MO2ProfileDir.Parent.Combine(p)).ToList(); _correctProfiles = _mo2Compiler.SelectedProfiles.Select(p => _mo2Compiler.MO2ProfileDir.Parent.Combine(p)).ToList();
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
if (!_correctProfiles.Any(p => source.AbsolutePath.InFolder(p))) if (!_correctProfiles.Any(p => source.AbsolutePath.InFolder(p)))
return null; return null;

View File

@ -12,7 +12,7 @@ namespace Wabbajack.Lib.CompilationSteps
{ {
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
// * TODO I don't know what this does // * TODO I don't know what this does
/* /*

View File

@ -16,7 +16,7 @@ namespace Wabbajack.Lib.CompilationSteps
_mo2Compiler = (MO2Compiler) compiler; _mo2Compiler = (MO2Compiler) compiler;
} }
public override async ValueTask<Directive> Run(RawSourceFile source) public override async ValueTask<Directive?> Run(RawSourceFile source)
{ {
var filename = source.Path.FileName; var filename = source.Path.FileName;
var gameFile = _mo2Compiler.GamePath.Combine((RelativePath)"Data", filename); var gameFile = _mo2Compiler.GamePath.Combine((RelativePath)"Data", filename);

View File

@ -43,22 +43,22 @@ namespace Wabbajack.Lib
/// <summary> /// <summary>
/// Archives required by this modlist /// Archives required by this modlist
/// </summary> /// </summary>
public List<Archive> Archives; public List<Archive> Archives = new List<Archive>();
/// <summary> /// <summary>
/// Author of the ModList /// Author of the ModList
/// </summary> /// </summary>
public string Author; public string Author = string.Empty;
/// <summary> /// <summary>
/// Description of the ModList /// Description of the ModList
/// </summary> /// </summary>
public string Description; public string Description = string.Empty;
/// <summary> /// <summary>
/// Install directives /// Install directives
/// </summary> /// </summary>
public List<Directive> Directives; public List<Directive> Directives = new List<Directive>();
/// <summary> /// <summary>
/// The game variant to which this game applies /// The game variant to which this game applies
@ -78,12 +78,12 @@ namespace Wabbajack.Lib
/// <summary> /// <summary>
/// Name of the ModList /// Name of the ModList
/// </summary> /// </summary>
public string Name; public string Name = string.Empty;
/// <summary> /// <summary>
/// readme path or website /// readme path or website
/// </summary> /// </summary>
public string Readme; public string Readme = string.Empty;
/// <summary> /// <summary>
/// Whether readme is a website /// Whether readme is a website
@ -93,12 +93,12 @@ namespace Wabbajack.Lib
/// <summary> /// <summary>
/// The build version of Wabbajack used when compiling the Modlist /// The build version of Wabbajack used when compiling the Modlist
/// </summary> /// </summary>
public Version WabbajackVersion; public Version? WabbajackVersion;
/// <summary> /// <summary>
/// Website of the ModList /// Website of the ModList
/// </summary> /// </summary>
public Uri Website; public Uri? Website;
/// <summary> /// <summary>
/// The size of all the archives once they're downloaded /// The size of all the archives once they're downloaded
@ -134,7 +134,7 @@ namespace Wabbajack.Lib
public class IgnoredDirectly : Directive public class IgnoredDirectly : Directive
{ {
public string Reason; public string Reason = string.Empty;
} }
public class NoMatch : IgnoredDirectly public class NoMatch : IgnoredDirectly
@ -190,12 +190,12 @@ namespace Wabbajack.Lib
[JsonName("FromArchive")] [JsonName("FromArchive")]
public class FromArchive : Directive public class FromArchive : Directive
{ {
private string _fullPath; private string? _fullPath;
public HashRelativePath ArchiveHashPath { get; set; } public HashRelativePath ArchiveHashPath { get; set; }
[JsonIgnore] [JsonIgnore]
public VirtualFile FromFile { get; set; } public VirtualFile? FromFile { get; set; }
[JsonIgnore] [JsonIgnore]
public string FullPath => _fullPath ??= string.Join("|", ArchiveHashPath); public string FullPath => _fullPath ??= string.Join("|", ArchiveHashPath);
@ -205,8 +205,17 @@ namespace Wabbajack.Lib
public class CreateBSA : Directive public class CreateBSA : Directive
{ {
public RelativePath TempID { get; set; } public RelativePath TempID { get; set; }
public ArchiveStateObject State { get; set; } public ArchiveStateObject State { get; }
public List<FileStateObject> FileStates { get; set; } public List<FileStateObject> FileStates { get; set; } = new List<FileStateObject>();
public CreateBSA(ArchiveStateObject state, IEnumerable<FileStateObject>? items = null)
{
State = state;
if (items != null)
{
FileStates.AddRange(items);
}
}
} }
[JsonName("PatchedFromArchive")] [JsonName("PatchedFromArchive")]
@ -231,7 +240,7 @@ namespace Wabbajack.Lib
public class MergedPatch : Directive public class MergedPatch : Directive
{ {
public RelativePath PatchID { get; set; } public RelativePath PatchID { get; set; }
public List<SourcePatch> Sources { get; set; } public List<SourcePatch> Sources { get; set; } = new List<SourcePatch>();
} }
[JsonName("Archive")] [JsonName("Archive")]
@ -245,49 +254,33 @@ namespace Wabbajack.Lib
/// <summary> /// <summary>
/// Meta INI for the downloaded archive /// Meta INI for the downloaded archive
/// </summary> /// </summary>
public string Meta { get; set; } public string? Meta { get; set; }
/// <summary> /// <summary>
/// Human friendly name of this archive /// Human friendly name of this archive
/// </summary> /// </summary>
public string Name { get; set; } public string Name { get; set; } = string.Empty;
public long Size { get; set; } public long Size { get; set; }
public AbstractDownloadState State { get; set; } public AbstractDownloadState State { get; }
public Archive(AbstractDownloadState state)
{
State = state;
}
} }
public class IndexedArchive public class IndexedArchive
{ {
public dynamic IniData; public dynamic? IniData;
public string Meta; public string Meta = string.Empty;
public string Name; public string Name = string.Empty;
public VirtualFile File { get; internal set; } public VirtualFile File { get; }
}
/// <summary> public IndexedArchive(VirtualFile file)
/// A archive entry {
/// </summary> File = file;
public class IndexedEntry }
{
/// <summary>
/// MurMur3 hash of this file
/// </summary>
public string Hash;
/// <summary>
/// Path in the archive to this file
/// </summary>
public string Path;
/// <summary>
/// Size of the file (uncompressed)
/// </summary>
public long Size;
}
public class IndexedArchiveEntry : IndexedEntry
{
public string[] HashPath;
} }
} }

View File

@ -11,19 +11,18 @@ namespace Wabbajack.Lib.Downloaders
public interface IMetaState public interface IMetaState
{ {
Uri URL { get; } Uri URL { get; }
string Name { get; set; } string? Name { get; set; }
string Author { get; set; } string? Author { get; set; }
string Version { get; set; } string? Version { get; set; }
string ImageURL { get; set; } string? ImageURL { get; set; }
bool IsNSFW { get; set; } bool IsNSFW { get; set; }
string Description { get; set; } string? Description { get; set; }
Task<bool> LoadMetaData(); Task<bool> LoadMetaData();
} }
public abstract class AbstractDownloadState public abstract class AbstractDownloadState
{ {
public static List<Type> KnownSubTypes = new List<Type> public static List<Type> KnownSubTypes = new List<Type>
{ {
typeof(HTTPDownloader.State), typeof(HTTPDownloader.State),
@ -48,7 +47,7 @@ namespace Wabbajack.Lib.Downloaders
static AbstractDownloadState() static AbstractDownloadState()
{ {
NameToType = KnownSubTypes.ToDictionary(t => t.FullName.Substring(t.Namespace.Length + 1), t => t); NameToType = KnownSubTypes.ToDictionary(t => t.FullName!.Substring(t.Namespace!.Length + 1), t => t);
TypeToName = NameToType.ToDictionary(k => k.Value, k => k.Key); TypeToName = NameToType.ToDictionary(k => k.Value, k => k.Key);
} }
@ -68,7 +67,6 @@ namespace Wabbajack.Lib.Downloaders
} }
} }
/// <summary> /// <summary>
/// Returns true if this file is allowed to be downloaded via whitelist /// Returns true if this file is allowed to be downloaded via whitelist
/// </summary> /// </summary>
@ -85,7 +83,9 @@ namespace Wabbajack.Lib.Downloaders
public async Task<bool> Download(AbsolutePath destination) public async Task<bool> Download(AbsolutePath destination)
{ {
destination.Parent.CreateDirectory(); destination.Parent.CreateDirectory();
return await Download(new Archive {Name = (string)destination.FileName}, destination); // ToDo
// Is this null override needed? Why is state allowed to be null here?
return await Download(new Archive(state: null!) {Name = (string)destination.FileName}, destination);
} }
/// <summary> /// <summary>
@ -96,7 +96,7 @@ namespace Wabbajack.Lib.Downloaders
public abstract IDownloader GetDownloader(); public abstract IDownloader GetDownloader();
public abstract string GetManifestURL(Archive a); public abstract string? GetManifestURL(Archive a);
public abstract string[] GetMetaIni(); public abstract string[] GetMetaIni();
} }
} }

View File

@ -18,10 +18,12 @@ namespace Wabbajack.Lib.Downloaders
where TState : AbstractIPS4Downloader<TDownloader, TState>.State<TDownloader>, new() where TState : AbstractIPS4Downloader<TDownloader, TState>.State<TDownloader>, new()
where TDownloader : IDownloader where TDownloader : IDownloader
{ {
public override string SiteName { get; } protected AbstractIPS4Downloader(Uri loginUri, string encryptedKeyName, string cookieDomain)
public override Uri SiteURL { get; } : base(loginUri, encryptedKeyName, cookieDomain, "ips4_member_id")
{
}
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode) public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{ {
Uri url = DownloaderUtils.GetDirectURL(archiveINI); Uri url = DownloaderUtils.GetDirectURL(archiveINI);
@ -70,23 +72,23 @@ namespace Wabbajack.Lib.Downloaders
}; };
} }
public class State<TStateDownloader> : AbstractDownloadState, IMetaState
public class State<TStateDownloader> : AbstractDownloadState, IMetaState where TStateDownloader : IDownloader where TStateDownloader : IDownloader
{ {
public string FullURL { get; set; } public string FullURL { get; set; } = string.Empty;
public bool IsAttachment { get; set; } public bool IsAttachment { get; set; }
public string FileID { get; set; } public string FileID { get; set; } = string.Empty;
public string FileName { get; set; } public string FileName { get; set; } = string.Empty;
// from IMetaState // from IMetaState
public Uri URL => new Uri($"{Site}/files/file/{FileName}"); public Uri URL => new Uri($"{Site}/files/file/{FileName}");
public string Name { get; set; } public string? Name { get; set; }
public string Author { get; set; } public string? Author { get; set; }
public string Version { get; set; } public string? Version { get; set; }
public string ImageURL { get; set; } public string? ImageURL { get; set; }
public virtual bool IsNSFW { get; set; } public virtual bool IsNSFW { get; set; }
public string Description { get; set; } public string? Description { get; set; }
private static bool IsHTTPS => Downloader.SiteURL.AbsolutePath.StartsWith("https://"); private static bool IsHTTPS => Downloader.SiteURL.AbsolutePath.StartsWith("https://");
private static string URLPrefix => IsHTTPS ? "https://" : "http://"; private static string URLPrefix => IsHTTPS ? "https://" : "http://";
@ -119,6 +121,7 @@ namespace Wabbajack.Lib.Downloaders
public override async Task<bool> Download(Archive a, AbsolutePath destination) public override async Task<bool> Download(Archive a, AbsolutePath destination)
{ {
await using var stream = await ResolveDownloadStream(); await using var stream = await ResolveDownloadStream();
if (stream == null) return false;
await using (var file = destination.Create()) await using (var file = destination.Create())
{ {
await stream.CopyToAsync(file); await stream.CopyToAsync(file);
@ -126,7 +129,7 @@ namespace Wabbajack.Lib.Downloaders
return true; return true;
} }
private async Task<Stream> ResolveDownloadStream() private async Task<Stream?> ResolveDownloadStream()
{ {
TOP: TOP:
string url; string url;
@ -236,12 +239,5 @@ namespace Wabbajack.Lib.Downloaders
return false; return false;
} }
} }
protected AbstractIPS4Downloader(Uri loginUri, string encryptedKeyName, string cookieDomain) :
base(loginUri, encryptedKeyName, cookieDomain, "ips4_member_id")
{
}
} }
} }

View File

@ -21,7 +21,17 @@ namespace Wabbajack.Lib.Downloaders
private readonly string _encryptedKeyName; private readonly string _encryptedKeyName;
private readonly string _cookieDomain; private readonly string _cookieDomain;
private readonly string _cookieName; private readonly string _cookieName;
internal Common.Http.Client AuthedClient; // ToDo
// Remove null assignment. Either add nullability to type, or figure way to prepare it safely
public Common.Http.Client AuthedClient { get; private set; } = null!;
public ReactiveCommand<Unit, Unit> TriggerLogin { get; }
public ReactiveCommand<Unit, Unit> ClearLogin { get; }
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable(_encryptedKeyName);
public abstract string SiteName { get; }
public virtual IObservable<string>? MetaInfo { get; }
public abstract Uri SiteURL { get; }
public virtual Uri? IconUri { get; }
/// <summary> /// <summary>
/// Sets up all the login facilites needed for a INeedsLogin downloader based on having the user log /// Sets up all the login facilites needed for a INeedsLogin downloader based on having the user log
@ -48,14 +58,6 @@ namespace Wabbajack.Lib.Downloaders
execute: () => Utils.CatchAndLog(() => Utils.DeleteEncryptedJson(_encryptedKeyName)), execute: () => Utils.CatchAndLog(() => Utils.DeleteEncryptedJson(_encryptedKeyName)),
canExecute: IsLoggedIn.ObserveOnGuiThread()); canExecute: IsLoggedIn.ObserveOnGuiThread());
} }
public ReactiveCommand<Unit, Unit> TriggerLogin { get; }
public ReactiveCommand<Unit, Unit> ClearLogin { get; }
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable(_encryptedKeyName);
public abstract string SiteName { get; }
public virtual IObservable<string> MetaInfo { get; }
public abstract Uri SiteURL { get; }
public virtual Uri IconUri { get; }
protected virtual async Task WhileWaiting(IWebDriver browser) protected virtual async Task WhileWaiting(IWebDriver browser)
{ {
@ -120,7 +122,7 @@ namespace Wabbajack.Lib.Downloaders
Downloader = downloader; Downloader = downloader;
} }
public override string ShortDescription => $"Getting {Downloader.SiteName} Login"; public override string ShortDescription => $"Getting {Downloader.SiteName} Login";
public override string ExtendedDescription { get; } public override string ExtendedDescription { get; } = string.Empty;
private readonly TaskCompletionSource<Helpers.Cookie[]> _source = new TaskCompletionSource<Helpers.Cookie[]>(); private readonly TaskCompletionSource<Helpers.Cookie[]> _source = new TaskCompletionSource<Helpers.Cookie[]>();
public Task<Helpers.Cookie[]> Task => _source.Task; public Task<Helpers.Cookie[]> Task => _source.Task;
@ -138,6 +140,4 @@ namespace Wabbajack.Lib.Downloaders
} }
} }
} }
} }

View File

@ -30,6 +30,15 @@ namespace Wabbajack.Lib.Downloaders
public class BethesdaNetDownloader : IUrlDownloader, INeedsLogin public class BethesdaNetDownloader : IUrlDownloader, INeedsLogin
{ {
public const string DataName = "bethesda-net-data"; public const string DataName = "bethesda-net-data";
public ReactiveCommand<Unit, Unit> TriggerLogin { get; }
public ReactiveCommand<Unit, Unit> ClearLogin { get; }
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable(DataName);
public string SiteName => "Bethesda.NET";
public IObservable<string> MetaInfo => Observable.Return(""); //"Wabbajack will start the game, then exit once you enter the Mods page";
public Uri SiteURL => new Uri("https://bethesda.net");
public Uri? IconUri { get; }
public BethesdaNetDownloader() public BethesdaNetDownloader()
{ {
TriggerLogin = ReactiveCommand.CreateFromTask(() => Utils.CatchAndLog(RequestLoginAndCache), IsLoggedIn.Select(b => !b).ObserveOn(RxApp.MainThreadScheduler)); TriggerLogin = ReactiveCommand.CreateFromTask(() => Utils.CatchAndLog(RequestLoginAndCache), IsLoggedIn.Select(b => !b).ObserveOn(RxApp.MainThreadScheduler));
@ -38,23 +47,23 @@ namespace Wabbajack.Lib.Downloaders
private static async Task RequestLoginAndCache() private static async Task RequestLoginAndCache()
{ {
var result = await Utils.Log(new RequestBethesdaNetLogin()).Task; await Utils.Log(new RequestBethesdaNetLogin()).Task;
} }
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode) public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{ {
var url = (Uri)DownloaderUtils.GetDirectURL(archiveINI); var url = (Uri)DownloaderUtils.GetDirectURL(archiveINI);
return StateFromUrl(url); return StateFromUrl(url);
} }
internal static AbstractDownloadState StateFromUrl(Uri url) internal static AbstractDownloadState? StateFromUrl(Uri url)
{ {
if (url != null && url.Host == "bethesda.net" && url.AbsolutePath.StartsWith("/en/mods/")) if (url != null && url.Host == "bethesda.net" && url.AbsolutePath.StartsWith("/en/mods/"))
{ {
var split = url.AbsolutePath.Split('/'); var split = url.AbsolutePath.Split('/');
var game = split[3]; var game = split[3];
var modId = split[5]; var modId = split[5];
return new State {GameName = game, ContentId = modId}; return new State(gameName: game, contentId: modId);
} }
return null; return null;
} }
@ -65,10 +74,11 @@ namespace Wabbajack.Lib.Downloaders
await Utils.Log(new RequestBethesdaNetLogin()).Task; await Utils.Log(new RequestBethesdaNetLogin()).Task;
} }
public static async Task<BethesdaNetData> Login(Game game) public static async Task<BethesdaNetData?> Login(Game game)
{ {
var metadata = game.MetaData(); var metadata = game.MetaData();
var gamePath = metadata.GameLocation()?.Combine(metadata.MainExecutable); if (metadata.MainExecutable == null) throw new NotImplementedException();
var gamePath = metadata.GameLocation().Combine(metadata.MainExecutable);
var info = new ProcessStartInfo var info = new ProcessStartInfo
{ {
FileName = @"Downloaders\BethesdaNet\bethnetlogin.exe", FileName = @"Downloaders\BethesdaNet\bethnetlogin.exe",
@ -103,28 +113,25 @@ namespace Wabbajack.Lib.Downloaders
} }
} }
public AbstractDownloadState GetDownloaderState(string url) public AbstractDownloadState? GetDownloaderState(string url)
{ {
return StateFromUrl(new Uri(url)); return StateFromUrl(new Uri(url));
} }
public ReactiveCommand<Unit, Unit> TriggerLogin { get; }
public ReactiveCommand<Unit, Unit> ClearLogin { get; }
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable(DataName);
public string SiteName => "Bethesda.NET";
public IObservable<string> MetaInfo => Observable.Return(""); //"Wabbajack will start the game, then exit once you enter the Mods page";
public Uri SiteURL => new Uri("https://bethesda.net");
public Uri IconUri { get; }
[JsonName("BethesdaNetDownloader")] [JsonName("BethesdaNetDownloader")]
public class State : AbstractDownloadState public class State : AbstractDownloadState
{ {
public string GameName { get; set; } public string GameName { get; }
public string ContentId { get; set; } public string ContentId { get; }
[JsonIgnore] [JsonIgnore]
public override object[] PrimaryKey => new object[] {GameName, ContentId}; public override object[] PrimaryKey => new object[] { GameName, ContentId };
public State(string gameName, string contentId)
{
GameName = gameName;
ContentId = contentId;
}
public override bool IsWhitelisted(ServerWhitelist whitelist) public override bool IsWhitelisted(ServerWhitelist whitelist)
{ {
@ -143,8 +150,8 @@ namespace Wabbajack.Lib.Downloaders
using var got = await client.GetAsync( using var got = await client.GetAsync(
$"https://content.cdp.bethesda.net/{collected.CDPProductId}/{collected.CDPPropertiesId}/{chunk.sha}"); $"https://content.cdp.bethesda.net/{collected.CDPProductId}/{collected.CDPPropertiesId}/{chunk.sha}");
var data = await got.Content.ReadAsByteArrayAsync(); var data = await got.Content.ReadAsByteArrayAsync();
if (collected.AESKey != null) if (collected.AESKey != null)
AESCTRDecrypt(collected.AESKey, collected.AESIV, data); AESCTRDecrypt(collected.AESKey, collected.AESIV!, data);
if (chunk.uncompressed_size == chunk.chunk_size) if (chunk.uncompressed_size == chunk.chunk_size)
await file.WriteAsync(data, 0, data.Length); await file.WriteAsync(data, 0, data.Length);
@ -198,7 +205,7 @@ namespace Wabbajack.Lib.Downloaders
public override async Task<bool> Verify(Archive archive) public override async Task<bool> Verify(Archive archive)
{ {
var info = await ResolveDownloadInfo(); await ResolveDownloadInfo();
return true; return true;
} }
@ -222,7 +229,7 @@ namespace Wabbajack.Lib.Downloaders
client.Headers.Add(("x-cdp-app", "UGC SDK")); client.Headers.Add(("x-cdp-app", "UGC SDK"));
client.Headers.Add(("x-cdp-app-ver", "0.9.11314/debug")); client.Headers.Add(("x-cdp-app-ver", "0.9.11314/debug"));
client.Headers.Add(("x-cdp-lib-ver", "0.9.11314/debug")); client.Headers.Add(("x-cdp-lib-ver", "0.9.11314/debug"));
client.Headers.Add(("x-cdp-platform","Win/32")); client.Headers.Add(("x-cdp-platform", "Win/32"));
posted = await client.PostAsync("https://api.bethesda.net/cdp-user/auth", posted = await client.PostAsync("https://api.bethesda.net/cdp-user/auth",
new StringContent("{\"access_token\": \"" + info.AccessToken + "\"}", Encoding.UTF8, new StringContent("{\"access_token\": \"" + info.AccessToken + "\"}", Encoding.UTF8,
@ -233,10 +240,10 @@ namespace Wabbajack.Lib.Downloaders
var got = await client.GetAsync($"https://api.bethesda.net/mods/ugc-workshop/content/get?content_id={ContentId}"); var got = await client.GetAsync($"https://api.bethesda.net/mods/ugc-workshop/content/get?content_id={ContentId}");
JObject data = JObject.Parse(await got.Content.ReadAsStringAsync()); JObject data = JObject.Parse(await got.Content.ReadAsStringAsync());
var content = data["platform"]["response"]["content"]; var content = data["platform"]!["response"]!["content"]!;
info.CDPBranchId = (int)content["cdp_branch_id"]; info.CDPBranchId = (int)content["cdp_branch_id"]!;
info.CDPProductId = (int)content["cdp_product_id"]; info.CDPProductId = (int)content["cdp_product_id"]!;
client.Headers.Add(("Authorization", $"Token {info.CDPToken}")); client.Headers.Add(("Authorization", $"Token {info.CDPToken}"));
client.Headers.Add(("Accept", "application/json")); client.Headers.Add(("Accept", "application/json"));
@ -246,7 +253,7 @@ namespace Wabbajack.Lib.Downloaders
$"https://api.bethesda.net/cdp-user/projects/{info.CDPProductId}/branches/{info.CDPBranchId}/tree/.json"); $"https://api.bethesda.net/cdp-user/projects/{info.CDPProductId}/branches/{info.CDPBranchId}/tree/.json");
var tree = (await got.Content.ReadAsStringAsync()).FromJsonString<CDPTree>(); var tree = (await got.Content.ReadAsStringAsync()).FromJsonString<CDPTree>();
got.Dispose(); got.Dispose();
got = await client.PostAsync($"https://api.bethesda.net/mods/ugc-content/add-subscription", new StringContent($"{{\"content_id\": \"{ContentId}\"}}", Encoding.UTF8, "application/json")); got = await client.PostAsync($"https://api.bethesda.net/mods/ugc-content/add-subscription", new StringContent($"{{\"content_id\": \"{ContentId}\"}}", Encoding.UTF8, "application/json"));
@ -255,14 +262,14 @@ namespace Wabbajack.Lib.Downloaders
$"https://api.bethesda.net/cdp-user/projects/{info.CDPProductId}/branches/{info.CDPBranchId}/depots/.json"); $"https://api.bethesda.net/cdp-user/projects/{info.CDPProductId}/branches/{info.CDPBranchId}/depots/.json");
var props_obj = JObject.Parse(await got.Content.ReadAsStringAsync()).Properties().First(); var props_obj = JObject.Parse(await got.Content.ReadAsStringAsync()).Properties().First();
info.CDPPropertiesId = (int)props_obj.Value["properties_id"]; info.CDPPropertiesId = (int)props_obj.Value["properties_id"]!;
info.AESKey = props_obj.Value["ex_info_A"].Select(e => (byte)e).ToArray(); info.AESKey = props_obj.Value["ex_info_A"].Select(e => (byte)e).ToArray();
info.AESIV = props_obj.Value["ex_info_B"].Select(e => (byte)e).Take(16).ToArray(); info.AESIV = props_obj.Value["ex_info_B"].Select(e => (byte)e).Take(16).ToArray();
return (client, tree, info); return (client, tree, info);
} }
static int AESCTRDecrypt(byte[] Key, byte[] IV, byte[] Data) static int AESCTRDecrypt(byte[] Key, byte[] IV, byte[] Data)
{ {
IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CTR/NoPadding"); IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CTR/NoPadding");
@ -283,27 +290,26 @@ namespace Wabbajack.Lib.Downloaders
public override string[] GetMetaIni() public override string[] GetMetaIni()
{ {
return new[] {"[General]", $"directURL=https://bethesda.net/en/mods/{GameName}/mod-detail/{ContentId}"}; return new[] { "[General]", $"directURL=https://bethesda.net/en/mods/{GameName}/mod-detail/{ContentId}" };
} }
private class BeamLoginResponse private class BeamLoginResponse
{ {
public string access_token { get; set; } public string access_token { get; set; } = string.Empty;
} }
private class CDPLoginResponse private class CDPLoginResponse
{ {
public string token { get; set; } public string token { get; set; } = string.Empty;
} }
private class CollectedBNetInfo private class CollectedBNetInfo
{ {
public byte[] AESKey { get; set; } public byte[] AESKey { get; set; } = null!;
public byte[] AESIV { get; set; } public byte[] AESIV { get; set; } = null!;
public string AccessToken { get; set; } public string AccessToken { get; set; } = string.Empty;
public string CDPToken { get; set; } public string CDPToken { get; set; } = string.Empty;
public int CDPBranchId { get; set; } public int CDPBranchId { get; set; }
public int CDPProductId { get; set; } public int CDPProductId { get; set; }
public int CDPPropertiesId { get; set; } public int CDPPropertiesId { get; set; }
@ -311,24 +317,24 @@ namespace Wabbajack.Lib.Downloaders
public class CDPTree public class CDPTree
{ {
public List<Depot> depot_list { get; set; } public List<Depot> depot_list { get; set; } = null!;
public class Depot public class Depot
{ {
public List<CDPFile> file_list { get; set; } public List<CDPFile> file_list { get; set; } = null!;
public class CDPFile public class CDPFile
{ {
public int chunk_count { get; set; } public int chunk_count { get; set; }
public List<Chunk> chunk_list { get; set; } public List<Chunk> chunk_list { get; set; } = null!;
public string name { get; set; } public string? name { get; set; }
public class Chunk public class Chunk
{ {
public int chunk_size { get; set; } public int chunk_size { get; set; }
public int index { get; set; } public int index { get; set; }
public string sha { get; set; } public string sha { get; set; } = string.Empty;
public int uncompressed_size { get; set; } public int uncompressed_size { get; set; }
} }
} }
@ -345,7 +351,7 @@ namespace Wabbajack.Lib.Downloaders
public class RequestBethesdaNetLogin : AUserIntervention public class RequestBethesdaNetLogin : AUserIntervention
{ {
public override string ShortDescription => "Logging into Bethesda.NET"; public override string ShortDescription => "Logging into Bethesda.NET";
public override string ExtendedDescription { get; } public override string ExtendedDescription { get; } = string.Empty;
private readonly TaskCompletionSource<BethesdaNetData> _source = new TaskCompletionSource<BethesdaNetData>(); private readonly TaskCompletionSource<BethesdaNetData> _source = new TaskCompletionSource<BethesdaNetData>();
public Task<BethesdaNetData> Task => _source.Task; public Task<BethesdaNetData> Task => _source.Task;
@ -367,8 +373,7 @@ namespace Wabbajack.Lib.Downloaders
[JsonName("BethesdaNetData")] [JsonName("BethesdaNetData")]
public class BethesdaNetData public class BethesdaNetData
{ {
public string body { get; set; } public string body { get; set; } = string.Empty;
public Dictionary<string, string> headers = new Dictionary<string, string>(); public Dictionary<string, string> headers = new Dictionary<string, string>();
} }
} }

View File

@ -43,7 +43,7 @@ namespace Wabbajack.Lib.Downloaders
IndexedDownloaders = Downloaders.ToDictionary(d => d.GetType()); IndexedDownloaders = Downloaders.ToDictionary(d => d.GetType());
} }
public static async Task<AbstractDownloadState> Infer(Uri uri) public static async Task<AbstractDownloadState?> Infer(Uri uri)
{ {
foreach (var inf in Inferencers) foreach (var inf in Inferencers)
{ {
@ -73,7 +73,7 @@ namespace Wabbajack.Lib.Downloaders
/// </summary> /// </summary>
/// <param name="ini"></param> /// <param name="ini"></param>
/// <returns></returns> /// <returns></returns>
public static AbstractDownloadState ResolveArchive(string url) public static AbstractDownloadState? ResolveArchive(string url)
{ {
return Downloaders.OfType<IUrlDownloader>().Select(d => d.GetDownloaderState(url)).FirstOrDefault(result => result != null); return Downloaders.OfType<IUrlDownloader>().Select(d => d.GetDownloaderState(url)).FirstOrDefault(result => result != null);
} }
@ -111,13 +111,9 @@ namespace Wabbajack.Lib.Downloaders
var patchName = $"{archive.Hash.ToHex()}_{upgrade.Hash.ToHex()}"; var patchName = $"{archive.Hash.ToHex()}_{upgrade.Hash.ToHex()}";
var patchPath = destination.Parent.Combine("_Patch_" + patchName); var patchPath = destination.Parent.Combine("_Patch_" + patchName);
var patchState = new Archive var patchState = new Archive(new HTTPDownloader.State($"https://wabbajackcdn.b-cdn.net/updates/{patchName}"))
{ {
Name = patchName, Name = patchName,
State = new HTTPDownloader.State
{
Url = $"https://wabbajackcdn.b-cdn.net/updates/{patchName}"
}
}; };
var patchResult = await Download(patchState, patchPath); var patchResult = await Download(patchState, patchPath);

View File

@ -4,7 +4,7 @@ namespace Wabbajack.Lib.Downloaders
{ {
public static class DownloaderUtils public static class DownloaderUtils
{ {
public static Uri GetDirectURL(dynamic meta) public static Uri? GetDirectURL(dynamic? meta)
{ {
var url = meta?.General?.directURL; var url = meta?.General?.directURL;
if (url == null) return null; if (url == null) return null;

View File

@ -7,13 +7,13 @@ namespace Wabbajack.Lib.Downloaders
{ {
public class DropboxDownloader : IDownloader, IUrlDownloader public class DropboxDownloader : IDownloader, IUrlDownloader
{ {
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode) public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{ {
var urlstring = archiveINI?.General?.directURL; var urlstring = archiveINI?.General?.directURL;
return GetDownloaderState(urlstring); return GetDownloaderState(urlstring);
} }
public AbstractDownloadState GetDownloaderState(string url) public AbstractDownloadState? GetDownloaderState(string url)
{ {
try try
{ {
@ -29,10 +29,7 @@ namespace Wabbajack.Lib.Downloaders
uri.Query = query.ToString(); uri.Query = query.ToString();
return new HTTPDownloader.State() return new HTTPDownloader.State(uri.ToString().Replace("dropbox.com:443/", "dropbox.com/"));
{
Url = uri.ToString().Replace("dropbox.com:443/", "dropbox.com/")
};
} }
catch (Exception) catch (Exception)
{ {

View File

@ -9,33 +9,30 @@ namespace Wabbajack.Lib.Downloaders
{ {
public class GameFileSourceDownloader : IDownloader public class GameFileSourceDownloader : IDownloader
{ {
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode) public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{ {
var gameName = (string)archiveINI?.General?.gameName; var gameName = (string?)archiveINI?.General?.gameName;
var gameFile = (string)archiveINI?.General?.gameFile; var gameFile = (string?)archiveINI?.General?.gameFile;
if (gameFile == null || gameFile == null) if (gameName == null || gameFile == null)
return null; return null;
var game = GameRegistry.GetByFuzzyName(gameName); if (!GameRegistry.TryGetByFuzzyName(gameName, out var game)) return null;
if (game == null) return null;
var path = game.GameLocation(); var path = game.TryGetGameLocation();
var filePath = path?.Combine(gameFile); var filePath = path?.Combine(gameFile);
if (!(filePath?.Exists ?? false))
if (!filePath?.Exists ?? false)
return null; return null;
var fp = filePath.Value; var fp = filePath.Value;
var hash = await fp.FileHashCachedAsync(); var hash = await fp.FileHashCachedAsync();
return new State return new State(game.InstalledVersion)
{ {
Game = game.Game, Game = game.Game,
GameFile = (RelativePath)gameFile, GameFile = (RelativePath)gameFile,
Hash = hash, Hash = hash
GameVersion = game.InstalledVersion
}; };
} }
@ -49,10 +46,15 @@ namespace Wabbajack.Lib.Downloaders
public Game Game { get; set; } public Game Game { get; set; }
public RelativePath GameFile { get; set; } public RelativePath GameFile { get; set; }
public Hash Hash { get; set; } public Hash Hash { get; set; }
public string GameVersion { get; set; } public string GameVersion { get; }
public State(string gameVersion)
{
GameVersion = gameVersion;
}
[JsonIgnore] [JsonIgnore]
internal AbsolutePath SourcePath => Game.MetaData().GameLocation().Value.Combine(GameFile); internal AbsolutePath SourcePath => Game.MetaData().GameLocation().Combine(GameFile);
[JsonIgnore] [JsonIgnore]
public override object[] PrimaryKey { get => new object[] {Game, GameVersion, GameFile}; } public override object[] PrimaryKey { get => new object[] {Game, GameVersion, GameFile}; }
@ -82,7 +84,7 @@ namespace Wabbajack.Lib.Downloaders
return DownloadDispatcher.GetInstance<GameFileSourceDownloader>(); return DownloadDispatcher.GetInstance<GameFileSourceDownloader>();
} }
public override string GetManifestURL(Archive a) public override string? GetManifestURL(Archive a)
{ {
return null; return null;
} }

View File

@ -10,22 +10,19 @@ namespace Wabbajack.Lib.Downloaders
{ {
public class GoogleDriveDownloader : IDownloader, IUrlDownloader public class GoogleDriveDownloader : IDownloader, IUrlDownloader
{ {
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode) public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{ {
var url = archiveINI?.General?.directURL; var url = archiveINI?.General?.directURL;
return GetDownloaderState(url); return GetDownloaderState(url);
} }
public AbstractDownloadState GetDownloaderState(string url) public AbstractDownloadState? GetDownloaderState(string url)
{ {
if (url != null && url.StartsWith("https://drive.google.com")) if (url != null && url.StartsWith("https://drive.google.com"))
{ {
var regex = new Regex("((?<=id=)[a-zA-Z0-9_-]*)|(?<=\\/file\\/d\\/)[a-zA-Z0-9_-]*"); var regex = new Regex("((?<=id=)[a-zA-Z0-9_-]*)|(?<=\\/file\\/d\\/)[a-zA-Z0-9_-]*");
var match = regex.Match(url); var match = regex.Match(url);
return new State return new State(match.ToString());
{
Id = match.ToString()
};
} }
return null; return null;
@ -38,10 +35,15 @@ namespace Wabbajack.Lib.Downloaders
[JsonName("GoogleDriveDownloader")] [JsonName("GoogleDriveDownloader")]
public class State : AbstractDownloadState public class State : AbstractDownloadState
{ {
public string Id { get; set; } public string Id { get; }
[JsonIgnore] [JsonIgnore]
public override object[] PrimaryKey { get => new object[] {Id}; } public override object[] PrimaryKey => new object[] { Id };
public State(string id)
{
Id = id;
}
public override bool IsWhitelisted(ServerWhitelist whitelist) public override bool IsWhitelisted(ServerWhitelist whitelist)
{ {
@ -64,7 +66,7 @@ namespace Wabbajack.Lib.Downloaders
var regex = new Regex("(?<=/uc\\?export=download&amp;confirm=).*(?=;id=)"); var regex = new Regex("(?<=/uc\\?export=download&amp;confirm=).*(?=;id=)");
var confirm = regex.Match(await response.Content.ReadAsStringAsync()); var confirm = regex.Match(await response.Content.ReadAsStringAsync());
var url = $"https://drive.google.com/uc?export=download&confirm={confirm}&id={Id}"; var url = $"https://drive.google.com/uc?export=download&confirm={confirm}&id={Id}";
var httpState = new HTTPDownloader.State {Url = url, Client = client}; var httpState = new HTTPDownloader.State(url) { Client = client };
return httpState; return httpState;
} }

View File

@ -15,29 +15,24 @@ namespace Wabbajack.Lib.Downloaders
{ {
public class HTTPDownloader : IDownloader, IUrlDownloader public class HTTPDownloader : IDownloader, IUrlDownloader
{ {
public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
{ {
var url = archiveINI?.General?.directURL; var url = archiveINI?.General?.directURL;
return GetDownloaderState(url, archiveINI); return GetDownloaderState(url, archiveINI);
} }
public AbstractDownloadState GetDownloaderState(string uri) public AbstractDownloadState? GetDownloaderState(string uri)
{ {
return GetDownloaderState(uri, null); return GetDownloaderState(uri, null);
} }
public AbstractDownloadState GetDownloaderState(string url, dynamic archiveINI) public AbstractDownloadState? GetDownloaderState(string url, dynamic? archiveINI)
{ {
if (url != null) if (url != null)
{ {
var tmp = new State var tmp = new State(url);
{
Url = url
};
if (archiveINI?.General?.directURLHeaders != null) if (archiveINI?.General?.directURLHeaders != null)
{ {
tmp.Headers = new List<string>();
tmp.Headers.AddRange(archiveINI?.General.directURLHeaders.Split('|')); tmp.Headers.AddRange(archiveINI?.General.directURLHeaders.Split('|'));
} }
return tmp; return tmp;
@ -53,15 +48,20 @@ namespace Wabbajack.Lib.Downloaders
[JsonName("HttpDownloader")] [JsonName("HttpDownloader")]
public class State : AbstractDownloadState public class State : AbstractDownloadState
{ {
public string Url { get; set; } public string Url { get; }
public List<string> Headers { get; set; } public List<string> Headers { get; } = new List<string>();
[JsonIgnore] [JsonIgnore]
public Common.Http.Client Client { get; set; } public Common.Http.Client? Client { get; set; }
[JsonIgnore] [JsonIgnore]
public override object[] PrimaryKey { get => new object[] {Url};} public override object[] PrimaryKey => new object[] { Url };
public State(string url)
{
Url = url;
}
public override bool IsWhitelisted(ServerWhitelist whitelist) public override bool IsWhitelisted(ServerWhitelist whitelist)
{ {
@ -85,19 +85,18 @@ namespace Wabbajack.Lib.Downloaders
var client = Client ?? new Common.Http.Client(); var client = Client ?? new Common.Http.Client();
client.Headers.Add(("User-Agent", Consts.UserAgent)); client.Headers.Add(("User-Agent", Consts.UserAgent));
if (Headers != null) foreach (var header in Headers)
foreach (var header in Headers) {
{ var idx = header.IndexOf(':');
var idx = header.IndexOf(':'); var k = header.Substring(0, idx);
var k = header.Substring(0, idx); var v = header.Substring(idx + 1);
var v = header.Substring(idx + 1); client.Headers.Add((k, v));
client.Headers.Add((k, v)); }
}
long totalRead = 0; long totalRead = 0;
var bufferSize = 1024 * 32; var bufferSize = 1024 * 32;
Utils.Status($"Starting Download {a?.Name ?? Url}", Percent.Zero); Utils.Status($"Starting Download {a.Name ?? Url}", Percent.Zero);
var response = await client.GetAsync(Url); var response = await client.GetAsync(Url);
TOP: TOP:
@ -177,7 +176,7 @@ TOP:
if (read == 0) break; if (read == 0) break;
Utils.Status($"Downloading {a.Name}", Percent.FactoryPutInRange(totalRead, contentSize)); Utils.Status($"Downloading {a.Name}", Percent.FactoryPutInRange(totalRead, contentSize));
fs.Write(buffer, 0, read); fs!.Write(buffer, 0, read);
totalRead += read; totalRead += read;
} }
} }
@ -203,7 +202,7 @@ TOP:
public override string[] GetMetaIni() public override string[] GetMetaIni()
{ {
if (Headers != null) if (Headers.Count > 0)
return new [] {"[General]", return new [] {"[General]",
$"directURL={Url}", $"directURL={Url}",
$"directURLHeaders={string.Join("|", Headers)}"}; $"directURLHeaders={string.Join("|", Headers)}"};

View File

@ -4,12 +4,11 @@ namespace Wabbajack.Lib.Downloaders
{ {
public interface IDownloader public interface IDownloader
{ {
Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode = false); Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode = false);
/// <summary> /// <summary>
/// Called before any downloads are inacted by the installer; /// Called before any downloads are inacted by the installer;
/// </summary> /// </summary>
Task Prepare(); Task Prepare();
} }
} }

View File

@ -11,9 +11,9 @@ namespace Wabbajack.Lib.Downloaders
ReactiveCommand<Unit, Unit> ClearLogin { get; } ReactiveCommand<Unit, Unit> ClearLogin { get; }
IObservable<bool> IsLoggedIn { get; } IObservable<bool> IsLoggedIn { get; }
string SiteName { get; } string SiteName { get; }
IObservable<string> MetaInfo { get; } IObservable<string>? MetaInfo { get; }
Uri SiteURL { get; } Uri SiteURL { get; }
Uri IconUri { get; } Uri? IconUri { get; }
} }
public struct LoginReturnMessage public struct LoginReturnMessage

View File

@ -2,6 +2,6 @@
{ {
public interface IUrlDownloader : IDownloader public interface IUrlDownloader : IDownloader
{ {
AbstractDownloadState GetDownloaderState(string url); AbstractDownloadState? GetDownloaderState(string url);
} }
} }

View File

@ -23,8 +23,6 @@ namespace Wabbajack.Lib.Downloaders
try try
{ {
authInfos = MegaApiClient.GenerateAuthInfos(username, password.ToNormalString()); authInfos = MegaApiClient.GenerateAuthInfos(username, password.ToNormalString());
username = null;
password = null;
} }
catch (ApiException e) catch (ApiException e)
{ {
@ -72,16 +70,16 @@ namespace Wabbajack.Lib.Downloaders
IsLoggedIn.ObserveOnGuiThread()); IsLoggedIn.ObserveOnGuiThread());
} }
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode) public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{ {
var url = archiveINI?.General?.directURL; var url = archiveINI?.General?.directURL;
return GetDownloaderState(url); return GetDownloaderState(url);
} }
public AbstractDownloadState GetDownloaderState(string url) public AbstractDownloadState? GetDownloaderState(string url)
{ {
if (url != null && url.StartsWith(Consts.MegaPrefix)) if (url != null && url.StartsWith(Consts.MegaPrefix))
return new State { Url = url}; return new State(url);
return null; return null;
} }
@ -92,6 +90,11 @@ namespace Wabbajack.Lib.Downloaders
[JsonName("MegaDownloader")] [JsonName("MegaDownloader")]
public class State : HTTPDownloader.State public class State : HTTPDownloader.State
{ {
public State(string url)
: base(url)
{
}
private static MegaApiClient MegaApiClient => DownloadDispatcher.GetInstance<MegaDownloader>().MegaApiClient; private static MegaApiClient MegaApiClient => DownloadDispatcher.GetInstance<MegaDownloader>().MegaApiClient;
private void MegaLogin() private void MegaLogin()

View File

@ -18,8 +18,8 @@ namespace Wabbajack.Lib.Downloaders
class FileEvent class FileEvent
{ {
public string FullPath { get; set; } public string FullPath { get; set; } = string.Empty;
public string Name { get; set; } public string Name { get; set; } = string.Empty;
public long Size { get; set; } public long Size { get; set; }
} }
@ -57,10 +57,10 @@ namespace Wabbajack.Lib.Downloaders
} }
} }
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode) public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{ {
var url = archiveINI?.General?.manualURL; var url = archiveINI?.General?.manualURL;
return url != null ? new State { Url = url} : null; return url != null ? new State(url) : null;
} }
public async Task Prepare() public async Task Prepare()
@ -70,10 +70,15 @@ namespace Wabbajack.Lib.Downloaders
[JsonName("ManualDownloader")] [JsonName("ManualDownloader")]
public class State : AbstractDownloadState public class State : AbstractDownloadState
{ {
public string Url { get; set; } public string Url { get; }
[JsonIgnore] [JsonIgnore]
public override object[] PrimaryKey { get => new object[] {Url}; } public override object[] PrimaryKey => new object[] { Url };
public State(string url)
{
Url = url;
}
public override bool IsWhitelisted(ServerWhitelist whitelist) public override bool IsWhitelisted(ServerWhitelist whitelist)
{ {
@ -83,7 +88,7 @@ namespace Wabbajack.Lib.Downloaders
public override async Task<bool> Download(Archive a, AbsolutePath destination) public override async Task<bool> Download(Archive a, AbsolutePath destination)
{ {
var (uri, client) = await Utils.Log(await ManuallyDownloadFile.Create(this)).Task; var (uri, client) = await Utils.Log(await ManuallyDownloadFile.Create(this)).Task;
var state = new HTTPDownloader.State {Url = uri.ToString(), Client = client}; var state = new HTTPDownloader.State(uri.ToString()) { Client = client };
return await state.Download(a, destination); return await state.Download(a, destination);
} }
@ -104,10 +109,10 @@ namespace Wabbajack.Lib.Downloaders
public override string[] GetMetaIni() public override string[] GetMetaIni()
{ {
return new [] { return new []
{
"[General]", "[General]",
$"manualURL={Url}" $"manualURL={Url}",
}; };
} }
} }

View File

@ -10,22 +10,24 @@ namespace Wabbajack.Lib.Downloaders
{ {
public class MediaFireDownloader : IUrlDownloader public class MediaFireDownloader : IUrlDownloader
{ {
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode) public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{ {
Uri url = DownloaderUtils.GetDirectURL(archiveINI); Uri url = DownloaderUtils.GetDirectURL(archiveINI);
if (url == null || url.Host != "www.mediafire.com") return null; if (url == null || url.Host != "www.mediafire.com") return null;
return new State return new State(url.ToString());
{
Url = url.ToString()
};
} }
public class State : AbstractDownloadState public class State : AbstractDownloadState
{ {
public string Url { get; set; } public string Url { get; }
public override object[] PrimaryKey { get => new object[] {Url};} public override object[] PrimaryKey => new object[] { Url };
public State(string url)
{
Url = url;
}
public override bool IsWhitelisted(ServerWhitelist whitelist) public override bool IsWhitelisted(ServerWhitelist whitelist)
{ {
@ -35,6 +37,7 @@ namespace Wabbajack.Lib.Downloaders
public override async Task<bool> Download(Archive a, AbsolutePath destination) public override async Task<bool> Download(Archive a, AbsolutePath destination)
{ {
var result = await Resolve(); var result = await Resolve();
if (result == null) return false;
return await result.Download(a, destination); return await result.Download(a, destination);
} }
@ -43,7 +46,7 @@ namespace Wabbajack.Lib.Downloaders
return await Resolve() != null; return await Resolve() != null;
} }
private async Task<HTTPDownloader.State> Resolve() private async Task<HTTPDownloader.State?> Resolve()
{ {
using (var d = await Driver.Create()) using (var d = await Driver.Create())
{ {
@ -52,10 +55,9 @@ namespace Wabbajack.Lib.Downloaders
await Task.Delay(1000); await Task.Delay(1000);
var newURL = await d.GetAttr("a.input", "href"); var newURL = await d.GetAttr("a.input", "href");
if (newURL == null || !newURL.StartsWith("http")) return null; if (newURL == null || !newURL.StartsWith("http")) return null;
return new HTTPDownloader.State() return new HTTPDownloader.State(newURL)
{ {
Client = new Common.Http.Client(), Client = new Common.Http.Client(),
Url = newURL
}; };
} }
} }
@ -84,15 +86,12 @@ namespace Wabbajack.Lib.Downloaders
{ {
} }
public AbstractDownloadState GetDownloaderState(string u) public AbstractDownloadState? GetDownloaderState(string u)
{ {
var url = new Uri(u); var url = new Uri(u);
if (url.Host != "www.mediafire.com") return null; if (url.Host != "www.mediafire.com") return null;
return new State return new State(url.ToString());
{
Url = url.ToString()
};
} }
} }
} }

View File

@ -11,20 +11,17 @@ namespace Wabbajack.Lib.Downloaders
{ {
public class ModDBDownloader : IDownloader, IUrlDownloader public class ModDBDownloader : IDownloader, IUrlDownloader
{ {
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode) public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{ {
var url = archiveINI?.General?.directURL; var url = archiveINI?.General?.directURL;
return GetDownloaderState(url); return GetDownloaderState(url);
} }
public AbstractDownloadState GetDownloaderState(string url) public AbstractDownloadState? GetDownloaderState(string url)
{ {
if (url != null && url.StartsWith("https://www.moddb.com/downloads/start")) if (url != null && url.StartsWith("https://www.moddb.com/downloads/start"))
{ {
return new State return new State(url);
{
Url = url
};
} }
return null; return null;
@ -37,10 +34,15 @@ namespace Wabbajack.Lib.Downloaders
[JsonName("ModDBDownloader")] [JsonName("ModDBDownloader")]
public class State : AbstractDownloadState public class State : AbstractDownloadState
{ {
public string Url { get; set; } public string Url { get; }
[JsonIgnore] [JsonIgnore]
public override object[] PrimaryKey { get => new object[]{Url}; } public override object[] PrimaryKey => new object[] { Url };
public State(string url)
{
Url = url;
}
public override bool IsWhitelisted(ServerWhitelist whitelist) public override bool IsWhitelisted(ServerWhitelist whitelist)
{ {
@ -56,7 +58,7 @@ namespace Wabbajack.Lib.Downloaders
{ {
try try
{ {
await new HTTPDownloader.State {Url = url}.Download(a, destination); await new HTTPDownloader.State(url).Download(a, destination);
return true; return true;
} }
catch (Exception) catch (Exception)

View File

@ -19,8 +19,8 @@ namespace Wabbajack.Lib.Downloaders
{ {
private bool _prepared; private bool _prepared;
private AsyncLock _lock = new AsyncLock(); private AsyncLock _lock = new AsyncLock();
private UserStatus _status; private UserStatus? _status;
private NexusApiClient _client; private NexusApiClient? _client;
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable("nexusapikey"); public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable("nexusapikey");
@ -50,9 +50,9 @@ namespace Wabbajack.Lib.Downloaders
canExecute: IsLoggedIn.ObserveOnGuiThread()); canExecute: IsLoggedIn.ObserveOnGuiThread());
} }
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode) public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{ {
var general = archiveINI?.General; var general = archiveINI.General;
if (general.modID != null && general.fileID != null && general.gameName != null) if (general.modID != null && general.fileID != null && general.gameName != null)
{ {
@ -135,17 +135,17 @@ namespace Wabbajack.Lib.Downloaders
[JsonIgnore] [JsonIgnore]
public Uri URL => new Uri($"http://nexusmods.com/{Game.MetaData().NexusName}/mods/{ModID}"); public Uri URL => new Uri($"http://nexusmods.com/{Game.MetaData().NexusName}/mods/{ModID}");
public string Name { get; set; } public string? Name { get; set; }
public string Author { get; set; } public string? Author { get; set; }
public string Version { get; set; } public string? Version { get; set; }
public string ImageURL { get; set; } public string? ImageURL { get; set; }
public bool IsNSFW { get; set; } public bool IsNSFW { get; set; }
public string Description { get; set; } public string? Description { get; set; }
[JsonProperty("GameName")] [JsonProperty("GameName")]
[JsonConverter(typeof(Utils.GameConverter))] [JsonConverter(typeof(Utils.GameConverter))]
@ -184,10 +184,7 @@ namespace Wabbajack.Lib.Downloaders
Utils.Log($"Downloading Nexus Archive - {a.Name} - {Game} - {ModID} - {FileID}"); Utils.Log($"Downloading Nexus Archive - {a.Name} - {Game} - {ModID} - {FileID}");
return await new HTTPDownloader.State return await new HTTPDownloader.State(url).Download(a, destination);
{
Url = url
}.Download(a, destination);
} }
public override async Task<bool> Verify(Archive a) public override async Task<bool> Verify(Archive a)

View File

@ -14,20 +14,21 @@ namespace Wabbajack.Lib.Downloaders
{ {
public class SteamWorkshopDownloader : IUrlDownloader public class SteamWorkshopDownloader : IUrlDownloader
{ {
private SteamWorkshopItem _item; public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
{ {
var id = archiveINI?.General?.itemID; var id = archiveINI?.General?.itemID;
var steamID = archiveINI?.General?.steamID; var steamID = archiveINI?.General?.steamID;
var size = archiveINI?.General?.itemSize; var size = archiveINI?.General?.itemSize;
_item = new SteamWorkshopItem if (steamID == null)
{
throw new ArgumentException("Steam workshop item had no steam ID.");
}
var item = new SteamWorkshopItem(GameRegistry.GetBySteamID(int.Parse(steamID)))
{ {
ItemID = id != null ? int.Parse(id) : 0, ItemID = id != null ? int.Parse(id) : 0,
Size = size != null ? int.Parse(size) : 0, Size = size != null ? int.Parse(size) : 0,
Game = steamID != null ? GameRegistry.GetBySteamID(int.Parse(steamID)) : null
}; };
return new State {Item = _item}; return new State(item);
} }
public async Task Prepare() public async Task Prepare()
@ -41,8 +42,14 @@ namespace Wabbajack.Lib.Downloaders
public class State : AbstractDownloadState public class State : AbstractDownloadState
{ {
public SteamWorkshopItem Item { get; set; } public SteamWorkshopItem Item { get; }
public override object[] PrimaryKey { get => new object[] {Item.Game, Item.ItemID}; }
public override object[] PrimaryKey => new object[] { Item.Game, Item.ItemID };
public State(SteamWorkshopItem item)
{
Item = item;
}
public override bool IsWhitelisted(ServerWhitelist whitelist) public override bool IsWhitelisted(ServerWhitelist whitelist)
{ {

View File

@ -5,7 +5,7 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders
{ {
public class BethesdaNetInferencer : IUrlInferencer public class BethesdaNetInferencer : IUrlInferencer
{ {
public async Task<AbstractDownloadState> Infer(Uri uri) public async Task<AbstractDownloadState?> Infer(Uri uri)
{ {
return BethesdaNetDownloader.StateFromUrl(uri); return BethesdaNetDownloader.StateFromUrl(uri);
} }

View File

@ -5,6 +5,6 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders
{ {
public interface IUrlInferencer public interface IUrlInferencer
{ {
Task<AbstractDownloadState> Infer(Uri uri); Task<AbstractDownloadState?> Infer(Uri uri);
} }
} }

View File

@ -8,10 +8,9 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders
{ {
public class YoutubeInferencer : IUrlInferencer public class YoutubeInferencer : IUrlInferencer
{ {
public async Task<AbstractDownloadState?> Infer(Uri uri)
public async Task<AbstractDownloadState> Infer(Uri uri)
{ {
var state = (YouTubeDownloader.State)YouTubeDownloader.UriToState(uri); var state = YouTubeDownloader.UriToState(uri) as YouTubeDownloader.State;
if (state == null) return null; if (state == null) return null;
var client = new YoutubeClient(Common.Http.ClientFactory.Client); var client = new YoutubeClient(Common.Http.ClientFactory.Client);
@ -24,13 +23,13 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders
.Select(line => .Select(line =>
{ {
var segments = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); var segments = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (segments.Length == 0) return (TimeSpan.Zero, null); if (segments.Length == 0) return (TimeSpan.Zero, string.Empty);
if (TryParseEx(segments.First(), out var s1)) if (TryParseEx(segments.First(), out var s1))
return (s1, string.Join(" ", segments.Skip(1))); return (s1, string.Join(" ", segments.Skip(1)));
if (TryParseEx(Enumerable.Last(segments), out var s2)) if (TryParseEx(Enumerable.Last(segments), out var s2))
return (s2, string.Join(" ", Utils.ButLast(segments))); return (s2, string.Join(" ", Utils.ButLast(segments)));
return (TimeSpan.Zero, null); return (TimeSpan.Zero, string.Empty);
}) })
.Where(t => t.Item2 != null) .Where(t => t.Item2 != null)
.ToList(); .ToList();

View File

@ -21,10 +21,10 @@ namespace Wabbajack.Lib.Downloaders
{ {
public class YouTubeDownloader : IDownloader public class YouTubeDownloader : IDownloader
{ {
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode) public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{ {
var directURL = (Uri)DownloaderUtils.GetDirectURL(archiveINI); var directURL = (Uri)DownloaderUtils.GetDirectURL(archiveINI);
var state = (State)UriToState(directURL); var state = UriToState(directURL) as State;
if (state == null) return state; if (state == null) return state;
var idx = 0; var idx = 0;
@ -45,7 +45,7 @@ namespace Wabbajack.Lib.Downloaders
return state; return state;
} }
internal static AbstractDownloadState UriToState(Uri directURL) internal static AbstractDownloadState? UriToState(Uri directURL)
{ {
if (directURL == null || !directURL.Host.EndsWith("youtube.com")) if (directURL == null || !directURL.Host.EndsWith("youtube.com"))
{ {
@ -53,7 +53,7 @@ namespace Wabbajack.Lib.Downloaders
} }
var key = HttpUtility.ParseQueryString(directURL.Query)["v"]; var key = HttpUtility.ParseQueryString(directURL.Query)["v"];
return key != null ? new State {Key = key} : null; return key != null ? new State(key) : null;
} }
public async Task Prepare() public async Task Prepare()
@ -63,13 +63,18 @@ namespace Wabbajack.Lib.Downloaders
[JsonName("YouTubeDownloader")] [JsonName("YouTubeDownloader")]
public class State : AbstractDownloadState public class State : AbstractDownloadState
{ {
public string Key { get; set; } public string Key { get; }
public List<Track> Tracks { get; set; } = new List<Track>(); public List<Track> Tracks { get; set; } = new List<Track>();
[JsonIgnore] [JsonIgnore]
public override object[] PrimaryKey => new object[] {Key}; public override object[] PrimaryKey => new object[] {Key};
public State(string key)
{
Key = key;
}
[JsonName("YouTubeTrack")] [JsonName("YouTubeTrack")]
public class Track public class Track
{ {
@ -79,8 +84,8 @@ namespace Wabbajack.Lib.Downloaders
WAV WAV
} }
public FormatEnum Format { get; set; } public FormatEnum Format { get; set; }
public string Name { get; set; } public string Name { get; set; } = string.Empty;
public TimeSpan Start { get; set; } public TimeSpan Start { get; set; }

View File

@ -12,6 +12,5 @@ namespace Wabbajack.Lib.Exceptions
Code = code; Code = code;
Reason = reason; Reason = reason;
} }
} }
} }

View File

@ -21,9 +21,9 @@ namespace Wabbajack.Lib.FileUploader
{ {
public static IObservable<bool> HaveAuthorAPIKey => Utils.HaveEncryptedJsonObservable(Consts.AuthorAPIKeyFile); public static IObservable<bool> HaveAuthorAPIKey => Utils.HaveEncryptedJsonObservable(Consts.AuthorAPIKeyFile);
public static string ApiKeyOverride = null; public static string? ApiKeyOverride = null;
public static async Task<string> GetAPIKey(string apiKey = null) public static async Task<string> GetAPIKey(string? apiKey = null)
{ {
if (ApiKeyOverride != null) return ApiKeyOverride; if (ApiKeyOverride != null) return ApiKeyOverride;
return apiKey ?? (await Consts.LocalAppDataPath.Combine(Consts.AuthorAPIKeyFile).ReadAllTextAsync()).Trim(); return apiKey ?? (await Consts.LocalAppDataPath.Combine(Consts.AuthorAPIKeyFile).ReadAllTextAsync()).Trim();
@ -32,7 +32,7 @@ namespace Wabbajack.Lib.FileUploader
public static Uri UploadURL => new Uri($"{Consts.WabbajackBuildServerUri}upload_file"); public static Uri UploadURL => new Uri($"{Consts.WabbajackBuildServerUri}upload_file");
public static long BLOCK_SIZE = (long)1024 * 1024 * 2; public static long BLOCK_SIZE = (long)1024 * 1024 * 2;
public static int MAX_CONNECTIONS = 8; public static int MAX_CONNECTIONS = 8;
public static Task<string> UploadFile(AbsolutePath filename, Action<double> progressFn, string apikey=null) public static Task<string> UploadFile(AbsolutePath filename, Action<double> progressFn, string? apikey = null)
{ {
var tcs = new TaskCompletionSource<string>(); var tcs = new TaskCompletionSource<string>();
Task.Run(async () => Task.Run(async () =>
@ -123,7 +123,7 @@ namespace Wabbajack.Lib.FileUploader
return tcs.Task; return tcs.Task;
} }
public static async Task<Common.Http.Client> GetAuthorizedClient(string apiKey=null) public static async Task<Common.Http.Client> GetAuthorizedClient(string? apiKey = null)
{ {
var client = new Common.Http.Client(); var client = new Common.Http.Client();
client.Headers.Add(("X-API-KEY", await GetAPIKey(apiKey))); client.Headers.Add(("X-API-KEY", await GetAPIKey(apiKey)));

View File

@ -78,10 +78,10 @@ namespace Wabbajack.Lib.LibCefHelpers
[JsonName("HttpCookie")] [JsonName("HttpCookie")]
public class Cookie public class Cookie
{ {
public string Name { get; set; } public string Name { get; set; } = string.Empty;
public string Value { get; set; } public string Value { get; set; } = string.Empty;
public string Domain { get; set; } public string Domain { get; set; } = string.Empty;
public string Path { get; set; } public string Path { get; set; } = string.Empty;
} }
public static void Init() public static void Init()

View File

@ -25,7 +25,6 @@ namespace Wabbajack.Lib
{ {
public class MO2Compiler : ACompiler public class MO2Compiler : ACompiler
{ {
private AbsolutePath _mo2DownloadsFolder; private AbsolutePath _mo2DownloadsFolder;
public AbsolutePath MO2Folder; public AbsolutePath MO2Folder;
@ -38,7 +37,7 @@ namespace Wabbajack.Lib
public override AbsolutePath GamePath { get; } public override AbsolutePath GamePath { get; }
public GameMetaData CompilingGame { get; set; } public GameMetaData CompilingGame { get; }
public override AbsolutePath ModListOutputFolder => ((RelativePath)"output_folder").RelativeToEntryPoint(); public override AbsolutePath ModListOutputFolder => ((RelativePath)"output_folder").RelativeToEntryPoint();
@ -48,7 +47,19 @@ namespace Wabbajack.Lib
Consts.LocalAppDataPath.Combine( Consts.LocalAppDataPath.Combine(
$"vfs_compile_cache-{Path.Combine((string)MO2Folder ?? "Unknown", "ModOrganizer.exe").StringSha256Hex()}.bin"); $"vfs_compile_cache-{Path.Combine((string)MO2Folder ?? "Unknown", "ModOrganizer.exe").StringSha256Hex()}.bin");
public dynamic MO2Ini { get; }
public static AbsolutePath GetTypicalDownloadsFolder(AbsolutePath mo2Folder) => mo2Folder.Combine("downloads");
public AbsolutePath MO2ProfileDir => MO2Folder.Combine("profiles", MO2Profile);
public ConcurrentBag<Directive> ExtraFiles { get; private set; } = new ConcurrentBag<Directive>();
public Dictionary<AbsolutePath, dynamic> ModInis { get; } = new Dictionary<AbsolutePath, dynamic>();
public HashSet<string> SelectedProfiles { get; set; } = new HashSet<string>();
public MO2Compiler(AbsolutePath mo2Folder, string mo2Profile, AbsolutePath outputFile) public MO2Compiler(AbsolutePath mo2Folder, string mo2Profile, AbsolutePath outputFile)
: base(steps: 20)
{ {
MO2Folder = mo2Folder; MO2Folder = mo2Folder;
MO2Profile = mo2Profile; MO2Profile = mo2Profile;
@ -59,8 +70,6 @@ namespace Wabbajack.Lib
ModListOutputFile = outputFile; ModListOutputFile = outputFile;
} }
public dynamic MO2Ini { get; }
public AbsolutePath MO2DownloadsFolder public AbsolutePath MO2DownloadsFolder
{ {
get get
@ -75,20 +84,10 @@ namespace Wabbajack.Lib
set => _mo2DownloadsFolder = value; set => _mo2DownloadsFolder = value;
} }
public static AbsolutePath GetTypicalDownloadsFolder(AbsolutePath mo2Folder) => mo2Folder.Combine("downloads");
public AbsolutePath MO2ProfileDir => MO2Folder.Combine("profiles", MO2Profile);
internal UserStatus User { get; private set; }
public ConcurrentBag<Directive> ExtraFiles { get; private set; }
public Dictionary<AbsolutePath, dynamic> ModInis { get; private set; }
public HashSet<string> SelectedProfiles { get; set; } = new HashSet<string>();
protected override async Task<bool> _Begin(CancellationToken cancel) protected override async Task<bool> _Begin(CancellationToken cancel)
{ {
if (cancel.IsCancellationRequested) return false; if (cancel.IsCancellationRequested) return false;
ConfigureProcessor(20, ConstructDynamicNumThreads(await RecommendQueueSize())); Queue.SetActiveThreadsObservable(ConstructDynamicNumThreads(await RecommendQueueSize()));
UpdateTracker.Reset(); UpdateTracker.Reset();
UpdateTracker.NextStep("Gathering information"); UpdateTracker.NextStep("Gathering information");
Info("Looking for other profiles"); Info("Looking for other profiles");
@ -126,6 +125,11 @@ namespace Wabbajack.Lib
if (lootPath.Exists) if (lootPath.Exists)
{ {
if (CompilingGame.MO2Name == null)
{
throw new ArgumentException("Compiling game had no MO2 name specified.");
}
var lootGameDirs = new [] var lootGameDirs = new []
{ {
CompilingGame.MO2Name, // most of the games use the MO2 name CompilingGame.MO2Name, // most of the games use the MO2 name
@ -170,9 +174,8 @@ namespace Wabbajack.Lib
IndexedArchives = (await MO2DownloadsFolder.EnumerateFiles() IndexedArchives = (await MO2DownloadsFolder.EnumerateFiles()
.Where(f => f.WithExtension(Consts.MetaFileExtension).Exists) .Where(f => f.WithExtension(Consts.MetaFileExtension).Exists)
.PMap(Queue, async f => new IndexedArchive .PMap(Queue, async f => new IndexedArchive(VFS.Index.ByRootPath[f])
{ {
File = VFS.Index.ByRootPath[f],
Name = (string)f.FileName, Name = (string)f.FileName,
IniData = f.WithExtension(Consts.MetaFileExtension).LoadIniFile(), IniData = f.WithExtension(Consts.MetaFileExtension).LoadIniFile(),
Meta = await f.WithExtension(Consts.MetaFileExtension).ReadAllTextAsync() Meta = await f.WithExtension(Consts.MetaFileExtension).ReadAllTextAsync()
@ -216,10 +219,9 @@ namespace Wabbajack.Lib
.GroupBy(f => f.Hash) .GroupBy(f => f.Hash)
.ToDictionary(f => f.Key, f => f.AsEnumerable()); .ToDictionary(f => f.Key, f => f.AsEnumerable());
AllFiles = mo2Files.Concat(gameFiles) AllFiles.SetTo(mo2Files.Concat(gameFiles)
.Concat(lootFiles) .Concat(lootFiles)
.DistinctBy(f => f.Path) .DistinctBy(f => f.Path));
.ToList();
Info($"Found {AllFiles.Count} files to build into mod list"); Info($"Found {AllFiles.Count} files to build into mod list");
@ -239,13 +241,10 @@ namespace Wabbajack.Lib
Error($"Found {dups.Count} duplicates, exiting"); Error($"Found {dups.Count} duplicates, exiting");
} }
ExtraFiles = new ConcurrentBag<Directive>();
if (cancel.IsCancellationRequested) return false; if (cancel.IsCancellationRequested) return false;
UpdateTracker.NextStep("Loading INIs"); UpdateTracker.NextStep("Loading INIs");
ModInis = MO2Folder.Combine(Consts.MO2ModFolderName) ModInis.SetTo(MO2Folder.Combine(Consts.MO2ModFolderName)
.EnumerateDirectories() .EnumerateDirectories()
.Select(f => .Select(f =>
{ {
@ -254,7 +253,7 @@ namespace Wabbajack.Lib
return metaPath.Exists ? (mod_name: f, metaPath.LoadIniFile()) : default; return metaPath.Exists ? (mod_name: f, metaPath.LoadIniFile()) : default;
}) })
.Where(f => f.Item1 != default) .Where(f => f.Item1 != default)
.ToDictionary(f => f.Item1, f => f.Item2); .Select(f => new KeyValuePair<AbsolutePath, dynamic>(f.Item1, f.Item2)));
if (cancel.IsCancellationRequested) return false; if (cancel.IsCancellationRequested) return false;
var stack = MakeStack(); var stack = MakeStack();
@ -270,7 +269,7 @@ namespace Wabbajack.Lib
PrintNoMatches(noMatch); PrintNoMatches(noMatch);
if (CheckForNoMatchExit(noMatch)) return false; if (CheckForNoMatchExit(noMatch)) return false;
InstallDirectives = results.Where(i => !(i is IgnoredDirectly)).ToList(); InstallDirectives.SetTo(results.Where(i => !(i is IgnoredDirectly)));
Info("Getting Nexus api_key, please click authorize if a browser window appears"); Info("Getting Nexus api_key, please click authorize if a browser window appears");
@ -337,7 +336,7 @@ namespace Wabbajack.Lib
{ {
return a; return a;
} }
})).Where(a => a != null).ToHashSet(); })).NotNull().ToHashSet();
if (remove.Count == 0) if (remove.Count == 0)
return; return;
@ -389,7 +388,6 @@ namespace Wabbajack.Lib
}); });
} }
private async Task IncludeArchiveMetadata() private async Task IncludeArchiveMetadata()
{ {
Utils.Log($"Including {SelectedArchives.Count} .meta files for downloads"); Utils.Log($"Including {SelectedArchives.Count} .meta files for downloads");
@ -411,13 +409,12 @@ namespace Wabbajack.Lib
/// </summary> /// </summary>
private void ResetMembers() private void ResetMembers()
{ {
AllFiles = null; AllFiles = new List<RawSourceFile>();
InstallDirectives = null; InstallDirectives = new List<Directive>();
SelectedArchives = null; SelectedArchives = new List<Archive>();
ExtraFiles = null; ExtraFiles = new ConcurrentBag<Directive>();
} }
/// <summary> /// <summary>
/// Fills in the Patch fields in files that require them /// Fills in the Patch fields in files that require them
/// </summary> /// </summary>
@ -487,8 +484,7 @@ namespace Wabbajack.Lib
return returnStream; return returnStream;
} }
Error($"Couldn't load data for {to}"); throw new ArgumentException($"Couldn't load data for {to}");
return null;
} }
public override IEnumerable<ICompilationStep> GetStack() public override IEnumerable<ICompilationStep> GetStack()
@ -567,12 +563,5 @@ namespace Wabbajack.Lib
new DropAll(this) new DropAll(this)
}; };
} }
public class IndexedFileMatch
{
public IndexedArchive Archive;
public IndexedArchiveEntry Entry;
public DateTime LastModified;
}
} }
} }

View File

@ -32,14 +32,18 @@ namespace Wabbajack.Lib
public AbsolutePath? GameFolder { get; set; } public AbsolutePath? GameFolder { get; set; }
public GameMetaData Game { get; }
public MO2Installer(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters parameters) public MO2Installer(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters parameters)
: base( : base(
archive: archive, archive: archive,
modList: modList, modList: modList,
outputFolder: outputFolder, outputFolder: outputFolder,
downloadFolder: downloadFolder, downloadFolder: downloadFolder,
parameters: parameters) parameters: parameters,
steps: 20)
{ {
Game = ModList.GameType.MetaData();
} }
protected override async Task<bool> _Begin(CancellationToken cancel) protected override async Task<bool> _Begin(CancellationToken cancel)
@ -48,28 +52,27 @@ namespace Wabbajack.Lib
var metric = Metrics.Send(Metrics.BeginInstall, ModList.Name); var metric = Metrics.Send(Metrics.BeginInstall, ModList.Name);
Utils.Log("Configuring Processor"); Utils.Log("Configuring Processor");
ConfigureProcessor(20, ConstructDynamicNumThreads(await RecommendQueueSize())); Queue.SetActiveThreadsObservable(ConstructDynamicNumThreads(await RecommendQueueSize()));
var game = ModList.GameType.MetaData();
if (GameFolder == null) if (GameFolder == null)
GameFolder = game.GameLocation(); GameFolder = Game.TryGetGameLocation();
if (GameFolder == null) if (GameFolder == null)
{ {
var otherGame = game.CommonlyConfusedWith.Where(g => g.MetaData().IsInstalled).Select(g => g.MetaData()).FirstOrDefault(); var otherGame = Game.CommonlyConfusedWith.Where(g => g.MetaData().IsInstalled).Select(g => g.MetaData()).FirstOrDefault();
if (otherGame != null) if (otherGame != null)
{ {
await Utils.Log(new CriticalFailureIntervention( await Utils.Log(new CriticalFailureIntervention(
$"In order to do a proper install Wabbajack needs to know where your {game.HumanFriendlyGameName} folder resides. However this game doesn't seem to be installed, we did however find a installed " + $"In order to do a proper install Wabbajack needs to know where your {Game.HumanFriendlyGameName} folder resides. However this game doesn't seem to be installed, we did however find a installed " +
$"copy of {otherGame.HumanFriendlyGameName}, did you install the wrong game?", $"copy of {otherGame.HumanFriendlyGameName}, did you install the wrong game?",
$"Could not locate {game.HumanFriendlyGameName}")) $"Could not locate {Game.HumanFriendlyGameName}"))
.Task; .Task;
} }
else else
{ {
await Utils.Log(new CriticalFailureIntervention( await Utils.Log(new CriticalFailureIntervention(
$"In order to do a proper install Wabbajack needs to know where your {game.HumanFriendlyGameName} folder resides. However this game doesn't seem to be installed", $"In order to do a proper install Wabbajack needs to know where your {Game.HumanFriendlyGameName} folder resides. However this game doesn't seem to be installed",
$"Could not locate {game.HumanFriendlyGameName}")) $"Could not locate {Game.HumanFriendlyGameName}"))
.Task; .Task;
} }
@ -169,7 +172,6 @@ namespace Wabbajack.Lib
private void CreateOutputMods() private void CreateOutputMods()
{ {
OutputFolder.Combine("profiles") OutputFolder.Combine("profiles")
.EnumerateFiles(true) .EnumerateFiles(true)
.Where(f => f.FileName == Consts.SettingsIni) .Where(f => f.FileName == Consts.SettingsIni)
@ -235,7 +237,7 @@ namespace Wabbajack.Lib
foreach (var esm in ModList.Directives.OfType<CleanedESM>().ToList()) foreach (var esm in ModList.Directives.OfType<CleanedESM>().ToList())
{ {
var filename = esm.To.FileName; var filename = esm.To.FileName;
var gameFile = GameFolder.Value.Combine((RelativePath)"Data", filename); var gameFile = GameFolder!.Value.Combine((RelativePath)"Data", filename);
Utils.Log($"Validating {filename}"); Utils.Log($"Validating {filename}");
var hash = gameFile.FileHash(); var hash = gameFile.FileHash();
if (hash != esm.SourceESMHash) if (hash != esm.SourceESMHash)
@ -310,7 +312,7 @@ namespace Wabbajack.Lib
private async Task GenerateCleanedESM(CleanedESM directive) private async Task GenerateCleanedESM(CleanedESM directive)
{ {
var filename = directive.To.FileName; var filename = directive.To.FileName;
var gameFile = GameFolder.Value.Combine((RelativePath)"Data", filename); var gameFile = GameFolder!.Value.Combine((RelativePath)"Data", filename);
Info($"Generating cleaned ESM for {filename}"); Info($"Generating cleaned ESM for {filename}");
if (!gameFile.Exists) throw new InvalidDataException($"Missing {filename} at {gameFile}"); if (!gameFile.Exists) throw new InvalidDataException($"Missing {filename} at {gameFile}");
Status($"Hashing game version of {filename}"); Status($"Hashing game version of {filename}");
@ -329,6 +331,10 @@ namespace Wabbajack.Lib
private void SetScreenSizeInPrefs() private void SetScreenSizeInPrefs()
{ {
if (SystemParameters == null)
{
throw new ArgumentNullException("System Parameters was null. Cannot set screen size prefs");
}
var config = new IniParserConfiguration {AllowDuplicateKeys = true, AllowDuplicateSections = true}; var config = new IniParserConfiguration {AllowDuplicateKeys = true, AllowDuplicateSections = true};
foreach (var file in OutputFolder.Combine("profiles").EnumerateFiles() foreach (var file in OutputFolder.Combine("profiles").EnumerateFiles()
.Where(f => ((string)f.FileName).EndsWith("refs.ini"))) .Where(f => ((string)f.FileName).EndsWith("refs.ini")))
@ -375,8 +381,8 @@ namespace Wabbajack.Lib
{ {
var data = Encoding.UTF8.GetString(await LoadBytesFromPath(directive.SourceDataID)); var data = Encoding.UTF8.GetString(await LoadBytesFromPath(directive.SourceDataID));
data = data.Replace(Consts.GAME_PATH_MAGIC_BACK, (string)GameFolder); data = data.Replace(Consts.GAME_PATH_MAGIC_BACK, (string)GameFolder!);
data = data.Replace(Consts.GAME_PATH_MAGIC_DOUBLE_BACK, ((string)GameFolder).Replace("\\", "\\\\")); data = data.Replace(Consts.GAME_PATH_MAGIC_DOUBLE_BACK, ((string)GameFolder!).Replace("\\", "\\\\"));
data = data.Replace(Consts.GAME_PATH_MAGIC_FORWARD, ((string)GameFolder).Replace("\\", "/")); data = data.Replace(Consts.GAME_PATH_MAGIC_FORWARD, ((string)GameFolder).Replace("\\", "/"));
data = data.Replace(Consts.MO2_PATH_MAGIC_BACK, (string)OutputFolder); data = data.Replace(Consts.MO2_PATH_MAGIC_BACK, (string)OutputFolder);

View File

@ -41,12 +41,11 @@ namespace Wabbajack.Lib
InstallSize = modlist.InstallSize; InstallSize = modlist.InstallSize;
// meta is being omitted due to it being useless and not very space friendly // meta is being omitted due to it being useless and not very space friendly
Archives = modlist.Archives.Select(a => new Archive Archives = modlist.Archives.Select(a => new Archive(a.State)
{ {
Hash = a.Hash, Hash = a.Hash,
Name = a.Name, Name = a.Name,
Size = a.Size, Size = a.Size,
State = a.State
}).ToList(); }).ToList();
} }
} }

View File

@ -13,13 +13,13 @@ namespace Wabbajack.Lib.ModListRegistry
public class ModlistMetadata public class ModlistMetadata
{ {
[JsonProperty("title")] [JsonProperty("title")]
public string Title { get; set; } public string Title { get; set; } = string.Empty;
[JsonProperty("description")] [JsonProperty("description")]
public string Description { get; set; } public string Description { get; set; } = string.Empty;
[JsonProperty("author")] [JsonProperty("author")]
public string Author { get; set; } public string Author { get; set; } = string.Empty;
[JsonProperty("game")] [JsonProperty("game")]
public Game Game { get; set; } public Game Game { get; set; }
@ -33,7 +33,7 @@ namespace Wabbajack.Lib.ModListRegistry
public LinksObject Links { get; set; } = new LinksObject(); public LinksObject Links { get; set; } = new LinksObject();
[JsonProperty("download_metadata")] [JsonProperty("download_metadata")]
public DownloadMetadata DownloadMetadata { get; set; } public DownloadMetadata? DownloadMetadata { get; set; }
[JsonIgnore] [JsonIgnore]
public ModListSummary ValidationSummary { get; set; } = new ModListSummary(); public ModListSummary ValidationSummary { get; set; } = new ModListSummary();
@ -42,22 +42,18 @@ namespace Wabbajack.Lib.ModListRegistry
public class LinksObject public class LinksObject
{ {
[JsonProperty("image")] [JsonProperty("image")]
public string ImageUri { get; set; } public string ImageUri { get; set; } = string.Empty;
[JsonProperty("readme")] [JsonProperty("readme")]
public string Readme { get; set; } public string Readme { get; set; } = string.Empty;
[JsonProperty("download")] [JsonProperty("download")]
public string Download { get; set; } public string Download { get; set; } = string.Empty;
[JsonProperty("machineURL")] [JsonProperty("machineURL")]
public string MachineURL { get; set; } public string MachineURL { get; set; } = string.Empty;
} }
public static async Task<List<ModlistMetadata>> LoadFromGithub() public static async Task<List<ModlistMetadata>> LoadFromGithub()
{ {
var client = new Common.Http.Client(); var client = new Common.Http.Client();
@ -103,18 +99,17 @@ namespace Wabbajack.Lib.ModListRegistry
public long SizeOfArchives { get; set; } public long SizeOfArchives { get; set; }
public long NumberOfInstalledFiles { get; set; } public long NumberOfInstalledFiles { get; set; }
public long SizeOfInstalledFiles { get; set; } public long SizeOfInstalledFiles { get; set; }
} }
[JsonName("ModListSummary")] [JsonName("ModListSummary")]
public class ModListSummary public class ModListSummary
{ {
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; } = string.Empty;
[JsonProperty("machineURL")] [JsonProperty("machineURL")]
public string MachineURL { get; set; } public string MachineURL { get; set; } = string.Empty;
[JsonProperty("checked")] [JsonProperty("checked")]
public DateTime Checked { get; set; } public DateTime Checked { get; set; }
[JsonProperty("failed")] [JsonProperty("failed")]

View File

@ -4,57 +4,57 @@ namespace Wabbajack.Lib.NexusApi
{ {
public class UserStatus public class UserStatus
{ {
public string email; public string email = string.Empty;
public bool is_premium; public bool is_premium;
public bool is_supporter; public bool is_supporter;
public string key; public string key = string.Empty;
public string name; public string name = string.Empty;
public string profile_url; public string profile_url = string.Empty;
public string user_id; public string user_id = string.Empty;
} }
public class NexusFileInfo public class NexusFileInfo
{ {
public long category_id { get; set; } public long category_id { get; set; }
public string category_name { get; set; } public string category_name { get; set; } = string.Empty;
public string changelog_html { get; set; } public string changelog_html { get; set; } = string.Empty;
public string description { get; set; } public string description { get; set; } = string.Empty;
public string external_virus_scan_url { get; set; } public string external_virus_scan_url { get; set; } = string.Empty;
public long file_id { get; set; } public long file_id { get; set; }
public string file_name { get; set; } public string file_name { get; set; } = string.Empty;
public bool is_primary { get; set; } public bool is_primary { get; set; }
public string mod_version { get; set; } public string mod_version { get; set; } = string.Empty;
public string name { get; set; } public string name { get; set; } = string.Empty;
public long size { get; set; } public long size { get; set; }
public long size_kb { get; set; } public long size_kb { get; set; }
public DateTime uploaded_time { get; set; } public DateTime uploaded_time { get; set; }
public long uploaded_timestamp { get; set; } public long uploaded_timestamp { get; set; }
public string version { get; set; } public string version { get; set; } = string.Empty;
} }
public class ModInfo public class ModInfo
{ {
public uint _internal_version { get; set; } public uint _internal_version { get; set; }
public string game_name { get; set; } public string game_name { get; set; } = string.Empty;
public string mod_id { get; set; } public string mod_id { get; set; } = string.Empty;
public string name { get; set; } public string name { get; set; } = string.Empty;
public string summary { get; set; } public string summary { get; set; } = string.Empty;
public string author { get; set; } public string author { get; set; } = string.Empty;
public string uploaded_by { get; set; } public string uploaded_by { get; set; } = string.Empty;
public string uploaded_users_profile_url { get; set; } public string uploaded_users_profile_url { get; set; } = string.Empty;
public string picture_url { get; set; } public string picture_url { get; set; } = string.Empty;
public bool contains_adult_content { get; set; } public bool contains_adult_content { get; set; }
} }
public class MD5Response public class MD5Response
{ {
public ModInfo mod; public ModInfo? mod;
public NexusFileInfo file_details; public NexusFileInfo? file_details;
} }
public class EndorsementResponse public class EndorsementResponse
{ {
public string message; public string message = string.Empty;
public string status; public string status = string.Empty;
} }
} }

View File

@ -27,12 +27,11 @@ namespace Wabbajack.Lib.NexusApi
#region Authentication #region Authentication
public string ApiKey { get; } public string? ApiKey { get; }
public bool IsAuthenticated => ApiKey != null; public bool IsAuthenticated => ApiKey != null;
private Task<UserStatus> _userStatus; private Task<UserStatus>? _userStatus;
public Task<UserStatus> UserStatus public Task<UserStatus> UserStatus
{ {
get get
@ -214,7 +213,7 @@ namespace Wabbajack.Lib.NexusApi
#endregion #endregion
private NexusApiClient(string apiKey = null) private NexusApiClient(string? apiKey = null)
{ {
ApiKey = apiKey; ApiKey = apiKey;
@ -230,9 +229,9 @@ namespace Wabbajack.Lib.NexusApi
Directory.CreateDirectory(Consts.NexusCacheDirectory); Directory.CreateDirectory(Consts.NexusCacheDirectory);
} }
public static async Task<NexusApiClient> Get(string apiKey = null) public static async Task<NexusApiClient> Get(string? apiKey = null)
{ {
apiKey = apiKey ?? await GetApiKey(); apiKey ??= await GetApiKey();
return new NexusApiClient(apiKey); return new NexusApiClient(apiKey);
} }
@ -316,9 +315,10 @@ namespace Wabbajack.Lib.NexusApi
throw; throw;
} }
} }
public class GetModFilesResponse public class GetModFilesResponse
{ {
public List<NexusFileInfo> files { get; set; } public List<NexusFileInfo> files { get; set; } = new List<NexusFileInfo>();
} }
public async Task<GetModFilesResponse> GetModFiles(Game game, long modid, bool useCache = true) public async Task<GetModFilesResponse> GetModFiles(Game game, long modid, bool useCache = true)
@ -350,18 +350,18 @@ namespace Wabbajack.Lib.NexusApi
private class DownloadLink private class DownloadLink
{ {
public string URI { get; set; } public string URI { get; set; } = string.Empty;
} }
public static MethodInfo CacheMethod { get; set; } private static string? _localCacheDir;
private static string _localCacheDir;
public static string LocalCacheDir public static string LocalCacheDir
{ {
get get
{ {
if (_localCacheDir == null) if (_localCacheDir == null)
_localCacheDir = Environment.GetEnvironmentVariable("NEXUSCACHEDIR"); _localCacheDir = Environment.GetEnvironmentVariable("NEXUSCACHEDIR");
if (_localCacheDir == null)
throw new ArgumentNullException($"Enviornment variable could not be located: NEXUSCACHEDIR");
return _localCacheDir; return _localCacheDir;
} }
set => _localCacheDir = value; set => _localCacheDir = value;

View File

@ -19,15 +19,10 @@ namespace Wabbajack.Lib.NexusApi
public static string FixupSummary(string argSummary) public static string FixupSummary(string argSummary)
{ {
if (argSummary != null) return argSummary.Replace("&#39;", "'")
{ .Replace("<br/>", "\n\n")
return argSummary.Replace("&#39;", "'") .Replace("<br />", "\n\n")
.Replace("<br/>", "\n\n") .Replace("&#33;", "!");
.Replace("<br />", "\n\n")
.Replace("&#33;", "!");
}
return argSummary;
} }
} }
} }

View File

@ -26,8 +26,7 @@ namespace Wabbajack.Lib.NexusApi
{ {
var parts = link.Uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries); var parts = link.Uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
var foundGame = GameRegistry.GetByFuzzyName(parts[0]); if (!GameRegistry.TryGetByFuzzyName(parts[0], out var foundGame))
if (foundGame == null)
{ {
game = Game.Oblivion; game = Game.Oblivion;
modId = 0; modId = 0;
@ -65,7 +64,8 @@ namespace Wabbajack.Lib.NexusApi
} }
return null; return null;
}).Where(v => v != null); })
.NotNull();
} }

View File

@ -10,7 +10,7 @@ namespace Wabbajack.Lib.NexusApi
public class RequestNexusAuthorization : AUserIntervention public class RequestNexusAuthorization : AUserIntervention
{ {
public override string ShortDescription => "Getting User's Nexus API Key"; public override string ShortDescription => "Getting User's Nexus API Key";
public override string ExtendedDescription { get; } public override string ExtendedDescription { get; } = string.Empty;
private readonly TaskCompletionSource<string> _source = new TaskCompletionSource<string>(); private readonly TaskCompletionSource<string> _source = new TaskCompletionSource<string>();
public Task<string> Task => _source.Task; public Task<string> Task => _source.Task;

View File

@ -10,7 +10,7 @@ namespace Wabbajack.Lib
public class ConfirmUpdateOfExistingInstall : ConfirmationIntervention public class ConfirmUpdateOfExistingInstall : ConfirmationIntervention
{ {
public AbsolutePath OutputFolder { get; set; } public AbsolutePath OutputFolder { get; set; }
public string ModListName { get; set; } public string ModListName { get; set; } = string.Empty;
public override string ShortDescription { get; } = "Do you want to overwrite existing files?"; public override string ShortDescription { get; } = "Do you want to overwrite existing files?";

View File

@ -9,9 +9,9 @@ namespace Wabbajack.Lib
public class ManuallyDownloadFile : AUserIntervention public class ManuallyDownloadFile : AUserIntervention
{ {
public ManualDownloader.State State { get; } public ManualDownloader.State State { get; }
public override string ShortDescription { get; } public override string ShortDescription { get; } = string.Empty;
public override string ExtendedDescription { get; } public override string ExtendedDescription { get; } = string.Empty;
private TaskCompletionSource<(Uri, Common.Http.Client)> _tcs = new TaskCompletionSource<(Uri, Common.Http.Client)>(); private TaskCompletionSource<(Uri, Common.Http.Client)> _tcs = new TaskCompletionSource<(Uri, Common.Http.Client)>();
public Task<(Uri, Common.Http.Client)> Task => _tcs.Task; public Task<(Uri, Common.Http.Client)> Task => _tcs.Task;

View File

@ -9,9 +9,9 @@ namespace Wabbajack.Lib
public class ManuallyDownloadNexusFile : AUserIntervention public class ManuallyDownloadNexusFile : AUserIntervention
{ {
public NexusDownloader.State State { get; } public NexusDownloader.State State { get; }
public override string ShortDescription { get; } public override string ShortDescription { get; } = string.Empty;
public override string ExtendedDescription { get; } public override string ExtendedDescription { get; } = string.Empty;
private TaskCompletionSource<Uri> _tcs = new TaskCompletionSource<Uri>(); private TaskCompletionSource<Uri> _tcs = new TaskCompletionSource<Uri>();
public Task<Uri> Task => _tcs.Task; public Task<Uri> Task => _tcs.Task;

View File

@ -14,9 +14,9 @@ namespace Wabbajack.Lib
public int ScreenWidth { get; set; } public int ScreenWidth { get; set; }
public long VideoMemorySize { get; set; } public long VideoMemorySize { get; set; }
public long SystemMemorySize { get; set; } public long SystemMemorySize { get; set; }
public Version WindowsVersion { get; set; } public Version WindowsVersion { get; set; } = Environment.OSVersion.Version;
/// <summary> /// <summary>
/// Value used in LE ENBs for VideoMemorySizeMb /// Value used in LE ENBs for VideoMemorySizeMb
/// </summary> /// </summary>

View File

@ -10,33 +10,32 @@ namespace Wabbajack.Lib.Validation
public bool? CanUseInOtherGames { get; set; } public bool? CanUseInOtherGames { get; set; }
} }
public class Author //public class Author
{ //{
public Permissions Permissions { get; set; } // public Permissions Permissions { get; set; }
public Dictionary<string, Game> Games; // public Dictionary<string, Game> Games;
} //}
public class Game //public class Game
{ //{
public Permissions Permissions; // public Permissions Permissions;
public Dictionary<string, Mod> Mods; // public Dictionary<string, Mod> Mods;
} //}
public class Mod //public class Mod
{ //{
public Permissions Permissions; // public Permissions Permissions;
public Dictionary<string, File> Files; // public Dictionary<string, File> Files;
} //}
public class File
{
public Permissions Permissions;
}
//public class File
//{
// public Permissions Permissions;
//}
public class ServerWhitelist public class ServerWhitelist
{ {
public List<string> GoogleIDs; public List<string> GoogleIDs = new List<string>();
public List<string> AllowedPrefixes; public List<string> AllowedPrefixes = new List<string>();
} }
} }

View File

@ -24,7 +24,7 @@ namespace Wabbajack.Lib
protected void RaiseAndSetIfChanged<T>( protected void RaiseAndSetIfChanged<T>(
ref T item, ref T item,
T newItem, T newItem,
[CallerMemberName] string propertyName = null) [CallerMemberName] string? propertyName = null)
{ {
if (EqualityComparer<T>.Default.Equals(item, newItem)) return; if (EqualityComparer<T>.Default.Equals(item, newItem)) return;
item = newItem; item = newItem;

View File

@ -11,6 +11,7 @@ using Directory = Alphaleonis.Win32.Filesystem.Directory;
using DirectoryInfo = Alphaleonis.Win32.Filesystem.DirectoryInfo; using DirectoryInfo = Alphaleonis.Win32.Filesystem.DirectoryInfo;
using File = Alphaleonis.Win32.Filesystem.File; using File = Alphaleonis.Win32.Filesystem.File;
using Path = Alphaleonis.Win32.Filesystem.Path; using Path = Alphaleonis.Win32.Filesystem.Path;
#nullable disable
namespace Wabbajack.Lib namespace Wabbajack.Lib
{ {
@ -26,7 +27,8 @@ namespace Wabbajack.Lib
modList: modList, modList: modList,
outputFolder: outputFolder, outputFolder: outputFolder,
downloadFolder: downloadFolder, downloadFolder: downloadFolder,
parameters: parameters) parameters: parameters,
steps: 10)
{ {
#if DEBUG #if DEBUG
// TODO: only for testing // TODO: only for testing
@ -51,7 +53,7 @@ namespace Wabbajack.Lib
} }
if (cancel.IsCancellationRequested) return false; if (cancel.IsCancellationRequested) return false;
ConfigureProcessor(10, ConstructDynamicNumThreads(await RecommendQueueSize())); Queue.SetActiveThreadsObservable(ConstructDynamicNumThreads(await RecommendQueueSize()));
DownloadFolder.CreateDirectory(); DownloadFolder.CreateDirectory();
if (cancel.IsCancellationRequested) return false; if (cancel.IsCancellationRequested) return false;
@ -122,7 +124,7 @@ namespace Wabbajack.Lib
var manualFilesDir = OutputFolder.Combine(Consts.ManualGameFilesDir); var manualFilesDir = OutputFolder.Combine(Consts.ManualGameFilesDir);
var gameFolder = GameInfo.GameLocation(); var gameFolder = GameInfo.TryGetGameLocation();
Info($"Copying files from {manualFilesDir} " + Info($"Copying files from {manualFilesDir} " +
$"to the game folder at {gameFolder}"); $"to the game folder at {gameFolder}");

View File

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier> <RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CefSharp.Common"> <PackageReference Include="CefSharp.Common">

View File

@ -13,8 +13,8 @@ namespace Wabbajack.Lib.WebAutomation
{ {
public class CefSharpWrapper : IWebDriver public class CefSharpWrapper : IWebDriver
{ {
private IWebBrowser _browser; private readonly IWebBrowser _browser;
public Action<Uri> DownloadHandler { get; set; } public Action<Uri>? DownloadHandler { get; set; }
public CefSharpWrapper(IWebBrowser browser) public CefSharpWrapper(IWebBrowser browser)
{ {
_browser = browser; _browser = browser;
@ -24,7 +24,7 @@ namespace Wabbajack.Lib.WebAutomation
{ {
var tcs = new TaskCompletionSource<bool>(); var tcs = new TaskCompletionSource<bool>();
EventHandler<LoadingStateChangedEventArgs> handler = null; EventHandler<LoadingStateChangedEventArgs>? handler = null;
handler = (sender, e) => handler = (sender, e) =>
{ {
if (!e.IsLoading) if (!e.IsLoading)
@ -68,7 +68,7 @@ namespace Wabbajack.Lib.WebAutomation
public class PopupBlocker : ILifeSpanHandler public class PopupBlocker : ILifeSpanHandler
{ {
private CefSharpWrapper _wrapper; private readonly CefSharpWrapper _wrapper;
public PopupBlocker(CefSharpWrapper cefSharpWrapper) public PopupBlocker(CefSharpWrapper cefSharpWrapper)
{ {
@ -77,7 +77,7 @@ namespace Wabbajack.Lib.WebAutomation
public bool OnBeforePopup(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, public bool OnBeforePopup(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl,
string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures,
IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser? newBrowser)
{ {
// Block popups // Block popups
newBrowser = null; newBrowser = null;
@ -86,7 +86,6 @@ namespace Wabbajack.Lib.WebAutomation
public void OnAfterCreated(IWebBrowser chromiumWebBrowser, IBrowser browser) public void OnAfterCreated(IWebBrowser chromiumWebBrowser, IBrowser browser)
{ {
} }
public bool DoClose(IWebBrowser chromiumWebBrowser, IBrowser browser) public bool DoClose(IWebBrowser chromiumWebBrowser, IBrowser browser)
@ -96,7 +95,6 @@ namespace Wabbajack.Lib.WebAutomation
public void OnBeforeClose(IWebBrowser chromiumWebBrowser, IBrowser browser) public void OnBeforeClose(IWebBrowser chromiumWebBrowser, IBrowser browser)
{ {
} }
} }
@ -112,7 +110,7 @@ namespace Wabbajack.Lib.WebAutomation
public void OnBeforeDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem, public void OnBeforeDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem,
IBeforeDownloadCallback callback) IBeforeDownloadCallback callback)
{ {
_wrapper.DownloadHandler(new Uri(downloadItem.Url)); _wrapper.DownloadHandler?.Invoke(new Uri(downloadItem.Url));
} }
public void OnDownloadUpdated(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem, public void OnDownloadUpdated(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem,

Some files were not shown because too many files have changed in this diff Show More