2019-08-04 22:08:03 +00:00
|
|
|
|
using CG.Web.MegaApiClient;
|
|
|
|
|
using Compression.BSA;
|
2019-08-24 23:20:54 +00:00
|
|
|
|
using Ookii.Dialogs.Wpf;
|
2019-07-23 04:17:52 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Net.Http;
|
2019-07-31 03:59:19 +00:00
|
|
|
|
using System.Reflection;
|
2019-07-23 04:17:52 +00:00
|
|
|
|
using System.Text;
|
2019-07-24 04:31:08 +00:00
|
|
|
|
using System.Text.RegularExpressions;
|
2019-07-23 04:17:52 +00:00
|
|
|
|
using System.Threading.Tasks;
|
2019-08-20 04:57:08 +00:00
|
|
|
|
using VFS;
|
2019-07-23 04:17:52 +00:00
|
|
|
|
using Wabbajack.Common;
|
|
|
|
|
|
|
|
|
|
namespace Wabbajack
|
|
|
|
|
{
|
|
|
|
|
public class Installer
|
|
|
|
|
{
|
2019-08-20 04:57:08 +00:00
|
|
|
|
public VirtualFileSystem VFS
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return VirtualFileSystem.VFS;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-23 04:17:52 +00:00
|
|
|
|
public Installer(ModList mod_list, string output_folder, Action<string> log_fn)
|
|
|
|
|
{
|
|
|
|
|
Outputfolder = output_folder;
|
|
|
|
|
ModList = mod_list;
|
|
|
|
|
Log_Fn = log_fn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string Outputfolder { get; }
|
|
|
|
|
public string DownloadFolder
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return Path.Combine(Outputfolder, "downloads");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public ModList ModList { get; }
|
|
|
|
|
public Action<string> Log_Fn { get; }
|
|
|
|
|
public Dictionary<string, string> HashedArchives { get; private set; }
|
2019-07-26 20:59:14 +00:00
|
|
|
|
|
2019-07-23 04:17:52 +00:00
|
|
|
|
public string NexusAPIKey { get; private set; }
|
2019-08-07 23:06:38 +00:00
|
|
|
|
public bool IgnoreMissingFiles { get; internal set; }
|
2019-08-24 23:20:54 +00:00
|
|
|
|
public string GameFolder { get; private set; }
|
2019-07-23 04:17:52 +00:00
|
|
|
|
|
|
|
|
|
public void Info(string msg, params object[] args)
|
|
|
|
|
{
|
|
|
|
|
if (args.Length > 0)
|
|
|
|
|
msg = String.Format(msg, args);
|
|
|
|
|
Log_Fn(msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Status(string msg, params object[] args)
|
|
|
|
|
{
|
|
|
|
|
if (args.Length > 0)
|
|
|
|
|
msg = String.Format(msg, args);
|
|
|
|
|
WorkQueue.Report(msg, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Status(int progress, string msg, params object[] args)
|
|
|
|
|
{
|
|
|
|
|
if (args.Length > 0)
|
|
|
|
|
msg = String.Format(msg, args);
|
|
|
|
|
WorkQueue.Report(msg, progress);
|
|
|
|
|
}
|
|
|
|
|
private void Error(string msg, params object[] args)
|
|
|
|
|
{
|
|
|
|
|
if (args.Length > 0)
|
|
|
|
|
msg = String.Format(msg, args);
|
|
|
|
|
Log_Fn(msg);
|
|
|
|
|
throw new Exception(msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Install()
|
|
|
|
|
{
|
|
|
|
|
Directory.CreateDirectory(Outputfolder);
|
|
|
|
|
Directory.CreateDirectory(DownloadFolder);
|
|
|
|
|
|
2019-08-24 23:20:54 +00:00
|
|
|
|
if (ModList.Directives.OfType<RemappedInlineFile>().FirstOrDefault() != null && !LocateGameFolder())
|
|
|
|
|
{
|
|
|
|
|
Info("Stopping installation because game folder was not selected");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-23 04:17:52 +00:00
|
|
|
|
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 {0}", a.Name);
|
2019-08-07 23:06:38 +00:00
|
|
|
|
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");
|
|
|
|
|
}
|
2019-07-23 04:17:52 +00:00
|
|
|
|
}
|
2019-08-20 04:57:08 +00:00
|
|
|
|
|
|
|
|
|
PrimeVFS();
|
|
|
|
|
|
2019-07-23 04:17:52 +00:00
|
|
|
|
BuildFolderStructure();
|
|
|
|
|
InstallArchives();
|
2019-07-23 04:27:26 +00:00
|
|
|
|
InstallIncludedFiles();
|
2019-07-28 23:04:23 +00:00
|
|
|
|
BuildBSAs();
|
2019-07-23 04:17:52 +00:00
|
|
|
|
|
|
|
|
|
Info("Installation complete! You may exit the program.");
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-24 23:20:54 +00:00
|
|
|
|
private bool LocateGameFolder()
|
|
|
|
|
{
|
|
|
|
|
var vf = new VistaFolderBrowserDialog();
|
|
|
|
|
vf.Description = "Please Locate Your Game Installation Path";
|
|
|
|
|
vf.UseDescriptionForTitle = true;
|
|
|
|
|
if (vf.ShowDialog() == true)
|
|
|
|
|
{
|
|
|
|
|
GameFolder = vf.SelectedPath;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-08-20 04:57:08 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// We don't want to make the installer index all the archives, that's just a waste of time, so instead
|
|
|
|
|
/// we'll pass just enough information to VFS to let it know about the files we have.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void PrimeVFS()
|
|
|
|
|
{
|
|
|
|
|
HashedArchives.Do(a => VFS.AddKnown(new VirtualFile()
|
|
|
|
|
{
|
2019-08-20 22:37:55 +00:00
|
|
|
|
Paths = new string[] { a.Value },
|
|
|
|
|
Hash = a.Key
|
2019-08-20 04:57:08 +00:00
|
|
|
|
}));
|
|
|
|
|
VFS.RefreshIndexes();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ModList.Directives
|
|
|
|
|
.OfType<FromArchive>()
|
|
|
|
|
.Do(f =>
|
|
|
|
|
{
|
2019-08-20 22:37:55 +00:00
|
|
|
|
var updated_path = new string[f.ArchiveHashPath.Length];
|
2019-08-20 04:57:08 +00:00
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-28 23:04:23 +00:00
|
|
|
|
private void BuildBSAs()
|
|
|
|
|
{
|
|
|
|
|
var bsas = ModList.Directives.OfType<CreateBSA>().ToList();
|
2019-07-30 03:32:52 +00:00
|
|
|
|
Info($"Building {bsas.Count} bsa files");
|
2019-07-28 23:04:23 +00:00
|
|
|
|
|
|
|
|
|
bsas.Do(bsa =>
|
|
|
|
|
{
|
|
|
|
|
Status($"Building {bsa.To}");
|
|
|
|
|
var source_dir = Path.Combine(Outputfolder, Consts.BSACreationDir, bsa.TempID);
|
2019-07-30 03:32:52 +00:00
|
|
|
|
var source_files = Directory.EnumerateFiles(source_dir, "*", SearchOption.AllDirectories)
|
|
|
|
|
.Select(e => e.Substring(source_dir.Length + 1))
|
|
|
|
|
.ToList();
|
|
|
|
|
|
2019-08-07 23:06:38 +00:00
|
|
|
|
if(source_files.Count > 0)
|
2019-07-30 03:32:52 +00:00
|
|
|
|
using (var a = new BSABuilder())
|
2019-07-28 23:04:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
2019-07-30 03:32:52 +00:00
|
|
|
|
//a.Create(Path.Combine(Outputfolder, bsa.To), (bsa_archive_type_t)bsa.Type, entries);
|
|
|
|
|
a.HeaderType = (VersionType)bsa.Type;
|
|
|
|
|
a.FileFlags = (FileFlags)bsa.FileFlags;
|
|
|
|
|
a.ArchiveFlags = (ArchiveFlags)bsa.ArchiveFlags;
|
2019-07-28 23:04:23 +00:00
|
|
|
|
|
2019-07-30 03:32:52 +00:00
|
|
|
|
source_files.PMap(f =>
|
2019-07-28 23:04:23 +00:00
|
|
|
|
{
|
2019-07-30 03:32:52 +00:00
|
|
|
|
Status($"Adding {f} to BSA");
|
|
|
|
|
using (var fs = File.OpenRead(Path.Combine(source_dir, f)))
|
|
|
|
|
a.AddFile(f, fs);
|
|
|
|
|
});
|
2019-07-28 23:04:23 +00:00
|
|
|
|
|
2019-07-30 03:32:52 +00:00
|
|
|
|
Info($"Writing {bsa.To}");
|
|
|
|
|
a.Build(Path.Combine(Outputfolder, bsa.To));
|
2019-07-28 23:04:23 +00:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2019-08-20 22:37:55 +00:00
|
|
|
|
if (Directory.Exists(Consts.BSACreationDir))
|
|
|
|
|
{
|
|
|
|
|
Info($"Removing temp folder {Consts.BSACreationDir}");
|
|
|
|
|
Directory.Delete(Path.Combine(Outputfolder, Consts.BSACreationDir), true);
|
|
|
|
|
}
|
2019-08-11 22:57:32 +00:00
|
|
|
|
|
2019-07-28 23:04:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-23 04:27:26 +00:00
|
|
|
|
private void InstallIncludedFiles()
|
|
|
|
|
{
|
|
|
|
|
Info("Writing inline files");
|
|
|
|
|
ModList.Directives
|
|
|
|
|
.OfType<InlineFile>()
|
|
|
|
|
.PMap(directive =>
|
|
|
|
|
{
|
|
|
|
|
Status("Writing included file {0}", directive.To);
|
|
|
|
|
var out_path = Path.Combine(Outputfolder, directive.To);
|
|
|
|
|
if (File.Exists(out_path)) File.Delete(out_path);
|
2019-08-24 23:20:54 +00:00
|
|
|
|
if (directive is RemappedInlineFile)
|
|
|
|
|
{
|
|
|
|
|
WriteRemappedFile((RemappedInlineFile)directive);
|
|
|
|
|
}
|
2019-08-25 03:46:32 +00:00
|
|
|
|
else if (directive is CleanedESM)
|
|
|
|
|
{
|
|
|
|
|
GenerateCleanedESM((CleanedESM)directive);
|
|
|
|
|
}
|
2019-08-24 23:20:54 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
File.WriteAllBytes(out_path, directive.SourceData.FromBase64());
|
|
|
|
|
}
|
2019-07-23 04:27:26 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-25 03:46:32 +00:00
|
|
|
|
private void GenerateCleanedESM(CleanedESM directive)
|
|
|
|
|
{
|
|
|
|
|
var filename = Path.GetFileName(directive.To);
|
|
|
|
|
var game_file = Path.Combine(GameFolder, "Data", filename);
|
|
|
|
|
Info($"Generating cleaned ESM for {filename}");
|
|
|
|
|
if (!File.Exists(game_file))
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidDataException($"Missing {filename} at {game_file}");
|
|
|
|
|
}
|
|
|
|
|
Status($"Hashing game version of {filename}");
|
|
|
|
|
var sha = Utils.FileSHA256(game_file);
|
|
|
|
|
if (sha != directive.SourceESMHash)
|
|
|
|
|
throw new InvalidDataException($"Cannot patch {filename} from the game folder hashes don't match have you already cleaned the file?");
|
|
|
|
|
|
|
|
|
|
var patch_data = directive.SourceData.FromBase64();
|
|
|
|
|
var to_file = Path.Combine(Outputfolder, directive.To);
|
|
|
|
|
Status($"Patching {filename}");
|
|
|
|
|
using (var output = File.OpenWrite(to_file)) {
|
|
|
|
|
BSDiff.Apply(File.OpenRead(game_file), () => new MemoryStream(patch_data), output);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-24 23:20:54 +00:00
|
|
|
|
private void WriteRemappedFile(RemappedInlineFile directive)
|
|
|
|
|
{
|
|
|
|
|
var data = Encoding.UTF8.GetString(directive.SourceData.FromBase64());
|
|
|
|
|
|
|
|
|
|
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, Outputfolder);
|
|
|
|
|
data = data.Replace(Consts.MO2_PATH_MAGIC_DOUBLE_BACK, Outputfolder.Replace("\\", "\\\\"));
|
|
|
|
|
data = data.Replace(Consts.MO2_PATH_MAGIC_FORWARD, Outputfolder.Replace("\\", "/"));
|
|
|
|
|
|
|
|
|
|
File.WriteAllText(Path.Combine(Outputfolder, directive.To), data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-23 04:17:52 +00:00
|
|
|
|
private void BuildFolderStructure()
|
|
|
|
|
{
|
|
|
|
|
Info("Building Folder Structure");
|
|
|
|
|
ModList.Directives
|
|
|
|
|
.Select(d => Path.Combine(Outputfolder, Path.GetDirectoryName(d.To)))
|
|
|
|
|
.ToHashSet()
|
|
|
|
|
.Do(f => {
|
|
|
|
|
if (Directory.Exists(f)) return;
|
|
|
|
|
Directory.CreateDirectory(f);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void InstallArchives()
|
|
|
|
|
{
|
|
|
|
|
Info("Installing Archives");
|
2019-08-07 23:06:38 +00:00
|
|
|
|
Info("Grouping Install Files");
|
2019-07-23 04:17:52 +00:00
|
|
|
|
var grouped = ModList.Directives
|
|
|
|
|
.OfType<FromArchive>()
|
2019-08-09 04:07:23 +00:00
|
|
|
|
.GroupBy(e => e.ArchiveHashPath[0])
|
2019-07-23 04:17:52 +00:00
|
|
|
|
.ToDictionary(k => k.Key);
|
|
|
|
|
var archives = ModList.Archives
|
2019-08-07 23:06:38 +00:00
|
|
|
|
.Select(a => new { Archive = a, AbsolutePath = HashedArchives.GetOrDefault(a.Hash) })
|
|
|
|
|
.Where(a => a.AbsolutePath != null)
|
2019-07-23 04:17:52 +00:00
|
|
|
|
.ToList();
|
|
|
|
|
|
2019-08-07 23:06:38 +00:00
|
|
|
|
Info("Installing Archives");
|
2019-07-23 04:17:52 +00:00
|
|
|
|
archives.PMap(a => InstallArchive(a.Archive, a.AbsolutePath, grouped[a.Archive.Hash]));
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void InstallArchive(Archive archive, string absolutePath, IGrouping<string, FromArchive> grouping)
|
|
|
|
|
{
|
2019-08-22 22:05:16 +00:00
|
|
|
|
Status($"Extracting {archive.Name}");
|
2019-08-09 04:07:23 +00:00
|
|
|
|
var files = grouping.GroupBy(e => e.FullPath)
|
2019-07-23 04:17:52 +00:00
|
|
|
|
.ToDictionary(e => e.Key);
|
|
|
|
|
|
2019-08-20 04:57:08 +00:00
|
|
|
|
|
|
|
|
|
var vfiles = files.Select(g =>
|
2019-07-23 04:17:52 +00:00
|
|
|
|
{
|
2019-08-20 04:57:08 +00:00
|
|
|
|
var first_file = g.Value.First();
|
|
|
|
|
var file = VFS.FileForArchiveHashPath(first_file.ArchiveHashPath);
|
|
|
|
|
file.StagedPath = first_file.To;
|
|
|
|
|
return file;
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
VFS.Stage(vfiles);
|
|
|
|
|
|
|
|
|
|
vfiles.Do(f => f.StagedPath = null);
|
2019-07-23 04:17:52 +00:00
|
|
|
|
|
|
|
|
|
Status("Copying duplicated files for {0}", archive.Name);
|
|
|
|
|
|
|
|
|
|
foreach (var dups in files.Where(e => e.Value.Count() > 1).Select(v => v.Value))
|
|
|
|
|
{
|
|
|
|
|
var ffrom = dups.First();
|
|
|
|
|
var from_path = Path.Combine(Outputfolder, ffrom.To);
|
|
|
|
|
foreach (var to in dups.Skip(1))
|
|
|
|
|
{
|
|
|
|
|
var to_path = Path.Combine(Outputfolder, to.To);
|
|
|
|
|
if (to_path.FileExists()) File.Delete(to_path);
|
|
|
|
|
File.Copy(from_path, to_path);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Now patch all the files from this archive
|
|
|
|
|
foreach (var to_patch in grouping.OfType<PatchedFromArchive>())
|
|
|
|
|
{
|
|
|
|
|
using (var patch_stream = new MemoryStream())
|
|
|
|
|
{
|
|
|
|
|
Status("Patching {0}", Path.GetFileName(to_patch.To));
|
|
|
|
|
// Read in the patch data
|
|
|
|
|
|
|
|
|
|
var patch_data = to_patch.Patch.FromBase64();
|
|
|
|
|
|
|
|
|
|
var to_file = Path.Combine(Outputfolder, to_patch.To);
|
|
|
|
|
MemoryStream old_data = new MemoryStream(File.ReadAllBytes(to_file));
|
|
|
|
|
|
2019-08-03 13:02:12 +00:00
|
|
|
|
// Remove the file we're about to patch
|
|
|
|
|
File.Delete(to_file);
|
|
|
|
|
|
2019-07-23 04:17:52 +00:00
|
|
|
|
// Patch it
|
|
|
|
|
using (var out_stream = File.OpenWrite(to_file))
|
|
|
|
|
{
|
|
|
|
|
BSDiff.Apply(old_data, () => new MemoryStream(patch_data), out_stream);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DownloadArchives()
|
|
|
|
|
{
|
|
|
|
|
var missing = ModList.Archives.Where(a => !HashedArchives.ContainsKey(a.Hash)).ToList();
|
|
|
|
|
Info("Missing {0} archives", missing.Count);
|
|
|
|
|
|
|
|
|
|
Info("Getting Nexus API Key, if a browser appears, please accept");
|
|
|
|
|
NexusAPIKey = NexusAPI.GetNexusAPIKey();
|
|
|
|
|
|
2019-08-04 22:08:03 +00:00
|
|
|
|
var user_status = NexusAPI.GetUserStatus(NexusAPIKey);
|
|
|
|
|
|
|
|
|
|
if (!user_status.is_premium) {
|
|
|
|
|
Info($"Automated installs with Wabbajack requires a premium nexus account. {user_status.name} is not a premium account");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-23 04:17:52 +00:00
|
|
|
|
DownloadMissingArchives(missing);
|
2019-08-04 22:08:03 +00:00
|
|
|
|
return;
|
2019-07-23 04:17:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DownloadMissingArchives(List<Archive> missing)
|
|
|
|
|
{
|
|
|
|
|
missing.PMap(archive =>
|
|
|
|
|
{
|
2019-08-04 22:08:03 +00:00
|
|
|
|
Info($"Downloading {archive.Name}");
|
|
|
|
|
var output_path = Path.Combine(DownloadFolder, archive.Name);
|
|
|
|
|
|
|
|
|
|
if (output_path.FileExists())
|
|
|
|
|
File.Delete(output_path);
|
|
|
|
|
|
2019-08-11 22:57:32 +00:00
|
|
|
|
|
2019-07-26 20:59:14 +00:00
|
|
|
|
switch (archive) {
|
|
|
|
|
case NexusMod a:
|
2019-08-02 22:31:13 +00:00
|
|
|
|
Info($"Downloading Nexus Archive - {archive.Name} - {a.GameName} - {a.ModID} - {a.FileID}");
|
|
|
|
|
string url;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
url = NexusAPI.GetNexusDownloadLink(a as NexusMod, NexusAPIKey);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Info($"{a.Name} - Error Getting Nexus Download URL - {ex.Message}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-07-26 20:59:14 +00:00
|
|
|
|
DownloadURLDirect(archive, url);
|
|
|
|
|
break;
|
2019-08-04 22:08:03 +00:00
|
|
|
|
case MEGAArchive a:
|
|
|
|
|
DownloadMegaArchive(a);
|
|
|
|
|
break;
|
2019-07-26 20:59:14 +00:00
|
|
|
|
case GoogleDriveMod a:
|
|
|
|
|
DownloadGoogleDriveArchive(a);
|
|
|
|
|
break;
|
|
|
|
|
case MODDBArchive a:
|
|
|
|
|
DownloadModDBArchive(archive, (archive as MODDBArchive).URL);
|
|
|
|
|
break;
|
2019-08-02 22:31:13 +00:00
|
|
|
|
case MediaFireArchive a:
|
|
|
|
|
DownloadMediaFireArchive(archive, a.URL);
|
|
|
|
|
break;
|
2019-07-26 20:59:14 +00:00
|
|
|
|
case DirectURLArchive a:
|
2019-08-02 22:31:13 +00:00
|
|
|
|
DownloadURLDirect(archive, a.URL, headers:a.Headers);
|
2019-07-26 20:59:14 +00:00
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
2019-07-23 04:17:52 +00:00
|
|
|
|
|
2019-07-26 20:59:14 +00:00
|
|
|
|
}
|
2019-07-23 04:17:52 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-02 22:31:13 +00:00
|
|
|
|
private void DownloadMediaFireArchive(Archive a, string url)
|
|
|
|
|
{
|
|
|
|
|
var client = new HttpClient();
|
|
|
|
|
var result = client.GetStringSync(url);
|
|
|
|
|
var regex = new Regex("(?<= href =\\\").*\\.mediafire\\.com.*(?=\\\")");
|
|
|
|
|
var confirm = regex.Match(result);
|
|
|
|
|
DownloadURLDirect(a, confirm.ToString(), client);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-04 22:08:03 +00:00
|
|
|
|
private void DownloadMegaArchive(MEGAArchive m)
|
|
|
|
|
{
|
|
|
|
|
var client = new MegaApiClient();
|
|
|
|
|
Status("Logging into MEGA (as anonymous)");
|
|
|
|
|
client.LoginAnonymous();
|
|
|
|
|
var file_link = new Uri(m.URL);
|
|
|
|
|
var node = client.GetNodeFromLink(file_link);
|
|
|
|
|
Status("Downloading MEGA file: {0}", m.Name);
|
|
|
|
|
|
|
|
|
|
var output_path = Path.Combine(DownloadFolder, m.Name);
|
|
|
|
|
client.DownloadFile(file_link, output_path);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-26 20:59:14 +00:00
|
|
|
|
private void DownloadGoogleDriveArchive(GoogleDriveMod a)
|
|
|
|
|
{
|
|
|
|
|
var initial_url = $"https://drive.google.com/uc?id={a.Id}&export=download";
|
|
|
|
|
var client = new HttpClient();
|
|
|
|
|
var result = client.GetStringSync(initial_url);
|
|
|
|
|
var regex = new Regex("(?<=/uc\\?export=download&confirm=).*(?=;id=)");
|
|
|
|
|
var confirm = regex.Match(result);
|
|
|
|
|
DownloadURLDirect(a, $"https://drive.google.com/uc?export=download&confirm={confirm}&id={a.Id}", client);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-24 04:31:08 +00:00
|
|
|
|
private void DownloadModDBArchive(Archive archive, string url)
|
|
|
|
|
{
|
|
|
|
|
var client = new HttpClient();
|
|
|
|
|
var result = client.GetStringSync(url);
|
|
|
|
|
var regex = new Regex("https:\\/\\/www\\.moddb\\.com\\/downloads\\/mirror\\/.*(?=\\\")");
|
|
|
|
|
var match = regex.Match(result);
|
|
|
|
|
DownloadURLDirect(archive, match.Value);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-02 22:31:13 +00:00
|
|
|
|
private void DownloadURLDirect(Archive archive, string url, HttpClient client = null, List<string> headers = null)
|
2019-07-23 04:17:52 +00:00
|
|
|
|
{
|
2019-08-02 22:31:13 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (client == null)
|
|
|
|
|
{
|
|
|
|
|
client = new HttpClient();
|
|
|
|
|
client.DefaultRequestHeaders.Add("User-Agent", Consts.UserAgent);
|
|
|
|
|
}
|
2019-07-23 04:17:52 +00:00
|
|
|
|
|
2019-08-02 22:31:13 +00:00
|
|
|
|
if (headers != null) {
|
|
|
|
|
foreach (var header in headers)
|
|
|
|
|
{
|
|
|
|
|
var idx = header.IndexOf(':');
|
|
|
|
|
var k = header.Substring(0, idx);
|
|
|
|
|
var v = header.Substring(idx + 1);
|
|
|
|
|
client.DefaultRequestHeaders.Add(k, v);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-23 04:17:52 +00:00
|
|
|
|
|
2019-08-02 22:31:13 +00:00
|
|
|
|
long total_read = 0;
|
|
|
|
|
int buffer_size = 1024 * 32;
|
2019-07-24 04:31:08 +00:00
|
|
|
|
|
2019-08-02 22:31:13 +00:00
|
|
|
|
var response = client.GetSync(url);
|
|
|
|
|
var stream = response.Content.ReadAsStreamAsync();
|
2019-08-04 22:08:03 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
stream.Wait();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
if (stream.IsFaulted)
|
|
|
|
|
{
|
|
|
|
|
Info($"While downloading {url} - {Utils.ExceptionToString(stream.Exception)}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-07-23 04:17:52 +00:00
|
|
|
|
|
2019-08-02 22:31:13 +00:00
|
|
|
|
string header_var = "1";
|
|
|
|
|
if (response.Content.Headers.Contains("Content-Length"))
|
|
|
|
|
header_var = response.Content.Headers.GetValues("Content-Length").FirstOrDefault();
|
2019-07-23 04:17:52 +00:00
|
|
|
|
|
2019-08-02 22:31:13 +00:00
|
|
|
|
long content_size = header_var != null ? long.Parse(header_var) : 1;
|
2019-07-23 04:17:52 +00:00
|
|
|
|
|
2019-08-02 22:31:13 +00:00
|
|
|
|
var output_path = Path.Combine(DownloadFolder, archive.Name);
|
2019-08-04 22:08:03 +00:00
|
|
|
|
;
|
2019-08-02 22:31:13 +00:00
|
|
|
|
|
|
|
|
|
using (var webs = stream.Result)
|
|
|
|
|
using (var fs = File.OpenWrite(output_path))
|
2019-07-23 04:17:52 +00:00
|
|
|
|
{
|
2019-08-02 22:31:13 +00:00
|
|
|
|
var buffer = new byte[buffer_size];
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
var read = webs.Read(buffer, 0, buffer_size);
|
|
|
|
|
if (read == 0) break;
|
|
|
|
|
Status((int)(total_read * 100 / content_size), "Downloading {0}", archive.Name);
|
2019-07-23 04:17:52 +00:00
|
|
|
|
|
2019-08-02 22:31:13 +00:00
|
|
|
|
fs.Write(buffer, 0, read);
|
|
|
|
|
total_read += read;
|
2019-07-23 04:17:52 +00:00
|
|
|
|
|
2019-08-02 22:31:13 +00:00
|
|
|
|
}
|
2019-07-23 04:17:52 +00:00
|
|
|
|
}
|
2019-08-02 22:31:13 +00:00
|
|
|
|
Status("Hashing {0}", archive.Name);
|
|
|
|
|
HashArchive(output_path);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Info($"{archive.Name} - Error downloading from: {url}");
|
2019-07-23 04:17:52 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private object GetNexusAPIKey()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void HashArchives()
|
|
|
|
|
{
|
|
|
|
|
HashedArchives = Directory.EnumerateFiles(DownloadFolder)
|
|
|
|
|
.Where(e => Consts.SupportedArchives.Contains(Path.GetExtension(e)))
|
|
|
|
|
.PMap(e => (HashArchive(e), e))
|
|
|
|
|
.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 {0}", Path.GetFileName(e));
|
|
|
|
|
File.WriteAllText(cache, Utils.FileSHA256(e));
|
|
|
|
|
return HashArchive(e);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-31 03:59:19 +00:00
|
|
|
|
public static string CheckForModPack()
|
|
|
|
|
{
|
|
|
|
|
using (var s = File.OpenRead(Assembly.GetExecutingAssembly().Location))
|
|
|
|
|
{
|
|
|
|
|
var magic_bytes = Encoding.ASCII.GetBytes(Consts.ModPackMagic);
|
|
|
|
|
s.Position = s.Length - magic_bytes.Length;
|
|
|
|
|
using (var br = new BinaryReader(s))
|
|
|
|
|
{
|
|
|
|
|
var bytes = br.ReadBytes(magic_bytes.Length);
|
|
|
|
|
var magic = Encoding.ASCII.GetString(bytes);
|
|
|
|
|
if (magic != Consts.ModPackMagic)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.Position = s.Length - magic_bytes.Length - 8;
|
|
|
|
|
var start_pos = br.ReadInt64();
|
|
|
|
|
s.Position = start_pos;
|
|
|
|
|
long length = br.ReadInt64();
|
|
|
|
|
|
|
|
|
|
return br.ReadBytes((int)length).BZip2String();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-23 04:17:52 +00:00
|
|
|
|
}
|
|
|
|
|
}
|