diff --git a/NebulaModel/Networking/IServer.cs b/NebulaModel/Networking/IServer.cs index 1147ef83f..1de43bc96 100644 --- a/NebulaModel/Networking/IServer.cs +++ b/NebulaModel/Networking/IServer.cs @@ -14,6 +14,7 @@ public interface IServer : INetworkProvider bool NgrokActive { get; } bool NgrokEnabled { get; } string NgrokLastErrorCode { get; } + string NgrokLastErrorCodeDesc { get; } public event EventHandler Connected; public event EventHandler Disconnected; diff --git a/NebulaNetwork/Ngrok/NgrokManager.cs b/NebulaNetwork/Ngrok/NgrokManager.cs index fbfd13227..fc1b11da8 100644 --- a/NebulaNetwork/Ngrok/NgrokManager.cs +++ b/NebulaNetwork/Ngrok/NgrokManager.cs @@ -21,11 +21,11 @@ namespace NebulaNetwork.Ngrok; public class NgrokManager { - private readonly string _authtoken; + private readonly string _authToken; private readonly TaskCompletionSource _ngrokAddressObtainedSource = new(); private readonly string _ngrokConfigPath; - private readonly string _ngrokPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + private readonly string _ngrokPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException("_ngrokPath null"), "ngrok-v3-stable-windows-amd64", "ngrok.exe"); private readonly int _port; @@ -37,13 +37,14 @@ public class NgrokManager public string NgrokAddress; public string NgrokLastErrorCode; + public string NgrokLastErrorCodeDesc; private static readonly string[] contents = { "version: 2" }; - public NgrokManager(int port, string authtoken = null, string region = null) + public NgrokManager(int port, string authToken = null, string region = null) { - _ngrokConfigPath = Path.Combine(Path.GetDirectoryName(_ngrokPath), "ngrok.yml"); + _ngrokConfigPath = Path.Combine(Path.GetDirectoryName(_ngrokPath) ?? throw new InvalidOperationException("_ngrokConfigPath null"), "ngrok.yml"); _port = port; - _authtoken = authtoken ?? Config.Options.NgrokAuthtoken; + _authToken = authToken ?? Config.Options.NgrokAuthtoken; _region = region ?? Config.Options.NgrokRegion; if (!NgrokEnabled) @@ -51,7 +52,7 @@ public NgrokManager(int port, string authtoken = null, string region = null) return; } - if (string.IsNullOrEmpty(_authtoken)) + if (string.IsNullOrEmpty(_authToken)) { Log.WarnInform("Ngrok support was enabled, however no Authtoken was provided".Translate()); return; @@ -97,6 +98,10 @@ public NgrokManager(int port, string authtoken = null, string region = null) try { await DownloadAndInstallNgrok(); + if (!IsNgrokInstalled()) + { + throw new FileNotFoundException(); + } } catch { @@ -108,14 +113,14 @@ public NgrokManager(int port, string authtoken = null, string region = null) if (!StartNgrok()) { Log.WarnInform( - string.Format("Failed to start Ngrok tunnel! LastErrorCode: {0}".Translate(), NgrokLastErrorCode)); + string.Format("Failed to start Ngrok tunnel! LastErrorCode: {0} {1}".Translate(), NgrokLastErrorCode, NgrokLastErrorCodeDesc)); return; } if (!IsNgrokActive()) { - Log.WarnInform(string.Format("Ngrok tunnel has exited prematurely! LastErrorCode: {0}".Translate(), - NgrokLastErrorCode)); + Log.WarnInform(string.Format("Ngrok tunnel has exited prematurely! LastErrorCode: {0} {1}".Translate(), + NgrokLastErrorCode, NgrokLastErrorCodeDesc)); } }); } @@ -124,22 +129,25 @@ private async Task DownloadAndInstallNgrok() { using (var client = new HttpClient()) { - using (var s = client.GetStreamAsync("https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-windows-amd64.zip")) + using var s = client.GetStreamAsync("https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-windows-amd64.zip"); + using var zip = new ZipArchive(await s, ZipArchiveMode.Read); + if (File.Exists(_ngrokPath)) { - using (var zip = new ZipArchive(await s, ZipArchiveMode.Read)) - { - if (File.Exists(_ngrokPath)) - { - File.Delete(_ngrokPath); - } - zip.ExtractToDirectory(Path.GetDirectoryName(_ngrokPath)); - } + File.Delete(_ngrokPath); } + zip.ExtractToDirectory(Path.GetDirectoryName(_ngrokPath)); } File.WriteAllLines(_ngrokConfigPath, contents); - Log.WarnInform("Ngrok install completed in the plugin folder".Translate()); + if (File.Exists(_ngrokPath)) + { + Log.WarnInform("Ngrok install completed in the plugin folder".Translate()); + } + else + { + Log.Error("Ngrok installation failed".Translate()); + } } private bool IsNgrokInstalled() @@ -151,36 +159,46 @@ private bool StartNgrok() { StopNgrok(); - _ngrokProcess = new Process(); - _ngrokProcess.StartInfo = new ProcessStartInfo + _ngrokProcess = new Process { - WindowStyle = ProcessWindowStyle.Hidden, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - FileName = _ngrokPath, - Arguments = - $"tcp {_port} --authtoken {_authtoken} --log stdout --log-format json --config \"{_ngrokConfigPath}\"" + + StartInfo = new ProcessStartInfo + { + WindowStyle = ProcessWindowStyle.Hidden, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + FileName = _ngrokPath, + Arguments = + $"tcp {_port} --authtoken {_authToken} --log stdout --log-format json --config \"{_ngrokConfigPath}\"" + (!string.IsNullOrEmpty(_region) ? $" --region {_region}" : "") + } }; _ngrokProcess.OutputDataReceived += OutputDataReceivedEventHandler; _ngrokProcess.ErrorDataReceived += ErrorDataReceivedEventHandler; + _ngrokProcess.Exited += (_, _) => + { + StopNgrok(); + }; var started = _ngrokProcess.Start(); if (IsNgrokActive()) { // This links the process as a child process by attaching a null debugger thus ensuring that the process is killed when its parent dies. - new ChildProcessLinker(_ngrokProcess, exception => + _ = new ChildProcessLinker(_ngrokProcess, _ => { Log.Warn( "Failed to link Ngrok process to DSP process as a child! (This might result in a left over ngrok process if the DSP process uncleanly killed)"); }); } + else + { + Log.Error("Failed to start Ngrok process!"); + } - _ngrokProcess.BeginOutputReadLine(); - _ngrokProcess.BeginErrorReadLine(); + _ngrokProcess?.BeginOutputReadLine(); + _ngrokProcess?.BeginErrorReadLine(); return started; } @@ -240,7 +258,15 @@ private void ErrorDataReceivedEventHandler(object sender, DataReceivedEventArgs return; } NgrokLastErrorCode = errorCodeMatches[errorCodeMatches.Count - 1].Value; - Log.WarnInform(string.Format("Ngrok Error! Code: {0}".Translate(), NgrokLastErrorCode)); + NgrokLastErrorCodeDesc = NgrokLastErrorCode switch + { + "ERR_NGROK_105" => "Authtoken is empty or expired".Translate(), + "ERR_NGROK_108" => "Session limit reached".Translate(), + "ERR_NGROK_123" => "Account email not verified".Translate(), + _ => string.Empty + }; + NgrokLastErrorCodeDesc = !string.IsNullOrWhiteSpace(NgrokLastErrorCodeDesc) ? $"({NgrokLastErrorCodeDesc})" : string.Empty; + Log.WarnInform(string.Format("Ngrok Error! Code: {0} {1}".Translate(), NgrokLastErrorCode, NgrokLastErrorCodeDesc)); } public void StopNgrok() @@ -250,23 +276,33 @@ public void StopNgrok() return; } _ngrokProcess.Refresh(); - if (!_ngrokProcess.HasExited) + try { - _ngrokProcess.Kill(); - _ngrokProcess.Close(); + if (!_ngrokProcess.HasExited) + { + _ngrokProcess.Kill(); + } } + catch (Exception e) + { + Log.Error(e); + } + _ngrokProcess.Close(); _ngrokProcess = null; } public bool IsNgrokActive() { - if (_ngrokProcess == null) + try + { + _ngrokProcess?.Refresh(); + return !_ngrokProcess?.HasExited ?? false; + } + catch (Exception e) { + Log.Error(e); return false; } - - _ngrokProcess.Refresh(); - return !_ngrokProcess.HasExited; } public Task GetNgrokAddressAsync() @@ -276,13 +312,13 @@ public Task GetNgrokAddressAsync() if (!IsNgrokActive()) { throw new Exception( - $"Not able to get Ngrok tunnel address because Ngrok is not started (or exited prematurely)! LastErrorCode: {NgrokLastErrorCode}"); + $"Not able to get Ngrok tunnel address because Ngrok is not started (or exited prematurely)! LastErrorCode: {NgrokLastErrorCode} {NgrokLastErrorCodeDesc}"); } if (!_ngrokAddressObtainedSource.Task.Wait(TimeSpan.FromSeconds(15))) { throw new TimeoutException( - $"Not able to get Ngrok tunnel address because 15s timeout was exceeded! LastErrorCode: {NgrokLastErrorCode}"); + $"Not able to get Ngrok tunnel address because 15s timeout was exceeded! LastErrorCode: {NgrokLastErrorCode} {NgrokLastErrorCodeDesc}"); } return NgrokAddress; @@ -294,7 +330,7 @@ public async Task GetTunnelAddressFromAPI() if (!IsNgrokActive()) { throw new Exception( - $"Not able to get Ngrok tunnel address from API because Ngrok is not started (or exited prematurely)! LastErrorCode: {NgrokLastErrorCode}"); + $"Not able to get Ngrok tunnel address from API because Ngrok is not started (or exited prematurely)! LastErrorCode: {NgrokLastErrorCode} {NgrokLastErrorCodeDesc}"); } if (_ngrokAPIAddress == null) diff --git a/NebulaNetwork/Server.cs b/NebulaNetwork/Server.cs index 4b018736c..523162a4b 100644 --- a/NebulaNetwork/Server.cs +++ b/NebulaNetwork/Server.cs @@ -77,6 +77,7 @@ public Server(ushort port, bool loadSaveFile = false) public bool NgrokActive => ngrokManager.IsNgrokActive(); public bool NgrokEnabled => ngrokManager.NgrokEnabled; public string NgrokLastErrorCode => ngrokManager.NgrokLastErrorCode; + public string NgrokLastErrorCodeDesc => ngrokManager.NgrokLastErrorCodeDesc; public event EventHandler Connected; public event EventHandler Disconnected; diff --git a/NebulaPatcher/Patches/Transpilers/ConstructionSystem_Transpiler.cs b/NebulaPatcher/Patches/Transpilers/ConstructionSystem_Transpiler.cs index 5d588f021..da8401041 100644 --- a/NebulaPatcher/Patches/Transpilers/ConstructionSystem_Transpiler.cs +++ b/NebulaPatcher/Patches/Transpilers/ConstructionSystem_Transpiler.cs @@ -34,7 +34,6 @@ public static IEnumerable AddBuildTargetToModules_Transpiler(IE new CodeMatch(OpCodes.Ldloc_0), new CodeMatch(OpCodes.Bgt_Un) ); - Log.Info(codeMatcher.IsValid); var sqrDist = codeMatcher.InstructionAt(-2).operand; var skipLabel = codeMatcher.Operand; codeMatcher.Advance(1) diff --git a/NebulaWorld/Chat/Commands/InfoCommandHandler.cs b/NebulaWorld/Chat/Commands/InfoCommandHandler.cs index 397506200..ec9f9237e 100644 --- a/NebulaWorld/Chat/Commands/InfoCommandHandler.cs +++ b/NebulaWorld/Chat/Commands/InfoCommandHandler.cs @@ -18,7 +18,7 @@ namespace NebulaWorld.Chat.Commands; public class InfoCommandHandler : IChatCommandHandler { - private static readonly string[] s_separator = { "]:" }; + private static readonly string[] s_separator = ["]:"]; public void Execute(ChatWindow window, string[] parameters) { @@ -73,7 +73,7 @@ public string GetDescription() public string[] GetUsage() { - return new[] { "[full]" }; + return ["[full]"]; } public static string GetServerInfoText(IServer server, IPUtils.IpInfo ipInfo, bool full) @@ -105,17 +105,17 @@ public static string GetServerInfoText(IServer server, IPUtils.IpInfo ipInfo, bo { if (server.NgrokActive) { - sb.Append("\n ").Append("Ngrok address: ".Translate()) + sb.Append("\n ").Append("Ngrok address: ".Translate()) .Append(FormatCopyString(server.NgrokAddress, true, NgrokAddressFilter)); } else { - sb.Append("\n ").Append("Ngrok address: Tunnel Inactive!".Translate()); + sb.Append("\n ").Append("Ngrok address: Tunnel Inactive!".Translate()); } - if (server.NgrokLastErrorCode != null) + if (!string.IsNullOrWhiteSpace(server.NgrokLastErrorCode)) { - sb.Append($" ({FormatCopyString(server.NgrokLastErrorCode)})"); + sb.Append($" ({FormatCopyString(server.NgrokLastErrorCode)}){FormatCopyString(server.NgrokLastErrorCodeDesc)}"); } }