Merge remote-tracking branch 'wabbajack-tools/master' into XAML-IDE-Featureset
BIN
Branding/Color_Palette_1.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
Branding/Color_Palette_2.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
Branding/Github_Card.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
Branding/PNGs/Banner.png
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
Branding/PNGs/Banner_Dark.png
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
Branding/PNGs/Banner_Dark_Transparent.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
Branding/PNGs/Banner_Transparent.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
Branding/PNGs/Letters.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
Branding/PNGs/Letters_Dark.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
Branding/PNGs/Letters_Dark_Transparent.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
Branding/PNGs/Letters_Transparent.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
Branding/PNGs/Logo.png
Normal file
After Width: | Height: | Size: 152 KiB |
BIN
Branding/PNGs/Logo_Dark.png
Normal file
After Width: | Height: | Size: 179 KiB |
BIN
Branding/PNGs/Logo_Dark_Transparent.png
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
Branding/PNGs/Logo_Transparent.png
Normal file
After Width: | Height: | Size: 120 KiB |
BIN
Branding/Project files/Banner.afdesign
Normal file
BIN
Branding/Project files/Wabbajack_Icon.xcf
Normal file
158
Branding/SVGs/Banner.svg
Normal file
After Width: | Height: | Size: 80 KiB |
37
Branding/SVGs/Letters.svg
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 262 43" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-128.835,-90.7199)">
|
||||
<g transform="matrix(1.03627,0,0,1.1194,0,-120.896)">
|
||||
<g id="Letter" transform="matrix(1,0,0,1,-6.85674e-14,5.36)">
|
||||
<g transform="matrix(2.12799,0,0,1.96995,130.765,184)">
|
||||
<path d="M0,18.744L-3.026,0L-1.821,0L0.991,17.084L3.722,0.08L5.034,0.08L7.658,17.03L10.363,0L11.46,0L8.462,18.744L6.855,18.744L4.338,3.401L1.714,18.744L0,18.744Z" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(2.12799,0,0,1.96995,179.882,194.235)">
|
||||
<path d="M0,8.327L-2.758,-3.803L-5.462,8.327L0,8.327ZM-6.587,13.549L-7.712,13.549L-3.615,-5.222L-1.821,-5.222L2.356,13.549L1.125,13.549L0.187,9.398L-5.65,9.398L-6.587,13.549Z" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(2.12799,0,0,1.96995,207.774,187.823)">
|
||||
<path d="M0,14.862C0.455,14.318 0.683,13.51 0.683,12.439L0.683,10.752C0.683,9.556 0.415,8.708 -0.121,8.208C-0.656,7.708 -1.495,7.458 -2.637,7.458L-4.967,7.458L-4.967,15.679L-2.182,15.679C-1.183,15.679 -0.455,15.407 0,14.862M-0.415,5.704C0.084,5.285 0.335,4.504 0.335,3.361L0.335,2.156C0.335,1.139 0.138,0.389 -0.254,-0.093C-0.647,-0.575 -1.326,-0.816 -2.29,-0.816L-4.967,-0.816L-4.967,6.334L-2.879,6.334C-1.736,6.334 -0.915,6.124 -0.415,5.704M0.683,-0.95C1.272,-0.289 1.566,0.71 1.566,2.049L1.566,3.12C1.566,4.191 1.383,5.026 1.017,5.624C0.651,6.222 0.013,6.619 -0.897,6.816C0.977,7.19 1.914,8.529 1.914,10.832L1.914,12.465C1.914,13.858 1.58,14.929 0.91,15.679C0.241,16.428 -0.79,16.803 -2.182,16.803L-6.199,16.803L-6.199,-1.941L-2.263,-1.941C-0.888,-1.941 0.094,-1.61 0.683,-0.95" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(2.12799,0,0,1.96995,235.011,187.823)">
|
||||
<path d="M0,14.862C0.455,14.318 0.683,13.51 0.683,12.439L0.683,10.752C0.683,9.556 0.415,8.708 -0.121,8.208C-0.656,7.708 -1.495,7.458 -2.638,7.458L-4.967,7.458L-4.967,15.679L-2.182,15.679C-1.183,15.679 -0.455,15.407 0,14.862M-0.415,5.704C0.085,5.285 0.335,4.504 0.335,3.361L0.335,2.156C0.335,1.139 0.138,0.389 -0.254,-0.093C-0.647,-0.575 -1.326,-0.816 -2.289,-0.816L-4.967,-0.816L-4.967,6.334L-2.879,6.334C-1.736,6.334 -0.915,6.124 -0.415,5.704M0.683,-0.95C1.272,-0.289 1.566,0.71 1.566,2.049L1.566,3.12C1.566,4.191 1.383,5.026 1.017,5.624C0.651,6.222 0.013,6.619 -0.897,6.816C0.977,7.19 1.914,8.529 1.914,10.832L1.914,12.465C1.914,13.858 1.58,14.929 0.91,15.679C0.241,16.428 -0.79,16.803 -2.182,16.803L-6.199,16.803L-6.199,-1.941L-2.263,-1.941C-0.888,-1.941 0.094,-1.61 0.683,-0.95" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(2.12799,0,0,1.96995,264.897,194.235)">
|
||||
<path d="M0,8.327L-2.758,-3.803L-5.463,8.327L0,8.327ZM-6.587,13.549L-7.712,13.549L-3.615,-5.222L-1.821,-5.222L2.356,13.549L1.125,13.549L0.187,9.398L-5.65,9.398L-6.587,13.549Z" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(2.12799,0,0,1.96995,279.654,186.268)">
|
||||
<path d="M0,16.468C0.357,16.468 0.616,16.459 0.777,16.441C1.687,16.37 2.356,16.071 2.785,15.544C3.213,15.018 3.427,14.192 3.427,13.067L3.427,-1.151L4.659,-1.151L4.659,12.987C4.659,14.558 4.338,15.705 3.695,16.428C3.053,17.151 2.124,17.539 0.91,17.593C0.785,17.61 0.58,17.619 0.295,17.619C0.027,17.619 -0.179,17.61 -0.321,17.593L-0.321,16.468L0,16.468Z" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(2.12799,0,0,1.96995,315.552,194.235)">
|
||||
<path d="M0,8.327L-2.758,-3.803L-5.463,8.327L0,8.327ZM-6.587,13.549L-7.712,13.549L-3.615,-5.222L-1.821,-5.222L2.356,13.549L1.125,13.549L0.187,9.398L-5.65,9.398L-6.587,13.549Z" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(2.12799,0,0,1.96995,345.181,218.681)">
|
||||
<path d="M0,-16.467C0.66,-15.601 0.991,-14.454 0.991,-13.026L0.991,-11.473L-0.187,-11.473L-0.187,-13.106C-0.187,-14.178 -0.415,-15.034 -0.87,-15.677C-1.325,-16.32 -2.044,-16.641 -3.026,-16.641C-4.008,-16.641 -4.726,-16.32 -5.181,-15.677C-5.637,-15.034 -5.864,-14.178 -5.864,-13.106L-5.864,-3.36C-5.864,-2.289 -5.637,-1.436 -5.181,-0.802C-4.726,-0.169 -4.008,0.148 -3.026,0.148C-2.044,0.148 -1.325,-0.169 -0.87,-0.802C-0.415,-1.436 -0.187,-2.289 -0.187,-3.36L-0.187,-5.582L0.991,-5.582L0.991,-3.44C0.991,-2.012 0.66,-0.865 0,0.001C-0.661,0.867 -1.678,1.299 -3.053,1.299C-4.427,1.299 -5.445,0.867 -6.105,0.001C-6.766,-0.865 -7.096,-2.012 -7.096,-3.44L-7.096,-13.026C-7.096,-14.454 -6.766,-15.601 -6.105,-16.467C-5.445,-17.333 -4.427,-17.766 -3.053,-17.766C-1.678,-17.766 -0.661,-17.333 0,-16.467" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(2.12799,0,0,1.96995,359.882,197.347)">
|
||||
<path d="M0,5.194L0,11.969L-1.232,11.969L-1.232,-6.775L0,-6.775L0,3.132L6.105,-6.775L7.39,-6.775L2.035,1.954L7.739,11.969L6.453,11.969L1.285,3.159L0,5.194Z" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.6 KiB |
130
Branding/SVGs/Logo.svg
Normal file
After Width: | Height: | Size: 76 KiB |
16
Branding/SVGs/Logo_Dark.svg
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
Branding/wabbajack.ico
Normal file
After Width: | Height: | Size: 399 KiB |
BIN
Branding/wabbajack.png
Normal file
After Width: | Height: | Size: 276 KiB |
24
CHANGELOG.md
@ -1,7 +1,27 @@
|
||||
### Changelog
|
||||
|
||||
#### Version 1.0 alpha - ???
|
||||
* Nothing yet...
|
||||
#### Version 1.0 alpha 5 - 11/2/2019
|
||||
* Fix a NPE exception with game ESM verification
|
||||
|
||||
#### Version 1.0 alpha 4 - 11/2/2019
|
||||
* Reorganize steps so that we run zEdit merges before NOMATCH_INCLUDE
|
||||
* Look for hidden/optional ESMs when building zEdit plugins
|
||||
* Check for modified ESMs before starting the long install process
|
||||
|
||||
#### Version 1.0 alpha 3 - 11/2/2019
|
||||
* Slideshow more responsive on pressing next
|
||||
* Slideshow timer resets when next is pressed
|
||||
* Changed modlist extension to `.wabbajack`
|
||||
* You can now open modlists directly (after initial launch)
|
||||
* Wabbajack will exit if MO2 is running
|
||||
* Added support for zEdit merges. We detect the zEdit install location by scanning the tool list in
|
||||
Mod Organizer's .ini files, then we use the merges.json file to figure out the contents of each merge.
|
||||
|
||||
#### Version 1.0 alpha 2 - 10/15/2019
|
||||
* Fix installer running in wrong mode
|
||||
|
||||
#### Version 1.0 alpha 1 - 10/14/2019
|
||||
* Several internal bug fixes
|
||||
|
||||
#### Version 0.9.5 - 10/12/2019
|
||||
* New Property system for chaning Modlist Name, Author, Description, Website, custom Banner and custom Readme
|
||||
|
@ -11,19 +11,16 @@ using Path = Alphaleonis.Win32.Filesystem.Path;
|
||||
|
||||
namespace Compression.BSA
|
||||
{
|
||||
public class BSABuilder : IDisposable, IBSABuilder
|
||||
public class BSABuilder : IBSABuilder
|
||||
{
|
||||
internal uint _archiveFlags;
|
||||
internal uint _fileCount;
|
||||
internal uint _fileFlags;
|
||||
internal byte[] _fileId;
|
||||
|
||||
private List<FileEntry> _files = new List<FileEntry>();
|
||||
internal uint _folderCount;
|
||||
internal List<FolderRecordBuilder> _folders = new List<FolderRecordBuilder>();
|
||||
internal uint _offset;
|
||||
internal uint _totalFileNameLength;
|
||||
internal uint _totalFolderNameLength;
|
||||
internal uint _version;
|
||||
|
||||
public BSABuilder()
|
||||
@ -238,8 +235,6 @@ namespace Compression.BSA
|
||||
public class FileEntry
|
||||
{
|
||||
internal BSABuilder _bsa;
|
||||
internal Stream _bytesSource;
|
||||
internal string _filenameSource;
|
||||
internal bool _flipCompression;
|
||||
internal FolderRecordBuilder _folder;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## Wabbajack - An automated modlist installer for TES/Fallout games
|
||||
|
||||
[![Build Status](https://dev.azure.com/tbaldridge/tbaldridge/_apis/build/status/halgari.wabbajack?branchName=master)](https://dev.azure.com/tbaldridge/tbaldridge/_build/latest?definitionId=1&branchName=master)
|
||||
[![Build Status](https://dev.azure.com/tbaldridge/tbaldridge/_apis/build/status/wabbajack-tools.wabbajack?branchName=master)](https://dev.azure.com/tbaldridge/tbaldridge/_build/latest?definitionId=3&branchName=master)
|
||||
|
||||
The general idea behind this program is fairly simple. Given a Mod Organizer 2 folder and profile, generate list of instructions that will allow
|
||||
a program to automatically recreate the contents of the folder on another machine. Think of it as replication, but without ever distributing copyrighted
|
||||
|
@ -4,4 +4,13 @@
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
|
||||
</configuration>
|
@ -5,7 +5,9 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Ceras;
|
||||
using Compression.BSA;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Newtonsoft.Json;
|
||||
@ -19,6 +21,9 @@ namespace VFS
|
||||
{
|
||||
public class VirtualFileSystem
|
||||
{
|
||||
public const ulong FileVersion = 0x01;
|
||||
public const string Magic = "WABBAJACK VFS FILE";
|
||||
|
||||
internal static string _stagedRoot;
|
||||
public static VirtualFileSystem VFS;
|
||||
private bool _disableDiskCache;
|
||||
@ -120,6 +125,14 @@ namespace VFS
|
||||
using (var fs = File.OpenRead("vfs_cache.bin"))
|
||||
using (var br = new BinaryReader(fs))
|
||||
{
|
||||
var magic = Encoding.ASCII.GetString(br.ReadBytes(Magic.Length));
|
||||
if (magic != Magic || br.ReadUInt64() != FileVersion)
|
||||
{
|
||||
fs.Close();
|
||||
File.Delete("vfs_cache.bin");
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
var fr = VirtualFile.Read(br);
|
||||
@ -127,7 +140,7 @@ namespace VFS
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EndOfStreamException ex)
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
}
|
||||
|
||||
@ -158,6 +171,9 @@ namespace VFS
|
||||
using (var fs = File.OpenWrite("vfs_cache.bin_new"))
|
||||
using (var bw = new BinaryWriter(fs))
|
||||
{
|
||||
bw.Write(Encoding.ASCII.GetBytes(Magic));
|
||||
bw.Write(FileVersion);
|
||||
|
||||
Utils.Log($"Syncing VFS to Disk: {_files.Count} entries");
|
||||
foreach (var f in _files.Values) f.Write(bw);
|
||||
}
|
||||
@ -505,7 +521,7 @@ namespace VFS
|
||||
}
|
||||
}
|
||||
|
||||
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
|
||||
[MemberConfig(TargetMember.None)]
|
||||
public class VirtualFile
|
||||
{
|
||||
private string _fullPath;
|
||||
@ -517,7 +533,7 @@ namespace VFS
|
||||
|
||||
internal string _stagedPath;
|
||||
|
||||
[JsonProperty]
|
||||
[Include]
|
||||
public string[] Paths
|
||||
{
|
||||
get => _paths;
|
||||
@ -529,13 +545,13 @@ namespace VFS
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty] public string Hash { get; set; }
|
||||
[Include] public string Hash { get; set; }
|
||||
|
||||
[JsonProperty] public long Size { get; set; }
|
||||
[Include] public long Size { get; set; }
|
||||
|
||||
[JsonProperty] public ulong LastModified { get; set; }
|
||||
[Include] public ulong LastModified { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[Include]
|
||||
public bool? FinishedIndexing { get; set; }
|
||||
|
||||
|
||||
@ -671,7 +687,7 @@ namespace VFS
|
||||
|
||||
var fio = new FileInfo(StagedPath);
|
||||
Size = fio.Length;
|
||||
Hash = StagedPath.FileSHA256();
|
||||
Hash = StagedPath.FileHash();
|
||||
LastModified = fio.LastWriteTime.ToMilliseconds();
|
||||
}
|
||||
|
||||
|
@ -73,6 +73,9 @@
|
||||
<Reference Include="AlphaFS, Version=2.2.0.0, Culture=neutral, PublicKeyToken=4d31a58f7d7ad5c9, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\AlphaFS.2.2.6\lib\net452\AlphaFS.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Ceras, Version=4.1.7.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Ceras.4.1.7\lib\net47\Ceras.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ICSharpCode.SharpZipLib, Version=1.2.0.246, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpZipLib.1.2.0\lib\net45\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
</Reference>
|
||||
@ -80,11 +83,17 @@
|
||||
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Collections.Immutable, Version=1.2.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Transactions" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
@ -108,6 +117,7 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
@ -3,8 +3,8 @@
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.4.0" newVersion="4.0.4.0" />
|
||||
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
@ -1,7 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="AlphaFS" version="2.2.6" targetFramework="net472" />
|
||||
<package id="Ceras" version="4.1.7" targetFramework="net472" />
|
||||
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" />
|
||||
<package id="SharpZipLib" version="1.2.0" targetFramework="net472" />
|
||||
<package id="System.Buffers" version="4.5.0" targetFramework="net472" />
|
||||
<package id="System.Collections.Immutable" version="1.5.0" targetFramework="net472" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net472" />
|
||||
</packages>
|
@ -9,10 +9,12 @@ namespace Wabbajack.Common
|
||||
{
|
||||
public static bool TestMode { get; set; } = false;
|
||||
|
||||
public static string ModlistExtension = ".modlist_v2";
|
||||
public static string GameFolderFilesDir = "Game Folder Files";
|
||||
public static string LOOTFolderFilesDir = "LOOT Config Files";
|
||||
public static string BSACreationDir = "TEMP_BSA_FILES";
|
||||
|
||||
public static string ModListDownloadFolder = "downloaded_mod_lists";
|
||||
|
||||
public static string MegaPrefix = "https://mega.nz/#!";
|
||||
|
||||
public static HashSet<string> SupportedArchives = new HashSet<string> {".zip", ".rar", ".7z", ".7zip", ".fomod", ".omod"};
|
||||
@ -43,13 +45,30 @@ namespace Wabbajack.Common
|
||||
|
||||
|
||||
public static string AppName = "Wabbajack";
|
||||
public static string HashCacheName = "Wabbajack.hash_cache";
|
||||
|
||||
public static HashSet<string> GameESMs = new HashSet<string>
|
||||
{"Skyrim.esm", "Update.esm", "Dawnguard.esm", "HearthFires.esm", "Dragonborn.esm"};
|
||||
{
|
||||
// Skyrim LE/SE
|
||||
"Skyrim.esm",
|
||||
"Update.esm",
|
||||
"Dawnguard.esm",
|
||||
"HearthFires.esm",
|
||||
"Dragonborn.esm",
|
||||
|
||||
// Fallout 4
|
||||
"DLCRobot.esm",
|
||||
"DLCworkshop01.esm",
|
||||
"DLCCoast.esm",
|
||||
"DLCWorkshop02.esm",
|
||||
"DLCWorkshop03.esm",
|
||||
"DLCNukaWorld.esm",
|
||||
"DLCUltraHighResolution.esm"
|
||||
|
||||
};
|
||||
|
||||
public static string ModPermissionsURL = "https://raw.githubusercontent.com/wabbajack-tools/opt-out-lists/master/NexusModPermissions.yml";
|
||||
public static string ServerWhitelistURL = "https://raw.githubusercontent.com/wabbajack-tools/opt-out-lists/master/ServerWhitelist.yml";
|
||||
public static string ModlistMetadataURL = "https://raw.githubusercontent.com/wabbajack-tools/mod-lists/master/modlists.json";
|
||||
|
||||
public static string UserAgent
|
||||
{
|
||||
@ -61,5 +80,7 @@ namespace Wabbajack.Common
|
||||
return headerString;
|
||||
}
|
||||
}
|
||||
|
||||
public static TimeSpan NexusCacheExpiry = new TimeSpan(1, 0, 0, 0);
|
||||
}
|
||||
}
|
73
Wabbajack.Common/ExtensionManager.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Wabbajack.Common
|
||||
{
|
||||
public class ExtensionManager
|
||||
{
|
||||
[DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
|
||||
|
||||
public static string Extension = ".wabbajack";
|
||||
|
||||
private static readonly string ProgIDPath = "Software\\Classes\\Wabbajack";
|
||||
private static readonly string ExtPath = $"Software\\Classes\\{Extension}";
|
||||
|
||||
private static readonly Dictionary<string, string> ProgIDList = new Dictionary<string, string>
|
||||
{
|
||||
{"", "Wabbajack"},
|
||||
{"FriendlyTypeName", "Wabbajack"},
|
||||
{"shell\\open\\command", "\"{appPath}\" -i \"%1\""},
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> ExtList = new Dictionary<string, string>
|
||||
{
|
||||
{"", "Wabbajack"},
|
||||
{"PerceivedType", "Compressed"}
|
||||
};
|
||||
|
||||
public static bool NeedsUpdating(string appPath)
|
||||
{
|
||||
var progIDKey = Registry.CurrentUser.OpenSubKey(ProgIDPath);
|
||||
var tempKey = progIDKey?.OpenSubKey("shell\\open\\command");
|
||||
if (progIDKey == null || tempKey == null) return true;
|
||||
return tempKey.GetValue("").ToString().Equals($"\"{appPath}\" -i \"%1\"");
|
||||
}
|
||||
|
||||
public static bool IsAssociated()
|
||||
{
|
||||
var progIDKey = Registry.CurrentUser.OpenSubKey(ProgIDPath);
|
||||
var extKey = Registry.CurrentUser.OpenSubKey(ExtPath);
|
||||
return (progIDKey != null && extKey != null);
|
||||
}
|
||||
|
||||
public static void Associate(string appPath)
|
||||
{
|
||||
var progIDKey = Registry.CurrentUser.CreateSubKey(ProgIDPath, RegistryKeyPermissionCheck.ReadWriteSubTree);
|
||||
foreach (KeyValuePair<string, string> entry in ProgIDList)
|
||||
{
|
||||
if (entry.Key.Contains("\\"))
|
||||
{
|
||||
var tempKey = progIDKey.CreateSubKey(entry.Key);
|
||||
tempKey.SetValue("", entry.Value.Replace("{appPath}", appPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
progIDKey?.SetValue(entry.Key, entry.Value.Replace("{appPath}", appPath));
|
||||
}
|
||||
}
|
||||
|
||||
var extKey = Registry.CurrentUser.CreateSubKey(ExtPath, RegistryKeyPermissionCheck.ReadWriteSubTree);
|
||||
foreach (KeyValuePair<string, string> entry in ExtList)
|
||||
{
|
||||
extKey?.SetValue(entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
progIDKey?.Close();
|
||||
extKey?.Close();
|
||||
SHChangeNotify(0x000000, 0x0000, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ using System.Reflection;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Compression.BSA;
|
||||
using ICSharpCode.SharpZipLib.GZip;
|
||||
//using OMODFramework;
|
||||
using OMODFramework;
|
||||
|
||||
namespace Wabbajack.Common
|
||||
{
|
||||
@ -53,12 +53,12 @@ namespace Wabbajack.Common
|
||||
|
||||
private static void ExtractAllWithOMOD(string source, string dest)
|
||||
{
|
||||
/*Utils.Log($"Extracting {Path.GetFileName(source)}");
|
||||
Framework f = new Framework();
|
||||
Utils.Log($"Extracting {Path.GetFileName(source)}");
|
||||
var f = new Framework();
|
||||
f.SetTempDirectory(dest);
|
||||
OMOD omod = new OMOD(source, ref f);
|
||||
var omod = new OMOD(source, ref f);
|
||||
omod.ExtractDataFiles();
|
||||
omod.ExtractPlugins();*/
|
||||
omod.ExtractPlugins();
|
||||
}
|
||||
|
||||
private static void ExtractAllWithBSA(string source, string dest)
|
||||
@ -139,7 +139,7 @@ namespace Wabbajack.Common
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
@ -197,7 +197,7 @@ namespace Wabbajack.Common
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ using Microsoft.Win32;
|
||||
namespace Wabbajack.Common
|
||||
{
|
||||
public enum Game {
|
||||
Morrowind,
|
||||
Oblivion,
|
||||
Fallout3,
|
||||
FalloutNewVegas,
|
||||
@ -55,6 +56,9 @@ namespace Wabbajack.Common
|
||||
|
||||
public static Dictionary<Game, GameMetaData> Games = new Dictionary<Game, GameMetaData>
|
||||
{
|
||||
{
|
||||
Game.Morrowind, new GameMetaData()
|
||||
},
|
||||
{
|
||||
Game.Oblivion, new GameMetaData
|
||||
{
|
||||
|
12
Wabbajack.Common/ModListRegistry.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wabbajack.Common
|
||||
{
|
||||
class ModListRegistry
|
||||
{
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.HashFunction.xxHash;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -10,10 +11,13 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ceras;
|
||||
using ICSharpCode.SharpZipLib.BZip2;
|
||||
using IniParser;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Bson;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
using File = Alphaleonis.Win32.Filesystem.File;
|
||||
using FileInfo = Alphaleonis.Win32.Filesystem.FileInfo;
|
||||
using Path = Alphaleonis.Win32.Filesystem.Path;
|
||||
@ -22,6 +26,12 @@ namespace Wabbajack.Common
|
||||
{
|
||||
public static class Utils
|
||||
{
|
||||
public static bool IsMO2Running(string mo2Path)
|
||||
{
|
||||
Process[] processList = Process.GetProcesses();
|
||||
return processList.Where(process => process.ProcessName == "ModOrganizer").Any(process => Path.GetDirectoryName(process.MainModule?.FileName) == mo2Path);
|
||||
}
|
||||
|
||||
public static string LogFile { get; private set; }
|
||||
static Utils()
|
||||
{
|
||||
@ -74,7 +84,10 @@ namespace Wabbajack.Common
|
||||
|
||||
public static void Status(string msg, int progress = 0)
|
||||
{
|
||||
_statusFn?.Invoke(msg, progress);
|
||||
if (WorkQueue.CustomReportFn != null)
|
||||
WorkQueue.CustomReportFn(progress, msg);
|
||||
else
|
||||
_statusFn?.Invoke(msg, progress);
|
||||
}
|
||||
|
||||
|
||||
@ -97,6 +110,20 @@ namespace Wabbajack.Common
|
||||
return sha.Hash.ToBase64();
|
||||
}
|
||||
|
||||
public static string FileHash(this string file)
|
||||
{
|
||||
var hash = new xxHashConfig();
|
||||
hash.HashSizeInBits = 64;
|
||||
hash.Seed = 0x42;
|
||||
using (var fs = File.OpenRead(file))
|
||||
{
|
||||
var config = new xxHashConfig();
|
||||
config.HashSizeInBits = 64;
|
||||
var value = xxHashFactory.Instance.Create(config).ComputeHash(fs);
|
||||
return value.AsBase64String();
|
||||
}
|
||||
}
|
||||
|
||||
public static void CopyToWithStatus(this Stream istream, long maxSize, Stream ostream, string status)
|
||||
{
|
||||
var buffer = new byte[1024 * 64];
|
||||
@ -187,13 +214,40 @@ namespace Wabbajack.Common
|
||||
return new DynamicIniData(new FileIniDataParser().ReadData(new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(file)))));
|
||||
}
|
||||
|
||||
public static void ToCERAS<T>(this T obj, string filename, ref SerializerConfig config)
|
||||
{
|
||||
var ceras = new CerasSerializer(config);
|
||||
byte[] buffer = null;
|
||||
ceras.Serialize(obj, ref buffer);
|
||||
using(var m1 = new MemoryStream(buffer))
|
||||
using (var m2 = new MemoryStream())
|
||||
{
|
||||
BZip2.Compress(m1, m2, false, 9);
|
||||
m2.Seek(0, SeekOrigin.Begin);
|
||||
File.WriteAllBytes(filename, m2.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public static T FromCERAS<T>(this Stream data, ref SerializerConfig config)
|
||||
{
|
||||
var ceras = new CerasSerializer(config);
|
||||
byte[] bytes = data.ReadAll();
|
||||
using (var m1 = new MemoryStream(bytes))
|
||||
using (var m2 = new MemoryStream())
|
||||
{
|
||||
BZip2.Decompress(m1, m2, false);
|
||||
m2.Seek(0, SeekOrigin.Begin);
|
||||
return ceras.Deserialize<T>(m2.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public static void ToJSON<T>(this T obj, string filename)
|
||||
{
|
||||
File.WriteAllText(filename,
|
||||
JsonConvert.SerializeObject(obj, Formatting.Indented,
|
||||
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto}));
|
||||
}
|
||||
|
||||
/*
|
||||
public static void ToBSON<T>(this T obj, string filename)
|
||||
{
|
||||
using (var fo = File.OpenWrite(filename))
|
||||
@ -204,25 +258,29 @@ namespace Wabbajack.Common
|
||||
{TypeNameHandling = TypeNameHandling.Auto});
|
||||
serializer.Serialize(br, obj);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
public static ulong ToMilliseconds(this DateTime date)
|
||||
{
|
||||
return (ulong) (date - new DateTime(1970, 1, 1)).TotalMilliseconds;
|
||||
}
|
||||
|
||||
public static string ToJSON<T>(this T obj)
|
||||
public static string ToJSON<T>(this T obj,
|
||||
TypeNameHandling handling = TypeNameHandling.All,
|
||||
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full)
|
||||
{
|
||||
return JsonConvert.SerializeObject(obj, Formatting.Indented,
|
||||
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All});
|
||||
new JsonSerializerSettings {TypeNameHandling = handling, TypeNameAssemblyFormatHandling = format});
|
||||
}
|
||||
|
||||
public static T FromJSON<T>(this string filename)
|
||||
|
||||
public static T FromJSON<T>(this string filename,
|
||||
TypeNameHandling handling = TypeNameHandling.All,
|
||||
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(File.ReadAllText(filename),
|
||||
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
|
||||
new JsonSerializerSettings {TypeNameHandling = handling, TypeNameAssemblyFormatHandling = format});
|
||||
}
|
||||
|
||||
/*
|
||||
public static T FromBSON<T>(this string filename, bool root_is_array = false)
|
||||
{
|
||||
using (var fo = File.OpenRead(filename))
|
||||
@ -232,14 +290,15 @@ namespace Wabbajack.Common
|
||||
{TypeNameHandling = TypeNameHandling.Auto});
|
||||
return serializer.Deserialize<T>(br);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
public static T FromJSONString<T>(this string data)
|
||||
public static T FromJSONString<T>(this string data,
|
||||
TypeNameHandling handling = TypeNameHandling.All,
|
||||
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(data,
|
||||
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
|
||||
new JsonSerializerSettings {TypeNameHandling = handling, TypeNameAssemblyFormatHandling = format});
|
||||
}
|
||||
|
||||
public static T FromJSON<T>(this Stream data)
|
||||
{
|
||||
var s = Encoding.UTF8.GetString(data.ReadAll());
|
||||
@ -567,5 +626,36 @@ namespace Wabbajack.Common
|
||||
Log(msg);
|
||||
throw new Exception(msg);
|
||||
}
|
||||
|
||||
public static Stream GetResourceStream(string name)
|
||||
{
|
||||
return (from assembly in AppDomain.CurrentDomain.GetAssemblies()
|
||||
where !assembly.IsDynamic
|
||||
from rname in assembly.GetManifestResourceNames()
|
||||
where rname == name
|
||||
select assembly.GetManifestResourceStream(name)).First();
|
||||
}
|
||||
|
||||
public static T FromYaml<T>(this Stream s)
|
||||
{
|
||||
var d = new DeserializerBuilder()
|
||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
return d.Deserialize<T>(new StreamReader(s));
|
||||
}
|
||||
|
||||
public static T FromYaml<T>(this string s)
|
||||
{
|
||||
var d = new DeserializerBuilder()
|
||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
return d.Deserialize<T>(new StringReader(s));
|
||||
}
|
||||
|
||||
public static void LogStatus(string s)
|
||||
{
|
||||
Status(s);
|
||||
Log(s);
|
||||
}
|
||||
}
|
||||
}
|
@ -73,6 +73,9 @@
|
||||
<Reference Include="AlphaFS, Version=2.2.0.0, Culture=neutral, PublicKeyToken=4d31a58f7d7ad5c9, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\AlphaFS.2.2.6\lib\net452\AlphaFS.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Ceras, Version=4.1.7.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Ceras.4.1.7\lib\net47\Ceras.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="erri120.OMODFramework, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\erri120.OMODFramework.1.0.0\lib\net472\erri120.OMODFramework.dll</HintPath>
|
||||
</Reference>
|
||||
@ -98,10 +101,25 @@
|
||||
<HintPath>..\packages\SevenZip.19.0.0\lib\net20\SevenZip.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.HashFunction.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=80c9288e394c1322, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Data.HashFunction.Core.2.0.0\lib\net45\System.Data.HashFunction.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.HashFunction.Interfaces, Version=2.0.0.0, Culture=neutral, PublicKeyToken=80c9288e394c1322, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Data.HashFunction.Interfaces.2.0.0\lib\net45\System.Data.HashFunction.Interfaces.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.HashFunction.xxHash, Version=2.0.0.0, Culture=neutral, PublicKeyToken=80c9288e394c1322, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Data.HashFunction.xxHash.2.0.0\lib\net45\System.Data.HashFunction.xxHash.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.Transactions" />
|
||||
@ -110,12 +128,16 @@
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="YamlDotNet, Version=8.0.0.0, Culture=neutral, PublicKeyToken=ec19458f3c15af5e, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\YamlDotNet.8.0.0\lib\net45\YamlDotNet.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="BSDiff.cs" />
|
||||
<Compile Include="ChildProcessTracker.cs" />
|
||||
<Compile Include="Consts.cs" />
|
||||
<Compile Include="DynamicIniData.cs" />
|
||||
<Compile Include="ExtensionManager.cs" />
|
||||
<Compile Include="FileExtractor.cs" />
|
||||
<Compile Include="GameMetaData.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
@ -126,6 +148,7 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="7z.dll.gz" />
|
||||
<EmbeddedResource Include="7z.exe.gz" />
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -15,6 +15,8 @@ namespace Wabbajack.Common
|
||||
|
||||
[ThreadStatic] internal static bool WorkerThread;
|
||||
|
||||
[ThreadStatic] public static Action<int, string> CustomReportFn;
|
||||
|
||||
public static int MaxQueueSize;
|
||||
public static int CurrentQueueSize;
|
||||
private static bool _inited;
|
||||
@ -64,7 +66,10 @@ namespace Wabbajack.Common
|
||||
|
||||
public static void Report(string msg, int progress)
|
||||
{
|
||||
ReportFunction(CpuId, msg, progress);
|
||||
if (CustomReportFn != null)
|
||||
CustomReportFn(progress, msg);
|
||||
else
|
||||
ReportFunction(CpuId, msg, progress);
|
||||
}
|
||||
|
||||
public static void QueueTask(Action a)
|
||||
|
11
Wabbajack.Common/app.config
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="AlphaFS" version="2.2.6" targetFramework="net472" />
|
||||
<package id="Ceras" version="4.1.7" targetFramework="net472" />
|
||||
<package id="erri120.OMODFramework" version="1.0.0" targetFramework="net472" />
|
||||
<package id="ini-parser" version="2.5.2" targetFramework="net472" />
|
||||
<package id="murmurhash" version="1.0.3" targetFramework="net472" />
|
||||
@ -9,5 +10,11 @@
|
||||
<package id="protobuf-net" version="2.4.0" targetFramework="net472" />
|
||||
<package id="SevenZip" version="19.0.0" targetFramework="net472" />
|
||||
<package id="SharpZipLib" version="1.2.0" targetFramework="net472" />
|
||||
<package id="System.Buffers" version="4.5.0" targetFramework="net472" />
|
||||
<package id="System.Data.HashFunction.Core" version="2.0.0" targetFramework="net472" />
|
||||
<package id="System.Data.HashFunction.Interfaces" version="2.0.0" targetFramework="net472" />
|
||||
<package id="System.Data.HashFunction.xxHash" version="2.0.0" targetFramework="net472" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net472" />
|
||||
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net472" />
|
||||
<package id="YamlDotNet" version="8.0.0" targetFramework="net472" />
|
||||
</packages>
|
29
Wabbajack.Lib/CerasConfig.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Ceras;
|
||||
using Compression.BSA;
|
||||
using VFS;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Lib.Downloaders;
|
||||
|
||||
namespace Wabbajack.Lib
|
||||
{
|
||||
public class CerasConfig
|
||||
{
|
||||
public static SerializerConfig Config = new SerializerConfig
|
||||
{
|
||||
KnownTypes =
|
||||
{
|
||||
typeof(ModList), typeof(Game), typeof(Directive), typeof(IgnoredDirectly),
|
||||
typeof(NoMatch), typeof(InlineFile), typeof(PropertyType), typeof(CleanedESM),
|
||||
typeof(RemappedInlineFile), typeof(FromArchive), typeof(CreateBSA), typeof(PatchedFromArchive),
|
||||
typeof(SourcePatch), typeof(MergedPatch), typeof(Archive), typeof(IndexedArchive), typeof(IndexedEntry),
|
||||
typeof(IndexedArchiveEntry), typeof(BSAIndexedEntry), typeof(VirtualFile),
|
||||
typeof(ArchiveStateObject), typeof(FileStateObject), typeof(IDownloader),
|
||||
typeof(IUrlDownloader), typeof(AbstractDownloadState), typeof(ManualDownloader.State),
|
||||
typeof(DropboxDownloader), typeof(GoogleDriveDownloader.State), typeof(HTTPDownloader.State),
|
||||
typeof(MegaDownloader.State), typeof(ModDBDownloader.State), typeof(NexusDownloader.State),
|
||||
typeof(BSAStateObject), typeof(BSAFileStateObject), typeof(BA2StateObject), typeof(BA2DX10EntryState),
|
||||
typeof(BA2FileEntryState), typeof(MediaFireDownloader.State)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
15
Wabbajack.Lib/CompilationSteps/ACompilationStep.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public abstract class ACompilationStep : ICompilationStep
|
||||
{
|
||||
protected Compiler _compiler;
|
||||
|
||||
public ACompilationStep(Compiler compiler)
|
||||
{
|
||||
_compiler = compiler;
|
||||
}
|
||||
|
||||
public abstract Directive Run(RawSourceFile source);
|
||||
public abstract IState GetState();
|
||||
}
|
||||
}
|
101
Wabbajack.Lib/CompilationSteps/DeconstructBSAs.cs
Normal file
@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Compression.BSA;
|
||||
using Newtonsoft.Json;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class DeconstructBSAs : ACompilationStep
|
||||
{
|
||||
private readonly IEnumerable<string> _include_directly;
|
||||
private readonly List<ICompilationStep> _microstack;
|
||||
private readonly List<ICompilationStep> _microstackWithInclude;
|
||||
|
||||
public DeconstructBSAs(Compiler compiler) : base(compiler)
|
||||
{
|
||||
_include_directly = _compiler.ModInis.Where(kv =>
|
||||
{
|
||||
var general = kv.Value.General;
|
||||
if (general.notes != null && general.notes.Contains(Consts.WABBAJACK_INCLUDE)) return true;
|
||||
if (general.comments != null && general.comments.Contains(Consts.WABBAJACK_INCLUDE)) return true;
|
||||
return false;
|
||||
})
|
||||
.Select(kv => $"mods\\{kv.Key}\\")
|
||||
.ToList();
|
||||
|
||||
_microstack = new List<ICompilationStep>
|
||||
{
|
||||
new DirectMatch(_compiler),
|
||||
new IncludePatches(_compiler),
|
||||
new DropAll(_compiler)
|
||||
};
|
||||
|
||||
_microstackWithInclude = new List<ICompilationStep>
|
||||
{
|
||||
new DirectMatch(_compiler),
|
||||
new IncludePatches(_compiler),
|
||||
new IncludeAll(_compiler)
|
||||
};
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!Consts.SupportedBSAs.Contains(Path.GetExtension(source.Path).ToLower())) return null;
|
||||
|
||||
var defaultInclude = false;
|
||||
if (source.Path.StartsWith("mods"))
|
||||
if (_include_directly.Any(path => source.Path.StartsWith(path)))
|
||||
defaultInclude = true;
|
||||
|
||||
var source_files = source.File.FileInArchive;
|
||||
|
||||
var stack = defaultInclude ? _microstackWithInclude : _microstack;
|
||||
|
||||
var id = Guid.NewGuid().ToString();
|
||||
|
||||
var matches = source_files.PMap(e => Compiler.RunStack(stack, new RawSourceFile(e)
|
||||
{
|
||||
Path = Path.Combine(Consts.BSACreationDir, id, e.Paths.Last())
|
||||
}));
|
||||
|
||||
|
||||
foreach (var match in matches)
|
||||
{
|
||||
if (match is IgnoredDirectly)
|
||||
Utils.Error($"File required for BSA {source.Path} creation doesn't exist: {match.To}");
|
||||
_compiler.ExtraFiles.Add(match);
|
||||
}
|
||||
|
||||
CreateBSA directive;
|
||||
using (var bsa = BSADispatch.OpenRead(source.AbsolutePath))
|
||||
{
|
||||
directive = new CreateBSA
|
||||
{
|
||||
To = source.Path,
|
||||
TempID = id,
|
||||
State = bsa.State,
|
||||
FileStates = bsa.Files.Select(f => f.State).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
return directive;
|
||||
}
|
||||
|
||||
[JsonObject("DeconstructBSAs")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new DeconstructBSAs(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
Wabbajack.Lib/CompilationSteps/DirectMatch.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System.Linq;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class DirectMatch : ACompilationStep
|
||||
{
|
||||
public DirectMatch(Compiler compiler) : base(compiler)
|
||||
{
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!_compiler.IndexedFiles.TryGetValue(source.Hash, out var found)) return null;
|
||||
var result = source.EvolveTo<FromArchive>();
|
||||
|
||||
var match = found.Where(f =>
|
||||
Path.GetFileName(f.Paths[f.Paths.Length - 1]) == Path.GetFileName(source.Path))
|
||||
.OrderBy(f => f.Paths.Length)
|
||||
.FirstOrDefault()
|
||||
?? found.OrderBy(f => f.Paths.Length).FirstOrDefault();
|
||||
|
||||
result.ArchiveHashPath = match.MakeRelativePaths();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
[JsonObject("DirectMatch")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new DirectMatch(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
Wabbajack.Lib/CompilationSteps/DropAll.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using Newtonsoft.Json;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class DropAll : ACompilationStep
|
||||
{
|
||||
public DropAll(Compiler compiler) : base(compiler)
|
||||
{
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
var result = source.EvolveTo<NoMatch>();
|
||||
result.Reason = "No Match in Stack";
|
||||
Utils.Log($"No match for: {source.Path}");
|
||||
return result;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
[JsonObject("DropAll")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new DropAll(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
Wabbajack.Lib/CompilationSteps/IStackStep.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public interface ICompilationStep
|
||||
{
|
||||
Directive Run(RawSourceFile source);
|
||||
IState GetState();
|
||||
}
|
||||
|
||||
public interface IState
|
||||
{
|
||||
ICompilationStep CreateStep(Compiler compiler);
|
||||
}
|
||||
}
|
64
Wabbajack.Lib/CompilationSteps/IgnoreDisabledMods.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IgnoreDisabledMods : ACompilationStep
|
||||
{
|
||||
private readonly IEnumerable<string> _allEnabledMods;
|
||||
|
||||
public IgnoreDisabledMods(Compiler compiler) : base(compiler)
|
||||
{
|
||||
var alwaysEnabled = _compiler.ModInis.Where(f => IsAlwaysEnabled(f.Value)).Select(f => f.Key).ToHashSet();
|
||||
|
||||
_allEnabledMods = _compiler.SelectedProfiles
|
||||
.SelectMany(p => File.ReadAllLines(Path.Combine(_compiler.MO2Folder, "profiles", p, "modlist.txt")))
|
||||
.Where(line => line.StartsWith("+") || line.EndsWith("_separator"))
|
||||
.Select(line => line.Substring(1))
|
||||
.Concat(alwaysEnabled)
|
||||
.Select(line => Path.Combine("mods", line) + "\\")
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!source.Path.StartsWith("mods") || _allEnabledMods.Any(mod => source.Path.StartsWith(mod)))
|
||||
return null;
|
||||
var r = source.EvolveTo<IgnoredDirectly>();
|
||||
r.Reason = "Disabled Mod";
|
||||
return r;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
|
||||
private static bool IsAlwaysEnabled(dynamic data)
|
||||
{
|
||||
if (data == null)
|
||||
return false;
|
||||
if (data.General != null && data.General.notes != null &&
|
||||
data.General.notes.Contains(
|
||||
Consts.WABBAJACK_ALWAYS_ENABLE))
|
||||
return true;
|
||||
if (data.General != null && data.General.comments != null &&
|
||||
data.General.notes.Contains(Consts.WABBAJACK_ALWAYS_ENABLE))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
[JsonObject("IgnoreDisabledMods")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IgnoreDisabledMods(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
Wabbajack.Lib/CompilationSteps/IgnoreEndsWith.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IgnoreEndsWith : ACompilationStep
|
||||
{
|
||||
private readonly string _postfix;
|
||||
private readonly string _reason;
|
||||
|
||||
public IgnoreEndsWith(Compiler compiler, string postfix) : base(compiler)
|
||||
{
|
||||
_postfix = postfix;
|
||||
_reason = $"Ignored because path ends with {postfix}";
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!source.Path.EndsWith(_postfix)) return null;
|
||||
var result = source.EvolveTo<IgnoredDirectly>();
|
||||
result.Reason = _reason;
|
||||
return result;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State(_postfix);
|
||||
}
|
||||
|
||||
[JsonObject("IgnoreEndsWith")]
|
||||
public class State : IState
|
||||
{
|
||||
public State(string postfix)
|
||||
{
|
||||
Postfix = postfix;
|
||||
}
|
||||
|
||||
public State()
|
||||
{
|
||||
}
|
||||
|
||||
public string Postfix { get; set; }
|
||||
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IgnoreEndsWith(compiler, Postfix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
Wabbajack.Lib/CompilationSteps/IgnoreGameFiles.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using Newtonsoft.Json;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IgnoreGameFiles : ACompilationStep
|
||||
{
|
||||
private readonly string _startDir;
|
||||
|
||||
public IgnoreGameFiles(Compiler compiler) : base(compiler)
|
||||
{
|
||||
_startDir = Consts.GameFolderFilesDir + "\\";
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!source.Path.StartsWith(_startDir)) return null;
|
||||
var i = source.EvolveTo<IgnoredDirectly>();
|
||||
i.Reason = "Default game file";
|
||||
return i;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
[JsonObject("IgnoreGameFiles")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IgnoreGameFiles(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
Wabbajack.Lib/CompilationSteps/IgnorePathContains.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IgnorePathContains : ACompilationStep
|
||||
{
|
||||
private readonly string _pattern;
|
||||
private readonly string _reason;
|
||||
|
||||
public IgnorePathContains(Compiler compiler, string pattern) : base(compiler)
|
||||
{
|
||||
_pattern = $"\\{pattern.Trim('\\')}\\";
|
||||
_reason = $"Ignored because path contains {_pattern}";
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!source.Path.Contains(_pattern)) return null;
|
||||
var result = source.EvolveTo<IgnoredDirectly>();
|
||||
result.Reason = _reason;
|
||||
return result;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State(_pattern);
|
||||
}
|
||||
|
||||
[JsonObject("IgnorePathContains")]
|
||||
public class State : IState
|
||||
{
|
||||
public State()
|
||||
{
|
||||
}
|
||||
|
||||
public State(string pattern)
|
||||
{
|
||||
Pattern = pattern;
|
||||
}
|
||||
|
||||
public string Pattern { get; set; }
|
||||
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IgnorePathContains(compiler, Pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
Wabbajack.Lib/CompilationSteps/IgnoreRegex.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IgnoreRegex : ACompilationStep
|
||||
{
|
||||
private readonly string _reason;
|
||||
private readonly Regex _regex;
|
||||
private readonly string _pattern;
|
||||
|
||||
public IgnoreRegex(Compiler compiler, string pattern) : base(compiler)
|
||||
{
|
||||
_pattern = pattern;
|
||||
_reason = $"Ignored because path matches regex {pattern}";
|
||||
_regex = new Regex(pattern);
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!_regex.IsMatch(source.Path)) return null;
|
||||
var result = source.EvolveTo<IgnoredDirectly>();
|
||||
result.Reason = _reason;
|
||||
return result;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State(_pattern);
|
||||
}
|
||||
|
||||
[JsonObject("IgnorePattern")]
|
||||
public class State : IState
|
||||
{
|
||||
public State()
|
||||
{
|
||||
}
|
||||
|
||||
public State(string pattern)
|
||||
{
|
||||
Pattern = pattern;
|
||||
}
|
||||
|
||||
public string Pattern { get; set; }
|
||||
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IgnoreRegex(compiler, Pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
Wabbajack.Lib/CompilationSteps/IgnoreStartsWith.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IgnoreStartsWith : ACompilationStep
|
||||
{
|
||||
private readonly string _prefix;
|
||||
private readonly string _reason;
|
||||
|
||||
public IgnoreStartsWith(Compiler compiler, string prefix) : base(compiler)
|
||||
{
|
||||
_prefix = prefix;
|
||||
_reason = string.Format("Ignored because path starts with {0}", _prefix);
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (source.Path.StartsWith(_prefix))
|
||||
{
|
||||
var result = source.EvolveTo<IgnoredDirectly>();
|
||||
result.Reason = _reason;
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State(_prefix);
|
||||
}
|
||||
|
||||
[JsonObject("IgnoreStartsWith")]
|
||||
public class State : IState
|
||||
{
|
||||
public State()
|
||||
{
|
||||
}
|
||||
|
||||
public State(string prefix)
|
||||
{
|
||||
Prefix = prefix;
|
||||
}
|
||||
|
||||
public string Prefix { get; set; }
|
||||
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IgnoreStartsWith(compiler, Prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IgnoreWabbajackInstallCruft : ACompilationStep
|
||||
{
|
||||
private readonly HashSet<string> _cruftFiles;
|
||||
|
||||
public IgnoreWabbajackInstallCruft(Compiler compiler) : base(compiler)
|
||||
{
|
||||
_cruftFiles = new HashSet<string>
|
||||
{
|
||||
"7z.dll", "7z.exe", "vfs_staged_files\\", "nexus.key_cache", "patch_cache\\",
|
||||
Consts.NexusCacheDirectory + "\\"
|
||||
};
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!_cruftFiles.Any(f => source.Path.StartsWith(f))) return null;
|
||||
var result = source.EvolveTo<IgnoredDirectly>();
|
||||
result.Reason = "Wabbajack Cruft file";
|
||||
return result;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
[JsonObject("IgnoreWabbajackInstallCruft")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IgnoreWabbajackInstallCruft(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
Wabbajack.Lib/CompilationSteps/IncludeAll.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IncludeAll : ACompilationStep
|
||||
{
|
||||
public IncludeAll(Compiler compiler) : base(compiler)
|
||||
{
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
var inline = source.EvolveTo<InlineFile>();
|
||||
inline.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath));
|
||||
return inline;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
[JsonObject("IncludeAll")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IncludeAll(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
Wabbajack.Lib/CompilationSteps/IncludeAllConfigs.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IncludeAllConfigs : ACompilationStep
|
||||
{
|
||||
public IncludeAllConfigs(Compiler compiler) : base(compiler)
|
||||
{
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!Consts.ConfigFileExtensions.Contains(Path.GetExtension(source.Path))) return null;
|
||||
var result = source.EvolveTo<InlineFile>();
|
||||
result.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath));
|
||||
return result;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
[JsonObject("IncludeAllConfigs")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IncludeAllConfigs(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
Wabbajack.Lib/CompilationSteps/IncludeDummyESPs.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IncludeDummyESPs : ACompilationStep
|
||||
{
|
||||
public IncludeDummyESPs(Compiler compiler) : base(compiler)
|
||||
{
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (Path.GetExtension(source.AbsolutePath) != ".esp" &&
|
||||
Path.GetExtension(source.AbsolutePath) != ".esm") return null;
|
||||
|
||||
var bsa = Path.Combine(Path.GetDirectoryName(source.AbsolutePath),
|
||||
Path.GetFileNameWithoutExtension(source.AbsolutePath) + ".bsa");
|
||||
var bsaTextures = Path.Combine(Path.GetDirectoryName(source.AbsolutePath),
|
||||
Path.GetFileNameWithoutExtension(source.AbsolutePath) + " - Textures.bsa");
|
||||
var espSize = new FileInfo(source.AbsolutePath).Length;
|
||||
|
||||
if (espSize > 250 || !File.Exists(bsa) && !File.Exists(bsaTextures)) return null;
|
||||
|
||||
var inline = source.EvolveTo<InlineFile>();
|
||||
inline.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath));
|
||||
return inline;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
|
||||
[JsonObject("IncludeDummyESPs")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IncludeDummyESPs(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
38
Wabbajack.Lib/CompilationSteps/IncludeLOOTFiles.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IncludeLootFiles : ACompilationStep
|
||||
{
|
||||
private readonly string _prefix;
|
||||
|
||||
public IncludeLootFiles(Compiler compiler) : base(compiler)
|
||||
{
|
||||
_prefix = Consts.LOOTFolderFilesDir + "\\";
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!source.Path.StartsWith(_prefix)) return null;
|
||||
var result = source.EvolveTo<InlineFile>();
|
||||
result.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath).ToBase64());
|
||||
return result;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
[JsonObject("IncludeLootFiles")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IncludeLootFiles(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
Wabbajack.Lib/CompilationSteps/IncludeModIniData.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IncludeModIniData : ACompilationStep
|
||||
{
|
||||
public IncludeModIniData(Compiler compiler) : base(compiler)
|
||||
{
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!source.Path.StartsWith("mods\\") || !source.Path.EndsWith("\\meta.ini")) return null;
|
||||
var e = source.EvolveTo<InlineFile>();
|
||||
e.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath));
|
||||
return e;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
[JsonObject("IncludeModIniData")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IncludeModIniData(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
42
Wabbajack.Lib/CompilationSteps/IncludeOtherProfiles.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IgnoreOtherProfiles : ACompilationStep
|
||||
{
|
||||
private readonly IEnumerable<string> _profiles;
|
||||
|
||||
public IgnoreOtherProfiles(Compiler compiler) : base(compiler)
|
||||
{
|
||||
_profiles = _compiler.SelectedProfiles
|
||||
.Select(p => Path.Combine("profiles", p) + "\\")
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!source.Path.StartsWith("profiles\\")) return null;
|
||||
if (_profiles.Any(profile => source.Path.StartsWith(profile))) return null;
|
||||
var c = source.EvolveTo<IgnoredDirectly>();
|
||||
c.Reason = "File not for selected profiles";
|
||||
return c;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
[JsonObject("IgnoreOtherProfiles")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IgnoreOtherProfiles(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
Wabbajack.Lib/CompilationSteps/IncludePatches.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
using VFS;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IncludePatches : ACompilationStep
|
||||
{
|
||||
private readonly Dictionary<string, IGrouping<string, VirtualFile>> _indexed;
|
||||
|
||||
public IncludePatches(Compiler compiler) : base(compiler)
|
||||
{
|
||||
_indexed = _compiler.IndexedFiles.Values
|
||||
.SelectMany(f => f)
|
||||
.GroupBy(f => Path.GetFileName(Enumerable.Last(f.Paths)).ToLower())
|
||||
.ToDictionary(f => f.Key);
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!_indexed.TryGetValue(Path.GetFileName(Enumerable.Last(source.File.Paths).ToLower()), out var value))
|
||||
return null;
|
||||
|
||||
var found = value.OrderByDescending(f => (f.TopLevelArchive ?? f).LastModified).First();
|
||||
|
||||
var e = source.EvolveTo<PatchedFromArchive>();
|
||||
e.ArchiveHashPath = found.MakeRelativePaths();
|
||||
e.To = source.Path;
|
||||
e.Hash = source.File.Hash;
|
||||
|
||||
Utils.TryGetPatch(found.Hash, source.File.Hash, out var data);
|
||||
|
||||
if (data != null)
|
||||
e.PatchID = _compiler.IncludeFile(data);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
[JsonObject("IncludePatches")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IncludePatches(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
Wabbajack.Lib/CompilationSteps/IncludePropertyFiles.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IncludePropertyFiles : ACompilationStep
|
||||
{
|
||||
public IncludePropertyFiles(Compiler compiler) : base(compiler)
|
||||
{
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
var files = new HashSet<string>
|
||||
{
|
||||
_compiler.ModListImage, _compiler.ModListReadme
|
||||
};
|
||||
if (!files.Any(f => source.AbsolutePath.Equals(f))) return null;
|
||||
if (!File.Exists(source.AbsolutePath)) return null;
|
||||
var isBanner = source.AbsolutePath == _compiler.ModListImage;
|
||||
//var isReadme = source.AbsolutePath == ModListReadme;
|
||||
var result = source.EvolveTo<PropertyFile>();
|
||||
result.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath));
|
||||
if (isBanner)
|
||||
{
|
||||
result.Type = PropertyType.Banner;
|
||||
_compiler.ModListImage = result.SourceDataID;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Type = PropertyType.Readme;
|
||||
_compiler.ModListReadme = result.SourceDataID;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
[JsonObject("IncludePropertyFiles")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IncludePropertyFiles(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
Wabbajack.Lib/CompilationSteps/IncludeRegex.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IncludeRegex : ACompilationStep
|
||||
{
|
||||
private readonly string _pattern;
|
||||
private readonly Regex _regex;
|
||||
|
||||
public IncludeRegex(Compiler compiler, string pattern) : base(compiler)
|
||||
{
|
||||
_pattern = pattern;
|
||||
_regex = new Regex(pattern);
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!_regex.IsMatch(source.Path)) return null;
|
||||
|
||||
var result = source.EvolveTo<InlineFile>();
|
||||
result.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath));
|
||||
return result;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State(_pattern);
|
||||
}
|
||||
|
||||
[JsonObject("IncludeRegex")]
|
||||
public class State : IState
|
||||
{
|
||||
public State()
|
||||
{
|
||||
}
|
||||
|
||||
public State(string pattern)
|
||||
{
|
||||
Pattern = pattern;
|
||||
}
|
||||
|
||||
public string Pattern { get; set; }
|
||||
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IncludeRegex(compiler, Pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
58
Wabbajack.Lib/CompilationSteps/IncludeStubbedConfigfiles.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using System.Text;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IncludeStubbedConfigFiles : ACompilationStep
|
||||
{
|
||||
public IncludeStubbedConfigFiles(Compiler compiler) : base(compiler)
|
||||
{
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
return Consts.ConfigFileExtensions.Contains(Path.GetExtension(source.Path)) ? RemapFile(source) : null;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
private Directive RemapFile(RawSourceFile source)
|
||||
{
|
||||
var data = File.ReadAllText(source.AbsolutePath);
|
||||
var originalData = data;
|
||||
|
||||
data = data.Replace(_compiler.GamePath, Consts.GAME_PATH_MAGIC_BACK);
|
||||
data = data.Replace(_compiler.GamePath.Replace("\\", "\\\\"), Consts.GAME_PATH_MAGIC_DOUBLE_BACK);
|
||||
data = data.Replace(_compiler.GamePath.Replace("\\", "/"), Consts.GAME_PATH_MAGIC_FORWARD);
|
||||
|
||||
data = data.Replace(_compiler.MO2Folder, Consts.MO2_PATH_MAGIC_BACK);
|
||||
data = data.Replace(_compiler.MO2Folder.Replace("\\", "\\\\"), Consts.MO2_PATH_MAGIC_DOUBLE_BACK);
|
||||
data = data.Replace(_compiler.MO2Folder.Replace("\\", "/"), Consts.MO2_PATH_MAGIC_FORWARD);
|
||||
|
||||
data = data.Replace(_compiler.MO2DownloadsFolder, Consts.DOWNLOAD_PATH_MAGIC_BACK);
|
||||
data = data.Replace(_compiler.MO2DownloadsFolder.Replace("\\", "\\\\"),
|
||||
Consts.DOWNLOAD_PATH_MAGIC_DOUBLE_BACK);
|
||||
data = data.Replace(_compiler.MO2DownloadsFolder.Replace("\\", "/"), Consts.DOWNLOAD_PATH_MAGIC_FORWARD);
|
||||
|
||||
if (data == originalData)
|
||||
return null;
|
||||
var result = source.EvolveTo<RemappedInlineFile>();
|
||||
result.SourceDataID = _compiler.IncludeFile(Encoding.UTF8.GetBytes(data));
|
||||
return result;
|
||||
}
|
||||
|
||||
[JsonObject("IncludeStubbedConfigFiles")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IncludeStubbedConfigFiles(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
65
Wabbajack.Lib/CompilationSteps/IncludeTaggedMods.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IncludeTaggedMods : ACompilationStep
|
||||
{
|
||||
private readonly IEnumerable<string> _includeDirectly;
|
||||
private readonly string _tag;
|
||||
|
||||
|
||||
public IncludeTaggedMods(Compiler compiler, string tag) : base(compiler)
|
||||
{
|
||||
_tag = tag;
|
||||
_includeDirectly = _compiler.ModInis.Where(kv =>
|
||||
{
|
||||
var general = kv.Value.General;
|
||||
if (general.notes != null && general.notes.Contains(_tag))
|
||||
return true;
|
||||
return general.comments != null && general.comments.Contains(_tag);
|
||||
}).Select(kv => $"mods\\{kv.Key}\\");
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (!source.Path.StartsWith("mods")) return null;
|
||||
foreach (var modpath in _includeDirectly)
|
||||
{
|
||||
if (!source.Path.StartsWith(modpath)) continue;
|
||||
var result = source.EvolveTo<InlineFile>();
|
||||
result.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath));
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State(_tag);
|
||||
}
|
||||
|
||||
[JsonObject("IncludeTaggedMods")]
|
||||
public class State : IState
|
||||
{
|
||||
public State()
|
||||
{
|
||||
}
|
||||
|
||||
public State(string tag)
|
||||
{
|
||||
Tag = tag;
|
||||
}
|
||||
|
||||
public string Tag { get; set; }
|
||||
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IncludeTaggedMods(compiler, Tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
57
Wabbajack.Lib/CompilationSteps/IncludeThisProfile.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IncludeThisProfile : ACompilationStep
|
||||
{
|
||||
private readonly IEnumerable<string> _correctProfiles;
|
||||
|
||||
public IncludeThisProfile(Compiler compiler) : base(compiler)
|
||||
{
|
||||
_correctProfiles = _compiler.SelectedProfiles.Select(p => Path.Combine("profiles", p) + "\\").ToList();
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
if (_correctProfiles.Any(p => source.Path.StartsWith(p)))
|
||||
{
|
||||
var data = source.Path.EndsWith("\\modlist.txt")
|
||||
? ReadAndCleanModlist(source.AbsolutePath)
|
||||
: File.ReadAllBytes(source.AbsolutePath);
|
||||
|
||||
var e = source.EvolveTo<InlineFile>();
|
||||
e.SourceDataID = _compiler.IncludeFile(data);
|
||||
return e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
private static byte[] ReadAndCleanModlist(string absolutePath)
|
||||
{
|
||||
var lines = File.ReadAllLines(absolutePath);
|
||||
lines = (from line in lines
|
||||
where !(line.StartsWith("-") && !line.EndsWith("_separator"))
|
||||
select line).ToArray();
|
||||
return Encoding.UTF8.GetBytes(string.Join("\r\n", lines));
|
||||
}
|
||||
|
||||
[JsonObject("IncludeThisProfile")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new IncludeThisProfile(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
Wabbajack.Lib/CompilationSteps/PatchStockESMs.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Wabbajack.Common;
|
||||
using File = Alphaleonis.Win32.Filesystem.File;
|
||||
using Path = Alphaleonis.Win32.Filesystem.Path;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class PatchStockESMs : ACompilationStep
|
||||
{
|
||||
public PatchStockESMs(Compiler compiler) : base(compiler)
|
||||
{
|
||||
}
|
||||
|
||||
public override Directive Run(RawSourceFile source)
|
||||
{
|
||||
var filename = Path.GetFileName(source.Path);
|
||||
var gameFile = Path.Combine(_compiler.GamePath, "Data", filename);
|
||||
if (!Consts.GameESMs.Contains(filename) || !source.Path.StartsWith("mods\\") ||
|
||||
!File.Exists(gameFile)) return null;
|
||||
|
||||
Utils.Log(
|
||||
$"A ESM named {filename} was found in a mod that shares a name with a core game ESMs, it is assumed this is a cleaned ESM and it will be binary patched.");
|
||||
var result = source.EvolveTo<CleanedESM>();
|
||||
result.SourceESMHash = _compiler.VFS.Lookup(gameFile).Hash;
|
||||
|
||||
Utils.Status($"Generating patch of {filename}");
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
Utils.CreatePatch(File.ReadAllBytes(gameFile), File.ReadAllBytes(source.AbsolutePath), ms);
|
||||
var data = ms.ToArray();
|
||||
result.SourceDataID = _compiler.IncludeFile(data);
|
||||
Utils.Log($"Generated a {data.Length} byte patch for {filename}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override IState GetState()
|
||||
{
|
||||
return new State();
|
||||
}
|
||||
|
||||
[JsonObject("PatchStockESMs")]
|
||||
public class State : IState
|
||||
{
|
||||
public ICompilationStep CreateStep(Compiler compiler)
|
||||
{
|
||||
return new PatchStockESMs(compiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
Wabbajack.Lib/CompilationSteps/Serialization.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public static class Serialization
|
||||
{
|
||||
public static string Serialize(IEnumerable<ICompilationStep> stack)
|
||||
{
|
||||
return stack.Select(s => s.GetState()).ToList()
|
||||
.ToJSON(TypeNameHandling.Auto, TypeNameAssemblyFormatHandling.Simple);
|
||||
}
|
||||
|
||||
public static List<ICompilationStep> Deserialize(string stack, Compiler compiler)
|
||||
{
|
||||
return stack.FromJSONString<List<IState>>(TypeNameHandling.Auto, TypeNameAssemblyFormatHandling.Simple)
|
||||
.Select(s => s.CreateStep(compiler)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
605
Wabbajack.Lib/Compiler.cs
Normal file
@ -0,0 +1,605 @@
|
||||
using CommonMark;
|
||||
using Compression.BSA;
|
||||
using System;
|
||||
using System.CodeDom;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using VFS;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Lib.CompilationSteps;
|
||||
using Wabbajack.Lib.Downloaders;
|
||||
using Wabbajack.Lib.NexusApi;
|
||||
using Wabbajack.Lib.Validation;
|
||||
using Directory = Alphaleonis.Win32.Filesystem.Directory;
|
||||
using File = Alphaleonis.Win32.Filesystem.File;
|
||||
using FileInfo = Alphaleonis.Win32.Filesystem.FileInfo;
|
||||
using Path = Alphaleonis.Win32.Filesystem.Path;
|
||||
|
||||
namespace Wabbajack.Lib
|
||||
{
|
||||
public class Compiler
|
||||
{
|
||||
private string _mo2DownloadsFolder;
|
||||
|
||||
public Dictionary<string, IEnumerable<IndexedFileMatch>> DirectMatchIndex;
|
||||
|
||||
|
||||
public string MO2Folder;
|
||||
|
||||
|
||||
public string MO2Profile;
|
||||
public string ModListName, ModListAuthor, ModListDescription, ModListWebsite, ModListImage, ModListReadme;
|
||||
public string WabbajackVersion;
|
||||
|
||||
public Compiler(string mo2_folder)
|
||||
{
|
||||
MO2Folder = mo2_folder;
|
||||
MO2Ini = Path.Combine(MO2Folder, "ModOrganizer.ini").LoadIniFile();
|
||||
GamePath = ((string)MO2Ini.General.gamePath).Replace("\\\\", "\\");
|
||||
}
|
||||
|
||||
public dynamic MO2Ini { get; }
|
||||
public string GamePath { get; }
|
||||
|
||||
public bool ShowReportWhenFinished { get; set; } = true;
|
||||
|
||||
public bool IgnoreMissingFiles { get; set; }
|
||||
|
||||
public string MO2DownloadsFolder
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_mo2DownloadsFolder != null) return _mo2DownloadsFolder;
|
||||
if (MO2Ini != null)
|
||||
if (MO2Ini.Settings != null)
|
||||
if (MO2Ini.Settings.download_directory != null)
|
||||
return MO2Ini.Settings.download_directory.Replace("/", "\\");
|
||||
return Path.Combine(MO2Folder, "downloads");
|
||||
}
|
||||
set => _mo2DownloadsFolder = value;
|
||||
}
|
||||
|
||||
public string MO2ProfileDir => Path.Combine(MO2Folder, "profiles", MO2Profile);
|
||||
|
||||
public string ModListOutputFolder => "output_folder";
|
||||
public string ModListOutputFile => MO2Profile + ExtensionManager.Extension;
|
||||
|
||||
public List<Directive> InstallDirectives { get; private set; }
|
||||
internal UserStatus User { get; private set; }
|
||||
public List<Archive> SelectedArchives { get; private set; }
|
||||
public List<RawSourceFile> AllFiles { get; private set; }
|
||||
public ModList ModList { get; private set; }
|
||||
public ConcurrentBag<Directive> ExtraFiles { get; private set; }
|
||||
public Dictionary<string, dynamic> ModInis { get; private set; }
|
||||
|
||||
public VirtualFileSystem VFS => VirtualFileSystem.VFS;
|
||||
|
||||
public List<IndexedArchive> IndexedArchives { get; private set; }
|
||||
public Dictionary<string, IEnumerable<VirtualFile>> IndexedFiles { get; private set; }
|
||||
|
||||
public HashSet<string> SelectedProfiles { get; set; } = new HashSet<string>();
|
||||
|
||||
public void Info(string msg)
|
||||
{
|
||||
Utils.Log(msg);
|
||||
}
|
||||
|
||||
public void Status(string msg)
|
||||
{
|
||||
WorkQueue.Report(msg, 0);
|
||||
}
|
||||
|
||||
private void Error(string msg)
|
||||
{
|
||||
Utils.Log(msg);
|
||||
throw new Exception(msg);
|
||||
}
|
||||
|
||||
internal string IncludeFile(byte[] data)
|
||||
{
|
||||
var id = Guid.NewGuid().ToString();
|
||||
File.WriteAllBytes(Path.Combine(ModListOutputFolder, id), data);
|
||||
return id;
|
||||
}
|
||||
|
||||
internal string IncludeFile(string data)
|
||||
{
|
||||
var id = Guid.NewGuid().ToString();
|
||||
File.WriteAllText(Path.Combine(ModListOutputFolder, id), data);
|
||||
return id;
|
||||
}
|
||||
|
||||
public bool Compile()
|
||||
{
|
||||
VirtualFileSystem.Clean();
|
||||
Info("Looking for other profiles");
|
||||
var other_profiles_path = Path.Combine(MO2ProfileDir, "otherprofiles.txt");
|
||||
SelectedProfiles = new HashSet<string>();
|
||||
if (File.Exists(other_profiles_path)) SelectedProfiles = File.ReadAllLines(other_profiles_path).ToHashSet();
|
||||
SelectedProfiles.Add(MO2Profile);
|
||||
|
||||
Info("Using Profiles: " + string.Join(", ", SelectedProfiles.OrderBy(p => p)));
|
||||
|
||||
Info($"Indexing {MO2Folder}");
|
||||
VFS.AddRoot(MO2Folder);
|
||||
Info($"Indexing {GamePath}");
|
||||
VFS.AddRoot(GamePath);
|
||||
|
||||
Info($"Indexing {MO2DownloadsFolder}");
|
||||
VFS.AddRoot(MO2DownloadsFolder);
|
||||
|
||||
Info("Cleaning output folder");
|
||||
if (Directory.Exists(ModListOutputFolder))
|
||||
Directory.Delete(ModListOutputFolder, true);
|
||||
|
||||
Directory.CreateDirectory(ModListOutputFolder);
|
||||
|
||||
var mo2_files = Directory.EnumerateFiles(MO2Folder, "*", SearchOption.AllDirectories)
|
||||
.Where(p => p.FileExists())
|
||||
.Select(p => new RawSourceFile(VFS.Lookup(p)) { Path = p.RelativeTo(MO2Folder) });
|
||||
|
||||
var game_files = Directory.EnumerateFiles(GamePath, "*", SearchOption.AllDirectories)
|
||||
.Where(p => p.FileExists())
|
||||
.Select(p => new RawSourceFile(VFS.Lookup(p))
|
||||
{ Path = Path.Combine(Consts.GameFolderFilesDir, p.RelativeTo(GamePath)) });
|
||||
|
||||
var loot_path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"LOOT");
|
||||
|
||||
// TODO: make this generic so we can add more paths
|
||||
IEnumerable<RawSourceFile> loot_files = new List<RawSourceFile>();
|
||||
if (Directory.Exists(loot_path))
|
||||
{
|
||||
Info($"Indexing {loot_path}");
|
||||
VFS.AddRoot(loot_path);
|
||||
|
||||
loot_files = Directory.EnumerateFiles(loot_path, "userlist.yaml", SearchOption.AllDirectories)
|
||||
.Where(p => p.FileExists())
|
||||
.Select(p => new RawSourceFile(VFS.Lookup(p))
|
||||
{ Path = Path.Combine(Consts.LOOTFolderFilesDir, p.RelativeTo(loot_path)) });
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Info("Indexing Archives");
|
||||
IndexedArchives = Directory.EnumerateFiles(MO2DownloadsFolder)
|
||||
.Where(f => File.Exists(f + ".meta"))
|
||||
.Select(f => new IndexedArchive
|
||||
{
|
||||
File = VFS.Lookup(f),
|
||||
Name = Path.GetFileName(f),
|
||||
IniData = (f + ".meta").LoadIniFile(),
|
||||
Meta = File.ReadAllText(f + ".meta")
|
||||
})
|
||||
.ToList();
|
||||
|
||||
Info("Indexing Files");
|
||||
var grouped = VFS.GroupedByArchive();
|
||||
IndexedFiles = IndexedArchives.Select(f =>
|
||||
{
|
||||
if (grouped.TryGetValue(f.File, out var result))
|
||||
return result;
|
||||
return new List<VirtualFile>();
|
||||
})
|
||||
.SelectMany(fs => fs)
|
||||
.Concat(IndexedArchives.Select(f => f.File))
|
||||
.OrderByDescending(f => f.TopLevelArchive.LastModified)
|
||||
.GroupBy(f => f.Hash)
|
||||
.ToDictionary(f => f.Key, f => f.AsEnumerable());
|
||||
|
||||
Info("Searching for mod files");
|
||||
|
||||
AllFiles = mo2_files.Concat(game_files)
|
||||
.Concat(loot_files)
|
||||
.DistinctBy(f => f.Path)
|
||||
.ToList();
|
||||
|
||||
Info($"Found {AllFiles.Count} files to build into mod list");
|
||||
|
||||
Info("Verifying destinations");
|
||||
var dups = AllFiles.GroupBy(f => f.Path)
|
||||
.Where(fs => fs.Count() > 1)
|
||||
.Select(fs =>
|
||||
{
|
||||
Utils.Log($"Duplicate files installed to {fs.Key} from : {String.Join(", ", fs.Select(f => f.AbsolutePath))}");
|
||||
return fs;
|
||||
}).ToList();
|
||||
|
||||
if (dups.Count > 0)
|
||||
{
|
||||
Error($"Found {dups.Count} duplicates, exiting");
|
||||
}
|
||||
|
||||
ExtraFiles = new ConcurrentBag<Directive>();
|
||||
|
||||
ModInis = Directory.EnumerateDirectories(Path.Combine(MO2Folder, "mods"))
|
||||
.Select(f =>
|
||||
{
|
||||
var mod_name = Path.GetFileName(f);
|
||||
var meta_path = Path.Combine(f, "meta.ini");
|
||||
if (File.Exists(meta_path))
|
||||
return (mod_name, meta_path.LoadIniFile());
|
||||
return (null, null);
|
||||
})
|
||||
.Where(f => f.Item2 != null)
|
||||
.ToDictionary(f => f.Item1, f => f.Item2);
|
||||
|
||||
var stack = MakeStack();
|
||||
|
||||
|
||||
Info("Running Compilation Stack");
|
||||
var results = AllFiles.PMap(f => RunStack(stack, f)).ToList();
|
||||
|
||||
// Add the extra files that were generated by the stack
|
||||
Info($"Adding {ExtraFiles.Count} that were generated by the stack");
|
||||
results = results.Concat(ExtraFiles).ToList();
|
||||
|
||||
var nomatch = results.OfType<NoMatch>();
|
||||
Info($"No match for {nomatch.Count()} files");
|
||||
foreach (var file in nomatch)
|
||||
Info($" {file.To}");
|
||||
if (nomatch.Count() > 0)
|
||||
{
|
||||
if (IgnoreMissingFiles)
|
||||
{
|
||||
Info("Continuing even though files were missing at the request of the user.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Info("Exiting due to no way to compile these files");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
InstallDirectives = results.Where(i => !(i is IgnoredDirectly)).ToList();
|
||||
|
||||
Info("Getting Nexus api_key, please click authorize if a browser window appears");
|
||||
|
||||
if (IndexedArchives.Any(a => a.IniData?.General?.gameName != null))
|
||||
{
|
||||
var nexusClient = new NexusApiClient();
|
||||
if (!nexusClient.IsPremium) Error($"User {nexusClient.Username} is not a premium Nexus user, so we cannot access the necessary API calls, cannot continue");
|
||||
|
||||
}
|
||||
|
||||
zEditIntegration.VerifyMerges(this);
|
||||
|
||||
GatherArchives();
|
||||
BuildPatches();
|
||||
|
||||
ModList = new ModList
|
||||
{
|
||||
GameType = GameRegistry.Games.Values.First(f => f.MO2Name == MO2Ini.General.gameName).Game,
|
||||
WabbajackVersion = WabbajackVersion,
|
||||
Archives = SelectedArchives,
|
||||
Directives = InstallDirectives,
|
||||
Name = ModListName ?? MO2Profile,
|
||||
Author = ModListAuthor ?? "",
|
||||
Description = ModListDescription ?? "",
|
||||
Readme = ModListReadme ?? "",
|
||||
Image = ModListImage ?? "",
|
||||
Website = ModListWebsite ?? ""
|
||||
};
|
||||
|
||||
ValidateModlist.RunValidation(ModList);
|
||||
|
||||
GenerateReport();
|
||||
ExportModlist();
|
||||
|
||||
ResetMembers();
|
||||
|
||||
ShowReport();
|
||||
|
||||
Info("Done Building Modlist");
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ExportModlist()
|
||||
{
|
||||
Utils.Log($"Exporting Modlist to : {ModListOutputFile}");
|
||||
|
||||
//ModList.ToJSON(Path.Combine(ModListOutputFolder, "modlist.json"));
|
||||
ModList.ToCERAS(Path.Combine(ModListOutputFolder, "modlist"), ref CerasConfig.Config);
|
||||
|
||||
if (File.Exists(ModListOutputFile))
|
||||
File.Delete(ModListOutputFile);
|
||||
|
||||
using (var fs = new FileStream(ModListOutputFile, FileMode.Create))
|
||||
{
|
||||
using (var za = new ZipArchive(fs, ZipArchiveMode.Create))
|
||||
{
|
||||
Directory.EnumerateFiles(ModListOutputFolder, "*.*")
|
||||
.DoProgress("Compressing Modlist",
|
||||
f =>
|
||||
{
|
||||
var ze = za.CreateEntry(Path.GetFileName(f));
|
||||
using (var os = ze.Open())
|
||||
using (var ins = File.OpenRead(f))
|
||||
{
|
||||
ins.CopyTo(os);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Utils.Log("Removing modlist staging folder");
|
||||
Directory.Delete(ModListOutputFolder, true);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void ShowReport()
|
||||
{
|
||||
if (!ShowReportWhenFinished) return;
|
||||
|
||||
var file = Path.GetTempFileName() + ".html";
|
||||
File.WriteAllText(file, ModList.ReportHTML);
|
||||
Process.Start(file);
|
||||
}
|
||||
|
||||
private void GenerateReport()
|
||||
{
|
||||
string css = "";
|
||||
using (Stream cssStream = Utils.GetResourceStream("Wabbajack.Lib.css-min.css"))
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(cssStream))
|
||||
{
|
||||
css = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
using (var fs = File.OpenWrite($"{ModList.Name}.md"))
|
||||
{
|
||||
fs.SetLength(0);
|
||||
using (var reporter = new ReportBuilder(fs, ModListOutputFolder))
|
||||
{
|
||||
reporter.Build(this, ModList);
|
||||
}
|
||||
}
|
||||
|
||||
ModList.ReportHTML = "<style>" + css + "</style>"
|
||||
+ CommonMarkConverter.Convert(File.ReadAllText($"{ModList.Name}.md"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear references to lists that hold a lot of data.
|
||||
/// </summary>
|
||||
private void ResetMembers()
|
||||
{
|
||||
AllFiles = null;
|
||||
InstallDirectives = null;
|
||||
SelectedArchives = null;
|
||||
ExtraFiles = null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fills in the Patch fields in files that require them
|
||||
/// </summary>
|
||||
private void BuildPatches()
|
||||
{
|
||||
Info("Gathering patch files");
|
||||
var groups = InstallDirectives.OfType<PatchedFromArchive>()
|
||||
.Where(p => p.PatchID == null)
|
||||
.GroupBy(p => p.ArchiveHashPath[0])
|
||||
.ToList();
|
||||
|
||||
Info($"Patching building patches from {groups.Count} archives");
|
||||
var absolute_paths = AllFiles.ToDictionary(e => e.Path, e => e.AbsolutePath);
|
||||
groups.PMap(group => BuildArchivePatches(group.Key, group, absolute_paths));
|
||||
|
||||
if (InstallDirectives.OfType<PatchedFromArchive>().FirstOrDefault(f => f.PatchID == null) != null)
|
||||
Error("Missing patches after generation, this should not happen");
|
||||
}
|
||||
|
||||
private void BuildArchivePatches(string archive_sha, IEnumerable<PatchedFromArchive> group,
|
||||
Dictionary<string, string> absolute_paths)
|
||||
{
|
||||
var archive = VFS.HashIndex[archive_sha];
|
||||
using (var files = VFS.StageWith(group.Select(g => VFS.FileForArchiveHashPath(g.ArchiveHashPath))))
|
||||
{
|
||||
var by_path = files.GroupBy(f => string.Join("|", f.Paths.Skip(1)))
|
||||
.ToDictionary(f => f.Key, f => f.First());
|
||||
// Now Create the patches
|
||||
group.PMap(entry =>
|
||||
{
|
||||
Info($"Patching {entry.To}");
|
||||
Status($"Patching {entry.To}");
|
||||
using (var origin = by_path[string.Join("|", entry.ArchiveHashPath.Skip(1))].OpenRead())
|
||||
using (var output = new MemoryStream())
|
||||
{
|
||||
var a = origin.ReadAll();
|
||||
var b = LoadDataForTo(entry.To, absolute_paths);
|
||||
Utils.CreatePatch(a, b, output);
|
||||
entry.PatchID = IncludeFile(output.ToArray());
|
||||
var file_size = File.GetSize(Path.Combine(ModListOutputFolder, entry.PatchID));
|
||||
Info($"Patch size {file_size} for {entry.To}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] LoadDataForTo(string to, Dictionary<string, string> absolute_paths)
|
||||
{
|
||||
if (absolute_paths.TryGetValue(to, out var absolute))
|
||||
return File.ReadAllBytes(absolute);
|
||||
|
||||
if (to.StartsWith(Consts.BSACreationDir))
|
||||
{
|
||||
var bsa_id = to.Split('\\')[1];
|
||||
var bsa = InstallDirectives.OfType<CreateBSA>().First(b => b.TempID == bsa_id);
|
||||
|
||||
using (var a = BSADispatch.OpenRead(Path.Combine(MO2Folder, bsa.To)))
|
||||
{
|
||||
var find = Path.Combine(to.Split('\\').Skip(2).ToArray());
|
||||
var file = a.Files.First(e => e.Path.Replace('/', '\\') == find);
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
file.CopyDataTo(ms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Error($"Couldn't load data for {to}");
|
||||
return null;
|
||||
}
|
||||
|
||||
private void GatherArchives()
|
||||
{
|
||||
Info("Building a list of archives based on the files required");
|
||||
|
||||
var shas = InstallDirectives.OfType<FromArchive>()
|
||||
.Select(a => a.ArchiveHashPath[0])
|
||||
.Distinct();
|
||||
|
||||
var archives = IndexedArchives.OrderByDescending(f => f.File.LastModified)
|
||||
.GroupBy(f => f.File.Hash)
|
||||
.ToDictionary(f => f.Key, f => f.First());
|
||||
|
||||
SelectedArchives = shas.PMap(sha => ResolveArchive(sha, archives));
|
||||
}
|
||||
|
||||
private Archive ResolveArchive(string sha, IDictionary<string, IndexedArchive> archives)
|
||||
{
|
||||
if (archives.TryGetValue(sha, out var found))
|
||||
{
|
||||
if (found.IniData == null)
|
||||
Error($"No download metadata found for {found.Name}, please use MO2 to query info or add a .meta file and try again.");
|
||||
|
||||
var result = new Archive();
|
||||
result.State = (AbstractDownloadState)DownloadDispatcher.ResolveArchive(found.IniData);
|
||||
|
||||
if (result.State == null)
|
||||
Error($"{found.Name} could not be handled by any of the downloaders");
|
||||
|
||||
result.Name = found.Name;
|
||||
result.Hash = found.File.Hash;
|
||||
result.Meta = found.Meta;
|
||||
result.Size = found.File.Size;
|
||||
|
||||
Info($"Checking link for {found.Name}");
|
||||
|
||||
if (!result.State.Verify())
|
||||
Error(
|
||||
$"Unable to resolve link for {found.Name}. If this is hosted on the Nexus the file may have been removed.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Error($"No match found for Archive sha: {sha} this shouldn't happen");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static Directive RunStack(IEnumerable<ICompilationStep> stack, RawSourceFile source)
|
||||
{
|
||||
Utils.Status($"Compiling {source.Path}");
|
||||
foreach (var step in stack)
|
||||
{
|
||||
var result = step.Run(source);
|
||||
if (result != null) return result;
|
||||
}
|
||||
|
||||
throw new InvalidDataException("Data fell out of the compilation stack");
|
||||
}
|
||||
|
||||
public IEnumerable<ICompilationStep> GetStack()
|
||||
{
|
||||
var user_config = Path.Combine(MO2ProfileDir, "compilation_stack.yml");
|
||||
if (File.Exists(user_config))
|
||||
return Serialization.Deserialize(File.ReadAllText(user_config), this);
|
||||
|
||||
var stack = MakeStack();
|
||||
|
||||
File.WriteAllText(Path.Combine(MO2ProfileDir, "_current_compilation_stack.yml"),
|
||||
Serialization.Serialize(stack));
|
||||
|
||||
return stack;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a execution stack. The stack should be passed into Run stack. Each function
|
||||
/// in this stack will be run in-order and the first to return a non-null result will have its
|
||||
/// result included into the pack
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<ICompilationStep> MakeStack()
|
||||
{
|
||||
Utils.Log("Generating compilation stack");
|
||||
return new List<ICompilationStep>
|
||||
{
|
||||
new IncludePropertyFiles(this),
|
||||
new IgnoreStartsWith(this,"logs\\"),
|
||||
new IncludeRegex(this, "^downloads\\\\.*\\.meta"),
|
||||
new IgnoreStartsWith(this, "downloads\\"),
|
||||
new IgnoreStartsWith(this,"webcache\\"),
|
||||
new IgnoreStartsWith(this, "overwrite\\"),
|
||||
new IgnorePathContains(this,"temporary_logs"),
|
||||
new IgnorePathContains(this, "GPUCache"),
|
||||
new IgnorePathContains(this, "SSEEdit Cache"),
|
||||
new IgnoreEndsWith(this, ".pyc"),
|
||||
new IgnoreEndsWith(this, ".log"),
|
||||
new IgnoreOtherProfiles(this),
|
||||
new IgnoreDisabledMods(this),
|
||||
new IncludeThisProfile(this),
|
||||
// Ignore the ModOrganizer.ini file it contains info created by MO2 on startup
|
||||
new IncludeStubbedConfigFiles(this),
|
||||
new IncludeLootFiles(this),
|
||||
new IgnoreStartsWith(this, Path.Combine(Consts.GameFolderFilesDir, "Data")),
|
||||
new IgnoreStartsWith(this, Path.Combine(Consts.GameFolderFilesDir, "Papyrus Compiler")),
|
||||
new IgnoreStartsWith(this, Path.Combine(Consts.GameFolderFilesDir, "Skyrim")),
|
||||
new IgnoreRegex(this, Consts.GameFolderFilesDir + "\\\\.*\\.bsa"),
|
||||
new IncludeModIniData(this),
|
||||
new DirectMatch(this),
|
||||
new IncludeTaggedMods(this, Consts.WABBAJACK_INCLUDE),
|
||||
new DeconstructBSAs(this), // Deconstruct BSAs before building patches so we don't generate massive patch files
|
||||
new IncludePatches(this),
|
||||
new IncludeDummyESPs(this),
|
||||
|
||||
|
||||
// If we have no match at this point for a game folder file, skip them, we can't do anything about them
|
||||
new IgnoreGameFiles(this),
|
||||
|
||||
// There are some types of files that will error the compilation, because they're created on-the-fly via tools
|
||||
// so if we don't have a match by this point, just drop them.
|
||||
new IgnoreEndsWith(this, ".ini"),
|
||||
new IgnoreEndsWith(this, ".html"),
|
||||
new IgnoreEndsWith(this, ".txt"),
|
||||
// Don't know why, but this seems to get copied around a bit
|
||||
new IgnoreEndsWith(this, "HavokBehaviorPostProcess.exe"),
|
||||
// Theme file MO2 downloads somehow
|
||||
new IgnoreEndsWith(this, "splash.png"),
|
||||
|
||||
new IgnoreEndsWith(this, ".bin"),
|
||||
new IgnoreEndsWith(this, ".refcache"),
|
||||
|
||||
new IgnoreWabbajackInstallCruft(this),
|
||||
|
||||
new PatchStockESMs(this),
|
||||
|
||||
new IncludeAllConfigs(this),
|
||||
new zEditIntegration.IncludeZEditPatches(this),
|
||||
new IncludeTaggedMods(this, Consts.WABBAJACK_NOMATCH_INCLUDE),
|
||||
|
||||
new DropAll(this)
|
||||
};
|
||||
}
|
||||
|
||||
public class IndexedFileMatch
|
||||
{
|
||||
public IndexedArchive Archive;
|
||||
public IndexedArchiveEntry Entry;
|
||||
public DateTime LastModified;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Ceras;
|
||||
using Compression.BSA;
|
||||
using VFS;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Downloaders;
|
||||
using Wabbajack.Lib.Downloaders;
|
||||
|
||||
namespace Wabbajack
|
||||
namespace Wabbajack.Lib
|
||||
{
|
||||
public class RawSourceFile
|
||||
{
|
||||
@ -33,7 +33,6 @@ namespace Wabbajack
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ModList
|
||||
{
|
||||
/// <summary>
|
||||
@ -46,6 +45,11 @@ namespace Wabbajack
|
||||
/// </summary>
|
||||
public Game GameType;
|
||||
|
||||
/// <summary>
|
||||
/// The build version of Wabbajack used when compiling the Modlist
|
||||
/// </summary>
|
||||
public string WabbajackVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Install directives
|
||||
/// </summary>
|
||||
@ -87,7 +91,6 @@ namespace Wabbajack
|
||||
public string ReportHTML;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Directive
|
||||
{
|
||||
/// <summary>
|
||||
@ -98,18 +101,15 @@ namespace Wabbajack
|
||||
public string Hash;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class IgnoredDirectly : Directive
|
||||
{
|
||||
public string Reason;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class NoMatch : IgnoredDirectly
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class InlineFile : Directive
|
||||
{
|
||||
/// <summary>
|
||||
@ -123,13 +123,11 @@ namespace Wabbajack
|
||||
/// <summary>
|
||||
/// File meant to be extracted before the installation
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PropertyFile : InlineFile
|
||||
{
|
||||
public PropertyType Type;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class CleanedESM : InlineFile
|
||||
{
|
||||
public string SourceESMHash;
|
||||
@ -138,12 +136,11 @@ namespace Wabbajack
|
||||
/// <summary>
|
||||
/// A file that has the game and MO2 folders remapped on installation
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class RemappedInlineFile : InlineFile
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[MemberConfig(TargetMember.All)]
|
||||
public class FromArchive : Directive
|
||||
{
|
||||
private string _fullPath;
|
||||
@ -153,9 +150,10 @@ namespace Wabbajack
|
||||
/// </summary>
|
||||
public string[] ArchiveHashPath;
|
||||
|
||||
[JsonIgnore] [NonSerialized] public VirtualFile FromFile;
|
||||
[Exclude]
|
||||
public VirtualFile FromFile;
|
||||
|
||||
[JsonIgnore]
|
||||
[Exclude]
|
||||
public string FullPath
|
||||
{
|
||||
get
|
||||
@ -166,7 +164,6 @@ namespace Wabbajack
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class CreateBSA : Directive
|
||||
{
|
||||
public string TempID;
|
||||
@ -175,33 +172,26 @@ namespace Wabbajack
|
||||
public List<FileStateObject> FileStates { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class PatchedFromArchive : FromArchive
|
||||
{
|
||||
public string Hash;
|
||||
|
||||
/// <summary>
|
||||
/// The file to apply to the source file to patch it
|
||||
/// </summary>
|
||||
public String PatchID;
|
||||
public string PatchID;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class SourcePatch
|
||||
{
|
||||
public string RelativePath;
|
||||
public string Hash;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class MergedPatch : Directive
|
||||
{
|
||||
public List<SourcePatch> Sources;
|
||||
public string Hash;
|
||||
public string PatchID;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Archive
|
||||
{
|
||||
/// <summary>
|
||||
@ -222,7 +212,6 @@ namespace Wabbajack
|
||||
public AbstractDownloadState State { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class IndexedArchive
|
||||
{
|
||||
public dynamic IniData;
|
||||
@ -234,7 +223,6 @@ namespace Wabbajack
|
||||
/// <summary>
|
||||
/// A archive entry
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class IndexedEntry
|
||||
{
|
||||
/// <summary>
|
||||
@ -253,7 +241,6 @@ namespace Wabbajack
|
||||
public long Size;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class IndexedArchiveEntry : IndexedEntry
|
||||
{
|
||||
public string[] HashPath;
|
||||
@ -262,7 +249,6 @@ namespace Wabbajack
|
||||
/// <summary>
|
||||
/// Data found inside a BSA file in an archive
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class BSAIndexedEntry : IndexedEntry
|
||||
{
|
||||
/// <summary>
|
@ -3,9 +3,11 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Validation;
|
||||
using System.Windows.Forms.VisualStyles;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Wabbajack.Lib.Validation;
|
||||
|
||||
namespace Wabbajack.Downloaders
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
/// <summary>
|
||||
/// Base for all abstract downloaders
|
||||
@ -25,6 +27,11 @@ namespace Wabbajack.Downloaders
|
||||
/// <param name="destination"></param>
|
||||
public abstract void Download(Archive a, string destination);
|
||||
|
||||
public void Download(string destination)
|
||||
{
|
||||
Download(new Archive {Name = Path.GetFileName(destination)}, destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this link is still valid
|
||||
/// </summary>
|
@ -5,7 +5,7 @@ using System.Security.Policy;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wabbajack.Downloaders
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public static class DownloadDispatcher
|
||||
{
|
||||
@ -16,6 +16,7 @@ namespace Wabbajack.Downloaders
|
||||
new GoogleDriveDownloader(),
|
||||
new ModDBDownloader(),
|
||||
new NexusDownloader(),
|
||||
new MediaFireDownloader(),
|
||||
new ManualDownloader(),
|
||||
new HTTPDownloader()
|
||||
};
|
||||
@ -37,5 +38,16 @@ namespace Wabbajack.Downloaders
|
||||
return Downloaders.Select(d => d.GetDownloaderState(ini)).FirstOrDefault(result => result != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduced version of Resolve archive that requires less information, but only works
|
||||
/// with a single URL string
|
||||
/// </summary>
|
||||
/// <param name="ini"></param>
|
||||
/// <returns></returns>
|
||||
public static AbstractDownloadState ResolveArchive(string url)
|
||||
{
|
||||
return Downloaders.OfType<IUrlDownloader>().Select(d => d.GetDownloaderState(url)).FirstOrDefault(result => result != null);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
19
Wabbajack.Lib/Downloaders/DownloaderUtils.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public static class DownloaderUtils
|
||||
{
|
||||
public static Uri GetDirectURL(dynamic meta)
|
||||
{
|
||||
var url = meta?.General?.directURL;
|
||||
if (url == null) return null;
|
||||
|
||||
return Uri.TryCreate((string) url, UriKind.Absolute, out var result) ? result : null;
|
||||
}
|
||||
}
|
||||
}
|
@ -5,15 +5,20 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace Wabbajack.Downloaders
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public class DropboxDownloader : IDownloader
|
||||
public class DropboxDownloader : IDownloader, IUrlDownloader
|
||||
{
|
||||
public AbstractDownloadState GetDownloaderState(dynamic archive_ini)
|
||||
{
|
||||
var urlstring = archive_ini?.General?.directURL;
|
||||
if (urlstring == null) return null;
|
||||
var uri = new UriBuilder((string)urlstring);
|
||||
return GetDownloaderState(urlstring);
|
||||
}
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(string url)
|
||||
{
|
||||
if (url == null) return null;
|
||||
var uri = new UriBuilder(url);
|
||||
if (uri.Host != "www.dropbox.com") return null;
|
||||
var query = HttpUtility.ParseQueryString(uri.Query);
|
||||
|
@ -7,18 +7,23 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Validation;
|
||||
using Wabbajack.Lib.Validation;
|
||||
|
||||
namespace Wabbajack.Downloaders
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public class GoogleDriveDownloader : IDownloader
|
||||
public class GoogleDriveDownloader : IDownloader, IUrlDownloader
|
||||
{
|
||||
public AbstractDownloadState GetDownloaderState(dynamic archive_ini)
|
||||
{
|
||||
var url = archive_ini?.General?.directURL;
|
||||
var regex = new Regex("((?<=id=)[a-zA-Z0-9_-]*)|(?<=\\/file\\/d\\/)[a-zA-Z0-9_-]*");
|
||||
return GetDownloaderState(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
|
||||
{
|
||||
@ -54,7 +59,7 @@ namespace Wabbajack.Downloaders
|
||||
var regex = new Regex("(?<=/uc\\?export=download&confirm=).*(?=;id=)");
|
||||
var confirm = regex.Match(result);
|
||||
var url = $"https://drive.google.com/uc?export=download&confirm={confirm}&id={Id}";
|
||||
var http_state = new HTTPDownloader.State {Url = url};
|
||||
var http_state = new HTTPDownloader.State {Url = url, Client = client};
|
||||
return http_state;
|
||||
}
|
||||
|
@ -1,24 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.ServiceModel.Configuration;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Ceras;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Validation;
|
||||
using Wabbajack.Lib.Validation;
|
||||
using File = Alphaleonis.Win32.Filesystem.File;
|
||||
|
||||
namespace Wabbajack.Downloaders
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public class HTTPDownloader : IDownloader
|
||||
public class HTTPDownloader : IDownloader, IUrlDownloader
|
||||
{
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(dynamic archive_ini)
|
||||
{
|
||||
var url = archive_ini?.General?.directURL;
|
||||
return GetDownloaderState(url, archive_ini);
|
||||
|
||||
}
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(string uri)
|
||||
{
|
||||
return GetDownloaderState(uri, null);
|
||||
}
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(string url, dynamic archive_ini)
|
||||
{
|
||||
if (url != null)
|
||||
{
|
||||
var tmp = new State
|
||||
@ -40,13 +48,16 @@ namespace Wabbajack.Downloaders
|
||||
{
|
||||
}
|
||||
|
||||
[MemberConfig(TargetMember.All)]
|
||||
public class State : AbstractDownloadState
|
||||
{
|
||||
public string Url { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public List<string> Headers { get; set; }
|
||||
|
||||
[Exclude]
|
||||
public HttpClient Client { get; set; }
|
||||
|
||||
public override bool IsWhitelisted(ServerWhitelist whitelist)
|
||||
{
|
||||
return whitelist.AllowedPrefixes.Any(p => Url.StartsWith(p));
|
||||
@ -59,7 +70,7 @@ namespace Wabbajack.Downloaders
|
||||
|
||||
public bool DoDownload(Archive a, string destination, bool download)
|
||||
{
|
||||
var client = new HttpClient();
|
||||
var client = Client ?? new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("User-Agent", Consts.UserAgent);
|
||||
|
||||
if (Headers != null)
|
||||
@ -80,12 +91,12 @@ namespace Wabbajack.Downloaders
|
||||
{
|
||||
stream.Wait();
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
;
|
||||
if (stream.IsFaulted)
|
||||
if (stream.IsFaulted || response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
Utils.Log($"While downloading {Url} - {stream.Exception.ExceptionToString()}");
|
||||
return false;
|
@ -3,9 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Validation;
|
||||
using Wabbajack.Lib.Validation;
|
||||
|
||||
namespace Wabbajack.Downloaders
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public interface IDownloader
|
||||
{
|
||||
@ -16,4 +16,5 @@ namespace Wabbajack.Downloaders
|
||||
/// </summary>
|
||||
void Prepare();
|
||||
}
|
||||
|
||||
}
|
13
Wabbajack.Lib/Downloaders/IUrlDownloader.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public interface IUrlDownloader : IDownloader
|
||||
{
|
||||
AbstractDownloadState GetDownloaderState(string url);
|
||||
}
|
||||
}
|
@ -9,18 +9,23 @@ using System.Threading.Tasks;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using CG.Web.MegaApiClient;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Validation;
|
||||
using Wabbajack.Lib.Validation;
|
||||
|
||||
namespace Wabbajack.Downloaders
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public class MegaDownloader : IDownloader
|
||||
public class MegaDownloader : IDownloader, IUrlDownloader
|
||||
{
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(dynamic archive_ini)
|
||||
{
|
||||
var url = archive_ini?.General?.directURL;
|
||||
return GetDownloaderState(url);
|
||||
}
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(string url)
|
||||
{
|
||||
if (url != null && url.StartsWith(Consts.MegaPrefix))
|
||||
return new State {Url = url};
|
||||
return new State { Url = url };
|
||||
return null;
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Validation;
|
||||
using Wabbajack.Lib.Validation;
|
||||
|
||||
namespace Wabbajack.Downloaders
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
class ManualDownloader : IDownloader
|
||||
{
|
91
Wabbajack.Lib/Downloaders/MediaFireDownloader.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Lib.Validation;
|
||||
using Wabbajack.Lib.WebAutomation;
|
||||
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public class MediaFireDownloader : IUrlDownloader
|
||||
{
|
||||
public AbstractDownloadState GetDownloaderState(dynamic archive_ini)
|
||||
{
|
||||
Uri url = DownloaderUtils.GetDirectURL(archive_ini);
|
||||
if (url == null || url.Host != "www.mediafire.com") return null;
|
||||
|
||||
return new State
|
||||
{
|
||||
Url = url.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
public class State : AbstractDownloadState
|
||||
{
|
||||
public string Url { get; set; }
|
||||
|
||||
public override bool IsWhitelisted(ServerWhitelist whitelist)
|
||||
{
|
||||
return whitelist.AllowedPrefixes.Any(p => Url.StartsWith(p));
|
||||
}
|
||||
|
||||
public override void Download(Archive a, string destination)
|
||||
{
|
||||
Resolve().Result.Download(a, destination);
|
||||
}
|
||||
|
||||
public override bool Verify()
|
||||
{
|
||||
return Resolve().Result != null;
|
||||
}
|
||||
|
||||
private async Task<HTTPDownloader.State> Resolve()
|
||||
{
|
||||
using (var d = await Driver.Create())
|
||||
{
|
||||
await d.NavigateTo(new Uri("http://www.mediafire.com/file/agiqzm1xwebczpx/WABBAJACK_TEST_FILE.tx"));
|
||||
// MediaFire creates the link after all the JS loads
|
||||
await Task.Delay(1000);
|
||||
var new_url = await d.GetAttr("a.input", "href");
|
||||
if (new_url == null || !new_url.StartsWith("http")) return null;
|
||||
return new HTTPDownloader.State()
|
||||
{
|
||||
Client = new HttpClient(),
|
||||
Url = new_url
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override IDownloader GetDownloader()
|
||||
{
|
||||
return DownloadDispatcher.GetInstance<MediaFireDownloader>();
|
||||
}
|
||||
|
||||
public override string GetReportEntry(Archive a)
|
||||
{
|
||||
return $"* [{a.Name} - {Url}]({Url})";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void Prepare()
|
||||
{
|
||||
}
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(string u)
|
||||
{
|
||||
var url = new Uri(u);
|
||||
if (url.Host != "www.mediafire.com") return null;
|
||||
|
||||
return new State
|
||||
{
|
||||
Url = url.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -6,16 +6,20 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Validation;
|
||||
using Wabbajack.Lib.Validation;
|
||||
|
||||
namespace Wabbajack.Downloaders
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public class ModDBDownloader : IDownloader
|
||||
public class ModDBDownloader : IDownloader, IUrlDownloader
|
||||
{
|
||||
public AbstractDownloadState GetDownloaderState(dynamic archive_ini)
|
||||
{
|
||||
var url = archive_ini?.General?.directURL;
|
||||
return GetDownloaderState(url);
|
||||
}
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(string url)
|
||||
{
|
||||
if (url != null && url.StartsWith("https://www.moddb.com/downloads/start"))
|
||||
{
|
||||
return new State
|
@ -4,10 +4,10 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.NexusApi;
|
||||
using Wabbajack.Validation;
|
||||
using Wabbajack.Lib.NexusApi;
|
||||
using Wabbajack.Lib.Validation;
|
||||
|
||||
namespace Wabbajack.Downloaders
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public class NexusDownloader : IDownloader
|
||||
{
|
||||
@ -103,8 +103,8 @@ namespace Wabbajack.Downloaders
|
||||
{
|
||||
try
|
||||
{
|
||||
new NexusApiClient().GetNexusDownloadLink(this, true);
|
||||
return true;
|
||||
var info = new NexusApiClient().GetFileInfo(this);
|
||||
return info.category_name != null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
@ -1,25 +1,21 @@
|
||||
using CG.Web.MegaApiClient;
|
||||
using Compression.BSA;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using VFS;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Downloaders;
|
||||
using Wabbajack.NexusApi;
|
||||
using Wabbajack.Validation;
|
||||
using Wabbajack.Lib.Downloaders;
|
||||
using Wabbajack.Lib.NexusApi;
|
||||
using Wabbajack.Lib.Validation;
|
||||
using Directory = Alphaleonis.Win32.Filesystem.Directory;
|
||||
using File = Alphaleonis.Win32.Filesystem.File;
|
||||
using FileInfo = Alphaleonis.Win32.Filesystem.FileInfo;
|
||||
using Path = Alphaleonis.Win32.Filesystem.Path;
|
||||
|
||||
namespace Wabbajack
|
||||
namespace Wabbajack.Lib
|
||||
{
|
||||
public class Installer
|
||||
{
|
||||
@ -70,7 +66,7 @@ namespace Wabbajack
|
||||
throw new Exception(msg);
|
||||
}
|
||||
|
||||
private byte[] LoadBytesFromPath(string path)
|
||||
public byte[] LoadBytesFromPath(string path)
|
||||
{
|
||||
using (var fs = new FileStream(ModListArchive, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
|
||||
@ -87,17 +83,37 @@ namespace Wabbajack
|
||||
{
|
||||
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
var entry = ar.GetEntry("modlist.json");
|
||||
var entry = ar.GetEntry("modlist");
|
||||
if (entry == null)
|
||||
{
|
||||
entry = ar.GetEntry("modlist.json");
|
||||
using (var e = entry.Open())
|
||||
return e.FromJSON<ModList>();
|
||||
}
|
||||
using (var e = entry.Open())
|
||||
return e.FromJSON<ModList>();
|
||||
return e.FromCERAS<ModList>(ref CerasConfig.Config);
|
||||
}
|
||||
}
|
||||
|
||||
public void Install()
|
||||
{
|
||||
var game = GameRegistry.Games[ModList.GameType];
|
||||
|
||||
if (GameFolder == null)
|
||||
GameFolder = game.GameLocation;
|
||||
|
||||
if (GameFolder == null)
|
||||
{
|
||||
MessageBox.Show(
|
||||
$"In order to do a proper install Wabbajack needs to know where your {game.MO2Name} folder resides. We tried looking the" +
|
||||
"game location up in the windows registry but were unable to find it, please make sure you launch the game once before running this installer. ",
|
||||
"Could not find game location", MessageBoxButton.OK);
|
||||
Utils.Log("Exiting because we couldn't find the game folder.");
|
||||
return;
|
||||
}
|
||||
|
||||
ValidateGameESMs();
|
||||
ValidateModlist.RunValidation(ModList);
|
||||
|
||||
VirtualFileSystem.Clean();
|
||||
@ -118,19 +134,6 @@ namespace Wabbajack
|
||||
}
|
||||
}
|
||||
|
||||
var game = GameRegistry.Games[ModList.GameType];
|
||||
|
||||
GameFolder = game.GameLocation;
|
||||
|
||||
if (GameFolder == null)
|
||||
{
|
||||
MessageBox.Show(
|
||||
$"In order to do a proper install Wabbajack needs to know where your {game.MO2Name} folder resides. We tried looking the" +
|
||||
"game location up in the windows registry but were unable to find it, please make sure you launch the game once before running this installer. ",
|
||||
"Could not find game location", MessageBoxButton.OK);
|
||||
Utils.Log("Exiting because we couldn't find the game folder.");
|
||||
return;
|
||||
}
|
||||
|
||||
HashArchives();
|
||||
DownloadArchives();
|
||||
@ -154,12 +157,29 @@ namespace Wabbajack
|
||||
InstallIncludedFiles();
|
||||
BuildBSAs();
|
||||
|
||||
zEditIntegration.GenerateMerges(this);
|
||||
|
||||
Info("Installation complete! You may exit the program.");
|
||||
// Removed until we decide if we want this functionality
|
||||
// Nexus devs weren't sure this was a good idea, I (halgari) agree.
|
||||
//AskToEndorse();
|
||||
}
|
||||
|
||||
private void ValidateGameESMs()
|
||||
{
|
||||
foreach (var esm in ModList.Directives.OfType<CleanedESM>().ToList())
|
||||
{
|
||||
var filename = Path.GetFileName(esm.To);
|
||||
var game_file = Path.Combine(GameFolder, "Data", filename);
|
||||
Utils.Log($"Validating {filename}");
|
||||
var hash = game_file.FileHash();
|
||||
if (hash != esm.SourceESMHash)
|
||||
{
|
||||
Utils.Error("Game ESM hash doesn't match, is the ESM already cleaned? Please verify your local game files.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AskToEndorse()
|
||||
{
|
||||
var mods = ModList.Archives
|
||||
@ -296,7 +316,7 @@ namespace Wabbajack
|
||||
Info($"Generating cleaned ESM for {filename}");
|
||||
if (!File.Exists(game_file)) throw new InvalidDataException($"Missing {filename} at {game_file}");
|
||||
Status($"Hashing game version of {filename}");
|
||||
var sha = game_file.FileSHA256();
|
||||
var sha = game_file.FileHash();
|
||||
if (sha != directive.SourceESMHash)
|
||||
throw new InvalidDataException(
|
||||
$"Cannot patch {filename} from the game folder hashes don't match have you already cleaned the file?");
|
||||
@ -305,8 +325,9 @@ namespace Wabbajack
|
||||
var to_file = Path.Combine(Outputfolder, directive.To);
|
||||
Status($"Patching {filename}");
|
||||
using (var output = File.OpenWrite(to_file))
|
||||
using (var input = File.OpenRead(game_file))
|
||||
{
|
||||
BSDiff.Apply(File.OpenRead(game_file), () => new MemoryStream(patch_data), output);
|
||||
BSDiff.Apply(input, () => new MemoryStream(patch_data), output);
|
||||
}
|
||||
}
|
||||
|
||||
@ -409,7 +430,7 @@ namespace Wabbajack
|
||||
}
|
||||
|
||||
Status($"Verifying Patch {Path.GetFileName(to_patch.To)}");
|
||||
var result_sha = to_file.FileSHA256();
|
||||
var result_sha = to_file.FileHash();
|
||||
if (result_sha != to_patch.Hash)
|
||||
throw new InvalidDataException($"Invalid Hash for {to_patch.To} after patching");
|
||||
}
|
||||
@ -479,7 +500,7 @@ namespace Wabbajack
|
||||
return File.ReadAllText(cache);
|
||||
|
||||
Status($"Hashing {Path.GetFileName(e)}");
|
||||
File.WriteAllText(cache, e.FileSHA256());
|
||||
File.WriteAllText(cache, e.FileHash());
|
||||
return HashArchive(e);
|
||||
}
|
||||
}
|
67
Wabbajack.Lib/ModListRegistry/ModListMetadata.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Newtonsoft.Json;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Lib.Downloaders;
|
||||
using Wabbajack.Lib.Validation;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
using File = System.IO.File;
|
||||
using Game = Wabbajack.Common.Game;
|
||||
|
||||
namespace Wabbajack.Lib.ModListRegistry
|
||||
{
|
||||
public class ModlistMetadata
|
||||
{
|
||||
[JsonProperty("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonProperty("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[JsonProperty("author")]
|
||||
public string Author { get; set; }
|
||||
|
||||
[JsonProperty("game")]
|
||||
public Game Game { get; set; }
|
||||
|
||||
[JsonProperty("official")]
|
||||
public bool Official { get; set; }
|
||||
|
||||
[JsonProperty("links")]
|
||||
public LinksObject Links { get; set; } = new LinksObject();
|
||||
|
||||
public class LinksObject
|
||||
{
|
||||
[JsonProperty("image")]
|
||||
public string ImageUri { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public BitmapImage Image { get; set; }
|
||||
|
||||
[JsonProperty("readme")]
|
||||
public string Readme { get; set; }
|
||||
|
||||
[JsonProperty("download")]
|
||||
public string Download { get; set; }
|
||||
|
||||
[JsonProperty("machineURL")]
|
||||
public string MachineURL { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public static List<ModlistMetadata> LoadFromGithub()
|
||||
{
|
||||
var client = new HttpClient();
|
||||
Utils.Log("Loading Modlists from Github");
|
||||
var result = client.GetStringSync(Consts.ModlistMetadataURL);
|
||||
return result.FromJSONString<List<ModlistMetadata>>();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Wabbajack.NexusApi
|
||||
namespace Wabbajack.Lib.NexusApi
|
||||
{
|
||||
public class UserStatus
|
||||
{
|
@ -9,14 +9,16 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Security.Authentication;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Downloaders;
|
||||
using Wabbajack.Lib.Downloaders;
|
||||
using WebSocketSharp;
|
||||
using static Wabbajack.NexusApi.NexusApiUtils;
|
||||
using static Wabbajack.Lib.NexusApi.NexusApiUtils;
|
||||
|
||||
namespace Wabbajack.NexusApi
|
||||
namespace Wabbajack.Lib.NexusApi
|
||||
{
|
||||
public class NexusApiClient : ViewModel
|
||||
{
|
||||
@ -136,25 +138,24 @@ namespace Wabbajack.NexusApi
|
||||
|
||||
private void UpdateRemaining(HttpResponseMessage response)
|
||||
{
|
||||
int dailyRemaining, hourlyRemaining;
|
||||
try
|
||||
{
|
||||
dailyRemaining = int.Parse(response.Headers.GetValues("x-rl-daily-remaining").First());
|
||||
hourlyRemaining = int.Parse(response.Headers.GetValues("x-rl-hourly-remaining").First());
|
||||
var dailyRemaining = int.Parse(response.Headers.GetValues("x-rl-daily-remaining").First());
|
||||
var hourlyRemaining = int.Parse(response.Headers.GetValues("x-rl-hourly-remaining").First());
|
||||
|
||||
lock (RemainingLock)
|
||||
{
|
||||
_dailyRemaining = Math.Min(dailyRemaining, hourlyRemaining);
|
||||
_hourlyRemaining = Math.Min(dailyRemaining, hourlyRemaining);
|
||||
}
|
||||
this.RaisePropertyChanged(nameof(DailyRemaining));
|
||||
this.RaisePropertyChanged(nameof(HourlyRemaining));
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
catch (Exception)
|
||||
{
|
||||
Utils.Log("Couldn't find x-rl-*-remaining headers in Nexus response. Ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
lock (RemainingLock)
|
||||
{
|
||||
_dailyRemaining = Math.Min(dailyRemaining, hourlyRemaining);
|
||||
_hourlyRemaining = Math.Min(dailyRemaining, hourlyRemaining);
|
||||
}
|
||||
this.RaisePropertyChanged(nameof(DailyRemaining));
|
||||
this.RaisePropertyChanged(nameof(HourlyRemaining));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -182,12 +183,29 @@ namespace Wabbajack.NexusApi
|
||||
var response = responseTask.Result;
|
||||
UpdateRemaining(response);
|
||||
|
||||
using (var stream = _httpClient.GetStreamSync(url))
|
||||
var contentTask = response.Content.ReadAsStreamAsync();
|
||||
contentTask.Wait();
|
||||
|
||||
using (var stream = contentTask.Result)
|
||||
{
|
||||
return stream.FromJSON<T>();
|
||||
}
|
||||
}
|
||||
|
||||
private T GetCached<T>(string url)
|
||||
{
|
||||
var code = Encoding.UTF8.GetBytes(url).ToHEX();
|
||||
var cache_file = Path.Combine(Consts.NexusCacheDirectory, code + ".json");
|
||||
if (File.Exists(cache_file) && DateTime.Now - File.GetLastWriteTime(cache_file) < Consts.NexusCacheExpiry)
|
||||
{
|
||||
return cache_file.FromJSON<T>();
|
||||
}
|
||||
|
||||
var result = Get<T>(url);
|
||||
result.ToJSON(cache_file);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public string GetNexusDownloadLink(NexusDownloader.State archive, bool cache = false)
|
||||
{
|
||||
@ -221,7 +239,18 @@ namespace Wabbajack.NexusApi
|
||||
public NexusFileInfo GetFileInfo(NexusDownloader.State mod)
|
||||
{
|
||||
var url = $"https://api.nexusmods.com/v1/games/{ConvertGameName(mod.GameName)}/mods/{mod.ModID}/files/{mod.FileID}.json";
|
||||
return Get<NexusFileInfo>(url);
|
||||
return GetCached<NexusFileInfo>(url);
|
||||
}
|
||||
|
||||
public class GetModFilesResponse
|
||||
{
|
||||
public List<NexusFileInfo> files;
|
||||
}
|
||||
|
||||
public IList<NexusFileInfo> GetModFiles(Game game, int modid)
|
||||
{
|
||||
var url = $"https://api.nexusmods.com/v1/games/{GameRegistry.Games[game].NexusName}/mods/{modid}/files.json";
|
||||
return GetCached<GetModFilesResponse>(url).files;
|
||||
}
|
||||
|
||||
public ModInfo GetModInfo(string gameName, string modId)
|
||||
@ -275,11 +304,11 @@ namespace Wabbajack.NexusApi
|
||||
}
|
||||
|
||||
|
||||
public static IEnumerable<UI.Slide> CachedSlideShow
|
||||
public static IEnumerable<Slide> CachedSlideShow
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Directory.Exists(Consts.NexusCacheDirectory)) return new UI.Slide[] { };
|
||||
if (!Directory.Exists(Consts.NexusCacheDirectory)) return new Slide[] { };
|
||||
|
||||
return Directory.EnumerateFiles(Consts.NexusCacheDirectory)
|
||||
.Where(f => f.EndsWith(".json"))
|
||||
@ -297,7 +326,7 @@ namespace Wabbajack.NexusApi
|
||||
})
|
||||
.Where(m => m != null)
|
||||
.Where(m => m._internal_version == CACHED_VERSION_NUMBER && m.picture_url != null)
|
||||
.Select(m => new UI.Slide(m.name,m.mod_id,m.summary,m.author,m.contains_adult_content,GetModURL(m.game_name,m.mod_id),m.picture_url));
|
||||
.Select(m => new Slide(m.name,m.mod_id,m.summary,m.author,m.contains_adult_content,GetModURL(m.game_name,m.mod_id),m.picture_url));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.NexusApi
|
||||
namespace Wabbajack.Lib.NexusApi
|
||||
{
|
||||
public sealed class NexusApiUtils
|
||||
{
|
@ -5,11 +5,11 @@ using System.Runtime.InteropServices;
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Wabbajack.WebAutomation")]
|
||||
[assembly: AssemblyTitle("Wabbajack.Lib")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Wabbajack.WebAutomation")]
|
||||
[assembly: AssemblyProduct("Wabbajack.Lib")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
@ -20,7 +20,7 @@ using System.Runtime.InteropServices;
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("e5a6f0e6-f79e-460d-82e2-e6330ace06ba")]
|
||||
[assembly: Guid("0a820830-a298-497d-85e0-e9a89efef5fe")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
@ -3,11 +3,11 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.NexusApi;
|
||||
using Wabbajack.Lib.NexusApi;
|
||||
using File = Alphaleonis.Win32.Filesystem.File;
|
||||
using Path = Alphaleonis.Win32.Filesystem.Path;
|
||||
|
||||
namespace Wabbajack
|
||||
namespace Wabbajack.Lib
|
||||
{
|
||||
public class ReportBuilder : IDisposable
|
||||
{
|
||||
@ -47,6 +47,7 @@ namespace Wabbajack
|
||||
public void Build(Compiler c, ModList lst)
|
||||
{
|
||||
Text($"### {lst.Name} by {lst.Author} - Installation Summary");
|
||||
Text($"Build with Wabbajack Version {lst.WabbajackVersion}");
|
||||
Text(lst.Description);
|
||||
Text($"#### Website:");
|
||||
NoWrapText($"[{lst.Website}]({lst.Website})");
|
33
Wabbajack.Lib/UI/Slide.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Wabbajack.Lib
|
||||
{
|
||||
public class Slide
|
||||
{
|
||||
public Slide(string modName, string modID, string modDescription, string modAuthor, bool isNSFW, string modUrl, string imageURL)
|
||||
{
|
||||
ModName = modName;
|
||||
ModDescription = modDescription;
|
||||
ModAuthor = modAuthor;
|
||||
IsNSFW = isNSFW;
|
||||
ModURL = modUrl;
|
||||
ModID = modID;
|
||||
ImageURL = imageURL;
|
||||
}
|
||||
|
||||
public string ModName { get; }
|
||||
public string ModDescription { get; }
|
||||
public string ModAuthor { get; }
|
||||
public bool IsNSFW { get; }
|
||||
public string ModURL { get; }
|
||||
public string ModID { get; }
|
||||
public BitmapImage Image { get; set; }
|
||||
public string ImageURL { get; }
|
||||
|
||||
}
|
||||
}
|
@ -1,13 +1,16 @@
|
||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Threading;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack
|
||||
namespace Wabbajack.Lib
|
||||
{
|
||||
public static class UIUtils
|
||||
{
|
||||
@ -63,7 +66,7 @@ namespace Wabbajack
|
||||
{
|
||||
var img = new BitmapImage();
|
||||
img.BeginInit();
|
||||
img.StreamSource = Assembly.GetExecutingAssembly().GetManifestResourceStream(name);
|
||||
img.StreamSource = Utils.GetResourceStream(name);
|
||||
img.EndInit();
|
||||
return img;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using System.Linq;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Updater
|
||||
namespace Wabbajack.Lib.Updater
|
||||
{
|
||||
public class CheckForUpdates
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Wabbajack.Validation
|
||||
namespace Wabbajack.Lib.Validation
|
||||
{
|
||||
public class Permissions
|
||||
{
|
@ -1,16 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Downloaders;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
using Wabbajack.Lib.Downloaders;
|
||||
using Path = Alphaleonis.Win32.Filesystem.Path;
|
||||
|
||||
namespace Wabbajack.Validation
|
||||
namespace Wabbajack.Lib.Validation
|
||||
{
|
||||
/// <summary>
|
||||
/// Core class for rights management. Given a Wabbajack modlist this class will return a list of all the
|
||||
@ -23,37 +20,28 @@ namespace Wabbajack.Validation
|
||||
|
||||
public void LoadAuthorPermissionsFromString(string s)
|
||||
{
|
||||
var d = new DeserializerBuilder()
|
||||
.WithNamingConvention(new PascalCaseNamingConvention())
|
||||
.Build();
|
||||
AuthorPermissions = d.Deserialize<Dictionary<string, Author>>(s);
|
||||
AuthorPermissions = s.FromYaml<Dictionary<string, Author>>();
|
||||
}
|
||||
|
||||
public void LoadServerWhitelist(string s)
|
||||
{
|
||||
var d = new DeserializerBuilder()
|
||||
.WithNamingConvention(new PascalCaseNamingConvention())
|
||||
.Build();
|
||||
ServerWhitelist = d.Deserialize<ServerWhitelist>(s);
|
||||
ServerWhitelist = s.FromYaml<ServerWhitelist>();
|
||||
}
|
||||
|
||||
public void LoadListsFromGithub()
|
||||
{
|
||||
var d = new DeserializerBuilder()
|
||||
.WithNamingConvention(new PascalCaseNamingConvention())
|
||||
.Build();
|
||||
var client = new HttpClient();
|
||||
Utils.Log("Loading Nexus Mod Permissions");
|
||||
using (var result = new StringReader(client.GetStringSync(Consts.ModPermissionsURL)))
|
||||
using (var result = client.GetStreamSync(Consts.ModPermissionsURL))
|
||||
{
|
||||
AuthorPermissions = d.Deserialize<Dictionary<string, Author>>(result);
|
||||
AuthorPermissions = result.FromYaml<Dictionary<string, Author>>();
|
||||
Utils.Log($"Loaded permissions for {AuthorPermissions.Count} authors");
|
||||
}
|
||||
|
||||
Utils.Log("Loading Server Whitelist");
|
||||
using (var result = new StringReader(client.GetStringSync(Consts.ServerWhitelistURL)))
|
||||
using (var result = client.GetStreamSync(Consts.ServerWhitelistURL))
|
||||
{
|
||||
ServerWhitelist = d.Deserialize<ServerWhitelist>(result);
|
||||
ServerWhitelist = result.FromYaml<ServerWhitelist>();
|
||||
Utils.Log($"Loaded permissions for {ServerWhitelist.AllowedPrefixes.Count} servers and {ServerWhitelist.GoogleIDs.Count} GDrive files");
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wabbajack
|
||||
namespace Wabbajack.Lib
|
||||
{
|
||||
public class ViewModel : ReactiveObject, IDisposable
|
||||
{
|
215
Wabbajack.Lib/Wabbajack.Lib.csproj
Normal file
@ -0,0 +1,215 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{0A820830-A298-497D-85E0-E9A89EFEF5FE}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Wabbajack.Lib</RootNamespace>
|
||||
<AssemblyName>Wabbajack.Lib</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="AlphaFS, Version=2.2.0.0, Culture=neutral, PublicKeyToken=4d31a58f7d7ad5c9, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\AlphaFS.2.2.6\lib\net452\AlphaFS.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Ceras, Version=4.1.7.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Ceras.4.1.7\lib\net47\Ceras.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="CommonMark, Version=0.1.0.0, Culture=neutral, PublicKeyToken=001ef8810438905d, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CommonMark.NET.0.15.1\lib\net45\CommonMark.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="DynamicData, Version=6.13.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\DynamicData.6.13.18\lib\net461\DynamicData.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MegaApiClient, Version=1.7.1.495, Culture=neutral, PublicKeyToken=0480d311efbeb4e2, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MegaApiClient.1.7.1\lib\net46\MegaApiClient.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Toolkit.Wpf.UI.Controls.WebView, Version=5.1.0.0, Culture=neutral, PublicKeyToken=4aff67a105548ee2, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Toolkit.Wpf.UI.Controls.WebView.5.1.1\lib\net462\Microsoft.Toolkit.Wpf.UI.Controls.WebView.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.WindowsAPICodePack, Version=1.1.3.3, Culture=neutral, PublicKeyToken=8985beaab7ea3f04, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft-WindowsAPICodePack-Core.1.1.3.3\lib\net452\Microsoft.WindowsAPICodePack.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.WindowsAPICodePack.Shell, Version=1.1.3.3, Culture=neutral, PublicKeyToken=8985beaab7ea3f04, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft-WindowsAPICodePack-Shell.1.1.3.3\lib\net452\Microsoft.WindowsAPICodePack.Shell.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="ReactiveUI, Version=10.5.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ReactiveUI.10.5.7\lib\net461\ReactiveUI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SharpCompress, Version=0.23.0.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpCompress.0.23.0\lib\net45\SharpCompress.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Splat, Version=9.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Splat.9.1.1\lib\net461\Splat.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Splat.Drawing, Version=9.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Splat.Drawing.9.1.1\lib\net461\Splat.Drawing.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Design" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Drawing.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Drawing.Primitives.4.3.0\lib\net45\System.Drawing.Primitives.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Reactive, Version=4.2.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Reactive.4.2.0\lib\net46\System.Reactive.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.3\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Transactions" />
|
||||
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Windows" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="websocket-sharp, Version=1.0.4.0, Culture=neutral, PublicKeyToken=5660b08a1845a91e, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\WebSocketSharpFork.1.0.4.0\lib\net35\websocket-sharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="YamlDotNet, Version=8.0.0.0, Culture=neutral, PublicKeyToken=ec19458f3c15af5e, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\YamlDotNet.8.0.0\lib\net45\YamlDotNet.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CerasConfig.cs" />
|
||||
<Compile Include="CompilationSteps\ACompilationStep.cs" />
|
||||
<Compile Include="CompilationSteps\DeconstructBSAs.cs" />
|
||||
<Compile Include="CompilationSteps\DirectMatch.cs" />
|
||||
<Compile Include="CompilationSteps\DropAll.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreDisabledMods.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreEndsWith.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreGameFiles.cs" />
|
||||
<Compile Include="CompilationSteps\IgnorePathContains.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreRegex.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreStartsWith.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreWabbajackInstallCruft.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeAll.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeAllConfigs.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeDummyESPs.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeLootFiles.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeModIniData.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeOtherProfiles.cs" />
|
||||
<Compile Include="CompilationSteps\IncludePatches.cs" />
|
||||
<Compile Include="CompilationSteps\IncludePropertyFiles.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeRegex.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeStubbedConfigFiles.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeTaggedMods.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeThisProfile.cs" />
|
||||
<Compile Include="CompilationSteps\IStackStep.cs" />
|
||||
<Compile Include="CompilationSteps\PatchStockESMs.cs" />
|
||||
<Compile Include="CompilationSteps\Serialization.cs" />
|
||||
<Compile Include="Compiler.cs" />
|
||||
<Compile Include="Data.cs" />
|
||||
<Compile Include="Downloaders\AbstractDownloadState.cs" />
|
||||
<Compile Include="Downloaders\DownloadDispatcher.cs" />
|
||||
<Compile Include="Downloaders\DownloaderUtils.cs" />
|
||||
<Compile Include="Downloaders\DropboxDownloader.cs" />
|
||||
<Compile Include="Downloaders\GoogleDriveDownloader.cs" />
|
||||
<Compile Include="Downloaders\HTTPDownloader.cs" />
|
||||
<Compile Include="Downloaders\IDownloader.cs" />
|
||||
<Compile Include="Downloaders\IUrlDownloader.cs" />
|
||||
<Compile Include="Downloaders\ManualDownloader.cs" />
|
||||
<Compile Include="Downloaders\MediaFireDownloader.cs" />
|
||||
<Compile Include="Downloaders\MEGADownloader.cs" />
|
||||
<Compile Include="Downloaders\ModDBDownloader.cs" />
|
||||
<Compile Include="Downloaders\NexusDownloader.cs" />
|
||||
<Compile Include="Installer.cs" />
|
||||
<Compile Include="ModListRegistry\ModListMetadata.cs" />
|
||||
<Compile Include="NexusApi\Dtos.cs" />
|
||||
<Compile Include="NexusApi\NexusApi.cs" />
|
||||
<Compile Include="NexusApi\NexusApiUtils.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ReportBuilder.cs" />
|
||||
<Compile Include="UI\Slide.cs" />
|
||||
<Compile Include="UI\UIUtils.cs" />
|
||||
<Compile Include="Updater\CheckForUpdates.cs" />
|
||||
<Compile Include="Validation\DTOs.cs" />
|
||||
<Compile Include="Validation\ValidateModlist.cs" />
|
||||
<Compile Include="ViewModel.cs" />
|
||||
<Compile Include="WebAutomation\WebAutomation.cs" />
|
||||
<Compile Include="WebAutomation\WebAutomationWindow.xaml.cs">
|
||||
<DependentUpon>WebAutomationWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="WebAutomation\WebAutomationWindowViewModel.cs" />
|
||||
<Compile Include="zEditIntegration.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Compression.BSA\Compression.BSA.csproj">
|
||||
<Project>{ff5d892f-8ff4-44fc-8f7f-cd58f307ad1b}</Project>
|
||||
<Name>Compression.BSA</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\VirtualFileSystem\VirtualFileSystem.csproj">
|
||||
<Project>{5128b489-bc28-4f66-9f0b-b4565af36cbc}</Project>
|
||||
<Name>VirtualFileSystem</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Wabbajack.Common\Wabbajack.Common.csproj">
|
||||
<Project>{b3f3fb6e-b9eb-4f49-9875-d78578bc7ae5}</Project>
|
||||
<Name>Wabbajack.Common</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="css-min.css" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="css.css" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<Page Include="WebAutomation\WebAutomationWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
97
Wabbajack.Lib/WebAutomation/WebAutomation.cs
Normal file
@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Wabbajack.Lib.WebAutomation
|
||||
{
|
||||
public enum DisplayMode
|
||||
{
|
||||
Visible,
|
||||
Hidden
|
||||
}
|
||||
|
||||
public class Driver : IDisposable
|
||||
{
|
||||
private WebAutomationWindow _window;
|
||||
private WebAutomationWindowViewModel _ctx;
|
||||
private Task<WebAutomationWindow> _windowTask;
|
||||
|
||||
private Driver(DisplayMode displayMode = DisplayMode.Hidden)
|
||||
{
|
||||
var windowTask = new TaskCompletionSource<WebAutomationWindow>();
|
||||
|
||||
var t = new Thread(() =>
|
||||
{
|
||||
_window = new WebAutomationWindow();
|
||||
_ctx = (WebAutomationWindowViewModel)_window.DataContext;
|
||||
// Initiates the dispatcher thread shutdown when the window closes
|
||||
|
||||
_window.Closed += (s, e) => _window.Dispatcher.InvokeShutdown();
|
||||
|
||||
if (displayMode == DisplayMode.Hidden)
|
||||
{
|
||||
_window.WindowState = WindowState.Minimized;
|
||||
_window.ShowInTaskbar = false;
|
||||
}
|
||||
|
||||
_window.Show();
|
||||
|
||||
windowTask.SetResult(_window);
|
||||
// Makes the thread support message pumping
|
||||
System.Windows.Threading.Dispatcher.Run();
|
||||
});
|
||||
_windowTask = windowTask.Task;
|
||||
|
||||
t.SetApartmentState(ApartmentState.STA);
|
||||
t.Start();
|
||||
}
|
||||
|
||||
public static async Task<Driver> Create()
|
||||
{
|
||||
var driver = new Driver();
|
||||
driver._window = await driver._windowTask;
|
||||
driver._ctx = (WebAutomationWindowViewModel) driver._window.DataContext;
|
||||
return driver;
|
||||
}
|
||||
|
||||
public Task<Uri> NavigateTo(Uri uri)
|
||||
{
|
||||
return _ctx.NavigateTo(uri);
|
||||
}
|
||||
|
||||
public Task<Uri> GetLocation()
|
||||
{
|
||||
var tcs = new TaskCompletionSource<Uri>();
|
||||
_window.Dispatcher.Invoke(() => tcs.SetResult(_window.WebView.Source));
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public Task<string> GetAttr(string selector, string attr)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<string>();
|
||||
_window.Dispatcher.Invoke(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var script = $"document.querySelector(\"{selector}\").{attr}";
|
||||
var result = _window.WebView.InvokeScript("eval", script);
|
||||
tcs.SetResult(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.SetException(ex);
|
||||
}
|
||||
});
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_window.Dispatcher.Invoke(_window.Close);
|
||||
}
|
||||
}
|
||||
}
|
14
Wabbajack.Lib/WebAutomation/WebAutomationWindow.xaml
Normal file
@ -0,0 +1,14 @@
|
||||
<Window x:Class="Wabbajack.WebAutomationWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Wabbajack"
|
||||
xmlns:controls="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls.WebView"
|
||||
mc:Ignorable="d"
|
||||
Title="WebAutomationWindow" Height="450" Width="800">
|
||||
<Grid>
|
||||
<controls:WebView Name="WebView">
|
||||
</controls:WebView>
|
||||
</Grid>
|
||||
</Window>
|