mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Compare commits
78 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1fad0f1d73 | ||
|
c5dae97060 | ||
|
931de94230 | ||
|
4cc08cfc0a | ||
|
f58b6190c6 | ||
|
dee84004f5 | ||
|
75a07ea050 | ||
|
8281e77247 | ||
|
fa4d9ff60e | ||
|
278e06139e | ||
|
ec36b8ab4a | ||
|
0ffe0ca8f4 | ||
|
dfeec6b235 | ||
|
c3e8621de6 | ||
|
d911eab6d8 | ||
|
1c5726245a | ||
|
a4f3782eb9 | ||
|
c8bce3a849 | ||
|
2d72370c32 | ||
|
22fcd4b5c8 | ||
|
fdce8e2f1b | ||
|
5cc6d866ea | ||
|
a545cb375a | ||
|
59b2f1a7a1 | ||
|
81d92e6d7b | ||
|
d41940aada | ||
|
ef22b2c389 | ||
|
94f780c6a7 | ||
|
ae640f48b2 | ||
|
a96aef7edd | ||
|
5e0d6ccb82 | ||
|
781cdb3848 | ||
|
8544a6b2e8 | ||
|
09457d6cb7 | ||
|
d9920fefdc | ||
|
749baf1bdf | ||
|
cd03d2991f | ||
|
f956943f45 | ||
|
a6e6758333 | ||
|
9aac41cf69 | ||
|
720a3b1f45 | ||
|
1b949b538a | ||
|
12409ababb | ||
|
a1da63984b | ||
|
37a5b7ce28 | ||
|
b84bfe2364 | ||
|
a5d9bea0ab | ||
|
fd01fadf2c | ||
|
f69088ea4b | ||
|
727f210dbb | ||
|
f1af0a739a | ||
|
b15a330ab8 | ||
|
0cc2ff62c4 | ||
|
0f9a82b425 | ||
|
1fa2aea45a | ||
|
d4387e8088 | ||
|
ddd34cc584 | ||
|
c0782cee6a | ||
|
ebd50a5b60 | ||
|
f36bd51414 | ||
|
b59ef4bfc9 | ||
|
b7832ce67c | ||
|
549ab3e666 | ||
|
5a21c5d738 | ||
|
3ec1ddcc41 | ||
|
e41e5c262b | ||
|
f4e992ff99 | ||
|
ed2aabd0b1 | ||
|
a76902df77 | ||
|
d0f508849c | ||
|
8daa2d1b63 | ||
|
772cc8cb85 | ||
|
c67da65cd8 | ||
|
6db4fd0737 | ||
|
321b08c2ae | ||
|
7cf03886b7 | ||
|
1cec66d6fe | ||
|
5154329bb2 |
17
.github/workflows/tests.yaml
vendored
17
.github/workflows/tests.yaml
vendored
@ -7,7 +7,7 @@ on:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
VERSION: 3.5.0.1
|
||||
VERSION: 3.6.1.1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -38,18 +38,6 @@ jobs:
|
||||
dotnet-version: '8.0.x'
|
||||
include-prerelease: true
|
||||
|
||||
- name: Setup .NET Core SDK 7.0.x
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '7.0.x'
|
||||
include-prerelease: true
|
||||
|
||||
- name: Setup .NET Core SDK 6.0.x
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
include-prerelease: true
|
||||
|
||||
- name: Test
|
||||
run: dotnet test /p:EnableWindowsTargeting=true --filter "Category!=FlakeyNetwork"
|
||||
|
||||
@ -84,6 +72,7 @@ jobs:
|
||||
- Wabbajack.Downloaders.Mega
|
||||
- Wabbajack.Downloaders.ModDB
|
||||
- Wabbajack.Downloaders.Nexus
|
||||
- Wabbajack.Downloaders.VerificationCache
|
||||
- Wabbajack.Downloaders.WabbajackCDN
|
||||
- Wabbajack.DTOs
|
||||
- Wabbajack.FileExtractor
|
||||
|
42
CHANGELOG.md
42
CHANGELOG.md
@ -1,5 +1,47 @@
|
||||
### Changelog
|
||||
|
||||
#### Version - 3.7.2.0 - 8/25/2024
|
||||
* Added a new button to the installer configuration window for verifying installs. This runs the same code as the verify CLI command, now it's in the UI for easier access. The output of this command
|
||||
is written to a `.html` file and opened in the default browser.
|
||||
* When a modlist install fails due to one or more missing non-nexus files, the installer will now write a `.html` file with all the links and instructions, and open it with the default browser. This data was
|
||||
previsoously only written to the log file.
|
||||
|
||||
#### Version - 3.7.1.1 - 8/13/2024
|
||||
* HOTFIX: buggy release pipeline caused some corruption in the files of 3.7.1.0
|
||||
|
||||
#### Version - 3.7.1.0 - 8/13/2024
|
||||
* Fixed file paths with special characters corrupting when packed into BSAs
|
||||
* This issue only affected Fallout 3, Fallout NV and Skyrim LE
|
||||
* Added logging to determine which downloaded files cannot be hashed
|
||||
* This could occur in the downloading phase when installing a modlist when there are broken/corrupted files in the downloads folder
|
||||
* Fixed Wabbajack crashing when double-clicking the browser window titlebar or URL
|
||||
* Fixed Wabbajack always using explorer.exe instead of the default file browser
|
||||
|
||||
#### Version - 3.7.0.0 - 6/21/2024
|
||||
* Added Starfield support
|
||||
* Note: Hashes were added earlier, but the earlier version was not fully compatible due to Wabbajack extracting the BA2 archives incorrectly. This has been fixed.
|
||||
* Updated GameFinder dependency
|
||||
* Updated WebView dependency
|
||||
* Updated other dependencies
|
||||
|
||||
#### Version - 3.6.1.1 - 5/30/2024
|
||||
* Fixed `set-nexus-api-key` CLI command
|
||||
* Fixed other issues related to OAuth
|
||||
|
||||
#### Version - 3.6.1.0 - 5/26/2024
|
||||
* Fixed a race condition on renewing Nexus Mods OAuth2 tokens
|
||||
* Added `set-nexus-api-key` CLI command
|
||||
* Added Starfield meta data
|
||||
* Added Fallout New Vegas Epic Games metadata
|
||||
|
||||
#### Version - 3.6.0.0 - 5/25/2024
|
||||
* Wabbajack now uses OAuth2 for Nexus Mods logins
|
||||
* Support for DirectURL use with LL files
|
||||
|
||||
#### Version - 3.5.0.2 - 5/21/2024
|
||||
* *HOTFIX* - change how we log into Nexus Mods. We still need to rewrite this on
|
||||
Oauth2, but this should fix the current issues we have, and get people back up and running
|
||||
|
||||
#### Version - 3.5.0.1 - 1/15/2024
|
||||
* *HOTFIX* - change the cache file names so files will be auto-rehashed
|
||||
|
||||
|
@ -19,7 +19,7 @@ namespace Wabbajack.LoginManagers;
|
||||
public class NexusLoginManager : ViewModel, ILoginFor<NexusDownloader>
|
||||
{
|
||||
private readonly ILogger<NexusLoginManager> _logger;
|
||||
private readonly ITokenProvider<NexusApiState> _token;
|
||||
private readonly ITokenProvider<NexusOAuthState> _token;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public string SiteName { get; } = "Nexus Mods";
|
||||
@ -35,7 +35,7 @@ public class NexusLoginManager : ViewModel, ILoginFor<NexusDownloader>
|
||||
[Reactive]
|
||||
public bool HaveLogin { get; set; }
|
||||
|
||||
public NexusLoginManager(ILogger<NexusLoginManager> logger, ITokenProvider<NexusApiState> token, IServiceProvider serviceProvider)
|
||||
public NexusLoginManager(ILogger<NexusLoginManager> logger, ITokenProvider<NexusOAuthState> token, IServiceProvider serviceProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_token = token;
|
||||
|
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>bin\Release\Publish</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
</PropertyGroup>
|
||||
</Project>
|
@ -1,92 +1,143 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Fizzler.Systems.HtmlAgilityPack;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Wabbajack.DTOs.Logins;
|
||||
using Wabbajack.DTOs.OAuth;
|
||||
using Wabbajack.Messages;
|
||||
using Wabbajack.Models;
|
||||
using Wabbajack.Networking.Http.Interfaces;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
using Cookie = Wabbajack.DTOs.Logins.Cookie;
|
||||
|
||||
namespace Wabbajack.UserIntervention;
|
||||
|
||||
public class NexusLoginHandler : BrowserWindowViewModel
|
||||
{
|
||||
private readonly EncryptedJsonTokenProvider<NexusApiState> _tokenProvider;
|
||||
private static Uri OAuthUrl = new Uri("https://users.nexusmods.com/oauth");
|
||||
private static string OAuthRedirectUrl = "https://127.0.0.1:1234";
|
||||
private static string OAuthClientId = "wabbajack";
|
||||
|
||||
private readonly EncryptedJsonTokenProvider<NexusOAuthState> _tokenProvider;
|
||||
private readonly ILogger<NexusLoginHandler> _logger;
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public NexusLoginHandler(EncryptedJsonTokenProvider<NexusApiState> tokenProvider)
|
||||
public NexusLoginHandler(ILogger<NexusLoginHandler> logger, HttpClient client, EncryptedJsonTokenProvider<NexusOAuthState> tokenProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_client = client;
|
||||
HeaderText = "Nexus Login";
|
||||
_tokenProvider = tokenProvider;
|
||||
}
|
||||
|
||||
private string Base64Id()
|
||||
{
|
||||
var bytes = new byte[32];
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
rng.GetBytes(bytes);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
|
||||
protected override async Task Run(CancellationToken token)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
// see https://www.rfc-editor.org/rfc/rfc7636#section-4.1
|
||||
var codeVerifier = Guid.NewGuid().ToString("N").ToBase64();
|
||||
|
||||
// see https://www.rfc-editor.org/rfc/rfc7636#section-4.2
|
||||
var codeChallengeBytes = SHA256.HashData(Encoding.UTF8.GetBytes(codeVerifier));
|
||||
var codeChallenge = StringBase64Extensions.Base64UrlEncode(codeChallengeBytes);
|
||||
|
||||
|
||||
Instructions = "Please log into the Nexus";
|
||||
|
||||
await NavigateTo(new Uri(
|
||||
"https://users.nexusmods.com/auth/continue?client_id=nexus&redirect_uri=https://www.nexusmods.com/oauth/callback&response_type=code&referrer=//www.nexusmods.com"));
|
||||
|
||||
|
||||
Cookie[] cookies = { };
|
||||
while (true)
|
||||
var state = Guid.NewGuid().ToString();
|
||||
|
||||
await NavigateTo(new Uri("https://nexusmods.com"));
|
||||
var codeCompletionSource = new TaskCompletionSource<Dictionary<string, StringValues>>();
|
||||
|
||||
Browser!.Browser.CoreWebView2.NewWindowRequested += (sender, args) =>
|
||||
{
|
||||
cookies = await GetCookies("nexusmods.com", token);
|
||||
if (cookies.Any(c => c.Name == "member_id"))
|
||||
break;
|
||||
var uri = new Uri(args.Uri);
|
||||
_logger.LogInformation("New Window Requested {Uri}", args.Uri);
|
||||
if (uri.Host != "127.0.0.1") return;
|
||||
|
||||
codeCompletionSource.TrySetResult(QueryHelpers.ParseQuery(uri.Query));
|
||||
args.Handled = true;
|
||||
};
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
await Task.Delay(500, token);
|
||||
var uri = GenerateAuthorizeUrl(codeChallenge, state);
|
||||
await NavigateTo(uri);
|
||||
|
||||
var ctx = await codeCompletionSource.Task;
|
||||
|
||||
if (ctx["state"].FirstOrDefault() != state)
|
||||
{
|
||||
throw new Exception("State mismatch");
|
||||
}
|
||||
|
||||
var code = ctx["code"].FirstOrDefault();
|
||||
|
||||
Instructions = "Getting API Key...";
|
||||
var result = await AuthorizeToken(codeVerifier, code, token);
|
||||
|
||||
if (result != null)
|
||||
result.ReceivedAt = DateTime.UtcNow.ToFileTimeUtc();
|
||||
|
||||
await NavigateTo(new Uri("https://next.nexusmods.com/settings/api-keys"));
|
||||
|
||||
var key = "";
|
||||
|
||||
while (true)
|
||||
await _tokenProvider.SetToken(new NexusOAuthState()
|
||||
{
|
||||
try
|
||||
{
|
||||
key = (await GetDom(token)).DocumentNode.QuerySelectorAll("img[alt='Wabbajack']").SelectMany(p => p.ParentNode.ParentNode.QuerySelectorAll("input[aria-label='api key']")).Select(node => node.Attributes["value"]).FirstOrDefault()?.Value;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
break;
|
||||
|
||||
try
|
||||
{
|
||||
await EvaluateJavaScript(
|
||||
"var found = document.querySelector(\"img[alt='Wabbajack']\").parentElement.parentElement.querySelector(\"button[aria-label='Request Api Key']\");" +
|
||||
"found.onclick= function() {return true;};" +
|
||||
"found.class = \" \"; " +
|
||||
"found.click();"
|
||||
);
|
||||
Instructions = "Generating API Key, Please Wait...";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
await Task.Delay(500, token);
|
||||
}
|
||||
|
||||
Instructions = "Success, saving information...";
|
||||
await _tokenProvider.SetToken(new NexusApiState
|
||||
{
|
||||
Cookies = cookies,
|
||||
ApiKey = key
|
||||
OAuth = result!
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<JwtTokenReply?> AuthorizeToken(string verifier, string code, CancellationToken cancel)
|
||||
{
|
||||
var request = new Dictionary<string, string> {
|
||||
{ "grant_type", "authorization_code" },
|
||||
{ "client_id", OAuthClientId },
|
||||
{ "redirect_uri", OAuthRedirectUrl },
|
||||
{ "code", code },
|
||||
{ "code_verifier", verifier },
|
||||
};
|
||||
|
||||
var content = new FormUrlEncodedContent(request);
|
||||
|
||||
var response = await _client.PostAsync($"{OAuthUrl}/token", content, cancel);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogCritical("Failed to get token {code} - {message}", response.StatusCode,
|
||||
response.ReasonPhrase);
|
||||
return null;
|
||||
}
|
||||
var responseString = await response.Content.ReadAsStringAsync(cancel);
|
||||
return JsonSerializer.Deserialize<JwtTokenReply>(responseString);
|
||||
}
|
||||
|
||||
internal static Uri GenerateAuthorizeUrl(string challenge, string state)
|
||||
{
|
||||
var request = new Dictionary<string, string>
|
||||
{
|
||||
{ "response_type", "code" },
|
||||
{ "scope", "public openid profile" },
|
||||
{ "code_challenge_method", "S256" },
|
||||
{ "client_id", OAuthClientId },
|
||||
{ "redirect_uri", OAuthRedirectUrl },
|
||||
{ "code_challenge", challenge },
|
||||
{ "state", state },
|
||||
};
|
||||
|
||||
return new Uri(QueryHelpers.AddQueryString($"{OAuthUrl}/authorize", request));
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ using System.Net.Http.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.Common;
|
||||
@ -103,4 +104,5 @@ public abstract class OAuth2LoginHandler<TLoginType> : BrowserWindowViewModel
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
126
Wabbajack.App.Wpf/UserIntervention/StringExtensions.cs
Normal file
126
Wabbajack.App.Wpf/UserIntervention/StringExtensions.cs
Normal file
@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Wabbajack.UserIntervention;
|
||||
|
||||
public static class StringBase64Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert string to base 64 encoding
|
||||
/// </summary>
|
||||
public static string ToBase64(this string input)
|
||||
{
|
||||
return ToBase64(Encoding.UTF8.GetBytes(input));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert byte array to base 64 encoding
|
||||
/// </summary>
|
||||
public static string ToBase64(this byte[] input)
|
||||
{
|
||||
return Convert.ToBase64String(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes <paramref name="input"/> using base64url encoding.
|
||||
/// </summary>
|
||||
/// <param name="input">The binary input to encode.</param>
|
||||
/// <returns>The base64url-encoded form of <paramref name="input"/>.</returns>
|
||||
[SkipLocalsInit]
|
||||
public static string Base64UrlEncode(ReadOnlySpan<byte> input)
|
||||
{
|
||||
// TODO: use Microsoft.AspNetCore.WebUtilities when .NET 8 is available
|
||||
// Source: https://github.com/dotnet/aspnetcore/blob/main/src/Shared/WebEncoders/WebEncoders.cs
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) .NET Foundation and Contributors
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
const int stackAllocThreshold = 128;
|
||||
|
||||
if (input.IsEmpty)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var bufferSize = GetArraySizeRequiredToEncode(input.Length);
|
||||
|
||||
char[]? bufferToReturnToPool = null;
|
||||
var buffer = bufferSize <= stackAllocThreshold
|
||||
? stackalloc char[stackAllocThreshold]
|
||||
: bufferToReturnToPool = ArrayPool<char>.Shared.Rent(bufferSize);
|
||||
|
||||
var numBase64Chars = Base64UrlEncode(input, buffer);
|
||||
var base64Url = new string(buffer[..numBase64Chars]);
|
||||
|
||||
if (bufferToReturnToPool != null)
|
||||
{
|
||||
ArrayPool<char>.Shared.Return(bufferToReturnToPool);
|
||||
}
|
||||
|
||||
return base64Url;
|
||||
}
|
||||
|
||||
|
||||
private static int Base64UrlEncode(ReadOnlySpan<byte> input, Span<char> output)
|
||||
{
|
||||
Debug.Assert(output.Length >= GetArraySizeRequiredToEncode(input.Length));
|
||||
|
||||
if (input.IsEmpty)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Use base64url encoding with no padding characters. See RFC 4648, Sec. 5.
|
||||
|
||||
Convert.TryToBase64Chars(input, output, out int charsWritten);
|
||||
|
||||
// Fix up '+' -> '-' and '/' -> '_'. Drop padding characters.
|
||||
for (var i = 0; i < charsWritten; i++)
|
||||
{
|
||||
var ch = output[i];
|
||||
switch (ch)
|
||||
{
|
||||
case '+':
|
||||
output[i] = '-';
|
||||
break;
|
||||
case '/':
|
||||
output[i] = '_';
|
||||
break;
|
||||
case '=':
|
||||
// We've reached a padding character; truncate the remainder.
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return charsWritten;
|
||||
}
|
||||
|
||||
private static int GetArraySizeRequiredToEncode(int count)
|
||||
{
|
||||
var numWholeOrPartialInputBlocks = checked(count + 2) / 3;
|
||||
return checked(numWholeOrPartialInputBlocks * 4);
|
||||
}
|
||||
}
|
@ -159,7 +159,6 @@ namespace Wabbajack
|
||||
Filters.Connect().QueryWhenChanged(),
|
||||
resultSelector: (target, type, checkOption, query) =>
|
||||
{
|
||||
Console.WriteLine("fff");
|
||||
switch (type)
|
||||
{
|
||||
case PathTypeOptions.Either:
|
||||
|
@ -56,7 +56,6 @@ namespace Wabbajack
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void OpenWebsite(Uri url)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("cmd.exe", $"/c start {url}")
|
||||
@ -64,16 +63,23 @@ namespace Wabbajack
|
||||
CreateNoWindow = true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static void OpenFolder(AbsolutePath path)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(KnownFolders.Windows.Combine("explorer.exe").ToString(), path.ToString())
|
||||
string folderPath = path.ToString();
|
||||
if (!folderPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
folderPath += Path.DirectorySeparatorChar.ToString();
|
||||
}
|
||||
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = folderPath,
|
||||
UseShellExecute = true,
|
||||
Verb = "open"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static AbsolutePath OpenFileDialog(string filter, string initialDirectory = null)
|
||||
{
|
||||
OpenFileDialog ofd = new OpenFileDialog();
|
||||
|
@ -35,6 +35,9 @@ using Wabbajack.Paths.IO;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
using Wabbajack.Util;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Wabbajack.CLI.Verbs;
|
||||
using Wabbajack.VFS;
|
||||
|
||||
namespace Wabbajack;
|
||||
|
||||
@ -151,6 +154,8 @@ public class InstallerVM : BackNavigatingVM, IBackNavigatingVM, ICpuStatusVM
|
||||
public ReactiveCommand<Unit, Unit> GoToInstallCommand { get; }
|
||||
public ReactiveCommand<Unit, Unit> BeginCommand { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> VerifyCommand { get; }
|
||||
|
||||
public InstallerVM(ILogger<InstallerVM> logger, DTOSerializer dtos, SettingsManager settingsManager, IServiceProvider serviceProvider,
|
||||
SystemParametersConstructor parametersConstructor, IGameLocator gameLocator, LogStream loggerProvider, ResourceMonitor resourceMonitor,
|
||||
Wabbajack.Services.OSIntegrated.Configuration configuration, HttpClient client, DownloadDispatcher dispatcher, IEnumerable<INeedsLogin> logins,
|
||||
@ -175,6 +180,8 @@ public class InstallerVM : BackNavigatingVM, IBackNavigatingVM, ICpuStatusVM
|
||||
BackCommand = ReactiveCommand.Create(() => NavigateToGlobal.Send(NavigateToGlobal.ScreenType.ModeSelectionView));
|
||||
|
||||
BeginCommand = ReactiveCommand.Create(() => BeginInstall().FireAndForget());
|
||||
|
||||
VerifyCommand = ReactiveCommand.Create(() => Verify().FireAndForget());
|
||||
|
||||
OpenReadmeCommand = ReactiveCommand.Create(() =>
|
||||
{
|
||||
@ -455,6 +462,37 @@ public class InstallerVM : BackNavigatingVM, IBackNavigatingVM, ICpuStatusVM
|
||||
Installer.Location.TargetPath = prev;
|
||||
}
|
||||
|
||||
private async Task Verify()
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
InstallState = InstallState.Installing;
|
||||
|
||||
StatusText = $"Verifying {ModList.Name}";
|
||||
|
||||
|
||||
var cmd = new VerifyModlistInstall(_serviceProvider.GetRequiredService<ILogger<VerifyModlistInstall>>(), _dtos,
|
||||
_serviceProvider.GetRequiredService<IResource<FileHashCache>>(),
|
||||
_serviceProvider.GetRequiredService<TemporaryFileManager>());
|
||||
|
||||
var result = await cmd.Run(ModListLocation.TargetPath, Installer.Location.TargetPath, _cancellationToken);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
TaskBarUpdate.Send($"Error during verification of {ModList.Name}", TaskbarItemProgressState.Error);
|
||||
InstallState = InstallState.Failure;
|
||||
StatusText = $"Error during install of {ModList.Name}";
|
||||
StatusProgress = Percent.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskBarUpdate.Send($"Finished verification of {ModList.Name}", TaskbarItemProgressState.Normal);
|
||||
InstallState = InstallState.Success;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private async Task BeginInstall()
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
|
@ -77,7 +77,7 @@ namespace Wabbajack
|
||||
|
||||
public void AfterInstallNavigation()
|
||||
{
|
||||
Process.Start("explorer.exe", Location.TargetPath.ToString());
|
||||
UIUtils.OpenFolder(Location.TargetPath);
|
||||
}
|
||||
|
||||
public async Task<bool> Install()
|
||||
|
@ -25,7 +25,6 @@ public partial class BrowserWindow : MetroWindow
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
_disposable = new CompositeDisposable();
|
||||
_serviceProvider = serviceProvider;
|
||||
Browser = _serviceProvider.GetRequiredService<WebView2>();
|
||||
@ -43,7 +42,10 @@ public partial class BrowserWindow : MetroWindow
|
||||
|
||||
private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
base.DragMove();
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
base.DragMove();
|
||||
}
|
||||
}
|
||||
|
||||
private void BrowserWindow_OnActivated(object sender, EventArgs e)
|
||||
|
@ -82,6 +82,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<local:BeginButton Grid.Row="1"
|
||||
x:Name="BeginButton"
|
||||
@ -104,12 +105,12 @@
|
||||
Foreground="{StaticResource WarningBrush}"
|
||||
Kind="ExclamationTriangleSolid" />
|
||||
<CheckBox Grid.Row="2" Grid.Column="2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
x:Name="OverwriteCheckBox"
|
||||
Content="Overwrite Installation"
|
||||
IsChecked="False"
|
||||
ToolTip="Confirm to overwrite files in install folder.">
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
x:Name="OverwriteCheckBox"
|
||||
Content="Overwrite Installation"
|
||||
IsChecked="False"
|
||||
ToolTip="Confirm to overwrite files in install folder.">
|
||||
<CheckBox.Style>
|
||||
<Style TargetType="CheckBox">
|
||||
<Setter Property="Opacity" Value="0.6" />
|
||||
@ -121,6 +122,13 @@
|
||||
</Style>
|
||||
</CheckBox.Style>
|
||||
</CheckBox>
|
||||
<Button Grid.Row="3" Grid.Column="2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
x:Name="VerifyButton"
|
||||
Content="Verify Installation"
|
||||
ToolTip="Scan the output folders, creating a report on any corrupt or missing files.">
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</rxui:ReactiveUserControl>
|
||||
|
@ -39,6 +39,9 @@ namespace Wabbajack
|
||||
this.WhenAny(x => x.ViewModel.BeginCommand)
|
||||
.BindToStrict(this, x => x.BeginButton.Command)
|
||||
.DisposeWith(dispose);
|
||||
this.WhenAny(x => x.ViewModel.VerifyCommand)
|
||||
.BindToStrict(this, x => x.VerifyButton.Command)
|
||||
.DisposeWith(dispose);
|
||||
this.BindStrict(ViewModel, vm => vm.OverwriteFiles, x => x.OverwriteCheckBox.IsChecked)
|
||||
.DisposeWith(dispose);
|
||||
|
||||
@ -48,6 +51,11 @@ namespace Wabbajack
|
||||
.Select(v => !v.Failed)
|
||||
.BindToStrict(this, view => view.BeginButton.IsEnabled)
|
||||
.DisposeWith(dispose);
|
||||
|
||||
this.WhenAnyValue(x => x.ViewModel.ErrorState)
|
||||
.Select(v => !v.Failed)
|
||||
.BindToStrict(this, view => view.VerifyButton.IsEnabled)
|
||||
.DisposeWith(dispose);
|
||||
|
||||
this.WhenAnyValue(x => x.ViewModel.ErrorState)
|
||||
.Select(v => v.Reason)
|
||||
|
@ -144,7 +144,10 @@ namespace Wabbajack
|
||||
|
||||
private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
this.DragMove();
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
this.DragMove();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -90,21 +90,23 @@
|
||||
<PackageReference Include="MahApps.Metro" Version="2.4.10" />
|
||||
<PackageReference Include="MahApps.Metro.IconPacks" Version="4.11.0" />
|
||||
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2151.40" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2535.41" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.5" />
|
||||
<PackageReference Include="Orc.FileAssociation" Version="5.0.0-alpha0061" />
|
||||
<PackageReference Include="Orc.FileAssociation" Version="5.0.0" />
|
||||
<PackageReference Include="PInvoke.User32" Version="0.7.124" />
|
||||
<PackageReference Include="ReactiveUI" Version="19.5.1" />
|
||||
<PackageReference Include="ReactiveUI.Fody" Version="19.5.1" />
|
||||
<PackageReference Include="ReactiveUI.WPF" Version="19.5.1" />
|
||||
<PackageReference Include="Silk.NET.DXGI" Version="2.18.0" />
|
||||
<PackageReference Include="Silk.NET.DXGI" Version="2.21.0" />
|
||||
<PackageReference Include="System.Reactive" Version="6.0.1-preview.1" />
|
||||
<PackageReference Include="WPFThemes.DarkBlend" Version="1.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wabbajack.CLI.Builder\Wabbajack.CLI.Builder.csproj" />
|
||||
<ProjectReference Include="..\Wabbajack.CLI\Wabbajack.CLI.csproj" />
|
||||
<ProjectReference Include="..\Wabbajack.Services.OSIntegrated\Wabbajack.Services.OSIntegrated.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>bin\Release\net8.0\publish\win-x64\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
</Project>
|
7
Wabbajack.CLI/Properties/launchSettings.json
Normal file
7
Wabbajack.CLI/Properties/launchSettings.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Wabbajack.CLI": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
@ -43,6 +43,8 @@ CommandLineBuilder.RegisterCommand<MirrorFile>(MirrorFile.Definition, c => ((Mir
|
||||
services.AddSingleton<MirrorFile>();
|
||||
CommandLineBuilder.RegisterCommand<ModlistReport>(ModlistReport.Definition, c => ((ModlistReport)c).Run);
|
||||
services.AddSingleton<ModlistReport>();
|
||||
CommandLineBuilder.RegisterCommand<SetNexusApiKey>(SetNexusApiKey.Definition, c => ((SetNexusApiKey)c).Run);
|
||||
services.AddSingleton<SetNexusApiKey>();
|
||||
CommandLineBuilder.RegisterCommand<SteamDownloadFile>(SteamDownloadFile.Definition, c => ((SteamDownloadFile)c).Run);
|
||||
services.AddSingleton<SteamDownloadFile>();
|
||||
CommandLineBuilder.RegisterCommand<SteamDumpAppInfo>(SteamDumpAppInfo.Definition, c => ((SteamDumpAppInfo)c).Run);
|
||||
|
40
Wabbajack.CLI/Verbs/SetNexusApiKey.cs
Normal file
40
Wabbajack.CLI/Verbs/SetNexusApiKey.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Wabbajack.CLI.Builder;
|
||||
using Wabbajack.DTOs.Logins;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
|
||||
namespace Wabbajack.CLI.Verbs;
|
||||
|
||||
public class SetNexusApiKey
|
||||
{
|
||||
private readonly EncryptedJsonTokenProvider<NexusOAuthState> _tokenProvider;
|
||||
private readonly ILogger<SetNexusApiKey> _logger;
|
||||
|
||||
public SetNexusApiKey(EncryptedJsonTokenProvider<NexusOAuthState> tokenProvider, ILogger<SetNexusApiKey> logger)
|
||||
{
|
||||
_tokenProvider = tokenProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public static VerbDefinition Definition = new("set-nexus-api-key",
|
||||
"Sets the Nexus API key to the specified value",
|
||||
[
|
||||
new OptionDefinition(typeof(string), "k", "key", "The Nexus API key")
|
||||
]);
|
||||
|
||||
public async Task<int> Run(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
_logger.LogInformation("Not setting Nexus API key, that looks like an empty string to me.");
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
await _tokenProvider.SetToken(new() { ApiKey = key });
|
||||
_logger.LogInformation("Set Nexus API Key to {key}", key);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -611,7 +611,9 @@ public class ValidateLists
|
||||
return (ArchiveStatus.InValid, archive);
|
||||
}
|
||||
|
||||
if (archive.State is Http http && http.Url.Host.EndsWith("github.com"))
|
||||
if (archive.State is Http http && (http.Url.Host.EndsWith("github.com")
|
||||
//TODO: Find a better solution for the list validation of LoversLab files.
|
||||
|| http.Url.Host.EndsWith("loverslab.com")))
|
||||
return (ArchiveStatus.Valid, archive);
|
||||
|
||||
try
|
||||
|
@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -26,11 +28,12 @@ public class VerifyModlistInstall
|
||||
private readonly DTOSerializer _dtos;
|
||||
private readonly ILogger<VerifyModlistInstall> _logger;
|
||||
|
||||
public VerifyModlistInstall(ILogger<VerifyModlistInstall> logger, DTOSerializer dtos, IResource<FileHashCache> limiter)
|
||||
public VerifyModlistInstall(ILogger<VerifyModlistInstall> logger, DTOSerializer dtos, IResource<FileHashCache> limiter, TemporaryFileManager temporaryFileManager)
|
||||
{
|
||||
_limiter = limiter;
|
||||
_logger = logger;
|
||||
_dtos = dtos;
|
||||
_temporaryFileManager = temporaryFileManager;
|
||||
}
|
||||
|
||||
public static VerbDefinition Definition = new("verify-modlist-install", "Verify a modlist installed correctly",
|
||||
@ -42,6 +45,7 @@ public class VerifyModlistInstall
|
||||
});
|
||||
|
||||
private readonly IResource<FileHashCache> _limiter;
|
||||
private readonly TemporaryFileManager _temporaryFileManager;
|
||||
|
||||
|
||||
public async Task<int> Run(AbsolutePath modlistLocation, AbsolutePath installFolder, CancellationToken token)
|
||||
@ -52,7 +56,9 @@ public class VerifyModlistInstall
|
||||
_logger.LogInformation("Indexing files");
|
||||
var byTo = list.Directives.ToDictionary(d => d.To);
|
||||
|
||||
var reportFile = _temporaryFileManager.CreateFile(Ext.Html);
|
||||
|
||||
|
||||
_logger.LogInformation("Scanning files");
|
||||
var errors = await list.Directives.PMapAllBatchedAsync(_limiter, async directive =>
|
||||
{
|
||||
@ -68,7 +74,7 @@ public class VerifyModlistInstall
|
||||
return new Result
|
||||
{
|
||||
Path = directive.To,
|
||||
Message = $"File does not exist directive {directive.GetType()}"
|
||||
Message = $"File does not exist"
|
||||
};
|
||||
}
|
||||
|
||||
@ -112,11 +118,30 @@ public class VerifyModlistInstall
|
||||
_logger.LogInformation("Found {Count} errors", errors.Count);
|
||||
|
||||
|
||||
foreach (var error in errors)
|
||||
|
||||
{
|
||||
_logger.LogError("{File} | {Message}", error.Path, error.Message);
|
||||
await using var stream = new StreamWriter(reportFile.Path.Open(FileMode.Create, FileAccess.Write));
|
||||
await stream.WriteLineAsync(
|
||||
"<html><head></head><body>");
|
||||
await stream.WriteLineAsync($"<h1>Verification Report for {modlistLocation}</h1>");
|
||||
|
||||
foreach (var error in errors)
|
||||
{
|
||||
if (error == null) continue;
|
||||
await stream.WriteLineAsync($"<div class=\"error\">{error.Message} | {error.Path}</div>");
|
||||
_logger.LogError("{File} | {Message}", error.Path, error.Message);
|
||||
}
|
||||
|
||||
await stream.WriteLineAsync("</body></html>");
|
||||
}
|
||||
|
||||
_logger.LogInformation("Report written to {Report}", reportFile.Path);
|
||||
|
||||
Process.Start(new ProcessStartInfo("cmd.exe", $"/c start {reportFile}")
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
});
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -18,13 +18,13 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="NLog" Version="5.2.5" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.5" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
<PackageReference Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1" />
|
||||
</ItemGroup>
|
||||
|
@ -14,13 +14,13 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -55,6 +55,7 @@ public class CompressionTests
|
||||
{
|
||||
if (name == "tes4.bsa") return; // not sure why is is failing
|
||||
|
||||
|
||||
var reader = await BSADispatch.Open(path);
|
||||
|
||||
var dataStates = await reader.Files
|
||||
@ -78,7 +79,6 @@ public class CompressionTests
|
||||
|
||||
var rebuiltStream = new MemoryStream();
|
||||
await build.Build(rebuiltStream, CancellationToken.None);
|
||||
rebuiltStream.Position = 0;
|
||||
|
||||
var reader2 = await BSADispatch.Open(new MemoryStreamFactory(rebuiltStream, path, path.LastModifiedUtc()));
|
||||
await reader.Files.Zip(reader2.Files)
|
||||
|
@ -12,15 +12,15 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="Xunit.DependencyInjection" Version="8.9.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="Xunit.DependencyInjection" Version="9.3.0" />
|
||||
<PackageReference Include="Xunit.DependencyInjection.Logging" Version="8.1.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -10,7 +10,7 @@ using Wabbajack.DTOs.BSA.ArchiveStates;
|
||||
using Wabbajack.DTOs.BSA.FileStates;
|
||||
using Wabbajack.Paths.IO;
|
||||
|
||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
||||
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||
|
||||
public class Builder : IBuilder
|
||||
{
|
||||
@ -33,7 +33,7 @@ public class Builder : IBuilder
|
||||
|
||||
break;
|
||||
case BA2EntryType.DX10:
|
||||
var resultdx10 = await DX10FileEntryBuilder.Create((BA2DX10File)state, src, _slab, token);
|
||||
var resultdx10 = await DX10FileEntryBuilder.Create((BA2DX10File)state, src, _slab, _state.Compression == 3, token);
|
||||
lock (_entries)
|
||||
{
|
||||
_entries.Add(resultdx10);
|
||||
@ -59,6 +59,13 @@ public class Builder : IBuilder
|
||||
bw.Write((uint) _entries.Count);
|
||||
var tableOffsetLoc = bw.BaseStream.Position;
|
||||
bw.Write((ulong) 0);
|
||||
if(_state.Version == 2 || _state.Version == 3)
|
||||
{
|
||||
bw.Write(_state.Unknown1);
|
||||
bw.Write(_state.Unknown2);
|
||||
if (_state.Version == 3)
|
||||
bw.Write(_state.Compression);
|
||||
}
|
||||
|
||||
foreach (var entry in _entries) entry.WriteHeader(bw, token);
|
||||
|
93
Wabbajack.Compression.BSA/BA2Archive/ChunkBuilder.cs
Normal file
93
Wabbajack.Compression.BSA/BA2Archive/ChunkBuilder.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||
using K4os.Compression.LZ4;
|
||||
using K4os.Compression.LZ4.Encoders;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.DTOs.BSA.FileStates;
|
||||
using Wabbajack.DTOs.GitHub;
|
||||
|
||||
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||
|
||||
public class ChunkBuilder
|
||||
{
|
||||
private BA2Chunk _chunk;
|
||||
private Stream _dataSlab;
|
||||
private long _offsetOffset;
|
||||
private uint _packSize;
|
||||
|
||||
public static async Task<ChunkBuilder> Create(BA2DX10File state, BA2Chunk chunk, Stream src,
|
||||
DiskSlabAllocator slab, bool useLz4Compression, CancellationToken token)
|
||||
{
|
||||
var builder = new ChunkBuilder {_chunk = chunk};
|
||||
|
||||
if (!chunk.Compressed)
|
||||
{
|
||||
builder._dataSlab = slab.Allocate(chunk.FullSz);
|
||||
await src.CopyToLimitAsync(builder._dataSlab, (int) chunk.FullSz, token);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!useLz4Compression)
|
||||
{
|
||||
var deflater = new Deflater(Deflater.BEST_COMPRESSION);
|
||||
await using var ms = new MemoryStream();
|
||||
await using (var ds = new DeflaterOutputStream(ms, deflater))
|
||||
{
|
||||
ds.IsStreamOwner = false;
|
||||
await src.CopyToLimitAsync(ds, (int)chunk.FullSz, token);
|
||||
}
|
||||
|
||||
builder._dataSlab = slab.Allocate(ms.Length);
|
||||
ms.Position = 0;
|
||||
await ms.CopyToLimitAsync(builder._dataSlab, (int)ms.Length, token);
|
||||
builder._packSize = (uint)ms.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] full = new byte[chunk.FullSz];
|
||||
await using (var copyStream = new MemoryStream())
|
||||
{
|
||||
await src.CopyToLimitAsync(copyStream, (int)chunk.FullSz, token);
|
||||
full = copyStream.ToArray();
|
||||
}
|
||||
var maxOutput = LZ4Codec.MaximumOutputSize((int)chunk.FullSz);
|
||||
byte[] compressed = new byte[maxOutput];
|
||||
int compressedSize = LZ4Codec.Encode(full, 0, full.Length, compressed, 0, compressed.Length, LZ4Level.L12_MAX);
|
||||
var ms = new MemoryStream(compressed, 0, compressedSize);
|
||||
builder._dataSlab = slab.Allocate(compressedSize);
|
||||
ms.Position = 0;
|
||||
await ms.CopyToLimitAsync(builder._dataSlab, compressedSize, token);
|
||||
builder._packSize = (uint)compressedSize;
|
||||
}
|
||||
}
|
||||
|
||||
builder._dataSlab.Position = 0;
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public void WriteHeader(BinaryWriter bw)
|
||||
{
|
||||
_offsetOffset = bw.BaseStream.Position;
|
||||
bw.Write((ulong) 0);
|
||||
bw.Write(_packSize);
|
||||
bw.Write(_chunk.FullSz);
|
||||
bw.Write(_chunk.StartMip);
|
||||
bw.Write(_chunk.EndMip);
|
||||
bw.Write(_chunk.Align);
|
||||
}
|
||||
|
||||
public async ValueTask WriteData(BinaryWriter bw, CancellationToken token)
|
||||
{
|
||||
var pos = bw.BaseStream.Position;
|
||||
bw.BaseStream.Position = _offsetOffset;
|
||||
bw.Write((ulong) pos);
|
||||
bw.BaseStream.Position = pos;
|
||||
await _dataSlab.CopyToLimitAsync(bw.BaseStream, (int) _dataSlab.Length, token);
|
||||
await _dataSlab.DisposeAsync();
|
||||
}
|
||||
}
|
179
Wabbajack.Compression.BSA/BA2Archive/DX10Entry.cs
Normal file
179
Wabbajack.Compression.BSA/BA2Archive/DX10Entry.cs
Normal file
@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DirectXTex;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||
using K4os.Compression.LZ4;
|
||||
using K4os.Compression.LZ4.Streams;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Compression.BSA.BA2Archive;
|
||||
using Wabbajack.DTOs.BSA.FileStates;
|
||||
using Wabbajack.DTOs.Streams;
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||
|
||||
public class DX10Entry : IBA2FileEntry
|
||||
{
|
||||
private readonly Reader _bsa;
|
||||
private ushort _chunkHdrLen;
|
||||
private List<TextureChunk> _chunks;
|
||||
private uint _dirHash;
|
||||
private string _extension;
|
||||
private byte _format;
|
||||
private ushort _height;
|
||||
private int _index;
|
||||
private uint _nameHash;
|
||||
private byte _numChunks;
|
||||
private byte _numMips;
|
||||
private ushort _unk16;
|
||||
private byte _unk8;
|
||||
private ushort _width;
|
||||
private readonly byte _isCubemap;
|
||||
private readonly byte _tileMode;
|
||||
|
||||
public DX10Entry(Reader ba2Reader, int idx)
|
||||
{
|
||||
_bsa = ba2Reader;
|
||||
var _rdr = ba2Reader._rdr;
|
||||
_nameHash = _rdr.ReadUInt32();
|
||||
FullPath = _nameHash.ToString("X");
|
||||
_extension = Encoding.UTF8.GetString(_rdr.ReadBytes(4));
|
||||
_dirHash = _rdr.ReadUInt32();
|
||||
_unk8 = _rdr.ReadByte();
|
||||
_numChunks = _rdr.ReadByte();
|
||||
_chunkHdrLen = _rdr.ReadUInt16();
|
||||
_height = _rdr.ReadUInt16();
|
||||
_width = _rdr.ReadUInt16();
|
||||
_numMips = _rdr.ReadByte();
|
||||
_format = _rdr.ReadByte();
|
||||
_isCubemap = _rdr.ReadByte();
|
||||
_tileMode = _rdr.ReadByte();
|
||||
_index = idx;
|
||||
|
||||
_chunks = Enumerable.Range(0, _numChunks)
|
||||
.Select(_ => new TextureChunk(_rdr))
|
||||
.ToList();
|
||||
}
|
||||
private DirectXTexUtility.TexMetadata? _metadata = null;
|
||||
|
||||
public DirectXTexUtility.TexMetadata Metadata
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_metadata == null)
|
||||
_metadata = DirectXTexUtility.GenerateMetadata(_width, _height, _numMips, (DirectXTexUtility.DXGIFormat)_format, _isCubemap == 1);
|
||||
return (DirectXTexUtility.TexMetadata)_metadata;
|
||||
}
|
||||
}
|
||||
|
||||
private uint _headerSize = 0;
|
||||
public uint HeaderSize
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_headerSize > 0)
|
||||
return _headerSize;
|
||||
uint size = 0;
|
||||
size += (uint)Marshal.SizeOf(DirectXTexUtility.DDSHeader.DDSMagic);
|
||||
size += (uint)Marshal.SizeOf<DirectXTexUtility.DDSHeader>();
|
||||
var pixelFormat = DirectXTexUtility.GetPixelFormat(Metadata);
|
||||
var hasDx10Header = DirectXTexUtility.HasDx10Header(pixelFormat);
|
||||
if (hasDx10Header)
|
||||
size += (uint)Marshal.SizeOf<DirectXTexUtility.DX10Header>();
|
||||
|
||||
return _headerSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
public string FullPath { get; set; }
|
||||
|
||||
public RelativePath Path => FullPath.ToRelativePath();
|
||||
public uint Size => (uint)_chunks.Sum(f => f._fullSz) + HeaderSize;
|
||||
|
||||
public AFile State => new BA2DX10File
|
||||
{
|
||||
Path = Path,
|
||||
NameHash = _nameHash,
|
||||
Extension = _extension,
|
||||
DirHash = _dirHash,
|
||||
Unk8 = _unk8,
|
||||
ChunkHdrLen = _chunkHdrLen,
|
||||
Height = _height,
|
||||
Width = _width,
|
||||
NumMips = _numMips,
|
||||
PixelFormat = _format,
|
||||
IsCubeMap = _isCubemap,
|
||||
TileMode = _tileMode,
|
||||
Index = _index,
|
||||
Chunks = _chunks.Select(ch => new BA2Chunk
|
||||
{
|
||||
FullSz = ch._fullSz,
|
||||
StartMip = ch._startMip,
|
||||
EndMip = ch._endMip,
|
||||
Align = ch._align,
|
||||
Compressed = ch._packSz != 0
|
||||
}).ToArray()
|
||||
};
|
||||
|
||||
public async ValueTask CopyDataTo(Stream output, CancellationToken token)
|
||||
{
|
||||
var bw = new BinaryWriter(output);
|
||||
|
||||
WriteHeader(bw);
|
||||
|
||||
await using var fs = await _bsa._streamFactory.GetStream();
|
||||
using var br = new BinaryReader(fs);
|
||||
foreach (var chunk in _chunks)
|
||||
{
|
||||
var full = new byte[chunk._fullSz];
|
||||
var isCompressed = chunk._packSz != 0;
|
||||
|
||||
br.BaseStream.Seek((long)chunk._offset, SeekOrigin.Begin);
|
||||
|
||||
if (!isCompressed)
|
||||
{
|
||||
await br.BaseStream.ReadAsync(full, token);
|
||||
}
|
||||
else
|
||||
{
|
||||
var compressed = new byte[chunk._packSz];
|
||||
await br.BaseStream.ReadAsync(compressed, token);
|
||||
if (_bsa._compression == 3)
|
||||
{
|
||||
LZ4Codec.PartialDecode(compressed, full);
|
||||
}
|
||||
else
|
||||
{
|
||||
var inflater = new Inflater();
|
||||
inflater.SetInput(compressed);
|
||||
inflater.Inflate(full);
|
||||
}
|
||||
}
|
||||
|
||||
await bw.BaseStream.WriteAsync(full, token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async ValueTask<IStreamFactory> GetStreamFactory(CancellationToken token)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
await CopyDataTo(ms, token);
|
||||
ms.Position = 0;
|
||||
return new MemoryStreamFactory(ms, Path, _bsa._streamFactory.LastModifiedUtc);
|
||||
}
|
||||
|
||||
private void WriteHeader(BinaryWriter bw)
|
||||
{
|
||||
DirectXTexUtility.GenerateDDSHeader(Metadata, DirectXTexUtility.DDSFlags.FORCEDX10EXTMISC2, out var header, out var header10);
|
||||
var headerBytes = DirectXTexUtility.EncodeDDSHeader(header, header10);
|
||||
bw.Write(headerBytes);
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,20 @@
|
||||
using DirectXTex;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Compression.BSA;
|
||||
using Wabbajack.DTOs.BSA.FileStates;
|
||||
using Wabbajack.DTOs.Texture;
|
||||
|
||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
||||
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||
|
||||
public class DX10FileEntryBuilder : IFileBuilder
|
||||
{
|
||||
private List<ChunkBuilder> _chunks;
|
||||
private BA2DX10File _state;
|
||||
private uint _headerSize = 0;
|
||||
|
||||
public uint FileHash => _state.NameHash;
|
||||
public uint DirHash => _state.DirHash;
|
||||
@ -43,20 +45,36 @@ public class DX10FileEntryBuilder : IFileBuilder
|
||||
foreach (var chunk in _chunks)
|
||||
await chunk.WriteData(wtr, token);
|
||||
}
|
||||
public uint GetHeaderSize(BA2DX10File state)
|
||||
{
|
||||
if (_headerSize > 0)
|
||||
return _headerSize;
|
||||
|
||||
public static async Task<DX10FileEntryBuilder> Create(BA2DX10File state, Stream src, DiskSlabAllocator slab,
|
||||
uint size = 0;
|
||||
size += (uint)Marshal.SizeOf(DirectXTexUtility.DDSHeader.DDSMagic);
|
||||
size += (uint)Marshal.SizeOf<DirectXTexUtility.DDSHeader>();
|
||||
var metadata = DirectXTexUtility.GenerateMetadata(state.Width, state.Height, state.NumMips, (DirectXTexUtility.DXGIFormat)state.PixelFormat, state.IsCubeMap == 1);
|
||||
var pixelFormat = DirectXTexUtility.GetPixelFormat(metadata);
|
||||
var hasDx10Header = DirectXTexUtility.HasDx10Header(pixelFormat);
|
||||
if (hasDx10Header)
|
||||
size += (uint)Marshal.SizeOf<DirectXTexUtility.DX10Header>();
|
||||
|
||||
return _headerSize = size;
|
||||
}
|
||||
|
||||
public static async Task<DX10FileEntryBuilder> Create(BA2DX10File state, Stream src, DiskSlabAllocator slab, bool useLz4Compression,
|
||||
CancellationToken token)
|
||||
{
|
||||
var builder = new DX10FileEntryBuilder {_state = state};
|
||||
|
||||
var headerSize = DDS.HeaderSizeForFormat((DXGI_FORMAT) state.PixelFormat) + 4;
|
||||
var headerSize = builder.GetHeaderSize(state);
|
||||
new BinaryReader(src).ReadBytes((int) headerSize);
|
||||
|
||||
// This can't be parallel because it all runs off the same base IO stream.
|
||||
builder._chunks = new List<ChunkBuilder>();
|
||||
|
||||
foreach (var chunk in state.Chunks)
|
||||
builder._chunks.Add(await ChunkBuilder.Create(state, chunk, src, slab, token));
|
||||
builder._chunks.Add(await ChunkBuilder.Create(state, chunk, src, slab, useLz4Compression, token));
|
||||
|
||||
return builder;
|
||||
}
|
@ -9,7 +9,7 @@ using Wabbajack.DTOs.BSA.FileStates;
|
||||
using Wabbajack.DTOs.Streams;
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
||||
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||
|
||||
public class FileEntry : IBA2FileEntry
|
||||
{
|
@ -6,7 +6,7 @@ using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.DTOs.BSA.FileStates;
|
||||
|
||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
||||
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||
|
||||
public class FileEntryBuilder : IFileBuilder
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using Wabbajack.Compression.BSA.Interfaces;
|
||||
|
||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
||||
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||
|
||||
internal interface IBA2FileEntry : IFile
|
||||
{
|
@ -2,7 +2,7 @@ using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
||||
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||
|
||||
internal interface IFileBuilder
|
||||
{
|
@ -7,7 +7,7 @@ using Wabbajack.Compression.BSA.Interfaces;
|
||||
using Wabbajack.DTOs.BSA.ArchiveStates;
|
||||
using Wabbajack.DTOs.Streams;
|
||||
|
||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
||||
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||
|
||||
public class Reader : IReader
|
||||
{
|
||||
@ -15,9 +15,17 @@ public class Reader : IReader
|
||||
internal string _headerMagic;
|
||||
internal ulong _nameTableOffset;
|
||||
internal uint _numFiles;
|
||||
internal uint _unknown1;
|
||||
internal uint _unknown2;
|
||||
internal uint _compression;
|
||||
internal BinaryReader _rdr;
|
||||
public IStreamFactory _streamFactory;
|
||||
internal BA2EntryType _type;
|
||||
|
||||
/// <summary>
|
||||
/// Fallout 4 - Version 1, 7 or 8
|
||||
/// Starfield - Version 2 or 3
|
||||
/// </summary>
|
||||
internal uint _version;
|
||||
|
||||
private Reader(Stream stream)
|
||||
@ -37,7 +45,10 @@ public class Reader : IReader
|
||||
Version = _version,
|
||||
HeaderMagic = _headerMagic,
|
||||
Type = _type,
|
||||
HasNameTable = HasNameTable
|
||||
HasNameTable = HasNameTable,
|
||||
Unknown1 = _unknown1,
|
||||
Unknown2 = _unknown2,
|
||||
Compression = _compression,
|
||||
};
|
||||
|
||||
|
||||
@ -67,6 +78,10 @@ public class Reader : IReader
|
||||
_numFiles = _rdr.ReadUInt32();
|
||||
_nameTableOffset = _rdr.ReadUInt64();
|
||||
|
||||
_unknown1 = (_version == 2 || _version == 3) ? _rdr.ReadUInt32() : 0;
|
||||
_unknown2 = (_version == 2 || _version == 3) ? _rdr.ReadUInt32() : 0;
|
||||
_compression = (_version == 3) ? _rdr.ReadUInt32() : 0;
|
||||
|
||||
var files = new List<IBA2FileEntry>();
|
||||
for (var idx = 0; idx < _numFiles; idx += 1)
|
||||
switch (_type)
|
||||
@ -94,4 +109,4 @@ public class Reader : IReader
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
||||
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||
|
||||
public class TextureChunk
|
||||
{
|
@ -22,7 +22,7 @@ public static class BSADispatch
|
||||
{
|
||||
FileType.TES3 => await Reader.Load(new NativeFileStreamFactory(filename)),
|
||||
FileType.BSA => await TES5Archive.Reader.Load(new NativeFileStreamFactory(filename)),
|
||||
FileType.BA2 => await FO4Archive.Reader.Load(new NativeFileStreamFactory(filename)),
|
||||
FileType.BA2 => await BA2Archive.Reader.Load(new NativeFileStreamFactory(filename)),
|
||||
_ => throw new InvalidDataException("Filename is not a .bsa or .ba2")
|
||||
};
|
||||
}
|
||||
@ -34,7 +34,7 @@ public static class BSADispatch
|
||||
{
|
||||
FileType.TES3 => await Reader.Load(factory),
|
||||
FileType.BSA => await TES5Archive.Reader.Load(factory),
|
||||
FileType.BA2 => await FO4Archive.Reader.Load(factory),
|
||||
FileType.BA2 => await BA2Archive.Reader.Load(factory),
|
||||
_ => throw new InvalidDataException("Filename is not a .bsa or .ba2")
|
||||
};
|
||||
}
|
||||
@ -46,7 +46,7 @@ public static class BSADispatch
|
||||
{
|
||||
FileType.TES3 => await Reader.Load(factory),
|
||||
FileType.BSA => await TES5Archive.Reader.Load(factory),
|
||||
FileType.BA2 => await FO4Archive.Reader.Load(factory),
|
||||
FileType.BA2 => await BA2Archive.Reader.Load(factory),
|
||||
_ => throw new InvalidDataException("Filename is not a .bsa or .ba2")
|
||||
};
|
||||
}
|
||||
@ -57,7 +57,7 @@ public static class BSADispatch
|
||||
{
|
||||
TES3State tes3 => new Builder(tes3),
|
||||
BSAState bsa => TES5Archive.Builder.Create(bsa, manager),
|
||||
BA2State ba2 => FO4Archive.Builder.Create(ba2, manager),
|
||||
BA2State ba2 => BA2Archive.Builder.Create(ba2, manager),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ public static class BinaryHelperExtensions
|
||||
return version switch
|
||||
{
|
||||
VersionType.TES3 => Encoding.ASCII,
|
||||
VersionType.FO3 => Encoding.UTF8,
|
||||
VersionType.SSE => Windows1252,
|
||||
_ => Encoding.UTF7
|
||||
};
|
||||
|
@ -1,215 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Wabbajack.DTOs.Texture;
|
||||
|
||||
namespace Compression.BSA;
|
||||
/*
|
||||
* Copied from https://raw.githubusercontent.com/AlexxEG/BSA_Browser/master/Sharp.BSA.BA2/BA2Util/DDS.cs
|
||||
* which is also GPL3 code. Modified slightly for Wabbajack
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copied from dds.h. Includes (almost) only stuff I need in this project.
|
||||
*
|
||||
* Link: https://github.com/digitalutopia1/BA2Lib/blob/master/BA2Lib/dds.h
|
||||
*
|
||||
*/
|
||||
|
||||
public class DDS
|
||||
{
|
||||
public const int DDS_MAGIC = 0x20534444; // "DDS "
|
||||
|
||||
public const int DDS_FOURCC = 0x00000004; // DDPF_FOURCC
|
||||
public const int DDS_RGB = 0x00000040; // DDPF_RGB
|
||||
public const int DDS_RGBA = 0x00000041; // DDPF_RGB | DDPF_ALPHAPIXELS
|
||||
|
||||
public const int
|
||||
DDS_HEADER_FLAGS_TEXTURE = 0x00001007; // DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
|
||||
|
||||
public const int DDS_HEADER_FLAGS_MIPMAP = 0x00020000; // DDSD_MIPMAPCOUNT
|
||||
public const int DDS_HEADER_FLAGS_LINEARSIZE = 0x00080000; // DDSD_LINEARSIZE
|
||||
|
||||
public const int DDS_SURFACE_FLAGS_TEXTURE = 0x00001000; // DDSCAPS_TEXTURE
|
||||
public const int DDS_SURFACE_FLAGS_MIPMAP = 0x00400008; // DDSCAPS_COMPLEX | DDSCAPS_MIPMAP
|
||||
|
||||
public const int DDS_ALPHA_MODE_UNKNOWN = 0x0;
|
||||
|
||||
public static uint HeaderSizeForFormat(DXGI_FORMAT fmt)
|
||||
{
|
||||
switch (fmt)
|
||||
{
|
||||
case DXGI_FORMAT.BC1_UNORM_SRGB:
|
||||
case DXGI_FORMAT.BC3_UNORM_SRGB:
|
||||
case DXGI_FORMAT.BC4_UNORM:
|
||||
case DXGI_FORMAT.BC5_SNORM:
|
||||
case DXGI_FORMAT.BC6H_UF16:
|
||||
case DXGI_FORMAT.BC7_UNORM:
|
||||
case DXGI_FORMAT.BC7_UNORM_SRGB:
|
||||
return DDS_HEADER_DXT10.Size + DDS_HEADER.Size;
|
||||
default:
|
||||
return DDS_HEADER.Size;
|
||||
}
|
||||
}
|
||||
|
||||
public static uint MAKEFOURCC(char ch0, char ch1, char ch2, char ch3)
|
||||
{
|
||||
// This is alien to me...
|
||||
return (byte) ch0 | ((uint) (byte) ch1 << 8) | ((uint) (byte) ch2 << 16) | ((uint) (byte) ch3 << 24);
|
||||
}
|
||||
}
|
||||
|
||||
public enum DXT10_RESOURCE_DIMENSION
|
||||
{
|
||||
DIMENSION_TEXTURE1D = 2,
|
||||
DIMENSION_TEXTURE2D = 3,
|
||||
DIMENSION_TEXTURE3D = 4
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum DDSCAPS2 : uint
|
||||
{
|
||||
CUBEMAP = 0x200,
|
||||
CUBEMAP_POSITIVEX = 0x400,
|
||||
CUBEMAP_NEGATIVEX = 0x800,
|
||||
CUBEMAP_POSITIVEY = 0x1000,
|
||||
CUBEMAP_NEGATIVEY = 0x2000,
|
||||
CUBEMAP_POSITIVEZ = 0x4000,
|
||||
CUBEMAP_NEGATIVEZ = 0x8000,
|
||||
CUBEMAP_ALLFACES = 0xFC00
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct DDS_HEADER
|
||||
{
|
||||
public uint dwSize;
|
||||
public uint dwHeaderFlags;
|
||||
public uint dwHeight;
|
||||
public uint dwWidth;
|
||||
public uint dwPitchOrLinearSize;
|
||||
public uint dwDepth; // only if DDS_HEADER_FLAGS_VOLUME is set in dwHeaderFlags
|
||||
public uint dwMipMapCount;
|
||||
public uint dwReserved1; // [11]
|
||||
public DDS_PIXELFORMAT PixelFormat; // ddspf
|
||||
public uint dwSurfaceFlags;
|
||||
public uint dwCubemapFlags;
|
||||
public uint dwReserved2; // [3]
|
||||
|
||||
public uint GetSize()
|
||||
{
|
||||
// 9 uint + DDS_PIXELFORMAT uints + 2 uint arrays with 14 uints total
|
||||
// each uint 4 bytes each
|
||||
return 9 * 4 + PixelFormat.GetSize() + 14 * 4;
|
||||
}
|
||||
|
||||
|
||||
public void Write(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(dwSize);
|
||||
bw.Write(dwHeaderFlags);
|
||||
bw.Write(dwHeight);
|
||||
bw.Write(dwWidth);
|
||||
bw.Write(dwPitchOrLinearSize);
|
||||
bw.Write(dwDepth);
|
||||
bw.Write(dwMipMapCount);
|
||||
|
||||
// Just write it multiple times, since it's never assigned a value anyway
|
||||
for (var i = 0; i < 11; i++)
|
||||
bw.Write(dwReserved1);
|
||||
|
||||
// DDS_PIXELFORMAT
|
||||
bw.Write(PixelFormat.dwSize);
|
||||
bw.Write(PixelFormat.dwFlags);
|
||||
bw.Write(PixelFormat.dwFourCC);
|
||||
bw.Write(PixelFormat.dwRGBBitCount);
|
||||
bw.Write(PixelFormat.dwRBitMask);
|
||||
bw.Write(PixelFormat.dwGBitMask);
|
||||
bw.Write(PixelFormat.dwBBitMask);
|
||||
bw.Write(PixelFormat.dwABitMask);
|
||||
|
||||
bw.Write(dwSurfaceFlags);
|
||||
bw.Write(dwCubemapFlags);
|
||||
|
||||
// Just write it multiple times, since it's never assigned a value anyway
|
||||
for (var i = 0; i < 3; i++)
|
||||
bw.Write(dwReserved2);
|
||||
}
|
||||
|
||||
public static uint Size
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return (uint) (sizeof(DDS_HEADER) + sizeof(int) * 10 + sizeof(int) * 2);
|
||||
}
|
||||
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct DDS_HEADER_DXT10
|
||||
{
|
||||
public uint dxgiFormat;
|
||||
public uint resourceDimension;
|
||||
public uint miscFlag;
|
||||
public uint arraySize;
|
||||
public uint miscFlags2;
|
||||
|
||||
public void Write(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(dxgiFormat);
|
||||
bw.Write(resourceDimension);
|
||||
bw.Write(miscFlag);
|
||||
bw.Write(arraySize);
|
||||
bw.Write(miscFlags2);
|
||||
}
|
||||
|
||||
public static uint Size
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return (uint) sizeof(DDS_HEADER_DXT10);
|
||||
}
|
||||
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct DDS_PIXELFORMAT
|
||||
{
|
||||
public uint dwSize;
|
||||
public uint dwFlags;
|
||||
public uint dwFourCC;
|
||||
public uint dwRGBBitCount;
|
||||
public uint dwRBitMask;
|
||||
public uint dwGBitMask;
|
||||
public uint dwBBitMask;
|
||||
public uint dwABitMask;
|
||||
|
||||
public DDS_PIXELFORMAT(uint size, uint flags, uint fourCC, uint rgbBitCount, uint rBitMask, uint gBitMask,
|
||||
uint bBitMask, uint aBitMask)
|
||||
{
|
||||
dwSize = size;
|
||||
dwFlags = flags;
|
||||
dwFourCC = fourCC;
|
||||
dwRGBBitCount = rgbBitCount;
|
||||
dwRBitMask = rBitMask;
|
||||
dwGBitMask = gBitMask;
|
||||
dwBBitMask = bBitMask;
|
||||
dwABitMask = aBitMask;
|
||||
}
|
||||
|
||||
public uint GetSize()
|
||||
{
|
||||
// 8 uints, each 4 bytes each
|
||||
return 8 * 4;
|
||||
}
|
||||
}
|
1116
Wabbajack.Compression.BSA/DirectXTexUtil.cs
Normal file
1116
Wabbajack.Compression.BSA/DirectXTexUtil.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,69 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.DTOs.BSA.FileStates;
|
||||
|
||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
||||
|
||||
public class ChunkBuilder
|
||||
{
|
||||
private BA2Chunk _chunk;
|
||||
private Stream _dataSlab;
|
||||
private long _offsetOffset;
|
||||
private uint _packSize;
|
||||
|
||||
public static async Task<ChunkBuilder> Create(BA2DX10File state, BA2Chunk chunk, Stream src,
|
||||
DiskSlabAllocator slab, CancellationToken token)
|
||||
{
|
||||
var builder = new ChunkBuilder {_chunk = chunk};
|
||||
|
||||
if (!chunk.Compressed)
|
||||
{
|
||||
builder._dataSlab = slab.Allocate(chunk.FullSz);
|
||||
await src.CopyToLimitAsync(builder._dataSlab, (int) chunk.FullSz, token);
|
||||
}
|
||||
else
|
||||
{
|
||||
var deflater = new Deflater(Deflater.BEST_COMPRESSION);
|
||||
await using var ms = new MemoryStream();
|
||||
await using (var ds = new DeflaterOutputStream(ms, deflater))
|
||||
{
|
||||
ds.IsStreamOwner = false;
|
||||
await src.CopyToLimitAsync(ds, (int) chunk.FullSz, token);
|
||||
}
|
||||
|
||||
builder._dataSlab = slab.Allocate(ms.Length);
|
||||
ms.Position = 0;
|
||||
await ms.CopyToLimitAsync(builder._dataSlab, (int) ms.Length, token);
|
||||
builder._packSize = (uint) ms.Length;
|
||||
}
|
||||
|
||||
builder._dataSlab.Position = 0;
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public void WriteHeader(BinaryWriter bw)
|
||||
{
|
||||
_offsetOffset = bw.BaseStream.Position;
|
||||
bw.Write((ulong) 0);
|
||||
bw.Write(_packSize);
|
||||
bw.Write(_chunk.FullSz);
|
||||
bw.Write(_chunk.StartMip);
|
||||
bw.Write(_chunk.EndMip);
|
||||
bw.Write(_chunk.Align);
|
||||
}
|
||||
|
||||
public async ValueTask WriteData(BinaryWriter bw, CancellationToken token)
|
||||
{
|
||||
var pos = bw.BaseStream.Position;
|
||||
bw.BaseStream.Position = _offsetOffset;
|
||||
bw.Write((ulong) pos);
|
||||
bw.BaseStream.Position = pos;
|
||||
await _dataSlab.CopyToLimitAsync(bw.BaseStream, (int) _dataSlab.Length, token);
|
||||
await _dataSlab.DisposeAsync();
|
||||
}
|
||||
}
|
@ -1,249 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Compression.BSA;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.DTOs.BSA.FileStates;
|
||||
using Wabbajack.DTOs.Streams;
|
||||
using Wabbajack.DTOs.Texture;
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
||||
|
||||
public class DX10Entry : IBA2FileEntry
|
||||
{
|
||||
private readonly Reader _bsa;
|
||||
private ushort _chunkHdrLen;
|
||||
private List<TextureChunk> _chunks;
|
||||
private uint _dirHash;
|
||||
private string _extension;
|
||||
private byte _format;
|
||||
private ushort _height;
|
||||
private int _index;
|
||||
private uint _nameHash;
|
||||
private byte _numChunks;
|
||||
private byte _numMips;
|
||||
private ushort _unk16;
|
||||
private byte _unk8;
|
||||
private ushort _width;
|
||||
private readonly byte _isCubemap;
|
||||
private readonly byte _tileMode;
|
||||
|
||||
public DX10Entry(Reader ba2Reader, int idx)
|
||||
{
|
||||
_bsa = ba2Reader;
|
||||
var _rdr = ba2Reader._rdr;
|
||||
_nameHash = _rdr.ReadUInt32();
|
||||
FullPath = _nameHash.ToString("X");
|
||||
_extension = Encoding.UTF8.GetString(_rdr.ReadBytes(4));
|
||||
_dirHash = _rdr.ReadUInt32();
|
||||
_unk8 = _rdr.ReadByte();
|
||||
_numChunks = _rdr.ReadByte();
|
||||
_chunkHdrLen = _rdr.ReadUInt16();
|
||||
_height = _rdr.ReadUInt16();
|
||||
_width = _rdr.ReadUInt16();
|
||||
_numMips = _rdr.ReadByte();
|
||||
_format = _rdr.ReadByte();
|
||||
_isCubemap = _rdr.ReadByte();
|
||||
_tileMode = _rdr.ReadByte();
|
||||
_index = idx;
|
||||
|
||||
_chunks = Enumerable.Range(0, _numChunks)
|
||||
.Select(_ => new TextureChunk(_rdr))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public uint HeaderSize => DDS.HeaderSizeForFormat((DXGI_FORMAT) _format);
|
||||
|
||||
public string FullPath { get; set; }
|
||||
|
||||
public RelativePath Path => FullPath.ToRelativePath();
|
||||
public uint Size => (uint) _chunks.Sum(f => f._fullSz) + HeaderSize + sizeof(uint);
|
||||
|
||||
public AFile State => new BA2DX10File
|
||||
{
|
||||
Path = Path,
|
||||
NameHash = _nameHash,
|
||||
Extension = _extension,
|
||||
DirHash = _dirHash,
|
||||
Unk8 = _unk8,
|
||||
ChunkHdrLen = _chunkHdrLen,
|
||||
Height = _height,
|
||||
Width = _width,
|
||||
NumMips = _numMips,
|
||||
PixelFormat = _format,
|
||||
IsCubeMap = _isCubemap,
|
||||
TileMode = _tileMode,
|
||||
Index = _index,
|
||||
Chunks = _chunks.Select(ch => new BA2Chunk
|
||||
{
|
||||
FullSz = ch._fullSz,
|
||||
StartMip = ch._startMip,
|
||||
EndMip = ch._endMip,
|
||||
Align = ch._align,
|
||||
Compressed = ch._packSz != 0
|
||||
}).ToArray()
|
||||
};
|
||||
|
||||
public async ValueTask CopyDataTo(Stream output, CancellationToken token)
|
||||
{
|
||||
var bw = new BinaryWriter(output);
|
||||
|
||||
WriteHeader(bw);
|
||||
|
||||
await using var fs = await _bsa._streamFactory.GetStream();
|
||||
using var br = new BinaryReader(fs);
|
||||
foreach (var chunk in _chunks)
|
||||
{
|
||||
var full = new byte[chunk._fullSz];
|
||||
var isCompressed = chunk._packSz != 0;
|
||||
|
||||
br.BaseStream.Seek((long) chunk._offset, SeekOrigin.Begin);
|
||||
|
||||
if (!isCompressed)
|
||||
{
|
||||
await br.BaseStream.ReadAsync(full, token);
|
||||
}
|
||||
else
|
||||
{
|
||||
var compressed = new byte[chunk._packSz];
|
||||
await br.BaseStream.ReadAsync(compressed, token);
|
||||
var inflater = new Inflater();
|
||||
inflater.SetInput(compressed);
|
||||
inflater.Inflate(full);
|
||||
}
|
||||
|
||||
await bw.BaseStream.WriteAsync(full, token);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<IStreamFactory> GetStreamFactory(CancellationToken token)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
await CopyDataTo(ms, token);
|
||||
ms.Position = 0;
|
||||
return new MemoryStreamFactory(ms, Path, _bsa._streamFactory.LastModifiedUtc);
|
||||
}
|
||||
|
||||
private void WriteHeader(BinaryWriter bw)
|
||||
{
|
||||
var ddsHeader = new DDS_HEADER();
|
||||
|
||||
ddsHeader.dwSize = ddsHeader.GetSize();
|
||||
ddsHeader.dwHeaderFlags = DDS.DDS_HEADER_FLAGS_TEXTURE | DDS.DDS_HEADER_FLAGS_LINEARSIZE |
|
||||
DDS.DDS_HEADER_FLAGS_MIPMAP;
|
||||
ddsHeader.dwHeight = _height;
|
||||
ddsHeader.dwWidth = _width;
|
||||
ddsHeader.dwMipMapCount = _numMips;
|
||||
ddsHeader.PixelFormat.dwSize = ddsHeader.PixelFormat.GetSize();
|
||||
ddsHeader.dwDepth = 1;
|
||||
ddsHeader.dwSurfaceFlags = DDS.DDS_SURFACE_FLAGS_TEXTURE | DDS.DDS_SURFACE_FLAGS_MIPMAP;
|
||||
ddsHeader.dwCubemapFlags = _isCubemap == 1 ? (uint)(DDSCAPS2.CUBEMAP
|
||||
| DDSCAPS2.CUBEMAP_NEGATIVEX | DDSCAPS2.CUBEMAP_POSITIVEX
|
||||
| DDSCAPS2.CUBEMAP_NEGATIVEY | DDSCAPS2.CUBEMAP_POSITIVEY
|
||||
| DDSCAPS2.CUBEMAP_NEGATIVEZ | DDSCAPS2.CUBEMAP_POSITIVEZ
|
||||
| DDSCAPS2.CUBEMAP_ALLFACES) : 0u;
|
||||
|
||||
|
||||
switch ((DXGI_FORMAT) _format)
|
||||
{
|
||||
case DXGI_FORMAT.BC1_UNORM:
|
||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
|
||||
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', 'T', '1');
|
||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height / 2); // 4bpp
|
||||
break;
|
||||
case DXGI_FORMAT.BC2_UNORM:
|
||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
|
||||
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', 'T', '3');
|
||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height); // 8bpp
|
||||
break;
|
||||
case DXGI_FORMAT.BC3_UNORM:
|
||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
|
||||
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', 'T', '5');
|
||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height); // 8bpp
|
||||
break;
|
||||
case DXGI_FORMAT.BC5_UNORM:
|
||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
|
||||
if (_bsa.UseATIFourCC)
|
||||
ddsHeader.PixelFormat.dwFourCC =
|
||||
DDS.MAKEFOURCC('A', 'T', 'I',
|
||||
'2'); // this is more correct but the only thing I have found that supports it is the nvidia photoshop plugin
|
||||
else
|
||||
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('B', 'C', '5', 'U');
|
||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height); // 8bpp
|
||||
break;
|
||||
case DXGI_FORMAT.BC1_UNORM_SRGB:
|
||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
|
||||
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', '1', '0');
|
||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height / 2); // 4bpp
|
||||
break;
|
||||
case DXGI_FORMAT.BC3_UNORM_SRGB:
|
||||
case DXGI_FORMAT.BC6H_UF16:
|
||||
case DXGI_FORMAT.BC4_UNORM:
|
||||
case DXGI_FORMAT.BC5_SNORM:
|
||||
case DXGI_FORMAT.BC7_UNORM:
|
||||
case DXGI_FORMAT.BC7_UNORM_SRGB:
|
||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
|
||||
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', '1', '0');
|
||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height); // 8bpp
|
||||
break;
|
||||
case DXGI_FORMAT.R8G8B8A8_UNORM:
|
||||
case DXGI_FORMAT.R8G8B8A8_UNORM_SRGB:
|
||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_RGBA;
|
||||
ddsHeader.PixelFormat.dwRGBBitCount = 32;
|
||||
ddsHeader.PixelFormat.dwRBitMask = 0x000000FF;
|
||||
ddsHeader.PixelFormat.dwGBitMask = 0x0000FF00;
|
||||
ddsHeader.PixelFormat.dwBBitMask = 0x00FF0000;
|
||||
ddsHeader.PixelFormat.dwABitMask = 0xFF000000;
|
||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height * 4); // 32bpp
|
||||
break;
|
||||
case DXGI_FORMAT.B8G8R8A8_UNORM:
|
||||
case DXGI_FORMAT.B8G8R8X8_UNORM:
|
||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_RGBA;
|
||||
ddsHeader.PixelFormat.dwRGBBitCount = 32;
|
||||
ddsHeader.PixelFormat.dwRBitMask = 0x00FF0000;
|
||||
ddsHeader.PixelFormat.dwGBitMask = 0x0000FF00;
|
||||
ddsHeader.PixelFormat.dwBBitMask = 0x000000FF;
|
||||
ddsHeader.PixelFormat.dwABitMask = 0xFF000000;
|
||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height * 4); // 32bpp
|
||||
break;
|
||||
case DXGI_FORMAT.R8_UNORM:
|
||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_RGB;
|
||||
ddsHeader.PixelFormat.dwRGBBitCount = 8;
|
||||
ddsHeader.PixelFormat.dwRBitMask = 0xFF;
|
||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height); // 8bpp
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unsupported DDS header format. File: " + FullPath);
|
||||
}
|
||||
|
||||
bw.Write((uint) DDS.DDS_MAGIC);
|
||||
ddsHeader.Write(bw);
|
||||
|
||||
switch ((DXGI_FORMAT) _format)
|
||||
{
|
||||
case DXGI_FORMAT.BC1_UNORM_SRGB:
|
||||
case DXGI_FORMAT.BC3_UNORM_SRGB:
|
||||
case DXGI_FORMAT.BC4_UNORM:
|
||||
case DXGI_FORMAT.BC5_SNORM:
|
||||
case DXGI_FORMAT.BC6H_UF16:
|
||||
case DXGI_FORMAT.BC7_UNORM:
|
||||
case DXGI_FORMAT.BC7_UNORM_SRGB:
|
||||
var dxt10 = new DDS_HEADER_DXT10
|
||||
{
|
||||
dxgiFormat = _format,
|
||||
resourceDimension = (uint) DXT10_RESOURCE_DIMENSION.DIMENSION_TEXTURE2D,
|
||||
miscFlag = 0,
|
||||
arraySize = 1,
|
||||
miscFlags2 = DDS.DDS_ALPHA_MODE_UNKNOWN
|
||||
};
|
||||
dxt10.Write(bw);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -12,13 +12,13 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -11,18 +11,18 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="Xunit.DependencyInjection" Version="8.9.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="Xunit.DependencyInjection" Version="9.3.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="YamlDotNet" Version="13.7.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -54,5 +54,6 @@ public enum Game
|
||||
[Description("Modding Tools")] ModdingTools,
|
||||
|
||||
[Description("Final Fantasy VII Remake")] FinalFantasy7Remake,
|
||||
[Description("Baldur's Gate 3")] BaldursGate3
|
||||
[Description("Baldur's Gate 3")] BaldursGate3,
|
||||
[Description("Starfield")] Starfield
|
||||
}
|
||||
|
@ -74,6 +74,7 @@ public static class GameRegistry
|
||||
MO2ArchiveName = "falloutnv",
|
||||
SteamIDs = new[] {22380, 22490}, // normal and RU version
|
||||
GOGIDs = new long[] {1454587428},
|
||||
EpicGameStoreIDs = new[] {"dabb52e328834da7bbe99691e374cb84"},
|
||||
RequiredFiles = new[]
|
||||
{
|
||||
"FalloutNV.exe".ToRelativePath()
|
||||
@ -602,6 +603,22 @@ public static class GameRegistry
|
||||
MainExecutable = @"bin/bg3.exe".ToRelativePath()
|
||||
}
|
||||
},
|
||||
{
|
||||
Game.Starfield, new GameMetaData
|
||||
{
|
||||
Game = Game.Starfield,
|
||||
NexusName = "starfield",
|
||||
NexusGameId = 4187,
|
||||
MO2Name = "Starfield",
|
||||
MO2ArchiveName = "Starfield",
|
||||
SteamIDs = [1716740],
|
||||
RequiredFiles = new []
|
||||
{
|
||||
@"Starfield.exe".ToRelativePath()
|
||||
},
|
||||
MainExecutable = @"Starfield.exe".ToRelativePath()
|
||||
}
|
||||
},
|
||||
{
|
||||
Game.ModdingTools, new GameMetaData
|
||||
{
|
||||
|
@ -1,10 +0,0 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Wabbajack.DTOs.Logins;
|
||||
|
||||
public class NexusApiState
|
||||
{
|
||||
[JsonPropertyName("api-key")] public string ApiKey { get; set; }
|
||||
|
||||
[JsonPropertyName("cookies")] public Cookie[] Cookies { get; set; }
|
||||
}
|
13
Wabbajack.DTOs/Logins/NexusOAuthState.cs
Normal file
13
Wabbajack.DTOs/Logins/NexusOAuthState.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Wabbajack.DTOs.OAuth;
|
||||
|
||||
namespace Wabbajack.DTOs.Logins;
|
||||
|
||||
public class NexusOAuthState
|
||||
{
|
||||
[JsonPropertyName("oauth")]
|
||||
public JwtTokenReply? OAuth { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("api_key")]
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
}
|
@ -17,4 +17,7 @@ public class BA2State : IArchive
|
||||
public BA2EntryType Type { get; set; }
|
||||
public string HeaderMagic { get; set; }
|
||||
public uint Version { get; set; }
|
||||
public uint Unknown1 { get; set; }
|
||||
public uint Unknown2 { get; set; }
|
||||
public uint Compression { get; set; }
|
||||
}
|
52
Wabbajack.DTOs/OAuth/JWTTokenReply.cs
Normal file
52
Wabbajack.DTOs/OAuth/JWTTokenReply.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Wabbajack.DTOs.OAuth;
|
||||
|
||||
/// <summary>
|
||||
/// JWT Token info as provided by the OAuth server
|
||||
/// </summary>
|
||||
public class JwtTokenReply
|
||||
{
|
||||
/// <summary>
|
||||
/// the token to use for authentication
|
||||
/// </summary>
|
||||
[JsonPropertyName("access_token")]
|
||||
public string? AccessToken { get; set; }
|
||||
|
||||
[JsonPropertyName("_received_at")]
|
||||
public long ReceivedAt { get; set; }
|
||||
|
||||
|
||||
public bool IsExpired => DateTime.FromFileTimeUtc(ReceivedAt) + TimeSpan.FromSeconds(ExpiresIn) - TimeSpan.FromMinutes(5) <= DateTimeOffset.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// token type, e.g. "Bearer"
|
||||
/// </summary>
|
||||
[JsonPropertyName("token_type")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// when the access token expires in seconds
|
||||
/// </summary>
|
||||
[JsonPropertyName("expires_in")]
|
||||
public ulong ExpiresIn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// token to use to refresh once this one has expired
|
||||
/// </summary>
|
||||
[JsonPropertyName("refresh_token")]
|
||||
public string? RefreshToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// space separated list of scopes. defined by the server, currently always "public"?
|
||||
/// </summary>
|
||||
[JsonPropertyName("scope")]
|
||||
public string? Scope { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// unix timestamp (seconds resolution) of when the token was created
|
||||
/// </summary>
|
||||
[JsonPropertyName("created_at")]
|
||||
public long CreatedAt { get; set; }
|
||||
}
|
@ -11,15 +11,15 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="Xunit.DependencyInjection.Logging" Version="8.1.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -148,8 +148,9 @@ public class DownloadDispatcher
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (HttpException)
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_logger.LogError($"Failed verifying {a.State.PrimaryKeyString}: {ex}");
|
||||
await _verificationCache.Put(a.State, false);
|
||||
return false;
|
||||
}
|
||||
|
@ -18,11 +18,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="GameFinder.StoreHandlers.EADesktop" Version="4.1.0" />
|
||||
<PackageReference Include="GameFinder.StoreHandlers.EGS" Version="4.1.0" />
|
||||
<PackageReference Include="GameFinder.StoreHandlers.GOG" Version="4.1.0" />
|
||||
<PackageReference Include="GameFinder.StoreHandlers.Origin" Version="4.1.0" />
|
||||
<PackageReference Include="GameFinder.StoreHandlers.Steam" Version="4.1.0" />
|
||||
<PackageReference Include="GameFinder.StoreHandlers.EADesktop" Version="4.2.2" />
|
||||
<PackageReference Include="GameFinder.StoreHandlers.EGS" Version="4.2.2" />
|
||||
<PackageReference Include="GameFinder.StoreHandlers.GOG" Version="4.2.2" />
|
||||
<PackageReference Include="GameFinder.StoreHandlers.Origin" Version="4.2.2" />
|
||||
<PackageReference Include="GameFinder.StoreHandlers.Steam" Version="4.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -12,7 +12,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.54" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.61" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -6,8 +6,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.54" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.61" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.54" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.61" />
|
||||
<PackageReference Include="MegaApiClient" Version="1.10.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
@ -48,7 +48,7 @@ public class NexusDownloader : ADownloader<Nexus>, IUrlDownloader
|
||||
|
||||
public override Task<bool> Prepare()
|
||||
{
|
||||
return Task.FromResult(_api.ApiKey.HaveToken());
|
||||
return Task.FromResult(_api.AuthInfo.HaveToken());
|
||||
}
|
||||
|
||||
public override bool IsAllowed(ServerAllowList allowList, IDownloadState state)
|
||||
@ -217,8 +217,9 @@ public class NexusDownloader : ADownloader<Nexus>, IUrlDownloader
|
||||
|
||||
return fileInfo.info.FileId == state.FileID;
|
||||
}
|
||||
catch (HttpException)
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_logger.LogError($"HttpException: {ex} on {archive.Name}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -11,17 +11,17 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="Xunit.DependencyInjection" Version="8.9.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="Xunit.DependencyInjection" Version="9.3.0" />
|
||||
<PackageReference Include="Xunit.DependencyInjection.Logging" Version="8.1.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -12,15 +12,15 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="Shipwreck.Phash" Version="0.5.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.2" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -11,14 +11,14 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="System.Data.HashFunction.xxHash" Version="2.0.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -11,14 +11,14 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -22,6 +23,7 @@ using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.BSA.FileStates;
|
||||
using Wabbajack.DTOs.Directives;
|
||||
using Wabbajack.DTOs.DownloadStates;
|
||||
using Wabbajack.DTOs.Interventions;
|
||||
using Wabbajack.DTOs.JsonConverters;
|
||||
using Wabbajack.Hashing.PHash;
|
||||
using Wabbajack.Hashing.xxHash64;
|
||||
@ -123,6 +125,12 @@ public class StandardInstaller : AInstaller<StandardInstaller>
|
||||
var missing = ModList.Archives.Where(a => !HashedArchives.ContainsKey(a.Hash)).ToList();
|
||||
if (missing.Count > 0)
|
||||
{
|
||||
if (missing.Any(m => m.State is not Nexus))
|
||||
{
|
||||
ShowMissingManualReport(missing.Where(m => m.State is not Nexus).ToArray());
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var a in missing)
|
||||
_logger.LogCritical("Unable to download {name} ({primaryKeyString})", a.Name,
|
||||
a.State.PrimaryKeyString);
|
||||
@ -168,6 +176,46 @@ public class StandardInstaller : AInstaller<StandardInstaller>
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ShowMissingManualReport(Archive[] toArray)
|
||||
{
|
||||
_logger.LogError("Writing Manual helper report");
|
||||
var report = _configuration.Downloads.Combine("MissingManuals.html");
|
||||
{
|
||||
using var writer = new StreamWriter(report.Open(FileMode.Create, FileAccess.Write, FileShare.None));
|
||||
writer.Write("<html><head><title>Missing Manual Downloads</title></head><body>");
|
||||
writer.Write("<h1>Missing Manual Downloads</h1>");
|
||||
writer.Write(
|
||||
"<p>Wabbajack was unable to download the following archives automaticall. Please download them manually and place them in the downloads folder you chose during the install setup.</p>");
|
||||
foreach (var archive in toArray)
|
||||
{
|
||||
switch (archive.State)
|
||||
{
|
||||
case Manual manual:
|
||||
writer.Write($"<h3>{archive.Name}</h1>");
|
||||
writer.Write($"<p>{manual.Prompt}</p>");
|
||||
writer.Write($"<p>Download URL: <a href=\"{manual.Url}\">{manual.Url}</a></p>");
|
||||
break;
|
||||
case MediaFire mediaFire:
|
||||
writer.Write($"<h3>{archive.Name}</h1>");
|
||||
writer.Write($"<p>Download URL: <a href=\"{mediaFire.Url}\">{mediaFire.Url}</a></p>");
|
||||
break;
|
||||
default:
|
||||
writer.Write($"<h3>{archive.Name}</h1>");
|
||||
writer.Write($"<p>Unknown download type</p>");
|
||||
writer.Write($"<p>Primary Key (may not be helpful): <a href=\"{archive.State.PrimaryKeyString}\">{archive.State.PrimaryKeyString}</a></p>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
writer.Write("</body></html>");
|
||||
}
|
||||
|
||||
Process.Start(new ProcessStartInfo("cmd.exe", $"/c start {report}")
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
});
|
||||
}
|
||||
|
||||
private Task RemapMO2File()
|
||||
{
|
||||
var iniFile = _configuration.Install.Combine("ModOrganizer.ini");
|
||||
@ -247,7 +295,16 @@ public class StandardInstaller : AInstaller<StandardInstaller>
|
||||
var metaFile = download.WithExtension(Ext.Meta);
|
||||
|
||||
var found = bySize[download.Size()];
|
||||
var hash = await FileHashCache.FileHashCachedAsync(download, token);
|
||||
Hash hash = default;
|
||||
try
|
||||
{
|
||||
hash = await FileHashCache.FileHashCachedAsync(download, token);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError($"Failed to get hash for file {download}!");
|
||||
throw;
|
||||
}
|
||||
var archive = found.FirstOrDefault(f => f.Hash == hash);
|
||||
|
||||
IEnumerable<string> meta;
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ini-parser-netstandard" Version="2.5.2" />
|
||||
<PackageReference Include="Octopus.Octodiff" Version="2.0.468" />
|
||||
<PackageReference Include="Octopus.Octodiff" Version="2.0.546" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,40 +0,0 @@
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using Wabbajack.DTOs.Logins;
|
||||
using Wabbajack.Networking.Http.Interfaces;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.Paths.IO;
|
||||
|
||||
namespace Wabbajack.Launcher.Models;
|
||||
|
||||
public class LegacyNexusApiKey : ITokenProvider<NexusApiState>
|
||||
{
|
||||
private AbsolutePath TokenPath => KnownFolders.WabbajackAppLocal.Combine("nexusapikey");
|
||||
public async ValueTask<NexusApiState?> Get()
|
||||
{
|
||||
var data = await TokenPath.ReadAllBytesAsync();
|
||||
var decoded = ProtectedData.Unprotect(data, Encoding.UTF8.GetBytes("nexusapikey"), DataProtectionScope.LocalMachine);
|
||||
var apiKey = JsonSerializer.Deserialize<string>(decoded)!;
|
||||
return new NexusApiState()
|
||||
{
|
||||
ApiKey = apiKey
|
||||
};
|
||||
}
|
||||
|
||||
public ValueTask SetToken(NexusApiState val)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public ValueTask<bool> Delete()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool HaveToken()
|
||||
{
|
||||
return TokenPath.FileExists();
|
||||
}
|
||||
}
|
@ -13,7 +13,6 @@ using Wabbajack.Downloaders.Http;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.JsonConverters;
|
||||
using Wabbajack.DTOs.Logins;
|
||||
using Wabbajack.Launcher.Models;
|
||||
using Wabbajack.Launcher.ViewModels;
|
||||
using Wabbajack.Networking.Http;
|
||||
using Wabbajack.Networking.Http.Interfaces;
|
||||
@ -57,7 +56,7 @@ internal class Program
|
||||
|
||||
services.AddSingleton<MainWindowViewModel>();
|
||||
services.AddSingleton<HttpClient>();
|
||||
services.AddSingleton<ITokenProvider<NexusApiState>, NexusApiTokenProvider>();
|
||||
services.AddSingleton<ITokenProvider<NexusOAuthState>, NexusApiTokenProvider>();
|
||||
services.AddSingleton<HttpDownloader>();
|
||||
services.AddAllSingleton<IResource, IResource<HttpClient>>(s => new Resource<HttpClient>("Web Requests", 4));
|
||||
services.AddAllSingleton<IHttpDownloader, SingleThreadedDownloader>();
|
||||
|
@ -32,9 +32,9 @@ public class MainWindowViewModel : ViewModelBase
|
||||
public Uri GITHUB_REPO = new("https://api.github.com/repos/wabbajack-tools/wabbajack/releases");
|
||||
private readonly NexusApi _nexusApi;
|
||||
private readonly HttpDownloader _downloader;
|
||||
private readonly ITokenProvider<NexusApiState> _tokenProvider;
|
||||
private readonly ITokenProvider<NexusOAuthState> _tokenProvider;
|
||||
|
||||
public MainWindowViewModel(NexusApi nexusApi, HttpDownloader downloader, ITokenProvider<NexusApiState> tokenProvider)
|
||||
public MainWindowViewModel(NexusApi nexusApi, HttpDownloader downloader, ITokenProvider<NexusOAuthState> tokenProvider)
|
||||
{
|
||||
_nexusApi = nexusApi;
|
||||
Status = "Checking for new versions";
|
||||
|
@ -19,15 +19,15 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.10" />
|
||||
<PackageReference Include="GitInfo" Version="3.3.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Avalonia" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia" Version="11.0.10" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.10" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.10" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.10" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
|
||||
<PackageReference Include="MessageBox.Avalonia" Version="3.1.5.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
|
@ -12,13 +12,13 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -40,4 +40,4 @@ public class NexusApiTests
|
||||
var (links, _) = await _api.DownloadLink("skyrimspecialedition", 12604, file.FileId);
|
||||
Assert.True(links.Length > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,4 +17,4 @@ public class Startup
|
||||
{
|
||||
loggerFactory.AddProvider(new XunitTestOutputLoggerProvider(accessor, delegate { return true; }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,15 +11,15 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="Xunit.DependencyInjection" Version="8.9.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="Xunit.DependencyInjection" Version="9.3.0" />
|
||||
<PackageReference Include="Xunit.DependencyInjection.Logging" Version="8.1.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -3,6 +3,6 @@ using Wabbajack.Networking.Http.Interfaces;
|
||||
|
||||
namespace Wabbajack.Networking.NexusApi;
|
||||
|
||||
public interface ApiKey : ITokenProvider<NexusApiState>
|
||||
public interface IAuthInfo : ITokenProvider<NexusOAuthState>
|
||||
{
|
||||
}
|
33
Wabbajack.Networking.NexusApi/DTOs/OAuthUserInfo.cs
Normal file
33
Wabbajack.Networking.NexusApi/DTOs/OAuthUserInfo.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using Octokit;
|
||||
|
||||
namespace Wabbajack.Networking.NexusApi.DTOs;
|
||||
|
||||
public record OAuthUserInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the User ID.
|
||||
/// </summary>
|
||||
[JsonPropertyName("sub")]
|
||||
public string Sub { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the User Name.
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar url.
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar")]
|
||||
public Uri? Avatar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of membership roles.
|
||||
/// </summary>
|
||||
[JsonPropertyName("membership_roles")]
|
||||
public string[] MembershipRoles { get; set; } = [];
|
||||
}
|
@ -8,4 +8,6 @@ public static class Endpoints
|
||||
public const string ModFile = "v1/games/{0}/mods/{1}/files/{2}.json";
|
||||
public const string DownloadLink = "v1/games/{0}/mods/{1}/files/{2}/download_link.json";
|
||||
public const string Updates = "v1/games/{0}/mods/updated.json?period={1}";
|
||||
|
||||
public const string OAuthValidate = "https://users.nexusmods.com/oauth/userinfo";
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@ -12,6 +13,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.Logins;
|
||||
using Wabbajack.DTOs.OAuth;
|
||||
using Wabbajack.Networking.Http;
|
||||
using Wabbajack.Networking.Http.Interfaces;
|
||||
using Wabbajack.Networking.NexusApi.DTOs;
|
||||
@ -28,14 +30,15 @@ public class NexusApi
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
private readonly IResource<HttpClient> _limiter;
|
||||
private readonly ILogger<NexusApi> _logger;
|
||||
public readonly ITokenProvider<NexusApiState> ApiKey;
|
||||
public readonly ITokenProvider<NexusOAuthState> AuthInfo;
|
||||
private DateTime _lastValidated;
|
||||
private (ValidateInfo info, ResponseMetadata header) _lastValidatedInfo;
|
||||
private AsyncLock _authLock = new();
|
||||
|
||||
public NexusApi(ITokenProvider<NexusApiState> apiKey, ILogger<NexusApi> logger, HttpClient client,
|
||||
public NexusApi(ITokenProvider<NexusOAuthState> authInfo, ILogger<NexusApi> logger, HttpClient client,
|
||||
IResource<HttpClient> limiter, ApplicationInfo appInfo, JsonSerializerOptions jsonOptions)
|
||||
{
|
||||
ApiKey = apiKey;
|
||||
AuthInfo = authInfo;
|
||||
_logger = logger;
|
||||
_client = client;
|
||||
_appInfo = appInfo;
|
||||
@ -48,8 +51,27 @@ public class NexusApi
|
||||
public virtual async Task<(ValidateInfo info, ResponseMetadata header)> Validate(
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var msg = await GenerateMessage(HttpMethod.Get, Endpoints.Validate);
|
||||
return await Send<ValidateInfo>(msg, token);
|
||||
var (isApi, code) = await GetAuthInfo();
|
||||
|
||||
if (isApi)
|
||||
{
|
||||
var msg = await GenerateMessage(HttpMethod.Get, Endpoints.Validate);
|
||||
_lastValidatedInfo = await Send<ValidateInfo>(msg, token);
|
||||
}
|
||||
else
|
||||
{
|
||||
var msg = await GenerateMessage(HttpMethod.Get, Endpoints.OAuthValidate);
|
||||
var (data, header) = await Send<OAuthUserInfo>(msg, token);
|
||||
var validateInfo = new ValidateInfo
|
||||
{
|
||||
IsPremium = data.MembershipRoles.Contains("premium"),
|
||||
Name = data.Name,
|
||||
};
|
||||
_lastValidatedInfo = (validateInfo, header);
|
||||
}
|
||||
|
||||
_lastValidated = DateTime.Now;
|
||||
return _lastValidatedInfo;
|
||||
}
|
||||
|
||||
public async Task<(ValidateInfo info, ResponseMetadata header)> ValidateCached(
|
||||
@ -60,8 +82,8 @@ public class NexusApi
|
||||
return _lastValidatedInfo;
|
||||
}
|
||||
|
||||
var msg = await GenerateMessage(HttpMethod.Get, Endpoints.Validate);
|
||||
_lastValidatedInfo = await Send<ValidateInfo>(msg, token);
|
||||
await Validate(token);
|
||||
|
||||
return _lastValidatedInfo;
|
||||
}
|
||||
|
||||
@ -172,19 +194,91 @@ public class NexusApi
|
||||
var userAgent =
|
||||
$"{_appInfo.ApplicationSlug}/{_appInfo.Version} ({_appInfo.OSVersion}; {_appInfo.Platform})";
|
||||
|
||||
if (!ApiKey.HaveToken())
|
||||
throw new Exception("Please log into the Nexus before attempting to use Wabbajack");
|
||||
await AddAuthHeaders(msg);
|
||||
|
||||
var token = (await ApiKey.Get())!;
|
||||
if (uri.StartsWith("http"))
|
||||
{
|
||||
msg.RequestUri = new Uri($"{string.Format(uri, parameters)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.RequestUri = new Uri($"https://api.nexusmods.com/{string.Format(uri, parameters)}");
|
||||
}
|
||||
|
||||
msg.RequestUri = new Uri($"https://api.nexusmods.com/{string.Format(uri, parameters)}");
|
||||
msg.Headers.Add("User-Agent", userAgent);
|
||||
msg.Headers.Add("Application-Name", _appInfo.ApplicationSlug);
|
||||
msg.Headers.Add("Application-Version", _appInfo.Version);
|
||||
msg.Headers.Add("apikey", token.ApiKey);
|
||||
|
||||
|
||||
msg.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
return msg;
|
||||
}
|
||||
|
||||
private async ValueTask AddAuthHeaders(HttpRequestMessage msg)
|
||||
{
|
||||
var (isApi, code) = await GetAuthInfo();
|
||||
if (string.IsNullOrWhiteSpace(code))
|
||||
throw new Exception("No API Key or OAuth Token found for NexusMods");
|
||||
|
||||
if (isApi)
|
||||
msg.Headers.Add("apikey", code);
|
||||
else
|
||||
{
|
||||
msg.Headers.Authorization = new AuthenticationHeaderValue("Bearer", code);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async ValueTask<(bool IsApiKey, string code)> GetAuthInfo()
|
||||
{
|
||||
using var _ = await _authLock.WaitAsync();
|
||||
if (AuthInfo.HaveToken())
|
||||
{
|
||||
var info = await AuthInfo.Get();
|
||||
if (info!.OAuth != null)
|
||||
{
|
||||
if (info!.OAuth.IsExpired)
|
||||
info = await RefreshToken(info, CancellationToken.None);
|
||||
return (false, info.OAuth!.AccessToken!);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(info.ApiKey))
|
||||
{
|
||||
return (true, info.ApiKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Environment.GetEnvironmentVariable("NEXUS_API_KEY") is { } apiKey)
|
||||
{
|
||||
return (true, apiKey);
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private async Task<NexusOAuthState> RefreshToken(NexusOAuthState state, CancellationToken cancel)
|
||||
{
|
||||
_logger.LogInformation("Refreshing OAuth Token");
|
||||
var request = new Dictionary<string, string>
|
||||
{
|
||||
{ "grant_type", "refresh_token" },
|
||||
{ "client_id", "wabbajack" },
|
||||
{ "refresh_token", state.OAuth!.RefreshToken },
|
||||
};
|
||||
|
||||
var content = new FormUrlEncodedContent(request);
|
||||
|
||||
var response = await _client.PostAsync($"https://users.nexusmods.com/oauth/token", content, cancel);
|
||||
var responseString = await response.Content.ReadAsStringAsync(cancel);
|
||||
var newJwt = JsonSerializer.Deserialize<JwtTokenReply>(responseString);
|
||||
if (newJwt != null)
|
||||
newJwt.ReceivedAt = DateTime.UtcNow.ToFileTimeUtc();
|
||||
|
||||
state.OAuth = newJwt;
|
||||
await AuthInfo.SetToken(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
public async Task<(UpdateEntry[], ResponseMetadata headers)> GetUpdates(Game game, CancellationToken token)
|
||||
{
|
||||
@ -274,7 +368,7 @@ public class NexusApi
|
||||
private async Task CheckAccess()
|
||||
{
|
||||
var msg = new HttpRequestMessage(HttpMethod.Get, "https://www.nexusmods.com/users/myaccount");
|
||||
msg.AddCookies((await ApiKey.Get())!.Cookies);
|
||||
throw new NotSupportedException("Uploading to NexusMods is currently disabled");
|
||||
using var response = await _client.SendAsync(msg);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
@ -292,7 +386,7 @@ public class NexusApi
|
||||
new Uri(
|
||||
$"https://www.nexusmods.com/{d.Game.MetaData().NexusName}/mods/edit/?id={d.ModId}&game_id={d.GameId}&step=files");
|
||||
|
||||
msg.AddCookies((await ApiKey.Get())!.Cookies);
|
||||
throw new NotSupportedException("Uploading to NexusMods is currently disabled");
|
||||
var form = new MultipartFormDataContent();
|
||||
form.Add(new StringContent(d.GameId.ToString()), "game_id");
|
||||
form.Add(new StringContent(d.Name), "name");
|
||||
@ -330,6 +424,7 @@ public class NexusApi
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> IsPremium(CancellationToken token)
|
||||
{
|
||||
|
@ -25,7 +25,7 @@ public class ProxiedNexusApi : NexusApi
|
||||
Endpoints.ModFile
|
||||
};
|
||||
|
||||
public ProxiedNexusApi(ITokenProvider<NexusApiState> apiKey, ILogger<ProxiedNexusApi> logger, HttpClient client,
|
||||
public ProxiedNexusApi(ITokenProvider<NexusOAuthState> apiKey, ILogger<ProxiedNexusApi> logger, HttpClient client,
|
||||
IResource<HttpClient> limiter,
|
||||
ApplicationInfo appInfo, JsonSerializerOptions jsonOptions, ITokenProvider<WabbajackApiState> apiState,
|
||||
ClientConfiguration wabbajackClientConfiguration)
|
||||
|
@ -12,7 +12,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -12,16 +12,16 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="Xunit.DependencyInjection" Version="8.9.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="Xunit.DependencyInjection" Version="9.3.0" />
|
||||
<PackageReference Include="Xunit.DependencyInjection.Logging" Version="8.1.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -14,7 +14,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Octokit" Version="9.0.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="13.7.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -12,13 +12,13 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FsCheck.Xunit" Version="2.16.6" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -11,13 +11,13 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -12,13 +12,13 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -166,7 +166,7 @@ public static class ServiceExtensions
|
||||
service.AddBethesdaNet();
|
||||
|
||||
// Token Providers
|
||||
service.AddAllSingleton<ITokenProvider<NexusApiState>, EncryptedJsonTokenProvider<NexusApiState>, NexusApiTokenProvider>();
|
||||
service.AddAllSingleton<ITokenProvider<NexusOAuthState>, EncryptedJsonTokenProvider<NexusOAuthState>, NexusApiTokenProvider>();
|
||||
service.AddAllSingleton<ITokenProvider<MegaToken>, EncryptedJsonTokenProvider<MegaToken>, MegaTokenProvider>();
|
||||
|
||||
service.AddAllSingleton<ITokenProvider<BethesdaNetLoginState>, EncryptedJsonTokenProvider<BethesdaNetLoginState>, BethesdaNetTokenProvider>();
|
||||
|
@ -5,10 +5,10 @@ using Wabbajack.Networking.NexusApi;
|
||||
|
||||
namespace Wabbajack.Services.OSIntegrated.TokenProviders;
|
||||
|
||||
public class NexusApiTokenProvider : EncryptedJsonTokenProvider<NexusApiState>, ApiKey
|
||||
public class NexusApiTokenProvider : EncryptedJsonTokenProvider<NexusOAuthState>, IAuthInfo
|
||||
{
|
||||
public NexusApiTokenProvider(ILogger<NexusApiTokenProvider> logger, DTOSerializer dtos) : base(logger, dtos,
|
||||
"nexus-login")
|
||||
"nexus-oauth-info")
|
||||
{
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -11,21 +11,21 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.118" />
|
||||
<PackageReference Include="Xunit.DependencyInjection" Version="8.9.0" />
|
||||
<PackageReference Include="Xunit.DependencyInjection" Version="9.3.0" />
|
||||
<PackageReference Include="Xunit.DependencyInjection.Logging" Version="8.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.118" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.118" PrivateAssets="None" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -8,7 +8,7 @@ mkdir c:\tmp\publish-wj
|
||||
dotnet clean
|
||||
dotnet publish Wabbajack.App.Wpf\Wabbajack.App.Wpf.csproj --framework "net8.0-windows" --runtime win-x64 --configuration Release /p:Platform=x64 -o c:\tmp\publish-wj\app /p:IncludeNativeLibrariesForSelfExtract=true --self-contained /p:DebugType=embedded
|
||||
dotnet publish Wabbajack.Launcher\Wabbajack.Launcher.csproj --framework "net8.0-windows" --runtime win-x64 --configuration Release /p:Platform=x64 -o c:\tmp\publish-wj\launcher /p:PublishSingleFile=true /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true --self-contained /p:DebugType=embedded
|
||||
dotnet publish c:\oss\Wabbajack\Wabbajack.CLI\Wabbajack.CLI.csproj --framework "net8.0-windows" --runtime win-x64 --configuration Release /p:Platform=x64 -o c:\tmp\publish-wj\app\cli /p:IncludeNativeLibrariesForSelfExtract=true --self-contained /p:DebugType=embedded
|
||||
dotnet publish Wabbajack.CLI\Wabbajack.CLI.csproj --framework "net8.0-windows" --runtime win-x64 --configuration Release /p:Platform=x64 -o c:\tmp\publish-wj\app\cli /p:IncludeNativeLibrariesForSelfExtract=true --self-contained /p:DebugType=embedded
|
||||
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" sign /fd sha256 /tr http://ts.ssl.com /td sha256 /sha1 8c26a8e0bf3e70eb89721cc4d86a87137153ccba c:\tmp\publish-wj\app\Wabbajack.exe
|
||||
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" sign /fd sha256 /tr http://ts.ssl.com /td sha256 /sha1 8c26a8e0bf3e70eb89721cc4d86a87137153ccba c:\tmp\publish-wj\launcher\Wabbajack.exe
|
||||
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" sign /fd sha256 /tr http://ts.ssl.com /td sha256 /sha1 8c26a8e0bf3e70eb89721cc4d86a87137153ccba c:\tmp\publish-wj\app\cli\wabbajack-cli.exe
|
||||
|
Loading…
x
Reference in New Issue
Block a user