mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
implement framework for content rights management
This commit is contained in:
parent
45bc7ec62d
commit
6c24e4fdbb
@ -18,6 +18,8 @@ namespace Wabbajack.Common
|
||||
public static HashSet<string> SupportedBSAs = new HashSet<string> {".bsa"};
|
||||
|
||||
public static HashSet<string> ConfigFileExtensions = new HashSet<string> {".json", ".ini", ".yml"};
|
||||
public static HashSet<string> ESPFileExtensions = new HashSet<string>() { ".esp", ".esm", ".esl"};
|
||||
public static HashSet<string> AssetFileExtensions = new HashSet<string>() {".dds", ".tga", ".nif", ".psc", ".pex"};
|
||||
|
||||
public static string NexusCacheDirectory = "nexus_link_cache";
|
||||
|
||||
|
@ -485,6 +485,11 @@ namespace Wabbajack.Common
|
||||
Log($"WARNING: {s}");
|
||||
}
|
||||
|
||||
public static TV GetOrDefault<TK, TV>(this Dictionary<TK, TV> dict, TK key)
|
||||
{
|
||||
return dict.TryGetValue(key, out var result) ? result : default;
|
||||
}
|
||||
|
||||
public static byte[] ConcatArrays(this IEnumerable<byte[]> arrays)
|
||||
{
|
||||
var outarr = new byte[arrays.Sum(a => a.Length)];
|
||||
|
181
Wabbajack.Test/ContentRightsManagementTests.cs
Normal file
181
Wabbajack.Test/ContentRightsManagementTests.cs
Normal file
@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Validation;
|
||||
using Game = Wabbajack.Common.Game;
|
||||
|
||||
namespace Wabbajack.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class ContentRightsManagementTests
|
||||
{
|
||||
private ValidateModlist validate;
|
||||
|
||||
private static string permissions = @"
|
||||
|
||||
bill:
|
||||
Permissions:
|
||||
CanExtractBSAs: false
|
||||
Games:
|
||||
Skyrim:
|
||||
Permissions:
|
||||
CanModifyESPs: false
|
||||
Mods:
|
||||
42:
|
||||
Permissions:
|
||||
CanModifyAssets: false
|
||||
Files:
|
||||
33:
|
||||
Permissions:
|
||||
CanUseInOtherGames: false
|
||||
";
|
||||
|
||||
|
||||
[TestInitialize]
|
||||
public void TestSetup()
|
||||
{
|
||||
WorkQueue.Init((x, y, z) => { }, (min, max) => { });
|
||||
validate = new ValidateModlist();
|
||||
validate.LoadAuthorPermissionsFromString(permissions);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestRightsFallthrough()
|
||||
{
|
||||
var permissions = validate.FilePermissions(new NexusMod()
|
||||
{
|
||||
Author = "bill",
|
||||
GameName = "Skyrim",
|
||||
ModID = "42",
|
||||
FileID = "33"
|
||||
});
|
||||
|
||||
permissions.CanExtractBSAs.AssertIsFalse();
|
||||
permissions.CanModifyESPs.AssertIsFalse();
|
||||
permissions.CanModifyAssets.AssertIsFalse();
|
||||
permissions.CanUseInOtherGames.AssertIsFalse();
|
||||
|
||||
permissions = validate.FilePermissions(new NexusMod()
|
||||
{
|
||||
Author = "bob",
|
||||
GameName = "Skyrim",
|
||||
ModID = "42",
|
||||
FileID = "33"
|
||||
});
|
||||
|
||||
permissions.CanExtractBSAs.AssertIsTrue();
|
||||
permissions.CanModifyESPs.AssertIsTrue();
|
||||
permissions.CanModifyAssets.AssertIsTrue();
|
||||
permissions.CanUseInOtherGames.AssertIsTrue();
|
||||
|
||||
permissions = validate.FilePermissions(new NexusMod()
|
||||
{
|
||||
Author = "bill",
|
||||
GameName = "Fallout4",
|
||||
ModID = "42",
|
||||
FileID = "33"
|
||||
});
|
||||
|
||||
permissions.CanExtractBSAs.AssertIsFalse();
|
||||
permissions.CanModifyESPs.AssertIsTrue();
|
||||
permissions.CanModifyAssets.AssertIsTrue();
|
||||
permissions.CanUseInOtherGames.AssertIsTrue();
|
||||
|
||||
permissions = validate.FilePermissions(new NexusMod()
|
||||
{
|
||||
Author = "bill",
|
||||
GameName = "Skyrim",
|
||||
ModID = "43",
|
||||
FileID = "33"
|
||||
});
|
||||
|
||||
permissions.CanExtractBSAs.AssertIsFalse();
|
||||
permissions.CanModifyESPs.AssertIsFalse();
|
||||
permissions.CanModifyAssets.AssertIsTrue();
|
||||
permissions.CanUseInOtherGames.AssertIsTrue();
|
||||
|
||||
permissions = validate.FilePermissions(new NexusMod()
|
||||
{
|
||||
Author = "bill",
|
||||
GameName = "Skyrim",
|
||||
ModID = "42",
|
||||
FileID = "31"
|
||||
});
|
||||
|
||||
permissions.CanExtractBSAs.AssertIsFalse();
|
||||
permissions.CanModifyESPs.AssertIsFalse();
|
||||
permissions.CanModifyAssets.AssertIsFalse();
|
||||
permissions.CanUseInOtherGames.AssertIsTrue();
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void TestModValidation()
|
||||
{
|
||||
var modlist = new ModList
|
||||
{
|
||||
GameType = Game.Skyrim,
|
||||
Archives = new List<Archive>
|
||||
{
|
||||
new NexusMod
|
||||
{
|
||||
GameName = "Skyrim",
|
||||
Author = "bill",
|
||||
ModID = "42",
|
||||
FileID = "33",
|
||||
Hash = "DEADBEEF"
|
||||
}
|
||||
},
|
||||
Directives = new List<Directive>
|
||||
{
|
||||
new FromArchive
|
||||
{
|
||||
ArchiveHashPath = new[] {"DEADBEEF", "foo\\bar\\baz.pex"},
|
||||
To = "foo\\bar\\baz.pex"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IEnumerable<string> errors;
|
||||
|
||||
// No errors, simple archive extraction
|
||||
errors = validate.Validate(modlist);
|
||||
Assert.AreEqual(errors.Count(), 0);
|
||||
|
||||
|
||||
// Error due to patched file
|
||||
modlist.Directives[0] = new PatchedFromArchive
|
||||
{
|
||||
Patch = new byte[]{0, 1, 3},
|
||||
ArchiveHashPath = new[] {"DEADBEEF", "foo\\bar\\baz.pex"},
|
||||
};
|
||||
|
||||
errors = validate.Validate(modlist);
|
||||
Assert.AreEqual(errors.Count(), 1);
|
||||
|
||||
// Error due to extracted BSA file
|
||||
modlist.Directives[0] = new FromArchive
|
||||
{
|
||||
ArchiveHashPath = new[] { "DEADBEEF", "foo.bsa", "foo\\bar\\baz.dds" },
|
||||
};
|
||||
|
||||
errors = validate.Validate(modlist);
|
||||
Assert.AreEqual(errors.Count(), 1);
|
||||
|
||||
|
||||
// Error due to game conversion
|
||||
modlist.GameType = Game.SkyrimSpecialEdition;
|
||||
modlist.Directives[0] = new FromArchive
|
||||
{
|
||||
ArchiveHashPath = new[] { "DEADBEEF", "foo\\bar\\baz.dds" },
|
||||
};
|
||||
errors = validate.Validate(modlist);
|
||||
Assert.AreEqual(errors.Count(), 1);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
22
Wabbajack.Test/Extensions.cs
Normal file
22
Wabbajack.Test/Extensions.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Wabbajack.Test
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static void AssertIsFalse(this bool? condition)
|
||||
{
|
||||
Assert.IsFalse(condition ?? true, string.Empty, (object[])null);
|
||||
}
|
||||
public static void AssertIsTrue(this bool? condition)
|
||||
{
|
||||
Assert.IsTrue(condition ?? false, string.Empty, (object[])null);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -73,9 +73,11 @@
|
||||
<Reference Include="System.Transactions" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Extensions.cs" />
|
||||
<Compile Include="TestUtils.cs" />
|
||||
<Compile Include="SanityTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ContentRightsManagementTests.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using VFS;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack
|
||||
{
|
||||
@ -38,6 +39,11 @@ namespace Wabbajack
|
||||
/// </summary>
|
||||
public List<Archive> Archives;
|
||||
|
||||
/// <summary>
|
||||
/// The game variant to which this game applies
|
||||
/// </summary>
|
||||
public Game GameType;
|
||||
|
||||
/// <summary>
|
||||
/// Install directives
|
||||
/// </summary>
|
||||
|
40
Wabbajack/Validation/DTOs.cs
Normal file
40
Wabbajack/Validation/DTOs.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Validation
|
||||
{
|
||||
public class Permissions
|
||||
{
|
||||
public bool? CanExtractBSAs { get; set; }
|
||||
public bool? CanModifyESPs { get; set; }
|
||||
public bool? CanModifyAssets { get; set; }
|
||||
public bool? CanUseInOtherGames { get; set; }
|
||||
}
|
||||
|
||||
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 Mod
|
||||
{
|
||||
public Permissions Permissions;
|
||||
public Dictionary<string, File> Files;
|
||||
}
|
||||
|
||||
public class File
|
||||
{
|
||||
public Permissions Permissions;
|
||||
}
|
||||
}
|
110
Wabbajack/Validation/ValidateModlist.cs
Normal file
110
Wabbajack/Validation/ValidateModlist.cs
Normal file
@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Wabbajack.Common;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace Wabbajack.Validation
|
||||
{
|
||||
/// <summary>
|
||||
/// Core class for rights management. Given a Wabbajack modlist this class will return a list of all the
|
||||
/// know rights violations of the modlist
|
||||
/// </summary>
|
||||
public class ValidateModlist
|
||||
{
|
||||
public Dictionary<string, Author> AuthorPermissions { get; set; }
|
||||
|
||||
public void LoadAuthorPermissionsFromString(string s)
|
||||
{
|
||||
var d = new DeserializerBuilder()
|
||||
.WithNamingConvention(new PascalCaseNamingConvention())
|
||||
.Build();
|
||||
AuthorPermissions = d.Deserialize<Dictionary<string, Author>>(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="mod"></param>
|
||||
/// <returns></returns>
|
||||
public Permissions FilePermissions(NexusMod 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 IEnumerable<string> Validate(ModList modlist)
|
||||
{
|
||||
ConcurrentStack<string> ValidationErrors = new ConcurrentStack<string>();
|
||||
|
||||
var nexus_mod_permissions = modlist.Archives
|
||||
.OfType<NexusMod>()
|
||||
.PMap(a => (a.Hash, FilePermissions(a), a))
|
||||
.ToDictionary(a => a.Hash, a => new { permissions = a.Item2, archive = a.a});
|
||||
|
||||
modlist.Directives
|
||||
.OfType<PatchedFromArchive>()
|
||||
.PMap(p =>
|
||||
{
|
||||
if (nexus_mod_permissions.TryGetValue(p.ArchiveHashPath[0], out var archive))
|
||||
{
|
||||
var ext = Path.GetExtension(p.ArchiveHashPath.Last());
|
||||
if (Consts.AssetFileExtensions.Contains(ext) && !(archive.permissions.CanModifyAssets ?? true))
|
||||
{
|
||||
ValidationErrors.Push($"{p.To} from {archive.archive.NexusURL} is set to disallow asset modification");
|
||||
}
|
||||
else if (Consts.ESPFileExtensions.Contains(ext) && !(archive.permissions.CanModifyESPs ?? true))
|
||||
{
|
||||
ValidationErrors.Push($"{p.To} from {archive.archive.NexusURL} is set to disallow asset ESP modification");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
modlist.Directives
|
||||
.OfType<FromArchive>()
|
||||
.PMap(p =>
|
||||
{
|
||||
if (nexus_mod_permissions.TryGetValue(p.ArchiveHashPath[0], out var archive))
|
||||
{
|
||||
if (!(archive.permissions.CanExtractBSAs ?? true) &&
|
||||
p.ArchiveHashPath.Skip(1).Any(a => Consts.SupportedBSAs.Contains(Path.GetExtension(a))))
|
||||
{
|
||||
ValidationErrors.Push($"{p.To} from {archive.archive.NexusURL} is set to disallow BSA Extraction");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var nexus = NexusApi.NexusApiUtils.ConvertGameName(GameRegistry.Games[modlist.GameType].NexusName);
|
||||
|
||||
modlist.Archives
|
||||
.OfType<NexusMod>()
|
||||
.Where(m => m.GameName.ToLower() != nexus)
|
||||
.Do(m => ValidationErrors.Push($"The modlist is for {nexus} but {m.Name} is for game type {m.GameName} and is not allowed to be converted to other game types"));
|
||||
|
||||
|
||||
return ValidationErrors.ToList();
|
||||
}
|
||||
}
|
||||
}
|
@ -174,6 +174,9 @@
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="YamlDotNet, Version=7.0.0.0, Culture=neutral, PublicKeyToken=ec19458f3c15af5e, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\YamlDotNet.7.0.0\lib\net45\YamlDotNet.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
@ -191,6 +194,8 @@
|
||||
<Compile Include="Themes\LeftMarginMultiplierConverter.cs" />
|
||||
<Compile Include="Themes\TreeViewItemExtensions.cs" />
|
||||
<Compile Include="UIUtils.cs" />
|
||||
<Compile Include="Validation\DTOs.cs" />
|
||||
<Compile Include="Validation\ValidateModlist.cs" />
|
||||
<Compile Include="zEditIntegration.cs" />
|
||||
<Page Include="MainWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
@ -291,6 +296,7 @@
|
||||
<EmbeddedResource Include="Icons\github.png" />
|
||||
<EmbeddedResource Include="Icons\patreon.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="..\packages\Fody.5.1.1\build\Fody.targets" Condition="Exists('..\packages\Fody.5.1.1\build\Fody.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
|
@ -22,4 +22,5 @@
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net472" />
|
||||
<package id="WebSocketSharpFork" version="1.0.4.0" targetFramework="net472" />
|
||||
<package id="WPFThemes.DarkBlend" version="1.0.8" targetFramework="net472" />
|
||||
<package id="YamlDotNet" version="7.0.0" targetFramework="net472" />
|
||||
</packages>
|
Loading…
Reference in New Issue
Block a user