mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Merge Master
This commit is contained in:
commit
470992cc4a
@ -184,4 +184,10 @@ dotnet_diagnostic.CS8609.severity = error
|
||||
dotnet_diagnostic.CS8714.severity = error
|
||||
|
||||
# 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
|
@ -53,14 +53,14 @@ namespace Wabbajack.BuildServer.Test
|
||||
public async Task CanNotifyOfInis()
|
||||
{
|
||||
var archive =
|
||||
new Archive
|
||||
{
|
||||
State = new NexusDownloader.State
|
||||
new Archive(
|
||||
new NexusDownloader.State
|
||||
{
|
||||
Game = Game.SkyrimSpecialEdition,
|
||||
ModID = long.MaxValue >> 3,
|
||||
FileID = long.MaxValue >> 3,
|
||||
},
|
||||
})
|
||||
{
|
||||
Name = Guid.NewGuid().ToString()
|
||||
};
|
||||
Assert.True(await AuthorAPI.UploadPackagedInis(new[] {archive}));
|
||||
|
@ -190,19 +190,14 @@ namespace Wabbajack.BuildServer.Test
|
||||
|
||||
|
||||
|
||||
ModListData = new ModList
|
||||
{
|
||||
Archives = new List<Archive>
|
||||
ModListData = new ModList();
|
||||
ModListData.Archives.Add(
|
||||
new Archive(new HTTPDownloader.State(MakeURL("test_archive.txt")))
|
||||
{
|
||||
new Archive
|
||||
{
|
||||
Hash = await test_archive_path.FileHashAsync(),
|
||||
Name = "test_archive",
|
||||
Size = test_archive_path.Size,
|
||||
State = new HTTPDownloader.State {Url = MakeURL("test_archive.txt")}
|
||||
}
|
||||
}
|
||||
};
|
||||
Hash = await test_archive_path.FileHashAsync(),
|
||||
Name = "test_archive",
|
||||
Size = test_archive_path.Size,
|
||||
});
|
||||
|
||||
var modListPath = "test_modlist.wabbajack".RelativeTo(Fixture.ServerPublicFolder);
|
||||
|
||||
|
@ -42,13 +42,9 @@ namespace Wabbajack.BuildServer.Test
|
||||
{
|
||||
Payload = new IndexJob
|
||||
{
|
||||
Archive = new Archive
|
||||
Archive = new Archive(new HTTPDownloader.State(MakeURL("old_file_data.random")))
|
||||
{
|
||||
Name = "Oldfile",
|
||||
State = new HTTPDownloader.State
|
||||
{
|
||||
Url = MakeURL("old_file_data.random"),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -57,13 +53,9 @@ namespace Wabbajack.BuildServer.Test
|
||||
{
|
||||
Payload = new IndexJob
|
||||
{
|
||||
Archive = new Archive
|
||||
Archive = new Archive(new HTTPDownloader.State(MakeURL("new_file_data.random")))
|
||||
{
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -102,10 +102,9 @@ namespace Wabbajack.BuildServer.Controllers
|
||||
Priority = Job.JobPriority.Low,
|
||||
Payload = new IndexJob
|
||||
{
|
||||
Archive = new Archive
|
||||
Archive = new Archive(data)
|
||||
{
|
||||
Name = entry.Name,
|
||||
State = data
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -22,7 +22,7 @@ namespace Wabbajack.BuildServer.Controllers
|
||||
public async Task<long> EnqueueJob(string 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);
|
||||
return job.Id;
|
||||
}
|
||||
|
@ -220,15 +220,19 @@ namespace Wabbajack.BuildServer.Controllers
|
||||
var allMods = await api.GetModFiles(state.Game, state.ModID);
|
||||
var archive = allMods.files.Where(m => !string.IsNullOrEmpty(m.category_name))
|
||||
.OrderBy(s => Math.Abs((long)s.size - origSize))
|
||||
.Select(s => new Archive {
|
||||
Name = s.file_name,
|
||||
Size = (long)s.size,
|
||||
State = new NexusDownloader.State
|
||||
.Select(s =>
|
||||
new Archive(
|
||||
new NexusDownloader.State
|
||||
{
|
||||
Game = state.Game,
|
||||
ModID = state.ModID,
|
||||
FileID = s.file_id
|
||||
})
|
||||
{
|
||||
Game = state.Game,
|
||||
ModID = state.ModID,
|
||||
FileID = s.file_id
|
||||
}}).FirstOrDefault();
|
||||
Name = s.file_name,
|
||||
Size = (long)s.size,
|
||||
})
|
||||
.FirstOrDefault();
|
||||
|
||||
if (archive == null)
|
||||
{
|
||||
|
@ -21,13 +21,12 @@ namespace Wabbajack.BuildServer.Models.Jobs
|
||||
{
|
||||
Utils.Log($"Indexing game files");
|
||||
var states = GameRegistry.Games.Values
|
||||
.Where(game => game.GameLocation() != null && game.MainExecutable != null)
|
||||
.SelectMany(game => game.GameLocation().Value.EnumerateFiles()
|
||||
.Select(file => new GameFileSourceDownloader.State
|
||||
.Where(game => game.TryGetGameLocation() != null && game.MainExecutable != null)
|
||||
.SelectMany(game => game.GameLocation().EnumerateFiles()
|
||||
.Select(file => new GameFileSourceDownloader.State(game.InstalledVersion)
|
||||
{
|
||||
Game = game.Game,
|
||||
GameVersion = game.InstalledVersion,
|
||||
GameFile = file.RelativeTo(game.GameLocation().Value),
|
||||
GameFile = file.RelativeTo(game.GameLocation()),
|
||||
}))
|
||||
.ToList();
|
||||
|
||||
@ -41,7 +40,7 @@ namespace Wabbajack.BuildServer.Models.Jobs
|
||||
|
||||
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}");
|
||||
try
|
||||
{
|
||||
@ -55,16 +54,15 @@ namespace Wabbajack.BuildServer.Models.Jobs
|
||||
|
||||
var with_hash = states.Where(state => state.Hash != default).ToList();
|
||||
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})
|
||||
.ToList();
|
||||
|
||||
foreach (var job in jobs)
|
||||
await sql.EnqueueJob(job);
|
||||
await sql.EnqueueJob(job);
|
||||
|
||||
return JobResult.Success();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override IEnumerable<object> PrimaryKey => new object[0];
|
||||
|
@ -39,13 +39,9 @@ namespace Wabbajack.BuildServer.Models.Jobs
|
||||
{
|
||||
Payload = new IndexJob
|
||||
{
|
||||
Archive = new Archive
|
||||
Archive = new Archive(new MegaDownloader.State(url.ToString()))
|
||||
{
|
||||
Name = Guid.NewGuid() + ".7z",
|
||||
State = new MegaDownloader.State
|
||||
{
|
||||
Url = url.ToString()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -57,15 +57,11 @@ namespace Wabbajack.BuildServer.Models.Jobs
|
||||
Priority = Job.JobPriority.High,
|
||||
Payload = new IndexJob
|
||||
{
|
||||
Archive = new Archive
|
||||
Archive = new Archive(new HTTPDownloader.State(file.Uri))
|
||||
{
|
||||
Name = file.MungedName,
|
||||
Size = file.Size,
|
||||
Hash = file.Hash,
|
||||
State = new HTTPDownloader.State
|
||||
{
|
||||
Url = file.Uri
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -541,9 +541,8 @@ namespace Wabbajack.BuildServer.Model.Models
|
||||
var result = await conn.QueryFirstOrDefaultAsync<string>(@"SELECT JsonState, Size FROM dbo.DownloadStates
|
||||
WHERE Hash = @hash AND PrimaryKey like 'NexusDownloader+State|%'",
|
||||
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
|
||||
};
|
||||
}
|
||||
@ -555,9 +554,8 @@ namespace Wabbajack.BuildServer.Model.Models
|
||||
LEFT JOIN dbo.IndexedFile indexed ON indexed.Hash = state.Hash
|
||||
WHERE state.Hash = @hash",
|
||||
new {Hash = (long)startingHash});
|
||||
return result == default ? null : new Archive
|
||||
return result == default ? null : new Archive(result.Item1.FromJsonString<AbstractDownloadState>())
|
||||
{
|
||||
State = result.Item1.FromJsonString<AbstractDownloadState>(),
|
||||
Hash = startingHash,
|
||||
Size = result.Item2
|
||||
};
|
||||
@ -568,9 +566,8 @@ namespace Wabbajack.BuildServer.Model.Models
|
||||
await using var conn = await Open();
|
||||
var result = await conn.QueryFirstOrDefaultAsync<(long Hash, string State)>(@"SELECT Hash, JsonState FROM dbo.DownloadStates WHERE 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)
|
||||
};
|
||||
}
|
||||
@ -688,10 +685,11 @@ namespace Wabbajack.BuildServer.Model.Models
|
||||
await using var conn = await Open();
|
||||
var results = await conn.QueryAsync<(Hash Hash, long Size, string State)>(
|
||||
@"SELECT Hash, Size, State FROM dbo.ModListArchives WHERE PrimaryKeyString NOT LIKE 'NexusDownloader+State|%'");
|
||||
return results.Select(r => new Archive {
|
||||
return results.Select(r => new Archive (r.State.FromJsonString<AbstractDownloadState>())
|
||||
{
|
||||
Size = r.Size,
|
||||
Hash = r.Hash,
|
||||
State = r.State.FromJsonString<AbstractDownloadState>()
|
||||
|
||||
}).ToList();}
|
||||
|
||||
public async Task UpdateNonNexusModlistArchivesStatus(IEnumerable<(Archive Archive, bool IsValid)> results)
|
||||
|
@ -318,7 +318,7 @@ namespace Wabbajack.CLI.Verbs
|
||||
|
||||
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);
|
||||
return Encoding.Default.GetString(bytes);
|
||||
}
|
||||
@ -328,9 +328,9 @@ namespace Wabbajack.CLI.Verbs
|
||||
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)
|
||||
{
|
||||
|
@ -9,7 +9,7 @@ namespace Wabbajack.CLI.Verbs
|
||||
public class DeleteFile : AVerb
|
||||
{
|
||||
[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()
|
||||
{
|
||||
|
@ -35,7 +35,7 @@ namespace Wabbajack.CLI.Verbs
|
||||
new[] {state}
|
||||
.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();
|
||||
|
||||
File.WriteAllLines(Output + ".meta", state.GetMetaIni());
|
||||
|
@ -21,6 +21,9 @@ namespace Wabbajack
|
||||
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)
|
||||
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)
|
||||
where K : notnull
|
||||
{
|
||||
@ -38,5 +44,15 @@ namespace Wabbajack
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,5 +33,32 @@ namespace Wabbajack
|
||||
yield return next;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
Wabbajack.Common/Extensions/ListExt.cs
Normal file
15
Wabbajack.Common/Extensions/ListExt.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Microsoft.Win32;
|
||||
@ -20,6 +21,7 @@ namespace Wabbajack.Common
|
||||
FalloutNewVegas,
|
||||
[Description("Skyrim Legendary Edition")]
|
||||
Skyrim,
|
||||
Enderal,
|
||||
[Description("Skyrim Special Edition")]
|
||||
SkyrimSpecialEdition,
|
||||
[Description("Fallout 4")]
|
||||
@ -85,23 +87,44 @@ namespace Wabbajack.Common
|
||||
{
|
||||
get
|
||||
{
|
||||
AbsolutePath? gameLoc = GameLocation();
|
||||
if (gameLoc == null)
|
||||
if (!TryGetGameLocation(out var gameLoc))
|
||||
throw new GameNotInstalledException(this);
|
||||
if (MainExecutable == null)
|
||||
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 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 +179,24 @@ namespace Wabbajack.Common
|
||||
}
|
||||
|
||||
/// <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"></param>
|
||||
/// <returns></returns>
|
||||
public static GameMetaData? GetByFuzzyName(string someName)
|
||||
/// 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 found</returns>
|
||||
/// <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();
|
||||
|
||||
GameMetaData? result = null;
|
||||
|
||||
result = GetByNexusName(someName);
|
||||
GameMetaData? result = GetByNexusName(someName);
|
||||
if (result != null) return result;
|
||||
|
||||
result = GetByMO2ArchiveName(someName);
|
||||
@ -174,6 +205,19 @@ namespace Wabbajack.Common
|
||||
return int.TryParse(someName, out int id) ? GetBySteamID(id) : null;
|
||||
}
|
||||
|
||||
public static bool TryGetByFuzzyName(string someName, [MaybeNullWhen(false)] out GameMetaData gameMetaData)
|
||||
{
|
||||
var result = TryGetByFuzzyName(someName);
|
||||
if (result == null)
|
||||
{
|
||||
gameMetaData = Games.Values.First();
|
||||
return false;
|
||||
}
|
||||
|
||||
gameMetaData = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<Game, GameMetaData> Games = new Dictionary<Game, GameMetaData>
|
||||
{
|
||||
{
|
||||
@ -497,6 +541,22 @@ namespace Wabbajack.Common
|
||||
"Stardew Valley.exe"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Game.Enderal, new GameMetaData
|
||||
{
|
||||
SupportedModManager = ModManager.MO2,
|
||||
Game = Game.Enderal,
|
||||
NexusName = "enderal",
|
||||
MO2Name = "Enderal",
|
||||
MO2ArchiveName = "enderal",
|
||||
SteamIDs = new List<int>{1027920},
|
||||
RequiredFiles = new List<string>
|
||||
{
|
||||
"TESV.exe"
|
||||
},
|
||||
MainExecutable = "TESV.exe"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -11,7 +11,7 @@ namespace Wabbajack.Common.Http
|
||||
{
|
||||
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 async Task<HttpResponseMessage> GetAsync(string url, HttpCompletionOption responseHeadersRead = HttpCompletionOption.ResponseHeadersRead)
|
||||
{
|
||||
|
@ -1,11 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Wabbajack.Common.Serialization.Json;
|
||||
@ -19,7 +17,7 @@ namespace Wabbajack.Common
|
||||
{
|
||||
new HashJsonConverter(),
|
||||
new RelativePathConverter(),
|
||||
new AbolutePathConverter(),
|
||||
new AbsolutePathConverter(),
|
||||
new HashRelativePathConverter(),
|
||||
new FullPathConverter(),
|
||||
new GameConverter(),
|
||||
@ -33,7 +31,7 @@ namespace Wabbajack.Common
|
||||
Converters = Converters};
|
||||
|
||||
public static JsonSerializerSettings GenericJsonSettings =>
|
||||
new JsonSerializerSettings { };
|
||||
new JsonSerializerSettings();
|
||||
|
||||
|
||||
public static void ToJson<T>(this T obj, string filename)
|
||||
@ -62,16 +60,12 @@ namespace Wabbajack.Common
|
||||
return JsonConvert.SerializeObject(obj, JsonSettings);
|
||||
}
|
||||
|
||||
public static T FromJson<T>(this AbsolutePath filename,
|
||||
TypeNameHandling handling = TypeNameHandling.All,
|
||||
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full)
|
||||
public static T FromJson<T>(this AbsolutePath filename)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(filename.ReadAllText(), JsonSettings)!;
|
||||
}
|
||||
|
||||
public static T FromJsonString<T>(this string data,
|
||||
TypeNameHandling handling = TypeNameHandling.Objects,
|
||||
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full)
|
||||
public static T FromJsonString<T>(this string data)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(data, JsonSettings)!;
|
||||
}
|
||||
@ -86,8 +80,6 @@ namespace Wabbajack.Common
|
||||
throw new JsonException("Type deserialized into null");
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private class HashJsonConverter : JsonConverter<Hash>
|
||||
{
|
||||
@ -118,7 +110,7 @@ namespace Wabbajack.Common
|
||||
}
|
||||
}
|
||||
|
||||
private class AbolutePathConverter : JsonConverter<AbsolutePath>
|
||||
private class AbsolutePathConverter : JsonConverter<AbsolutePath>
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, AbsolutePath value, JsonSerializer serializer)
|
||||
{
|
||||
@ -232,8 +224,7 @@ namespace Wabbajack.Common
|
||||
return (Game)i;
|
||||
}
|
||||
|
||||
GameMetaData? game = GameRegistry.GetByFuzzyName(str);
|
||||
if (game == null)
|
||||
if (!GameRegistry.TryGetByFuzzyName(str, out var game))
|
||||
{
|
||||
throw new ArgumentException($"Could not convert {str} to a Game type.");
|
||||
}
|
||||
@ -248,11 +239,11 @@ namespace Wabbajack.Common
|
||||
{
|
||||
private static Dictionary<string, Type> _nameToType = new Dictionary<string, Type>();
|
||||
private static Dictionary<Type, string> _typeToName = new Dictionary<Type, string>();
|
||||
private static bool _inited = false;
|
||||
private static bool _init;
|
||||
|
||||
public JsonNameSerializationBinder()
|
||||
{
|
||||
if (_inited)
|
||||
if (_init)
|
||||
return;
|
||||
|
||||
var customDisplayNameTypes =
|
||||
@ -282,7 +273,7 @@ namespace Wabbajack.Common
|
||||
_typeToName = _nameToType.ToDictionary(
|
||||
t => t.Value,
|
||||
t => t.Key);
|
||||
_inited = true;
|
||||
_init = true;
|
||||
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Security;
|
||||
using DynamicData;
|
||||
using Microsoft.Win32;
|
||||
#nullable enable
|
||||
|
||||
namespace Wabbajack.Common.StoreHandlers
|
||||
{
|
||||
@ -21,9 +22,14 @@ namespace Wabbajack.Common.StoreHandlers
|
||||
|
||||
public class SteamWorkshopItem
|
||||
{
|
||||
public SteamGame? Game;
|
||||
public readonly SteamGame Game;
|
||||
public int ItemID;
|
||||
public int Size;
|
||||
|
||||
public SteamWorkshopItem(SteamGame game)
|
||||
{
|
||||
Game = game;
|
||||
}
|
||||
}
|
||||
|
||||
public class SteamHandler : AStoreHandler
|
||||
@ -202,14 +208,14 @@ namespace Wabbajack.Common.StoreHandlers
|
||||
var bracketStart = 0;
|
||||
var bracketEnd = 0;
|
||||
|
||||
SteamWorkshopItem? currentItem = new SteamWorkshopItem();
|
||||
SteamWorkshopItem? currentItem = new SteamWorkshopItem(game);
|
||||
|
||||
lines.Do(l =>
|
||||
{
|
||||
if (end)
|
||||
return;
|
||||
if (currentItem == null)
|
||||
currentItem = new SteamWorkshopItem();
|
||||
currentItem = new SteamWorkshopItem(game);
|
||||
|
||||
var currentLine = lines.IndexOf(l);
|
||||
if (l.ContainsCaseInsensitive("\"appid\"") && !foundAppID)
|
||||
@ -271,7 +277,6 @@ namespace Wabbajack.Common.StoreHandlers
|
||||
|
||||
bracketStart = 0;
|
||||
bracketEnd = 0;
|
||||
currentItem.Game = game;
|
||||
game.WorkshopItems.Add(currentItem);
|
||||
|
||||
Utils.Log($"Found Steam Workshop item {currentItem.ItemID}");
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ namespace Wabbajack.Common
|
||||
private readonly BehaviorSubject<(int DesiredCPUs, int CurrentCPUs)> _cpuCountSubj = new BehaviorSubject<(int DesiredCPUs, int CurrentCPUs)>((0, 0));
|
||||
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);
|
||||
|
||||
@ -66,7 +66,7 @@ namespace Wabbajack.Common
|
||||
/// Creates a WorkQueue whos number of threads is determined by the given observable
|
||||
/// </summary>
|
||||
/// <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
|
||||
_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
|
||||
/// </summary>
|
||||
/// <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);
|
||||
}
|
||||
|
@ -15,9 +15,9 @@ namespace Wabbajack.Lib
|
||||
{
|
||||
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>();
|
||||
|
||||
@ -42,7 +42,6 @@ namespace Wabbajack.Lib
|
||||
private Subject<bool> _isRunning { get; } = new Subject<bool>();
|
||||
public IObservable<bool> IsRunning => _isRunning;
|
||||
|
||||
private int _configured;
|
||||
private int _started;
|
||||
private readonly CancellationTokenSource _cancel = new CancellationTokenSource();
|
||||
|
||||
@ -53,21 +52,16 @@ namespace Wabbajack.Lib
|
||||
public BehaviorSubject<byte> MaxCores = new BehaviorSubject<byte>(byte.MaxValue);
|
||||
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);
|
||||
VFS = new Context(Queue) { UpdateTracker = UpdateTracker };
|
||||
Queue.Status.Subscribe(_queueStatus)
|
||||
.DisposeWith(_subs);
|
||||
Queue.LogMessages.Subscribe(_logMessages)
|
||||
.DisposeWith(_subs);
|
||||
UpdateTracker.Progress.Subscribe(_percentCompleted);
|
||||
UpdateTracker.StepName.Subscribe(_textStatus);
|
||||
VFS = new Context(Queue) { UpdateTracker = UpdateTracker };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
@ -19,10 +19,10 @@ namespace Wabbajack.Lib
|
||||
{
|
||||
public abstract class ACompiler : ABatchProcessor
|
||||
{
|
||||
public string ModListName, ModListAuthor, ModListDescription, ModListWebsite;
|
||||
public string? ModListName, ModListAuthor, ModListDescription, ModListWebsite;
|
||||
public AbsolutePath ModListImage, ModListReadme;
|
||||
public bool ReadmeIsWebsite;
|
||||
protected Version WabbajackVersion;
|
||||
protected Version? WabbajackVersion;
|
||||
|
||||
public abstract AbsolutePath VFSCacheName { get; }
|
||||
//protected string VFSCacheName => Path.Combine(Consts.LocalAppDataPath, $"vfs_compile_cache.bin");
|
||||
@ -42,14 +42,19 @@ namespace Wabbajack.Lib
|
||||
|
||||
public bool IgnoreMissingFiles { get; set; }
|
||||
|
||||
public ICollection<Archive> SelectedArchives = new List<Archive>();
|
||||
public List<Directive> InstallDirectives = new List<Directive>();
|
||||
public List<RawSourceFile> AllFiles = new List<RawSourceFile>();
|
||||
public List<Archive> SelectedArchives { get; protected set; } = new List<Archive>();
|
||||
public List<Directive> InstallDirectives { get; protected set; } = new List<Directive>();
|
||||
public List<RawSourceFile> AllFiles { get; protected set; } = new List<RawSourceFile>();
|
||||
public ModList ModList = new ModList();
|
||||
|
||||
public List<IndexedArchive> IndexedArchives = new List<IndexedArchive>();
|
||||
public Dictionary<Hash, IEnumerable<VirtualFile>> IndexedFiles = new Dictionary<Hash, IEnumerable<VirtualFile>>();
|
||||
|
||||
public ACompiler(int steps)
|
||||
: base(steps)
|
||||
{
|
||||
}
|
||||
|
||||
public static void Info(string msg)
|
||||
{
|
||||
Utils.Log(msg);
|
||||
@ -216,7 +221,7 @@ namespace Wabbajack.Lib
|
||||
.GroupBy(f => f.File.Hash)
|
||||
.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)
|
||||
@ -226,8 +231,7 @@ namespace Wabbajack.Lib
|
||||
return await ResolveArchive(found);
|
||||
}
|
||||
|
||||
Error($"No match found for Archive sha: {hash.ToBase64()} this shouldn't happen");
|
||||
return null;
|
||||
throw new ArgumentException($"No match found for Archive sha: {hash.ToBase64()} this shouldn't happen");
|
||||
}
|
||||
|
||||
public async Task<Archive> ResolveArchive(IndexedArchive archive)
|
||||
@ -238,10 +242,7 @@ namespace Wabbajack.Lib
|
||||
Error(
|
||||
$"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
|
||||
{
|
||||
State = await DownloadDispatcher.ResolveArchive(archive.IniData)
|
||||
};
|
||||
var result = new Archive(await DownloadDispatcher.ResolveArchive(archive.IniData));
|
||||
|
||||
if (result.State == null)
|
||||
Error($"{archive.Name} could not be handled by any of the downloaders");
|
||||
|
@ -27,11 +27,12 @@ namespace Wabbajack.Lib
|
||||
|
||||
public AbsolutePath ModListArchive { 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;
|
||||
ModListArchive = archive;
|
||||
@ -166,6 +167,10 @@ namespace Wabbajack.Lib
|
||||
.PDoIndexed(queue, async (idx, group) =>
|
||||
{
|
||||
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);
|
||||
await CopyFile(group.Key.StagedPath, firstDest, true);
|
||||
|
||||
@ -173,11 +178,10 @@ namespace Wabbajack.Lib
|
||||
{
|
||||
await CopyFile(firstDest, OutputFolder.Combine(copy.To), false);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Status("Unstaging files");
|
||||
onFinish();
|
||||
await onFinish();
|
||||
|
||||
// Now patch all the files from this archive
|
||||
await grouping.OfType<PatchedFromArchive>()
|
||||
@ -278,11 +282,11 @@ namespace Wabbajack.Lib
|
||||
var hashResults = await DownloadFolder.EnumerateFiles()
|
||||
.Where(e => e.Extension != Consts.HashFileExtension)
|
||||
.PMap(Queue, async e => (await e.FileHashCachedAsync(), e));
|
||||
HashedArchives = hashResults
|
||||
HashedArchives.SetTo(hashResults
|
||||
.OrderByDescending(e => e.Item2.LastModified)
|
||||
.GroupBy(e => e.Item1)
|
||||
.Select(e => e.First())
|
||||
.ToDictionary(e => e.Item1, e => e.Item2);
|
||||
.Select(e => new KeyValuePair<Hash, AbsolutePath>(e.Item1, e.Item2)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -391,8 +395,13 @@ namespace Wabbajack.Lib
|
||||
|
||||
return await path.FileHashAsync() == d.Hash ? d : null;
|
||||
}))
|
||||
.Where(d => d != null)
|
||||
.Do(d => indexed.Remove(d.To));
|
||||
.Do(d =>
|
||||
{
|
||||
if (d != null)
|
||||
{
|
||||
indexed.Remove(d.To);
|
||||
}
|
||||
});
|
||||
|
||||
UpdateTracker.NextStep("Updating ModList");
|
||||
Utils.Log($"Optimized {ModList.Directives.Count} directives to {indexed.Count} required");
|
||||
|
@ -14,7 +14,7 @@ namespace Wabbajack.Lib
|
||||
return client;
|
||||
}
|
||||
|
||||
public static async Task<Archive> GetModUpgrade(Hash hash)
|
||||
public static async Task<Archive?> GetModUpgrade(Hash hash)
|
||||
{
|
||||
using var response = await GetClient()
|
||||
.GetAsync($"{Consts.WabbajackBuildServerUri}alternative/{hash.ToHex()}");
|
||||
@ -33,7 +33,7 @@ namespace Wabbajack.Lib
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> GetModIni(Hash hash)
|
||||
public static async Task<string?> GetModIni(Hash hash)
|
||||
{
|
||||
var client = new Common.Http.Client();
|
||||
try
|
||||
|
@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
_compiler = compiler;
|
||||
}
|
||||
|
||||
public abstract ValueTask<Directive> Run(RawSourceFile source);
|
||||
public abstract ValueTask<Directive?> Run(RawSourceFile source);
|
||||
public abstract IState GetState();
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
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;
|
||||
|
||||
@ -79,12 +79,12 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
CreateBSA directive;
|
||||
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,
|
||||
TempID = (RelativePath)id,
|
||||
State = bsa.State,
|
||||
FileStates = bsa.Files.Select(f => f.State).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
var result = source.EvolveTo<FromArchive>();
|
||||
|
@ -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>();
|
||||
result.Reason = "No Match in Stack";
|
||||
|
@ -4,7 +4,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public interface ICompilationStep
|
||||
{
|
||||
ValueTask<Directive> Run(RawSourceFile source);
|
||||
ValueTask<Directive?> Run(RawSourceFile source);
|
||||
IState GetState();
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
.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 (_allEnabledMods.Any(mod => source.AbsolutePath.InFolder(mod)))
|
||||
|
@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
_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;
|
||||
var result = source.EvolveTo<IgnoredDirectly>();
|
||||
@ -30,17 +30,13 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
[JsonObject("IgnoreEndsWith")]
|
||||
public class State : IState
|
||||
{
|
||||
public string Postfix { get; set; }
|
||||
|
||||
public State(string postfix)
|
||||
{
|
||||
Postfix = postfix;
|
||||
}
|
||||
|
||||
public State()
|
||||
{
|
||||
}
|
||||
|
||||
public string Postfix { get; set; }
|
||||
|
||||
public ICompilationStep CreateStep(ACompiler compiler)
|
||||
{
|
||||
return new IgnoreEndsWith(compiler, Postfix);
|
||||
|
@ -13,7 +13,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
_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;
|
||||
var i = source.EvolveTo<IgnoredDirectly>();
|
||||
|
@ -15,7 +15,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
_gameFolder = compiler.GamePath;
|
||||
}
|
||||
|
||||
public override async ValueTask<Directive> Run(RawSourceFile source)
|
||||
public override async ValueTask<Directive?> Run(RawSourceFile source)
|
||||
{
|
||||
if (_gameFolderFilesExists)
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
_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;
|
||||
var result = source.EvolveTo<IgnoredDirectly>();
|
||||
@ -30,17 +30,13 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
[JsonObject("IgnorePathContains")]
|
||||
public class State : IState
|
||||
{
|
||||
public State()
|
||||
{
|
||||
}
|
||||
public string Pattern { get; set; }
|
||||
|
||||
public State(string pattern)
|
||||
{
|
||||
Pattern = pattern;
|
||||
}
|
||||
|
||||
public string Pattern { get; set; }
|
||||
|
||||
public ICompilationStep CreateStep(ACompiler compiler)
|
||||
{
|
||||
return new IgnorePathContains(compiler, Pattern);
|
||||
|
@ -17,7 +17,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
_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;
|
||||
var result = source.EvolveTo<IgnoredDirectly>();
|
||||
@ -33,17 +33,13 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
[JsonObject("IgnorePattern")]
|
||||
public class State : IState
|
||||
{
|
||||
public State()
|
||||
{
|
||||
}
|
||||
public string Pattern { get; set; }
|
||||
|
||||
public State(string pattern)
|
||||
{
|
||||
Pattern = pattern;
|
||||
}
|
||||
|
||||
public string Pattern { get; set; }
|
||||
|
||||
public ICompilationStep CreateStep(ACompiler compiler)
|
||||
{
|
||||
return new IgnoreRegex(compiler, Pattern);
|
||||
|
@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
_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))
|
||||
{
|
||||
@ -35,17 +35,13 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
[JsonObject("IgnoreStartsWith")]
|
||||
public class State : IState
|
||||
{
|
||||
public State()
|
||||
{
|
||||
}
|
||||
public string Prefix { get; set; }
|
||||
|
||||
public State(string prefix)
|
||||
{
|
||||
Prefix = prefix;
|
||||
}
|
||||
|
||||
public string Prefix { get; set; }
|
||||
|
||||
public ICompilationStep CreateStep(ACompiler compiler)
|
||||
{
|
||||
return new IgnoreStartsWith(compiler, Prefix);
|
||||
|
@ -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;
|
||||
var result = source.EvolveTo<IgnoredDirectly>();
|
||||
|
@ -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>();
|
||||
inline.SourceDataID = await _compiler.IncludeFile(await source.AbsolutePath.ReadAllBytesAsync());
|
||||
|
@ -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;
|
||||
var result = source.EvolveTo<InlineFile>();
|
||||
|
@ -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 &&
|
||||
source.AbsolutePath.Extension != Consts.ESM) return null;
|
||||
|
@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
_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;
|
||||
var result = source.EvolveTo<InlineFile>();
|
||||
|
@ -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;
|
||||
var e = source.EvolveTo<InlineFile>();
|
||||
|
@ -23,7 +23,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
.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 (_profiles.Any(profile => source.AbsolutePath.InFolder(profile))) return null;
|
||||
|
@ -12,11 +12,11 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
public class IncludePatches : ACompilationStep
|
||||
{
|
||||
private readonly Dictionary<RelativePath, IGrouping<RelativePath, VirtualFile>> _indexed;
|
||||
private VirtualFile _bsa;
|
||||
private VirtualFile? _bsa;
|
||||
private Dictionary<RelativePath, VirtualFile> _indexedByName;
|
||||
private MO2Compiler _mo2Compiler;
|
||||
|
||||
public IncludePatches(ACompiler compiler, VirtualFile constructingFromBSA = null) : base(compiler)
|
||||
public IncludePatches(ACompiler compiler, VirtualFile? constructingFromBSA = null) : base(compiler)
|
||||
{
|
||||
_bsa = constructingFromBSA;
|
||||
_mo2Compiler = (MO2Compiler)compiler;
|
||||
@ -30,9 +30,8 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
.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;
|
||||
RelativePath nameWithoutExt = name;
|
||||
if (name.Extension == Consts.MOHIDDEN)
|
||||
@ -41,7 +40,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
if (!_indexed.TryGetValue(name, out var choices))
|
||||
_indexed.TryGetValue(nameWithoutExt, out choices);
|
||||
|
||||
dynamic modIni = null;
|
||||
dynamic? modIni = null;
|
||||
if (source.AbsolutePath.InFolder(_mo2Compiler.MO2ModsFolder))
|
||||
{
|
||||
if (_bsa == null)
|
||||
@ -55,7 +54,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
|
||||
var installationFile = (RelativePath)modIni?.General?.installationFile;
|
||||
|
||||
VirtualFile found = null;
|
||||
VirtualFile? found = null;
|
||||
|
||||
// Find based on exact file name + ext
|
||||
if (choices != null)
|
||||
|
@ -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>
|
||||
{
|
||||
|
@ -16,7 +16,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
_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;
|
||||
|
||||
@ -33,17 +33,13 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
[JsonObject("IncludeRegex")]
|
||||
public class State : IState
|
||||
{
|
||||
public State()
|
||||
{
|
||||
}
|
||||
public string Pattern { get; set; }
|
||||
|
||||
public State(string pattern)
|
||||
{
|
||||
Pattern = pattern;
|
||||
}
|
||||
|
||||
public string Pattern { get; set; }
|
||||
|
||||
public ICompilationStep CreateStep(ACompiler compiler)
|
||||
{
|
||||
return new IncludeRegex(compiler, Pattern);
|
||||
|
@ -18,7 +18,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
_game = steamGame;
|
||||
}
|
||||
|
||||
public override async ValueTask<Directive> Run(RawSourceFile source)
|
||||
public override async ValueTask<Directive?> Run(RawSourceFile source)
|
||||
{
|
||||
if (!_regex.IsMatch((string)source.Path))
|
||||
return null;
|
||||
@ -31,7 +31,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
if (id == 0)
|
||||
return null;
|
||||
|
||||
SteamWorkshopItem item = null;
|
||||
SteamWorkshopItem? item = null;
|
||||
_game.WorkshopItems.Where(i => i.ItemID == id).Do(i => item = i);
|
||||
if (item == null)
|
||||
return null;
|
||||
|
@ -15,7 +15,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
_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;
|
||||
}
|
||||
@ -25,7 +25,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
return new State();
|
||||
}
|
||||
|
||||
private async Task<Directive> RemapFile(RawSourceFile source)
|
||||
private async Task<Directive?> RemapFile(RawSourceFile source)
|
||||
{
|
||||
var data = await source.AbsolutePath.ReadAllTextAsync();
|
||||
var originalData = data;
|
||||
|
@ -26,7 +26,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
}).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;
|
||||
foreach (var modpath in _includeDirectly)
|
||||
@ -48,17 +48,13 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
[JsonObject("IncludeTaggedMods")]
|
||||
public class State : IState
|
||||
{
|
||||
public State()
|
||||
{
|
||||
}
|
||||
public string Tag { get; set; }
|
||||
|
||||
public State(string tag)
|
||||
{
|
||||
Tag = tag;
|
||||
}
|
||||
|
||||
public string Tag { get; set; }
|
||||
|
||||
public ICompilationStep CreateStep(ACompiler compiler)
|
||||
{
|
||||
return new IncludeTaggedMods(compiler, Tag);
|
||||
|
@ -19,7 +19,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
_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)))
|
||||
return null;
|
||||
|
@ -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
|
||||
/*
|
||||
|
@ -16,7 +16,7 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
_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 gameFile = _mo2Compiler.GamePath.Combine((RelativePath)"Data", filename);
|
||||
|
@ -43,22 +43,22 @@ namespace Wabbajack.Lib
|
||||
/// <summary>
|
||||
/// Archives required by this modlist
|
||||
/// </summary>
|
||||
public List<Archive> Archives;
|
||||
public List<Archive> Archives = new List<Archive>();
|
||||
|
||||
/// <summary>
|
||||
/// Author of the ModList
|
||||
/// </summary>
|
||||
public string Author;
|
||||
public string Author = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Description of the ModList
|
||||
/// </summary>
|
||||
public string Description;
|
||||
public string Description = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Install directives
|
||||
/// </summary>
|
||||
public List<Directive> Directives;
|
||||
public List<Directive> Directives = new List<Directive>();
|
||||
|
||||
/// <summary>
|
||||
/// The game variant to which this game applies
|
||||
@ -78,12 +78,12 @@ namespace Wabbajack.Lib
|
||||
/// <summary>
|
||||
/// Name of the ModList
|
||||
/// </summary>
|
||||
public string Name;
|
||||
public string Name = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// readme path or website
|
||||
/// </summary>
|
||||
public string Readme;
|
||||
public string Readme = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Whether readme is a website
|
||||
@ -93,12 +93,12 @@ namespace Wabbajack.Lib
|
||||
/// <summary>
|
||||
/// The build version of Wabbajack used when compiling the Modlist
|
||||
/// </summary>
|
||||
public Version WabbajackVersion;
|
||||
public Version? WabbajackVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Website of the ModList
|
||||
/// </summary>
|
||||
public Uri Website;
|
||||
public Uri? Website;
|
||||
|
||||
/// <summary>
|
||||
/// The size of all the archives once they're downloaded
|
||||
@ -134,7 +134,7 @@ namespace Wabbajack.Lib
|
||||
|
||||
public class IgnoredDirectly : Directive
|
||||
{
|
||||
public string Reason;
|
||||
public string Reason = string.Empty;
|
||||
}
|
||||
|
||||
public class NoMatch : IgnoredDirectly
|
||||
@ -190,12 +190,12 @@ namespace Wabbajack.Lib
|
||||
[JsonName("FromArchive")]
|
||||
public class FromArchive : Directive
|
||||
{
|
||||
private string _fullPath;
|
||||
private string? _fullPath;
|
||||
|
||||
public HashRelativePath ArchiveHashPath { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public VirtualFile FromFile { get; set; }
|
||||
public VirtualFile? FromFile { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string FullPath => _fullPath ??= string.Join("|", ArchiveHashPath);
|
||||
@ -205,8 +205,17 @@ namespace Wabbajack.Lib
|
||||
public class CreateBSA : Directive
|
||||
{
|
||||
public RelativePath TempID { get; set; }
|
||||
public ArchiveStateObject State { get; set; }
|
||||
public List<FileStateObject> FileStates { get; set; }
|
||||
public ArchiveStateObject State { get; }
|
||||
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")]
|
||||
@ -231,7 +240,7 @@ namespace Wabbajack.Lib
|
||||
public class MergedPatch : Directive
|
||||
{
|
||||
public RelativePath PatchID { get; set; }
|
||||
public List<SourcePatch> Sources { get; set; }
|
||||
public List<SourcePatch> Sources { get; set; } = new List<SourcePatch>();
|
||||
}
|
||||
|
||||
[JsonName("Archive")]
|
||||
@ -245,49 +254,33 @@ namespace Wabbajack.Lib
|
||||
/// <summary>
|
||||
/// Meta INI for the downloaded archive
|
||||
/// </summary>
|
||||
public string Meta { get; set; }
|
||||
public string? Meta { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Human friendly name of this archive
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public long Size { get; set; }
|
||||
|
||||
public AbstractDownloadState State { get; set; }
|
||||
public AbstractDownloadState State { get; }
|
||||
|
||||
public Archive(AbstractDownloadState state)
|
||||
{
|
||||
State = state;
|
||||
}
|
||||
}
|
||||
|
||||
public class IndexedArchive
|
||||
{
|
||||
public dynamic IniData;
|
||||
public string Meta;
|
||||
public string Name;
|
||||
public VirtualFile File { get; internal set; }
|
||||
}
|
||||
public dynamic? IniData;
|
||||
public string Meta = string.Empty;
|
||||
public string Name = string.Empty;
|
||||
public VirtualFile File { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A archive entry
|
||||
/// </summary>
|
||||
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;
|
||||
public IndexedArchive(VirtualFile file)
|
||||
{
|
||||
File = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,19 +11,18 @@ namespace Wabbajack.Lib.Downloaders
|
||||
public interface IMetaState
|
||||
{
|
||||
Uri URL { get; }
|
||||
string Name { get; set; }
|
||||
string Author { get; set; }
|
||||
string Version { get; set; }
|
||||
Uri ImageURL { get; set; }
|
||||
string? Name { get; set; }
|
||||
string? Author { get; set; }
|
||||
string? Version { get; set; }
|
||||
Uri? ImageURL { get; set; }
|
||||
bool IsNSFW { get; set; }
|
||||
string Description { get; set; }
|
||||
string? Description { get; set; }
|
||||
|
||||
Task<bool> LoadMetaData();
|
||||
}
|
||||
|
||||
public abstract class AbstractDownloadState
|
||||
{
|
||||
|
||||
public static List<Type> KnownSubTypes = new List<Type>
|
||||
{
|
||||
typeof(HTTPDownloader.State),
|
||||
@ -48,7 +47,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -67,7 +66,6 @@ namespace Wabbajack.Lib.Downloaders
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this file is allowed to be downloaded via whitelist
|
||||
/// </summary>
|
||||
@ -84,7 +82,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
public async Task<bool> Download(AbsolutePath destination)
|
||||
{
|
||||
destination.Parent.CreateDirectory();
|
||||
return await Download(new Archive {Name = (string)destination.FileName}, destination);
|
||||
return await Download(new Archive(this) {Name = (string)destination.FileName}, destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -95,7 +93,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
|
||||
public abstract IDownloader GetDownloader();
|
||||
|
||||
public abstract string GetManifestURL(Archive a);
|
||||
public abstract string? GetManifestURL(Archive a);
|
||||
public abstract string[] GetMetaIni();
|
||||
}
|
||||
}
|
||||
|
@ -18,10 +18,12 @@ namespace Wabbajack.Lib.Downloaders
|
||||
where TState : AbstractIPS4Downloader<TDownloader, TState>.State<TDownloader>, new()
|
||||
where TDownloader : IDownloader
|
||||
{
|
||||
public override string SiteName { get; }
|
||||
public override Uri SiteURL { get; }
|
||||
protected AbstractIPS4Downloader(Uri loginUri, string encryptedKeyName, string cookieDomain)
|
||||
: 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);
|
||||
|
||||
@ -70,23 +72,23 @@ namespace Wabbajack.Lib.Downloaders
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public class State<TStateDownloader> : AbstractDownloadState, IMetaState where TStateDownloader : IDownloader
|
||||
public class State<TStateDownloader> : AbstractDownloadState, IMetaState
|
||||
where TStateDownloader : IDownloader
|
||||
{
|
||||
public string FullURL { get; set; }
|
||||
public string FullURL { get; set; } = string.Empty;
|
||||
public bool IsAttachment { get; set; }
|
||||
public string FileID { get; set; }
|
||||
|
||||
public string FileName { get; set; }
|
||||
public string FileID { get; set; } = string.Empty;
|
||||
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
|
||||
// from IMetaState
|
||||
public Uri URL => new Uri($"{Site}/files/file/{FileName}");
|
||||
public string Name { get; set; }
|
||||
public string Author { get; set; }
|
||||
public string Version { get; set; }
|
||||
public Uri ImageURL { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Author { get; set; }
|
||||
public string? Version { get; set; }
|
||||
public Uri? ImageURL { 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 string URLPrefix => IsHTTPS ? "https://" : "http://";
|
||||
@ -119,6 +121,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
public override async Task<bool> Download(Archive a, AbsolutePath destination)
|
||||
{
|
||||
await using var stream = await ResolveDownloadStream();
|
||||
if (stream == null) return false;
|
||||
await using (var file = destination.Create())
|
||||
{
|
||||
await stream.CopyToAsync(file);
|
||||
@ -126,7 +129,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<Stream> ResolveDownloadStream()
|
||||
private async Task<Stream?> ResolveDownloadStream()
|
||||
{
|
||||
TOP:
|
||||
string url;
|
||||
@ -236,12 +239,5 @@ namespace Wabbajack.Lib.Downloaders
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected AbstractIPS4Downloader(Uri loginUri, string encryptedKeyName, string cookieDomain) :
|
||||
base(loginUri, encryptedKeyName, cookieDomain, "ips4_member_id")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,17 @@ namespace Wabbajack.Lib.Downloaders
|
||||
private readonly string _encryptedKeyName;
|
||||
private readonly string _cookieDomain;
|
||||
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>
|
||||
/// 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)),
|
||||
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)
|
||||
{
|
||||
@ -120,7 +122,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
Downloader = downloader;
|
||||
}
|
||||
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[]>();
|
||||
public Task<Helpers.Cookie[]> Task => _source.Task;
|
||||
@ -138,6 +140,4 @@ namespace Wabbajack.Lib.Downloaders
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -30,6 +30,15 @@ namespace Wabbajack.Lib.Downloaders
|
||||
public class BethesdaNetDownloader : IUrlDownloader, INeedsLogin
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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);
|
||||
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/"))
|
||||
{
|
||||
var split = url.AbsolutePath.Split('/');
|
||||
var game = split[3];
|
||||
var modId = split[5];
|
||||
return new State {GameName = game, ContentId = modId};
|
||||
return new State(gameName: game, contentId: modId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -65,10 +74,11 @@ namespace Wabbajack.Lib.Downloaders
|
||||
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 gamePath = metadata.GameLocation()?.Combine(metadata.MainExecutable);
|
||||
if (metadata.MainExecutable == null) throw new NotImplementedException();
|
||||
var gamePath = metadata.GameLocation().Combine(metadata.MainExecutable);
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
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")]
|
||||
public class State : AbstractDownloadState
|
||||
{
|
||||
public string GameName { get; set; }
|
||||
public string ContentId { get; set; }
|
||||
|
||||
public string GameName { get; }
|
||||
public string ContentId { get; }
|
||||
|
||||
[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)
|
||||
{
|
||||
@ -143,8 +150,8 @@ namespace Wabbajack.Lib.Downloaders
|
||||
using var got = await client.GetAsync(
|
||||
$"https://content.cdp.bethesda.net/{collected.CDPProductId}/{collected.CDPPropertiesId}/{chunk.sha}");
|
||||
var data = await got.Content.ReadAsByteArrayAsync();
|
||||
if (collected.AESKey != null)
|
||||
AESCTRDecrypt(collected.AESKey, collected.AESIV, data);
|
||||
if (collected.AESKey != null)
|
||||
AESCTRDecrypt(collected.AESKey, collected.AESIV!, data);
|
||||
|
||||
if (chunk.uncompressed_size == chunk.chunk_size)
|
||||
await file.WriteAsync(data, 0, data.Length);
|
||||
@ -198,7 +205,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
|
||||
public override async Task<bool> Verify(Archive archive)
|
||||
{
|
||||
var info = await ResolveDownloadInfo();
|
||||
await ResolveDownloadInfo();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -222,7 +229,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
client.Headers.Add(("x-cdp-app", "UGC SDK"));
|
||||
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-platform","Win/32"));
|
||||
client.Headers.Add(("x-cdp-platform", "Win/32"));
|
||||
|
||||
posted = await client.PostAsync("https://api.bethesda.net/cdp-user/auth",
|
||||
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}");
|
||||
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.CDPProductId = (int)content["cdp_product_id"];
|
||||
info.CDPBranchId = (int)content["cdp_branch_id"]!;
|
||||
info.CDPProductId = (int)content["cdp_product_id"]!;
|
||||
|
||||
client.Headers.Add(("Authorization", $"Token {info.CDPToken}"));
|
||||
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");
|
||||
|
||||
var tree = (await got.Content.ReadAsStringAsync()).FromJsonString<CDPTree>();
|
||||
|
||||
|
||||
got.Dispose();
|
||||
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");
|
||||
|
||||
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.AESIV = props_obj.Value["ex_info_B"].Select(e => (byte)e).Take(16).ToArray();
|
||||
|
||||
return (client, tree, info);
|
||||
}
|
||||
|
||||
|
||||
static int AESCTRDecrypt(byte[] Key, byte[] IV, byte[] Data)
|
||||
{
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CTR/NoPadding");
|
||||
@ -283,27 +290,26 @@ namespace Wabbajack.Lib.Downloaders
|
||||
|
||||
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
|
||||
{
|
||||
public string access_token { get; set; }
|
||||
|
||||
public string access_token { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private class CDPLoginResponse
|
||||
{
|
||||
public string token { get; set; }
|
||||
public string token { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private class CollectedBNetInfo
|
||||
{
|
||||
public byte[] AESKey { get; set; }
|
||||
public byte[] AESIV { get; set; }
|
||||
public string AccessToken { get; set; }
|
||||
public string CDPToken { get; set; }
|
||||
public byte[] AESKey { get; set; } = null!;
|
||||
public byte[] AESIV { get; set; } = null!;
|
||||
public string AccessToken { get; set; } = string.Empty;
|
||||
public string CDPToken { get; set; } = string.Empty;
|
||||
public int CDPBranchId { get; set; }
|
||||
public int CDPProductId { get; set; }
|
||||
public int CDPPropertiesId { get; set; }
|
||||
@ -311,24 +317,24 @@ namespace Wabbajack.Lib.Downloaders
|
||||
|
||||
public class CDPTree
|
||||
{
|
||||
public List<Depot> depot_list { get; set; }
|
||||
public List<Depot> depot_list { get; set; } = null!;
|
||||
|
||||
public class Depot
|
||||
{
|
||||
public List<CDPFile> file_list { get; set; }
|
||||
public List<CDPFile> file_list { get; set; } = null!;
|
||||
|
||||
public class CDPFile
|
||||
{
|
||||
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 int chunk_size { 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; }
|
||||
}
|
||||
}
|
||||
@ -345,7 +351,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
public class RequestBethesdaNetLogin : AUserIntervention
|
||||
{
|
||||
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>();
|
||||
public Task<BethesdaNetData> Task => _source.Task;
|
||||
@ -367,8 +373,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
[JsonName("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>();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
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)
|
||||
{
|
||||
@ -73,7 +73,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
/// </summary>
|
||||
/// <param name="ini"></param>
|
||||
/// <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);
|
||||
}
|
||||
@ -111,13 +111,9 @@ namespace Wabbajack.Lib.Downloaders
|
||||
var patchName = $"{archive.Hash.ToHex()}_{upgrade.Hash.ToHex()}";
|
||||
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,
|
||||
State = new HTTPDownloader.State
|
||||
{
|
||||
Url = $"https://wabbajackcdn.b-cdn.net/updates/{patchName}"
|
||||
}
|
||||
};
|
||||
|
||||
var patchResult = await Download(patchState, patchPath);
|
||||
|
@ -4,7 +4,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public static class DownloaderUtils
|
||||
{
|
||||
public static Uri GetDirectURL(dynamic meta)
|
||||
public static Uri? GetDirectURL(dynamic? meta)
|
||||
{
|
||||
var url = meta?.General?.directURL;
|
||||
if (url == null) return null;
|
||||
|
@ -7,13 +7,13 @@ namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
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;
|
||||
return GetDownloaderState(urlstring);
|
||||
}
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(string url)
|
||||
public AbstractDownloadState? GetDownloaderState(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -29,10 +29,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
|
||||
uri.Query = query.ToString();
|
||||
|
||||
return new HTTPDownloader.State()
|
||||
{
|
||||
Url = uri.ToString().Replace("dropbox.com:443/", "dropbox.com/")
|
||||
};
|
||||
return new HTTPDownloader.State(uri.ToString().Replace("dropbox.com:443/", "dropbox.com/"));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -9,33 +9,30 @@ namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
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 gameFile = (string)archiveINI?.General?.gameFile;
|
||||
var gameName = (string?)archiveINI?.General?.gameName;
|
||||
var gameFile = (string?)archiveINI?.General?.gameFile;
|
||||
|
||||
if (gameFile == null || gameFile == null)
|
||||
if (gameName == null || gameFile == null)
|
||||
return null;
|
||||
|
||||
var game = GameRegistry.GetByFuzzyName(gameName);
|
||||
if (game == null) return null;
|
||||
if (!GameRegistry.TryGetByFuzzyName(gameName, out var game)) return null;
|
||||
|
||||
var path = game.GameLocation();
|
||||
var path = game.TryGetGameLocation();
|
||||
var filePath = path?.Combine(gameFile);
|
||||
|
||||
|
||||
if (!filePath?.Exists ?? false)
|
||||
if (!(filePath?.Exists ?? false))
|
||||
return null;
|
||||
|
||||
var fp = filePath.Value;
|
||||
var hash = await fp.FileHashCachedAsync();
|
||||
|
||||
return new State
|
||||
return new State(game.InstalledVersion)
|
||||
{
|
||||
Game = game.Game,
|
||||
GameFile = (RelativePath)gameFile,
|
||||
Hash = hash,
|
||||
GameVersion = game.InstalledVersion
|
||||
Hash = hash
|
||||
};
|
||||
}
|
||||
|
||||
@ -49,10 +46,15 @@ namespace Wabbajack.Lib.Downloaders
|
||||
public Game Game { get; set; }
|
||||
public RelativePath GameFile { get; set; }
|
||||
public Hash Hash { get; set; }
|
||||
public string GameVersion { get; set; }
|
||||
public string GameVersion { get; }
|
||||
|
||||
public State(string gameVersion)
|
||||
{
|
||||
GameVersion = gameVersion;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
internal AbsolutePath SourcePath => Game.MetaData().GameLocation().Value.Combine(GameFile);
|
||||
internal AbsolutePath SourcePath => Game.MetaData().GameLocation().Combine(GameFile);
|
||||
|
||||
[JsonIgnore]
|
||||
public override object[] PrimaryKey { get => new object[] {Game, GameVersion, GameFile}; }
|
||||
@ -82,7 +84,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
return DownloadDispatcher.GetInstance<GameFileSourceDownloader>();
|
||||
}
|
||||
|
||||
public override string GetManifestURL(Archive a)
|
||||
public override string? GetManifestURL(Archive a)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -10,22 +10,19 @@ namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
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;
|
||||
return GetDownloaderState(url);
|
||||
}
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(string url)
|
||||
public AbstractDownloadState? GetDownloaderState(string url)
|
||||
{
|
||||
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 match = regex.Match(url);
|
||||
return new State
|
||||
{
|
||||
Id = match.ToString()
|
||||
};
|
||||
return new State(match.ToString());
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -38,10 +35,15 @@ namespace Wabbajack.Lib.Downloaders
|
||||
[JsonName("GoogleDriveDownloader")]
|
||||
public class State : AbstractDownloadState
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Id { get; }
|
||||
|
||||
[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)
|
||||
{
|
||||
@ -64,7 +66,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
var regex = new Regex("(?<=/uc\\?export=download&confirm=).*(?=;id=)");
|
||||
var confirm = regex.Match(await response.Content.ReadAsStringAsync());
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -15,29 +15,24 @@ namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
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;
|
||||
return GetDownloaderState(url, archiveINI);
|
||||
}
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(string uri)
|
||||
public AbstractDownloadState? GetDownloaderState(string uri)
|
||||
{
|
||||
return GetDownloaderState(uri, null);
|
||||
}
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(string url, dynamic archiveINI)
|
||||
public AbstractDownloadState? GetDownloaderState(string url, dynamic? archiveINI)
|
||||
{
|
||||
if (url != null)
|
||||
{
|
||||
var tmp = new State
|
||||
{
|
||||
Url = url
|
||||
};
|
||||
var tmp = new State(url);
|
||||
if (archiveINI?.General?.directURLHeaders != null)
|
||||
{
|
||||
tmp.Headers = new List<string>();
|
||||
tmp.Headers.AddRange(archiveINI?.General.directURLHeaders.Split('|'));
|
||||
}
|
||||
return tmp;
|
||||
@ -53,15 +48,20 @@ namespace Wabbajack.Lib.Downloaders
|
||||
[JsonName("HttpDownloader")]
|
||||
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]
|
||||
public Common.Http.Client Client { get; set; }
|
||||
public Common.Http.Client? Client { get; set; }
|
||||
|
||||
[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)
|
||||
{
|
||||
@ -85,19 +85,18 @@ namespace Wabbajack.Lib.Downloaders
|
||||
var client = Client ?? new Common.Http.Client();
|
||||
client.Headers.Add(("User-Agent", Consts.UserAgent));
|
||||
|
||||
if (Headers != null)
|
||||
foreach (var header in Headers)
|
||||
{
|
||||
var idx = header.IndexOf(':');
|
||||
var k = header.Substring(0, idx);
|
||||
var v = header.Substring(idx + 1);
|
||||
client.Headers.Add((k, v));
|
||||
}
|
||||
foreach (var header in Headers)
|
||||
{
|
||||
var idx = header.IndexOf(':');
|
||||
var k = header.Substring(0, idx);
|
||||
var v = header.Substring(idx + 1);
|
||||
client.Headers.Add((k, v));
|
||||
}
|
||||
|
||||
long totalRead = 0;
|
||||
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);
|
||||
TOP:
|
||||
|
||||
@ -177,7 +176,7 @@ TOP:
|
||||
if (read == 0) break;
|
||||
Utils.Status($"Downloading {a.Name}", Percent.FactoryPutInRange(totalRead, contentSize));
|
||||
|
||||
fs.Write(buffer, 0, read);
|
||||
fs!.Write(buffer, 0, read);
|
||||
totalRead += read;
|
||||
}
|
||||
}
|
||||
@ -203,7 +202,7 @@ TOP:
|
||||
|
||||
public override string[] GetMetaIni()
|
||||
{
|
||||
if (Headers != null)
|
||||
if (Headers.Count > 0)
|
||||
return new [] {"[General]",
|
||||
$"directURL={Url}",
|
||||
$"directURLHeaders={string.Join("|", Headers)}"};
|
||||
|
@ -4,12 +4,11 @@ namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public interface IDownloader
|
||||
{
|
||||
Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode = false);
|
||||
Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode = false);
|
||||
|
||||
/// <summary>
|
||||
/// Called before any downloads are inacted by the installer;
|
||||
/// </summary>
|
||||
Task Prepare();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,9 +11,9 @@ namespace Wabbajack.Lib.Downloaders
|
||||
ReactiveCommand<Unit, Unit> ClearLogin { get; }
|
||||
IObservable<bool> IsLoggedIn { get; }
|
||||
string SiteName { get; }
|
||||
IObservable<string> MetaInfo { get; }
|
||||
IObservable<string>? MetaInfo { get; }
|
||||
Uri SiteURL { get; }
|
||||
Uri IconUri { get; }
|
||||
Uri? IconUri { get; }
|
||||
}
|
||||
|
||||
public struct LoginReturnMessage
|
||||
|
@ -2,6 +2,6 @@
|
||||
{
|
||||
public interface IUrlDownloader : IDownloader
|
||||
{
|
||||
AbstractDownloadState GetDownloaderState(string url);
|
||||
AbstractDownloadState? GetDownloaderState(string url);
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,6 @@ namespace Wabbajack.Lib.Downloaders
|
||||
try
|
||||
{
|
||||
authInfos = MegaApiClient.GenerateAuthInfos(username, password.ToNormalString());
|
||||
username = null;
|
||||
password = null;
|
||||
}
|
||||
catch (ApiException e)
|
||||
{
|
||||
@ -72,16 +70,16 @@ namespace Wabbajack.Lib.Downloaders
|
||||
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;
|
||||
return GetDownloaderState(url);
|
||||
}
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(string url)
|
||||
public AbstractDownloadState? GetDownloaderState(string url)
|
||||
{
|
||||
if (url != null && url.StartsWith(Consts.MegaPrefix))
|
||||
return new State { Url = url};
|
||||
return new State(url);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -92,6 +90,11 @@ namespace Wabbajack.Lib.Downloaders
|
||||
[JsonName("MegaDownloader")]
|
||||
public class State : HTTPDownloader.State
|
||||
{
|
||||
public State(string url)
|
||||
: base(url)
|
||||
{
|
||||
}
|
||||
|
||||
private static MegaApiClient MegaApiClient => DownloadDispatcher.GetInstance<MegaDownloader>().MegaApiClient;
|
||||
|
||||
private void MegaLogin()
|
||||
|
@ -18,8 +18,8 @@ namespace Wabbajack.Lib.Downloaders
|
||||
|
||||
class FileEvent
|
||||
{
|
||||
public string FullPath { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string FullPath { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
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;
|
||||
return url != null ? new State { Url = url} : null;
|
||||
return url != null ? new State(url) : null;
|
||||
}
|
||||
|
||||
public async Task Prepare()
|
||||
@ -70,10 +70,15 @@ namespace Wabbajack.Lib.Downloaders
|
||||
[JsonName("ManualDownloader")]
|
||||
public class State : AbstractDownloadState
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string Url { get; }
|
||||
|
||||
[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)
|
||||
{
|
||||
@ -83,7 +88,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
public override async Task<bool> Download(Archive a, AbsolutePath destination)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@ -104,10 +109,10 @@ namespace Wabbajack.Lib.Downloaders
|
||||
|
||||
public override string[] GetMetaIni()
|
||||
{
|
||||
return new [] {
|
||||
return new []
|
||||
{
|
||||
"[General]",
|
||||
$"manualURL={Url}"
|
||||
|
||||
$"manualURL={Url}",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -10,22 +10,24 @@ namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
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);
|
||||
if (url == null || url.Host != "www.mediafire.com") return null;
|
||||
|
||||
return new State
|
||||
{
|
||||
Url = url.ToString()
|
||||
};
|
||||
return new State(url.ToString());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@ -35,6 +37,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
public override async Task<bool> Download(Archive a, AbsolutePath destination)
|
||||
{
|
||||
var result = await Resolve();
|
||||
if (result == null) return false;
|
||||
return await result.Download(a, destination);
|
||||
}
|
||||
|
||||
@ -43,7 +46,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
return await Resolve() != null;
|
||||
}
|
||||
|
||||
private async Task<HTTPDownloader.State> Resolve()
|
||||
private async Task<HTTPDownloader.State?> Resolve()
|
||||
{
|
||||
using (var d = await Driver.Create())
|
||||
{
|
||||
@ -52,10 +55,9 @@ namespace Wabbajack.Lib.Downloaders
|
||||
await Task.Delay(1000);
|
||||
var newURL = await d.GetAttr("a.input", "href");
|
||||
if (newURL == null || !newURL.StartsWith("http")) return null;
|
||||
return new HTTPDownloader.State()
|
||||
return new HTTPDownloader.State(newURL)
|
||||
{
|
||||
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);
|
||||
if (url.Host != "www.mediafire.com") return null;
|
||||
|
||||
return new State
|
||||
{
|
||||
Url = url.ToString()
|
||||
};
|
||||
return new State(url.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,20 +11,17 @@ namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
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;
|
||||
return GetDownloaderState(url);
|
||||
}
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(string url)
|
||||
public AbstractDownloadState? GetDownloaderState(string url)
|
||||
{
|
||||
if (url != null && url.StartsWith("https://www.moddb.com/downloads/start"))
|
||||
{
|
||||
return new State
|
||||
{
|
||||
Url = url
|
||||
};
|
||||
return new State(url);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -37,10 +34,15 @@ namespace Wabbajack.Lib.Downloaders
|
||||
[JsonName("ModDBDownloader")]
|
||||
public class State : AbstractDownloadState
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string Url { get; }
|
||||
|
||||
[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)
|
||||
{
|
||||
@ -56,7 +58,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
try
|
||||
{
|
||||
await new HTTPDownloader.State {Url = url}.Download(a, destination);
|
||||
await new HTTPDownloader.State(url).Download(a, destination);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
|
@ -19,8 +19,8 @@ namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
private bool _prepared;
|
||||
private AsyncLock _lock = new AsyncLock();
|
||||
private UserStatus _status;
|
||||
private NexusApiClient _client;
|
||||
private UserStatus? _status;
|
||||
private NexusApiClient? _client;
|
||||
|
||||
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable("nexusapikey");
|
||||
|
||||
@ -50,9 +50,9 @@ namespace Wabbajack.Lib.Downloaders
|
||||
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)
|
||||
{
|
||||
@ -135,17 +135,17 @@ namespace Wabbajack.Lib.Downloaders
|
||||
[JsonIgnore]
|
||||
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 Uri ImageURL { get; set; }
|
||||
public Uri? ImageURL { get; set; }
|
||||
|
||||
public bool IsNSFW { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
public string? Description { get; set; }
|
||||
|
||||
[JsonProperty("GameName")]
|
||||
[JsonConverter(typeof(Utils.GameConverter))]
|
||||
@ -184,10 +184,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
|
||||
Utils.Log($"Downloading Nexus Archive - {a.Name} - {Game} - {ModID} - {FileID}");
|
||||
|
||||
return await new HTTPDownloader.State
|
||||
{
|
||||
Url = url
|
||||
}.Download(a, destination);
|
||||
return await new HTTPDownloader.State(url).Download(a, destination);
|
||||
}
|
||||
|
||||
public override async Task<bool> Verify(Archive a)
|
||||
|
@ -14,20 +14,21 @@ namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
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 steamID = archiveINI?.General?.steamID;
|
||||
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,
|
||||
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()
|
||||
@ -41,8 +42,14 @@ namespace Wabbajack.Lib.Downloaders
|
||||
|
||||
public class State : AbstractDownloadState
|
||||
{
|
||||
public SteamWorkshopItem Item { get; set; }
|
||||
public override object[] PrimaryKey { get => new object[] {Item.Game, Item.ItemID}; }
|
||||
public SteamWorkshopItem Item { get; }
|
||||
|
||||
public override object[] PrimaryKey => new object[] { Item.Game, Item.ItemID };
|
||||
|
||||
public State(SteamWorkshopItem item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
public override bool IsWhitelisted(ServerWhitelist whitelist)
|
||||
{
|
||||
|
@ -5,7 +5,7 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders
|
||||
{
|
||||
public class BethesdaNetInferencer : IUrlInferencer
|
||||
{
|
||||
public async Task<AbstractDownloadState> Infer(Uri uri)
|
||||
public async Task<AbstractDownloadState?> Infer(Uri uri)
|
||||
{
|
||||
return BethesdaNetDownloader.StateFromUrl(uri);
|
||||
}
|
||||
|
@ -5,6 +5,6 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders
|
||||
{
|
||||
public interface IUrlInferencer
|
||||
{
|
||||
Task<AbstractDownloadState> Infer(Uri uri);
|
||||
Task<AbstractDownloadState?> Infer(Uri uri);
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,9 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders
|
||||
{
|
||||
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;
|
||||
|
||||
var client = new YoutubeClient(Common.Http.ClientFactory.Client);
|
||||
@ -24,13 +23,13 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders
|
||||
.Select(line =>
|
||||
{
|
||||
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))
|
||||
return (s1, string.Join(" ", segments.Skip(1)));
|
||||
if (TryParseEx(Enumerable.Last(segments), out var s2))
|
||||
return (s2, string.Join(" ", Utils.ButLast(segments)));
|
||||
return (TimeSpan.Zero, null);
|
||||
return (TimeSpan.Zero, string.Empty);
|
||||
})
|
||||
.Where(t => t.Item2 != null)
|
||||
.ToList();
|
||||
|
@ -21,10 +21,10 @@ namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
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 state = (State)UriToState(directURL);
|
||||
var state = UriToState(directURL) as State;
|
||||
if (state == null) return state;
|
||||
|
||||
var idx = 0;
|
||||
@ -45,7 +45,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
return state;
|
||||
}
|
||||
|
||||
internal static AbstractDownloadState UriToState(Uri directURL)
|
||||
internal static AbstractDownloadState? UriToState(Uri directURL)
|
||||
{
|
||||
if (directURL == null || !directURL.Host.EndsWith("youtube.com"))
|
||||
{
|
||||
@ -53,7 +53,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
}
|
||||
|
||||
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()
|
||||
@ -63,13 +63,18 @@ namespace Wabbajack.Lib.Downloaders
|
||||
[JsonName("YouTubeDownloader")]
|
||||
public class State : AbstractDownloadState
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string Key { get; }
|
||||
|
||||
public List<Track> Tracks { get; set; } = new List<Track>();
|
||||
|
||||
[JsonIgnore]
|
||||
public override object[] PrimaryKey => new object[] {Key};
|
||||
|
||||
public State(string key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
[JsonName("YouTubeTrack")]
|
||||
public class Track
|
||||
{
|
||||
@ -79,8 +84,8 @@ namespace Wabbajack.Lib.Downloaders
|
||||
WAV
|
||||
}
|
||||
public FormatEnum Format { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public TimeSpan Start { get; set; }
|
||||
|
||||
|
@ -12,6 +12,5 @@ namespace Wabbajack.Lib.Exceptions
|
||||
Code = code;
|
||||
Reason = reason;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ namespace Wabbajack.Lib.FileUploader
|
||||
{
|
||||
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;
|
||||
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 long BLOCK_SIZE = (long)1024 * 1024 * 2;
|
||||
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>();
|
||||
Task.Run(async () =>
|
||||
@ -123,7 +123,7 @@ namespace Wabbajack.Lib.FileUploader
|
||||
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();
|
||||
client.Headers.Add(("X-API-KEY", await GetAPIKey(apiKey)));
|
||||
|
@ -78,10 +78,10 @@ namespace Wabbajack.Lib.LibCefHelpers
|
||||
[JsonName("HttpCookie")]
|
||||
public class Cookie
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
public string Domain { get; set; }
|
||||
public string Path { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Value { get; set; } = string.Empty;
|
||||
public string Domain { get; set; } = string.Empty;
|
||||
public string Path { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
|
@ -25,7 +25,6 @@ namespace Wabbajack.Lib
|
||||
{
|
||||
public class MO2Compiler : ACompiler
|
||||
{
|
||||
|
||||
private AbsolutePath _mo2DownloadsFolder;
|
||||
|
||||
public AbsolutePath MO2Folder;
|
||||
@ -38,7 +37,7 @@ namespace Wabbajack.Lib
|
||||
|
||||
public override AbsolutePath GamePath { get; }
|
||||
|
||||
public GameMetaData CompilingGame { get; set; }
|
||||
public GameMetaData CompilingGame { get; }
|
||||
|
||||
public override AbsolutePath ModListOutputFolder => ((RelativePath)"output_folder").RelativeToEntryPoint();
|
||||
|
||||
@ -48,7 +47,19 @@ namespace Wabbajack.Lib
|
||||
Consts.LocalAppDataPath.Combine(
|
||||
$"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)
|
||||
: base(steps: 20)
|
||||
{
|
||||
MO2Folder = mo2Folder;
|
||||
MO2Profile = mo2Profile;
|
||||
@ -59,8 +70,6 @@ namespace Wabbajack.Lib
|
||||
ModListOutputFile = outputFile;
|
||||
}
|
||||
|
||||
public dynamic MO2Ini { get; }
|
||||
|
||||
public AbsolutePath MO2DownloadsFolder
|
||||
{
|
||||
get
|
||||
@ -75,20 +84,10 @@ namespace Wabbajack.Lib
|
||||
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)
|
||||
{
|
||||
if (cancel.IsCancellationRequested) return false;
|
||||
ConfigureProcessor(20, ConstructDynamicNumThreads(await RecommendQueueSize()));
|
||||
Queue.SetActiveThreadsObservable(ConstructDynamicNumThreads(await RecommendQueueSize()));
|
||||
UpdateTracker.Reset();
|
||||
UpdateTracker.NextStep("Gathering information");
|
||||
Info("Looking for other profiles");
|
||||
@ -126,6 +125,11 @@ namespace Wabbajack.Lib
|
||||
|
||||
if (lootPath.Exists)
|
||||
{
|
||||
if (CompilingGame.MO2Name == null)
|
||||
{
|
||||
throw new ArgumentException("Compiling game had no MO2 name specified.");
|
||||
}
|
||||
|
||||
var lootGameDirs = new []
|
||||
{
|
||||
CompilingGame.MO2Name, // most of the games use the MO2 name
|
||||
@ -170,9 +174,8 @@ namespace Wabbajack.Lib
|
||||
|
||||
IndexedArchives = (await MO2DownloadsFolder.EnumerateFiles()
|
||||
.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,
|
||||
IniData = f.WithExtension(Consts.MetaFileExtension).LoadIniFile(),
|
||||
Meta = await f.WithExtension(Consts.MetaFileExtension).ReadAllTextAsync()
|
||||
@ -216,10 +219,9 @@ namespace Wabbajack.Lib
|
||||
.GroupBy(f => f.Hash)
|
||||
.ToDictionary(f => f.Key, f => f.AsEnumerable());
|
||||
|
||||
AllFiles = mo2Files.Concat(gameFiles)
|
||||
AllFiles.SetTo(mo2Files.Concat(gameFiles)
|
||||
.Concat(lootFiles)
|
||||
.DistinctBy(f => f.Path)
|
||||
.ToList();
|
||||
.DistinctBy(f => f.Path));
|
||||
|
||||
Info($"Found {AllFiles.Count} files to build into mod list");
|
||||
|
||||
@ -239,13 +241,10 @@ namespace Wabbajack.Lib
|
||||
Error($"Found {dups.Count} duplicates, exiting");
|
||||
}
|
||||
|
||||
ExtraFiles = new ConcurrentBag<Directive>();
|
||||
|
||||
|
||||
if (cancel.IsCancellationRequested) return false;
|
||||
UpdateTracker.NextStep("Loading INIs");
|
||||
|
||||
ModInis = MO2Folder.Combine(Consts.MO2ModFolderName)
|
||||
ModInis.SetTo(MO2Folder.Combine(Consts.MO2ModFolderName)
|
||||
.EnumerateDirectories()
|
||||
.Select(f =>
|
||||
{
|
||||
@ -254,7 +253,7 @@ namespace Wabbajack.Lib
|
||||
return metaPath.Exists ? (mod_name: f, metaPath.LoadIniFile()) : 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;
|
||||
var stack = MakeStack();
|
||||
@ -270,7 +269,7 @@ namespace Wabbajack.Lib
|
||||
PrintNoMatches(noMatch);
|
||||
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");
|
||||
|
||||
@ -337,7 +336,7 @@ namespace Wabbajack.Lib
|
||||
{
|
||||
return a;
|
||||
}
|
||||
})).Where(a => a != null).ToHashSet();
|
||||
})).NotNull().ToHashSet();
|
||||
|
||||
if (remove.Count == 0)
|
||||
return;
|
||||
@ -389,7 +388,6 @@ namespace Wabbajack.Lib
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private async Task IncludeArchiveMetadata()
|
||||
{
|
||||
Utils.Log($"Including {SelectedArchives.Count} .meta files for downloads");
|
||||
@ -411,13 +409,12 @@ namespace Wabbajack.Lib
|
||||
/// </summary>
|
||||
private void ResetMembers()
|
||||
{
|
||||
AllFiles = null;
|
||||
InstallDirectives = null;
|
||||
SelectedArchives = null;
|
||||
ExtraFiles = null;
|
||||
AllFiles = new List<RawSourceFile>();
|
||||
InstallDirectives = new List<Directive>();
|
||||
SelectedArchives = new List<Archive>();
|
||||
ExtraFiles = new ConcurrentBag<Directive>();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fills in the Patch fields in files that require them
|
||||
/// </summary>
|
||||
@ -450,7 +447,7 @@ namespace Wabbajack.Lib
|
||||
private async Task BuildArchivePatches(Hash archiveSha, IEnumerable<PatchedFromArchive> group,
|
||||
Dictionary<RelativePath, AbsolutePath> absolutePaths)
|
||||
{
|
||||
using var files = await VFS.StageWith(@group.Select(g => VFS.Index.FileForArchiveHashPath(g.ArchiveHashPath)));
|
||||
await using var files = await VFS.StageWith(@group.Select(g => VFS.Index.FileForArchiveHashPath(g.ArchiveHashPath)));
|
||||
var byPath = files.GroupBy(f => string.Join("|", f.FilesInFullPath.Skip(1).Select(i => i.Name)))
|
||||
.ToDictionary(f => f.Key, f => f.First());
|
||||
// Now Create the patches
|
||||
@ -487,8 +484,7 @@ namespace Wabbajack.Lib
|
||||
return returnStream;
|
||||
}
|
||||
|
||||
Error($"Couldn't load data for {to}");
|
||||
return null;
|
||||
throw new ArgumentException($"Couldn't load data for {to}");
|
||||
}
|
||||
|
||||
public override IEnumerable<ICompilationStep> GetStack()
|
||||
@ -567,12 +563,5 @@ namespace Wabbajack.Lib
|
||||
new DropAll(this)
|
||||
};
|
||||
}
|
||||
|
||||
public class IndexedFileMatch
|
||||
{
|
||||
public IndexedArchive Archive;
|
||||
public IndexedArchiveEntry Entry;
|
||||
public DateTime LastModified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,14 +32,18 @@ namespace Wabbajack.Lib
|
||||
|
||||
public AbsolutePath? GameFolder { get; set; }
|
||||
|
||||
public GameMetaData Game { get; }
|
||||
|
||||
public MO2Installer(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters parameters)
|
||||
: base(
|
||||
archive: archive,
|
||||
modList: modList,
|
||||
outputFolder: outputFolder,
|
||||
downloadFolder: downloadFolder,
|
||||
parameters: parameters)
|
||||
parameters: parameters,
|
||||
steps: 20)
|
||||
{
|
||||
Game = ModList.GameType.MetaData();
|
||||
}
|
||||
|
||||
protected override async Task<bool> _Begin(CancellationToken cancel)
|
||||
@ -48,28 +52,27 @@ namespace Wabbajack.Lib
|
||||
var metric = Metrics.Send(Metrics.BeginInstall, ModList.Name);
|
||||
Utils.Log("Configuring Processor");
|
||||
|
||||
ConfigureProcessor(20, ConstructDynamicNumThreads(await RecommendQueueSize()));
|
||||
var game = ModList.GameType.MetaData();
|
||||
Queue.SetActiveThreadsObservable(ConstructDynamicNumThreads(await RecommendQueueSize()));
|
||||
|
||||
if (GameFolder == null)
|
||||
GameFolder = game.GameLocation();
|
||||
GameFolder = Game.TryGetGameLocation();
|
||||
|
||||
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)
|
||||
{
|
||||
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?",
|
||||
$"Could not locate {game.HumanFriendlyGameName}"))
|
||||
$"Could not locate {Game.HumanFriendlyGameName}"))
|
||||
.Task;
|
||||
}
|
||||
else
|
||||
{
|
||||
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",
|
||||
$"Could not locate {game.HumanFriendlyGameName}"))
|
||||
$"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}"))
|
||||
.Task;
|
||||
}
|
||||
|
||||
@ -169,7 +172,6 @@ namespace Wabbajack.Lib
|
||||
|
||||
private void CreateOutputMods()
|
||||
{
|
||||
|
||||
OutputFolder.Combine("profiles")
|
||||
.EnumerateFiles(true)
|
||||
.Where(f => f.FileName == Consts.SettingsIni)
|
||||
@ -235,7 +237,7 @@ namespace Wabbajack.Lib
|
||||
foreach (var esm in ModList.Directives.OfType<CleanedESM>().ToList())
|
||||
{
|
||||
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}");
|
||||
var hash = gameFile.FileHash();
|
||||
if (hash != esm.SourceESMHash)
|
||||
@ -310,7 +312,7 @@ namespace Wabbajack.Lib
|
||||
private async Task GenerateCleanedESM(CleanedESM directive)
|
||||
{
|
||||
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}");
|
||||
if (!gameFile.Exists) throw new InvalidDataException($"Missing {filename} at {gameFile}");
|
||||
Status($"Hashing game version of {filename}");
|
||||
@ -329,6 +331,10 @@ namespace Wabbajack.Lib
|
||||
|
||||
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};
|
||||
foreach (var file in OutputFolder.Combine("profiles").EnumerateFiles()
|
||||
.Where(f => ((string)f.FileName).EndsWith("refs.ini")))
|
||||
@ -375,8 +381,8 @@ namespace Wabbajack.Lib
|
||||
{
|
||||
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_DOUBLE_BACK, ((string)GameFolder).Replace("\\", "\\\\"));
|
||||
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_FORWARD, ((string)GameFolder).Replace("\\", "/"));
|
||||
|
||||
data = data.Replace(Consts.MO2_PATH_MAGIC_BACK, (string)OutputFolder);
|
||||
|
@ -41,12 +41,11 @@ namespace Wabbajack.Lib
|
||||
InstallSize = modlist.InstallSize;
|
||||
|
||||
// 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,
|
||||
Name = a.Name,
|
||||
Size = a.Size,
|
||||
State = a.State
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
@ -13,13 +13,13 @@ namespace Wabbajack.Lib.ModListRegistry
|
||||
public class ModlistMetadata
|
||||
{
|
||||
[JsonProperty("title")]
|
||||
public string Title { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("description")]
|
||||
public string Description { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("author")]
|
||||
public string Author { get; set; }
|
||||
public string Author { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("game")]
|
||||
public Game Game { get; set; }
|
||||
@ -33,7 +33,7 @@ namespace Wabbajack.Lib.ModListRegistry
|
||||
public LinksObject Links { get; set; } = new LinksObject();
|
||||
|
||||
[JsonProperty("download_metadata")]
|
||||
public DownloadMetadata DownloadMetadata { get; set; }
|
||||
public DownloadMetadata? DownloadMetadata { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public ModListSummary ValidationSummary { get; set; } = new ModListSummary();
|
||||
@ -42,22 +42,18 @@ namespace Wabbajack.Lib.ModListRegistry
|
||||
public class LinksObject
|
||||
{
|
||||
[JsonProperty("image")]
|
||||
public string ImageUri { get; set; }
|
||||
public string ImageUri { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("readme")]
|
||||
public string Readme { get; set; }
|
||||
public string Readme { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("download")]
|
||||
public string Download { get; set; }
|
||||
|
||||
public string Download { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("machineURL")]
|
||||
public string MachineURL { get; set; }
|
||||
public string MachineURL { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static async Task<List<ModlistMetadata>> LoadFromGithub()
|
||||
{
|
||||
var client = new Common.Http.Client();
|
||||
@ -103,18 +99,17 @@ namespace Wabbajack.Lib.ModListRegistry
|
||||
public long SizeOfArchives { get; set; }
|
||||
public long NumberOfInstalledFiles { get; set; }
|
||||
public long SizeOfInstalledFiles { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[JsonName("ModListSummary")]
|
||||
public class ModListSummary
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("machineURL")]
|
||||
public string MachineURL { get; set; }
|
||||
|
||||
public string MachineURL { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("checked")]
|
||||
public DateTime Checked { get; set; }
|
||||
[JsonProperty("failed")]
|
||||
|
@ -4,68 +4,68 @@ namespace Wabbajack.Lib.NexusApi
|
||||
{
|
||||
public class UserStatus
|
||||
{
|
||||
public string email;
|
||||
public string email = string.Empty;
|
||||
public bool is_premium;
|
||||
public bool is_supporter;
|
||||
public string key;
|
||||
public string name;
|
||||
public string profile_url;
|
||||
public string user_id;
|
||||
public string key = string.Empty;
|
||||
public string name = string.Empty;
|
||||
public string profile_url = string.Empty;
|
||||
public string user_id = string.Empty;
|
||||
}
|
||||
|
||||
public class NexusFileInfo
|
||||
{
|
||||
public long category_id { get; set; }
|
||||
public string category_name { get; set; }
|
||||
public string changelog_html { get; set; }
|
||||
public string description { get; set; }
|
||||
public string external_virus_scan_url { get; set; }
|
||||
public string category_name { get; set; } = string.Empty;
|
||||
public string changelog_html { get; set; } = string.Empty;
|
||||
public string description { get; set; } = string.Empty;
|
||||
public string external_virus_scan_url { get; set; } = string.Empty;
|
||||
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 string mod_version { get; set; }
|
||||
public string name { get; set; }
|
||||
public string mod_version { get; set; } = string.Empty;
|
||||
public string name { get; set; } = string.Empty;
|
||||
public long size { get; set; }
|
||||
public long size_kb { get; set; }
|
||||
public DateTime uploaded_time { get; set; }
|
||||
public long uploaded_timestamp { get; set; }
|
||||
public string version { get; set; }
|
||||
public string version { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class ModInfo
|
||||
{
|
||||
public string name { get; set; }
|
||||
public string summary { get; set; }
|
||||
public string description { get; set; }
|
||||
public Uri picture_url { get; set; }
|
||||
public string mod_id { get; set; }
|
||||
public string name { get; set; } = string.Empty;
|
||||
public string summary { get; set; } = string.Empty;
|
||||
public string description { get; set; } = string.Empty;
|
||||
public Uri picture_url { get; set; } = new Uri(string.Empty);
|
||||
public string mod_id { get; set; } = string.Empty;
|
||||
public long game_id { get; set; }
|
||||
public bool allow_rating { get; set; }
|
||||
public string domain_name { get; set; }
|
||||
public string domain_name { get; set; } = string.Empty;
|
||||
public long category_id { get; set; }
|
||||
public string version { get; set; }
|
||||
public string version { get; set; } = string.Empty;
|
||||
public long endorsement_count { get; set; }
|
||||
public long created_timestamp { get; set; }
|
||||
public DateTime created_time { get; set; }
|
||||
public DateTime created_time { get; set; }
|
||||
public long updated_timestamp { get; set; }
|
||||
public DateTime updated_time { get; set; }
|
||||
public string author { get; set; }
|
||||
public string uploaded_by { get; set; }
|
||||
public Uri uploaded_users_profile_url { get; set; }
|
||||
public string author { get; set; } = string.Empty;
|
||||
public string uploaded_by { get; set; } = string.Empty;
|
||||
public Uri uploaded_users_profile_url { get; set; } = new Uri(string.Empty);
|
||||
public bool contains_adult_content { get; set; }
|
||||
public string status { get; set; }
|
||||
public string status { get; set; } = string.Empty;
|
||||
public bool available { get; set; } = true;
|
||||
}
|
||||
|
||||
public class MD5Response
|
||||
{
|
||||
public ModInfo mod;
|
||||
public NexusFileInfo file_details;
|
||||
public ModInfo? mod;
|
||||
public NexusFileInfo? file_details;
|
||||
}
|
||||
|
||||
public class EndorsementResponse
|
||||
{
|
||||
public string message;
|
||||
public string status;
|
||||
public string message = string.Empty;
|
||||
public string status = string.Empty;
|
||||
}
|
||||
}
|
||||
|
@ -27,12 +27,11 @@ namespace Wabbajack.Lib.NexusApi
|
||||
|
||||
#region Authentication
|
||||
|
||||
public string ApiKey { get; }
|
||||
public string? ApiKey { get; }
|
||||
|
||||
public bool IsAuthenticated => ApiKey != null;
|
||||
|
||||
private Task<UserStatus> _userStatus;
|
||||
|
||||
private Task<UserStatus>? _userStatus;
|
||||
public Task<UserStatus> UserStatus
|
||||
{
|
||||
get
|
||||
@ -214,7 +213,7 @@ namespace Wabbajack.Lib.NexusApi
|
||||
|
||||
#endregion
|
||||
|
||||
private NexusApiClient(string apiKey = null)
|
||||
private NexusApiClient(string? apiKey = null)
|
||||
{
|
||||
ApiKey = apiKey;
|
||||
|
||||
@ -230,9 +229,9 @@ namespace Wabbajack.Lib.NexusApi
|
||||
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);
|
||||
}
|
||||
|
||||
@ -316,9 +315,10 @@ namespace Wabbajack.Lib.NexusApi
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@ -350,18 +350,18 @@ namespace Wabbajack.Lib.NexusApi
|
||||
|
||||
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
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_localCacheDir == null)
|
||||
_localCacheDir = Environment.GetEnvironmentVariable("NEXUSCACHEDIR");
|
||||
if (_localCacheDir == null)
|
||||
throw new ArgumentNullException($"Enviornment variable could not be located: NEXUSCACHEDIR");
|
||||
return _localCacheDir;
|
||||
}
|
||||
set => _localCacheDir = value;
|
||||
|
@ -19,15 +19,10 @@ namespace Wabbajack.Lib.NexusApi
|
||||
|
||||
public static string FixupSummary(string argSummary)
|
||||
{
|
||||
if (argSummary != null)
|
||||
{
|
||||
return argSummary.Replace("'", "'")
|
||||
.Replace("<br/>", "\n\n")
|
||||
.Replace("<br />", "\n\n")
|
||||
.Replace("!", "!");
|
||||
}
|
||||
|
||||
return argSummary;
|
||||
return argSummary.Replace("'", "'")
|
||||
.Replace("<br/>", "\n\n")
|
||||
.Replace("<br />", "\n\n")
|
||||
.Replace("!", "!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,7 @@ namespace Wabbajack.Lib.NexusApi
|
||||
{
|
||||
var parts = link.Uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var foundGame = GameRegistry.GetByFuzzyName(parts[0]);
|
||||
if (foundGame == null)
|
||||
if (!GameRegistry.TryGetByFuzzyName(parts[0], out var foundGame))
|
||||
{
|
||||
game = Game.Oblivion;
|
||||
modId = 0;
|
||||
@ -65,7 +64,8 @@ namespace Wabbajack.Lib.NexusApi
|
||||
}
|
||||
|
||||
return null;
|
||||
}).Where(v => v != null);
|
||||
})
|
||||
.NotNull();
|
||||
}
|
||||
|
||||
|
||||
|
@ -10,7 +10,7 @@ namespace Wabbajack.Lib.NexusApi
|
||||
public class RequestNexusAuthorization : AUserIntervention
|
||||
{
|
||||
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>();
|
||||
public Task<string> Task => _source.Task;
|
||||
|
@ -10,7 +10,7 @@ namespace Wabbajack.Lib
|
||||
public class ConfirmUpdateOfExistingInstall : ConfirmationIntervention
|
||||
{
|
||||
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?";
|
||||
|
||||
|
@ -9,9 +9,9 @@ namespace Wabbajack.Lib
|
||||
public class ManuallyDownloadFile : AUserIntervention
|
||||
{
|
||||
public ManualDownloader.State State { get; }
|
||||
public override string ShortDescription { get; }
|
||||
public override string ExtendedDescription { get; }
|
||||
|
||||
public override string ShortDescription { get; } = string.Empty;
|
||||
public override string ExtendedDescription { get; } = string.Empty;
|
||||
|
||||
private TaskCompletionSource<(Uri, Common.Http.Client)> _tcs = new TaskCompletionSource<(Uri, Common.Http.Client)>();
|
||||
public Task<(Uri, Common.Http.Client)> Task => _tcs.Task;
|
||||
|
||||
|
@ -9,9 +9,9 @@ namespace Wabbajack.Lib
|
||||
public class ManuallyDownloadNexusFile : AUserIntervention
|
||||
{
|
||||
public NexusDownloader.State State { get; }
|
||||
public override string ShortDescription { get; }
|
||||
public override string ExtendedDescription { get; }
|
||||
|
||||
public override string ShortDescription { get; } = string.Empty;
|
||||
public override string ExtendedDescription { get; } = string.Empty;
|
||||
|
||||
private TaskCompletionSource<Uri> _tcs = new TaskCompletionSource<Uri>();
|
||||
public Task<Uri> Task => _tcs.Task;
|
||||
|
||||
|
@ -14,9 +14,9 @@ namespace Wabbajack.Lib
|
||||
public int ScreenWidth { get; set; }
|
||||
public long VideoMemorySize { get; set; }
|
||||
public long SystemMemorySize { get; set; }
|
||||
|
||||
public Version WindowsVersion { get; set; }
|
||||
|
||||
|
||||
public Version WindowsVersion { get; set; } = Environment.OSVersion.Version;
|
||||
|
||||
/// <summary>
|
||||
/// Value used in LE ENBs for VideoMemorySizeMb
|
||||
/// </summary>
|
||||
|
@ -10,33 +10,32 @@ namespace Wabbajack.Lib.Validation
|
||||
public bool? CanUseInOtherGames { get; set; }
|
||||
}
|
||||
|
||||
public class Author
|
||||
{
|
||||
public Permissions Permissions { get; set; }
|
||||
public Dictionary<string, Game> Games;
|
||||
}
|
||||
//public class Author
|
||||
//{
|
||||
// public Permissions Permissions { get; set; }
|
||||
// public Dictionary<string, Game> Games;
|
||||
//}
|
||||
|
||||
public class Game
|
||||
{
|
||||
public Permissions Permissions;
|
||||
public Dictionary<string, Mod> Mods;
|
||||
}
|
||||
//public class Game
|
||||
//{
|
||||
// public Permissions Permissions;
|
||||
// public Dictionary<string, Mod> Mods;
|
||||
//}
|
||||
|
||||
public class Mod
|
||||
{
|
||||
public Permissions Permissions;
|
||||
public Dictionary<string, File> Files;
|
||||
}
|
||||
|
||||
public class File
|
||||
{
|
||||
public Permissions Permissions;
|
||||
}
|
||||
//public class Mod
|
||||
//{
|
||||
// public Permissions Permissions;
|
||||
// public Dictionary<string, File> Files;
|
||||
//}
|
||||
|
||||
//public class File
|
||||
//{
|
||||
// public Permissions Permissions;
|
||||
//}
|
||||
|
||||
public class ServerWhitelist
|
||||
{
|
||||
public List<string> GoogleIDs;
|
||||
public List<string> AllowedPrefixes;
|
||||
public List<string> GoogleIDs = new List<string>();
|
||||
public List<string> AllowedPrefixes = new List<string>();
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ namespace Wabbajack.Lib
|
||||
protected void RaiseAndSetIfChanged<T>(
|
||||
ref T item,
|
||||
T newItem,
|
||||
[CallerMemberName] string propertyName = null)
|
||||
[CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(item, newItem)) return;
|
||||
item = newItem;
|
||||
|
@ -11,6 +11,7 @@ using Directory = Alphaleonis.Win32.Filesystem.Directory;
|
||||
using DirectoryInfo = Alphaleonis.Win32.Filesystem.DirectoryInfo;
|
||||
using File = Alphaleonis.Win32.Filesystem.File;
|
||||
using Path = Alphaleonis.Win32.Filesystem.Path;
|
||||
#nullable disable
|
||||
|
||||
namespace Wabbajack.Lib
|
||||
{
|
||||
@ -26,7 +27,8 @@ namespace Wabbajack.Lib
|
||||
modList: modList,
|
||||
outputFolder: outputFolder,
|
||||
downloadFolder: downloadFolder,
|
||||
parameters: parameters)
|
||||
parameters: parameters,
|
||||
steps: 10)
|
||||
{
|
||||
#if DEBUG
|
||||
// TODO: only for testing
|
||||
@ -51,7 +53,7 @@ namespace Wabbajack.Lib
|
||||
}
|
||||
|
||||
if (cancel.IsCancellationRequested) return false;
|
||||
ConfigureProcessor(10, ConstructDynamicNumThreads(await RecommendQueueSize()));
|
||||
Queue.SetActiveThreadsObservable(ConstructDynamicNumThreads(await RecommendQueueSize()));
|
||||
DownloadFolder.CreateDirectory();
|
||||
|
||||
if (cancel.IsCancellationRequested) return false;
|
||||
@ -122,7 +124,7 @@ namespace Wabbajack.Lib
|
||||
|
||||
var manualFilesDir = OutputFolder.Combine(Consts.ManualGameFilesDir);
|
||||
|
||||
var gameFolder = GameInfo.GameLocation();
|
||||
var gameFolder = GameInfo.TryGetGameLocation();
|
||||
|
||||
Info($"Copying files from {manualFilesDir} " +
|
||||
$"to the game folder at {gameFolder}");
|
||||
|
@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<Platforms>x64</Platforms>
|
||||
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<Platforms>x64</Platforms>
|
||||
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CefSharp.Common">
|
||||
|
@ -13,8 +13,8 @@ namespace Wabbajack.Lib.WebAutomation
|
||||
{
|
||||
public class CefSharpWrapper : IWebDriver
|
||||
{
|
||||
private IWebBrowser _browser;
|
||||
public Action<Uri> DownloadHandler { get; set; }
|
||||
private readonly IWebBrowser _browser;
|
||||
public Action<Uri>? DownloadHandler { get; set; }
|
||||
public CefSharpWrapper(IWebBrowser browser)
|
||||
{
|
||||
_browser = browser;
|
||||
@ -24,7 +24,7 @@ namespace Wabbajack.Lib.WebAutomation
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
EventHandler<LoadingStateChangedEventArgs> handler = null;
|
||||
EventHandler<LoadingStateChangedEventArgs>? handler = null;
|
||||
handler = (sender, e) =>
|
||||
{
|
||||
if (!e.IsLoading)
|
||||
@ -68,7 +68,7 @@ namespace Wabbajack.Lib.WebAutomation
|
||||
|
||||
public class PopupBlocker : ILifeSpanHandler
|
||||
{
|
||||
private CefSharpWrapper _wrapper;
|
||||
private readonly CefSharpWrapper _wrapper;
|
||||
|
||||
public PopupBlocker(CefSharpWrapper cefSharpWrapper)
|
||||
{
|
||||
@ -77,7 +77,7 @@ namespace Wabbajack.Lib.WebAutomation
|
||||
|
||||
public bool OnBeforePopup(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl,
|
||||
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
|
||||
newBrowser = null;
|
||||
@ -86,7 +86,6 @@ namespace Wabbajack.Lib.WebAutomation
|
||||
|
||||
public void OnAfterCreated(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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +110,7 @@ namespace Wabbajack.Lib.WebAutomation
|
||||
public void OnBeforeDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem,
|
||||
IBeforeDownloadCallback callback)
|
||||
{
|
||||
_wrapper.DownloadHandler(new Uri(downloadItem.Url));
|
||||
_wrapper.DownloadHandler?.Invoke(new Uri(downloadItem.Url));
|
||||
}
|
||||
|
||||
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
Loading…
Reference in New Issue
Block a user