Skip to content

Commit

Permalink
rewrite online installation info creation
Browse files Browse the repository at this point in the history
  • Loading branch information
ZingBallyhoo committed Dec 2, 2023
1 parent 6dfd3dd commit 9c2513a
Show file tree
Hide file tree
Showing 12 changed files with 265 additions and 304 deletions.
31 changes: 18 additions & 13 deletions TACTLib/Client/ClientHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
using TACTLib.Helpers;
using TACTLib.Protocol;
using TACTLib.Protocol.NGDP;
using TACTLib.Protocol.Ribbit;

// ReSharper disable NotAccessedField.Global

Expand Down Expand Up @@ -56,7 +55,7 @@ public class ClientHandler {
/// <seealso cref="ClientCreateArgs.ProductDatabaseFilename"/>
public readonly ProductInstall? AgentProduct;

public readonly INetworkHandler? NetHandle;
public readonly CDNClient? CDNClient;

/// <summary>The base path of the container. E.g where the game executables are.</summary>
public readonly string BasePath;
Expand Down Expand Up @@ -128,13 +127,9 @@ public ClientHandler(string? basePath, ClientCreateArgs createArgs) {
InstallationInfoFile = new InstallationInfoFile(installationInfoPath);
}

if (CreateArgs.Online) {
using var _ = new PerfCounter("INetworkHandler::ctor`ClientHandler");
if (CreateArgs.OnlineRootHost.StartsWith("ribbit:")) {
NetHandle = new RibbitCDNClient(this);
} else {
NetHandle = new NGDPClient(this);
}
if (CreateArgs.Online)
{
CDNClient = new CDNClient(this);
}

if (IsStaticContainer) {
Expand All @@ -144,8 +139,18 @@ public ClientHandler(string? basePath, ClientCreateArgs createArgs) {
} else if (CreateArgs.VersionSource == ClientCreateArgs.InstallMode.Local) {
InstallationInfo = new InstallationInfo(InstallationInfoFile!.Values, ProductCode!);
} else {
using var _ = new PerfCounter("InstallationInfo::ctor`INetworkHandler");
InstallationInfo = new InstallationInfo(NetHandle!, CreateArgs.OnlineRegion);
CreateArgs.OnlineRootHost = ClientCreateArgs.EU_NGDP;

NGDPClientBase ngdpClient;
if (CreateArgs.OnlineRootHost.StartsWith("ribbit:")) {
ngdpClient = new RibbitClient(CreateArgs.OnlineRootHost);
} else {
ngdpClient = new NGDPClient(CreateArgs.OnlineRootHost);
}

using var _ = new PerfCounter("NGDPClientBase::CreateInstallationInfo`string`string");
var installationInfoData = ngdpClient.CreateInstallationInfo(GetProduct()!, CreateArgs.OnlineRegion);
InstallationInfo = new InstallationInfo(installationInfoData);
}

if (CreateArgs.OverrideBuildConfig != null) {
Expand Down Expand Up @@ -334,7 +339,7 @@ private bool CanShareCDNData([NotNullWhen(true)] ClientHandler? other) {
private Stream? TryOpenRemoteLooseFile(FullEKey fullKey) {
if (CDNIndex == null) return null;
if (!CDNIndex.IsLooseFile(fullKey)) return null;
var encodedData = NetHandle!.OpenData(fullKey);
var encodedData = CDNClient!.OpenData(fullKey);
if (encodedData == null) throw new Exception($"failed to fetch loose cdn file {fullKey.ToHexString()}");
return TryDecodeToStream(encodedData);
}
Expand All @@ -360,7 +365,7 @@ private bool CanShareCDNData([NotNullWhen(true)] ClientHandler? other) {
}
}

return CreateArgs.Online ? NetHandle!.OpenConfig(key) : null;
return CreateArgs.Online ? CDNClient!.OpenConfig(key) : null;
}

public string? GetProduct() => ProductCode;
Expand Down
23 changes: 12 additions & 11 deletions TACTLib/Config/InstallationInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,22 @@ public InstallationInfo(List<Dictionary<string, string>> vals, string product) {
}))}])");
}

public InstallationInfo(INetworkHandler netHandle, string region) {
Values = netHandle.CreateInstallationInfo(region);
}

public static List<Dictionary<string, string>> ParseToDict(TextReader reader) {
string[]? keys = null;
List<Dictionary<string, string>> ret = new List<Dictionary<string, string>>();
var valueDicts = new List<Dictionary<string, string>>();

string? line;
while ((line = reader.ReadLine()?.Trim()) != null) {
if (line.Length == 0) {
if (line.Length == 0)
{
continue;
}
if (line.StartsWith("##"))
{
continue;
}

string[] tokens = line.Split('|');
var tokens = line.Split('|');

if (keys == null) {
keys = new string[tokens.Length];
Expand All @@ -71,16 +72,16 @@ public static List<Dictionary<string, string>> ParseToDict(TextReader reader) {
keys[j] = tokens[j].Split('!')[0].Replace(" ", "");
}
} else {
Dictionary<string, string> vals = new Dictionary<string, string>();
var values = new Dictionary<string, string>();
for (var j = 0; j < tokens.Length; ++j) {
vals[keys[j]] = tokens[j];
values[keys[j]] = tokens[j];
}

ret.Add(vals);
valueDicts.Add(values);
}
}

return ret;
return valueDicts;
}
}
}
37 changes: 1 addition & 36 deletions TACTLib/Config/InstallationInfoFile.cs
Original file line number Diff line number Diff line change
@@ -1,48 +1,13 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using TACTLib.Protocol;

namespace TACTLib.Config {
public class InstallationInfoFile {
public readonly List<Dictionary<string, string>> Values;

public InstallationInfoFile(string path) {
using StreamReader reader = new StreamReader(path);
using var reader = new StreamReader(path);
Values = InstallationInfo.ParseToDict(reader);
}

public InstallationInfoFile(INetworkHandler netHandle, string region) {
Values = new List<Dictionary<string, string>> { netHandle.CreateInstallationInfo(region) };
}

/// <summary>
/// Returns an <see cref="InstallationInfo"/> for the given product if it exists, returns the first active entry for the specified product
/// Optionally you can specify a region if you want to get the installation info for a specific region
/// </summary>
/// <param name="product">product code</param>
/// <param name="region">optional region</param>
public InstallationInfo? GetInstallationInfoForProduct(string? product, string? region = null) {
if (string.IsNullOrEmpty(product)) {
return null;
}

Dictionary<string, string>? data;
if (!string.IsNullOrEmpty(region)) {
data = Values.FirstOrDefault(x => CollectionExtensions.GetValueOrDefault<string, string>(x, "Product") == product && CollectionExtensions.GetValueOrDefault<string, string>(x, "Branch") == region);
} else {
data = Values.OrderByDescending(x => x.GetValueOrDefault("Active") == "1").FirstOrDefault(x => x.GetValueOrDefault("Product") == product);
}

return data != null ? new InstallationInfo(data) : null;
}

/// <summary>
/// Returns an <see cref="InstallationInfo"/> for the first active (or not active) entry if one exists
/// </summary>
public InstallationInfo? GetFirstOrDefault() {
var data = Values.OrderByDescending(x => x["Active"] == "1").FirstOrDefault();
return data != null ? new InstallationInfo(data) : null;
}
}
}
74 changes: 41 additions & 33 deletions TACTLib/Protocol/CDNClient.cs
Original file line number Diff line number Diff line change
@@ -1,65 +1,73 @@
using System.Collections.Generic;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using TACTLib.Client;

namespace TACTLib.Protocol {
public abstract class CDNClient : INetworkHandler {
protected readonly ClientHandler client;

public class CDNClient
{
private static readonly HttpClientHandler s_httpClientHandler = new HttpClientHandler
{
// unlikely to be supported by cdn but...
AutomaticDecompression = DecompressionMethods.All
};
private static readonly HttpClient s_httpClient = new HttpClient(s_httpClientHandler);
public static readonly HttpClient s_httpClient = new HttpClient(s_httpClientHandler);

private readonly ClientHandler m_client;

protected CDNClient(ClientHandler handler) {
client = handler;
public CDNClient(ClientHandler handler)
{
m_client = handler;
}

public abstract Dictionary<string, string> CreateInstallationInfo(string region);

public byte[]? OpenData(CKey key) {
// todo: caching
public byte[]? OpenData(CKey key)
{
return FetchCDN("data", key.ToHexString());
}

public Stream? OpenConfig(string key) {
// todo: caching
public Stream? OpenConfig(string key)
{
var data = FetchCDN("config", key);
if (data == null) return null;
return new MemoryStream(data);
}

public byte[]? FetchCDN(string type, string key, (int, int)? range=null, string? suffix=null) {
key = key.ToLower();
var hosts = client.InstallationInfo.Values["CDNHosts"].Split(' ');
foreach (var host in hosts) {
try {
if (host == "cdn.blizzard.com" || host == "us.cdn.blizzard.com") {
continue; // satan was here
}
var url = $"http://{host}/{client.InstallationInfo.Values["CDNPath"]}/{type}/{key.Substring(0, 2)}/{key.Substring(2, 2)}/{key}";
Logger.Info("CDN", $"Fetching file {url}");
if (suffix != null) url += suffix;
public byte[]? FetchCDN(string type, string key, (int start, int end)? range=null, string? suffix=null)
{
key = key.ToLowerInvariant();

var hosts = m_client.InstallationInfo.Values["CDNHosts"].Split(' ');
foreach (var host in hosts)
{
if (host.EndsWith("cdn.blizzard.com"))
{
continue; // these still dont work very well..
}

var url = $"http://{host}/{m_client.InstallationInfo.Values["CDNPath"]}/{type}/{key.AsSpan(0, 2)}/{key.AsSpan(2, 2)}/{key}{suffix}";
Logger.Info("CDN", $"Fetching file {url}");

var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
if (range != null)
var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
if (range != null)
{
requestMessage.Headers.Range = new RangeHeaderValue(range.Value.start, range.Value.end);
}

try
{
using var response = s_httpClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead);
if (!response.IsSuccessStatusCode)
{
requestMessage.Headers.Range = new RangeHeaderValue(range.Value.Item1, range.Value.Item2);
continue;
}

using var response = s_httpClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead);
if (!response.IsSuccessStatusCode) continue;

var result = response.Content.ReadAsByteArrayAsync().Result;

var result = response.Content.ReadAsByteArrayAsync().Result; // todo: async over sync
return result;
} catch {
} catch (Exception e) {
// ignored
Logger.Debug("CDN", $"Error fetching {url}: {e}");
}
}

Expand Down
4 changes: 2 additions & 2 deletions TACTLib/Protocol/CDNIndexHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ private void DownloadIndexFile(string archive, int i)
{
try
{
var cdn = (CDNClient) Client.NetHandle!;
var cdn = Client.CDNClient!;
var indexData = cdn.FetchCDN("data", archive, null, ".index");
if (indexData == null) throw new Exception($"failed to fetch archive index data for {archive} (index {i})");
using var indexDataStream = new MemoryStream(indexData);
Expand Down Expand Up @@ -321,7 +321,7 @@ public bool IsLooseFile(FullKey key) {
{
var archive = Client.ConfigHandler.CDNConfig.Archives[entry.Index];

var cdn = (CDNClient) Client.NetHandle!;
var cdn = Client.CDNClient!;
var stream = cdn.FetchCDN("data", archive, ((int)entry.Offset, (int)entry.Offset + (int)entry.Size - 1));
return stream;
}
Expand Down
10 changes: 0 additions & 10 deletions TACTLib/Protocol/INetworkHandler.cs

This file was deleted.

Loading

0 comments on commit 9c2513a

Please sign in to comment.