Remove Beth.NET and YT downloader, no one used them and they only sortof worked

This commit is contained in:
Timothy Baldridge 2020-09-09 19:49:22 -06:00
parent 4da8fbb9f7
commit f129a6bd22
18 changed files with 0 additions and 1021 deletions

View File

@ -39,8 +39,6 @@ namespace Wabbajack.Lib.Downloaders
typeof(DeadlyStreamDownloader.State),
typeof(TESAllianceDownloader.State),
typeof(TESAllDownloader.State),
typeof(BethesdaNetDownloader.State),
typeof(YouTubeDownloader.State),
typeof(YandexDownloader.State),
typeof(WabbajackCDNDownloader.State)
};

View File

@ -1,123 +0,0 @@
"""
Simple process that uses Fria to inspect the HTTP headers, and sent data during a Skyrim/Fallout login to Bethesda.NET. That data is output to the screen and can later be used to
allow Wabbajack to log into Bethesda.NET.
"""
import frida
import sys
from subprocess import Popen, PIPE
import psutil, time, json
from pathlib import Path
known_headers = {}
shutdown = False
def on_message(message, data):
msg_type, msg_data = message["payload"]
if msg_type == "header":
header, value = msg_data.split(": ")
if header not in known_headers:
known_headers[header] = value
if msg_type == "data":
try:
data = json.loads(msg_data)
if "scheme" in data and "language" in data and "payload" in data:
shutdown_and_print(data)
except:
return
def main(target_process):
session = frida.attach(target_process)
script = session.create_script("""
// Find base address of current imported jvm.dll by main process fledge.exe
var reqHeaders = Module.getExportByName('winhttp.dll', 'WinHttpAddRequestHeaders');
Interceptor.attach(reqHeaders, { // Intercept calls to our SetAesDecrypt function
// When function is called, print out its parameters
onEnter: function (args) {
send(['header', args[1].readUtf16String(args[2].toInt32())]);
},
// When function is finished
onLeave: function (retval) {
}
});
var reqHeaders = Module.getExportByName('winhttp.dll', 'WinHttpWriteData');
console.log("WinHttpAddRequestHeaders: " + reqHeaders);
Interceptor.attach(reqHeaders, { // Intercept calls to our SetAesDecrypt function
// When function is called, print out its parameters
onEnter: function (args) {
send(['data', args[1].readUtf8String(args[2].toInt32())]);
},
// When function is finished
onLeave: function (retval) {
}
});
""")
script.on('message', on_message)
script.load()
while not shutdown and psutil.pid_exists(target_process):
time.sleep(0.5)
session.detach()
sys.exit(1)
def wait_for_game(started, name):
no_exe = 0
parent_path = Path(started).parent
while True:
found = False
time.sleep(1)
for proc in psutil.process_iter():
try:
if Path(proc.exe()).parent == parent_path:
no_exe = 0
found = True
except:
pass
if proc.name() == name:
return proc.pid
if not found:
print("Not Found " + str(no_exe))
no_exe += 1
if no_exe == 3:
sys.exit(1)
def shutdown_and_print(data):
global shutdown
output = {"body": json.dumps(data), "headers": known_headers}
print(json.dumps(output))
for proc in psutil.process_iter():
if proc.pid == pid:
proc.kill()
break
shutdown = True
if __name__ == '__main__':
start = """C:\Steam\steamapps\common\Skyrim Special Edition\SkyrimSE.exe"""
wait_for = "SkyrimSE.exe"
if len(sys.argv) == 3:
start = sys.argv[1]
wait_for = sys.argv[2]
target_process = Popen([start])
pid = wait_for_game(start, wait_for)
main(pid)

View File

@ -1,386 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reactive;
using System.Reactive.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Windows.Input;
using Newtonsoft.Json.Linq;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using ReactiveUI;
using Wabbajack.Common;
using Wabbajack.Common.Serialization.Json;
using Wabbajack.Lib.Validation;
using File = Alphaleonis.Win32.Filesystem.File;
using Game = Wabbajack.Common.Game;
using Path = Alphaleonis.Win32.Filesystem.Path;
namespace Wabbajack.Lib.Downloaders
{
public class BethesdaNetDownloader : IUrlDownloader, INeedsLogin
{
private bool _isPrepared;
public const string DataName = "bethesda-net-data";
public ReactiveCommand<Unit, Unit> TriggerLogin { get; }
public ReactiveCommand<Unit, Unit> ClearLogin { get; }
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable(DataName);
public string SiteName => "Bethesda.NET";
public IObservable<string> MetaInfo => Observable.Return(""); //"Wabbajack will start the game, then exit once you enter the Mods page";
public Uri SiteURL => new Uri("https://bethesda.net");
public Uri? IconUri => new Uri("https://bethesda.net/favicon.ico");
public BethesdaNetDownloader()
{
TriggerLogin = ReactiveCommand.CreateFromTask(() => Utils.CatchAndLog(RequestLoginAndCache), IsLoggedIn.Select(b => !b).ObserveOn(RxApp.MainThreadScheduler));
ClearLogin = ReactiveCommand.CreateFromTask(() => Utils.CatchAndLog(async () => await Utils.DeleteEncryptedJson(DataName)), IsLoggedIn.ObserveOn(RxApp.MainThreadScheduler));
}
private static async Task RequestLoginAndCache()
{
await Utils.Log(new RequestBethesdaNetLogin()).Task;
}
public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{
var url = (Uri)DownloaderUtils.GetDirectURL(archiveINI);
return StateFromUrl(url);
}
internal static AbstractDownloadState? StateFromUrl(Uri url)
{
if (url != null && url.Host == "bethesda.net" && url.AbsolutePath.StartsWith("/en/mods/"))
{
var split = url.AbsolutePath.Split('/');
var game = split[3];
var modId = split[5];
return new State(gameName: game, contentId: modId);
}
return null;
}
public async Task Prepare()
{
if (_isPrepared) return;
if (Utils.HaveEncryptedJson(DataName))
{
_isPrepared = true;
return;
}
await Utils.Log(new RequestBethesdaNetLogin()).Task;
_isPrepared = true;
}
public static async Task<BethesdaNetData?> Login(Game game)
{
var metadata = game.MetaData();
if (metadata.MainExecutable == null) throw new NotImplementedException();
var gamePath = metadata.GameLocation().Combine(metadata.MainExecutable);
var info = new ProcessStartInfo
{
FileName = @"Downloaders\BethesdaNet\bethnetlogin.exe",
Arguments = $"\"{gamePath}\" {metadata.MainExecutable}",
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
var process = Process.Start(info);
ChildProcessTracker.AddProcess(process);
string last_line = "";
while (true)
{
var line = await process.StandardOutput.ReadLineAsync();
if (line == null) break;
last_line = line;
}
try
{
var result = last_line.FromJsonString<BethesdaNetData>();
await result.ToEcryptedJson(DataName);
return result;
}
catch (Exception ex)
{
Utils.Error(ex, "Could not save Bethesda.NET login info");
return null;
}
}
public AbstractDownloadState? GetDownloaderState(string url)
{
return StateFromUrl(new Uri(url));
}
[JsonName("BethesdaNetDownloader")]
public class State : AbstractDownloadState
{
public string GameName { get; }
public string ContentId { get; }
[JsonIgnore]
public override object[] PrimaryKey => new object[] { GameName, ContentId };
public State(string gameName, string contentId)
{
GameName = gameName;
ContentId = contentId;
}
public override bool IsWhitelisted(ServerWhitelist whitelist)
{
return true;
}
public override async Task<bool> Download(Archive a, AbsolutePath destination)
{
var (client, info, collected) = await ResolveDownloadInfo();
await using var tf = new TempFile();
await using var file = tf.File.Create();
var max_chunks = info.depot_list[0].file_list[0].chunk_count;
foreach (var chunk in info.depot_list[0].file_list[0].chunk_list.OrderBy(c => c.index))
{
Utils.Status($"Downloading {a.Name}", Percent.FactoryPutInRange(chunk.index, max_chunks));
using var got = await client.GetAsync(
$"https://content.cdp.bethesda.net/{collected.CDPProductId}/{collected.CDPPropertiesId}/{chunk.sha}");
var data = await got.Content.ReadAsByteArrayAsync();
if (collected.AESKey != null)
AESCTRDecrypt(collected.AESKey, collected.AESIV!, data);
if (chunk.uncompressed_size == chunk.chunk_size)
await file.WriteAsync(data, 0, data.Length);
else
{
using (var ms = new MemoryStream(data))
using (var zlibStream =
new ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream(ms))
await zlibStream.CopyToAsync(file);
}
}
file.Close();
await ConvertCKMToZip((AbsolutePath)file.Name, destination);
return true;
}
private const uint CKM_Magic = 0x52415442; // BTAR
private async Task ConvertCKMToZip(AbsolutePath src, AbsolutePath dest)
{
using var reader = new BinaryReader(await src.OpenRead());
var magic = reader.ReadUInt32();
if (magic != CKM_Magic)
throw new InvalidDataException("Invalid magic format in CKM parsing");
ushort majorVersion = reader.ReadUInt16();
ushort minorVersion = reader.ReadUInt16();
if (majorVersion != 1)
throw new InvalidDataException("Archive major version is unknown. Should be 1.");
if (minorVersion < 2 || minorVersion > 4)
throw new InvalidDataException("Archive minor version is unknown. Should be 2, 3, or 4.");
await using var fos = await dest.Create();
using var archive = new ZipArchive(fos, ZipArchiveMode.Create);
while (reader.PeekChar() != -1)
{
ushort nameLength = reader.ReadUInt16();
string name = Encoding.UTF8.GetString(reader.ReadBytes(nameLength));
ulong dataLength = reader.ReadUInt64();
if (dataLength > int.MaxValue)
throw new Exception();
var entry = archive.CreateEntry(name, CompressionLevel.NoCompression);
await using var output = entry.Open();
await reader.BaseStream.CopyToLimitAsync(output, (long)dataLength);
}
}
public override async Task<bool> Verify(Archive archive)
{
await ResolveDownloadInfo();
return true;
}
private async Task<(Wabbajack.Lib.Http.Client, CDPTree, CollectedBNetInfo)> ResolveDownloadInfo()
{
var info = new CollectedBNetInfo();
var login_info = await Utils.FromEncryptedJson<BethesdaNetData>(DataName);
var client = new Wabbajack.Lib.Http.Client();
client.Headers.Add(("User-Agent", "bnet"));
foreach (var header in login_info.headers.Where(h => h.Key.ToLower().StartsWith("x-")))
client.Headers.Add((header.Key, header.Value));
var posted = await client.PostAsync("https://api.bethesda.net/beam/accounts/external_login",
new StringContent(login_info.body, Encoding.UTF8, "application/json"));
info.AccessToken = (await posted.Content.ReadAsStringAsync()).FromJsonString<BeamLoginResponse>().access_token;
client.Headers.Add(("x-cdp-app", "UGC SDK"));
client.Headers.Add(("x-cdp-app-ver", "0.9.11314/debug"));
client.Headers.Add(("x-cdp-lib-ver", "0.9.11314/debug"));
client.Headers.Add(("x-cdp-platform", "Win/32"));
posted = await client.PostAsync("https://api.bethesda.net/cdp-user/auth",
new StringContent("{\"access_token\": \"" + info.AccessToken + "\"}", Encoding.UTF8,
"application/json"));
info.CDPToken = (await posted.Content.ReadAsStringAsync()).FromJsonString<CDPLoginResponse>().token;
client.Headers.Add(("X-Access-Token", info.AccessToken));
var got = await client.GetAsync($"https://api.bethesda.net/mods/ugc-workshop/content/get?content_id={ContentId}");
JObject data = JObject.Parse(await got.Content.ReadAsStringAsync());
var content = data["platform"]!["response"]!["content"]!;
info.CDPBranchId = (int)content["cdp_branch_id"]!;
info.CDPProductId = (int)content["cdp_product_id"]!;
client.Headers.Add(("Authorization", $"Token {info.CDPToken}"));
client.Headers.Add(("Accept", "application/json"));
got.Dispose();
got = await client.GetAsync(
$"https://api.bethesda.net/cdp-user/projects/{info.CDPProductId}/branches/{info.CDPBranchId}/tree/.json");
var tree = (await got.Content.ReadAsStringAsync()).FromJsonString<CDPTree>();
got.Dispose();
got = await client.PostAsync($"https://api.bethesda.net/mods/ugc-content/add-subscription", new StringContent($"{{\"content_id\": \"{ContentId}\"}}", Encoding.UTF8, "application/json"));
got.Dispose();
got = await client.GetAsync(
$"https://api.bethesda.net/cdp-user/projects/{info.CDPProductId}/branches/{info.CDPBranchId}/depots/.json");
var props_obj = JObject.Parse(await got.Content.ReadAsStringAsync()).Properties().First();
info.CDPPropertiesId = (int)props_obj.Value["properties_id"]!;
info.AESKey = props_obj.Value["ex_info_A"].Select(e => (byte)e).ToArray();
info.AESIV = props_obj.Value["ex_info_B"].Select(e => (byte)e).Take(16).ToArray();
return (client, tree, info);
}
static int AESCTRDecrypt(byte[] Key, byte[] IV, byte[] Data)
{
IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CTR/NoPadding");
cipher.Init(false, new ParametersWithIV(ParameterUtilities.CreateKeyParameter("AES", Key), IV));
return cipher.DoFinal(Data, Data, 0);
}
public override IDownloader GetDownloader()
{
return DownloadDispatcher.GetInstance<BethesdaNetDownloader>();
}
public override string GetManifestURL(Archive a)
{
return $"https://bethesda.net/en/mods/{GameName}/mod-detail/{ContentId}";
}
public override string[] GetMetaIni()
{
return new[] { "[General]", $"directURL=https://bethesda.net/en/mods/{GameName}/mod-detail/{ContentId}" };
}
private class BeamLoginResponse
{
public string access_token { get; set; } = string.Empty;
}
private class CDPLoginResponse
{
public string token { get; set; } = string.Empty;
}
private class CollectedBNetInfo
{
public byte[] AESKey { get; set; } = null!;
public byte[] AESIV { get; set; } = null!;
public string AccessToken { get; set; } = string.Empty;
public string CDPToken { get; set; } = string.Empty;
public int CDPBranchId { get; set; }
public int CDPProductId { get; set; }
public int CDPPropertiesId { get; set; }
}
public class CDPTree
{
public List<Depot> depot_list { get; set; } = null!;
public class Depot
{
public List<CDPFile> file_list { get; set; } = null!;
public class CDPFile
{
public int chunk_count { get; set; }
public List<Chunk> chunk_list { get; set; } = null!;
public string? name { get; set; }
public class Chunk
{
public int chunk_size { get; set; }
public int index { get; set; }
public string sha { get; set; } = string.Empty;
public int uncompressed_size { get; set; }
}
}
}
}
}
}
internal class DownloadInfo
{
}
public class RequestBethesdaNetLogin : AUserIntervention
{
public override string ShortDescription => "Logging into Bethesda.NET";
public override string ExtendedDescription { get; } = string.Empty;
private readonly TaskCompletionSource<BethesdaNetData> _source = new TaskCompletionSource<BethesdaNetData>();
public Task<BethesdaNetData> Task => _source.Task;
public void Resume(BethesdaNetData data)
{
Handled = true;
_source.SetResult(data);
}
public override void Cancel()
{
Handled = true;
_source.SetCanceled();
}
}
[JsonName("BethesdaNetData")]
public class BethesdaNetData
{
public string body { get; set; } = string.Empty;
public Dictionary<string, string> headers = new Dictionary<string, string>();
}
}

View File

@ -24,10 +24,8 @@ namespace Wabbajack.Lib.Downloaders
new LoversLabDownloader(),
new VectorPlexusDownloader(),
new DeadlyStreamDownloader(),
new BethesdaNetDownloader(),
new TESAllianceDownloader(),
new TESAllDownloader(),
new YouTubeDownloader(),
new WabbajackCDNDownloader(),
new YandexDownloader(),
new HTTPDownloader(),
@ -36,8 +34,6 @@ namespace Wabbajack.Lib.Downloaders
public static readonly List<IUrlInferencer> Inferencers = new List<IUrlInferencer>()
{
new BethesdaNetInferencer(),
new YoutubeInferencer(),
new WabbajackCDNInfluencer()
};

View File

@ -1,13 +0,0 @@
using System;
using System.Threading.Tasks;
namespace Wabbajack.Lib.Downloaders.UrlDownloaders
{
public class BethesdaNetInferencer : IUrlInferencer
{
public async Task<AbstractDownloadState?> Infer(Uri uri)
{
return BethesdaNetDownloader.StateFromUrl(uri);
}
}
}

View File

@ -1,95 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DynamicData;
using Org.BouncyCastle.Utilities.Collections;
using Wabbajack.Common;
using YoutubeExplode;
namespace Wabbajack.Lib.Downloaders.UrlDownloaders
{
public class YoutubeInferencer : IUrlInferencer
{
public async Task<AbstractDownloadState?> Infer(Uri uri)
{
var state = YouTubeDownloader.UriToState(uri) as YouTubeDownloader.State;
if (state == null) return null;
var client = new YoutubeClient(Wabbajack.Lib.Http.ClientFactory.Client);
var video = await client.Videos.GetAsync(state.Key);
var desc = video.Description;
var replaceChars = new HashSet<char>() {'_', '(', ')', '-'};
var lines = desc.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(line => line.Trim())
.Select(line =>
{
var segments = replaceChars.Aggregate(line, (acc, c) => acc.Replace(c, ' '))
.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (segments.Length == 0) return (TimeSpan.Zero, string.Empty);
foreach (var segment in segments)
{
if (TryParseEx(segment, out var si))
{
return (si, string.Join(" ", segments.Where(s => !s.Contains(":"))));
}
}
return (TimeSpan.Zero, string.Empty);
})
.Where(t => t.Item2 != string.Empty)
.ToList();
var tracks = lines.Select((line, idx) => new YouTubeDownloader.State.Track
{
Name = Sanitize(line.Item2),
Start = line.Item1,
End = idx < lines.Count - 1 ? lines[idx + 1].Item1 : video.Duration,
Format = YouTubeDownloader.State.Track.FormatEnum.XWM
}).ToList();
foreach (var track in tracks)
{
Utils.Log($"Inferred Track {track.Name} {track.Format} {track.Start}-{track.End}");
}
state.Tracks = tracks;
return state;
}
private string Sanitize(string input)
{
return input.Replace(":", "_").Replace("'", "").Replace("\"", "");
}
private static bool TryParseEx(string s, out TimeSpan span)
{
var ints = s.Split(':').Select(segment => int.TryParse(segment, out int v) ? v : -1).ToArray();
if (ints.Any(i => i == -1))
{
span = TimeSpan.Zero;
return false;
}
switch (ints.Length)
{
case 2:
span = new TimeSpan(0, ints[0], ints[1]);
break;
case 3:
span = new TimeSpan(ints[0], ints[1], ints[2]);
break;
default:
span = TimeSpan.Zero;
return false;
}
return true;
}
}
}

View File

@ -1,250 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Compression;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Newtonsoft.Json;
using Wabbajack.Common;
using Wabbajack.Common.Serialization.Json;
using Wabbajack.Lib.Validation;
using YoutubeExplode;
using YoutubeExplode.Exceptions;
using YoutubeExplode.Videos.Streams;
using File = Alphaleonis.Win32.Filesystem.File;
using Path = Alphaleonis.Win32.Filesystem.Path;
namespace Wabbajack.Lib.Downloaders
{
public class YouTubeDownloader : IDownloader
{
public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{
var directURL = (Uri)DownloaderUtils.GetDirectURL(archiveINI);
var state = UriToState(directURL) as State;
if (state == null) return state;
var idx = 0;
while (true)
{
var section = archiveINI[$"track/{idx}"];
if (section.name == null) break;
var track = new State.Track();
track.Name = section.name;
track.Start = TimeSpan.Parse(section.start);
track.End = TimeSpan.Parse(section.end);
track.Format = Enum.Parse<State.Track.FormatEnum>(section.format);
state.Tracks.Add(track);
idx += 1;
}
return state;
}
internal static AbstractDownloadState? UriToState(Uri directURL)
{
if (directURL == null || !directURL.Host.EndsWith("youtube.com"))
{
return null;
}
var key = HttpUtility.ParseQueryString(directURL.Query)["v"];
return key != null ? new State(key) : null;
}
public async Task Prepare()
{
}
[JsonName("YouTubeDownloader")]
public class State : AbstractDownloadState
{
public string Key { get; }
public List<Track> Tracks { get; set; } = new List<Track>();
[JsonIgnore]
public override object[] PrimaryKey => new object[] {Key};
public State(string key)
{
Key = key;
}
[JsonName("YouTubeTrack")]
public class Track
{
public enum FormatEnum
{
XWM,
WAV
}
public FormatEnum Format { get; set; }
public string Name { get; set; } = string.Empty;
public TimeSpan Start { get; set; }
public TimeSpan End { get; set; }
}
public override bool IsWhitelisted(ServerWhitelist whitelist)
{
return true;
}
public override async Task<bool> Download(Archive a, AbsolutePath destination)
{
try
{
using var queue = new WorkQueue();
await using var folder = await TempFolder.Create();
folder.Dir.Combine("tracks").CreateDirectory();
var client = new YoutubeClient(Wabbajack.Lib.Http.ClientFactory.Client);
var meta = await client.Videos.GetAsync(Key);
var video = await client.Videos.Streams.GetManifestAsync(Key);
var stream = video.Streams.OfType<AudioOnlyStreamInfo>().Where(f => f.AudioCodec.StartsWith("mp4a")).OrderByDescending(a => a.Bitrate)
.ToArray().First();
var initialDownload = folder.Dir.Combine("initial_download");
var trackFolder = folder.Dir.Combine("tracks");
await using (var fs = await initialDownload.Create())
{
await client.Videos.Streams.CopyToAsync(stream, fs, new Progress($"Downloading {a.Name}"),
CancellationToken.None);
}
await Tracks.PMap(queue, async track =>
{
Utils.Status($"Extracting track {track.Name}");
await ExtractTrack(initialDownload, trackFolder, track);
});
await using var dest = await destination.Create();
using var ar = new ZipArchive(dest, ZipArchiveMode.Create);
foreach (var track in trackFolder.EnumerateFiles().OrderBy(e => e))
{
Utils.Status($"Adding {track.FileName} to archive");
var entry = ar.CreateEntry(Path.Combine("Data", "Music", (string)track.RelativeTo(trackFolder)), CompressionLevel.NoCompression);
entry.LastWriteTime = meta.UploadDate;
await using var es = entry.Open();
await using var ins = await track.OpenRead();
await ins.CopyToAsync(es);
}
return true;
}
catch (VideoUnavailableException)
{
return false;
}
}
private AbsolutePath FFMpegPath => "Downloaders/Converters/ffmpeg.exe".RelativeTo(AbsolutePath.EntryPoint);
private AbsolutePath xWMAEncodePath = "Downloaders/Converters/xWMAEncode.exe".RelativeTo(AbsolutePath.EntryPoint);
private Extension WAVExtension = new Extension(".wav");
private Extension XWMExtension = new Extension(".xwm");
private async Task ExtractTrack(AbsolutePath source, AbsolutePath destFolder, Track track)
{
Utils.Log($"Extracting {track.Name}");
var wavFile = track.Name.RelativeTo(destFolder).WithExtension(WAVExtension);
var process = new ProcessHelper
{
Path = FFMpegPath,
Arguments = new object[] {"-hide_banner", "-loglevel", "panic", "-threads", 1, "-i", source, "-ss", track.Start, "-t", track.End - track.Start, wavFile},
ThrowOnNonZeroExitCode = true
};
var ffmpegLogs = process.Output.Where(arg => arg.Type == ProcessHelper.StreamType.Output)
.ForEachAsync(val =>
{
Utils.Status($"Extracting {track.Name} - {val.Line}");
});
await process.Start();
ffmpegLogs.Dispose();
if (track.Format == Track.FormatEnum.WAV) return;
process = new ProcessHelper()
{
Path = xWMAEncodePath,
Arguments = new object[] {"-b", 192000, wavFile, wavFile.ReplaceExtension(XWMExtension)},
ThrowOnNonZeroExitCode = true
};
var xwmLogs = process.Output.Where(arg => arg.Type == ProcessHelper.StreamType.Output)
.ForEachAsync(val =>
{
Utils.Log($"Encoding {track.Name} - {val.Line}");
});
await process.Start();
xwmLogs.Dispose();
await wavFile.DeleteAsync();
}
private class Progress : IProgress<double>
{
private string _prefix;
public Progress(string prefix)
{
_prefix = prefix;
}
public void Report(double value)
{
Utils.Status(_prefix, Percent.FactoryPutInRange(value));
}
}
public override async Task<bool> Verify(Archive archive)
{
try
{
var client = new YoutubeClient(Wabbajack.Lib.Http.ClientFactory.Client);
var video = await client.Videos.GetAsync(Key);
return true;
}
catch (VideoUnavailableException)
{
return false;
}
}
public override IDownloader GetDownloader()
{
return DownloadDispatcher.GetInstance<YouTubeDownloader>();
}
public override string GetManifestURL(Archive a)
{
return $"https://www.youtube.com/watch?v={Key}";
}
public override string[] GetMetaIni()
{
IEnumerable<string> start = new List<string> {"[General]", $"directURL=https://www.youtube.com/watch?v={Key}"};
start = start.Concat(Tracks.SelectMany((track, idx) =>
{
return new[]
{
$"\n[track/{idx}]", $"name={track.Name}", $"start={track.Start}", $"end={track.End}",
$"format={track.Format}"
};
}));
return start.ToArray();
}
}
}
}

View File

@ -102,8 +102,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Downloaders\BethesdaNet" />
<Folder Include="Downloaders\Converters" />
</ItemGroup>
</Project>

View File

@ -485,63 +485,6 @@ namespace Wabbajack.Test
Consts.TestMode = true;
}
/* Disabled, will be removed in the future
[Fact]
public async Task BethesdaNetDownload()
{
var downloader = DownloadDispatcher.GetInstance<BethesdaNetDownloader>();
Assert.True(await downloader.IsLoggedIn.FirstAsync());
var ini = $@"[General]
directURL=https://bethesda.net/en/mods/skyrim/mod-detail/4145641";
await using var filename = new TempFile();
var state = (AbstractDownloadState)await DownloadDispatcher.ResolveArchive(ini.LoadIniString());
Assert.NotNull(state);
var converted = state.ViaJSON();
Assert.True(await converted.Verify(new Archive(state: null!) { Name = "mod.ckm"}));
Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List<string>() }));
await converted.Download(new Archive(state: null!) { Name = "mod.zip" }, filename.Path);
await using var fs = await filename.Path.OpenRead();
using var archive = new ZipArchive(fs);
var entries = archive.Entries.Select(e => e.FullName).ToList();
Assert.Equal(entries, new List<string> {@"Data\TestCK.esp", @"Data\TestCK.ini"});
}*/
/*
[Fact]
public async Task YoutubeDownloader()
{
var infered_ini = await DownloadDispatcher.Infer(new Uri("https://www.youtube.com/watch?v=4ceowgHn8BE"));
Assert.IsAssignableFrom<YouTubeDownloader.State>(infered_ini);
Assert.Equal(15, ((YouTubeDownloader.State)infered_ini).Tracks.Count);
var ini = string.Join("\n", infered_ini.GetMetaIni());
var state = (YouTubeDownloader.State)await DownloadDispatcher.ResolveArchive(ini.LoadIniString());
Assert.Equal(15, state.Tracks.Count);
Assert.NotNull(state);
var converted = RoundTripState(state);
Assert.True(await converted.Verify(new Archive(state: null!) { Name = "yt_test.zip"}));
Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List<string>() }));
await using var tempFile = new TempFile();
await converted.Download(new Archive(state: null!) { Name = "yt_test.zip"}, tempFile.Path);
Assert.Equal(Hash.FromBase64("pD7UoVNY4o8="), await tempFile.Path.FileHashAsync());
}
*/
/// <summary>
/// Tests that files from different sources don't overwrite eachother when downloaded by AInstaller
/// </summary>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 714 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 642 KiB

View File

@ -1,64 +0,0 @@
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using CefSharp;
using CefSharp.Wpf;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.Common;
using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.WebAutomation;
namespace Wabbajack
{
public class BethesdaNetLoginVM : ViewModel, IBackNavigatingVM
{
[Reactive]
public string Instructions { get; set; }
[Reactive]
public ViewModel NavigateBackTarget { get; set; }
[Reactive]
public ReactiveCommand<Unit, Unit> BackCommand { get; set; }
public ReactiveCommand<Unit, Unit> LoginViaSkyrimSE { get; }
public ReactiveCommand<Unit, Unit> LoginViaFallout4 { get; }
private Subject<bool> LoggingIn = new Subject<bool>();
private BethesdaNetLoginVM()
{
Instructions = "Login to Bethesda.NET in-game...";
LoginViaSkyrimSE = ReactiveCommand.CreateFromTask(async () =>
{
LoggingIn.OnNext(true);
Instructions = "Starting Skyrim Special Edition...";
await BethesdaNetDownloader.Login(Game.SkyrimSpecialEdition);
LoggingIn.OnNext(false);
await BackCommand.Execute();
}, Game.SkyrimSpecialEdition.MetaData().IsInstalled
? LoggingIn.Select(e => !e).StartWith(true)
: Observable.Return(false));
LoginViaFallout4 = ReactiveCommand.CreateFromTask(async () =>
{
LoggingIn.OnNext(true);
Instructions = "Starting Fallout 4...";
await BethesdaNetDownloader.Login(Game.Fallout4);
LoggingIn.OnNext(false);
await BackCommand.Execute();
}, Game.Fallout4.MetaData().IsInstalled
? LoggingIn.Select(e => !e).StartWith(true)
: Observable.Return(false));
}
public static async Task<BethesdaNetLoginVM> GetNew()
{
// Make sure libraries are extracted first
return new BethesdaNetLoginVM();
}
}
}

View File

@ -57,21 +57,6 @@ namespace Wabbajack
MainWindow.NavigateTo(oldPane);
}
private async Task WrapBethesdaNetLogin(IUserIntervention intervention)
{
CancellationTokenSource cancel = new CancellationTokenSource();
var oldPane = MainWindow.ActivePane;
var vm = await BethesdaNetLoginVM.GetNew();
MainWindow.NavigateTo(vm);
vm.BackCommand = ReactiveCommand.Create(() =>
{
cancel.Cancel();
MainWindow.NavigateTo(oldPane);
intervention.Cancel();
});
}
public async Task Handle(IUserIntervention msg)
{
switch (msg)
@ -90,9 +75,6 @@ namespace Wabbajack
case ManuallyDownloadFile c:
await WrapBrowserJob(msg, (vm, cancel) => HandleManualDownload(vm, cancel, c));
break;
case RequestBethesdaNetLogin c:
await WrapBethesdaNetLogin(c);
break;
case AbstractNeedsLoginDownloader.RequestSiteLogin c:
await WrapBrowserJob(msg, async (vm, cancel) =>
{

View File

@ -37,9 +37,6 @@
<DataTemplate DataType="{x:Type local:WebBrowserVM}">
<local:WebBrowserView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:BethesdaNetLoginVM}">
<local:BethesdaNetLoginView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:SettingsVM}">
<local:SettingsView ViewModel="{Binding}" />
</DataTemplate>

View File

@ -87,8 +87,6 @@
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\GameGridIcons\Fallout4.png" />
<Resource Include="Resources\GameGridIcons\SkyrimSpecialEdition.png" />
<Resource Include="Resources\middle_mouse_button.png" />
<Resource Include="Resources\MO2Button.png" />
<Resource Include="Resources\VortexButton.png" />