Merge pull request #1869 from wabbajack-tools/beth-net-redux

Beth net redux
This commit is contained in:
Timothy Baldridge 2022-02-12 06:59:42 -07:00 committed by GitHub
commit 97c14f6a7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 2402 additions and 1259 deletions

View File

@ -76,6 +76,7 @@ public partial class App
services.AddTransient<NexusLogin>();
services.AddTransient<VectorPlexus>();
services.AddTransient<LoversLab>();
services.AddTransient<BethesdaNetLogin>();
services.AddSingleton<SystemParametersConstructor>();
services.AddSingleton<IStateContainer, StateContainer>();
return services;

View File

@ -0,0 +1,75 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Web.WebView2.Core;
using Wabbajack.Common;
using Wabbajack.Networking.BethesdaNet;
using Wabbajack.Services.OSIntegrated;
namespace Wabbajack.App.Blazor.Browser.ViewModels;
public class BethesdaNetLogin : BrowserTabViewModel
{
private readonly EncryptedJsonTokenProvider<Wabbajack.DTOs.Logins.BethesdaNetLoginState> _tokenProvider;
private readonly Client _client;
public BethesdaNetLogin(EncryptedJsonTokenProvider<Wabbajack.DTOs.Logins.BethesdaNetLoginState> tokenProvider, Wabbajack.Networking.BethesdaNet.Client client)
{
_tokenProvider = tokenProvider;
_client = client;
HeaderText = "Bethesda Net Login";
}
protected override async Task Run(CancellationToken token)
{
await WaitForReady();
Instructions = "Please log in to Bethesda.net";
string requestJson = "";
Browser.Browser.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
Browser.Browser.CoreWebView2.WebResourceRequested += (sender, args) =>
{
if (args.Request.Uri == "https://api.bethesda.net/dwemer/attunement/v1/authenticate" && args.Request.Method == "POST")
{
requestJson = args.Request.Content.ReadAllText();
args.Request.Content = new MemoryStream(Encoding.UTF8.GetBytes(requestJson));
}
};
await NavigateTo(new Uri("https://bethesda.net/en/dashboard"));
while (true)
{
var code = await GetCookies("bethesda.net", token);
if (code.Any(c => c.Name == "bnet-session")) break;
}
var data = JsonSerializer.Deserialize<LoginRequest>(requestJson);
var provider = new Wabbajack.DTOs.Logins.BethesdaNetLoginState()
{
Username = data.UserName,
Password = data.Password
};
await _tokenProvider.SetToken(provider);
await _client.Login(token);
await Task.Delay(10);
}
public class LoginRequest
{
[JsonPropertyName("username")]
public string UserName { get; set; }
[JsonPropertyName("password")]
public string Password { get; set; }
}
}

View File

@ -13,6 +13,7 @@
<button onclick="@LoginToNexus">Login To Nexus</button>
<button onclick="@LoginToVectorPlexus">Login To Vector Plexus</button>
<button onclick="@LoginToLoversLab">Login To Lovers Lab</button>
<button onclick="@LoginToBethesdaNet">Login To Bethesda Net</button>
</div>
</div>
@ -32,4 +33,8 @@
{
MessageBus.Current.SendMessage(new OpenBrowserTab(_serviceProvider.GetRequiredService<VectorPlexus>()));
}
public void LoginToBethesdaNet()
{
MessageBus.Current.SendMessage(new OpenBrowserTab(_serviceProvider.GetRequiredService<BethesdaNetLogin>()));
}
}

View File

@ -68,6 +68,8 @@ internal class Program
services.AddSingleton<IVerb, SteamAppDumpInfo>();
services.AddSingleton<IVerb, SteamDownloadFile>();
services.AddSingleton<IVerb, UploadToNexus>();
services.AddSingleton<IVerb, ListCreationClubContent>();
services.AddSingleton<IVerb, Extract>();
services.AddSingleton<IUserInterventionHandler, UserInterventionHandler>();
}).Build();

View File

@ -4,9 +4,11 @@ using System.CommandLine.Invocation;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Wabbajack.Common;
using Wabbajack.Downloaders;
using Wabbajack.DTOs;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
namespace Wabbajack.CLI.Verbs;
@ -42,7 +44,8 @@ public class DownloadUrl : IVerb
}
var archive = new Archive() {State = parsed, Name = output.FileName.ToString()};
await _dispatcher.Download(archive, output, CancellationToken.None);
var hash = await _dispatcher.Download(archive, output, CancellationToken.None); ;
Console.WriteLine($"Download complete: {output.Size().ToFileSizeString()} {hash} {hash.ToHex()} {(long)hash}");
return 0;
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Wabbajack.Common;
using Wabbajack.Downloaders;
using Wabbajack.DTOs;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
namespace Wabbajack.CLI.Verbs;
public class Extract : IVerb
{
private readonly ILogger<DownloadUrl> _logger;
private readonly FileExtractor.FileExtractor _extractor;
public Extract(ILogger<DownloadUrl> logger, FileExtractor.FileExtractor extractor)
{
_logger = logger;
_extractor = extractor;
}
public Command MakeCommand()
{
var command = new Command("extract");
command.Add(new Option<AbsolutePath>(new[] {"-i", "-input"}, "Input Archive"));
command.Add(new Option<AbsolutePath>(new[] {"-o", "-output"}, "Output folder"));
command.Description = "Extracts the contents of an archive into a folder";
command.Handler = CommandHandler.Create(Run);
return command;
}
private async Task<int> Run(AbsolutePath input, AbsolutePath output, CancellationToken token)
{
if (!output.DirectoryExists())
output.Parent.CreateDirectory();
await _extractor.ExtractAll(input, output, token, f =>
{
Console.WriteLine($" - {f}");
return true;
});
return 0;
}
}

View File

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Wabbajack.Common;
using Wabbajack.Downloaders.Bethesda;
using Wabbajack.DTOs;
using Wabbajack.DTOs.DownloadStates;
using Wabbajack.DTOs.Logins.BethesdaNet;
using Wabbajack.Networking.BethesdaNet;
using Wabbajack.Paths;
namespace Wabbajack.CLI.Verbs;
public class ListCreationClubContent : IVerb
{
private readonly ILogger<ListCreationClubContent> _logger;
private readonly Client _client;
private readonly BethesdaDownloader _downloader;
public ListCreationClubContent(ILogger<ListCreationClubContent> logger, Client wjClient, Wabbajack.Downloaders.Bethesda.BethesdaDownloader downloader)
{
_logger = logger;
_client = wjClient;
_downloader = downloader;
}
public Command MakeCommand()
{
var command = new Command("list-creation-club-content");
command.Description = "Lists all known creation club content";
command.Handler = CommandHandler.Create(Run);
return command;
}
public async Task<int> Run(CancellationToken token)
{
_logger.LogInformation("Getting list of content");
var skyrimContent = (await _client.ListContent(Game.SkyrimSpecialEdition, token))
.Select(f => (Game.SkyrimSpecialEdition, f));
var falloutContent = (await _client.ListContent(Game.Fallout4, token))
.Select(f => (Game.Fallout4, f));
foreach (var (game, content) in skyrimContent.Concat(falloutContent).OrderBy(f => f.f.Content.Name))
{
Console.WriteLine($"Game: {game}");
Console.WriteLine($"Name: {content.Content.Name}");
Console.WriteLine($"Download Size: {content.Content.DepotSize.ToFileSizeString()}");
Console.WriteLine($"Uri: {_downloader.UnParse(content.State)}");
Console.WriteLine("-----------------------------------");
}
return 0;
}
}

View File

@ -22,6 +22,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Wabbajack.Downloaders.Bethesda\Wabbajack.Downloaders.Bethesda.csproj" />
<ProjectReference Include="..\Wabbajack.Downloaders.Dispatcher\Wabbajack.Downloaders.Dispatcher.csproj" />
<ProjectReference Include="..\Wabbajack.Hashing.xxHash64\Wabbajack.Hashing.xxHash64.csproj" />
<ProjectReference Include="..\Wabbajack.Networking.Discord\Wabbajack.Networking.Discord.csproj" />

View File

@ -5,3 +5,4 @@ Relaxed RAR format,52 61 72 21,RAR
RAR5 or newer, 52 61 72 21 1A 07 01 00,RAR_NEW
RAR4 or older, 52 61 72 21 1A 07 00,RAR_OLD
DDS, 44 44 53 20,DDS
Bethesda Tar, 42 54 41 52, BTAR

View File

@ -1,7 +1,7 @@
namespace Wabbajack.Common.FileSignatures;

namespace Wabbajack.Common.FileSignatures {
public enum FileType
{
public enum FileType { BTAR,
_123,
_386,
_3G2,
@ -431,13 +431,13 @@ public enum FileType
XZ,
ZAP,
ZIP,
ZOO
ZOO,
}
public static class Definitions
{
public static (FileType, byte[])[] Signatures =
{
public static class Definitions {
public static (FileType, byte[])[] Signatures = {
// Morrowind BSA
(FileType.TES3, new byte[] {0x00, 0x01, 0x00, 0x00}),
@ -459,6 +459,9 @@ public static class Definitions
// DDS
(FileType.DDS, new byte[] {0x44, 0x44, 0x53, 0x20}),
// Bethesda Tar
(FileType. BTAR, new byte[] {0x42, 0x54, 0x41, 0x52}),
// JPEG2000 image files
(FileType.JP2, new byte[] {0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20}),
@ -481,12 +484,7 @@ public static class Definitions
(FileType.TBI, new byte[] {0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00}),
// Bitcoin Core wallet.dat file
(FileType.DAT,
new byte[]
{
0x00, 0x00, 0x00, 0x00, 0x62, 0x31, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}),
(FileType.DAT, new byte[] {0x00, 0x00, 0x00, 0x00, 0x62, 0x31, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
// MPEG-4 video_1
(FileType._3GP5, new byte[] {0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70}),
@ -534,11 +532,7 @@ public static class Definitions
(FileType.CUR, new byte[] {0x00, 0x00, 0x02, 0x00}),
// Compucon-Singer embroidery design file
(FileType.XXX,
new byte[]
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}),
(FileType.XXX, new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
// QuattroPro spreadsheet
(FileType.WB2, new byte[] {0x00, 0x00, 0x02, 0x00}),
@ -574,28 +568,13 @@ public static class Definitions
(FileType.TTF, new byte[] {0x00, 0x01, 0x00, 0x00, 0x00}),
// Microsoft Money file
(FileType.MNY,
new byte[]
{
0x00, 0x01, 0x00, 0x00, 0x4D, 0x53, 0x49, 0x53, 0x41, 0x4D, 0x20, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61,
0x73, 0x65
}),
(FileType.MNY, new byte[] {0x00, 0x01, 0x00, 0x00, 0x4D, 0x53, 0x49, 0x53, 0x41, 0x4D, 0x20, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65}),
// Microsoft Access 2007
(FileType.ACCDB,
new byte[]
{
0x00, 0x01, 0x00, 0x00, 0x53, 0x74, 0x61, 0x6E, 0x64, 0x61, 0x72, 0x64, 0x20, 0x41, 0x43, 0x45, 0x20,
0x44, 0x42
}),
(FileType.ACCDB, new byte[] {0x00, 0x01, 0x00, 0x00, 0x53, 0x74, 0x61, 0x6E, 0x64, 0x61, 0x72, 0x64, 0x20, 0x41, 0x43, 0x45, 0x20, 0x44, 0x42}),
// Microsoft Access
(FileType.MDB,
new byte[]
{
0x00, 0x01, 0x00, 0x00, 0x53, 0x74, 0x61, 0x6E, 0x64, 0x61, 0x72, 0x64, 0x20, 0x4A, 0x65, 0x74, 0x20,
0x44, 0x42
}),
(FileType.MDB, new byte[] {0x00, 0x01, 0x00, 0x00, 0x53, 0x74, 0x61, 0x6E, 0x64, 0x61, 0x72, 0x64, 0x20, 0x4A, 0x65, 0x74, 0x20, 0x44, 0x42}),
// Palm Address Book Archive
(FileType.ABA, new byte[] {0x00, 0x01, 0x42, 0x41}),
@ -604,11 +583,7 @@ public static class Definitions
(FileType.DBA, new byte[] {0x00, 0x01, 0x42, 0x44}),
// Netscape Navigator (v4) database
(FileType.DB,
new byte[]
{
0x00, 0x06, 0x15, 0x61, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x04, 0xD2, 0x00, 0x00, 0x10, 0x00
}),
(FileType.DB, new byte[] {0x00, 0x06, 0x15, 0x61, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x04, 0xD2, 0x00, 0x00, 0x10, 0x00}),
// FLIC animation
(FileType.FLI, new byte[] {0x00, 0x11}),
@ -662,11 +637,7 @@ public static class Definitions
(FileType.DB4, new byte[] {0x04}),
// Adobe InDesign
(FileType.INDD,
new byte[]
{
0x06, 0x06, 0xED, 0xF5, 0xD8, 0x1D, 0x46, 0xE5, 0xBD, 0x31, 0xEF, 0xE7, 0xFE, 0x74, 0xB7, 0x1D
}),
(FileType.INDD, new byte[] {0x06, 0x06, 0xED, 0xF5, 0xD8, 0x1D, 0x46, 0xE5, 0xBD, 0x31, 0xEF, 0xE7, 0xFE, 0x74, 0xB7, 0x1D}),
// Material Exchange Format
(FileType.MXF, new byte[] {0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x02}),
@ -696,11 +667,7 @@ public static class Definitions
(FileType.PCX, new byte[] {0x0A, 0x05, 0x01, 0x01}),
// MultiBit Bitcoin wallet file
(FileType.WALLET,
new byte[]
{
0x0A, 0x16, 0x6F, 0x72, 0x67, 0x2E, 0x62, 0x69, 0x74, 0x63, 0x6F, 0x69, 0x6E, 0x2E, 0x70, 0x72
}),
(FileType.WALLET, new byte[] {0x0A, 0x16, 0x6F, 0x72, 0x67, 0x2E, 0x62, 0x69, 0x74, 0x63, 0x6F, 0x69, 0x6E, 0x2E, 0x70, 0x72}),
// Monochrome Picture TIFF bitmap
(FileType.MP, new byte[] {0x0C, 0xED}),
@ -865,12 +832,7 @@ public static class Definitions
(FileType.AU, new byte[] {0x2E, 0x73, 0x6E, 0x64}),
// Thunderbird-Mozilla Mail Summary File
(FileType.MSF,
new byte[]
{
0x2F, 0x2F, 0x20, 0x3C, 0x21, 0x2D, 0x2D, 0x20, 0x3C, 0x6D, 0x64, 0x62, 0x3A, 0x6D, 0x6F, 0x72, 0x6B,
0x3A, 0x7A
}),
(FileType.MSF, new byte[] {0x2F, 0x2F, 0x20, 0x3C, 0x21, 0x2D, 0x2D, 0x20, 0x3C, 0x6D, 0x64, 0x62, 0x3A, 0x6D, 0x6F, 0x72, 0x6B, 0x3A, 0x7A}),
// MS security catalog file
(FileType.CAT, new byte[] {0x30}),
@ -900,11 +862,7 @@ public static class Definitions
(FileType.WRI, new byte[] {0x32, 0xBE}),
// Pfaff Home Embroidery
(FileType.PCS,
new byte[]
{
0x32, 0x03, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xFF, 0x00
}),
(FileType.PCS, new byte[] {0x32, 0x03, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xFF, 0x00}),
// 7-Zip compressed file
(FileType._7Z, new byte[] {0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C}),
@ -928,40 +886,19 @@ public static class Definitions
(FileType.WSC, new byte[] {0x3C, 0x3F}),
// Windows Visual Stylesheet
(FileType.MANIFEST,
new byte[] {0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D}),
(FileType.MANIFEST, new byte[] {0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D}),
// User Interface Language
(FileType.XML,
new byte[]
{
0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x31, 0x2E,
0x30, 0x22, 0x3F, 0x3E
}),
(FileType.XML, new byte[] {0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x31, 0x2E, 0x30, 0x22, 0x3F, 0x3E}),
// MMC Snap-in Control file
(FileType.MSC,
new byte[]
{
0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x31, 0x2E,
0x30, 0x22, 0x3F, 0x3E, 0x0D, 0x0A, 0x3C, 0x4D, 0x4D, 0x43, 0x5F, 0x43, 0x6F, 0x6E, 0x73, 0x6F, 0x6C,
0x65, 0x46, 0x69, 0x6C, 0x65, 0x20, 0x43, 0x6F, 0x6E, 0x73, 0x6F, 0x6C, 0x65, 0x56, 0x65, 0x72, 0x73,
0x69, 0x6F, 0x6E, 0x3D, 0x22
}),
(FileType.MSC, new byte[] {0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x31, 0x2E, 0x30, 0x22, 0x3F, 0x3E, 0x0D, 0x0A, 0x3C, 0x4D, 0x4D, 0x43, 0x5F, 0x43, 0x6F, 0x6E, 0x73, 0x6F, 0x6C, 0x65, 0x46, 0x69, 0x6C, 0x65, 0x20, 0x43, 0x6F, 0x6E, 0x73, 0x6F, 0x6C, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22}),
// Picasa movie project file
(FileType.MXF,
new byte[]
{
0x3C, 0x43, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x54, 0x69, 0x6D, 0x65, 0x6C, 0x69, 0x6E, 0x65, 0x3E
}),
(FileType.MXF, new byte[] {0x3C, 0x43, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x54, 0x69, 0x6D, 0x65, 0x6C, 0x69, 0x6E, 0x65, 0x3E}),
// Csound music
(FileType.CSD,
new byte[]
{
0x3C, 0x43, 0x73, 0x6F, 0x75, 0x6E, 0x64, 0x53, 0x79, 0x6E, 0x74, 0x68, 0x65, 0x73, 0x69, 0x7A
}),
(FileType.CSD, new byte[] {0x3C, 0x43, 0x73, 0x6F, 0x75, 0x6E, 0x64, 0x53, 0x79, 0x6E, 0x74, 0x68, 0x65, 0x73, 0x69, 0x7A}),
// Adobe FrameMaker
(FileType.FM, new byte[] {0x3C, 0x4D, 0x61, 0x6B, 0x65, 0x72, 0x46, 0x69}),
@ -970,11 +907,7 @@ public static class Definitions
(FileType.MIF, new byte[] {0x3C, 0x4D, 0x61, 0x6B, 0x65, 0x72, 0x46, 0x69}),
// GPS Exchange (v1.1)
(FileType.GPX,
new byte[]
{
0x3C, 0x67, 0x70, 0x78, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x31, 0x2E
}),
(FileType.GPX, new byte[] {0x3C, 0x67, 0x70, 0x78, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x31, 0x2E}),
// BASE85 file
(FileType.B85, new byte[] {0x3C, 0x7E, 0x36, 0x3C, 0x5C, 0x25, 0x5F, 0x30, 0x67, 0x53, 0x71, 0x68, 0x3B}),
@ -1142,11 +1075,7 @@ public static class Definitions
(FileType.SWF, new byte[] {0x43, 0x57, 0x53}),
// Calculux Indoor lighting project file
(FileType.CIN,
new byte[]
{
0x43, 0x61, 0x6C, 0x63, 0x75, 0x6C, 0x75, 0x78, 0x20, 0x49, 0x6E, 0x64, 0x6F, 0x6F, 0x72, 0x20
}),
(FileType.CIN, new byte[] {0x43, 0x61, 0x6C, 0x63, 0x75, 0x6C, 0x75, 0x78, 0x20, 0x49, 0x6E, 0x64, 0x6F, 0x6F, 0x72, 0x20}),
// WhereIsIt Catalog
(FileType.CTF, new byte[] {0x43, 0x61, 0x74, 0x61, 0x6C, 0x6F, 0x67, 0x20}),
@ -1158,11 +1087,7 @@ public static class Definitions
(FileType.CRX, new byte[] {0x43, 0x72, 0x32, 0x34}),
// Creative Voice
(FileType.VOC,
new byte[]
{
0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x56, 0x6F, 0x69, 0x63, 0x65, 0x20, 0x46
}),
(FileType.VOC, new byte[] {0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x56, 0x6F, 0x69, 0x63, 0x65, 0x20, 0x46}),
// PowerISO Direct-Access-Archive image
(FileType.DAA, new byte[] {0x44, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00}),
@ -1258,18 +1183,10 @@ public static class Definitions
(FileType.GX2, new byte[] {0x47, 0x58, 0x32}),
// Genetec video archive
(FileType.G64,
new byte[]
{
0x47, 0x65, 0x6E, 0x65, 0x74, 0x65, 0x63, 0x20, 0x4F, 0x6D, 0x6E, 0x69, 0x63, 0x61, 0x73, 0x74
}),
(FileType.G64, new byte[] {0x47, 0x65, 0x6E, 0x65, 0x74, 0x65, 0x63, 0x20, 0x4F, 0x6D, 0x6E, 0x69, 0x63, 0x61, 0x73, 0x74}),
// SAS Transport dataset
(FileType.XPT,
new byte[]
{
0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x20, 0x52, 0x45, 0x43, 0x4F, 0x52, 0x44, 0x2A, 0x2A, 0x2A
}),
(FileType.XPT, new byte[] {0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x20, 0x52, 0x45, 0x43, 0x4F, 0x52, 0x44, 0x2A, 0x2A, 0x2A}),
// Harvard Graphics presentation file
(FileType.SH3, new byte[] {0x48, 0x48, 0x47, 0x42, 0x31}),
@ -1317,11 +1234,7 @@ public static class Definitions
(FileType.DAT, new byte[] {0x49, 0x6E, 0x6E, 0x6F, 0x20, 0x53, 0x65, 0x74}),
// Inter@ctive Pager Backup (BlackBerry file
(FileType.IPD,
new byte[]
{
0x49, 0x6E, 0x74, 0x65, 0x72, 0x40, 0x63, 0x74, 0x69, 0x76, 0x65, 0x20, 0x50, 0x61, 0x67, 0x65
}),
(FileType.IPD, new byte[] {0x49, 0x6E, 0x74, 0x65, 0x72, 0x40, 0x63, 0x74, 0x69, 0x76, 0x65, 0x20, 0x50, 0x61, 0x67, 0x65}),
// JARCS compressed archive
(FileType.JAR, new byte[] {0x4A, 0x41, 0x52, 0x43, 0x53, 0x00}),
@ -1375,22 +1288,13 @@ public static class Definitions
(FileType.MAR, new byte[] {0x4D, 0x41, 0x52, 0x43}),
// MATLAB v5 workspace
(FileType.MAT,
new byte[]
{
0x4D, 0x41, 0x54, 0x4C, 0x41, 0x42, 0x20, 0x35, 0x2E, 0x30, 0x20, 0x4D, 0x41, 0x54, 0x2D, 0x66, 0x69,
0x6C, 0x65
}),
(FileType.MAT, new byte[] {0x4D, 0x41, 0x54, 0x4C, 0x41, 0x42, 0x20, 0x35, 0x2E, 0x30, 0x20, 0x4D, 0x41, 0x54, 0x2D, 0x66, 0x69, 0x6C, 0x65}),
// MAr compressed archive
(FileType.MAR, new byte[] {0x4D, 0x41, 0x72, 0x30, 0x00}),
// TargetExpress target file
(FileType.MTE,
new byte[]
{
0x4D, 0x43, 0x57, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6E, 0x6F, 0x67, 0x6F, 0x6C, 0x69, 0x65, 0x73
}),
(FileType.MTE, new byte[] {0x4D, 0x43, 0x57, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6E, 0x6F, 0x67, 0x6F, 0x6C, 0x69, 0x65, 0x73}),
// Windows dump file
(FileType.DMP, new byte[] {0x4D, 0x44, 0x4D, 0x50, 0x93, 0xA7}),
@ -1534,26 +1438,13 @@ public static class Definitions
(FileType.ZAP, new byte[] {0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF}),
// MS C++ debugging symbols file
(FileType.PDB,
new byte[]
{
0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x43, 0x2F, 0x43, 0x2B, 0x2B, 0x20
}),
(FileType.PDB, new byte[] {0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x43, 0x2F, 0x43, 0x2B, 0x2B, 0x20}),
// Visual Studio .NET file
(FileType.SLN,
new byte[]
{
0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x56, 0x69, 0x73, 0x75, 0x61, 0x6C
}),
(FileType.SLN, new byte[] {0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x56, 0x69, 0x73, 0x75, 0x61, 0x6C}),
// Windows Media Player playlist
(FileType.WPL,
new byte[]
{
0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73,
0x20, 0x4D, 0x65, 0x64, 0x69, 0x61, 0x20, 0x50, 0x6C, 0x61, 0x79, 0x65, 0x72, 0x20, 0x2D, 0x2D, 0x20
}),
(FileType.WPL, new byte[] {0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x20, 0x4D, 0x65, 0x64, 0x69, 0x61, 0x20, 0x50, 0x6C, 0x61, 0x79, 0x65, 0x72, 0x20, 0x2D, 0x2D, 0x20}),
// VMapSource GPS Waypoint Database
(FileType.GDB, new byte[] {0x4D, 0x73, 0x52, 0x63, 0x66}),
@ -1751,12 +1642,7 @@ public static class Definitions
(FileType.QSD, new byte[] {0x51, 0x57, 0x20, 0x56, 0x65, 0x72, 0x2E, 0x20}),
// Outlook-Exchange message subheader
(FileType.MSG,
new byte[]
{
0x52, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x74, 0x00, 0x20, 0x00, 0x45, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x72,
0x00, 0x79, 0x00
}),
(FileType.MSG, new byte[] {0x52, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x74, 0x00, 0x20, 0x00, 0x45, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x72, 0x00, 0x79, 0x00}),
// Shareaza (P2P) thumbnail
(FileType.DAT, new byte[] {0x52, 0x41, 0x5A, 0x41, 0x54, 0x44, 0x42, 0x31}),
@ -1840,12 +1726,7 @@ public static class Definitions
(FileType.CPI, new byte[] {0x53, 0x49, 0x45, 0x54, 0x52, 0x4F, 0x4E, 0x49}),
// Flexible Image Transport System (FITS) file
(FileType.FITS,
new byte[]
{
0x53, 0x49, 0x4D, 0x50, 0x4C, 0x45, 0x20, 0x20, 0x3D, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54
}),
(FileType.FITS, new byte[] {0x53, 0x49, 0x4D, 0x50, 0x4C, 0x45, 0x20, 0x20, 0x3D, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54}),
// StuffIt archive
(FileType.SIT, new byte[] {0x53, 0x49, 0x54, 0x21, 0x00}),
@ -1860,11 +1741,7 @@ public static class Definitions
(FileType.SPVB, new byte[] {0x53, 0x50, 0x56, 0x42}),
// SQLite database file
(FileType.DB,
new byte[]
{
0x53, 0x51, 0x4C, 0x69, 0x74, 0x65, 0x20, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x20, 0x33, 0x00
}),
(FileType.DB, new byte[] {0x53, 0x51, 0x4C, 0x69, 0x74, 0x65, 0x20, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x20, 0x33, 0x00}),
// DB2 conversion file
(FileType.CNV, new byte[] {0x53, 0x51, 0x4C, 0x4F, 0x43, 0x4F, 0x4E, 0x56}),
@ -1900,11 +1777,7 @@ public static class Definitions
(FileType.MIF, new byte[] {0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20}),
// SPSS template
(FileType.SCT,
new byte[]
{
0x57, 0x04, 0x00, 0x00, 0x53, 0x50, 0x53, 0x53, 0x20, 0x74, 0x65, 0x6D, 0x70, 0x6C, 0x61, 0x74
}),
(FileType.SCT, new byte[] {0x57, 0x04, 0x00, 0x00, 0x53, 0x50, 0x53, 0x53, 0x20, 0x74, 0x65, 0x6D, 0x70, 0x6C, 0x61, 0x74}),
// RIFF Windows Audio
(FileType.WAV, new byte[] {0x57, 0x41, 0x56, 0x45, 0x66, 0x6D, 0x74, 0x20}),
@ -2153,12 +2026,7 @@ public static class Definitions
(FileType.PSP, new byte[] {0x7E, 0x42, 0x4B, 0x00}),
// Easy Street Draw diagram file
(FileType.ESD,
new byte[]
{
0x7E, 0x45, 0x53, 0x44, 0x77, 0xF6, 0x85, 0x3E, 0xBF, 0x6A, 0xD2, 0x11, 0x45, 0x61, 0x73, 0x79, 0x20,
0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x20, 0x44, 0x72, 0x61, 0x77
}),
(FileType.ESD, new byte[] {0x7E, 0x45, 0x53, 0x44, 0x77, 0xF6, 0x85, 0x3E, 0xBF, 0x6A, 0xD2, 0x11, 0x45, 0x61, 0x73, 0x79, 0x20, 0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x20, 0x44, 0x72, 0x61, 0x77}),
// Digital Watchdog DW-TP-500G audio
(FileType.IMG, new byte[] {0x7E, 0x74, 0x2C, 0x01, 0x50, 0x70, 0x02, 0x4D, 0x52}),
@ -2236,11 +2104,7 @@ public static class Definitions
(FileType.WRI, new byte[] {0xBE, 0x00, 0x00, 0x00, 0xAB}),
// Palm Desktop DateBook
(FileType.DAT,
new byte[]
{
0xBE, 0xBA, 0xFE, 0xCA, 0x0F, 0x50, 0x61, 0x6C, 0x6D, 0x53, 0x47, 0x20, 0x44, 0x61, 0x74, 0x61
}),
(FileType.DAT, new byte[] {0xBE, 0xBA, 0xFE, 0xCA, 0x0F, 0x50, 0x61, 0x6C, 0x6D, 0x53, 0x47, 0x20, 0x44, 0x61, 0x74, 0x61}),
// MS Agent Character file
(FileType.ACS, new byte[] {0xC3, 0xAB, 0xCD, 0xAB}),
@ -2510,6 +2374,7 @@ public static class Definitions
(FileType.MOF, new byte[] {0xFF, 0xFE, 0x23, 0x00, 0x6C, 0x00, 0x69, 0x00}),
// DOS system driver
(FileType.SYS, new byte[] {0xFF, 0xFF, 0xFF, 0xFF})
};
}
(FileType.SYS, new byte[] {0xFF, 0xFF, 0xFF, 0xFF}),
};}}

View File

@ -1,5 +1,8 @@
<#@ template language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#
byte[] StringToByteArray(string hex)

View File

@ -0,0 +1,16 @@
using Wabbajack.DTOs.JsonConverters;
namespace Wabbajack.DTOs.DownloadStates;
[JsonName("Bethesda")]
public class Bethesda : ADownloadState
{
public override string TypeName { get; } = "Bethesda";
public override object[] PrimaryKey => new object[] {Game, IsCCMod, ProductId, BranchId, ContentId};
public Game Game { get; set; }
public bool IsCCMod { get; set; }
public string ContentId { get; set; }
public long ProductId { get; set; }
public long BranchId { get; set; }
public string Name { get; set; }
}

View File

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace Wabbajack.DTOs.Logins.BethesdaNet;
public class BeamLogin
{
[JsonPropertyName("password")]
public string Password { get; set; }
[JsonPropertyName("language")]
public string Language { get; set; } = "en";
}

View File

@ -0,0 +1,33 @@
using System.Text.Json.Serialization;
namespace Wabbajack.DTOs.Logins.BethesdaNet;
public class BeamLoginResponse
{
[JsonPropertyName("access_token")]
public string AccessToken { get; set; }
[JsonPropertyName("account")]
public BeamAccount Account { get; set; }
}
public class BeamAccount
{
[JsonPropertyName("admin")]
public bool Admin { get; set; }
[JsonPropertyName("admin_read_only")]
public bool AdminReadOnly { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("mfa_enabled")]
public bool MFAEnabled { get; set; }
[JsonPropertyName("sms_enabled_number")]
public object sms_enabled_number { get; set; }
[JsonPropertyName("username")]
public string UserName { get; set; }
}

View File

@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
namespace Wabbajack.DTOs.Logins.BethesdaNet;
public class CDPAuthPost
{
[JsonPropertyName("access_token")]
public string AccessToken { get; set; }
}

View File

@ -0,0 +1,19 @@
using System.Text.Json.Serialization;
namespace Wabbajack.DTOs.Logins.BethesdaNet;
public class CDPAuthResponse
{
[JsonPropertyName("entitlement_ids")]
public int[] EntitlementIds { get; set; }
[JsonPropertyName("beam_client_api_key")]
public string BeamClientApiKey { get; set; }
[JsonPropertyName("session_id")]
public string SessionId { get; set; }
[JsonPropertyName("token")]
public string Token { get; set; }
[JsonPropertyName("beam_token")]
public string[] BeamToken { get; set; }
[JsonPropertyName("oauth_token")]
public string OAuthToken { get; set; }
}

View File

@ -0,0 +1,10 @@
using Wabbajack.DTOs.Logins.BethesdaNet;
namespace Wabbajack.DTOs.Logins;
public class BethesdaNetLoginState
{
public string Username { get; set; }
public string Password { get; set; }
public BeamLoginResponse? BeamResponse { get; set; }
}

View File

@ -0,0 +1,178 @@
using System.IO.Compression;
using System.Security.Cryptography;
using CS_AES_CTR;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using Microsoft.Extensions.Logging;
using Wabbajack.Common;
using Wabbajack.Downloaders.Interfaces;
using Wabbajack.DTOs;
using Wabbajack.DTOs.DownloadStates;
using Wabbajack.DTOs.Validation;
using Wabbajack.Hashing.xxHash64;
using Wabbajack.Networking.BethesdaNet;
using Wabbajack.Networking.BethesdaNet.DTOs;
using Wabbajack.Networking.Http;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter;
namespace Wabbajack.Downloaders.Bethesda;
public class BethesdaDownloader : ADownloader<DTOs.DownloadStates.Bethesda>, IUrlDownloader, IChunkedSeekableStreamDownloader
{
private readonly Client _client;
private readonly IResource<HttpClient> _limiter;
private readonly HttpClient _httpClient;
private readonly ILogger<BethesdaDownloader> _logger;
public BethesdaDownloader(ILogger<BethesdaDownloader> logger, Client client, HttpClient httpClient, IResource<HttpClient> limiter)
{
_logger = logger;
_client = client;
_limiter = limiter;
_httpClient = httpClient;
}
public override async Task<Hash> Download(Archive archive, DTOs.DownloadStates.Bethesda state, AbsolutePath destination, IJob job, CancellationToken token)
{
var depot = await _client.GetDepots(state, token);
var tree = await _client.GetTree(state, token);
var chunks = tree!.DepotList.First().FileList.First().ChunkList;
using var os = destination.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
var hasher = new xxHashAlgorithm(0);
Hash finalHash = default;
var aesKey = depot.ExInfoA.ToArray();
var aesIV = depot.ExInfoB.Take(16).ToArray();
await chunks.PMapAll(async chunk =>
{
var data = await GetChunk(state, chunk, depot.PropertiesId, token);
var reported = job.Report(data.Length, token);
var aesCtr = new AES_CTR(aesKey, aesIV, false);
data = aesCtr.DecryptBytes(data);
if (chunk.UncompressedSize != chunk.ChunkSize)
{
var inflater = new InflaterInputStream(new MemoryStream(data));
data = await inflater.ReadAllAsync();
}
await reported;
return data;
})
.Do(async data =>
{
if (data.Length < tree.DepotList.First().BytesPerChunk)
{
hasher.HashBytes(data);
}
else
{
finalHash = Hash.FromULong(hasher.FinalizeHashValueInternal(data));
}
await os.WriteAsync(data, token);
});
return finalHash;
}
private async Task<byte[]> GetChunk(DTOs.DownloadStates.Bethesda state, Chunk chunk, long propertiesId,
CancellationToken token)
{
var uri = new Uri($"https://content.cdp.bethesda.net/{state.ProductId}/{propertiesId}/{chunk.Sha}");
var msg = new HttpRequestMessage(HttpMethod.Get, uri);
msg.Headers.Add("User-Agent", "bnet");
using var job = await _limiter.Begin("Getting chunk", chunk.ChunkSize, token);
using var response = await _httpClient.SendAsync(msg, token);
if (!response.IsSuccessStatusCode)
throw new HttpException(response);
await job.Report(chunk.ChunkSize, token);
return await response.Content.ReadAsByteArrayAsync(token);
}
public override async Task<bool> Prepare()
{
await _client.CDPAuth(CancellationToken.None);
return true;
}
public override bool IsAllowed(ServerAllowList allowList, IDownloadState state)
{
return true;
}
public override IDownloadState? Resolve(IReadOnlyDictionary<string, string> iniData)
{
if (iniData.ContainsKey("directURL") && Uri.TryCreate(iniData["directURL"], UriKind.Absolute, out var uri))
{
return Parse(uri);
}
return null;
}
public override Priority Priority => Priority.Normal;
public override async Task<bool> Verify(Archive archive, DTOs.DownloadStates.Bethesda state, IJob job, CancellationToken token)
{
var depot = await _client.GetDepots(state, token);
return depot != null;
}
public override IEnumerable<string> MetaIni(Archive a, DTOs.DownloadStates.Bethesda state)
{
return new[] {$"directURL={UnParse(state)}"};
}
public IDownloadState? Parse(Uri uri)
{
if (uri.Scheme != "bethesda") return null;
var path = uri.PathAndQuery.Split("/", StringSplitOptions.RemoveEmptyEntries);
if (path.Length != 4) return null;
var game = GameRegistry.TryGetByFuzzyName(uri.Host);
if (game == null) return null;
if (!long.TryParse(path[1], out var productId)) return null;
if (!long.TryParse(path[2], out var branchId)) return null;
bool isCCMod = false;
switch (path[0])
{
case "cc":
isCCMod = true;
break;
case "mod":
isCCMod = false;
break;
default:
return null;
}
return new DTOs.DownloadStates.Bethesda
{
Game = game.Game,
IsCCMod = isCCMod,
ProductId = productId,
BranchId = branchId,
ContentId = path[3]
};
}
public Uri UnParse(IDownloadState state)
{
var cstate = (DTOs.DownloadStates.Bethesda) state;
return new Uri($"bethesda://{cstate.Game}/{(cstate.IsCCMod ? "cc" : "mod")}/{cstate.ProductId}/{cstate.BranchId}/{cstate.ContentId}");
}
public ValueTask<Stream> GetChunkedSeekableStream(Archive archive, CancellationToken token)
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
using Wabbajack.Downloaders.Interfaces;
using Wabbajack.DTOs;
namespace Wabbajack.Downloaders.Bethesda;
public static class ServiceExtensions
{
public static IServiceCollection AddBethesdaDownloader(this IServiceCollection services)
{
return services.AddAllSingleton<IDownloader, IDownloader<DTOs.DownloadStates.Bethesda>, IUrlDownloader, BethesdaDownloader>();
}
}

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Wabbajack.Common\Wabbajack.Common.csproj" />
<ProjectReference Include="..\Wabbajack.Downloaders.Interfaces\Wabbajack.Downloaders.Interfaces.csproj" />
<ProjectReference Include="..\Wabbajack.Networking.BethesdaNet\Wabbajack.Networking.BethesdaNet.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="LibAES-CTR" Version="0.9.6" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
</ItemGroup>
</Project>

View File

@ -180,6 +180,34 @@ public class DownloaderTests
"https://authored-files.wabbajack.org/Tonal%20Architect_WJ_TEST_FILES.zip_9cb97a01-3354-4077-9e4a-7e808d47794fFFOOO")
}
}
},
// Bethesda
new object[]
{
new Archive
{
Hash = default,
State = new DTOs.DownloadStates.Bethesda
{
Game = Game.SkyrimSpecialEdition,
IsCCMod = true,
ProductId = 4,
BranchID = 90898,
ContentId = "4059054"
}
},
new Archive
{
Hash = default,
State = new DTOs.DownloadStates.Bethesda
{
Game = Game.SkyrimSpecialEdition,
IsCCMod = true,
ProductId = 6,
BranchID = 9898,
ContentId = "059054"
}
},
}
};

View File

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Wabbajack.Downloaders.Bethesda;
using Wabbajack.Downloaders.Http;
using Wabbajack.Downloaders.IPS4OAuth2Downloader;
using Wabbajack.Downloaders.MediaFire;
@ -24,6 +25,7 @@ public static class ServiceExtensions
.AddIPS4OAuth2Downloaders()
.AddWabbajackCDNDownloader()
.AddGameFileDownloader()
.AddBethesdaDownloader()
.AddWabbajackClient()
.AddSingleton<DownloadDispatcher>();
}

View File

@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Wabbajack.Downloaders.Bethesda\Wabbajack.Downloaders.Bethesda.csproj" />
<ProjectReference Include="..\Wabbajack.Downloaders.GameFile\Wabbajack.Downloaders.GameFile.csproj" />
<ProjectReference Include="..\Wabbajack.Downloaders.GoogleDrive\Wabbajack.Downloaders.GoogleDrive.csproj" />
<ProjectReference Include="..\Wabbajack.Downloaders.Http\Wabbajack.Downloaders.Http.csproj" />

View File

@ -11,6 +11,7 @@
<ProjectReference Include="..\Wabbajack.Common\Wabbajack.Common.csproj" />
<ProjectReference Include="..\Wabbajack.Downloaders.Interfaces\Wabbajack.Downloaders.Interfaces.csproj" />
<ProjectReference Include="..\Wabbajack.DTOs\Wabbajack.DTOs.csproj" />
<ProjectReference Include="..\Wabbajack.Networking.BethesdaNet\Wabbajack.Networking.BethesdaNet.csproj" />
<ProjectReference Include="..\Wabbajack.Networking.Http.Interfaces\Wabbajack.Networking.Http.Interfaces.csproj" />
<ProjectReference Include="..\Wabbajack.Paths.IO\Wabbajack.Paths.IO.csproj" />
</ItemGroup>

View File

@ -1,9 +1,11 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@ -11,8 +13,10 @@ using OMODFramework;
using Wabbajack.Common;
using Wabbajack.Common.FileSignatures;
using Wabbajack.Compression.BSA;
using Wabbajack.Compression.BSA.FO4Archive;
using Wabbajack.DTOs.Streams;
using Wabbajack.FileExtractor.ExtractedFiles;
using Wabbajack.IO.Async;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter;
@ -24,6 +28,7 @@ public class FileExtractor
public static readonly SignatureChecker ArchiveSigs = new(FileType.TES3,
FileType.BSA,
FileType.BA2,
FileType.BTAR,
FileType.ZIP,
//FileType.EXE,
FileType.RAR_OLD,
@ -43,6 +48,7 @@ public class FileExtractor
new Extension(".7zip"),
new Extension(".rar"),
new Extension(".zip"),
new Extension(".btar"),
OMODExtension,
FOMODExtension
};
@ -98,6 +104,9 @@ public class FileExtractor
break;
}
case FileType.BTAR:
results = await GatheringExtractWithBTAR(sFn, shouldExtract, mapfn, token);
break;
case FileType.BSA:
case FileType.BA2:
@ -120,6 +129,75 @@ public class FileExtractor
return results;
}
private async Task<IDictionary<RelativePath,T>> GatheringExtractWithBTAR<T>
(IStreamFactory sFn, Predicate<RelativePath> shouldExtract, Func<RelativePath,IExtractedFile,ValueTask<T>> mapfn, CancellationToken token)
{
await using var strm = await sFn.GetStream();
var astrm = new AsyncBinaryReader(strm);
var magic = BinaryPrimitives.ReadUInt32BigEndian(await astrm.ReadBytes(4));
// BTAR Magic
if (magic != 0x42544152) throw new Exception("Not a valid BTAR file");
if (await astrm.ReadUInt16() != 1) throw new Exception("Invalid BTAR major version, should be 1");
var minorVersion = await astrm.ReadUInt16();
if (minorVersion is < 2 or > 4) throw new Exception("Invalid BTAR minor version");
var results = new Dictionary<RelativePath, T>();
while (astrm.Position < astrm.Length)
{
var nameLength = await astrm.ReadUInt16();
var name = Encoding.UTF8.GetString(await astrm.ReadBytes(nameLength)).ToRelativePath();
var dataLength = await astrm.ReadUInt64();
var newPos = astrm.Position + (long)dataLength;
if (!shouldExtract(name))
{
astrm.Position += (long)dataLength;
continue;
}
var result = await mapfn(name, new BTARExtractedFile(sFn, name, astrm, astrm.Position, (long) dataLength));
results.Add(name, result);
astrm.Position = newPos;
}
return results;
}
private class BTARExtractedFile : IExtractedFile
{
private readonly IStreamFactory _parent;
private readonly AsyncBinaryReader _rdr;
private readonly long _start;
private readonly long _length;
private readonly RelativePath _name;
public BTARExtractedFile(IStreamFactory parent, RelativePath name, AsyncBinaryReader rdr, long startingPosition, long length)
{
_name = name;
_parent = parent;
_rdr = rdr;
_start = startingPosition;
_length = length;
}
public DateTime LastModifiedUtc => _parent.LastModifiedUtc;
public IPath Name => _name;
public async ValueTask<Stream> GetStream()
{
_rdr.Position = _start;
var data = await _rdr.ReadBytes((int) _length);
return new MemoryStream(data);
}
public bool CanMove { get; set; } = true;
public async ValueTask Move(AbsolutePath newPath, CancellationToken token)
{
await using var output = newPath.Open(FileMode.Create, FileAccess.Read, FileShare.Read);
_rdr.Position = _start;
await _rdr.BaseStream.CopyToLimitAsync(output, (int)_length, token);
}
}
private async Task<Dictionary<RelativePath, T>> GatheringExtractWithOMOD<T>
(Stream archive, Predicate<RelativePath> shouldExtract, Func<RelativePath, IExtractedFile, ValueTask<T>> mapfn,
CancellationToken token)

View File

@ -0,0 +1,186 @@
using System.Net.Http.Json;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Wabbajack.DTOs;
using Wabbajack.DTOs.DownloadStates;
using Wabbajack.DTOs.Logins;
using Wabbajack.DTOs.Logins.BethesdaNet;
using Wabbajack.Networking.BethesdaNet.DTOs;
using Wabbajack.Networking.Http;
using Wabbajack.Networking.Http.Interfaces;
namespace Wabbajack.Networking.BethesdaNet;
/// <summary>
/// This code is heavily based on code researched and prototyped by Nukem9 https://github.com/Nukem9/bethnet_cli
/// </summary>
public class Client
{
private readonly ITokenProvider<BethesdaNetLoginState> _tokenProvider;
private readonly ILogger<Client> _logger;
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
private CDPAuthResponse? _entitlementData = null;
public const string AgentPlatform = "WINDOWS";
public const string AgentProduct = "FALLOUT4";
public const string AgentLanguage = "en";
private const string ClientAPIKey = "FeBqmQA8wxd94RtqymKwzmtcQcaA5KHOpDkQBSegx4WePeluZTCIm5scoeKTbmGl";
private const string ClientId = "95578d65-45bf-4a03-b7f7-a43d29b9467d";
private const string AgentVersion = $"{AgentProduct};;BDK;1.0013.99999.1;{AgentPlatform}";
private string FingerprintKey { get; set; }
public Client(ILogger<Client> logger, HttpClient client, ITokenProvider<BethesdaNetLoginState> tokenProvider)
{
_tokenProvider = tokenProvider;
_logger = logger;
_httpClient = client;
_jsonOptions = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
SetFingerprint();
}
public async Task Login(CancellationToken token)
{
var loginData = await _tokenProvider.Get();
var msg = MakeMessage(HttpMethod.Post, new Uri($"https://api.bethesda.net/beam/accounts/login/{loginData!.Username}"));
msg.Headers.Add("X-Client-API-key", ClientAPIKey);
msg.Headers.Add("x-src-fp", FingerprintKey);
msg.Headers.Add("X-Platform", AgentPlatform);
msg.Content = new StringContent(JsonSerializer.Serialize(new BeamLogin
{
Password = loginData!.Password,
Language = AgentLanguage
}, _jsonOptions), Encoding.UTF8, "application/json");
var result = await _httpClient.SendAsync(msg, token);
if (!result.IsSuccessStatusCode)
throw new HttpException(result);
var response = await result.Content.ReadFromJsonAsync<BeamLoginResponse>(_jsonOptions, token);
loginData.BeamResponse = response;
await _tokenProvider.SetToken(loginData);
}
public async Task CDPAuth(CancellationToken token)
{
var state = await _tokenProvider.Get();
if (string.IsNullOrEmpty(state.BeamResponse?.AccessToken))
throw new Exception("Can't get CDPAuth before Bethesda Net login");
var msg = MakeMessage(HttpMethod.Post, new Uri("https://api.bethesda.net/cdp-user/auth"));
msg.Headers.Add("x-src-fp", FingerprintKey);
msg.Headers.Add("x-cdp-app", "UGC SDK");
msg.Headers.Add("x-cdp-app-ver", "0.9.11314/debug");
msg.Headers.Add("x-cdp-lib-ver", "0.9.11314/debug");
msg.Headers.Add("x-cdp-platform", "Win/32");
msg.Content = new StringContent(JsonSerializer.Serialize(new CDPAuthPost()
{AccessToken = state.BeamResponse.AccessToken}), Encoding.UTF8, "application/json");
var request = await _httpClient.SendAsync(msg, token);
if (!request.IsSuccessStatusCode)
throw new HttpException(request);
_entitlementData = await request.Content.ReadFromJsonAsync<CDPAuthResponse>(_jsonOptions, token);
}
private HttpRequestMessage MakeMessage(HttpMethod method, Uri uri)
{
var msg = new HttpRequestMessage(method, uri);
msg.Headers.Add("User-Agent", "bnet");
msg.Headers.Add("Accept", "application/json");
msg.Headers.Add("X-BNET-Agent", AgentVersion);
return msg;
}
private void SetFingerprint()
{
var keyBytes = new byte[20];
using (var rng = new System.Security.Cryptography.RNGCryptoServiceProvider())
rng.GetBytes(keyBytes);
FingerprintKey = string.Concat(Array.ConvertAll(keyBytes, x => x.ToString("X2")));
}
public async Task<IEnumerable<(Content Content, Bethesda State)>> ListContent(Game game, CancellationToken token)
{
var gameKey = game switch
{
Game.SkyrimSpecialEdition => "SKYRIM",
Game.Fallout4 => "FALLOUT4",
_ => throw new InvalidOperationException("Only Skyrim and Fallout 4 are supported for Bethesda Net content")
};
await EnsureAuthed(token);
var authData = await _tokenProvider.Get();
var msg = MakeMessage(HttpMethod.Get,
new Uri(
$"https://api.bethesda.net/mods/ugc-workshop/list?page=1;sort=alpha;order=asc;number_results=500;platform=WINDOWS;product={gameKey};cc_mod=true"));
msg.Headers.Add("X-Access-Token", authData!.BeamResponse!.AccessToken);
var request = await _httpClient.SendAsync(msg, token);
if (!request.IsSuccessStatusCode)
throw new HttpException(request);
var response = await request.Content.ReadFromJsonAsync<ListSubscribeResponse>(_jsonOptions, token);
return response!.Platform.Response.Content
.Select(c => (c, new Bethesda
{
Game = game,
ContentId = c.ContentId,
IsCCMod = c.CcMod,
ProductId = c.CdpProductId,
BranchId = c.CdpBranchId
}));
}
private async Task EnsureAuthed(CancellationToken token)
{
if (_entitlementData == null)
await CDPAuth(token);
}
private int ProductId(Game game)
{
if (game == Game.SkyrimSpecialEdition) return 4;
return 0;
}
public async Task<Depot?> GetDepots(Bethesda state, CancellationToken token)
{
return (await MakeCdpRequest<Dictionary<string, Depot>>(state, "depots", token))?.Values.First();
}
public async Task<Tree?> GetTree(Bethesda state, CancellationToken token)
{
return await MakeCdpRequest<Tree>(state, "tree", token);
}
private async Task<T?> MakeCdpRequest<T>(Bethesda state, string type, CancellationToken token)
{
await EnsureAuthed(token);
var msg = MakeMessage(HttpMethod.Get,
new Uri($"https://api.bethesda.net/cdp-user/projects/{state.ProductId}/branches/{state.BranchId}/{type}/.json"));
msg.Headers.Add("x-src-fp", FingerprintKey);
msg.Headers.Add("x-cdp-app", "UGC SDK");
msg.Headers.Add("x-cdp-app-ver", "0.9.11314/debug");
msg.Headers.Add("x-cdp-lib-ver", "0.9.11314/debug");
msg.Headers.Add("x-cdp-platform", "Win/32");
msg.Headers.Add("Authorization", $"Token {_entitlementData!.Token}");
using var request = await _httpClient.SendAsync(msg, token);
if (!request.IsSuccessStatusCode)
throw new HttpException(request);
var response = await request.Content.ReadFromJsonAsync<T>(_jsonOptions, token);
return response;
}
}

View File

@ -0,0 +1,66 @@
using System.Text.Json.Serialization;
namespace Wabbajack.Networking.BethesdaNet.DTOs;
public class Depot
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("properties_id")]
public int PropertiesId { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("build")]
public int Build { get; set; }
[JsonPropertyName("bytes_per_chunk")]
public int BytesPerChunk { get; set; }
[JsonPropertyName("size_on_disk")]
public int SizeOnDisk { get; set; }
[JsonPropertyName("download_size")]
public int DownloadSize { get; set; }
[JsonPropertyName("depot_type")]
public int DepotType { get; set; }
[JsonPropertyName("deployment_order")]
public int DeploymentOrder { get; set; }
[JsonPropertyName("compression_type")]
public int CompressionType { get; set; }
[JsonPropertyName("encryption_type")]
public int EncryptionType { get; set; }
[JsonPropertyName("language")]
public int Language { get; set; }
[JsonPropertyName("region")]
public int Region { get; set; }
[JsonPropertyName("default_region")]
public bool DefaultRegion { get; set; }
[JsonPropertyName("default_language")]
public bool DefaultLanguage { get; set; }
[JsonPropertyName("platform")]
public int Platform { get; set; }
[JsonPropertyName("architecture")]
public int Architecture { get; set; }
[JsonPropertyName("ex_info_A")]
public List<byte> ExInfoA { get; set; }
[JsonPropertyName("ex_info_B")]
public List<byte> ExInfoB { get; set; }
[JsonPropertyName("is_dlc")]
public bool IsDlc { get; set; }
}

View File

@ -0,0 +1,151 @@
using System.Text.Json.Serialization;
namespace Wabbajack.Networking.BethesdaNet.DTOs;
public class Price
{
[JsonPropertyName("currency_id")]
public int CurrencyId { get; set; }
[JsonPropertyName("price_id")]
public int PriceId { get; set; }
[JsonPropertyName("sale")]
public bool Sale { get; set; }
[JsonPropertyName("currency_name")]
public string CurrencyName { get; set; }
[JsonPropertyName("currency_type")]
public string CurrencyType { get; set; }
[JsonPropertyName("amount")]
public int Amount { get; set; }
[JsonPropertyName("original_amount")]
public int OriginalAmount { get; set; }
}
public class Content
{
[JsonPropertyName("rating")]
public double Rating { get; set; }
[JsonPropertyName("version")]
public int Version { get; set; }
[JsonPropertyName("depot_size")]
public int DepotSize { get; set; }
[JsonPropertyName("is_subscribed")]
public bool IsSubscribed { get; set; }
[JsonPropertyName("preview_file_size")]
public int PreviewFileSize { get; set; }
[JsonPropertyName("preview_file_url")]
public string PreviewFileUrl { get; set; }
[JsonPropertyName("is_following")]
public bool IsFollowing { get; set; }
[JsonPropertyName("wip")]
public bool Wip { get; set; }
[JsonPropertyName("is_published")]
public bool IsPublished { get; set; }
[JsonPropertyName("user_rating")]
public int UserRating { get; set; }
[JsonPropertyName("platform")]
public List<string> Platform { get; set; }
[JsonPropertyName("state")]
public int State { get; set; }
[JsonPropertyName("rating_count")]
public int RatingCount { get; set; }
[JsonPropertyName("cdp_branch_id")]
public long CdpBranchId { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("username")]
public string Username { get; set; }
[JsonPropertyName("product")]
public string Product { get; set; }
[JsonPropertyName("updated")]
public string Updated { get; set; }
[JsonPropertyName("cc_mod")]
public bool CcMod { get; set; }
[JsonPropertyName("bundle")]
public bool Bundle { get; set; }
[JsonPropertyName("prices")]
public List<Price> Prices { get; set; }
[JsonPropertyName("is_public")]
public bool IsPublic { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("catalog_item_id")]
public int CatalogItemId { get; set; }
[JsonPropertyName("is_auto_moderated")]
public bool IsAutoModerated { get; set; }
[JsonPropertyName("content_id")]
public string ContentId { get; set; }
[JsonPropertyName("cdp_product_id")]
public long CdpProductId { get; set; }
}
public class Response
{
[JsonPropertyName("product")]
public List<string> Product { get; set; }
[JsonPropertyName("total_results_count")]
public int TotalResultsCount { get; set; }
[JsonPropertyName("content")]
public List<Content> Content { get; set; }
[JsonPropertyName("platform")]
public List<string> Platform { get; set; }
[JsonPropertyName("page_results_count")]
public int PageResultsCount { get; set; }
[JsonPropertyName("page")]
public int Page { get; set; }
}
public class Platform
{
[JsonPropertyName("message")]
public string Message { get; set; }
[JsonPropertyName("code")]
public int Code { get; set; }
[JsonPropertyName("response")]
public Response Response { get; set; }
}
public class ListSubscribeResponse
{
[JsonPropertyName("platform")]
public Platform Platform { get; set; }
}

View File

@ -0,0 +1,194 @@
using System.Text.Json.Serialization;
namespace Wabbajack.Networking.BethesdaNet.DTOs;
public class BuildHistory
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("description")]
public string Description { get; set; }
}
public class BuildFields
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("create_date")]
public string CreateDate { get; set; }
[JsonPropertyName("description")]
public string Description { get; set; }
[JsonPropertyName("build_type")]
public int BuildType { get; set; }
[JsonPropertyName("locked")]
public bool Locked { get; set; }
[JsonPropertyName("storage_key")]
public string StorageKey { get; set; }
[JsonPropertyName("major")]
public bool Major { get; set; }
}
public class Chunk
{
[JsonPropertyName("index")]
public int Index { get; set; }
[JsonPropertyName("chunk_size")]
public int ChunkSize { get; set; }
[JsonPropertyName("uncompressed_size")]
public int UncompressedSize { get; set; }
[JsonPropertyName("sha")]
public string Sha { get; set; }
}
public class FileList
{
[JsonPropertyName("file_id")]
public int FileId { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("sha")]
public string Sha { get; set; }
[JsonPropertyName("file_size")]
public int FileSize { get; set; }
[JsonPropertyName("compressed_size")]
public int CompressedSize { get; set; }
[JsonPropertyName("chunk_count")]
public int ChunkCount { get; set; }
[JsonPropertyName("modifiable")]
public bool Modifiable { get; set; }
[JsonPropertyName("chunk_list")]
public Chunk[] ChunkList { get; set; }
}
public class DepotList
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("properties_id")]
public int PropertiesId { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("build")]
public int Build { get; set; }
[JsonPropertyName("bytes_per_chunk")]
public int BytesPerChunk { get; set; }
[JsonPropertyName("size_on_disk")]
public int SizeOnDisk { get; set; }
[JsonPropertyName("download_size")]
public int DownloadSize { get; set; }
[JsonPropertyName("depot_type")]
public int DepotType { get; set; }
[JsonPropertyName("deployment_order")]
public int DeploymentOrder { get; set; }
[JsonPropertyName("compression_type")]
public int CompressionType { get; set; }
[JsonPropertyName("encryption_type")]
public int EncryptionType { get; set; }
[JsonPropertyName("language")]
public int Language { get; set; }
[JsonPropertyName("region")]
public int Region { get; set; }
[JsonPropertyName("default_region")]
public bool DefaultRegion { get; set; }
[JsonPropertyName("default_language")]
public bool DefaultLanguage { get; set; }
[JsonPropertyName("platform")]
public int Platform { get; set; }
[JsonPropertyName("architecture")]
public int Architecture { get; set; }
[JsonPropertyName("is_dlc")]
public bool IsDlc { get; set; }
[JsonPropertyName("file_list")]
public FileList[] FileList { get; set; }
}
public class Tree
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("entitlement_id")]
public int EntitlementId { get; set; }
[JsonPropertyName("branch_type")]
public int BranchType { get; set; }
[JsonPropertyName("project")]
public int Project { get; set; }
[JsonPropertyName("build")]
public int Build { get; set; }
[JsonPropertyName("available")]
public bool Available { get; set; }
[JsonPropertyName("preload")]
public bool Preload { get; set; }
[JsonPropertyName("preload_ondeck")]
public bool PreloadOndeck { get; set; }
[JsonPropertyName("diff_type")]
public int DiffType { get; set; }
[JsonPropertyName("build_history_length")]
public int BuildHistoryLength { get; set; }
[JsonPropertyName("promote_ondeck_after_diff")]
public bool PromoteOndeckAfterDiff { get; set; }
[JsonPropertyName("storage_url")]
public string StorageUrl { get; set; }
[JsonPropertyName("build_history")]
public List<BuildHistory> BuildHistory { get; set; }
[JsonPropertyName("build_fields")]
public BuildFields BuildFields { get; set; }
[JsonPropertyName("depot_list")]
public List<DepotList> DepotList { get; set; }
}

View File

@ -0,0 +1,11 @@
using Microsoft.Extensions.DependencyInjection;
namespace Wabbajack.Networking.BethesdaNet;
public static class ServiceExtensions
{
public static void AddBethesdaNet(this IServiceCollection services)
{
services.AddSingleton<BethesdaNet.Client>();
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
<HintPath>..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\6.0.1\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Wabbajack.DTOs\Wabbajack.DTOs.csproj" />
<ProjectReference Include="..\Wabbajack.Networking.Http.Interfaces\Wabbajack.Networking.Http.Interfaces.csproj" />
<ProjectReference Include="..\Wabbajack.Networking.Http\Wabbajack.Networking.Http.csproj" />
</ItemGroup>
</Project>

View File

@ -11,6 +11,7 @@ using Wabbajack.Downloaders.GameFile;
using Wabbajack.DTOs;
using Wabbajack.DTOs.Logins;
using Wabbajack.Installer;
using Wabbajack.Networking.BethesdaNet;
using Wabbajack.Networking.Discord;
using Wabbajack.Networking.Http;
using Wabbajack.Networking.Http.Interfaces;
@ -111,9 +112,11 @@ public static class ServiceExtensions
service.AddSingleton<Client>();
service.AddSingleton<WriteOnlyClient>();
service.AddBethesdaNet();
// Token Providers
service.AddAllSingleton<ITokenProvider<NexusApiState>, EncryptedJsonTokenProvider<NexusApiState>, NexusApiTokenProvider>();
service.AddAllSingleton<ITokenProvider<BethesdaNetLoginState>, EncryptedJsonTokenProvider<BethesdaNetLoginState>, BethesdaNetTokenProvider>();
service
.AddAllSingleton<ITokenProvider<LoversLabLoginState>, EncryptedJsonTokenProvider<LoversLabLoginState>,
LoversLabTokenProvider>();

View File

@ -0,0 +1,13 @@
using Microsoft.Extensions.Logging;
using Wabbajack.DTOs.JsonConverters;
using Wabbajack.DTOs.Logins;
namespace Wabbajack.Services.OSIntegrated.TokenProviders;
public class BethesdaNetTokenProvider : EncryptedJsonTokenProvider<BethesdaNetLoginState>
{
public BethesdaNetTokenProvider(ILogger<BethesdaNetLoginState> logger, DTOSerializer dtos) : base(logger, dtos,
"bethesda-net")
{
}
}

View File

@ -20,6 +20,7 @@
<ProjectReference Include="..\Wabbajack.Compiler\Wabbajack.Compiler.csproj" />
<ProjectReference Include="..\Wabbajack.Downloaders.Dispatcher\Wabbajack.Downloaders.Dispatcher.csproj" />
<ProjectReference Include="..\Wabbajack.Installer\Wabbajack.Installer.csproj" />
<ProjectReference Include="..\Wabbajack.Networking.BethesdaNet\Wabbajack.Networking.BethesdaNet.csproj" />
<ProjectReference Include="..\Wabbajack.Networking.Discord\Wabbajack.Networking.Discord.csproj" />
<ProjectReference Include="..\Wabbajack.Networking.Steam\Wabbajack.Networking.Steam.csproj" />
<ProjectReference Include="..\Wabbajack.VFS\Wabbajack.VFS.csproj" />

View File

@ -133,6 +133,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wabbajack.IO.Async", "Wabba
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wabbajack.Compression.Zip.Test", "Wabbajack.Compression.Zip.Test\Wabbajack.Compression.Zip.Test.csproj", "{6D7EA87E-6ABE-4BA3-B93A-BE5E71A4DE7C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wabbajack.Networking.BethesdaNet", "Wabbajack.Networking.BethesdaNet\Wabbajack.Networking.BethesdaNet.csproj", "{A3813D73-9A8E-4CE7-861A-C59043DFFC14}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wabbajack.Downloaders.Bethesda", "Wabbajack.Downloaders.Bethesda\Wabbajack.Downloaders.Bethesda.csproj", "{B10BB6D6-B3FC-4A76-8A07-6A0A0ADDE198}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -363,6 +367,14 @@ Global
{6D7EA87E-6ABE-4BA3-B93A-BE5E71A4DE7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D7EA87E-6ABE-4BA3-B93A-BE5E71A4DE7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6D7EA87E-6ABE-4BA3-B93A-BE5E71A4DE7C}.Release|Any CPU.Build.0 = Release|Any CPU
{A3813D73-9A8E-4CE7-861A-C59043DFFC14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A3813D73-9A8E-4CE7-861A-C59043DFFC14}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A3813D73-9A8E-4CE7-861A-C59043DFFC14}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3813D73-9A8E-4CE7-861A-C59043DFFC14}.Release|Any CPU.Build.0 = Release|Any CPU
{B10BB6D6-B3FC-4A76-8A07-6A0A0ADDE198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B10BB6D6-B3FC-4A76-8A07-6A0A0ADDE198}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B10BB6D6-B3FC-4A76-8A07-6A0A0ADDE198}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B10BB6D6-B3FC-4A76-8A07-6A0A0ADDE198}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -408,6 +420,8 @@ Global
{10165025-D30B-44B7-A764-50E15603AE56} = {F677890D-5109-43BC-97C7-C4CD47C8EE0C}
{64AD7E26-5643-4969-A61C-E0A90FA25FCB} = {F677890D-5109-43BC-97C7-C4CD47C8EE0C}
{6D7EA87E-6ABE-4BA3-B93A-BE5E71A4DE7C} = {F677890D-5109-43BC-97C7-C4CD47C8EE0C}
{A3813D73-9A8E-4CE7-861A-C59043DFFC14} = {F01F8595-5FD7-4506-8469-F4A5522DACC1}
{B10BB6D6-B3FC-4A76-8A07-6A0A0ADDE198} = {98B731EE-4FC0-4482-A069-BCBA25497871}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0AA30275-0F38-4A7D-B645-F5505178DDE8}