Merge pull request #793 from wabbajack-tools/game-sourced-downloads

Game sourced downloads
This commit is contained in:
Timothy Baldridge 2020-05-04 22:45:06 -06:00 committed by GitHub
commit 5d71fc4618
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 325 additions and 84 deletions

View File

@ -19,7 +19,8 @@ namespace Wabbajack.CLI
typeof(DeleteFile),
typeof(Changelog),
typeof(FindSimilar),
typeof(BSADump)
typeof(BSADump),
typeof(MigrateGameFolderFiles)
};
}
}

View File

@ -22,6 +22,7 @@ namespace Wabbajack.CLI
(Changelog opts) => opts.Execute(),
(FindSimilar opts) => opts.Execute(),
(BSADump opts) => opts.Execute(),
(MigrateGameFolderFiles opts) => opts.Execute(),
errs => 1);
}
}

View File

@ -0,0 +1,25 @@
using System.Threading.Tasks;
using CommandLine;
using Wabbajack.Common;
using Wabbajack.Lib.Tasks;
namespace Wabbajack.CLI.Verbs
{
[Verb("migrate-game-folder", HelpText = "Migrates game files into the 'Game Folder Files' in a MO2 directory")]
public class MigrateGameFolderFiles : AVerb
{
[IsDirectory(CustomMessage = "Downloads folder at %1 does not exist!")]
[Option('i', "input", HelpText = "Input Mod Organizer 2 Folder", Required = true)]
public string MO2Folder { get; set; } = "";
protected override async Task<ExitCode> Run()
{
if (await MigrateGameFolder.Execute((AbsolutePath)MO2Folder))
{
return ExitCode.Ok;
};
return ExitCode.Error;
}
}
}

View File

@ -24,5 +24,20 @@ namespace Wabbajack.Common.Test
tempFile.Path.MoveTo(tempFile2.Path);
}
[Fact]
public void CanGetTopParentOfPath()
{
var path = (RelativePath)"foo/bar";
Assert.Equal((RelativePath)"foo", path.TopParent);
}
[Fact]
public void CanGetTopParentOfSinglePath()
{
var path = (RelativePath)"foo";
Assert.Equal((RelativePath)"foo", path.TopParent);
}
}
}

View File

@ -85,7 +85,7 @@ namespace Wabbajack.Common
public AbsolutePath? TryGetGameLocation()
{
return Consts.TestMode ? AbsolutePath.GetCurrentDirectory() : StoreHandler.Instance.TryGetGamePath(Game);
return StoreHandler.Instance.TryGetGamePath(Game);
}
public bool TryGetGameLocation(out AbsolutePath path)
@ -180,10 +180,20 @@ namespace Wabbajack.Common
result = GetByMO2ArchiveName(someName);
if (result != null) return result;
result = GetByMO2Name(someName);
if (result != null) return result;
return int.TryParse(someName, out int id) ? GetBySteamID(id) : null;
}
private static GameMetaData? GetByMO2Name(string gameName)
{
gameName = gameName.ToLower();
return Games.Values.FirstOrDefault(g => g.MO2Name?.ToLower() == gameName);
}
public static bool TryGetByFuzzyName(string someName, [MaybeNullWhen(false)] out GameMetaData gameMetaData)
{
var result = TryGetByFuzzyName(someName);

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Alphaleonis.Win32.Filesystem;
@ -208,12 +209,10 @@ namespace Wabbajack.Common
public RelativePath RelativeTo(AbsolutePath p)
{
if (_path.Substring(0, p._path.Length + 1) != p._path + "\\")
{
throw new InvalidDataException("Not a parent path");
}
return new RelativePath(_path.Substring(p._path.Length + 1));
var relPath = Path.GetRelativePath(p._path, _path);
if (relPath == _path)
throw new ArgumentException($"{_path} is not a subpath of {p._path}");
return new RelativePath(relPath);
}
public async Task<string> ReadAllTextAsync()
@ -342,6 +341,31 @@ namespace Wabbajack.Common
{
File.Copy(_path, dest._path);
}
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
public bool HardLinkTo(AbsolutePath destination)
{
Utils.Log($"Hard Linking {_path} to {destination}");
return CreateHardLink((string)destination, (string)this, IntPtr.Zero);
}
public async ValueTask HardLinkIfOversize(AbsolutePath destination)
{
if (!destination.Parent.Exists)
destination.Parent.CreateDirectory();
if (Root == destination.Root && Consts.SupportedBSAs.Contains(Extension))
{
if (HardLinkTo(destination))
return;
}
await CopyToAsync(destination);
}
public async Task<IEnumerable<string>> ReadAllLinesAsync()
{
@ -350,7 +374,8 @@ namespace Wabbajack.Common
public byte[] ReadAllBytes()
{
return File.ReadAllBytes(_path);
using var file = OpenShared();
return file.ReadAll();
}
public static AbsolutePath GetCurrentDirectory()
@ -393,7 +418,7 @@ namespace Wabbajack.Common
public FileStream OpenShared()
{
return File.Open(_path, FileMode.Open, FileAccess.Read);
return File.Open(_path, FileMode.Open, FileAccess.Read, FileShare.Read);
}
public FileStream WriteShared()

View File

@ -155,7 +155,7 @@ namespace Wabbajack.Common.StoreHandlers
if (!l.ContainsCaseInsensitive("\"installdir\""))
return;
var path = new RelativePath($"common//{GetVdfValue(l)}").RelativeTo(u);
var path = new RelativePath("common").Combine(GetVdfValue(l)).RelativeTo(u);
if (path.Exists)
game.Path = path;
});

View File

@ -4,6 +4,7 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common;
using Wabbajack.Lib.CompilationSteps;
@ -100,6 +101,15 @@ namespace Wabbajack.Lib
return id;
}
internal async Task<(RelativePath, AbsolutePath)> IncludeString(string str)
{
var id = IncludeId();
var fullPath = ModListOutputFolder.Combine(id);
await fullPath.WriteAllTextAsync(str);
return (id, fullPath);
}
public async Task<bool> GatherMetaData()
{
Utils.Log($"Getting meta data for {SelectedArchives.Count} archives");

View File

@ -29,9 +29,11 @@ namespace Wabbajack.Lib
public ModList ModList { get; private set; }
public Dictionary<Hash, AbsolutePath> HashedArchives { get; } = new Dictionary<Hash, AbsolutePath>();
public GameMetaData Game { get; }
public SystemParameters? SystemParameters { get; set; }
public AInstaller(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters? parameters, int steps)
public AInstaller(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters? parameters, int steps, Game game)
: base(steps)
{
ModList = modList;
@ -39,6 +41,7 @@ namespace Wabbajack.Lib
OutputFolder = outputFolder;
DownloadFolder = downloadFolder;
SystemParameters = parameters;
Game = game.MetaData();
}
public void Info(string msg)
@ -138,7 +141,7 @@ namespace Wabbajack.Lib
Status($"Copying files for {archive.Name}");
async ValueTask CopyFile(AbsolutePath from, AbsolutePath to, bool useMove)
async ValueTask CopyFile(AbsolutePath from, AbsolutePath to)
{
if (to.Exists)
{
@ -153,11 +156,7 @@ namespace Wabbajack.Lib
from.IsReadOnly = false;
}
if (useMove)
from.MoveTo(to);
else
from.CopyTo(to);
await @from.CopyToAsync(to);
// If we don't do this, the file will use the last-modified date of the file when it was compressed
// into an archive, which isn't really what we want in the case of files installed archives
to.LastModified = DateTime.Now;
@ -173,11 +172,18 @@ namespace Wabbajack.Lib
}
var firstDest = OutputFolder.Combine(group.First().To);
await group.Key.StagedFile.MoveTo(firstDest);
if (group.Key.IsNative)
{
await group.Key.AbsoluteName.HardLinkIfOversize(firstDest);
}
else
{
await group.Key.StagedFile.MoveTo(firstDest);
}
foreach (var copy in group.Skip(1))
{
await CopyFile(firstDest, OutputFolder.Combine(copy.To), false);
await CopyFile(firstDest, OutputFolder.Combine(copy.To));
}
});
@ -280,7 +286,9 @@ namespace Wabbajack.Lib
public async Task HashArchives()
{
var hashResults = await DownloadFolder.EnumerateFiles()
var hashResults = await
DownloadFolder.EnumerateFiles()
.Concat(Game.GameLocation().EnumerateFiles())
.Where(e => e.Extension != Consts.HashFileExtension)
.PMap(Queue, async e => (await e.FileHashCachedAsync(), e));
HashedArchives.SetTo(hashResults

View File

@ -17,17 +17,14 @@ namespace Wabbajack.Lib.CompilationSteps
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (_gameFolderFilesExists)
{
if (source.AbsolutePath.InFolder(_gameFolder))
{
var result = source.EvolveTo<IgnoredDirectly>();
result.Reason = $"Ignoring game files because {Consts.GameFolderFilesDir} exists";
return result;
}
}
if (!_gameFolderFilesExists) return null;
if (!source.AbsolutePath.InFolder(_gameFolder)) return null;
var result = source.EvolveTo<IgnoredDirectly>();
result.Reason = $"Ignoring game files because {Consts.GameFolderFilesDir} exists";
return result;
return null;
}
public override IState GetState()

View File

@ -94,5 +94,10 @@ namespace Wabbajack.Lib.Downloaders
public abstract string? GetManifestURL(Archive a);
public abstract string[] GetMetaIni();
public string GetMetaIniString()
{
return string.Join("\n", GetMetaIni());
}
}
}

View File

@ -46,13 +46,18 @@ namespace Wabbajack.Lib.Downloaders
public Game Game { get; set; }
public RelativePath GameFile { get; set; }
public Hash Hash { get; set; }
public string GameVersion { get; }
public string GameVersion { get; set; } = "";
public State(string gameVersion)
{
GameVersion = gameVersion;
}
public State()
{
}
[JsonIgnore]
internal AbsolutePath SourcePath => Game.MetaData().GameLocation().Combine(GameFile);
@ -93,6 +98,7 @@ namespace Wabbajack.Lib.Downloaders
{
return new[] {"[General]", $"gameName={Game.MetaData().MO2ArchiveName}", $"gameFile={GameFile}"};
}
}
}
}

View File

@ -1,24 +1,17 @@
using Compression.BSA;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Alphaleonis.Win32.Filesystem;
using Wabbajack.Common;
using Wabbajack.Lib.CompilationSteps;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.FileUploader;
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 Game = Wabbajack.Common.Game;
using Wabbajack.VirtualFileSystem;
using Path = Alphaleonis.Win32.Filesystem.Path;
namespace Wabbajack.Lib
@ -105,7 +98,7 @@ namespace Wabbajack.Lib
var roots = new List<AbsolutePath>
{
MO2Folder, GamePath, MO2DownloadsFolder
MO2Folder, GamePath, MO2DownloadsFolder, CompilingGame.GameLocation()
};
// TODO: make this generic so we can add more paths
@ -172,6 +165,7 @@ namespace Wabbajack.Lib
UpdateTracker.NextStep("Pre-validating Archives");
// Find all Downloads
IndexedArchives = (await MO2DownloadsFolder.EnumerateFiles()
.Where(f => f.WithExtension(Consts.MetaFileExtension).Exists)
.PMap(Queue, async f => new IndexedArchive(VFS.Index.ByRootPath[f])
@ -180,9 +174,37 @@ namespace Wabbajack.Lib
IniData = f.WithExtension(Consts.MetaFileExtension).LoadIniFile(),
Meta = await f.WithExtension(Consts.MetaFileExtension).ReadAllTextAsync()
})).ToList();
var stockGameFolder = CompilingGame.GameLocation();
foreach (var (relativePath, hash) in await ClientAPI.GetGameFiles(CompilingGame.Game, Version.Parse(CompilingGame.InstalledVersion)))
{
if (!VFS.Index.ByRootPath.TryGetValue(relativePath.RelativeTo(stockGameFolder), out var virtualFile))
continue;
if (virtualFile.Hash != hash)
{
Utils.Log(
$"File {relativePath} int the game folder appears to be modified, it will not be used during compilation");
continue;
}
var state = new GameFileSourceDownloader.State
{
Game = CompilingGame.Game, GameVersion = CompilingGame.InstalledVersion, GameFile = relativePath
};
Utils.Log($"Adding Game file: {relativePath}");
IndexedArchives.Add(new IndexedArchive(virtualFile)
{
Name = (string)relativePath.FileName,
IniData = state.GetMetaIniString().LoadIniString(),
Meta = state.GetMetaIniString()
});
}
await CleanInvalidArchives();
UpdateTracker.NextStep("Finding Install Files");
@ -395,11 +417,13 @@ namespace Wabbajack.Lib
await SelectedArchives.PMap(Queue, async a =>
{
var source = MO2DownloadsFolder.Combine(a.Name + Consts.MetaFileExtension);
var ini = a.State.GetMetaIniString();
var (id, fullPath) = await IncludeString(ini);
InstallDirectives.Add(new ArchiveMeta
{
SourceDataID = await IncludeFile(source),
SourceDataID = id,
Size = source.Size,
Hash = await source.FileHashAsync(),
Hash = await fullPath.FileHashAsync(),
To = source.FileName
});
});
@ -523,9 +547,6 @@ namespace Wabbajack.Lib
// Ignore the ModOrganizer.ini file it contains info created by MO2 on startup
new IncludeStubbedConfigFiles(this),
new IncludeLootFiles(this),
new IgnoreStartsWith(this, Path.Combine((string)Consts.GameFolderFilesDir, "Data")),
new IgnoreStartsWith(this, Path.Combine((string)Consts.GameFolderFilesDir, "Papyrus Compiler")),
new IgnoreStartsWith(this, Path.Combine((string)Consts.GameFolderFilesDir, "Skyrim")),
new IgnoreRegex(this, Consts.GameFolderFilesDir + "\\\\.*\\.bsa"),
new IncludeRegex(this, "^[^\\\\]*\\.bat$"),
new IncludeModIniData(this),

View File

@ -32,8 +32,6 @@ namespace Wabbajack.Lib
public AbsolutePath? GameFolder { get; set; }
public GameMetaData Game { get; }
public MO2Installer(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters parameters)
: base(
archive: archive,
@ -41,11 +39,15 @@ namespace Wabbajack.Lib
outputFolder: outputFolder,
downloadFolder: downloadFolder,
parameters: parameters,
steps: 20)
steps: 20,
game: modList.GameType)
{
Game = ModList.GameType.MetaData();
var gameExe = Consts.GameFolderFilesDir.Combine(modList.GameType.MetaData().MainExecutable!);
RedirectGamePath = modList.Directives.Any(d => d.To == gameExe);
}
public bool RedirectGamePath { get; }
protected override async Task<bool> _Begin(CancellationToken cancel)
{
if (cancel.IsCancellationRequested) return false;
@ -379,9 +381,12 @@ namespace Wabbajack.Lib
{
var data = Encoding.UTF8.GetString(await LoadBytesFromPath(directive.SourceDataID));
data = data.Replace(Consts.GAME_PATH_MAGIC_BACK, (string)GameFolder!);
data = data.Replace(Consts.GAME_PATH_MAGIC_DOUBLE_BACK, ((string)GameFolder!).Replace("\\", "\\\\"));
data = data.Replace(Consts.GAME_PATH_MAGIC_FORWARD, ((string)GameFolder).Replace("\\", "/"));
var gameFolder = (string)(RedirectGamePath ? Consts.GameFolderFilesDir.RelativeTo(OutputFolder) : GameFolder!);
data = data.Replace(Consts.GAME_PATH_MAGIC_BACK, gameFolder);
data = data.Replace(Consts.GAME_PATH_MAGIC_DOUBLE_BACK, gameFolder.Replace("\\", "\\\\"));
data = data.Replace(Consts.GAME_PATH_MAGIC_FORWARD, gameFolder.Replace("\\", "/"));
data = data.Replace(Consts.MO2_PATH_MAGIC_BACK, (string)OutputFolder);
data = data.Replace(Consts.MO2_PATH_MAGIC_DOUBLE_BACK, ((string)OutputFolder).Replace("\\", "\\\\"));

View File

@ -0,0 +1,53 @@
using System.Threading.Tasks;
using Wabbajack.Common;
namespace Wabbajack.Lib.Tasks
{
public class MigrateGameFolder
{
public static async Task<bool> Execute(AbsolutePath mo2Folder)
{
var iniPath = mo2Folder.Combine(Consts.ModOrganizer2Ini);
if (!iniPath.Exists)
{
Utils.Log($"Game folder conversion failed, {Consts.ModOrganizer2Ini} does not exist in {mo2Folder}");
return false;
}
var newGamePath = mo2Folder.Combine(Consts.GameFolderFilesDir);
newGamePath.CreateDirectory();
var gameIni = iniPath.LoadIniFile();
if (!GameRegistry.TryGetByFuzzyName((string)gameIni.General.gameName, out var gameMeta))
{
Utils.Log($"Could not locate game for {gameIni.General.gameName}");
return false;
}
var orginGamePath = gameMeta.GameLocation();
foreach (var file in gameMeta.GameLocation().EnumerateFiles())
{
var relPath = file.RelativeTo(orginGamePath);
var newFile = relPath.RelativeTo(newGamePath);
if (newFile.Exists)
{
Utils.Log($"Skipping {relPath} it already exists in the target path");
continue;
}
Utils.Log($"Copying/Linking {relPath}");
await file.HardLinkIfOversize(newFile);
}
Utils.Log("Remapping INI");
var iniString = await iniPath.ReadAllTextAsync();
iniString = iniString.Replace((string)orginGamePath, (string)newGamePath);
iniString = iniString.Replace(((string)orginGamePath).Replace(@"\", @"\\"), ((string)newGamePath).Replace(@"\", @"\\"));
iniString = iniString.Replace(((string)orginGamePath).Replace(@"\", @"/"), ((string)newGamePath).Replace(@"\", @"/"));
await iniPath.WriteAllTextAsync(iniString);
return true;
}
}
}

View File

@ -81,7 +81,7 @@ namespace Wabbajack.Lib.WebAutomation
IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser? newBrowser)
{
// Block popups
newBrowser = chromiumWebBrowser;
newBrowser = null;
return true;
}

View File

@ -16,6 +16,7 @@ namespace Wabbajack.Lib.WebAutomation
{
_browser = new ChromiumWebBrowser();
_driver = new CefSharpWrapper(_browser);
}
public static async Task<Driver> Create()

View File

@ -505,7 +505,7 @@ namespace Wabbajack.Test
await folder.DeleteDirectory();
folder.CreateDirectory();
var inst = new TestInstaller(default, null, default, folder, null);
var inst = new TestInstaller(default, new ModList {GameType = Game.SkyrimSpecialEdition}, default, folder, null);
await inst.DownloadMissingArchives(archivesa, true);
await inst.DownloadMissingArchives(archivesb, true);
@ -551,7 +551,7 @@ namespace Wabbajack.Test
class TestInstaller : AInstaller
{
public TestInstaller(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters parameters)
: base(archive, modList, outputFolder, downloadFolder, parameters, steps: 1)
: base(archive, modList, outputFolder, downloadFolder, parameters, steps: 1, modList.GameType)
{
Queue.SetActiveThreadsObservable(Observable.Return(1));
}

View File

@ -61,7 +61,6 @@ namespace Wabbajack.Test
DownloadAndInstall(Game.SkyrimSpecialEdition, 12604, "SkyUI"),
DownloadAndInstall(Game.Fallout4, 11925, "Anti-Tank Rifle"),
DownloadAndInstall(Game.SkyrimSpecialEdition, 4783, "Frost Armor UNP"),
DownloadAndInstall(Game.Fallout4, 43474, "EM 2 Rifle No.9 Mk1"),
DownloadAndInstall(Game.SkyrimSpecialEdition, 32359, "Frost Armor HDT"));
// We're going to fully patch this mod from another source.

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@ -159,31 +160,6 @@ namespace Wabbajack.Test
Assert.False(extraFolder.Exists);
}
[Fact]
public async Task CleanedESMTest()
{
var profile = utils.AddProfile();
var mod = utils.AddMod("Cleaned ESMs");
var updateEsm = utils.AddModFile(mod, @"Update.esm", 10);
await utils.Configure();
var gameFile = utils.GameFolder.Combine("Data", "Update.esm");
utils.GenerateRandomFileData(gameFile, 20);
var modlist = await CompileAndInstall(profile);
utils.VerifyInstalledFile(mod, @"Update.esm");
var compiler = await ConfigureAndRunCompiler(profile);
// Update the file and verify that it throws an error.
utils.GenerateRandomFileData(gameFile, 20);
var exception = await Assert.ThrowsAsync<InvalidGameESMError>(async () => await Install(compiler));
Assert.IsAssignableFrom<InvalidGameESMError>(exception);
}
[Fact]
public async Task SetScreenSizeTest()
{
@ -421,6 +397,40 @@ namespace Wabbajack.Test
utils.VerifyInstalledFile(mod, @"baz.bsa");
}
[Fact]
public async Task CanSourceFilesFromStockGameFiles()
{
Consts.TestMode = false;
var profile = utils.AddProfile();
var mod = utils.AddMod();
var skyrimExe = utils.AddModFile(mod, @"Data\test.exe", 10);
var gameFolder = Consts.GameFolderFilesDir.RelativeTo(utils.MO2Folder);
gameFolder.CreateDirectory();
var gameMeta = Game.SkyrimSpecialEdition.MetaData();
await gameMeta.GameLocation().Combine(gameMeta.MainExecutable!).CopyToAsync(skyrimExe);
await gameMeta.GameLocation().Combine(gameMeta.MainExecutable!).CopyToAsync(gameFolder.Combine(gameMeta.MainExecutable!));
await utils.Configure();
await CompileAndInstall(profile);
utils.VerifyInstalledFile(mod, @"Data\test.exe");
Assert.False("SkyrimSE.exe".RelativeTo(utils.DownloadsFolder).Exists, "File should not appear in the download folder because it should be copied from the game folder");
var file = "ModOrganizer.ini".RelativeTo(utils.InstallFolder);
Assert.True(file.Exists);
var ini = file.LoadIniFile();
Assert.Equal(((AbsolutePath)(string)ini?.General?.gamePath).Combine(gameMeta.MainExecutable),
Consts.GameFolderFilesDir.Combine(gameMeta.MainExecutable).RelativeTo(utils.InstallFolder));
Consts.TestMode = true;
}
[Fact]
public async Task NoMatchIncludeIncludesNonMatchingFiles()

View File

@ -0,0 +1,49 @@
using System.Threading.Tasks;
using Wabbajack.Common;
using Wabbajack.Lib.Tasks;
using Xunit;
using Xunit.Abstractions;
namespace Wabbajack.Test
{
public class TasksTests : ACompilerTest
{
[Fact]
public async Task CanRemapGameFolder()
{
await using var tempFolder = await TempFolder.Create();
var gameff = tempFolder.Dir.Combine(Consts.GameFolderFilesDir);
gameff.CreateDirectory();
await gameff.Combine("some_file.txt").WriteAllTextAsync("some_file");
await gameff.Combine("steam_api64.dll").WriteAllTextAsync("steam_api");
var meta = Game.SkyrimSpecialEdition.MetaData();
await tempFolder.Dir.Combine(Consts.ModOrganizer2Ini)
.WriteAllLinesAsync(
"[General]",
$"gameName={meta.MO2Name}",
$"gamePath={meta.GameLocation()}",
$"pathDouble={meta.GameLocation().ToString().Replace(@"\", @"\\")}",
$"pathForward={meta.GameLocation().ToString().Replace(@"\", @"/")}");
Assert.True(await MigrateGameFolder.Execute(tempFolder.Dir));
Assert.Equal("some_file", await gameff.Combine("some_file.txt").ReadAllTextAsync());
Assert.Equal("steam_api", await gameff.Combine("steam_api64.dll").ReadAllTextAsync());
Assert.Equal(Hash.FromBase64("k5EWx/9Woqg="), await gameff.Combine(@"Data\Skyrim - Interface.bsa").FileHashAsync());
var ini = tempFolder.Dir.Combine(Consts.ModOrganizer2Ini).LoadIniFile();
Assert.Equal(gameff, (AbsolutePath)(string)ini.General.gamePath);
Assert.Equal(gameff, (AbsolutePath)(string)ini.General.pathDouble);
Assert.Equal(gameff, (AbsolutePath)(string)ini.General.pathForward);
}
public TasksTests(ITestOutputHelper helper) : base(helper)
{
}
}
}