using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Wabbajack.Common;
using Wabbajack.Lib.Downloaders;
using Path = Alphaleonis.Win32.Filesystem.Path;
namespace Wabbajack.Lib.Validation
{
///
/// Core class for rights management. Given a Wabbajack ModList this class will return a list of all the
/// known rights violations of the ModList
///
public class ValidateModlist
{
public Dictionary AuthorPermissions { get; set; } = new Dictionary();
private readonly WorkQueue _queue;
public ServerWhitelist ServerWhitelist { get; set; } = new ServerWhitelist();
public ValidateModlist(WorkQueue workQueue)
{
_queue = workQueue;
}
public void LoadAuthorPermissionsFromString(string s)
{
AuthorPermissions = s.FromYaml>();
}
public void LoadServerWhitelist(string s)
{
ServerWhitelist = s.FromYaml();
}
public async Task LoadListsFromGithub()
{
var client = new Common.Http.Client();
Utils.Log("Loading server whitelist");
using (var response = await client.GetAsync(Consts.ServerWhitelistURL))
using (var result = await response.Content.ReadAsStreamAsync())
{
ServerWhitelist = result.FromYaml();
Utils.Log($"Loaded permissions for {ServerWhitelist.AllowedPrefixes.Count} servers and {ServerWhitelist.GoogleIDs.Count} Google Drive files");
}
}
public static async Task RunValidation(WorkQueue queue, ModList modlist)
{
var validator = new ValidateModlist(queue);
await validator.LoadListsFromGithub();
Utils.Log("Running validation checks");
var errors = await validator.Validate(modlist);
errors.Do(e => Utils.Log(e));
if (errors.Count() > 0)
{
throw new Exception($"{errors.Count()} validation errors found, cannot continue.");
}
else
{
Utils.Log("No validation failures");
}
}
///
/// Takes all the permissions for a given Nexus mods and merges them down to a single permissions record
/// the more specific record having precedence in each field.
///
///
///
public Permissions FilePermissions(NexusDownloader.State mod)
{
var author_permissions = AuthorPermissions.GetOrDefault(mod.Author)?.Permissions;
var game_permissions = AuthorPermissions.GetOrDefault(mod.Author)?.Games.GetOrDefault(mod.GameName)?.Permissions;
var mod_permissions = AuthorPermissions.GetOrDefault(mod.Author)?.Games.GetOrDefault(mod.GameName)?.Mods.GetOrDefault(mod.ModID)
?.Permissions;
var file_permissions = AuthorPermissions.GetOrDefault(mod.Author)?.Games.GetOrDefault(mod.GameName)?.Mods
.GetOrDefault(mod.ModID)?.Files.GetOrDefault(mod.FileID)?.Permissions;
return new Permissions
{
CanExtractBSAs = file_permissions?.CanExtractBSAs ?? mod_permissions?.CanExtractBSAs ??
game_permissions?.CanExtractBSAs ?? author_permissions?.CanExtractBSAs ?? true,
CanModifyAssets = file_permissions?.CanModifyAssets ?? mod_permissions?.CanModifyAssets ??
game_permissions?.CanModifyAssets ?? author_permissions?.CanModifyAssets ?? true,
CanModifyESPs = file_permissions?.CanModifyESPs ?? mod_permissions?.CanModifyESPs ??
game_permissions?.CanModifyESPs ?? author_permissions?.CanModifyESPs ?? true,
CanUseInOtherGames = file_permissions?.CanUseInOtherGames ?? mod_permissions?.CanUseInOtherGames ??
game_permissions?.CanUseInOtherGames ?? author_permissions?.CanUseInOtherGames ?? true,
};
}
public async Task> Validate(ModList modlist)
{
ConcurrentStack ValidationErrors = new ConcurrentStack();
var nexus_mod_permissions = (await modlist.Archives
.Where(a => a.State is NexusDownloader.State)
.PMap(_queue, a => (a.Hash, FilePermissions((NexusDownloader.State)a.State), a)))
.ToDictionary(a => a.Hash, a => new { permissions = a.Item2, archive = a.a });
await modlist.Directives
.OfType()
.PMap(_queue, p =>
{
if (nexus_mod_permissions.TryGetValue(p.ArchiveHashPath[0], out var archive))
{
var ext = Path.GetExtension(p.ArchiveHashPath.Last());
var url = (archive.archive.State as NexusDownloader.State).URL;
if (Consts.AssetFileExtensions.Contains(ext) && !(archive.permissions.CanModifyAssets ?? true))
{
ValidationErrors.Push($"{p.To} from {url} is set to disallow asset modification");
}
else if (Consts.ESPFileExtensions.Contains(ext) && !(archive.permissions.CanModifyESPs ?? true))
{
ValidationErrors.Push($"{p.To} from {url} is set to disallow asset ESP modification");
}
}
});
await modlist.Directives
.OfType()
.PMap(_queue, p =>
{
if (nexus_mod_permissions.TryGetValue(p.ArchiveHashPath[0], out var archive))
{
var url = (archive.archive.State as NexusDownloader.State).URL;
if (!(archive.permissions.CanExtractBSAs ?? true) &&
p.ArchiveHashPath.Skip(1).ButLast().Any(a => Consts.SupportedBSAs.Contains(Path.GetExtension(a).ToLower())))
{
ValidationErrors.Push($"{p.To} from {url} is set to disallow BSA extraction");
}
}
});
var nexus = NexusApi.NexusApiUtils.ConvertGameName(modlist.GameType.MetaData().NexusName);
modlist.Archives
.Where(a => a.State is NexusDownloader.State)
.Where(m => NexusApi.NexusApiUtils.ConvertGameName(((NexusDownloader.State)m.State).GameName) != nexus)
.Do(m =>
{
var permissions = FilePermissions((NexusDownloader.State)m.State);
if (!(permissions.CanUseInOtherGames ?? true))
{
ValidationErrors.Push(
$"The ModList is for {nexus} but {m.Name} is for game type {((NexusDownloader.State)m.State).GameName} and is not allowed to be converted to other game types");
}
});
modlist.Archives
.Where(m => !m.State.IsWhitelisted(ServerWhitelist))
.Do(m =>
{
ValidationErrors.Push($"{m.Name} is not a whitelisted download");
});
return ValidationErrors.ToList();
}
}
}