Merge pull request #142 from erri120/vortex-support

Vortex support
This commit is contained in:
Timothy Baldridge 2019-11-14 17:18:42 -07:00 committed by GitHub
commit 37d002f95a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 1571 additions and 233 deletions

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wabbajack.Common
{
public enum ModManager
{
MO2,
Vortex
}
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Win32;

View File

@ -1,16 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Alphaleonis.Win32.Filesystem;
using Microsoft.Win32;
namespace Wabbajack.Common
{
public enum Game {
//MO2 GAMES
Morrowind,
Oblivion,
Fallout3,
@ -18,16 +14,33 @@ namespace Wabbajack.Common
Skyrim,
SkyrimSpecialEdition,
Fallout4,
SkyrimVR
SkyrimVR,
//VORTEX GAMES
DarkestDungeon,
DivinityOriginalSin2,
DivinityOriginalSin2DE, //definitive edition has its own nexus page but same Steam/GOG ids
Starbound,
SWKOTOR,
SWKOTOR2,
WITCHER,
WITCHER2,
WITCHER3
}
public class GameMetaData
{
public ModManager SupportedModManager { get; internal set; }
public string MO2ArchiveName { get; internal set; }
public Game Game { get; internal set; }
public string NexusName { get; internal set; }
public string MO2Name { get; internal set; }
public string GameLocationRegistryKey { get; internal set; }
// to get steam ids: https://steamdb.info
public List<int> SteamIDs { get; internal set; }
// to get gog ids: https://www.gogdb.org
public List<int> GOGIDs { get; internal set; }
// these are additional folders when a game installs mods outside the game folder
public List<string> AdditionalFolders { get; internal set; }
public string GameLocation
{
@ -53,6 +66,11 @@ namespace Wabbajack.Common
return Games.Values.FirstOrDefault(g => g.MO2ArchiveName?.ToLower() == gamename);
}
public static GameMetaData GetByNexusName(string gameName)
{
return Games.Values.FirstOrDefault(g => g.NexusName == gameName.ToLower());
}
public static Dictionary<Game, GameMetaData> Games = new Dictionary<Game, GameMetaData>
{
@ -62,72 +80,195 @@ namespace Wabbajack.Common
{
Game.Oblivion, new GameMetaData
{
SupportedModManager = ModManager.MO2,
Game = Game.Oblivion,
NexusName = "oblivion",
MO2Name = "Oblivion",
MO2ArchiveName = "oblivion",
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\Oblivion"
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\Oblivion",
SteamIDs = new List<int> {22330}
}
},
{
Game.Fallout3, new GameMetaData
{
SupportedModManager = ModManager.MO2,
Game = Game.Fallout3,
NexusName = "fallout3",
MO2Name = "fallout3",
MO2ArchiveName = "fallout3",
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\Fallout3"
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\Fallout3",
SteamIDs = new List<int> {22300, 22370} // base game and GotY
}
},
{
Game.FalloutNewVegas, new GameMetaData
{
SupportedModManager = ModManager.MO2,
Game = Game.FalloutNewVegas,
NexusName = "newvegas",
MO2Name = "New Vegas",
MO2ArchiveName = "falloutnv",
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\falloutnv"
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\falloutnv",
SteamIDs = new List<int> {22380}
}
},
{
Game.Skyrim, new GameMetaData
{
SupportedModManager = ModManager.MO2,
Game = Game.Skyrim,
NexusName = "skyrim",
MO2Name = "Skyrim",
MO2ArchiveName = "skyrim",
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\skyrim"
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\skyrim",
SteamIDs = new List<int> {72850}
}
},
{
Game.SkyrimSpecialEdition, new GameMetaData
{
SupportedModManager = ModManager.MO2,
Game = Game.SkyrimSpecialEdition,
NexusName = "skyrimspecialedition",
MO2Name = "Skyrim Special Edition",
MO2ArchiveName = "skyrimse",
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\Skyrim Special Edition"
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\Skyrim Special Edition",
SteamIDs = new List<int> {489830}
}
},
{
Game.Fallout4, new GameMetaData
{
SupportedModManager = ModManager.MO2,
Game = Game.Fallout4,
NexusName = "fallout4",
MO2Name = "Fallout 4",
MO2ArchiveName = "fallout4",
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\Fallout4"
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\Fallout4",
SteamIDs = new List<int> {377160}
}
},
/*{
Game.Fallout4VR, new GameMetaData
{
SupportedModManager = ModManager.MO2,
Game = Game.Fallout4VR,
NexusName = "fallout4",
MO2Name = "Fallout 4",
MO2ArchiveName = "fallout4",
SteamIDs = new List<int>{611660}
}
},*/
{
Game.SkyrimVR, new GameMetaData
{
SupportedModManager = ModManager.MO2,
Game = Game.SkyrimVR,
NexusName = "skyrimspecialedition",
MO2Name = "Skyrim VR",
MO2ArchiveName = "skyrimse",
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\Skyrim VR"
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\Skyrim VR",
SteamIDs = new List<int> {611670}
}
},
{
Game.DarkestDungeon, new GameMetaData
{
SupportedModManager = ModManager.Vortex,
Game = Game.DarkestDungeon,
NexusName = "darkestdungeon",
SteamIDs = new List<int> {262060},
GOGIDs = new List<int>{1450711444}
}
},
{
Game.DivinityOriginalSin2, new GameMetaData
{
SupportedModManager = ModManager.Vortex,
Game = Game.DivinityOriginalSin2,
NexusName = "divinityoriginalsin2",
SteamIDs = new List<int> {435150},
GOGIDs = new List<int>{1584823040},
AdditionalFolders = new List<string>
{
"%documents%\\Larian Studios\\Divinity Original Sin 2\\Mods\\",
}
}
},
{
Game.DivinityOriginalSin2DE, new GameMetaData
{
SupportedModManager = ModManager.Vortex,
Game = Game.DivinityOriginalSin2DE,
NexusName = "divinityoriginalsin2definitiveedition",
SteamIDs = new List<int> {435150},
GOGIDs = new List<int>{1584823040},
AdditionalFolders = new List<string>
{
"%documents%\\Larian Studios\\Divinity Original Sin 2 Definitive Edition\\Mods\\"
}
}
},
{
Game.Starbound, new GameMetaData
{
SupportedModManager = ModManager.Vortex,
Game = Game.Starbound,
NexusName = "starbound",
SteamIDs = new List<int>{211820},
GOGIDs = new List<int>{1452598881}
}
},
{
Game.SWKOTOR, new GameMetaData
{
SupportedModManager = ModManager.Vortex,
Game = Game.SWKOTOR,
NexusName = "kotor",
SteamIDs = new List<int>{32370},
GOGIDs = new List<int>{1207666283}
}
},
{
Game.SWKOTOR2, new GameMetaData
{
SupportedModManager = ModManager.Vortex,
Game = Game.SWKOTOR2,
NexusName = "kotor2",
SteamIDs = new List<int>{208580},
GOGIDs = new List<int>{1421404581}
}
},
{
Game.WITCHER, new GameMetaData
{
SupportedModManager = ModManager.Vortex,
Game = Game.WITCHER,
NexusName = "witcher",
SteamIDs = new List<int>{20900},
GOGIDs = new List<int>{1207658924}
}
},
{
Game.WITCHER2, new GameMetaData
{
SupportedModManager = ModManager.Vortex,
Game = Game.WITCHER2,
NexusName = "witcher2",
SteamIDs = new List<int>{20920},
GOGIDs = new List<int>{1207658930}
}
},
{
Game.WITCHER3, new GameMetaData
{
SupportedModManager = ModManager.Vortex,
Game = Game.WITCHER3,
NexusName = "witcher3",
SteamIDs = new List<int>{292030, 499450}, // normal and GotY
GOGIDs = new List<int>{1207664643, 1495134320, 1207664663, 1640424747} // normal, GotY and both in packages
}
}
};

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

View File

@ -91,6 +91,8 @@
<Compile Include="DynamicIniData.cs" />
<Compile Include="Error States\ErrorResponse.cs" />
<Compile Include="Error States\GetResponse.cs" />
<Compile Include="Enums\ModManager.cs" />
<Compile Include="Enums\RunMode.cs" />
<Compile Include="ExtensionManager.cs" />
<Compile Include="Extensions\DictionaryExt.cs" />
<Compile Include="Extensions\HashHelper.cs" />

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VFS;
using Wabbajack.Common;
using Wabbajack.Lib.CompilationSteps;
namespace Wabbajack.Lib
{
public abstract class ACompiler
{
public ModManager ModManager;
public string GamePath;
public string ModListOutputFolder;
public string ModListOutputFile;
public List<Archive> SelectedArchives;
public List<Directive> InstallDirectives;
public List<RawSourceFile> AllFiles;
public ModList ModList;
public VirtualFileSystem VFS;
public List<IndexedArchive> IndexedArchives;
public Dictionary<string, IEnumerable<VirtualFile>> IndexedFiles;
public abstract void Info(string msg);
public abstract void Status(string msg);
public abstract void Error(string msg);
internal abstract string IncludeFile(byte[] data);
internal abstract string IncludeFile(string data);
public abstract bool Compile();
public abstract Directive RunStack(IEnumerable<ICompilationStep> stack, RawSourceFile source);
public abstract IEnumerable<ICompilationStep> GetStack();
public abstract IEnumerable<ICompilationStep> MakeStack();
}
}

View File

@ -2,9 +2,9 @@
{
public abstract class ACompilationStep : ICompilationStep
{
protected Compiler _compiler;
protected ACompiler _compiler;
public ACompilationStep(Compiler compiler)
public ACompilationStep(ACompiler compiler)
{
_compiler = compiler;
}

View File

@ -13,10 +13,12 @@ namespace Wabbajack.Lib.CompilationSteps
private readonly IEnumerable<string> _include_directly;
private readonly List<ICompilationStep> _microstack;
private readonly List<ICompilationStep> _microstackWithInclude;
private readonly Compiler _mo2Compiler;
public DeconstructBSAs(Compiler compiler) : base(compiler)
public DeconstructBSAs(ACompiler compiler) : base(compiler)
{
_include_directly = _compiler.ModInis.Where(kv =>
_mo2Compiler = (Compiler) compiler;
_include_directly = _mo2Compiler.ModInis.Where(kv =>
{
var general = kv.Value.General;
if (general.notes != null && general.notes.Contains(Consts.WABBAJACK_INCLUDE)) return true;
@ -28,16 +30,16 @@ namespace Wabbajack.Lib.CompilationSteps
_microstack = new List<ICompilationStep>
{
new DirectMatch(_compiler),
new IncludePatches(_compiler),
new DropAll(_compiler)
new DirectMatch(_mo2Compiler),
new IncludePatches(_mo2Compiler),
new DropAll(_mo2Compiler)
};
_microstackWithInclude = new List<ICompilationStep>
{
new DirectMatch(_compiler),
new IncludePatches(_compiler),
new IncludeAll(_compiler)
new DirectMatch(_mo2Compiler),
new IncludePatches(_mo2Compiler),
new IncludeAll(_mo2Compiler)
};
}
@ -61,7 +63,7 @@ namespace Wabbajack.Lib.CompilationSteps
var id = Guid.NewGuid().ToString();
var matches = source_files.PMap(e => Compiler.RunStack(stack, new RawSourceFile(e)
var matches = source_files.PMap(e => _mo2Compiler.RunStack(stack, new RawSourceFile(e)
{
Path = Path.Combine(Consts.BSACreationDir, id, e.Paths.Last())
}));
@ -71,7 +73,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
if (match is IgnoredDirectly)
Utils.Error($"File required for BSA {source.Path} creation doesn't exist: {match.To}");
_compiler.ExtraFiles.Add(match);
_mo2Compiler.ExtraFiles.Add(match);
}
CreateBSA directive;
@ -92,7 +94,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("DeconstructBSAs")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new DeconstructBSAs(compiler);
}

View File

@ -6,7 +6,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
public class DirectMatch : ACompilationStep
{
public DirectMatch(Compiler compiler) : base(compiler)
public DirectMatch(ACompiler compiler) : base(compiler)
{
}
@ -34,7 +34,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("DirectMatch")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new DirectMatch(compiler);
}

View File

@ -5,7 +5,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
public class DropAll : ACompilationStep
{
public DropAll(Compiler compiler) : base(compiler)
public DropAll(ACompiler compiler) : base(compiler)
{
}
@ -25,7 +25,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("DropAll")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new DropAll(compiler);
}

View File

@ -8,6 +8,6 @@
public interface IState
{
ICompilationStep CreateStep(Compiler compiler);
ICompilationStep CreateStep(ACompiler compiler);
}
}

View File

@ -9,13 +9,15 @@ namespace Wabbajack.Lib.CompilationSteps
public class IgnoreDisabledMods : ACompilationStep
{
private readonly IEnumerable<string> _allEnabledMods;
private readonly Compiler _mo2Compiler;
public IgnoreDisabledMods(Compiler compiler) : base(compiler)
public IgnoreDisabledMods(ACompiler compiler) : base(compiler)
{
var alwaysEnabled = _compiler.ModInis.Where(f => IsAlwaysEnabled(f.Value)).Select(f => f.Key).ToHashSet();
_mo2Compiler = (Compiler) compiler;
var alwaysEnabled = _mo2Compiler.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")))
_allEnabledMods = _mo2Compiler.SelectedProfiles
.SelectMany(p => File.ReadAllLines(Path.Combine(_mo2Compiler.MO2Folder, "profiles", p, "modlist.txt")))
.Where(line => line.StartsWith("+") || line.EndsWith("_separator"))
.Select(line => line.Substring(1))
.Concat(alwaysEnabled)
@ -55,7 +57,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IgnoreDisabledMods")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IgnoreDisabledMods(compiler);
}

View File

@ -7,7 +7,7 @@ namespace Wabbajack.Lib.CompilationSteps
private readonly string _postfix;
private readonly string _reason;
public IgnoreEndsWith(Compiler compiler, string postfix) : base(compiler)
public IgnoreEndsWith(ACompiler compiler, string postfix) : base(compiler)
{
_postfix = postfix;
_reason = $"Ignored because path ends with {postfix}";
@ -40,7 +40,7 @@ namespace Wabbajack.Lib.CompilationSteps
public string Postfix { get; set; }
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IgnoreEndsWith(compiler, Postfix);
}

View File

@ -7,7 +7,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
private readonly string _startDir;
public IgnoreGameFiles(Compiler compiler) : base(compiler)
public IgnoreGameFiles(ACompiler compiler) : base(compiler)
{
_startDir = Consts.GameFolderFilesDir + "\\";
}
@ -28,7 +28,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IgnoreGameFiles")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IgnoreGameFiles(compiler);
}

View File

@ -7,7 +7,7 @@ namespace Wabbajack.Lib.CompilationSteps
private readonly string _pattern;
private readonly string _reason;
public IgnorePathContains(Compiler compiler, string pattern) : base(compiler)
public IgnorePathContains(ACompiler compiler, string pattern) : base(compiler)
{
_pattern = $"\\{pattern.Trim('\\')}\\";
_reason = $"Ignored because path contains {_pattern}";
@ -40,7 +40,7 @@ namespace Wabbajack.Lib.CompilationSteps
public string Pattern { get; set; }
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IgnorePathContains(compiler, Pattern);
}

View File

@ -9,7 +9,7 @@ namespace Wabbajack.Lib.CompilationSteps
private readonly Regex _regex;
private readonly string _pattern;
public IgnoreRegex(Compiler compiler, string pattern) : base(compiler)
public IgnoreRegex(ACompiler compiler, string pattern) : base(compiler)
{
_pattern = pattern;
_reason = $"Ignored because path matches regex {pattern}";
@ -43,7 +43,7 @@ namespace Wabbajack.Lib.CompilationSteps
public string Pattern { get; set; }
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IgnoreRegex(compiler, Pattern);
}

View File

@ -7,7 +7,7 @@ namespace Wabbajack.Lib.CompilationSteps
private readonly string _prefix;
private readonly string _reason;
public IgnoreStartsWith(Compiler compiler, string prefix) : base(compiler)
public IgnoreStartsWith(ACompiler compiler, string prefix) : base(compiler)
{
_prefix = prefix;
_reason = string.Format("Ignored because path starts with {0}", _prefix);
@ -44,7 +44,7 @@ namespace Wabbajack.Lib.CompilationSteps
public string Prefix { get; set; }
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IgnoreStartsWith(compiler, Prefix);
}

View File

@ -0,0 +1,40 @@
using System;
using System.IO;
using Newtonsoft.Json;
using Wabbajack.Common;
namespace Wabbajack.Lib.CompilationSteps
{
public class IgnoreVortex : ACompilationStep
{
private readonly VortexCompiler _vortex;
public IgnoreVortex(ACompiler compiler) : base(compiler)
{
_vortex = (VortexCompiler) compiler;
}
public override Directive Run(RawSourceFile source)
{
if (Path.GetDirectoryName(source.AbsolutePath) != _vortex.DownloadsFolder) return null;
var result = source.EvolveTo<IgnoredDirectly>();
result.Reason = "Ignored because it is a Vortex file";
return result;
}
public override IState GetState()
{
return new State();
}
[JsonObject("IgnoreVortex")]
public class State : IState
{
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IgnoreVortex(compiler);
}
}
}
}

View File

@ -9,7 +9,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
private readonly HashSet<string> _cruftFiles;
public IgnoreWabbajackInstallCruft(Compiler compiler) : base(compiler)
public IgnoreWabbajackInstallCruft(ACompiler compiler) : base(compiler)
{
_cruftFiles = new HashSet<string>
{
@ -34,7 +34,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IgnoreWabbajackInstallCruft")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IgnoreWabbajackInstallCruft(compiler);
}

View File

@ -5,7 +5,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
public class IncludeAll : ACompilationStep
{
public IncludeAll(Compiler compiler) : base(compiler)
public IncludeAll(ACompiler compiler) : base(compiler)
{
}
@ -24,7 +24,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IncludeAll")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludeAll(compiler);
}

View File

@ -6,7 +6,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
public class IncludeAllConfigs : ACompilationStep
{
public IncludeAllConfigs(Compiler compiler) : base(compiler)
public IncludeAllConfigs(ACompiler compiler) : base(compiler)
{
}
@ -26,7 +26,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IncludeAllConfigs")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludeAllConfigs(compiler);
}

View File

@ -5,7 +5,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
public class IncludeDummyESPs : ACompilationStep
{
public IncludeDummyESPs(Compiler compiler) : base(compiler)
public IncludeDummyESPs(ACompiler compiler) : base(compiler)
{
}
@ -36,7 +36,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IncludeDummyESPs")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludeDummyESPs(compiler);
}

View File

@ -8,7 +8,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
private readonly string _prefix;
public IncludeLootFiles(Compiler compiler) : base(compiler)
public IncludeLootFiles(ACompiler compiler) : base(compiler)
{
_prefix = Consts.LOOTFolderFilesDir + "\\";
}
@ -29,7 +29,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IncludeLootFiles")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludeLootFiles(compiler);
}

View File

@ -5,7 +5,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
public class IncludeModIniData : ACompilationStep
{
public IncludeModIniData(Compiler compiler) : base(compiler)
public IncludeModIniData(ACompiler compiler) : base(compiler)
{
}
@ -25,7 +25,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IncludeModIniData")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludeModIniData(compiler);
}

View File

@ -8,10 +8,13 @@ namespace Wabbajack.Lib.CompilationSteps
public class IgnoreOtherProfiles : ACompilationStep
{
private readonly IEnumerable<string> _profiles;
private readonly Compiler _mo2Compiler;
public IgnoreOtherProfiles(Compiler compiler) : base(compiler)
public IgnoreOtherProfiles(ACompiler compiler) : base(compiler)
{
_profiles = _compiler.SelectedProfiles
_mo2Compiler = (Compiler) compiler;
_profiles = _mo2Compiler.SelectedProfiles
.Select(p => Path.Combine("profiles", p) + "\\")
.ToList();
}
@ -33,7 +36,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IgnoreOtherProfiles")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IgnoreOtherProfiles(compiler);
}

View File

@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
private readonly Dictionary<string, IGrouping<string, VirtualFile>> _indexed;
public IncludePatches(Compiler compiler) : base(compiler)
public IncludePatches(ACompiler compiler) : base(compiler)
{
_indexed = _compiler.IndexedFiles.Values
.SelectMany(f => f)
@ -47,7 +47,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IncludePatches")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludePatches(compiler);
}

View File

@ -7,31 +7,34 @@ namespace Wabbajack.Lib.CompilationSteps
{
public class IncludePropertyFiles : ACompilationStep
{
public IncludePropertyFiles(Compiler compiler) : base(compiler)
private readonly Compiler _mo2Compiler;
public IncludePropertyFiles(ACompiler compiler) : base(compiler)
{
_mo2Compiler = (Compiler) compiler;
}
public override Directive Run(RawSourceFile source)
{
var files = new HashSet<string>
{
_compiler.ModListImage, _compiler.ModListReadme
_mo2Compiler.ModListImage, _mo2Compiler.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 isBanner = source.AbsolutePath == _mo2Compiler.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;
_mo2Compiler.ModListImage = result.SourceDataID;
}
else
{
result.Type = PropertyType.Readme;
_compiler.ModListReadme = result.SourceDataID;
_mo2Compiler.ModListReadme = result.SourceDataID;
}
return result;
@ -45,7 +48,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IncludePropertyFiles")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludePropertyFiles(compiler);
}

View File

@ -9,7 +9,7 @@ namespace Wabbajack.Lib.CompilationSteps
private readonly string _pattern;
private readonly Regex _regex;
public IncludeRegex(Compiler compiler, string pattern) : base(compiler)
public IncludeRegex(ACompiler compiler, string pattern) : base(compiler)
{
_pattern = pattern;
_regex = new Regex(pattern);
@ -43,7 +43,7 @@ namespace Wabbajack.Lib.CompilationSteps
public string Pattern { get; set; }
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludeRegex(compiler, Pattern);
}

View File

@ -7,8 +7,11 @@ namespace Wabbajack.Lib.CompilationSteps
{
public class IncludeStubbedConfigFiles : ACompilationStep
{
public IncludeStubbedConfigFiles(Compiler compiler) : base(compiler)
private readonly Compiler _mo2Compiler;
public IncludeStubbedConfigFiles(ACompiler compiler) : base(compiler)
{
_mo2Compiler = (Compiler) compiler;
}
public override Directive Run(RawSourceFile source)
@ -26,18 +29,18 @@ namespace Wabbajack.Lib.CompilationSteps
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(_mo2Compiler.GamePath, Consts.GAME_PATH_MAGIC_BACK);
data = data.Replace(_mo2Compiler.GamePath.Replace("\\", "\\\\"), Consts.GAME_PATH_MAGIC_DOUBLE_BACK);
data = data.Replace(_mo2Compiler.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(_mo2Compiler.MO2Folder, Consts.MO2_PATH_MAGIC_BACK);
data = data.Replace(_mo2Compiler.MO2Folder.Replace("\\", "\\\\"), Consts.MO2_PATH_MAGIC_DOUBLE_BACK);
data = data.Replace(_mo2Compiler.MO2Folder.Replace("\\", "/"), Consts.MO2_PATH_MAGIC_FORWARD);
data = data.Replace(_compiler.MO2DownloadsFolder, Consts.DOWNLOAD_PATH_MAGIC_BACK);
data = data.Replace(_compiler.MO2DownloadsFolder.Replace("\\", "\\\\"),
data = data.Replace(_mo2Compiler.MO2DownloadsFolder, Consts.DOWNLOAD_PATH_MAGIC_BACK);
data = data.Replace(_mo2Compiler.MO2DownloadsFolder.Replace("\\", "\\\\"),
Consts.DOWNLOAD_PATH_MAGIC_DOUBLE_BACK);
data = data.Replace(_compiler.MO2DownloadsFolder.Replace("\\", "/"), Consts.DOWNLOAD_PATH_MAGIC_FORWARD);
data = data.Replace(_mo2Compiler.MO2DownloadsFolder.Replace("\\", "/"), Consts.DOWNLOAD_PATH_MAGIC_FORWARD);
if (data == originalData)
return null;
@ -49,7 +52,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IncludeStubbedConfigFiles")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludeStubbedConfigFiles(compiler);
}

View File

@ -9,12 +9,13 @@ namespace Wabbajack.Lib.CompilationSteps
{
private readonly IEnumerable<string> _includeDirectly;
private readonly string _tag;
private readonly Compiler _mo2Compiler;
public IncludeTaggedMods(Compiler compiler, string tag) : base(compiler)
public IncludeTaggedMods(ACompiler compiler, string tag) : base(compiler)
{
_mo2Compiler = (Compiler) compiler;
_tag = tag;
_includeDirectly = _compiler.ModInis.Where(kv =>
_includeDirectly = _mo2Compiler.ModInis.Where(kv =>
{
var general = kv.Value.General;
if (general.notes != null && general.notes.Contains(_tag))
@ -56,7 +57,7 @@ namespace Wabbajack.Lib.CompilationSteps
public string Tag { get; set; }
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludeTaggedMods(compiler, Tag);
}

View File

@ -9,10 +9,12 @@ namespace Wabbajack.Lib.CompilationSteps
public class IncludeThisProfile : ACompilationStep
{
private readonly IEnumerable<string> _correctProfiles;
private readonly Compiler _mo2Compiler;
public IncludeThisProfile(Compiler compiler) : base(compiler)
public IncludeThisProfile(ACompiler compiler) : base(compiler)
{
_correctProfiles = _compiler.SelectedProfiles.Select(p => Path.Combine("profiles", p) + "\\").ToList();
_mo2Compiler = (Compiler) compiler;
_correctProfiles = _mo2Compiler.SelectedProfiles.Select(p => Path.Combine("profiles", p) + "\\").ToList();
}
public override Directive Run(RawSourceFile source)
@ -48,7 +50,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("IncludeThisProfile")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludeThisProfile(compiler);
}

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common;
namespace Wabbajack.Lib.CompilationSteps
{
public class IncludeVortexDeployment : ACompilationStep
{
public IncludeVortexDeployment(ACompiler compiler) : base(compiler)
{
}
public override Directive Run(RawSourceFile source)
{
if (!source.Path.EndsWith("vortex.deployment.msgpack") &&
!source.Path.EndsWith("\\vortex.deployment.json")) return null;
var inline = source.EvolveTo<InlineFile>();
inline.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath));
return inline;
}
public override IState GetState()
{
return new State();
}
public class State : IState
{
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludeVortexDeployment(compiler);
}
}
}
}

View File

@ -8,14 +8,17 @@ namespace Wabbajack.Lib.CompilationSteps
{
public class PatchStockESMs : ACompilationStep
{
public PatchStockESMs(Compiler compiler) : base(compiler)
private readonly Compiler _mo2Compiler;
public PatchStockESMs(ACompiler compiler) : base(compiler)
{
_mo2Compiler = (Compiler) compiler;
}
public override Directive Run(RawSourceFile source)
{
var filename = Path.GetFileName(source.Path);
var gameFile = Path.Combine(_compiler.GamePath, "Data", filename);
var gameFile = Path.Combine(_mo2Compiler.GamePath, "Data", filename);
if (!Consts.GameESMs.Contains(filename) || !source.Path.StartsWith("mods\\") ||
!File.Exists(gameFile)) return null;
@ -44,7 +47,7 @@ namespace Wabbajack.Lib.CompilationSteps
[JsonObject("PatchStockESMs")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new PatchStockESMs(compiler);
}

View File

@ -13,7 +13,7 @@ namespace Wabbajack.Lib.CompilationSteps
.ToJSON(TypeNameHandling.Auto, TypeNameAssemblyFormatHandling.Simple);
}
public static List<ICompilationStep> Deserialize(string stack, Compiler compiler)
public static List<ICompilationStep> Deserialize(string stack, ACompiler compiler)
{
return stack.FromJSONString<List<IState>>(TypeNameHandling.Auto, TypeNameAssemblyFormatHandling.Simple)
.Select(s => s.CreateStep(compiler)).ToList();

View File

@ -26,7 +26,7 @@ using Path = Alphaleonis.Win32.Filesystem.Path;
namespace Wabbajack.Lib
{
public class Compiler
public class Compiler : ACompiler
{
private string _mo2DownloadsFolder;
@ -42,9 +42,23 @@ namespace Wabbajack.Lib
public Compiler(string mo2_folder)
{
ModManager = ModManager.MO2;
MO2Folder = mo2_folder;
MO2Ini = Path.Combine(MO2Folder, "ModOrganizer.ini").LoadIniFile();
GamePath = ((string)MO2Ini.General.gamePath).Replace("\\\\", "\\");
ModListOutputFolder = "output_folder";
ModListOutputFile = MO2Profile + ExtensionManager.Extension;
SelectedArchives = new List<Archive>();
InstallDirectives = new List<Directive>();
AllFiles = new List<RawSourceFile>();
ModList = new ModList();
VFS = VirtualFileSystem.VFS;
IndexedArchives = new List<IndexedArchive>();
IndexedFiles = new Dictionary<string, IEnumerable<VirtualFile>>();
}
public dynamic MO2Ini { get; }
@ -70,55 +84,43 @@ namespace Wabbajack.Lib
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)
public override void Info(string msg)
{
Utils.Log(msg);
}
public void Status(string msg)
public override void Status(string msg)
{
WorkQueue.Report(msg, 0);
}
private void Error(string msg)
public override void Error(string msg)
{
Utils.Log(msg);
throw new Exception(msg);
}
internal string IncludeFile(byte[] data)
internal override string IncludeFile(byte[] data)
{
var id = Guid.NewGuid().ToString();
File.WriteAllBytes(Path.Combine(ModListOutputFolder, id), data);
return id;
}
internal string IncludeFile(string data)
internal override string IncludeFile(string data)
{
var id = Guid.NewGuid().ToString();
File.WriteAllText(Path.Combine(ModListOutputFolder, id), data);
return id;
}
public bool Compile()
public override bool Compile()
{
VirtualFileSystem.Clean();
Info("Looking for other profiles");
@ -283,6 +285,7 @@ namespace Wabbajack.Lib
GameType = GameRegistry.Games.Values.First(f => f.MO2Name == MO2Ini.General.gameName).Game,
WabbajackVersion = WabbajackVersion,
Archives = SelectedArchives,
ModManager = ModManager.MO2,
Directives = InstallDirectives,
Name = ModListName ?? MO2Profile,
Author = ModListAuthor ?? "",
@ -533,7 +536,7 @@ namespace Wabbajack.Lib
}
public static Directive RunStack(IEnumerable<ICompilationStep> stack, RawSourceFile source)
public override Directive RunStack(IEnumerable<ICompilationStep> stack, RawSourceFile source)
{
Utils.Status($"Compiling {source.Path}");
foreach (var step in stack)
@ -545,7 +548,7 @@ namespace Wabbajack.Lib
throw new InvalidDataException("Data fell out of the compilation stack");
}
public IEnumerable<ICompilationStep> GetStack()
public override IEnumerable<ICompilationStep> GetStack()
{
var user_config = Path.Combine(MO2ProfileDir, "compilation_stack.yml");
if (File.Exists(user_config))
@ -566,7 +569,7 @@ namespace Wabbajack.Lib
/// result included into the pack
/// </summary>
/// <returns></returns>
public IEnumerable<ICompilationStep> MakeStack()
public override IEnumerable<ICompilationStep> MakeStack()
{
Utils.Log("Generating compilation stack");
return new List<ICompilationStep>

View File

@ -40,6 +40,11 @@ namespace Wabbajack.Lib
/// </summary>
public List<Archive> Archives;
/// <summary>
/// The Mod Manager used to create the modlist
/// </summary>
public ModManager ModManager;
/// <summary>
/// The game variant to which this game applies
/// </summary>
@ -204,6 +209,7 @@ namespace Wabbajack.Lib
/// </summary>
public string Hash;
/// <summary>
/// Meta INI for the downloaded archive
/// </summary>
public string Meta;

View File

@ -19,7 +19,8 @@ namespace Wabbajack.Lib.Downloaders
if (general.modID != null && general.fileID != null && general.gameName != null)
{
var name = (string)general.gameName;
var game = GameRegistry.GetByMO2ArchiveName(name).Game;
var gameMeta = GameRegistry.GetByMO2ArchiveName(name);
var game = gameMeta != null ? GameRegistry.GetByMO2ArchiveName(name).Game : GameRegistry.GetByNexusName(name).Game;
var info = new NexusApiClient().GetModInfo(game, general.modID);
return new State
{

View File

@ -46,6 +46,12 @@ namespace Wabbajack.Lib.NexusApi
public bool contains_adult_content;
}
public class MD5Response
{
public ModInfo mod;
public NexusFileInfo file_details;
}
public class EndorsementResponse
{
public string message;

View File

@ -222,7 +222,6 @@ namespace Wabbajack.Lib.NexusApi
}
public string GetNexusDownloadLink(NexusDownloader.State archive, bool cache = false)
{
if (cache && TryGetCachedLink(archive, out var result))
@ -269,6 +268,12 @@ namespace Wabbajack.Lib.NexusApi
return GetCached<GetModFilesResponse>(url).files;
}
public List<MD5Response> GetModInfoFromMD5(Game game, string md5Hash)
{
var url = $"https://api.nexusmods.com/v1/games/{GameRegistry.Games[game].NexusName}/mods/md5_search/{md5Hash}.json";
return Get<List<MD5Response>>(url);
}
public ModInfo GetModInfo(Game game, string modId)
{
var url = $"https://api.nexusmods.com/v1/games/{GameRegistry.Games[game].NexusName}/mods/{modId}.json";
@ -365,4 +370,4 @@ namespace Wabbajack.Lib.NexusApi
}
}
}
}

View File

@ -1,4 +1,5 @@
using Wabbajack.Common;
using System.Text.RegularExpressions;
using Wabbajack.Common;
namespace Wabbajack.Lib.NexusApi
{
@ -6,6 +7,8 @@ namespace Wabbajack.Lib.NexusApi
{
public static string ConvertGameName(string gameName)
{
if (Regex.IsMatch(gameName, @"^[^a-z\s]+\.[^a-z\s]+$"))
return gameName;
return GameRegistry.GetByMO2ArchiveName(gameName)?.NexusName ?? gameName.ToLower();
}

View File

@ -0,0 +1,452 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Microsoft.WindowsAPICodePack.Shell;
using VFS;
using Wabbajack.Common;
using Wabbajack.Lib.CompilationSteps;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.ModListRegistry;
using Wabbajack.Lib.NexusApi;
using File = Alphaleonis.Win32.Filesystem.File;
namespace Wabbajack.Lib
{
public class VortexCompiler : ACompiler
{
public Game Game { get; }
public string GameName { get; }
public string VortexFolder { get; set; }
public string StagingFolder { get; set; }
public string DownloadsFolder { get; set; }
public bool IgnoreMissingFiles { get; set; }
public VortexCompiler(string gameName, string gamePath)
{
ModManager = ModManager.Vortex;
// TODO: only for testing
IgnoreMissingFiles = true;
string[] args = Environment.GetCommandLineArgs();
GamePath = gamePath;
GameName = gameName;
Game = GameRegistry.GetByNexusName(GameName).Game;
//args: wabbajacke.exe gameName gamePath vortexfolder stagingfolder downloadsfolder
VortexFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Vortex");
if (File.Exists(Path.Combine(VortexFolder, gameName, "mods", "__vortex_staging_folder")))
StagingFolder = Path.Combine(VortexFolder, gameName, "mods");
if (File.Exists(Path.Combine(VortexFolder, "downloads", "__vortex_downloads_folder")))
DownloadsFolder = Path.Combine(VortexFolder, "downloads", gameName);
if (args.Length >= 4)
StagingFolder = args[3];
if (args.Length == 5)
DownloadsFolder = args[4];
ModListOutputFolder = "output_folder";
// TODO: add custom modlist name
ModListOutputFile = $"VORTEX_TEST_MODLIST{ExtensionManager.Extension}";
VFS = VirtualFileSystem.VFS;
SelectedArchives = new List<Archive>();
AllFiles = new List<RawSourceFile>();
IndexedArchives = new List<IndexedArchive>();
IndexedFiles = new Dictionary<string, IEnumerable<VirtualFile>>();
}
public override void Info(string msg)
{
Utils.Log(msg);
}
public override void Status(string msg)
{
WorkQueue.Report(msg, 0);
}
public override void Error(string msg)
{
Utils.Log(msg);
throw new Exception(msg);
}
internal override string IncludeFile(byte[] data)
{
var id = Guid.NewGuid().ToString();
File.WriteAllBytes(Path.Combine(ModListOutputFolder, id), data);
return id;
}
internal override string IncludeFile(string data)
{
var id = Guid.NewGuid().ToString();
File.WriteAllText(Path.Combine(ModListOutputFolder, id), data);
return id;
}
public override bool Compile()
{
VirtualFileSystem.Clean();
Info($"Starting Vortex compilation for {GameName} at {GamePath} with staging folder at {StagingFolder} and downloads folder at {DownloadsFolder}.");
Info("Starting pre-compilation steps");
CreateMetaFiles();
Info($"Indexing {StagingFolder}");
VFS.AddRoot(StagingFolder);
Info($"Indexing {GamePath}");
VFS.AddRoot(GamePath);
Info($"Indexing {DownloadsFolder}");
VFS.AddRoot(DownloadsFolder);
AddExternalFolder();
Info("Cleaning output folder");
if (Directory.Exists(ModListOutputFolder)) Directory.Delete(ModListOutputFolder, true);
Directory.CreateDirectory(ModListOutputFolder);
IEnumerable<RawSourceFile> vortexStagingFiles = Directory.EnumerateFiles(StagingFolder, "*", SearchOption.AllDirectories)
.Where(p => p.FileExists() && p != "__vortex_staging_folder")
.Select(p => new RawSourceFile(VFS.Lookup(p))
{Path = p.RelativeTo(StagingFolder)});
IEnumerable<RawSourceFile> vortexDownloads = Directory.EnumerateFiles(DownloadsFolder, "*", SearchOption.AllDirectories)
.Where(p => p.FileExists())
.Select(p => new RawSourceFile(VFS.Lookup(p))
{Path = p.RelativeTo(DownloadsFolder)});
IEnumerable<RawSourceFile> gameFiles = Directory.EnumerateFiles(GamePath, "*", SearchOption.AllDirectories)
.Where(p => p.FileExists())
.Select(p => new RawSourceFile(VFS.Lookup(p))
{ Path = Path.Combine(Consts.GameFolderFilesDir, p.RelativeTo(GamePath)) });
Info("Indexing Archives");
IndexedArchives = Directory.EnumerateFiles(DownloadsFolder)
.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");
IDictionary<VirtualFile, IEnumerable<VirtualFile>> grouped = VFS.GroupedByArchive();
IndexedFiles = IndexedArchives.Select(f => grouped.TryGetValue(f.File, out var result) ? result : 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 = vortexStagingFiles.Concat(vortexDownloads)
.Concat(gameFiles)
.DistinctBy(f => f.Path)
.ToList();
Info($"Found {AllFiles.Count} files to build into mod list");
Info("Verifying destinations");
List<IGrouping<string, RawSourceFile>> 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");
}
IEnumerable<ICompilationStep> stack = MakeStack();
Info("Running Compilation Stack");
List<Directive> results = AllFiles.PMap(f => RunStack(stack.Where(s => s != null), f)).ToList();
IEnumerable<NoMatch> noMatch = results.OfType<NoMatch>().ToList();
Info($"No match for {noMatch.Count()} files");
foreach (var file in noMatch)
Info($" {file.To}");
if (noMatch.Any())
{
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();
// TODO: nexus stuff
/*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");
}
*/
GatherArchives();
ModList = new ModList
{
Archives = SelectedArchives,
ModManager = ModManager.Vortex,
Directives = InstallDirectives,
GameType = Game
};
ExportModList();
Info("Done Building ModList");
return true;
}
/// <summary>
/// Some have mods outside their game folder located
/// </summary>
private void AddExternalFolder()
{
var currentGame = GameRegistry.Games[Game];
if (currentGame.AdditionalFolders == null || currentGame.AdditionalFolders.Count == 0) return;
currentGame.AdditionalFolders.Do(f =>
{
var path = f.Replace("%documents%", KnownFolders.Documents.Path);
if (!Directory.Exists(path)) return;
Info($"Indexing {path}");
VFS.AddRoot(path);
});
}
private void ExportModList()
{
Utils.Log($"Exporting ModList to: {ModListOutputFolder}");
// using JSON for better debugging
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("Exporting ModList metadata");
var metadata = new ModlistMetadata.DownloadMetadata
{
Size = File.GetSize(ModListOutputFile),
Hash = ModListOutputFile.FileHash(),
NumberOfArchives = ModList.Archives.Count,
SizeOfArchives = ModList.Archives.Sum(a => a.Size),
NumberOfInstalledFiles = ModList.Directives.Count,
SizeOfInstalledFiles = ModList.Directives.Sum(a => a.Size)
};
metadata.ToJSON(ModListOutputFile + ".meta.json");
Utils.Log("Removing ModList staging folder");
//Directory.Delete(ModListOutputFolder, true);
}
/*private void GenerateReport()
{
string css;
using (var cssStream = Utils.GetResourceStream("Wabbajack.Lib.css-min.css"))
using (var 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);
}
}
}*/
private void CreateMetaFiles()
{
Utils.Log("Getting Nexus api_key, please click authorize if a browser window appears");
var nexusClient = new NexusApiClient();
Directory.EnumerateFiles(DownloadsFolder, "*", SearchOption.TopDirectoryOnly)
.Where(f => File.Exists(f) && Path.GetExtension(f) != ".meta" && !File.Exists(f+".meta"))
.Do(f =>
{
Utils.Log($"Trying to create meta file for {Path.GetFileName(f)}");
var metaString = $"[General]\n" +
$"repository=Nexus\n" +
$"installed=true\n" +
$"uninstalled=false\n" +
$"paused=false\n" +
$"removed=false\n" +
$"gameName={GameName}\n";
string hash;
using(var md5 = MD5.Create())
using (var stream = File.OpenRead(f))
{
Utils.Log($"Calculating hash for {Path.GetFileName(f)}");
byte[] cH = md5.ComputeHash(stream);
hash = BitConverter.ToString(cH).Replace("-", "").ToLowerInvariant();
Utils.Log($"Hash is {hash}");
}
List<MD5Response> md5Response = nexusClient.GetModInfoFromMD5(Game, hash);
if (md5Response.Count >= 1)
{
var modInfo = md5Response[0].mod;
metaString += $"modID={modInfo.mod_id}\ndescription={NexusApiUtils.FixupSummary(modInfo.summary)}\n" +
$"modName={modInfo.name}\nfileID={md5Response[0].file_details.file_id}";
File.WriteAllText(f+".meta",metaString, Encoding.UTF8);
}
else
{
Error("Error while getting information from nexusmods via MD5 hash!");
}
});
}
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 override 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 override IEnumerable<ICompilationStep> GetStack()
{
var s = Consts.TestMode ? DownloadsFolder : VortexFolder;
var userConfig = Path.Combine(s, "compilation_stack.yml");
if (File.Exists(userConfig))
return Serialization.Deserialize(File.ReadAllText(userConfig), this);
IEnumerable<ICompilationStep> stack = MakeStack();
File.WriteAllText(Path.Combine(s, "_current_compilation_stack.yml"),
Serialization.Serialize(stack));
return stack;
}
public override IEnumerable<ICompilationStep> MakeStack()
{
Utils.Log("Generating compilation stack");
return new List<ICompilationStep>
{
//new IncludePropertyFiles(this),
new IncludeVortexDeployment(this),
new IncludeRegex(this, "^*\\.meta"),
new IgnoreVortex(this),
Game == Game.DarkestDungeon ? new IncludeRegex(this, "project\\.xml$") : null,
new IgnoreStartsWith(this, " __vortex_staging_folder"),
new IgnoreEndsWith(this, "__vortex_staging_folder"),
new IgnoreGameFiles(this),
new DirectMatch(this),
new IgnoreGameFiles(this),
new IgnoreWabbajackInstallCruft(this),
new DropAll(this)
};
}
}
}

View File

@ -0,0 +1,307 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using VFS;
using Wabbajack.Common;
using Wabbajack.Lib.Downloaders;
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 VortexInstaller
{
public string ModListArchive { get; }
public ModList ModList { get; }
public Dictionary<string, string> HashedArchives { get; private set; }
public GameMetaData GameInfo { get; internal set; }
public string StagingFolder { get; set; }
public string DownloadFolder { get; set; }
public VirtualFileSystem VFS => VirtualFileSystem.VFS;
public bool IgnoreMissingFiles { get; internal set; }
public VortexInstaller(string archive, ModList modList)
{
ModListArchive = archive;
ModList = modList;
// TODO: only for testing
IgnoreMissingFiles = true;
GameInfo = GameRegistry.Games[ModList.GameType];
}
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);
}
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))
using (var ms = new MemoryStream())
{
var entry = ar.GetEntry(path);
using (var e = entry.Open())
e.CopyTo(ms);
return ms.ToArray();
}
}
public static ModList LoadFromFile(string path)
{
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
{
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()
{
Directory.CreateDirectory(DownloadFolder);
VirtualFileSystem.Clean();
HashArchives();
DownloadArchives();
HashArchives();
var missing = ModList.Archives.Where(a => !HashedArchives.ContainsKey(a.Hash)).ToList();
if (missing.Count > 0)
{
foreach (var a in missing)
Info($"Unable to download {a.Name}");
if (IgnoreMissingFiles)
Info("Missing some archives, but continuing anyways at the request of the user");
else
Error("Cannot continue, was unable to download one or more archives");
}
PrimeVFS();
BuildFolderStructure();
InstallArchives();
InstallIncludedFiles();
//InctallIncludedDownloadMetas();
Info("Installation complete! You may exit the program.");
}
private void BuildFolderStructure()
{
Info("Building Folder Structure");
ModList.Directives
.OfType<FromArchive>()
.Select(d => Path.Combine(StagingFolder, Path.GetDirectoryName(d.To)))
.ToHashSet()
.Do(f =>
{
if (Directory.Exists(f)) return;
Directory.CreateDirectory(f);
});
}
private void InstallArchives()
{
Info("Installing Archives");
Info("Grouping Install Files");
var grouped = ModList.Directives
.OfType<FromArchive>()
.GroupBy(e => e.ArchiveHashPath[0])
.ToDictionary(k => k.Key);
var archives = ModList.Archives
.Select(a => new { Archive = a, AbsolutePath = HashedArchives.GetOrDefault(a.Hash) })
.Where(a => a.AbsolutePath != null)
.ToList();
Info("Installing Archives");
archives.PMap(a => InstallArchive(a.Archive, a.AbsolutePath, grouped[a.Archive.Hash]));
}
private void InstallArchive(Archive archive, string absolutePath, IGrouping<string, FromArchive> grouping)
{
Status($"Extracting {archive.Name}");
var vFiles = grouping.Select(g =>
{
var file = VFS.FileForArchiveHashPath(g.ArchiveHashPath);
g.FromFile = file;
return g;
}).ToList();
var onFinish = VFS.Stage(vFiles.Select(f => f.FromFile).Distinct());
Status($"Copying files for {archive.Name}");
void CopyFile(string from, string to, bool useMove)
{
if(File.Exists(to))
File.Delete(to);
if (useMove)
File.Move(from, to);
else
File.Copy(from, to);
}
vFiles.GroupBy(f => f.FromFile)
.DoIndexed((idx, group) =>
{
Utils.Status("Installing files", idx * 100 / vFiles.Count);
var firstDest = Path.Combine(StagingFolder, group.First().To);
CopyFile(group.Key.StagedPath, firstDest, true);
foreach (var copy in group.Skip(1))
{
var nextDest = Path.Combine(StagingFolder, copy.To);
CopyFile(firstDest, nextDest, false);
}
});
Status("Unstaging files");
onFinish();
}
private void InstallIncludedFiles()
{
Info("Writing inline files");
ModList.Directives.OfType<InlineFile>()
.PMap(directive =>
{
Status($"Writing included file {directive.To}");
var outPath = Path.Combine(StagingFolder, directive.To);
if(File.Exists(outPath)) File.Delete(outPath);
File.WriteAllBytes(outPath, LoadBytesFromPath(directive.SourceDataID));
});
}
private void PrimeVFS()
{
HashedArchives.Do(a => VFS.AddKnown(new VirtualFile
{
Paths = new[] { a.Value },
Hash = a.Key
}));
VFS.RefreshIndexes();
ModList.Directives
.OfType<FromArchive>()
.Do(f =>
{
var updated_path = new string[f.ArchiveHashPath.Length];
f.ArchiveHashPath.CopyTo(updated_path, 0);
updated_path[0] = VFS.HashIndex[updated_path[0]].Where(e => e.IsConcrete).First().FullPath;
VFS.AddKnown(new VirtualFile { Paths = updated_path });
});
VFS.BackfillMissing();
}
private void DownloadArchives()
{
var missing = ModList.Archives.Where(a => !HashedArchives.ContainsKey(a.Hash)).ToList();
Info($"Missing {missing.Count} archives");
Info("Getting Nexus API Key, if a browser appears, please accept");
var dispatchers = missing.Select(m => m.State.GetDownloader()).Distinct();
foreach (var dispatcher in dispatchers)
dispatcher.Prepare();
DownloadMissingArchives(missing);
}
private void DownloadMissingArchives(List<Archive> missing, bool download = true)
{
if (download)
{
foreach (var a in missing.Where(a => a.State.GetType() == typeof(ManualDownloader.State)))
{
var output_path = Path.Combine(DownloadFolder, a.Name);
a.State.Download(a, output_path);
}
}
missing.Where(a => a.State.GetType() != typeof(ManualDownloader.State))
.PMap(archive =>
{
Info($"Downloading {archive.Name}");
var output_path = Path.Combine(DownloadFolder, archive.Name);
if (!download) return DownloadArchive(archive, download);
if (output_path.FileExists())
File.Delete(output_path);
return DownloadArchive(archive, download);
});
}
public bool DownloadArchive(Archive archive, bool download)
{
try
{
archive.State.Download(archive, Path.Combine(DownloadFolder, archive.Name));
}
catch (Exception ex)
{
Utils.Log($"Download error for file {archive.Name}");
Utils.Log(ex.ToString());
return false;
}
return false;
}
private void HashArchives()
{
HashedArchives = Directory.EnumerateFiles(DownloadFolder)
.Where(e => !e.EndsWith(".sha"))
.PMap(e => (HashArchive(e), e))
.OrderByDescending(e => File.GetLastWriteTime(e.Item2))
.GroupBy(e => e.Item1)
.Select(e => e.First())
.ToDictionary(e => e.Item1, e => e.Item2);
}
private string HashArchive(string e)
{
var cache = e + ".sha";
if (cache.FileExists() && new FileInfo(cache).LastWriteTime >= new FileInfo(e).LastWriteTime)
return File.ReadAllText(cache);
Status($"Hashing {Path.GetFileName(e)}");
File.WriteAllText(cache, e.FileHash());
return HashArchive(e);
}
}
}

View File

@ -77,6 +77,7 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="ACompiler.cs" />
<Compile Include="CerasConfig.cs" />
<Compile Include="CompilationSteps\ACompilationStep.cs" />
<Compile Include="CompilationSteps\DeconstructBSAs.cs" />
@ -88,6 +89,7 @@
<Compile Include="CompilationSteps\IgnorePathContains.cs" />
<Compile Include="CompilationSteps\IgnoreRegex.cs" />
<Compile Include="CompilationSteps\IgnoreStartsWith.cs" />
<Compile Include="CompilationSteps\IgnoreVortex.cs" />
<Compile Include="CompilationSteps\IgnoreWabbajackInstallCruft.cs" />
<Compile Include="CompilationSteps\IncludeAll.cs" />
<Compile Include="CompilationSteps\IncludeAllConfigs.cs" />
@ -101,6 +103,7 @@
<Compile Include="CompilationSteps\IncludeStubbedConfigFiles.cs" />
<Compile Include="CompilationSteps\IncludeTaggedMods.cs" />
<Compile Include="CompilationSteps\IncludeThisProfile.cs" />
<Compile Include="CompilationSteps\IncludeVortexDeployment.cs" />
<Compile Include="CompilationSteps\IStackStep.cs" />
<Compile Include="CompilationSteps\PatchStockESMs.cs" />
<Compile Include="CompilationSteps\Serialization.cs" />
@ -131,6 +134,8 @@
<Compile Include="Validation\DTOs.cs" />
<Compile Include="Validation\ValidateModlist.cs" />
<Compile Include="ViewModel.cs" />
<Compile Include="VortexCompiler.cs" />
<Compile Include="VortexInstaller.cs" />
<Compile Include="WebAutomation\WebAutomation.cs" />
<Compile Include="WebAutomation\WebAutomationWindow.xaml.cs">
<DependentUpon>WebAutomationWindow.xaml</DependentUpon>

View File

@ -14,9 +14,12 @@ namespace Wabbajack.Lib
{
public class zEditIntegration
{
public static string FindzEditPath(Compiler compiler)
private static Compiler _mo2Compiler;
public static string FindzEditPath(ACompiler compiler)
{
var executables = compiler.MO2Ini.customExecutables;
_mo2Compiler = (Compiler) compiler;
var executables = _mo2Compiler.MO2Ini.customExecutables;
if (executables.size == null) return null;
foreach (var idx in Enumerable.Range(1, int.Parse(executables.size)))
@ -35,7 +38,7 @@ namespace Wabbajack.Lib
{
private Dictionary<string, zEditMerge> _mergesIndexed;
public IncludeZEditPatches(Compiler compiler) : base(compiler)
public IncludeZEditPatches(ACompiler compiler) : base(compiler)
{
var zEditPath = FindzEditPath(compiler);
var havezEdit = zEditPath != null;
@ -63,7 +66,7 @@ namespace Wabbajack.Lib
_mergesIndexed =
merges.ToDictionary(
m => Path.Combine(compiler.MO2Folder, "mods", m.Key.name, m.Key.filename),
m => Path.Combine(_mo2Compiler.MO2Folder, "mods", m.Key.name, m.Key.filename),
m => m.First());
}
@ -89,12 +92,12 @@ namespace Wabbajack.Lib
return new SourcePatch
{
RelativePath = abs_path.RelativeTo(_compiler.MO2Folder),
RelativePath = abs_path.RelativeTo(_mo2Compiler.MO2Folder),
Hash = _compiler.VFS[abs_path].Hash
};
}).ToList();
var src_data = result.Sources.Select(f => File.ReadAllBytes(Path.Combine(_compiler.MO2Folder, f.RelativePath)))
var src_data = result.Sources.Select(f => File.ReadAllBytes(Path.Combine(_mo2Compiler.MO2Folder, f.RelativePath)))
.ConcatArrays();
var dst_data = File.ReadAllBytes(source.AbsolutePath);
@ -117,7 +120,7 @@ namespace Wabbajack.Lib
[JsonObject("IncludeZEditPatches")]
public class State : IState
{
public ICompilationStep CreateStep(Compiler compiler)
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludeZEditPatches(compiler);
}

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Documents;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Wabbajack.Common;
using Wabbajack.Lib;

View File

@ -0,0 +1,71 @@
using System;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using VFS;
using Wabbajack.Common;
using Wabbajack.Lib;
namespace Wabbajack.Test
{
public abstract class AVortexCompilerTest
{
public TestContext TestContext { get; set; }
protected TestUtils utils { get; set; }
[TestInitialize]
public void TestInitialize()
{
Consts.TestMode = true;
utils = new TestUtils
{
GameName = "darkestdungeon"
};
Utils.LogMessages.Subscribe(f => TestContext.WriteLine(f));
}
[TestCleanup]
public void TestCleanup()
{
utils.Dispose();
}
protected VortexCompiler ConfigureAndRunCompiler()
{
var vortexCompiler = MakeCompiler();
vortexCompiler.VFS.Reset();
vortexCompiler.DownloadsFolder = utils.DownloadsFolder;
vortexCompiler.StagingFolder = utils.InstallFolder;
Directory.CreateDirectory(utils.InstallFolder);
Assert.IsTrue(vortexCompiler.Compile());
return vortexCompiler;
}
protected VortexCompiler MakeCompiler()
{
VirtualFileSystem.Reconfigure(utils.TestFolder);
var vortexCompiler = new VortexCompiler(utils.GameName, utils.GameFolder);
return vortexCompiler;
}
protected ModList CompileAndInstall()
{
var vortexCompiler = ConfigureAndRunCompiler();
Install(vortexCompiler);
return vortexCompiler.ModList;
}
protected void Install(VortexCompiler vortexCompiler)
{
var modList = Installer.LoadFromFile(vortexCompiler.ModListOutputFile);
var installer = new Installer(vortexCompiler.ModListOutputFile, modList, utils.InstallFolder)
{
DownloadFolder = utils.DownloadsFolder,
GameFolder = utils.GameFolder,
};
installer.Install();
}
}
}

View File

@ -0,0 +1,25 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Wabbajack.Lib.CompilationSteps;
namespace Wabbajack.Test
{
[TestClass]
public class VortexTests : AVortexCompilerTest
{
[TestMethod]
public void TestVortexStackSerialization()
{
utils.AddMod("test");
utils.Configure();
var vortexCompiler = ConfigureAndRunCompiler();
var stack = vortexCompiler.MakeStack();
var serialized = Serialization.Serialize(stack);
var rounded = Serialization.Serialize(Serialization.Deserialize(serialized, vortexCompiler));
Assert.AreEqual(serialized, rounded);
Assert.IsNotNull(vortexCompiler.GetStack());
}
}
}

View File

@ -94,6 +94,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ACompilerTest.cs" />
<Compile Include="AVortexCompilerTest.cs" />
<Compile Include="CSP\ChannelTests.cs" />
<Compile Include="CSP\CSPTests.cs" />
<Compile Include="DownloaderTests.cs" />
@ -105,6 +106,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ContentRightsManagementTests.cs" />
<Compile Include="CompilationStackTests.cs" />
<Compile Include="VortexTests.cs" />
<Compile Include="WebAutomationTests.cs" />
<Compile Include="zEditIntegrationTests.cs" />
</ItemGroup>

View File

@ -53,6 +53,7 @@ namespace Wabbajack
public class InstallationSettings
{
public string InstallationLocation { get; set; }
public string StagingLocation { get; set; }
public string DownloadLocation { get; set; }
}

View File

@ -196,9 +196,30 @@ namespace Wabbajack
private async Task ExecuteBegin()
{
Compiler compiler;
try
if (false)
{
string[] args = Environment.GetCommandLineArgs();
var compiler = new VortexCompiler(args[1], args[2]);
await Task.Run(() =>
{
Compiling = true;
try
{
compiler.Compile();
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log($"Can't continue: {ex.ExceptionToString()}");
}
finally
{
Compiling = false;
}
});
}else{
Compiler compiler;
try {
compiler = new Compiler(this.Mo2Folder)
{
MO2Profile = this.MOProfile,
@ -209,34 +230,35 @@ namespace Wabbajack
ModListWebsite = this.Website,
ModListReadme = this.ReadMeText.TargetPath,
};
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log($"Compiler error: {ex.ExceptionToString()}");
return;
}
await Task.Run(() =>
{
Compiling = true;
try
{
compiler.Compile();
if (compiler.ModList?.ReportHTML != null)
{
this.HTMLReport = compiler.ModList.ReportHTML;
}
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log($"Compiler error: {ex.ExceptionToString()}");
return;
}
finally
await Task.Run(() =>
{
Compiling = false;
}
});
Compiling = true;
try
{
compiler.Compile();
if (compiler.ModList?.ReportHTML != null)
{
this.HTMLReport = compiler.ModList.ReportHTML;
}
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log($"Compiler error: {ex.ExceptionToString()}");
}
finally
{
Compiling = false;
}
});
}
}
}
}

View File

@ -1,4 +1,4 @@
using Syroot.Windows.IO;
using Syroot.Windows.IO;
using System;
using ReactiveUI;
using System.Diagnostics;
@ -50,10 +50,15 @@ namespace Wabbajack
[Reactive]
public bool InstallingMode { get; set; }
[Reactive]
public bool IsMO2ModList { get; set; }
public FilePickerVM Location { get; }
public FilePickerVM DownloadLocation { get; }
public FilePickerVM StagingLocation { get; }
private readonly ObservableAsPropertyHelper<float> _ProgressPercent;
public float ProgressPercent => _ProgressPercent.Value;
@ -114,15 +119,25 @@ namespace Wabbajack
this.DownloadLocation.AdditionalError = this.WhenAny(x => x.DownloadLocation.TargetPath)
.Select(x => Utils.IsDirectoryPathValid(x));
StagingLocation = new FilePickerVM
{
DoExistsCheck = true,
PathType = FilePickerVM.PathTypeOptions.Folder,
PromptTitle = "Select your Vortex Staging Folder",
AdditionalError = this.WhenAny(x => x.StagingLocation.TargetPath)
.Select(Utils.IsDirectoryPathValid)
};
// Load settings
InstallationSettings settings = this.MWVM.Settings.InstallationSettings.TryCreate(source);
this.Location.TargetPath = settings.InstallationLocation;
this.DownloadLocation.TargetPath = settings.DownloadLocation;
var settings = MWVM.Settings.InstallationSettings.TryCreate(source);
this.MWVM.Settings.SaveSignal
.Subscribe(_ =>
{
settings.InstallationLocation = this.Location.TargetPath;
settings.DownloadLocation = this.DownloadLocation.TargetPath;
settings.DownloadLocation = DownloadLocation.TargetPath;
if (IsMO2ModList)
settings.InstallationLocation = Location.TargetPath;
else
settings.StagingLocation = StagingLocation.TargetPath;
})
.DisposeWith(this.CompositeDisposable);
@ -148,6 +163,38 @@ namespace Wabbajack
});
return default(ModListVM);
}
if (modList.ModManager == ModManager.Vortex)
{
IsMO2ModList = false;
StagingLocation.TargetPath = settings.StagingLocation;
var vortexFolder =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Vortex");
var stagingFolder = Path.Combine(vortexFolder, GameRegistry.Games[modList.GameType].NexusName,
"mods");
var downloadFolder = Path.Combine(vortexFolder, "downloads",
GameRegistry.Games[modList.GameType].NexusName);
MessageBox.Show(
"The ModList you are about to install was compiled from a Vortex installation. " +
"Vortex support is still very bleeding edge and installing this ModList WILL OVERRIDE your existing mods. " +
"If you encounter any errors during installation go to our discord and ping erri120#2285 with your error and a log file.",
"Important information regarding Vortex support", MessageBoxButton.OK, MessageBoxImage.Stop);
if (!Directory.Exists(vortexFolder)) return new ModListVM(modList, modListPath);
if (Directory.Exists(stagingFolder) &&
File.Exists(Path.Combine(stagingFolder, "__vortex_staging_folder")))
StagingLocation.TargetPath = stagingFolder;
if (Directory.Exists(Path.Combine(vortexFolder, "downloads")) &&
File.Exists(Path.Combine(vortexFolder, "downloads", "__vortex_downloads_folder")))
DownloadLocation.TargetPath = downloadFolder;
}
else
{
Location.TargetPath = settings.InstallationLocation;
DownloadLocation.TargetPath = settings.DownloadLocation;
IsMO2ModList = true;
}
return new ModListVM(modList, modListPath);
})
.ObserveOnGuiThread()
@ -219,10 +266,13 @@ namespace Wabbajack
this.WhenAny(x => x.Installing),
this.WhenAny(x => x.Location.InError),
this.WhenAny(x => x.DownloadLocation.InError),
resultSelector: (installing, loc, download) =>
this.WhenAny(x => x.StagingLocation.InError),
resultSelector: (installing, loc, download, staging) =>
{
if (installing) return false;
return !loc && !download;
if (IsMO2ModList)
return !loc && !download;
return !staging && !download;
})
.ObserveOnGuiThread());
this.VisitWebsiteCommand = ReactiveCommand.Create(
@ -289,35 +339,68 @@ namespace Wabbajack
private void ExecuteBegin()
{
this.Installing = true;
this.InstallingMode = true;
var installer = new Installer(this.ModListPath, this.ModList.SourceModList, Location.TargetPath)
Installing = true;
InstallingMode = true;
if (ModList.ModManager == ModManager.Vortex)
{
DownloadFolder = DownloadLocation.TargetPath
};
var th = new Thread(() =>
var installer = new VortexInstaller(ModListPath, ModList.SourceModList)
{
StagingFolder = StagingLocation.TargetPath,
DownloadFolder = DownloadLocation.TargetPath
};
var th = new Thread(() =>
{
try
{
installer.Install();
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log(ex.StackTrace);
Utils.Log(ex.ToString());
Utils.Log($"{ex.Message} - Can't continue");
}
finally
{
Installing = false;
}
})
{
Priority = ThreadPriority.BelowNormal
};
th.Start();
}
else
{
try
var installer = new Installer(this.ModListPath, this.ModList.SourceModList, Location.TargetPath)
{
installer.Install();
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log(ex.StackTrace);
Utils.Log(ex.ToString());
Utils.Log($"{ex.Message} - Can't continue");
}
finally
DownloadFolder = DownloadLocation.TargetPath
};
var th = new Thread(() =>
{
try
{
installer.Install();
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log(ex.StackTrace);
Utils.Log(ex.ToString());
Utils.Log($"{ex.Message} - Can't continue");
}
finally
{
this.Installing = false;
}
})
{
Priority = ThreadPriority.BelowNormal
};
th.Start();
this.Installing = false;
}
})
{
Priority = ThreadPriority.BelowNormal
};
th.Start();
}
}
}
}

View File

@ -1,13 +1,8 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using Wabbajack.Common;
using Wabbajack.Lib;
@ -18,13 +13,14 @@ namespace Wabbajack
{
public ModList SourceModList { get; }
public string ModListPath { get; }
public string Name => this.SourceModList.Name;
public string ReportHTML => this.SourceModList.ReportHTML;
public string Readme => this.SourceModList.Readme;
public string ImageURL => this.SourceModList.Image;
public string Author => this.SourceModList.Author;
public string Description => this.SourceModList.Description;
public string Website => this.SourceModList.Website;
public string Name => SourceModList.Name;
public string ReportHTML => SourceModList.ReportHTML;
public string Readme => SourceModList.Readme;
public string ImageURL => SourceModList.Image;
public string Author => SourceModList.Author;
public string Description => SourceModList.Description;
public string Website => SourceModList.Website;
public ModManager ModManager => SourceModList.ModManager;
// Image isn't exposed as a direct property, but as an observable.
// This acts as a caching mechanism, as interested parties will trigger it to be created,
@ -33,10 +29,10 @@ namespace Wabbajack
public ModListVM(ModList sourceModList, string modListPath)
{
this.ModListPath = modListPath;
this.SourceModList = sourceModList;
ModListPath = modListPath;
SourceModList = sourceModList;
this.ImageObservable = Observable.Return(this.ImageURL)
ImageObservable = Observable.Return(this.ImageURL)
.ObserveOn(RxApp.TaskpoolScheduler)
.Select(url =>
{

View File

@ -277,43 +277,93 @@
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="80" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="14"
Text="Installation Location"
TextAlignment="Center" />
<local:FilePicker
Grid.Row="1"
Grid.Column="2"
Height="30"
VerticalAlignment="Center"
DataContext="{Binding Location}"
FontSize="14" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="14"
Text="Download Location"
TextAlignment="Center" />
<local:FilePicker
Grid.Row="2"
Grid.Column="2"
Height="30"
VerticalAlignment="Center"
DataContext="{Binding DownloadLocation}"
FontSize="14" />
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Visibility="{Binding IsMO2ModList, Converter={StaticResource bool2VisibilityConverter}}">
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="14"
Text="Installation Location"
TextAlignment="Center" />
<local:FilePicker
Grid.Row="1"
Grid.Column="2"
Height="30"
VerticalAlignment="Center"
DataContext="{Binding Location}"
FontSize="14" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="14"
Text="Download Location"
TextAlignment="Center" />
<local:FilePicker
Grid.Row="1"
Grid.Column="2"
Height="30"
VerticalAlignment="Center"
DataContext="{Binding DownloadLocation}"
FontSize="14" />
</Grid>
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Visibility="{Binding IsMO2ModList, Converter={StaticResource bool2VisibilityConverter}, ConverterParameter=False}">
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="14"
Text="Staging Folder"
TextAlignment="Center" />
<local:FilePicker
Grid.Row="0"
Grid.Column="2"
Height="30"
VerticalAlignment="Center"
DataContext="{Binding StagingLocation}"
FontSize="14" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="14"
Text="Download Folder"
TextAlignment="Center" />
<local:FilePicker
Grid.Row="1"
Grid.Column="2"
Height="30"
VerticalAlignment="Center"
DataContext="{Binding DownloadLocation}"
FontSize="14" />
</Grid>
<local:BeginButton
Grid.Row="1"
Grid.RowSpan="2"
Grid.Column="4"
Margin="0,0,25,0"
HorizontalAlignment="Right"

View File

@ -185,7 +185,6 @@
<DependentUpon>CompilerView.xaml</DependentUpon>
</Compile>
<Compile Include="Converters\IsNotNullVisibilityConverter.cs" />
<Compile Include="Enums\RunMode.cs" />
<Compile Include="Extensions\ReactiveUIExt.cs" />
<Compile Include="Views\DownloadWindow.xaml.cs">
<DependentUpon>DownloadWindow.xaml</DependentUpon>