From b35dd864a00d071ae8cd6569e96bf52b0b5f2ed8 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sat, 27 May 2023 11:18:54 +0100 Subject: [PATCH 01/34] bump to net7_0 --- BNetInstaller/BNetInstaller.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BNetInstaller/BNetInstaller.csproj b/BNetInstaller/BNetInstaller.csproj index c975b7e..0e28fdf 100644 --- a/BNetInstaller/BNetInstaller.csproj +++ b/BNetInstaller/BNetInstaller.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net7.0 From 31482fab396fdfa9fed4eb7d5ab1367c6938240d Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sat, 27 May 2023 11:19:22 +0100 Subject: [PATCH 02/34] rename Requester to something more appropriate --- BNetInstaller/AgentApp.cs | 21 +++---- .../{Requester.cs => AgentClient.cs} | 63 ++++++++++++------- 2 files changed, 51 insertions(+), 33 deletions(-) rename BNetInstaller/{Requester.cs => AgentClient.cs} (53%) diff --git a/BNetInstaller/AgentApp.cs b/BNetInstaller/AgentApp.cs index ce36b5f..476e04e 100644 --- a/BNetInstaller/AgentApp.cs +++ b/BNetInstaller/AgentApp.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.IO; using BNetInstaller.Endpoints.Agent; -using BNetInstaller.Endpoints.Game; using BNetInstaller.Endpoints.Install; using BNetInstaller.Endpoints.Repair; using BNetInstaller.Endpoints.Update; @@ -11,20 +10,19 @@ namespace BNetInstaller; -internal class AgentApp : IDisposable +internal sealed class AgentApp : IDisposable { public readonly AgentEndpoint AgentEndpoint; public readonly InstallEndpoint InstallEndpoint; public readonly UpdateEndpoint UpdateEndpoint; public readonly RepairEndpoint RepairEndpoint; - public readonly GameEndpoint GameEndpoint; public readonly VersionEndpoint VersionEndpoint; private readonly string AgentPath; private readonly int Port = 5050; private Process Process; - private Requester Requester; + private AgentClient Client; public AgentApp() { @@ -36,12 +34,11 @@ public AgentApp() Environment.Exit(0); } - AgentEndpoint = new(Requester); - InstallEndpoint = new(Requester); - UpdateEndpoint = new(Requester); - RepairEndpoint = new(Requester); - GameEndpoint = new(Requester); - VersionEndpoint = new(Requester); + AgentEndpoint = new(Client); + InstallEndpoint = new(Client); + UpdateEndpoint = new(Client); + RepairEndpoint = new(Client); + VersionEndpoint = new(Client); } private bool StartProcess() @@ -55,7 +52,7 @@ private bool StartProcess() try { Process = Process.Start(AgentPath, $"--port={Port}"); - Requester = new Requester(Port); + Client = new AgentClient(Port); return true; } catch (Win32Exception) @@ -70,7 +67,7 @@ public void Dispose() if (Process?.HasExited == false) Process.Kill(); - Requester?.Dispose(); + Client?.Dispose(); Process?.Dispose(); } } diff --git a/BNetInstaller/Requester.cs b/BNetInstaller/AgentClient.cs similarity index 53% rename from BNetInstaller/Requester.cs rename to BNetInstaller/AgentClient.cs index 0fd2ee4..3d057d3 100644 --- a/BNetInstaller/Requester.cs +++ b/BNetInstaller/AgentClient.cs @@ -3,31 +3,25 @@ using System.Net.Http; using System.Threading.Tasks; using BNetInstaller.Constants; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; +using System.Text.Json; namespace BNetInstaller; -internal class Requester : IDisposable +internal sealed class AgentClient : IDisposable { - public string BaseAddress { get; } - private readonly HttpClient _client; - private readonly JsonSerializerSettings _serializerSettings; + private readonly JsonSerializerOptions _serializerOptions; - public Requester(int port) + public AgentClient(int port) { - BaseAddress = $"http://127.0.0.1:{port}/{{0}}"; - _client = new(); _client.DefaultRequestHeaders.Add("User-Agent", "phoenix-agent/1.0"); + _client.BaseAddress = new Uri($"http://127.0.0.1:{port}"); - _serializerSettings = new() + _serializerOptions = new() { - ContractResolver = new DefaultContractResolver() - { - NamingStrategy = new SnakeCaseNamingStrategy() - } + PropertyNamingPolicy = SnakeCaseNamingPolicy.Instance, + DictionaryKeyPolicy = SnakeCaseNamingPolicy.Instance, }; } @@ -38,13 +32,13 @@ public void SetAuthorization(string authorization) public async Task SendAsync(string endpoint, HttpVerb verb, string content = null) { - var url = string.Format(BaseAddress, endpoint); - var request = new HttpRequestMessage(new(verb.ToString()), url); + var request = new HttpRequestMessage(new(verb.ToString()), endpoint); if (verb != HttpVerb.GET && !string.IsNullOrEmpty(content)) request.Content = new StringContent(content); var response = await _client.SendAsync(request); + if (!response.IsSuccessStatusCode) await HandleRequestFailure(response); @@ -53,11 +47,10 @@ public async Task SendAsync(string endpoint, HttpVerb verb, public async Task SendAsync(string endpoint, HttpVerb verb, T payload = null) where T : class { - var content = payload != null ? - JsonConvert.SerializeObject(payload, _serializerSettings) : - null; - - return await SendAsync(endpoint, verb, content); + if (payload == null) + return await SendAsync(endpoint, verb); + else + return await SendAsync(endpoint, verb, JsonSerializer.Serialize(payload, _serializerOptions)); } private static async Task HandleRequestFailure(HttpResponseMessage response) @@ -73,3 +66,31 @@ public void Dispose() _client.Dispose(); } } + +file class SnakeCaseNamingPolicy : JsonNamingPolicy +{ + public static readonly SnakeCaseNamingPolicy Instance = new(); + + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) + return string.Empty; + + Span lower = stackalloc char[0x100]; + Span output = stackalloc char[0x100]; + + name.AsSpan().ToLowerInvariant(lower); + + var length = 0; + + for (var i = 0; i < name.Length; i++) + { + if (i != 0 && name[i] is >= 'A' and <= 'Z') + output[length++] = '_'; + + output[length++] = lower[i]; + } + + return new string(output[..length]); + } +} From 91665e4c8eb3a2d423cb1138cd8c0d1f492aa40a Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sat, 27 May 2023 11:22:28 +0100 Subject: [PATCH 03/34] rename Options.Create more appropriately --- BNetInstaller/Options.cs | 4 ++-- BNetInstaller/Program.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BNetInstaller/Options.cs b/BNetInstaller/Options.cs index 31d3e86..77931cd 100644 --- a/BNetInstaller/Options.cs +++ b/BNetInstaller/Options.cs @@ -5,7 +5,7 @@ namespace BNetInstaller; -internal class Options +internal sealed class Options { [Option("prod", Required = true, HelpText = "TACT Product")] public string Product { get; set; } @@ -37,7 +37,7 @@ public void Sanitise() Directory = Directory.Replace("/", "\\").Trim().TrimEnd('\\') + '\\'; } - public static string[] Generate() + public static string[] Create() { static string GetInput(string message) { diff --git a/BNetInstaller/Program.cs b/BNetInstaller/Program.cs index 5e9ef55..aa4c915 100644 --- a/BNetInstaller/Program.cs +++ b/BNetInstaller/Program.cs @@ -11,7 +11,7 @@ internal static class Program private static async Task Main(string[] args) { if (args == null || args.Length == 0) - args = Options.Generate(); + args = Options.Create(); using Parser parser = new(s => { From 297d7be5101755bc977a40e4b5ee898fadecfe89 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sat, 27 May 2023 11:33:14 +0100 Subject: [PATCH 04/34] remove old endpoint --- BNetInstaller/Endpoints/Game/GameEndpoint.cs | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 BNetInstaller/Endpoints/Game/GameEndpoint.cs diff --git a/BNetInstaller/Endpoints/Game/GameEndpoint.cs b/BNetInstaller/Endpoints/Game/GameEndpoint.cs deleted file mode 100644 index 9eb6f14..0000000 --- a/BNetInstaller/Endpoints/Game/GameEndpoint.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Threading.Tasks; -using BNetInstaller.Constants; -using Newtonsoft.Json.Linq; - -namespace BNetInstaller.Endpoints.Game; - -internal class GameEndpoint : BaseEndpoint -{ - public GameEndpoint(Requester requester) : base("game", requester) - { - } - - public async Task Get(string uid) - { - using var response = await Requester.SendAsync(Endpoint + "/" + uid, HttpVerb.GET); - return await Deserialize(response); - } -} From 824ec095efda8522fbc173e9f6ecb27f8f69faab Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sat, 27 May 2023 11:48:16 +0100 Subject: [PATCH 05/34] prep models for new endpoint implementation --- BNetInstaller/Models/IModel.cs | 5 +++++ BNetInstaller/Models/InstallModel.cs | 2 +- BNetInstaller/Models/NullModel.cs | 5 +++++ BNetInstaller/Models/PriorityModel.cs | 2 +- BNetInstaller/Models/ProductModel.cs | 2 +- BNetInstaller/Models/UidModel.cs | 2 +- 6 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 BNetInstaller/Models/IModel.cs create mode 100644 BNetInstaller/Models/NullModel.cs diff --git a/BNetInstaller/Models/IModel.cs b/BNetInstaller/Models/IModel.cs new file mode 100644 index 0000000..f183bd3 --- /dev/null +++ b/BNetInstaller/Models/IModel.cs @@ -0,0 +1,5 @@ +namespace BNetInstaller.Models; + +internal interface IModel +{ +} diff --git a/BNetInstaller/Models/InstallModel.cs b/BNetInstaller/Models/InstallModel.cs index 62dae53..cde051e 100644 --- a/BNetInstaller/Models/InstallModel.cs +++ b/BNetInstaller/Models/InstallModel.cs @@ -1,6 +1,6 @@ namespace BNetInstaller.Models; -internal class InstallModel : ProductPriorityModel +internal sealed class InstallModel : ProductPriorityModel { public string[] InstructionsDataset { get; set; } public string InstructionsPatchUrl { get; set; } diff --git a/BNetInstaller/Models/NullModel.cs b/BNetInstaller/Models/NullModel.cs new file mode 100644 index 0000000..59c9106 --- /dev/null +++ b/BNetInstaller/Models/NullModel.cs @@ -0,0 +1,5 @@ +namespace BNetInstaller.Models; + +internal sealed class NullModel : IModel +{ +} diff --git a/BNetInstaller/Models/PriorityModel.cs b/BNetInstaller/Models/PriorityModel.cs index b294c26..36500a0 100644 --- a/BNetInstaller/Models/PriorityModel.cs +++ b/BNetInstaller/Models/PriorityModel.cs @@ -1,6 +1,6 @@ namespace BNetInstaller.Models; -internal class PriorityModel +internal sealed class PriorityModel : IModel { public bool InsertAtHead { get; set; } = true; public double Value { get; set; } = 900; diff --git a/BNetInstaller/Models/ProductModel.cs b/BNetInstaller/Models/ProductModel.cs index 00a7e65..c11e848 100644 --- a/BNetInstaller/Models/ProductModel.cs +++ b/BNetInstaller/Models/ProductModel.cs @@ -1,6 +1,6 @@ namespace BNetInstaller.Models; -internal class ProductModel +internal sealed class ProductModel : IModel { public string AccountCountry { get; set; } = "USA"; public bool Finalized { get; set; } = true; diff --git a/BNetInstaller/Models/UidModel.cs b/BNetInstaller/Models/UidModel.cs index e86479f..074938d 100644 --- a/BNetInstaller/Models/UidModel.cs +++ b/BNetInstaller/Models/UidModel.cs @@ -1,6 +1,6 @@ namespace BNetInstaller.Models; -internal class UidModel +internal class UidModel : IModel { public string Uid { get; set; } } From ce2a1beeb3bb69522daf887079fd7f955a4d4f73 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sat, 27 May 2023 14:53:17 +0100 Subject: [PATCH 06/34] replace JSON.net and reimplement endpoint structure --- BNetInstaller/BNetInstaller.csproj | 1 - .../Endpoints/Agent/AgentEndpoint.cs | 26 +++------- BNetInstaller/Endpoints/BaseEndpoint.cs | 51 +++++++++++++++---- .../Endpoints/BaseProductEndpoint.cs | 21 ++++++++ .../Endpoints/Install/InstallEndpoint.cs | 27 +++------- BNetInstaller/Endpoints/ProductEndpoint.cs | 33 +++--------- .../Endpoints/Repair/RepairEndpoint.cs | 27 ++-------- .../Endpoints/Update/UpdateEndpoint.cs | 27 ++-------- .../Endpoints/Version/VersionEndpoint.cs | 21 +++----- BNetInstaller/Program.cs | 6 +-- 10 files changed, 100 insertions(+), 140 deletions(-) create mode 100644 BNetInstaller/Endpoints/BaseProductEndpoint.cs diff --git a/BNetInstaller/BNetInstaller.csproj b/BNetInstaller/BNetInstaller.csproj index 0e28fdf..8bc6087 100644 --- a/BNetInstaller/BNetInstaller.csproj +++ b/BNetInstaller/BNetInstaller.csproj @@ -7,7 +7,6 @@ - diff --git a/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs b/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs index e10d2fd..04e498b 100644 --- a/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs +++ b/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs @@ -1,35 +1,23 @@ using System; -using System.Threading.Tasks; -using BNetInstaller.Constants; -using Newtonsoft.Json.Linq; +using System.Text.Json.Nodes; +using BNetInstaller.Models; namespace BNetInstaller.Endpoints.Agent; -internal class AgentEndpoint : BaseEndpoint +internal sealed class AgentEndpoint : BaseEndpoint { - public AgentEndpoint(Requester requester) : base("agent", requester) + public AgentEndpoint(AgentClient client) : base("agent", client) { } - public async Task Delete() + protected override void ValidateResponse(JsonNode response, string content) { - await Requester.SendAsync(Endpoint, HttpVerb.DELETE); - } - - public async Task Get() - { - using var response = await Requester.SendAsync(Endpoint, HttpVerb.GET); - return await Deserialize(response); - } - - protected override void ValidateResponse(JToken response, string content) - { - var token = response.Value("authorization"); + var token = response["authorization"]?.GetValue(); if (string.IsNullOrEmpty(token)) throw new Exception("Agent Error: Unable to authenticate.", new(content)); - Requester.SetAuthorization(token); + Client.SetAuthorization(token); base.ValidateResponse(response, content); } } diff --git a/BNetInstaller/Endpoints/BaseEndpoint.cs b/BNetInstaller/Endpoints/BaseEndpoint.cs index 69aa236..f32e73d 100644 --- a/BNetInstaller/Endpoints/BaseEndpoint.cs +++ b/BNetInstaller/Endpoints/BaseEndpoint.cs @@ -1,33 +1,66 @@ using System; using System.Net.Http; +using System.Text.Json.Nodes; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; +using BNetInstaller.Constants; +using BNetInstaller.Models; namespace BNetInstaller.Endpoints; -internal abstract class BaseEndpoint +internal abstract class BaseEndpoint where T : class, IModel, new() { public string Endpoint { get; } + public T Model { get; } - protected Requester Requester { get; } + protected AgentClient Client { get; } - protected BaseEndpoint(string endpoint, Requester requester) + protected BaseEndpoint(string endpoint, AgentClient client) { Endpoint = endpoint; - Requester = requester; + Client = client; + Model = new(); } - protected async Task Deserialize(HttpResponseMessage response) + public virtual async Task Get() + { + using var response = await Client.SendAsync(Endpoint, HttpVerb.GET); + return await Deserialize(response); + } + + public virtual async Task Post() + { + if (Model is NullModel) + return default; + + using var response = await Client.SendAsync(Endpoint, HttpVerb.POST, Model); + return await Deserialize(response); + } + + public virtual async Task Put() + { + if (Model is NullModel) + return default; + + using var response = await Client.SendAsync(Endpoint, HttpVerb.PUT, Model); + return await Deserialize(response); + } + + public virtual async Task Delete() + { + await Client.SendAsync(Endpoint, HttpVerb.DELETE, Model); + } + + protected async Task Deserialize(HttpResponseMessage response) { var content = await response.Content.ReadAsStringAsync(); - var result = JToken.Parse(content); + var result = JsonNode.Parse(content); ValidateResponse(result, content); return result; } - protected virtual void ValidateResponse(JToken response, string content) + protected virtual void ValidateResponse(JsonNode response, string content) { - var errorCode = response.Value("error"); + var errorCode = response["error"]?.GetValue(); if (errorCode > 0) throw new Exception($"Agent Error: {errorCode}", new(content)); } diff --git a/BNetInstaller/Endpoints/BaseProductEndpoint.cs b/BNetInstaller/Endpoints/BaseProductEndpoint.cs new file mode 100644 index 0000000..a773937 --- /dev/null +++ b/BNetInstaller/Endpoints/BaseProductEndpoint.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using BNetInstaller.Models; + +namespace BNetInstaller.Endpoints; + +internal abstract class BaseProductEndpoint : BaseEndpoint where T : class, IModel, new() +{ + public ProductEndpoint Product { get; private set; } + + protected BaseProductEndpoint(string endpoint, AgentClient client) : base(endpoint, client) + { + } + + public override async Task Post() + { + var content = await base.Post(); + Product = ProductEndpoint.CreateFromResponse(content, Client); + return content; + } +} diff --git a/BNetInstaller/Endpoints/Install/InstallEndpoint.cs b/BNetInstaller/Endpoints/Install/InstallEndpoint.cs index 864a0b5..2b0ed6f 100644 --- a/BNetInstaller/Endpoints/Install/InstallEndpoint.cs +++ b/BNetInstaller/Endpoints/Install/InstallEndpoint.cs @@ -1,32 +1,18 @@ using System; -using System.Threading.Tasks; -using BNetInstaller.Constants; +using System.Text.Json.Nodes; using BNetInstaller.Models; -using Newtonsoft.Json.Linq; namespace BNetInstaller.Endpoints.Install; -internal class InstallEndpoint : BaseEndpoint +internal sealed class InstallEndpoint : BaseProductEndpoint { - public InstallModel Model { get; } - public ProductEndpoint Product { get; private set; } - - public InstallEndpoint(Requester requester) : base("install", requester) + public InstallEndpoint(AgentClient client) : base("install", client) { - Model = new(); } - public async Task Post() + protected override void ValidateResponse(JsonNode response, string content) { - using var response = await Requester.SendAsync(Endpoint, HttpVerb.POST, Model); - var content = await Deserialize(response); - Product = ProductEndpoint.CreateFromResponse(content, Requester); - return content; - } - - protected override void ValidateResponse(JToken response, string content) - { - var agentError = response.Value("error"); + var agentError = response["error"]?.GetValue(); if (agentError > 0) { @@ -34,7 +20,8 @@ protected override void ValidateResponse(JToken response, string content) foreach (var section in SubSections) { var token = response["form"]?[section]; - var errorCode = token?.Value("error"); + var errorCode = token?["error"]?.GetValue(); + if (errorCode > 0) throw new Exception($"Agent Error: Unable to install - {errorCode} ({section}).", new(content)); } diff --git a/BNetInstaller/Endpoints/ProductEndpoint.cs b/BNetInstaller/Endpoints/ProductEndpoint.cs index 8c7bb15..36530d1 100644 --- a/BNetInstaller/Endpoints/ProductEndpoint.cs +++ b/BNetInstaller/Endpoints/ProductEndpoint.cs @@ -1,38 +1,21 @@ -using System.Threading.Tasks; -using BNetInstaller.Constants; +using System.Text.Json.Nodes; using BNetInstaller.Models; -using Newtonsoft.Json.Linq; namespace BNetInstaller.Endpoints; -internal class ProductEndpoint : BaseEndpoint +internal sealed class ProductEndpoint : BaseEndpoint { - public ProductModel Model { get; } - - public ProductEndpoint(string endpoint, Requester requester) : base(endpoint, requester) - { - Model = new(); - } - - public async Task Get() - { - using var response = await Requester.SendAsync(Endpoint, HttpVerb.GET); - return await Deserialize(response); - } - - public async Task Post() + public ProductEndpoint(string endpoint, AgentClient client) : base(endpoint, client) { - using var response = await Requester.SendAsync(Endpoint, HttpVerb.POST, Model); - return await Deserialize(response); } - public static ProductEndpoint CreateFromResponse(JToken content, Requester requester) + public static ProductEndpoint CreateFromResponse(JsonNode content, AgentClient client) { - var responseURI = content.Value("response_uri"); + var responseURI = content["response_uri"]?.GetValue(); - if (!string.IsNullOrEmpty(responseURI)) - return new(responseURI.TrimStart('/'), requester); + if (string.IsNullOrEmpty(responseURI)) + return null; - return null; + return new(responseURI.TrimStart('/'), client); } } diff --git a/BNetInstaller/Endpoints/Repair/RepairEndpoint.cs b/BNetInstaller/Endpoints/Repair/RepairEndpoint.cs index d14c930..c4c5e63 100644 --- a/BNetInstaller/Endpoints/Repair/RepairEndpoint.cs +++ b/BNetInstaller/Endpoints/Repair/RepairEndpoint.cs @@ -1,32 +1,11 @@ -using System.Threading.Tasks; -using BNetInstaller.Constants; -using BNetInstaller.Models; -using Newtonsoft.Json.Linq; +using BNetInstaller.Models; namespace BNetInstaller.Endpoints.Repair; -internal class RepairEndpoint : BaseEndpoint +internal sealed class RepairEndpoint : BaseProductEndpoint { - public ProductPriorityModel Model { get; } - public ProductEndpoint Product { get; private set; } - - public RepairEndpoint(Requester requester) : base("repair", requester) + public RepairEndpoint(AgentClient client) : base("repair", client) { - Model = new(); Model.Priority.Value = 1000; } - - public async Task Get() - { - using var response = await Requester.SendAsync(Endpoint, HttpVerb.GET); - return await Deserialize(response); - } - - public async Task Post() - { - using var response = await Requester.SendAsync(Endpoint, HttpVerb.POST, Model); - var content = await Deserialize(response); - Product = ProductEndpoint.CreateFromResponse(content, Requester); - return content; - } } diff --git a/BNetInstaller/Endpoints/Update/UpdateEndpoint.cs b/BNetInstaller/Endpoints/Update/UpdateEndpoint.cs index 4ea0e8f..28a1c33 100644 --- a/BNetInstaller/Endpoints/Update/UpdateEndpoint.cs +++ b/BNetInstaller/Endpoints/Update/UpdateEndpoint.cs @@ -1,32 +1,11 @@ -using System.Threading.Tasks; -using BNetInstaller.Constants; -using BNetInstaller.Models; -using Newtonsoft.Json.Linq; +using BNetInstaller.Models; namespace BNetInstaller.Endpoints.Update; -internal class UpdateEndpoint : BaseEndpoint +internal sealed class UpdateEndpoint : BaseProductEndpoint { - public ProductPriorityModel Model { get; } - public ProductEndpoint Product { get; private set; } - - public UpdateEndpoint(Requester requester) : base("update", requester) + public UpdateEndpoint(AgentClient client) : base("update", client) { - Model = new(); Model.Priority.Value = 699; } - - public async Task Get() - { - using var response = await Requester.SendAsync(Endpoint, HttpVerb.GET); - return await Deserialize(response); - } - - public async Task Post() - { - using var response = await Requester.SendAsync(Endpoint, HttpVerb.POST, Model); - var content = await Deserialize(response); - Product = ProductEndpoint.CreateFromResponse(content, Requester); - return content; - } } diff --git a/BNetInstaller/Endpoints/Version/VersionEndpoint.cs b/BNetInstaller/Endpoints/Version/VersionEndpoint.cs index 7f83083..0310d52 100644 --- a/BNetInstaller/Endpoints/Version/VersionEndpoint.cs +++ b/BNetInstaller/Endpoints/Version/VersionEndpoint.cs @@ -1,28 +1,19 @@ -using System.Threading.Tasks; +using System.Text.Json.Nodes; +using System.Threading.Tasks; using BNetInstaller.Constants; using BNetInstaller.Models; -using Newtonsoft.Json.Linq; namespace BNetInstaller.Endpoints.Version; -internal class VersionEndpoint : BaseEndpoint +internal sealed class VersionEndpoint : BaseEndpoint { - public UidModel Model { get; } - - public VersionEndpoint(Requester requester) : base("version", requester) + public VersionEndpoint(AgentClient client) : base("version", client) { - Model = new(); - } - - public async Task Get() - { - using var response = await Requester.SendAsync(Endpoint + "/" + Model.Uid, HttpVerb.GET); - return await Deserialize(response); } - public async Task Post() + public override async Task Get() { - using var response = await Requester.SendAsync(Endpoint, HttpVerb.POST, Model); + using var response = await Client.SendAsync(Endpoint + "/" + Model.Uid, HttpVerb.GET); return await Deserialize(response); } } diff --git a/BNetInstaller/Program.cs b/BNetInstaller/Program.cs index aa4c915..9b4e64e 100644 --- a/BNetInstaller/Program.cs +++ b/BNetInstaller/Program.cs @@ -110,13 +110,13 @@ static void Print(string label, object value) => var stats = await endpoint.Get(); // check for completion - var complete = stats.Value("download_complete"); + var complete = stats["download_complete"]?.GetValue(); if (complete == true) return true; // get progress percentage and playability - var progress = stats.Value("progress"); - var playable = stats.Value("playable"); + var progress = stats["progress"]?.GetValue(); + var playable = stats["playable"]?.GetValue(); if (!progress.HasValue) return false; From b3102462403e3c91085783bc0b22341cc8880104 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sat, 27 May 2023 14:53:46 +0100 Subject: [PATCH 07/34] bump CommandLineParser --- BNetInstaller/BNetInstaller.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BNetInstaller/BNetInstaller.csproj b/BNetInstaller/BNetInstaller.csproj index 8bc6087..5bf2b27 100644 --- a/BNetInstaller/BNetInstaller.csproj +++ b/BNetInstaller/BNetInstaller.csproj @@ -6,7 +6,7 @@ - + From 74da8f5d5510ba5fe85c167e158cf89126661bdc Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sat, 27 May 2023 14:58:46 +0100 Subject: [PATCH 08/34] use new net7_0 regex generator --- BNetInstaller/Options.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/BNetInstaller/Options.cs b/BNetInstaller/Options.cs index 77931cd..266b71b 100644 --- a/BNetInstaller/Options.cs +++ b/BNetInstaller/Options.cs @@ -5,7 +5,7 @@ namespace BNetInstaller; -internal sealed class Options +internal sealed partial class Options { [Option("prod", Required = true, HelpText = "TACT Product")] public string Product { get; set; } @@ -30,7 +30,7 @@ public void Sanitise() // remove _locale suffix for wiki copy-pasters if (UID.Contains("_locale", StringComparison.OrdinalIgnoreCase)) - UID = Regex.Replace(UID, "\\(?_locale\\)?", $"_{Locale}", RegexOptions.IgnoreCase); + UID = LocaleSuffixRegex().Replace(UID, $"_{Locale}"); Product = Product.ToLowerInvariant().Trim(); UID = UID.ToLowerInvariant().Trim(); @@ -68,4 +68,7 @@ static string GetInput(string message) return args; } + + [GeneratedRegex("\\(?_locale\\)?", RegexOptions.IgnoreCase, "en-GB")] + private static partial Regex LocaleSuffixRegex(); } From bd974a9a060d29c3325c48b794848b43ed85d88a Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sat, 27 May 2023 15:15:24 +0100 Subject: [PATCH 09/34] use HttpMethod properly --- BNetInstaller/AgentClient.cs | 13 ++++++------- BNetInstaller/Constants/HttpVerb.cs | 9 --------- BNetInstaller/Endpoints/BaseEndpoint.cs | 9 ++++----- BNetInstaller/Endpoints/Version/VersionEndpoint.cs | 6 +++--- 4 files changed, 13 insertions(+), 24 deletions(-) delete mode 100644 BNetInstaller/Constants/HttpVerb.cs diff --git a/BNetInstaller/AgentClient.cs b/BNetInstaller/AgentClient.cs index 3d057d3..f1f6f32 100644 --- a/BNetInstaller/AgentClient.cs +++ b/BNetInstaller/AgentClient.cs @@ -2,7 +2,6 @@ using System.Diagnostics; using System.Net.Http; using System.Threading.Tasks; -using BNetInstaller.Constants; using System.Text.Json; namespace BNetInstaller; @@ -30,11 +29,11 @@ public void SetAuthorization(string authorization) _client.DefaultRequestHeaders.Add("Authorization", authorization); } - public async Task SendAsync(string endpoint, HttpVerb verb, string content = null) + public async Task SendAsync(string endpoint, HttpMethod method, string content = null) { - var request = new HttpRequestMessage(new(verb.ToString()), endpoint); + var request = new HttpRequestMessage(method, endpoint); - if (verb != HttpVerb.GET && !string.IsNullOrEmpty(content)) + if (method != HttpMethod.Get && !string.IsNullOrEmpty(content)) request.Content = new StringContent(content); var response = await _client.SendAsync(request); @@ -45,12 +44,12 @@ public async Task SendAsync(string endpoint, HttpVerb verb, return response; } - public async Task SendAsync(string endpoint, HttpVerb verb, T payload = null) where T : class + public async Task SendAsync(string endpoint, HttpMethod method, T payload = null) where T : class { if (payload == null) - return await SendAsync(endpoint, verb); + return await SendAsync(endpoint, method); else - return await SendAsync(endpoint, verb, JsonSerializer.Serialize(payload, _serializerOptions)); + return await SendAsync(endpoint, method, JsonSerializer.Serialize(payload, _serializerOptions)); } private static async Task HandleRequestFailure(HttpResponseMessage response) diff --git a/BNetInstaller/Constants/HttpVerb.cs b/BNetInstaller/Constants/HttpVerb.cs deleted file mode 100644 index 0034bf3..0000000 --- a/BNetInstaller/Constants/HttpVerb.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace BNetInstaller.Constants; - -internal enum HttpVerb -{ - GET, - PUT, - POST, - DELETE -} diff --git a/BNetInstaller/Endpoints/BaseEndpoint.cs b/BNetInstaller/Endpoints/BaseEndpoint.cs index f32e73d..f45a8d7 100644 --- a/BNetInstaller/Endpoints/BaseEndpoint.cs +++ b/BNetInstaller/Endpoints/BaseEndpoint.cs @@ -2,7 +2,6 @@ using System.Net.Http; using System.Text.Json.Nodes; using System.Threading.Tasks; -using BNetInstaller.Constants; using BNetInstaller.Models; namespace BNetInstaller.Endpoints; @@ -23,7 +22,7 @@ protected BaseEndpoint(string endpoint, AgentClient client) public virtual async Task Get() { - using var response = await Client.SendAsync(Endpoint, HttpVerb.GET); + using var response = await Client.SendAsync(Endpoint, HttpMethod.Get); return await Deserialize(response); } @@ -32,7 +31,7 @@ public virtual async Task Post() if (Model is NullModel) return default; - using var response = await Client.SendAsync(Endpoint, HttpVerb.POST, Model); + using var response = await Client.SendAsync(Endpoint, HttpMethod.Post, Model); return await Deserialize(response); } @@ -41,13 +40,13 @@ public virtual async Task Put() if (Model is NullModel) return default; - using var response = await Client.SendAsync(Endpoint, HttpVerb.PUT, Model); + using var response = await Client.SendAsync(Endpoint, HttpMethod.Put, Model); return await Deserialize(response); } public virtual async Task Delete() { - await Client.SendAsync(Endpoint, HttpVerb.DELETE, Model); + await Client.SendAsync(Endpoint, HttpMethod.Delete, Model); } protected async Task Deserialize(HttpResponseMessage response) diff --git a/BNetInstaller/Endpoints/Version/VersionEndpoint.cs b/BNetInstaller/Endpoints/Version/VersionEndpoint.cs index 0310d52..cf149d7 100644 --- a/BNetInstaller/Endpoints/Version/VersionEndpoint.cs +++ b/BNetInstaller/Endpoints/Version/VersionEndpoint.cs @@ -1,6 +1,6 @@ -using System.Text.Json.Nodes; +using System.Net.Http; +using System.Text.Json.Nodes; using System.Threading.Tasks; -using BNetInstaller.Constants; using BNetInstaller.Models; namespace BNetInstaller.Endpoints.Version; @@ -13,7 +13,7 @@ public VersionEndpoint(AgentClient client) : base("version", client) public override async Task Get() { - using var response = await Client.SendAsync(Endpoint + "/" + Model.Uid, HttpVerb.GET); + using var response = await Client.SendAsync(Endpoint + "/" + Model.Uid, HttpMethod.Get); return await Deserialize(response); } } From 0fbe72cf885565b0a7d24dcd4fccb21afcc4a4d1 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sat, 27 May 2023 15:29:18 +0100 Subject: [PATCH 10/34] minor refactoring + global usings --- BNetInstaller/AgentApp.cs | 24 ++++++++++--------- BNetInstaller/AgentClient.cs | 8 ++----- BNetInstaller/BNetInstaller.csproj | 7 ++++++ .../Endpoints/Agent/AgentEndpoint.cs | 6 +---- BNetInstaller/Endpoints/BaseEndpoint.cs | 8 +------ .../Endpoints/BaseProductEndpoint.cs | 6 +---- .../Endpoints/Install/InstallEndpoint.cs | 6 +---- BNetInstaller/Endpoints/ProductEndpoint.cs | 5 +--- .../Endpoints/Repair/RepairEndpoint.cs | 4 +--- .../Endpoints/Update/UpdateEndpoint.cs | 4 +--- .../Endpoints/Version/VersionEndpoint.cs | 7 +----- BNetInstaller/Options.cs | 3 +-- BNetInstaller/Program.cs | 4 +--- 13 files changed, 32 insertions(+), 60 deletions(-) diff --git a/BNetInstaller/AgentApp.cs b/BNetInstaller/AgentApp.cs index 476e04e..ce53340 100644 --- a/BNetInstaller/AgentApp.cs +++ b/BNetInstaller/AgentApp.cs @@ -1,7 +1,5 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; using System.Diagnostics; -using System.IO; using BNetInstaller.Endpoints.Agent; using BNetInstaller.Endpoints.Install; using BNetInstaller.Endpoints.Repair; @@ -12,6 +10,8 @@ namespace BNetInstaller; internal sealed class AgentApp : IDisposable { + public const int Port = 5050; + public readonly AgentEndpoint AgentEndpoint; public readonly InstallEndpoint InstallEndpoint; public readonly UpdateEndpoint UpdateEndpoint; @@ -19,21 +19,22 @@ internal sealed class AgentApp : IDisposable public readonly VersionEndpoint VersionEndpoint; private readonly string AgentPath; - private readonly int Port = 5050; - - private Process Process; - private AgentClient Client; + private readonly Process Process; + private readonly AgentClient Client; public AgentApp() { AgentPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Battle.net", "Agent", "Agent.exe"); - if (!StartProcess()) + if (!StartProcess(out var process)) { Console.WriteLine("Please ensure Battle.net is installed and has recently been opened."); Environment.Exit(0); } + Process = process; + Client = new(Port); + AgentEndpoint = new(Client); InstallEndpoint = new(Client); UpdateEndpoint = new(Client); @@ -41,22 +42,23 @@ public AgentApp() VersionEndpoint = new(Client); } - private bool StartProcess() + private bool StartProcess(out Process process) { if (!File.Exists(AgentPath)) { + process= null; Console.WriteLine("Unable to find Agent.exe."); return false; } try { - Process = Process.Start(AgentPath, $"--port={Port}"); - Client = new AgentClient(Port); + process = Process.Start(AgentPath, $"--port={Port}"); return true; } catch (Win32Exception) { + process = null; Console.WriteLine("Unable to start Agent.exe."); return false; } diff --git a/BNetInstaller/AgentClient.cs b/BNetInstaller/AgentClient.cs index f1f6f32..cf0bacf 100644 --- a/BNetInstaller/AgentClient.cs +++ b/BNetInstaller/AgentClient.cs @@ -1,8 +1,4 @@ -using System; -using System.Diagnostics; -using System.Net.Http; -using System.Threading.Tasks; -using System.Text.Json; +using System.Diagnostics; namespace BNetInstaller; @@ -15,7 +11,7 @@ public AgentClient(int port) { _client = new(); _client.DefaultRequestHeaders.Add("User-Agent", "phoenix-agent/1.0"); - _client.BaseAddress = new Uri($"http://127.0.0.1:{port}"); + _client.BaseAddress = new($"http://127.0.0.1:{port}"); _serializerOptions = new() { diff --git a/BNetInstaller/BNetInstaller.csproj b/BNetInstaller/BNetInstaller.csproj index 5bf2b27..e72b2ff 100644 --- a/BNetInstaller/BNetInstaller.csproj +++ b/BNetInstaller/BNetInstaller.csproj @@ -3,10 +3,17 @@ Exe net7.0 + enable + + + + + + diff --git a/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs b/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs index 04e498b..2b0e2e5 100644 --- a/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs +++ b/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs @@ -1,8 +1,4 @@ -using System; -using System.Text.Json.Nodes; -using BNetInstaller.Models; - -namespace BNetInstaller.Endpoints.Agent; +namespace BNetInstaller.Endpoints.Agent; internal sealed class AgentEndpoint : BaseEndpoint { diff --git a/BNetInstaller/Endpoints/BaseEndpoint.cs b/BNetInstaller/Endpoints/BaseEndpoint.cs index f45a8d7..51a7ced 100644 --- a/BNetInstaller/Endpoints/BaseEndpoint.cs +++ b/BNetInstaller/Endpoints/BaseEndpoint.cs @@ -1,10 +1,4 @@ -using System; -using System.Net.Http; -using System.Text.Json.Nodes; -using System.Threading.Tasks; -using BNetInstaller.Models; - -namespace BNetInstaller.Endpoints; +namespace BNetInstaller.Endpoints; internal abstract class BaseEndpoint where T : class, IModel, new() { diff --git a/BNetInstaller/Endpoints/BaseProductEndpoint.cs b/BNetInstaller/Endpoints/BaseProductEndpoint.cs index a773937..df3dc35 100644 --- a/BNetInstaller/Endpoints/BaseProductEndpoint.cs +++ b/BNetInstaller/Endpoints/BaseProductEndpoint.cs @@ -1,8 +1,4 @@ -using System.Text.Json.Nodes; -using System.Threading.Tasks; -using BNetInstaller.Models; - -namespace BNetInstaller.Endpoints; +namespace BNetInstaller.Endpoints; internal abstract class BaseProductEndpoint : BaseEndpoint where T : class, IModel, new() { diff --git a/BNetInstaller/Endpoints/Install/InstallEndpoint.cs b/BNetInstaller/Endpoints/Install/InstallEndpoint.cs index 2b0ed6f..6ae8c6d 100644 --- a/BNetInstaller/Endpoints/Install/InstallEndpoint.cs +++ b/BNetInstaller/Endpoints/Install/InstallEndpoint.cs @@ -1,8 +1,4 @@ -using System; -using System.Text.Json.Nodes; -using BNetInstaller.Models; - -namespace BNetInstaller.Endpoints.Install; +namespace BNetInstaller.Endpoints.Install; internal sealed class InstallEndpoint : BaseProductEndpoint { diff --git a/BNetInstaller/Endpoints/ProductEndpoint.cs b/BNetInstaller/Endpoints/ProductEndpoint.cs index 36530d1..c8b26f3 100644 --- a/BNetInstaller/Endpoints/ProductEndpoint.cs +++ b/BNetInstaller/Endpoints/ProductEndpoint.cs @@ -1,7 +1,4 @@ -using System.Text.Json.Nodes; -using BNetInstaller.Models; - -namespace BNetInstaller.Endpoints; +namespace BNetInstaller.Endpoints; internal sealed class ProductEndpoint : BaseEndpoint { diff --git a/BNetInstaller/Endpoints/Repair/RepairEndpoint.cs b/BNetInstaller/Endpoints/Repair/RepairEndpoint.cs index c4c5e63..e513ddc 100644 --- a/BNetInstaller/Endpoints/Repair/RepairEndpoint.cs +++ b/BNetInstaller/Endpoints/Repair/RepairEndpoint.cs @@ -1,6 +1,4 @@ -using BNetInstaller.Models; - -namespace BNetInstaller.Endpoints.Repair; +namespace BNetInstaller.Endpoints.Repair; internal sealed class RepairEndpoint : BaseProductEndpoint { diff --git a/BNetInstaller/Endpoints/Update/UpdateEndpoint.cs b/BNetInstaller/Endpoints/Update/UpdateEndpoint.cs index 28a1c33..3c6d7a6 100644 --- a/BNetInstaller/Endpoints/Update/UpdateEndpoint.cs +++ b/BNetInstaller/Endpoints/Update/UpdateEndpoint.cs @@ -1,6 +1,4 @@ -using BNetInstaller.Models; - -namespace BNetInstaller.Endpoints.Update; +namespace BNetInstaller.Endpoints.Update; internal sealed class UpdateEndpoint : BaseProductEndpoint { diff --git a/BNetInstaller/Endpoints/Version/VersionEndpoint.cs b/BNetInstaller/Endpoints/Version/VersionEndpoint.cs index cf149d7..1d5feff 100644 --- a/BNetInstaller/Endpoints/Version/VersionEndpoint.cs +++ b/BNetInstaller/Endpoints/Version/VersionEndpoint.cs @@ -1,9 +1,4 @@ -using System.Net.Http; -using System.Text.Json.Nodes; -using System.Threading.Tasks; -using BNetInstaller.Models; - -namespace BNetInstaller.Endpoints.Version; +namespace BNetInstaller.Endpoints.Version; internal sealed class VersionEndpoint : BaseEndpoint { diff --git a/BNetInstaller/Options.cs b/BNetInstaller/Options.cs index 266b71b..187fd0e 100644 --- a/BNetInstaller/Options.cs +++ b/BNetInstaller/Options.cs @@ -1,5 +1,4 @@ -using System; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using BNetInstaller.Constants; using CommandLine; diff --git a/BNetInstaller/Program.cs b/BNetInstaller/Program.cs index 9b4e64e..9401104 100644 --- a/BNetInstaller/Program.cs +++ b/BNetInstaller/Program.cs @@ -1,6 +1,4 @@ -using System; -using System.Threading.Tasks; -using BNetInstaller.Constants; +using BNetInstaller.Constants; using BNetInstaller.Endpoints; using CommandLine; From 23c44c36c6608a444d532673d176029131ad47cd Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sun, 28 May 2023 20:08:46 +0100 Subject: [PATCH 11/34] misc --- BNetInstaller/AgentClient.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/BNetInstaller/AgentClient.cs b/BNetInstaller/AgentClient.cs index cf0bacf..b883e5b 100644 --- a/BNetInstaller/AgentClient.cs +++ b/BNetInstaller/AgentClient.cs @@ -71,21 +71,20 @@ public override string ConvertName(string name) if (string.IsNullOrEmpty(name)) return string.Empty; - Span lower = stackalloc char[0x100]; + Span input = stackalloc char[0x100]; Span output = stackalloc char[0x100]; - name.AsSpan().ToLowerInvariant(lower); + var inputLen = name.AsSpan().ToLowerInvariant(input); + var outputLen = 0; - var length = 0; - - for (var i = 0; i < name.Length; i++) + for (var i = 0; i < inputLen; i++) { if (i != 0 && name[i] is >= 'A' and <= 'Z') - output[length++] = '_'; + output[outputLen++] = '_'; - output[length++] = lower[i]; + output[outputLen++] = input[i]; } - return new string(output[..length]); + return new string(output[..outputLen]); } } From baacdf193e7d98f1317e8169b563664145f96632 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sun, 28 May 2023 20:17:27 +0100 Subject: [PATCH 12/34] fix delete method --- BNetInstaller/Endpoints/BaseEndpoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BNetInstaller/Endpoints/BaseEndpoint.cs b/BNetInstaller/Endpoints/BaseEndpoint.cs index 51a7ced..9936d2f 100644 --- a/BNetInstaller/Endpoints/BaseEndpoint.cs +++ b/BNetInstaller/Endpoints/BaseEndpoint.cs @@ -40,7 +40,7 @@ public virtual async Task Put() public virtual async Task Delete() { - await Client.SendAsync(Endpoint, HttpMethod.Delete, Model); + await Client.SendAsync(Endpoint, HttpMethod.Delete); } protected async Task Deserialize(HttpResponseMessage response) From 51883873c67e2b02112fb94fd48f5af47073cfb3 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sun, 28 May 2023 20:55:26 +0100 Subject: [PATCH 13/34] pattern matching --- BNetInstaller/Options.cs | 2 +- BNetInstaller/Program.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BNetInstaller/Options.cs b/BNetInstaller/Options.cs index 187fd0e..8e59f30 100644 --- a/BNetInstaller/Options.cs +++ b/BNetInstaller/Options.cs @@ -62,7 +62,7 @@ static string GetInput(string message) Console.WriteLine(); // fix repair arg - if (args[8] != "" && args[8][0] == 'Y') + if (args[8] is ['Y', ..]) args[8] = "--repair"; return args; diff --git a/BNetInstaller/Program.cs b/BNetInstaller/Program.cs index 9401104..6f15297 100644 --- a/BNetInstaller/Program.cs +++ b/BNetInstaller/Program.cs @@ -8,7 +8,7 @@ internal static class Program { private static async Task Main(string[] args) { - if (args == null || args.Length == 0) + if (args is not { Length: > 0 }) args = Options.Create(); using Parser parser = new(s => From c6b879d6f03c3d1a266649231aa90e595baffcc5 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Sun, 28 May 2023 21:00:11 +0100 Subject: [PATCH 14/34] minor SnakeCaseNamingPolicy performance increase --- BNetInstaller/AgentClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BNetInstaller/AgentClient.cs b/BNetInstaller/AgentClient.cs index b883e5b..5895793 100644 --- a/BNetInstaller/AgentClient.cs +++ b/BNetInstaller/AgentClient.cs @@ -79,7 +79,7 @@ public override string ConvertName(string name) for (var i = 0; i < inputLen; i++) { - if (i != 0 && name[i] is >= 'A' and <= 'Z') + if (name[i] is >= 'A' and <= 'Z' && i != 0) output[outputLen++] = '_'; output[outputLen++] = input[i]; From d54e1800ad34104f432bd88069b9be7bfc397dc6 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Tue, 30 May 2023 10:24:01 +0100 Subject: [PATCH 15/34] refactor InstallEndpoint validation.. again --- .../Endpoints/Install/InstallEndpoint.cs | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/BNetInstaller/Endpoints/Install/InstallEndpoint.cs b/BNetInstaller/Endpoints/Install/InstallEndpoint.cs index 6ae8c6d..5352433 100644 --- a/BNetInstaller/Endpoints/Install/InstallEndpoint.cs +++ b/BNetInstaller/Endpoints/Install/InstallEndpoint.cs @@ -10,27 +10,20 @@ protected override void ValidateResponse(JsonNode response, string content) { var agentError = response["error"]?.GetValue(); - if (agentError > 0) - { - // try to identify the erroneous section - foreach (var section in SubSections) - { - var token = response["form"]?[section]; - var errorCode = token?["error"]?.GetValue(); + if (agentError <= 0) + return; - if (errorCode > 0) - throw new Exception($"Agent Error: Unable to install - {errorCode} ({section}).", new(content)); - } + // try to identify the erroneous section + foreach (var section in new[] { "authentication", "game_dir", "min_spec" }) + { + var node = response["form"]?[section]; + var errorCode = node?["error"]?.GetValue(); - // fallback to throwing a global error - throw new Exception($"Agent Error: {agentError}", new(content)); + if (errorCode > 0) + throw new Exception($"Agent Error: Unable to install - {errorCode} ({section}).", new(content)); } - } - private static readonly string[] SubSections = new[] - { - "authentication", - "game_dir", - "min_spec" - }; + // fallback to throwing a global error + throw new Exception($"Agent Error: {agentError}", new(content)); + } } From c4855bc7ec890d050f285eba7ff771974bd614e3 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Tue, 30 May 2023 10:41:47 +0100 Subject: [PATCH 16/34] this is handled within BaseEndpoint --- BNetInstaller/AgentClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BNetInstaller/AgentClient.cs b/BNetInstaller/AgentClient.cs index 5895793..f9c814f 100644 --- a/BNetInstaller/AgentClient.cs +++ b/BNetInstaller/AgentClient.cs @@ -29,7 +29,7 @@ public async Task SendAsync(string endpoint, HttpMethod met { var request = new HttpRequestMessage(method, endpoint); - if (method != HttpMethod.Get && !string.IsNullOrEmpty(content)) + if (!string.IsNullOrEmpty(content)) request.Content = new StringContent(content); var response = await _client.SendAsync(request); From fe51db9ee8e64260e3d48db3a6aad01a4fca07be Mon Sep 17 00:00:00 2001 From: Barncastle Date: Tue, 30 May 2023 10:42:06 +0100 Subject: [PATCH 17/34] misc --- BNetInstaller/AgentApp.cs | 2 +- BNetInstaller/AgentClient.cs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/BNetInstaller/AgentApp.cs b/BNetInstaller/AgentApp.cs index ce53340..26bc283 100644 --- a/BNetInstaller/AgentApp.cs +++ b/BNetInstaller/AgentApp.cs @@ -28,7 +28,7 @@ public AgentApp() if (!StartProcess(out var process)) { - Console.WriteLine("Please ensure Battle.net is installed and has recently been opened."); + Console.WriteLine("Please ensure Battle.net is installed and has recently been signed in to."); Environment.Exit(0); } diff --git a/BNetInstaller/AgentClient.cs b/BNetInstaller/AgentClient.cs index f9c814f..c472972 100644 --- a/BNetInstaller/AgentClient.cs +++ b/BNetInstaller/AgentClient.cs @@ -74,14 +74,18 @@ public override string ConvertName(string name) Span input = stackalloc char[0x100]; Span output = stackalloc char[0x100]; + // lowercase the name var inputLen = name.AsSpan().ToLowerInvariant(input); var outputLen = 0; for (var i = 0; i < inputLen; i++) { + // prefix an underscore before any capitals + // excluding the initial character if (name[i] is >= 'A' and <= 'Z' && i != 0) output[outputLen++] = '_'; + // write the lowercase character to the output output[outputLen++] = input[i]; } From 9c6b795fdb4c2846243113368344edb4314e2d74 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Tue, 30 May 2023 12:10:42 +0100 Subject: [PATCH 18/34] misc --- BNetInstaller/AgentClient.cs | 4 ++-- BNetInstaller/Endpoints/Agent/AgentEndpoint.cs | 5 +++-- BNetInstaller/Options.cs | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/BNetInstaller/AgentClient.cs b/BNetInstaller/AgentClient.cs index c472972..9a3ded8 100644 --- a/BNetInstaller/AgentClient.cs +++ b/BNetInstaller/AgentClient.cs @@ -20,9 +20,9 @@ public AgentClient(int port) }; } - public void SetAuthorization(string authorization) + public void SetAuthToken(string token) { - _client.DefaultRequestHeaders.Add("Authorization", authorization); + _client.DefaultRequestHeaders.Add("Authorization", token); } public async Task SendAsync(string endpoint, HttpMethod method, string content = null) diff --git a/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs b/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs index 2b0e2e5..b357d67 100644 --- a/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs +++ b/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs @@ -8,12 +8,13 @@ public AgentEndpoint(AgentClient client) : base("agent", client) protected override void ValidateResponse(JsonNode response, string content) { + base.ValidateResponse(response, content); + var token = response["authorization"]?.GetValue(); if (string.IsNullOrEmpty(token)) throw new Exception("Agent Error: Unable to authenticate.", new(content)); - Client.SetAuthorization(token); - base.ValidateResponse(response, content); + Client.SetAuthToken(token); } } diff --git a/BNetInstaller/Options.cs b/BNetInstaller/Options.cs index 8e59f30..18bf655 100644 --- a/BNetInstaller/Options.cs +++ b/BNetInstaller/Options.cs @@ -9,7 +9,7 @@ internal sealed partial class Options [Option("prod", Required = true, HelpText = "TACT Product")] public string Product { get; set; } - [Option("lang", Required = true, HelpText = "Game/Asset language")] + [Option("lang", Required = true, HelpText = "Game/Asset Language")] public Locale Locale { get; set; } [Option("dir", Required = true, HelpText = "Installation Directory")] @@ -18,7 +18,7 @@ internal sealed partial class Options [Option("uid", HelpText = "Agent Product UID (Required if different to the TACT product)")] public string UID { get; set; } - [Option("repair", HelpText = "Run installation repair")] + [Option("repair", HelpText = "Repair Product Installation")] public bool Repair { get; set; } public void Sanitise() From 83a6492aaf682cbc5e09aa0323c48cd2cf3bc836 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Tue, 30 May 2023 17:42:14 +0100 Subject: [PATCH 19/34] fix --- BNetInstaller/Endpoints/Install/InstallEndpoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BNetInstaller/Endpoints/Install/InstallEndpoint.cs b/BNetInstaller/Endpoints/Install/InstallEndpoint.cs index 5352433..bd40883 100644 --- a/BNetInstaller/Endpoints/Install/InstallEndpoint.cs +++ b/BNetInstaller/Endpoints/Install/InstallEndpoint.cs @@ -10,7 +10,7 @@ protected override void ValidateResponse(JsonNode response, string content) { var agentError = response["error"]?.GetValue(); - if (agentError <= 0) + if (agentError.GetValueOrDefault() <= 0) return; // try to identify the erroneous section From 8e85935423932dbc5a5887e8552006915bab37f5 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Fri, 2 Jun 2023 21:52:45 +0100 Subject: [PATCH 20/34] add some requested specialist features --- BNetInstaller/Options.cs | 6 +++++ BNetInstaller/Program.cs | 57 +++++++++++++++++++++++++++++----------- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/BNetInstaller/Options.cs b/BNetInstaller/Options.cs index 18bf655..99f860c 100644 --- a/BNetInstaller/Options.cs +++ b/BNetInstaller/Options.cs @@ -21,6 +21,12 @@ internal sealed partial class Options [Option("repair", HelpText = "Repair Product Installation")] public bool Repair { get; set; } + [Option("console-env", Hidden = true)] + public bool ConsoleEnvironment { get; set; } = true; + + [Option("post-download-script", Hidden = true)] + public string PostDownloadScript { get; set; } + public void Sanitise() { // ensure a UID exists diff --git a/BNetInstaller/Program.cs b/BNetInstaller/Program.cs index 6f15297..7e7a980 100644 --- a/BNetInstaller/Program.cs +++ b/BNetInstaller/Program.cs @@ -1,4 +1,5 @@ -using BNetInstaller.Constants; +using System.Diagnostics; +using BNetInstaller.Constants; using BNetInstaller.Endpoints; using CommandLine; @@ -57,13 +58,16 @@ private static async Task Run(Options options) }; // process the task - await operation; + var complete = await operation; // send close signal await app.AgentEndpoint.Delete(); + + // run the post download script if applicable + RunPostDownloadScript(options, complete); } - private static async Task InstallProduct(Options options, AgentApp app) + private static async Task InstallProduct(Options options, AgentApp app) { // initiate download app.UpdateEndpoint.Model.Uid = options.UID; @@ -71,17 +75,18 @@ private static async Task InstallProduct(Options options, AgentApp app) // first try install endpoint if (await ProgressLoop(options, app.InstallEndpoint.Product)) - return; + return true; // then try the update endpoint instead if (await ProgressLoop(options, app.UpdateEndpoint.Product)) - return; + return true; // failing that another agent or the BNet app has probably taken control of the install Console.WriteLine("Another application has taken over. Launch the Battle.Net app to resume installation."); + return false; } - private static async Task RepairProduct(Options options, AgentApp app) + private static async Task RepairProduct(Options options, AgentApp app) { // initiate repair app.RepairEndpoint.Model.Uid = options.UID; @@ -89,16 +94,19 @@ private static async Task RepairProduct(Options options, AgentApp app) // run the repair endpoint if (await ProgressLoop(options, app.RepairEndpoint.Product)) - return; + return true; Console.WriteLine("Unable to repair this product."); + return false; } private static async Task ProgressLoop(Options options, ProductEndpoint endpoint) { var locale = options.Locale.ToString(); - var cursorLeft = Console.CursorLeft; - var cursorTop = Console.CursorTop; + var cursor = (Left: 0, Top: 0); + + if (options.ConsoleEnvironment) + cursor = Console.GetCursorPosition(); static void Print(string label, object value) => Console.WriteLine("{0,-20}{1,-20}", label, value); @@ -109,6 +117,7 @@ static void Print(string label, object value) => // check for completion var complete = stats["download_complete"]?.GetValue(); + if (complete == true) return true; @@ -119,12 +128,22 @@ static void Print(string label, object value) => if (!progress.HasValue) return false; - Console.SetCursorPosition(cursorLeft, cursorTop); - Print("Downloading:", options.Product); - Print("Language:", locale); - Print("Directory:", options.Directory); - Print("Progress:", progress.Value.ToString("P4")); - Print("Playable:", playable.GetValueOrDefault()); + // some non-console environments don't support + // cursor positioning or line rewriting + if (options.ConsoleEnvironment) + { + Console.SetCursorPosition(cursor.Left, cursor.Top); + Print("Downloading:", options.Product); + Print("Language:", locale); + Print("Directory:", options.Directory); + Print("Progress:", progress.Value.ToString("P4")); + Print("Playable:", playable.GetValueOrDefault()); + } + else + { + Print("Progress:", progress.Value.ToString("P4")); + } + await Task.Delay(2000); // exit @ 100% @@ -132,4 +151,12 @@ static void Print(string label, object value) => return true; } } + + private static void RunPostDownloadScript(Options options, bool complete) + { + if (!complete || !File.Exists(options.PostDownloadScript)) + return; + + Process.Start(options.PostDownloadScript); + } } From 2eb7892485e7c268f97b52b5948a026da3f217d5 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Mon, 5 Jun 2023 12:26:05 +0100 Subject: [PATCH 21/34] readonly variable naming conventions --- BNetInstaller/AgentApp.cs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/BNetInstaller/AgentApp.cs b/BNetInstaller/AgentApp.cs index 26bc283..735be5f 100644 --- a/BNetInstaller/AgentApp.cs +++ b/BNetInstaller/AgentApp.cs @@ -18,13 +18,13 @@ internal sealed class AgentApp : IDisposable public readonly RepairEndpoint RepairEndpoint; public readonly VersionEndpoint VersionEndpoint; - private readonly string AgentPath; - private readonly Process Process; - private readonly AgentClient Client; + private readonly string _agentPath; + private readonly Process _process; + private readonly AgentClient _client; public AgentApp() { - AgentPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Battle.net", "Agent", "Agent.exe"); + _agentPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Battle.net", "Agent", "Agent.exe"); if (!StartProcess(out var process)) { @@ -32,19 +32,19 @@ public AgentApp() Environment.Exit(0); } - Process = process; - Client = new(Port); + _process = process; + _client = new(Port); - AgentEndpoint = new(Client); - InstallEndpoint = new(Client); - UpdateEndpoint = new(Client); - RepairEndpoint = new(Client); - VersionEndpoint = new(Client); + AgentEndpoint = new(_client); + InstallEndpoint = new(_client); + UpdateEndpoint = new(_client); + RepairEndpoint = new(_client); + VersionEndpoint = new(_client); } private bool StartProcess(out Process process) { - if (!File.Exists(AgentPath)) + if (!File.Exists(_agentPath)) { process= null; Console.WriteLine("Unable to find Agent.exe."); @@ -53,7 +53,7 @@ private bool StartProcess(out Process process) try { - process = Process.Start(AgentPath, $"--port={Port}"); + process = Process.Start(_agentPath, $"--port={Port}"); return true; } catch (Win32Exception) @@ -66,10 +66,10 @@ private bool StartProcess(out Process process) public void Dispose() { - if (Process?.HasExited == false) - Process.Kill(); + if (_process?.HasExited == false) + _process.Kill(); - Client?.Dispose(); - Process?.Dispose(); + _client?.Dispose(); + _process?.Dispose(); } } From e08695d947ad7abc5870ccf5cfbf7158210cce5b Mon Sep 17 00:00:00 2001 From: Barncastle Date: Mon, 5 Jun 2023 20:38:03 +0100 Subject: [PATCH 22/34] misc --- BNetInstaller/Program.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/BNetInstaller/Program.cs b/BNetInstaller/Program.cs index 7e7a980..d1331d3 100644 --- a/BNetInstaller/Program.cs +++ b/BNetInstaller/Program.cs @@ -154,9 +154,7 @@ static void Print(string label, object value) => private static void RunPostDownloadScript(Options options, bool complete) { - if (!complete || !File.Exists(options.PostDownloadScript)) - return; - + if (complete && File.Exists(options.PostDownloadScript)) Process.Start(options.PostDownloadScript); } } From f349fda4164e725ace81aa2e0469368666b8d101 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Mon, 9 Jun 2025 15:49:03 +0100 Subject: [PATCH 23/34] over engineer agent background tasks --- BNetInstaller/Operations/AgentTask.cs | 73 ++++++++++++++ .../Operations/InstallProductTask.cs | 33 +++++++ BNetInstaller/Operations/RepairProductTask.cs | 27 ++++++ BNetInstaller/Program.cs | 95 +------------------ 4 files changed, 138 insertions(+), 90 deletions(-) create mode 100644 BNetInstaller/Operations/AgentTask.cs create mode 100644 BNetInstaller/Operations/InstallProductTask.cs create mode 100644 BNetInstaller/Operations/RepairProductTask.cs diff --git a/BNetInstaller/Operations/AgentTask.cs b/BNetInstaller/Operations/AgentTask.cs new file mode 100644 index 0000000..d29547c --- /dev/null +++ b/BNetInstaller/Operations/AgentTask.cs @@ -0,0 +1,73 @@ +using System.Runtime.CompilerServices; +using BNetInstaller.Endpoints; + +namespace BNetInstaller.Operations; + +internal abstract class AgentTask +{ + private readonly Options _options; + private TaskAwaiter? _awaiter; + + public AgentTask(Options options) + { + _options = options; + } + + public TaskAwaiter GetAwaiter() => _awaiter ??= InnerTask().GetAwaiter(); + + public T GetResult() => GetAwaiter().GetResult(); + + protected abstract Task InnerTask(); + + protected async Task PrintProgress(ProductEndpoint endpoint) + { + var locale = _options.Locale.ToString(); + var cursor = (Left: 0, Top: 0); + + if (_options.ConsoleEnvironment) + cursor = Console.GetCursorPosition(); + + static void Print(string label, object value) => + Console.WriteLine("{0,-20}{1,-20}", label, value); + + while (true) + { + var stats = await endpoint.Get(); + + // check for completion + var complete = stats["download_complete"]?.GetValue(); + + if (complete == true) + return true; + + // get progress percentage and playability + var progress = stats["progress"]?.GetValue(); + var playable = stats["playable"]?.GetValue(); + + if (!progress.HasValue) + return false; + + // some non-console environments don't support + // cursor positioning or line rewriting + if (_options.ConsoleEnvironment) + { + Console.SetCursorPosition(cursor.Left, cursor.Top); + Print("Downloading:", _options.Product); + Print("Language:", locale); + Print("Directory:", _options.Directory); + Print("Progress:", progress.Value.ToString("P4")); + Print("Playable:", playable.GetValueOrDefault()); + } + else + { + Print("Progress:", progress.Value.ToString("P4")); + } + + await Task.Delay(2000); + + // exit @ 100% + if (progress == 1f) + return true; + } + } +} diff --git a/BNetInstaller/Operations/InstallProductTask.cs b/BNetInstaller/Operations/InstallProductTask.cs new file mode 100644 index 0000000..b659f61 --- /dev/null +++ b/BNetInstaller/Operations/InstallProductTask.cs @@ -0,0 +1,33 @@ +namespace BNetInstaller.Operations; + +internal sealed class InstallProductTask : AgentTask +{ + private readonly Options _options; + private readonly AgentApp _app; + + public InstallProductTask(Options options, AgentApp app) : base(options) + { + _options = options; + _app = app; + } + + protected override async Task InnerTask() + { + // initiate the download + _app.UpdateEndpoint.Model.Uid = _options.UID; + await _app.UpdateEndpoint.Post(); + + // first try the install endpoint + if (await PrintProgress(_app.InstallEndpoint.Product)) + return true; + + // then try the update endpoint instead + if (await PrintProgress(_app.UpdateEndpoint.Product)) + return true; + + // failing that another agent or the BNet app has + // probably taken control of the install + Console.WriteLine("Another application has taken over. Launch the Battle.Net app to resume installation."); + return false; + } +} diff --git a/BNetInstaller/Operations/RepairProductTask.cs b/BNetInstaller/Operations/RepairProductTask.cs new file mode 100644 index 0000000..f5ca1d9 --- /dev/null +++ b/BNetInstaller/Operations/RepairProductTask.cs @@ -0,0 +1,27 @@ +namespace BNetInstaller.Operations; + +internal sealed class RepairProductTask : AgentTask +{ + private readonly Options _options; + private readonly AgentApp _app; + + public RepairProductTask(Options options, AgentApp app) : base(options) + { + _options = options; + _app = app; + } + + protected override async Task InnerTask() + { + // initiate the repair + _app.RepairEndpoint.Model.Uid = _options.UID; + await _app.RepairEndpoint.Post(); + + // run the repair endpoint + if (await PrintProgress(_app.RepairEndpoint.Product)) + return true; + + Console.WriteLine("Unable to repair this product."); + return false; + } +} diff --git a/BNetInstaller/Program.cs b/BNetInstaller/Program.cs index d1331d3..5865323 100644 --- a/BNetInstaller/Program.cs +++ b/BNetInstaller/Program.cs @@ -1,6 +1,6 @@ using System.Diagnostics; using BNetInstaller.Constants; -using BNetInstaller.Endpoints; +using BNetInstaller.Operations; using CommandLine; namespace BNetInstaller; @@ -50,10 +50,10 @@ private static async Task Run(Options options) Console.WriteLine(); - var operation = mode switch + AgentTask operation = mode switch { - Mode.Install => InstallProduct(options, app), - Mode.Repair => RepairProduct(options, app), + Mode.Install => new InstallProductTask(options, app), + Mode.Repair => new RepairProductTask(options, app), _ => throw new NotSupportedException(), }; @@ -67,94 +67,9 @@ private static async Task Run(Options options) RunPostDownloadScript(options, complete); } - private static async Task InstallProduct(Options options, AgentApp app) - { - // initiate download - app.UpdateEndpoint.Model.Uid = options.UID; - await app.UpdateEndpoint.Post(); - - // first try install endpoint - if (await ProgressLoop(options, app.InstallEndpoint.Product)) - return true; - - // then try the update endpoint instead - if (await ProgressLoop(options, app.UpdateEndpoint.Product)) - return true; - - // failing that another agent or the BNet app has probably taken control of the install - Console.WriteLine("Another application has taken over. Launch the Battle.Net app to resume installation."); - return false; - } - - private static async Task RepairProduct(Options options, AgentApp app) - { - // initiate repair - app.RepairEndpoint.Model.Uid = options.UID; - await app.RepairEndpoint.Post(); - - // run the repair endpoint - if (await ProgressLoop(options, app.RepairEndpoint.Product)) - return true; - - Console.WriteLine("Unable to repair this product."); - return false; - } - - private static async Task ProgressLoop(Options options, ProductEndpoint endpoint) - { - var locale = options.Locale.ToString(); - var cursor = (Left: 0, Top: 0); - - if (options.ConsoleEnvironment) - cursor = Console.GetCursorPosition(); - - static void Print(string label, object value) => - Console.WriteLine("{0,-20}{1,-20}", label, value); - - while (true) - { - var stats = await endpoint.Get(); - - // check for completion - var complete = stats["download_complete"]?.GetValue(); - - if (complete == true) - return true; - - // get progress percentage and playability - var progress = stats["progress"]?.GetValue(); - var playable = stats["playable"]?.GetValue(); - - if (!progress.HasValue) - return false; - - // some non-console environments don't support - // cursor positioning or line rewriting - if (options.ConsoleEnvironment) - { - Console.SetCursorPosition(cursor.Left, cursor.Top); - Print("Downloading:", options.Product); - Print("Language:", locale); - Print("Directory:", options.Directory); - Print("Progress:", progress.Value.ToString("P4")); - Print("Playable:", playable.GetValueOrDefault()); - } - else - { - Print("Progress:", progress.Value.ToString("P4")); - } - - await Task.Delay(2000); - - // exit @ 100% - if (progress == 1f) - return true; - } - } - private static void RunPostDownloadScript(Options options, bool complete) { if (complete && File.Exists(options.PostDownloadScript)) - Process.Start(options.PostDownloadScript); + Process.Start(options.PostDownloadScript); } } From eb78b858fdf2ea1599c2cdf505cb435b782a5960 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Mon, 9 Jun 2025 15:55:04 +0100 Subject: [PATCH 24/34] inline enums --- BNetInstaller/Constants/Locale.cs | 17 ----------------- BNetInstaller/Constants/Mode.cs | 7 ------- BNetInstaller/Options.cs | 17 ++++++++++++++++- BNetInstaller/Program.cs | 7 ++++++- 4 files changed, 22 insertions(+), 26 deletions(-) delete mode 100644 BNetInstaller/Constants/Locale.cs delete mode 100644 BNetInstaller/Constants/Mode.cs diff --git a/BNetInstaller/Constants/Locale.cs b/BNetInstaller/Constants/Locale.cs deleted file mode 100644 index d1083b5..0000000 --- a/BNetInstaller/Constants/Locale.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace BNetInstaller.Constants; - -internal enum Locale -{ - deDE, - enUS, - esMX, - ptBR, - esES, - frFR, - itIT, - koKR, - plPL, - ruRU, - zhCN, - zhTW -} diff --git a/BNetInstaller/Constants/Mode.cs b/BNetInstaller/Constants/Mode.cs deleted file mode 100644 index 67f6c48..0000000 --- a/BNetInstaller/Constants/Mode.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BNetInstaller.Constants; - -public enum Mode -{ - Install, - Repair -} diff --git a/BNetInstaller/Options.cs b/BNetInstaller/Options.cs index 99f860c..16e9399 100644 --- a/BNetInstaller/Options.cs +++ b/BNetInstaller/Options.cs @@ -1,5 +1,4 @@ using System.Text.RegularExpressions; -using BNetInstaller.Constants; using CommandLine; namespace BNetInstaller; @@ -77,3 +76,19 @@ static string GetInput(string message) [GeneratedRegex("\\(?_locale\\)?", RegexOptions.IgnoreCase, "en-GB")] private static partial Regex LocaleSuffixRegex(); } + +internal enum Locale +{ + deDE, + enUS, + esMX, + ptBR, + esES, + frFR, + itIT, + koKR, + plPL, + ruRU, + zhCN, + zhTW +} diff --git a/BNetInstaller/Program.cs b/BNetInstaller/Program.cs index 5865323..c7e3129 100644 --- a/BNetInstaller/Program.cs +++ b/BNetInstaller/Program.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using BNetInstaller.Constants; using BNetInstaller.Operations; using CommandLine; @@ -73,3 +72,9 @@ private static void RunPostDownloadScript(Options options, bool complete) Process.Start(options.PostDownloadScript); } } + +file enum Mode +{ + Install, + Repair +} From 5872c18df7366e4a1301f4f71d73ff3814939524 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Mon, 9 Jun 2025 16:41:20 +0100 Subject: [PATCH 25/34] update agent process code --- BNetInstaller/AgentApp.cs | 55 ++++++++++++++++++++------ BNetInstaller/BNetInstaller.csproj | 1 + BNetInstaller/NativeMethods.cs | 63 ++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 13 deletions(-) create mode 100644 BNetInstaller/NativeMethods.cs diff --git a/BNetInstaller/AgentApp.cs b/BNetInstaller/AgentApp.cs index 735be5f..22c66a6 100644 --- a/BNetInstaller/AgentApp.cs +++ b/BNetInstaller/AgentApp.cs @@ -10,30 +10,25 @@ namespace BNetInstaller; internal sealed class AgentApp : IDisposable { - public const int Port = 5050; - public readonly AgentEndpoint AgentEndpoint; public readonly InstallEndpoint InstallEndpoint; public readonly UpdateEndpoint UpdateEndpoint; public readonly RepairEndpoint RepairEndpoint; public readonly VersionEndpoint VersionEndpoint; - private readonly string _agentPath; private readonly Process _process; + private readonly int _port; private readonly AgentClient _client; public AgentApp() { - _agentPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Battle.net", "Agent", "Agent.exe"); - - if (!StartProcess(out var process)) + if (!StartProcess(out _process, out _port)) { Console.WriteLine("Please ensure Battle.net is installed and has recently been signed in to."); Environment.Exit(0); } - _process = process; - _client = new(Port); + _client = new(_port); AgentEndpoint = new(_client); InstallEndpoint = new(_client); @@ -42,28 +37,62 @@ public AgentApp() VersionEndpoint = new(_client); } - private bool StartProcess(out Process process) + private static bool StartProcess(out Process process, out int port) { - if (!File.Exists(_agentPath)) + (process, port) = (null, -1); + + var agentPath = GetAgentPath(); + + if (!File.Exists(agentPath)) { - process= null; Console.WriteLine("Unable to find Agent.exe."); return false; } try { - process = Process.Start(_agentPath, $"--port={Port}"); + process = Process.Start(new ProcessStartInfo(agentPath) + { + Arguments = "--internalclienttools", + UseShellExecute = true, + }); + + // detect listening port + while (!process.HasExited && port == -1) + { + Thread.Sleep(250); + port = NativeMethods.GetProcessListeningPort(process.Id); + } + + if (process.HasExited || port == -1) + { + Console.WriteLine("Unable to connect to Agent.exe."); + return false; + } + return true; } catch (Win32Exception) { - process = null; Console.WriteLine("Unable to start Agent.exe."); return false; } } + private static string GetAgentPath() + { + var agentDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Battle.net", "Agent"); + var parentPath = Path.Combine(agentDirectory, "Agent.exe"); + var parentVersion = 0; + + // read parent Agent.exe version + if (File.Exists(parentPath)) + parentVersion = FileVersionInfo.GetVersionInfo(parentPath).ProductPrivatePart; + + // return expected child Agent path + return Path.Combine(agentDirectory, $"Agent.{parentVersion}", "Agent.exe"); + } + public void Dispose() { if (_process?.HasExited == false) diff --git a/BNetInstaller/BNetInstaller.csproj b/BNetInstaller/BNetInstaller.csproj index e72b2ff..eeca536 100644 --- a/BNetInstaller/BNetInstaller.csproj +++ b/BNetInstaller/BNetInstaller.csproj @@ -4,6 +4,7 @@ Exe net7.0 enable + true diff --git a/BNetInstaller/NativeMethods.cs b/BNetInstaller/NativeMethods.cs new file mode 100644 index 0000000..4d30577 --- /dev/null +++ b/BNetInstaller/NativeMethods.cs @@ -0,0 +1,63 @@ +using System.Buffers.Binary; +using System.Runtime.InteropServices; + +namespace BNetInstaller; + +internal static partial class NativeMethods +{ + private const int MIB_TCPROW2_SIZE = 0x1C; + private const int MIB_TCP_STATE_LISTEN = 2; + + [LibraryImport("iphlpapi.dll", EntryPoint = "GetTcpTable2")] + private static partial int GetTcpTable(nint tcpTable, ref int size, [MarshalAs(UnmanagedType.Bool)] bool bOrder); + + public static int GetProcessListeningPort(int pid) + { + var size = 0; + + // get MIB_TCPTABLE2 size + _ = GetTcpTable(0, ref size, false); + + var buffer = Marshal.AllocHGlobal(size); + var pBuffer = buffer; + + // read MIB_TCPTABLE2 + if (GetTcpTable(pBuffer, ref size, false) != 0) + return -1; + + try + { + var dwNumEntries = Marshal.ReadInt32(pBuffer); // MIB_TCPTABLE2->dwNumEntries + pBuffer += sizeof(int); + + for (var i = 0; i < dwNumEntries; i++) + { + var row = Marshal.PtrToStructure(pBuffer); + pBuffer += MIB_TCPROW2_SIZE; + + if (row.dwOwningPid == pid && row.dwState == MIB_TCP_STATE_LISTEN) + { + return BinaryPrimitives.ReverseEndianness((ushort)row.dwLocalPort); + } + } + } + finally + { + Marshal.FreeHGlobal(buffer); + } + + return -1; + } + + [StructLayout(LayoutKind.Sequential)] + private struct MIB_TCPROW2 + { + public int dwState; + public int dwLocalAddr; + public int dwLocalPort; + public int dwRemoteAddr; + public int dwRemotePort; + public int dwOwningPid; + public int dwOffloadState; + } +} From 35223f280e061fcf1b2c6ef6bc255ac60c655d5f Mon Sep 17 00:00:00 2001 From: Barncastle Date: Mon, 9 Jun 2025 16:45:16 +0100 Subject: [PATCH 26/34] update to net8_0 --- BNetInstaller/BNetInstaller.csproj | 2 +- BNetInstaller/Endpoints/Agent/AgentEndpoint.cs | 6 +----- BNetInstaller/Endpoints/BaseEndpoint.cs | 15 ++++----------- BNetInstaller/Endpoints/BaseProductEndpoint.cs | 6 +----- .../Endpoints/Install/InstallEndpoint.cs | 6 +----- BNetInstaller/Endpoints/ProductEndpoint.cs | 6 +----- .../Endpoints/Version/VersionEndpoint.cs | 6 +----- BNetInstaller/Models/ProductModel.cs | 2 +- BNetInstaller/Operations/AgentTask.cs | 9 ++------- BNetInstaller/Operations/InstallProductTask.cs | 12 +++--------- BNetInstaller/Operations/RepairProductTask.cs | 12 +++--------- BNetInstaller/Program.cs | 2 +- 12 files changed, 20 insertions(+), 64 deletions(-) diff --git a/BNetInstaller/BNetInstaller.csproj b/BNetInstaller/BNetInstaller.csproj index eeca536..0378ccb 100644 --- a/BNetInstaller/BNetInstaller.csproj +++ b/BNetInstaller/BNetInstaller.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable true diff --git a/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs b/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs index b357d67..2f486ef 100644 --- a/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs +++ b/BNetInstaller/Endpoints/Agent/AgentEndpoint.cs @@ -1,11 +1,7 @@ namespace BNetInstaller.Endpoints.Agent; -internal sealed class AgentEndpoint : BaseEndpoint +internal sealed class AgentEndpoint(AgentClient client) : BaseEndpoint("agent", client) { - public AgentEndpoint(AgentClient client) : base("agent", client) - { - } - protected override void ValidateResponse(JsonNode response, string content) { base.ValidateResponse(response, content); diff --git a/BNetInstaller/Endpoints/BaseEndpoint.cs b/BNetInstaller/Endpoints/BaseEndpoint.cs index 9936d2f..0ec53fe 100644 --- a/BNetInstaller/Endpoints/BaseEndpoint.cs +++ b/BNetInstaller/Endpoints/BaseEndpoint.cs @@ -1,18 +1,11 @@ namespace BNetInstaller.Endpoints; -internal abstract class BaseEndpoint where T : class, IModel, new() +internal abstract class BaseEndpoint(string endpoint, AgentClient client) where T : class, IModel, new() { - public string Endpoint { get; } - public T Model { get; } + public string Endpoint { get; } = endpoint; + public T Model { get; } = new(); - protected AgentClient Client { get; } - - protected BaseEndpoint(string endpoint, AgentClient client) - { - Endpoint = endpoint; - Client = client; - Model = new(); - } + protected AgentClient Client { get; } = client; public virtual async Task Get() { diff --git a/BNetInstaller/Endpoints/BaseProductEndpoint.cs b/BNetInstaller/Endpoints/BaseProductEndpoint.cs index df3dc35..eaa0783 100644 --- a/BNetInstaller/Endpoints/BaseProductEndpoint.cs +++ b/BNetInstaller/Endpoints/BaseProductEndpoint.cs @@ -1,13 +1,9 @@ namespace BNetInstaller.Endpoints; -internal abstract class BaseProductEndpoint : BaseEndpoint where T : class, IModel, new() +internal abstract class BaseProductEndpoint(string endpoint, AgentClient client) : BaseEndpoint(endpoint, client) where T : class, IModel, new() { public ProductEndpoint Product { get; private set; } - protected BaseProductEndpoint(string endpoint, AgentClient client) : base(endpoint, client) - { - } - public override async Task Post() { var content = await base.Post(); diff --git a/BNetInstaller/Endpoints/Install/InstallEndpoint.cs b/BNetInstaller/Endpoints/Install/InstallEndpoint.cs index bd40883..341b01c 100644 --- a/BNetInstaller/Endpoints/Install/InstallEndpoint.cs +++ b/BNetInstaller/Endpoints/Install/InstallEndpoint.cs @@ -1,11 +1,7 @@ namespace BNetInstaller.Endpoints.Install; -internal sealed class InstallEndpoint : BaseProductEndpoint +internal sealed class InstallEndpoint(AgentClient client) : BaseProductEndpoint("install", client) { - public InstallEndpoint(AgentClient client) : base("install", client) - { - } - protected override void ValidateResponse(JsonNode response, string content) { var agentError = response["error"]?.GetValue(); diff --git a/BNetInstaller/Endpoints/ProductEndpoint.cs b/BNetInstaller/Endpoints/ProductEndpoint.cs index c8b26f3..955881d 100644 --- a/BNetInstaller/Endpoints/ProductEndpoint.cs +++ b/BNetInstaller/Endpoints/ProductEndpoint.cs @@ -1,11 +1,7 @@ namespace BNetInstaller.Endpoints; -internal sealed class ProductEndpoint : BaseEndpoint +internal sealed class ProductEndpoint(string endpoint, AgentClient client) : BaseEndpoint(endpoint, client) { - public ProductEndpoint(string endpoint, AgentClient client) : base(endpoint, client) - { - } - public static ProductEndpoint CreateFromResponse(JsonNode content, AgentClient client) { var responseURI = content["response_uri"]?.GetValue(); diff --git a/BNetInstaller/Endpoints/Version/VersionEndpoint.cs b/BNetInstaller/Endpoints/Version/VersionEndpoint.cs index 1d5feff..3ca625b 100644 --- a/BNetInstaller/Endpoints/Version/VersionEndpoint.cs +++ b/BNetInstaller/Endpoints/Version/VersionEndpoint.cs @@ -1,11 +1,7 @@ namespace BNetInstaller.Endpoints.Version; -internal sealed class VersionEndpoint : BaseEndpoint +internal sealed class VersionEndpoint(AgentClient client) : BaseEndpoint("version", client) { - public VersionEndpoint(AgentClient client) : base("version", client) - { - } - public override async Task Get() { using var response = await Client.SendAsync(Endpoint + "/" + Model.Uid, HttpMethod.Get); diff --git a/BNetInstaller/Models/ProductModel.cs b/BNetInstaller/Models/ProductModel.cs index c11e848..71a9636 100644 --- a/BNetInstaller/Models/ProductModel.cs +++ b/BNetInstaller/Models/ProductModel.cs @@ -6,7 +6,7 @@ internal sealed class ProductModel : IModel public bool Finalized { get; set; } = true; public string GameDir { get; set; } public string GeoIpCountry { get; set; } = "US"; - public string[] Language { get; set; } = new[] { "enus" }; + public string[] Language { get; set; } = ["enus"]; public string SelectedAssetLocale { get; set; } = "enus"; public string SelectedLocale { get; set; } = "enus"; public string Shortcut { get; set; } = "all"; diff --git a/BNetInstaller/Operations/AgentTask.cs b/BNetInstaller/Operations/AgentTask.cs index d29547c..e29ba94 100644 --- a/BNetInstaller/Operations/AgentTask.cs +++ b/BNetInstaller/Operations/AgentTask.cs @@ -3,16 +3,11 @@ namespace BNetInstaller.Operations; -internal abstract class AgentTask +internal abstract class AgentTask(Options options) { - private readonly Options _options; + private readonly Options _options = options; private TaskAwaiter? _awaiter; - public AgentTask(Options options) - { - _options = options; - } - public TaskAwaiter GetAwaiter() => _awaiter ??= InnerTask().GetAwaiter(); public T GetResult() => GetAwaiter().GetResult(); diff --git a/BNetInstaller/Operations/InstallProductTask.cs b/BNetInstaller/Operations/InstallProductTask.cs index b659f61..c5107d9 100644 --- a/BNetInstaller/Operations/InstallProductTask.cs +++ b/BNetInstaller/Operations/InstallProductTask.cs @@ -1,15 +1,9 @@ namespace BNetInstaller.Operations; -internal sealed class InstallProductTask : AgentTask +internal sealed class InstallProductTask(Options options, AgentApp app) : AgentTask(options) { - private readonly Options _options; - private readonly AgentApp _app; - - public InstallProductTask(Options options, AgentApp app) : base(options) - { - _options = options; - _app = app; - } + private readonly Options _options = options; + private readonly AgentApp _app = app; protected override async Task InnerTask() { diff --git a/BNetInstaller/Operations/RepairProductTask.cs b/BNetInstaller/Operations/RepairProductTask.cs index f5ca1d9..f6d38f6 100644 --- a/BNetInstaller/Operations/RepairProductTask.cs +++ b/BNetInstaller/Operations/RepairProductTask.cs @@ -1,15 +1,9 @@ namespace BNetInstaller.Operations; -internal sealed class RepairProductTask : AgentTask +internal sealed class RepairProductTask(Options options, AgentApp app) : AgentTask(options) { - private readonly Options _options; - private readonly AgentApp _app; - - public RepairProductTask(Options options, AgentApp app) : base(options) - { - _options = options; - _app = app; - } + private readonly Options _options = options; + private readonly AgentApp _app = app; protected override async Task InnerTask() { diff --git a/BNetInstaller/Program.cs b/BNetInstaller/Program.cs index c7e3129..e3fd3b8 100644 --- a/BNetInstaller/Program.cs +++ b/BNetInstaller/Program.cs @@ -35,7 +35,7 @@ private static async Task Run(Options options) await app.AgentEndpoint.Get(); Console.WriteLine($"Queuing {mode}"); - app.InstallEndpoint.Model.InstructionsDataset = new[] { "torrent", "win", options.Product, locale.ToLowerInvariant() }; + app.InstallEndpoint.Model.InstructionsDataset = ["torrent", "win", options.Product, locale.ToLowerInvariant()]; app.InstallEndpoint.Model.InstructionsPatchUrl = $"http://us.patch.battle.net:1119/{options.Product}"; app.InstallEndpoint.Model.Uid = options.UID; await app.InstallEndpoint.Post(); From 91b27fb6a173a86bed1d2932aaa28b7757dfdaa7 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Mon, 7 Jul 2025 11:58:09 +0100 Subject: [PATCH 27/34] switch to System.CommandLine --- BNetInstaller/BNetInstaller.csproj | 2 +- BNetInstaller/Options.cs | 103 +++++++++++++++++++++++------ BNetInstaller/Program.cs | 17 ++--- 3 files changed, 88 insertions(+), 34 deletions(-) diff --git a/BNetInstaller/BNetInstaller.csproj b/BNetInstaller/BNetInstaller.csproj index 0378ccb..00b1936 100644 --- a/BNetInstaller/BNetInstaller.csproj +++ b/BNetInstaller/BNetInstaller.csproj @@ -8,7 +8,7 @@ - + diff --git a/BNetInstaller/Options.cs b/BNetInstaller/Options.cs index 16e9399..10edf87 100644 --- a/BNetInstaller/Options.cs +++ b/BNetInstaller/Options.cs @@ -1,29 +1,16 @@ -using System.Text.RegularExpressions; -using CommandLine; +using System.CommandLine; +using System.Text.RegularExpressions; namespace BNetInstaller; internal sealed partial class Options { - [Option("prod", Required = true, HelpText = "TACT Product")] public string Product { get; set; } - - [Option("lang", Required = true, HelpText = "Game/Asset Language")] public Locale Locale { get; set; } - - [Option("dir", Required = true, HelpText = "Installation Directory")] public string Directory { get; set; } - - [Option("uid", HelpText = "Agent Product UID (Required if different to the TACT product)")] public string UID { get; set; } - - [Option("repair", HelpText = "Repair Product Installation")] public bool Repair { get; set; } - - [Option("console-env", Hidden = true)] - public bool ConsoleEnvironment { get; set; } = true; - - [Option("post-download-script", Hidden = true)] + public bool ConsoleEnvironment { get; set; } public string PostDownloadScript { get; set; } public void Sanitise() @@ -34,14 +21,59 @@ public void Sanitise() // remove _locale suffix for wiki copy-pasters if (UID.Contains("_locale", StringComparison.OrdinalIgnoreCase)) - UID = LocaleSuffixRegex().Replace(UID, $"_{Locale}"); + UID = ExtractLocaleRegex().Replace(UID, $"_{Locale}"); Product = Product.ToLowerInvariant().Trim(); UID = UID.ToLowerInvariant().Trim(); - Directory = Directory.Replace("/", "\\").Trim().TrimEnd('\\') + '\\'; + Directory = Path.GetFullPath(Directory + "\\"); } - public static string[] Create() + [GeneratedRegex("\\(?_locale\\)?", RegexOptions.IgnoreCase)] + private static partial Regex ExtractLocaleRegex(); +} + +internal static class OptionsBinder +{ + private static readonly Option Product = new("--prod") + { + HelpName = "TACT Product", + Required = true + }; + + private static readonly Option Locale = new("--lang") + { + HelpName = "Game/Asset language", + Required = true + }; + + private static readonly Option Directory = new("--dir") + { + HelpName = "Installation Directory", + Required = true + }; + + private static readonly Option UID = new("--uid") + { + HelpName = "Agent Product UID (Required if different to the TACT product)", + Required = true + }; + + private static readonly Option Repair = new("--repair") + { + HelpName = "Run installation repair" + }; + + private static readonly Option ConsoleEnvironment = new("--console-env") + { + Hidden = true + }; + + private static readonly Option PostDownloadScript = new("--post-download-script") + { + Hidden = true + }; + + public static string[] CreateArgs() { static string GetInput(string message) { @@ -73,12 +105,41 @@ static string GetInput(string message) return args; } - [GeneratedRegex("\\(?_locale\\)?", RegexOptions.IgnoreCase, "en-GB")] - private static partial Regex LocaleSuffixRegex(); + public static RootCommand BuildRootCommand(Func task) + { + var rootCommand = new RootCommand() + { + Product, + Locale, + Directory, + UID, + Repair + }; + + rootCommand.SetAction(async context => + { + await task(new() + { + Product = context.CommandResult.GetValue(Product), + Locale = context.CommandResult.GetValue(Locale), + Directory = context.CommandResult.GetValue(Directory), + UID = context.CommandResult.GetValue(UID), + Repair = context.CommandResult.GetValue(Repair), + ConsoleEnvironment = context.CommandResult.GetValue(ConsoleEnvironment) ?? true, // https://github.com/dotnet/command-line-api/issues/2257 + PostDownloadScript = context.CommandResult.GetValue(PostDownloadScript), + }); + }); + + rootCommand.TreatUnmatchedTokensAsErrors = false; + + return rootCommand; + } } internal enum Locale { + arSA, + enSA, deDE, enUS, esMX, diff --git a/BNetInstaller/Program.cs b/BNetInstaller/Program.cs index e3fd3b8..664ebbe 100644 --- a/BNetInstaller/Program.cs +++ b/BNetInstaller/Program.cs @@ -1,6 +1,5 @@ using System.Diagnostics; using BNetInstaller.Operations; -using CommandLine; namespace BNetInstaller; @@ -9,18 +8,12 @@ internal static class Program private static async Task Main(string[] args) { if (args is not { Length: > 0 }) - args = Options.Create(); + args = OptionsBinder.CreateArgs(); - using Parser parser = new(s => - { - s.HelpWriter = Console.Error; - s.CaseInsensitiveEnumValues = true; - s.AutoVersion = false; - }); - - await parser - .ParseArguments(args) - .MapResult(Run, Task.FromResult); + await OptionsBinder + .BuildRootCommand(Run) + .Parse(args) + .InvokeAsync(); } private static async Task Run(Options options) From ae3d5c630d25736cc703bab9dc60f9ef317ddd1e Mon Sep 17 00:00:00 2001 From: Barncastle Date: Mon, 7 Jul 2025 12:06:24 +0100 Subject: [PATCH 28/34] fix nullable warnings --- BNetInstaller/AgentApp.cs | 4 ++-- BNetInstaller/AgentClient.cs | 7 +++---- BNetInstaller/BNetInstaller.csproj | 1 + BNetInstaller/Options.cs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/BNetInstaller/AgentApp.cs b/BNetInstaller/AgentApp.cs index 22c66a6..88ef343 100644 --- a/BNetInstaller/AgentApp.cs +++ b/BNetInstaller/AgentApp.cs @@ -58,13 +58,13 @@ private static bool StartProcess(out Process process, out int port) }); // detect listening port - while (!process.HasExited && port == -1) + while (process is { HasExited: false } && port == -1) { Thread.Sleep(250); port = NativeMethods.GetProcessListeningPort(process.Id); } - if (process.HasExited || port == -1) + if (process is not { HasExited: false } || port == -1) { Console.WriteLine("Unable to connect to Agent.exe."); return false; diff --git a/BNetInstaller/AgentClient.cs b/BNetInstaller/AgentClient.cs index 9a3ded8..48dfa80 100644 --- a/BNetInstaller/AgentClient.cs +++ b/BNetInstaller/AgentClient.cs @@ -35,7 +35,7 @@ public async Task SendAsync(string endpoint, HttpMethod met var response = await _client.SendAsync(request); if (!response.IsSuccessStatusCode) - await HandleRequestFailure(response); + await HandleRequestFailure(response, endpoint); return response; } @@ -48,12 +48,11 @@ public async Task SendAsync(string endpoint, HttpMethod return await SendAsync(endpoint, method, JsonSerializer.Serialize(payload, _serializerOptions)); } - private static async Task HandleRequestFailure(HttpResponseMessage response) + private static async Task HandleRequestFailure(HttpResponseMessage response, string endpoint) { - var uri = response.RequestMessage.RequestUri.AbsolutePath; var statusCode = response.StatusCode; var content = await response.Content.ReadAsStringAsync(); - Debug.WriteLine($"{(int)statusCode} {statusCode}: {uri} {content}"); + Debug.WriteLine($"{(int)statusCode} {statusCode}: {endpoint} {content}"); } public void Dispose() diff --git a/BNetInstaller/BNetInstaller.csproj b/BNetInstaller/BNetInstaller.csproj index 00b1936..7cc0d40 100644 --- a/BNetInstaller/BNetInstaller.csproj +++ b/BNetInstaller/BNetInstaller.csproj @@ -5,6 +5,7 @@ net8.0 enable true + warnings diff --git a/BNetInstaller/Options.cs b/BNetInstaller/Options.cs index 10edf87..6838584 100644 --- a/BNetInstaller/Options.cs +++ b/BNetInstaller/Options.cs @@ -78,7 +78,7 @@ public static string[] CreateArgs() static string GetInput(string message) { Console.Write(message); - return Console.ReadLine().Trim().Trim('"'); + return Console.ReadLine()?.Trim().Trim('"'); } Console.WriteLine("Please complete the following information:"); From 2d23f50a3ebd39513dd69a4c807901e536c83ff3 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Mon, 7 Jul 2025 12:41:36 +0100 Subject: [PATCH 29/34] minor argument tweaks --- BNetInstaller/Operations/AgentTask.cs | 4 ++-- BNetInstaller/Options.cs | 21 ++++++++++++--------- BNetInstaller/Program.cs | 11 +++-------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/BNetInstaller/Operations/AgentTask.cs b/BNetInstaller/Operations/AgentTask.cs index e29ba94..6e0c5f6 100644 --- a/BNetInstaller/Operations/AgentTask.cs +++ b/BNetInstaller/Operations/AgentTask.cs @@ -19,7 +19,7 @@ protected async Task PrintProgress(ProductEndpoint endpoint) var locale = _options.Locale.ToString(); var cursor = (Left: 0, Top: 0); - if (_options.ConsoleEnvironment) + if (_options.Verbose) cursor = Console.GetCursorPosition(); static void Print(string label, object value) => @@ -44,7 +44,7 @@ static void Print(string label, object value) => // some non-console environments don't support // cursor positioning or line rewriting - if (_options.ConsoleEnvironment) + if (_options.Verbose) { Console.SetCursorPosition(cursor.Left, cursor.Top); Print("Downloading:", _options.Product); diff --git a/BNetInstaller/Options.cs b/BNetInstaller/Options.cs index 6838584..dffb89d 100644 --- a/BNetInstaller/Options.cs +++ b/BNetInstaller/Options.cs @@ -10,8 +10,8 @@ internal sealed partial class Options public string Directory { get; set; } public string UID { get; set; } public bool Repair { get; set; } - public bool ConsoleEnvironment { get; set; } - public string PostDownloadScript { get; set; } + public bool Verbose { get; set; } + public string PostDownload { get; set; } public void Sanitise() { @@ -63,14 +63,15 @@ internal static class OptionsBinder HelpName = "Run installation repair" }; - private static readonly Option ConsoleEnvironment = new("--console-env") + private static readonly Option Verbose = new("--verbose") { - Hidden = true + HelpName = "Enables/disables verbose progress reporting", + DefaultValueFactory = (_) => true }; - private static readonly Option PostDownloadScript = new("--post-download-script") + private static readonly Option PostDownload = new("--post-download") { - Hidden = true + HelpName = "Specifies a file or app to run on completion" }; public static string[] CreateArgs() @@ -113,7 +114,9 @@ public static RootCommand BuildRootCommand(Func task) Locale, Directory, UID, - Repair + Repair, + Verbose, + PostDownload }; rootCommand.SetAction(async context => @@ -125,8 +128,8 @@ await task(new() Directory = context.CommandResult.GetValue(Directory), UID = context.CommandResult.GetValue(UID), Repair = context.CommandResult.GetValue(Repair), - ConsoleEnvironment = context.CommandResult.GetValue(ConsoleEnvironment) ?? true, // https://github.com/dotnet/command-line-api/issues/2257 - PostDownloadScript = context.CommandResult.GetValue(PostDownloadScript), + Verbose = context.CommandResult.GetValue(Verbose), + PostDownload = context.CommandResult.GetValue(PostDownload), }); }); diff --git a/BNetInstaller/Program.cs b/BNetInstaller/Program.cs index 664ebbe..9f0a01f 100644 --- a/BNetInstaller/Program.cs +++ b/BNetInstaller/Program.cs @@ -55,14 +55,9 @@ private static async Task Run(Options options) // send close signal await app.AgentEndpoint.Delete(); - // run the post download script if applicable - RunPostDownloadScript(options, complete); - } - - private static void RunPostDownloadScript(Options options, bool complete) - { - if (complete && File.Exists(options.PostDownloadScript)) - Process.Start(options.PostDownloadScript); + // run the post download app/script if applicable + if (complete && File.Exists(options.PostDownload)) + Process.Start(options.PostDownload); } } From 779c91a1f84365c1adf21bb969ac80e9691534d6 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Mon, 7 Jul 2025 16:19:20 +0100 Subject: [PATCH 30/34] fix potential memory leak --- BNetInstaller/NativeMethods.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BNetInstaller/NativeMethods.cs b/BNetInstaller/NativeMethods.cs index 4d30577..1d061d3 100644 --- a/BNetInstaller/NativeMethods.cs +++ b/BNetInstaller/NativeMethods.cs @@ -21,12 +21,12 @@ public static int GetProcessListeningPort(int pid) var buffer = Marshal.AllocHGlobal(size); var pBuffer = buffer; - // read MIB_TCPTABLE2 - if (GetTcpTable(pBuffer, ref size, false) != 0) - return -1; - try { + // read MIB_TCPTABLE2 + if (GetTcpTable(pBuffer, ref size, false) != 0) + return -1; + var dwNumEntries = Marshal.ReadInt32(pBuffer); // MIB_TCPTABLE2->dwNumEntries pBuffer += sizeof(int); From 4dc8aa6c4b598fa21d764ff7a339c55fcda19b95 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Fri, 8 Aug 2025 12:21:20 +0100 Subject: [PATCH 31/34] update readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 287f5cd..3bae739 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A tool for installing, updating and repairing games via Blizzard's Battle.net ap Windows only. See [releases](https://github.com/barncastle/Battle.Net-Installer/releases) for a compiled binary. #### Project Prerequisites -- [.NET 6.0](https://dotnet.microsoft.com/download/dotnet) +- [.NET 8.0](https://dotnet.microsoft.com/download/dotnet) - [Battle.net](https://www.blizzard.com/en-us/apps/battle.net/desktop) must be installed, up to date and have been recently signed in to. #### Arguments @@ -16,6 +16,8 @@ Windows only. See [releases](https://github.com/barncastle/Battle.Net-Installer/ | --dir | Installation Directory **(Required)** | | --uid | Agent UID (Required if different to the TACT Product) | | --repair | Repairs the installation opposed to installing/updating it | +| --verbose | Enables/disables verbose progress reporting | +| --post-download | Specifies a file or app to run on completion | | --help | Shows this table | - All TACT Products and Agent UIDs can be found [here](https://wowdev.wiki/TACT#Products) however only (green) Active products will work. From 5fc9d0131cd8c541f7d30c886b120ba533567711 Mon Sep 17 00:00:00 2001 From: Barncastle Date: Fri, 8 Aug 2025 12:30:06 +0100 Subject: [PATCH 32/34] update publish profile --- .../Properties/PublishProfiles/FolderProfile.pubxml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/BNetInstaller/Properties/PublishProfiles/FolderProfile.pubxml b/BNetInstaller/Properties/PublishProfiles/FolderProfile.pubxml index 922c241..7b2f166 100644 --- a/BNetInstaller/Properties/PublishProfiles/FolderProfile.pubxml +++ b/BNetInstaller/Properties/PublishProfiles/FolderProfile.pubxml @@ -7,12 +7,12 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release Any CPU - net6.0 - bin\Release\net6.0\publish\ + net8.0 + bin\Release\net8.0\publish\ win-x64 true - True - False - True + true + false + true \ No newline at end of file From dc80bcb6a546f09706d0b1f06163b12f0ddec052 Mon Sep 17 00:00:00 2001 From: barncastle Date: Fri, 29 Aug 2025 13:52:31 +0100 Subject: [PATCH 33/34] update install endpoint --- BNetInstaller/Models/InstallModel.cs | 1 - BNetInstaller/Program.cs | 4 ---- 2 files changed, 5 deletions(-) diff --git a/BNetInstaller/Models/InstallModel.cs b/BNetInstaller/Models/InstallModel.cs index cde051e..31000e5 100644 --- a/BNetInstaller/Models/InstallModel.cs +++ b/BNetInstaller/Models/InstallModel.cs @@ -2,7 +2,6 @@ internal sealed class InstallModel : ProductPriorityModel { - public string[] InstructionsDataset { get; set; } public string InstructionsPatchUrl { get; set; } public string InstructionsProduct { get; set; } = "NGDP"; public double MonitorPid { get; set; } = 12345; diff --git a/BNetInstaller/Program.cs b/BNetInstaller/Program.cs index 9f0a01f..a74d001 100644 --- a/BNetInstaller/Program.cs +++ b/BNetInstaller/Program.cs @@ -28,16 +28,12 @@ private static async Task Run(Options options) await app.AgentEndpoint.Get(); Console.WriteLine($"Queuing {mode}"); - app.InstallEndpoint.Model.InstructionsDataset = ["torrent", "win", options.Product, locale.ToLowerInvariant()]; app.InstallEndpoint.Model.InstructionsPatchUrl = $"http://us.patch.battle.net:1119/{options.Product}"; app.InstallEndpoint.Model.Uid = options.UID; await app.InstallEndpoint.Post(); Console.WriteLine($"Starting {mode}"); app.InstallEndpoint.Product.Model.GameDir = options.Directory; - app.InstallEndpoint.Product.Model.Language[0] = locale; - app.InstallEndpoint.Product.Model.SelectedAssetLocale = locale; - app.InstallEndpoint.Product.Model.SelectedLocale = locale; await app.InstallEndpoint.Product.Post(); Console.WriteLine(); From 3aa2d98d866b393197da0b75d7381b7fff495ad9 Mon Sep 17 00:00:00 2001 From: barncastle Date: Fri, 29 Aug 2025 13:57:55 +0100 Subject: [PATCH 34/34] fix json serialization --- BNetInstaller/Properties/PublishProfiles/FolderProfile.pubxml | 1 + 1 file changed, 1 insertion(+) diff --git a/BNetInstaller/Properties/PublishProfiles/FolderProfile.pubxml b/BNetInstaller/Properties/PublishProfiles/FolderProfile.pubxml index 7b2f166..b680ddb 100644 --- a/BNetInstaller/Properties/PublishProfiles/FolderProfile.pubxml +++ b/BNetInstaller/Properties/PublishProfiles/FolderProfile.pubxml @@ -14,5 +14,6 @@ https://go.microsoft.com/fwlink/?LinkID=208121. true false true + true \ No newline at end of file