Get get manifests and decrypt filenames

This commit is contained in:
Timothy Baldridge 2022-01-08 18:19:08 -07:00
parent ac1f4a7427
commit 3304b2a584
5 changed files with 227 additions and 4 deletions

View File

@ -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;
} }
} }

View File

@ -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;
}
} }

View 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; }
}

View File

@ -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);
}
} }

View 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)!;
}
}