mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Get get manifests and decrypt filenames
This commit is contained in:
parent
ac1f4a7427
commit
3304b2a584
@ -4,9 +4,13 @@ using System.CommandLine.Invocation;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using SteamKit2;
|
||||||
using Wabbajack.DTOs;
|
using Wabbajack.DTOs;
|
||||||
|
using Wabbajack.DTOs.JsonConverters;
|
||||||
using Wabbajack.Networking.Http.Interfaces;
|
using Wabbajack.Networking.Http.Interfaces;
|
||||||
using Wabbajack.Networking.Steam;
|
using Wabbajack.Networking.Steam;
|
||||||
|
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||||
|
|
||||||
namespace Wabbajack.CLI.Verbs;
|
namespace Wabbajack.CLI.Verbs;
|
||||||
|
|
||||||
@ -16,14 +20,16 @@ public class SteamAppDumpInfo : IVerb
|
|||||||
private readonly Client _client;
|
private readonly Client _client;
|
||||||
private readonly ITokenProvider<SteamLoginState> _token;
|
private readonly ITokenProvider<SteamLoginState> _token;
|
||||||
private readonly DepotDownloader _downloader;
|
private readonly DepotDownloader _downloader;
|
||||||
|
private readonly DTOSerializer _dtos;
|
||||||
|
|
||||||
public SteamAppDumpInfo(ILogger<SteamAppDumpInfo> logger, Client steamClient, ITokenProvider<SteamLoginState> token,
|
public SteamAppDumpInfo(ILogger<SteamAppDumpInfo> logger, Client steamClient, ITokenProvider<SteamLoginState> token,
|
||||||
DepotDownloader downloader)
|
DepotDownloader downloader, DTOSerializer dtos)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_client = steamClient;
|
_client = steamClient;
|
||||||
_token = token;
|
_token = token;
|
||||||
_downloader = downloader;
|
_downloader = downloader;
|
||||||
|
_dtos = dtos;
|
||||||
}
|
}
|
||||||
public Command MakeCommand()
|
public Command MakeCommand()
|
||||||
{
|
{
|
||||||
@ -44,14 +50,28 @@ public class SteamAppDumpInfo : IVerb
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _client.Login();
|
await _client.Login();
|
||||||
|
var appId = (uint) game.SteamIDs.First();
|
||||||
|
|
||||||
if (!await _downloader.AccountHasAccess((uint) game.SteamIDs.First()))
|
if (!await _downloader.AccountHasAccess(appId))
|
||||||
{
|
{
|
||||||
_logger.LogError("Your account does not have access to this Steam App");
|
_logger.LogError("Your account does not have access to this Steam App");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var appData = await _downloader.GetAppInfo((uint)game.SteamIDs.First());
|
||||||
|
|
||||||
|
Console.WriteLine("App Depots: ");
|
||||||
|
|
||||||
|
Console.WriteLine(_dtos.Serialize(appData, true));
|
||||||
|
|
||||||
|
Console.WriteLine("Loading Manifests");
|
||||||
|
var servers = await _client.LoadCDNServers();
|
||||||
|
//var manifest = await _client.GetAppManifest();
|
||||||
|
|
||||||
|
var data = await _client.GetAppManifest(appId, 489831, 7089166303853251347);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,10 +1,14 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.IO.Compression;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SteamKit2;
|
using SteamKit2;
|
||||||
|
using SteamKit2.CDN;
|
||||||
using SteamKit2.Internal;
|
using SteamKit2.Internal;
|
||||||
using Wabbajack.DTOs.Interventions;
|
using Wabbajack.DTOs.Interventions;
|
||||||
|
using Wabbajack.DTOs.JsonConverters;
|
||||||
using Wabbajack.Networking.Http.Interfaces;
|
using Wabbajack.Networking.Http.Interfaces;
|
||||||
|
using Wabbajack.Networking.Steam.DTOs;
|
||||||
using Wabbajack.Networking.Steam.UserInterventions;
|
using Wabbajack.Networking.Steam.UserInterventions;
|
||||||
|
|
||||||
namespace Wabbajack.Networking.Steam;
|
namespace Wabbajack.Networking.Steam;
|
||||||
@ -30,6 +34,8 @@ public class Client : IDisposable
|
|||||||
|
|
||||||
public TaskCompletionSource _licenseRequest = new();
|
public TaskCompletionSource _licenseRequest = new();
|
||||||
private readonly SteamApps _steamApps;
|
private readonly SteamApps _steamApps;
|
||||||
|
private readonly DTOSerializer _dtos;
|
||||||
|
private IReadOnlyCollection<Server> _cdnServers = Array.Empty<Server>();
|
||||||
|
|
||||||
public SteamApps.LicenseListCallback.License[] Licenses { get; private set; }
|
public SteamApps.LicenseListCallback.License[] Licenses { get; private set; }
|
||||||
|
|
||||||
@ -37,16 +43,24 @@ public class Client : IDisposable
|
|||||||
|
|
||||||
public ConcurrentDictionary<uint, SteamApps.PICSProductInfoCallback.PICSProductInfo?> PackageInfos { get; } = new();
|
public ConcurrentDictionary<uint, SteamApps.PICSProductInfoCallback.PICSProductInfo?> PackageInfos { get; } = new();
|
||||||
|
|
||||||
|
public ConcurrentDictionary<uint, (SteamApps.PICSProductInfoCallback.PICSProductInfo ProductInfo, AppInfo AppInfo)> AppInfo { get; } =
|
||||||
|
new();
|
||||||
|
|
||||||
|
public ConcurrentDictionary<uint, ulong> AppTokens { get; } = new();
|
||||||
|
|
||||||
|
|
||||||
|
public ConcurrentDictionary<uint, byte[]> DepotKeys { get; } = new();
|
||||||
|
|
||||||
|
|
||||||
public Client(ILogger<Client> logger, HttpClient client, ITokenProvider<SteamLoginState> token,
|
public Client(ILogger<Client> logger, HttpClient client, ITokenProvider<SteamLoginState> token,
|
||||||
IUserInterventionHandler interventionHandler)
|
IUserInterventionHandler interventionHandler, DTOSerializer dtos)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_httpClient = client;
|
_httpClient = client;
|
||||||
|
_dtos = dtos;
|
||||||
_interventionHandler = interventionHandler;
|
_interventionHandler = interventionHandler;
|
||||||
_client = new SteamClient(SteamConfiguration.Create(c =>
|
_client = new SteamClient(SteamConfiguration.Create(c =>
|
||||||
{
|
{
|
||||||
c.WithHttpClientFactory(() => client);
|
|
||||||
c.WithProtocolTypes(ProtocolTypes.WebSocket);
|
c.WithProtocolTypes(ProtocolTypes.WebSocket);
|
||||||
c.WithUniverse(EUniverse.Public);
|
c.WithUniverse(EUniverse.Public);
|
||||||
}));
|
}));
|
||||||
@ -87,6 +101,7 @@ public class Client : IDisposable
|
|||||||
IsBackground = true
|
IsBackground = true
|
||||||
}
|
}
|
||||||
.Start();
|
.Start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLicenseList(SteamApps.LicenseListCallback obj)
|
private void OnLicenseList(SteamApps.LicenseListCallback obj)
|
||||||
@ -298,4 +313,95 @@ public class Client : IDisposable
|
|||||||
return packages.Distinct().ToDictionary(p => p, p => PackageInfos[p]);
|
return packages.Distinct().ToDictionary(p => p, p => PackageInfos[p]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<AppInfo> GetAppInfo(uint appId)
|
||||||
|
{
|
||||||
|
if (AppInfo.TryGetValue(appId, out var info))
|
||||||
|
return info.AppInfo;
|
||||||
|
|
||||||
|
var result = await _steamApps.PICSGetAccessTokens(new List<uint> {appId}, new List<uint>());
|
||||||
|
|
||||||
|
if (result.AppTokensDenied.Contains(appId))
|
||||||
|
throw new SteamException($"Cannot get app token for {appId}", EResult.Invalid, EResult.Invalid);
|
||||||
|
|
||||||
|
foreach (var token in result.AppTokens)
|
||||||
|
{
|
||||||
|
AppTokens[token.Key] = token.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = new SteamApps.PICSRequest(appId);
|
||||||
|
if (AppTokens.ContainsKey(appId))
|
||||||
|
{
|
||||||
|
request.AccessToken = AppTokens[appId];
|
||||||
|
}
|
||||||
|
|
||||||
|
var appResult = await _steamApps.PICSGetProductInfo(new List<SteamApps.PICSRequest> {request},
|
||||||
|
new List<SteamApps.PICSRequest>());
|
||||||
|
|
||||||
|
if (appResult.Failed)
|
||||||
|
throw new SteamException($"Error getting app info for {appId}", EResult.Invalid, EResult.Invalid);
|
||||||
|
|
||||||
|
foreach (var (_, value) in appResult.Results!.SelectMany(v => v.Apps))
|
||||||
|
{
|
||||||
|
var translated = KeyValueTranslator.Translate<AppInfo>(value.KeyValues, _dtos);
|
||||||
|
AppInfo[value.ID] = (value, translated);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AppInfo[appId].AppInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyCollection<Server>> LoadCDNServers()
|
||||||
|
{
|
||||||
|
if (_cdnServers.Count > 0) return _cdnServers;
|
||||||
|
_logger.LogInformation("Loading CDN servers");
|
||||||
|
_cdnServers = await ContentServerDirectoryService.LoadAsync(_client.Configuration);
|
||||||
|
_logger.LogInformation("{Count} servers found", _cdnServers.Count);
|
||||||
|
|
||||||
|
return _cdnServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DepotManifest> GetAppManifest(uint appId, uint depotId, ulong manifestId)
|
||||||
|
{
|
||||||
|
await LoadCDNServers();
|
||||||
|
var client = _cdnServers.First();
|
||||||
|
|
||||||
|
var uri = new UriBuilder()
|
||||||
|
{
|
||||||
|
Host = client.Host,
|
||||||
|
Port = client.Port,
|
||||||
|
Scheme = client.Protocol.ToString(),
|
||||||
|
Path = $"depot/{depotId}/manifest/{manifestId}/5"
|
||||||
|
}.Uri;
|
||||||
|
|
||||||
|
var rawData = await _httpClient.GetByteArrayAsync(uri);
|
||||||
|
|
||||||
|
using var zip = new ZipArchive(new MemoryStream(rawData));
|
||||||
|
var firstEntry = zip.Entries.First();
|
||||||
|
var data = new MemoryStream();
|
||||||
|
await using var entryStream = firstEntry.Open();
|
||||||
|
await entryStream.CopyToAsync(data);
|
||||||
|
var manifest = DepotManifest.Deserialize(data.ToArray());
|
||||||
|
|
||||||
|
if (manifest.FilenamesEncrypted)
|
||||||
|
manifest.DecryptFilenames(await GetDepotKey(depotId, appId));
|
||||||
|
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async ValueTask<byte[]> GetDepotKey(uint depotId, uint appId)
|
||||||
|
{
|
||||||
|
if (DepotKeys.ContainsKey(depotId))
|
||||||
|
return DepotKeys[depotId];
|
||||||
|
|
||||||
|
_logger.LogInformation("Requesting Depot Key for {DepotId}", depotId);
|
||||||
|
|
||||||
|
var result = await _steamApps.GetDepotDecryptionKey(depotId, appId);
|
||||||
|
if (result.Result != EResult.OK)
|
||||||
|
throw new SteamException($"Error gettding Depot Key for {depotId} {appId}", result.Result, EResult.Invalid);
|
||||||
|
|
||||||
|
DepotKeys[depotId] = result.DepotKey;
|
||||||
|
return result.DepotKey;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
43
Wabbajack.Networking.Steam/DTOs/AppInfo.cs
Normal file
43
Wabbajack.Networking.Steam/DTOs/AppInfo.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Wabbajack.DTOs.JsonConverters;
|
||||||
|
|
||||||
|
namespace Wabbajack.Networking.Steam.DTOs;
|
||||||
|
|
||||||
|
|
||||||
|
public class AppInfo
|
||||||
|
{
|
||||||
|
|
||||||
|
[JsonPropertyName("depots")]
|
||||||
|
public Dictionary<string, Depot> Depots { get; set; } = new();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Depot
|
||||||
|
{
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("config")]
|
||||||
|
public DepotConfig Config { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("maxsize")]
|
||||||
|
public ulong MaxSize { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("depotfromapp")]
|
||||||
|
public uint DepotFromApp { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("sharedinstall")]
|
||||||
|
public uint SharedInstall { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("manifests")]
|
||||||
|
public Dictionary<string, string> Manifests { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DepotConfig
|
||||||
|
{
|
||||||
|
[JsonPropertyName("oslist")]
|
||||||
|
public string OSList { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("language")]
|
||||||
|
public string Language { get; set; }
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SteamKit2;
|
||||||
|
using Wabbajack.Networking.Steam.DTOs;
|
||||||
|
|
||||||
namespace Wabbajack.Networking.Steam;
|
namespace Wabbajack.Networking.Steam;
|
||||||
|
|
||||||
@ -29,4 +31,9 @@ public class DepotDownloader
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<AppInfo> GetAppInfo(uint appId)
|
||||||
|
{
|
||||||
|
return await _steamClient.GetAppInfo(appId);
|
||||||
|
}
|
||||||
}
|
}
|
47
Wabbajack.Networking.Steam/KeyValueTranslator.cs
Normal file
47
Wabbajack.Networking.Steam/KeyValueTranslator.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using SteamKit2;
|
||||||
|
using Wabbajack.DTOs.JsonConverters;
|
||||||
|
|
||||||
|
namespace Wabbajack.Networking.Steam;
|
||||||
|
|
||||||
|
public class KeyValueTranslator
|
||||||
|
{
|
||||||
|
public static void Translate(Utf8JsonWriter wtr, List<KeyValue> kvs)
|
||||||
|
{
|
||||||
|
foreach (var kv in kvs)
|
||||||
|
{
|
||||||
|
wtr.WritePropertyName(kv.Name!);
|
||||||
|
if (kv.Value == null)
|
||||||
|
{
|
||||||
|
wtr.WriteStartObject();
|
||||||
|
Translate(wtr, kv.Children);
|
||||||
|
wtr.WriteEndObject();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wtr.WriteStringValue(kv.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static T Translate<T>(KeyValue kv, DTOSerializer dtos)
|
||||||
|
{
|
||||||
|
var ms = new MemoryStream();
|
||||||
|
var wtr = new Utf8JsonWriter(ms, new JsonWriterOptions()
|
||||||
|
{
|
||||||
|
Indented = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
wtr.WriteStartObject();
|
||||||
|
Translate(wtr, kv.Children);
|
||||||
|
wtr.WriteEndObject();
|
||||||
|
wtr.Flush();
|
||||||
|
|
||||||
|
var str = Encoding.UTF8.GetString(ms.ToArray());
|
||||||
|
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<T>(str, dtos.Options)!;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user