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> SupportedBSAs = new HashSet<string> {".bsa"};
|
||||||
|
|
||||||
public static HashSet<string> ConfigFileExtensions = new HashSet<string> {".json", ".ini", ".yml"};
|
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";
|
public static string NexusCacheDirectory = "nexus_link_cache";
|
||||||
|
|
||||||
|
@ -485,6 +485,11 @@ namespace Wabbajack.Common
|
|||||||
Log($"WARNING: {s}");
|
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)
|
public static byte[] ConcatArrays(this IEnumerable<byte[]> arrays)
|
||||||
{
|
{
|
||||||
var outarr = new byte[arrays.Sum(a => a.Length)];
|
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" />
|
<Reference Include="System.Transactions" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Extensions.cs" />
|
||||||
<Compile Include="TestUtils.cs" />
|
<Compile Include="TestUtils.cs" />
|
||||||
<Compile Include="SanityTests.cs" />
|
<Compile Include="SanityTests.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="ContentRightsManagementTests.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using VFS;
|
using VFS;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
|
||||||
namespace Wabbajack
|
namespace Wabbajack
|
||||||
{
|
{
|
||||||
@ -38,6 +39,11 @@ namespace Wabbajack
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Archive> Archives;
|
public List<Archive> Archives;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The game variant to which this game applies
|
||||||
|
/// </summary>
|
||||||
|
public Game GameType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Install directives
|
/// Install directives
|
||||||
/// </summary>
|
/// </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="WindowsBase" />
|
||||||
<Reference Include="PresentationCore" />
|
<Reference Include="PresentationCore" />
|
||||||
<Reference Include="PresentationFramework" />
|
<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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ApplicationDefinition Include="App.xaml">
|
<ApplicationDefinition Include="App.xaml">
|
||||||
@ -191,6 +194,8 @@
|
|||||||
<Compile Include="Themes\LeftMarginMultiplierConverter.cs" />
|
<Compile Include="Themes\LeftMarginMultiplierConverter.cs" />
|
||||||
<Compile Include="Themes\TreeViewItemExtensions.cs" />
|
<Compile Include="Themes\TreeViewItemExtensions.cs" />
|
||||||
<Compile Include="UIUtils.cs" />
|
<Compile Include="UIUtils.cs" />
|
||||||
|
<Compile Include="Validation\DTOs.cs" />
|
||||||
|
<Compile Include="Validation\ValidateModlist.cs" />
|
||||||
<Compile Include="zEditIntegration.cs" />
|
<Compile Include="zEditIntegration.cs" />
|
||||||
<Page Include="MainWindow.xaml">
|
<Page Include="MainWindow.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
@ -291,6 +296,7 @@
|
|||||||
<EmbeddedResource Include="Icons\github.png" />
|
<EmbeddedResource Include="Icons\github.png" />
|
||||||
<EmbeddedResource Include="Icons\patreon.png" />
|
<EmbeddedResource Include="Icons\patreon.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<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')" />
|
<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">
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||||
|
@ -22,4 +22,5 @@
|
|||||||
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net472" />
|
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net472" />
|
||||||
<package id="WebSocketSharpFork" version="1.0.4.0" targetFramework="net472" />
|
<package id="WebSocketSharpFork" version="1.0.4.0" targetFramework="net472" />
|
||||||
<package id="WPFThemes.DarkBlend" version="1.0.8" targetFramework="net472" />
|
<package id="WPFThemes.DarkBlend" version="1.0.8" targetFramework="net472" />
|
||||||
|
<package id="YamlDotNet" version="7.0.0" targetFramework="net472" />
|
||||||
</packages>
|
</packages>
|
Loading…
Reference in New Issue
Block a user