Merge remote-tracking branch 'wabbajack-tools/master' into XAML-IDE-Featureset

This commit is contained in:
Justin Swanson 2019-11-02 18:30:35 -05:00
commit 422fde257b
180 changed files with 5130 additions and 8871 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
Branding/Github_Card.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
Branding/PNGs/Banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
Branding/PNGs/Letters.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
Branding/PNGs/Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

BIN
Branding/PNGs/Logo_Dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Binary file not shown.

158
Branding/SVGs/Banner.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 80 KiB

37
Branding/SVGs/Letters.svg Normal file
View 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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
Branding/wabbajack.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

BIN
Branding/wabbajack.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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>

View File

@ -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();
}

View File

@ -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" />

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}

View 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);
}
}
}

View File

@ -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)
{
}

View File

@ -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
{

View 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
{
}
}

View File

@ -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,6 +84,9 @@ namespace Wabbajack.Common
public static void Status(string msg, int progress = 0)
{
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);
}
}
}

View File

@ -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>

View File

@ -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,6 +66,9 @@ namespace Wabbajack.Common
public static void Report(string msg, int progress)
{
if (CustomReportFn != null)
CustomReportFn(progress, msg);
else
ReportFunction(CpuId, msg, progress);
}

View 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>

View File

@ -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>

View 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)
}
};
}
}

View 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();
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View File

@ -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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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
View 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;
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}
}

View 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;
}
}
}

View File

@ -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);

View File

@ -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&amp;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;
}

View File

@ -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;

View File

@ -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();
}
}

View 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);
}
}

View File

@ -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;
}

View File

@ -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
{

View 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()
};
}
}
}

View File

@ -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

View File

@ -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)
{

View File

@ -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.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);
}
}

View 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>>();
}
}
}

View File

@ -1,6 +1,6 @@
using System;
namespace Wabbajack.NexusApi
namespace Wabbajack.Lib.NexusApi
{
public class UserStatus
{

View File

@ -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,17 +138,10 @@ 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());
}
catch (InvalidDataException)
{
Utils.Log("Couldn't find x-rl-*-remaining headers in Nexus response. Ignoring");
return;
}
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)
{
@ -156,6 +151,12 @@ namespace Wabbajack.NexusApi
this.RaisePropertyChanged(nameof(DailyRemaining));
this.RaisePropertyChanged(nameof(HourlyRemaining));
}
catch (Exception)
{
Utils.Log("Couldn't find x-rl-*-remaining headers in Nexus response. Ignoring");
}
}
#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));
}
}

View File

@ -1,6 +1,6 @@
using Wabbajack.Common;
namespace Wabbajack.NexusApi
namespace Wabbajack.Lib.NexusApi
{
public sealed class NexusApiUtils
{

View File

@ -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:
//

View File

@ -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
View 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; }
}
}

View File

@ -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;
}

View File

@ -1,7 +1,7 @@
using System.Linq;
using Wabbajack.Common;
namespace Wabbajack.Updater
namespace Wabbajack.Lib.Updater
{
public class CheckForUpdates
{

View File

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace Wabbajack.Validation
namespace Wabbajack.Lib.Validation
{
public class Permissions
{

View File

@ -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");
}

View File

@ -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
{

View 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>

View 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);
}
}
}

View 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>

Some files were not shown because too many files have changed in this diff Show More