diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ICompatibleLogger.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ICompatibleLogger.cs index 74b323f..16d4edb 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ICompatibleLogger.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ICompatibleLogger.cs @@ -10,8 +10,8 @@ namespace WelsonJS.Launcher { public interface ICompatibleLogger { - void Info(string message); - void Warn(string message); - void Error(string message); + void Info(params object[] args); + void Warn(params object[] args); + void Error(params object[] args); } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/JsCore.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/JsCore.cs index 38db84a..53af2b5 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/JsCore.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/JsCore.cs @@ -4,53 +4,47 @@ // https://github.com/gnh1201/welsonjs // using System; +using System.Globalization; using System.Runtime.InteropServices; namespace WelsonJS.Launcher { public sealed class JsCore : IDisposable { - private IntPtr _runtime = IntPtr.Zero; - private IntPtr _context = IntPtr.Zero; + private JsNative.JsRuntime _rt; + private JsNative.JsContext _ctx; private bool _disposed; public JsCore() { - Check(JsCreateRuntime(0, IntPtr.Zero, out _runtime), nameof(JsCreateRuntime)); - Check(JsCreateContext(_runtime, out _context), nameof(JsCreateContext)); - Check(JsSetCurrentContext(_context), nameof(JsSetCurrentContext)); + Check(JsNative.JsCreateRuntime(JsNative.JsRuntimeAttributes.None, null, out _rt), "JsCreateRuntime"); + Check(JsNative.JsCreateContext(_rt, out _ctx), "JsCreateContext"); + Check(JsNative.JsSetCurrentContext(_ctx), "JsSetCurrentContext"); } - /// - /// Evaluates JavaScript and returns the result converted to string (via JsConvertValueToString). - /// public string EvaluateToString(string script, string sourceUrl = "repl") { if (_disposed) throw new ObjectDisposedException(nameof(JsCore)); - if (script is null) throw new ArgumentNullException(nameof(script)); + if (script == null) throw new ArgumentNullException(nameof(script)); - Check(JsRunScript(script, IntPtr.Zero, sourceUrl, out var result), nameof(JsRunScript)); + JsNative.JsValue result; + Check(JsNative.JsRunScript(script, UIntPtr.Zero, sourceUrl, out result), "JsRunScript"); - // Convert result -> JsString - Check(JsConvertValueToString(result, out var jsString), nameof(JsConvertValueToString)); + JsNative.JsValue jsStr; + Check(JsNative.JsConvertValueToString(result, out jsStr), "JsConvertValueToString"); - // Extract pointer/length (UTF-16) and marshal to managed string - Check(JsStringToPointer(jsString, out var p, out var len), nameof(JsStringToPointer)); - return Marshal.PtrToStringUni(p, checked((int)len)); + IntPtr p; + UIntPtr len; + Check(JsNative.JsStringToPointer(jsStr, out p, out len), "JsStringToPointer"); + + int chars = checked((int)len); + return Marshal.PtrToStringUni(p, chars); } - /// - /// Evaluates JavaScript for side effects; discards the result. - /// - public void Execute(string script, string sourceUrl = "repl") + private static void Check(JsNative.JsErrorCode code, string op) { - _ = EvaluateToString(script, sourceUrl); - } - - private static void Check(JsErrorCode code, string op) - { - if (code != JsErrorCode.JsNoError) - throw new InvalidOperationException($"{op} failed with {code} (0x{(int)code:X})."); + if (code != JsNative.JsErrorCode.JsNoError) + throw new InvalidOperationException(op + " failed: " + code + " (0x" + ((int)code).ToString("X", CultureInfo.InvariantCulture) + ")"); } public void Dispose() @@ -60,112 +54,18 @@ namespace WelsonJS.Launcher try { - // Unset the current context from the SAME physical thread that set it. - JsSetCurrentContext(IntPtr.Zero); - } - catch - { - // Swallow to ensure runtime is disposed. + // Unset current context + JsNative.JsSetCurrentContext(new JsNative.JsContext { Handle = IntPtr.Zero }); } + catch { /* ignore */ } finally { - if (_runtime != IntPtr.Zero) - { - JsDisposeRuntime(_runtime); - _runtime = IntPtr.Zero; - } - _context = IntPtr.Zero; + if (_rt.Handle != IntPtr.Zero) + JsNative.JsDisposeRuntime(_rt); } - GC.SuppressFinalize(this); } - ~JsCore() => Dispose(); - - // ========================= - // P/Invoke surface (as given) - // ========================= - - // Essential (expanded) JsErrorCode set matching ChakraCore’s headers layout. - // Values are grouped by category bases (0x10000, 0x20000, ...). - public enum JsErrorCode - { - // Success - JsNoError = 0, - - // Category bases (useful when inspecting ranges) - JsErrorCategoryUsage = 0x10000, - JsErrorCategoryEngine = 0x20000, - JsErrorCategoryScript = 0x30000, - JsErrorCategoryFatal = 0x40000, - - // Usage errors (0x10001+) - JsErrorInvalidArgument = 0x10001, - JsErrorNullArgument = 0x10002, - JsErrorNoCurrentContext = 0x10003, - JsErrorInExceptionState = 0x10004, - JsErrorNotImplemented = 0x10005, - JsErrorWrongThread = 0x10006, - JsErrorRuntimeInUse = 0x10007, - JsErrorBadSerializedScript = 0x10008, - JsErrorInDisabledState = 0x10009, - JsErrorCannotDisableExecution = 0x1000A, - JsErrorHeapEnumInProgress = 0x1000B, - JsErrorArgumentNotObject = 0x1000C, - JsErrorInProfileCallback = 0x1000D, - JsErrorInThreadServiceCallback = 0x1000E, - JsErrorCannotSerializeDebugScript = 0x1000F, - JsErrorAlreadyDebuggingContext = 0x10010, - JsErrorAlreadyProfilingContext = 0x10011, - JsErrorIdleNotEnabled = 0x10012, - - // Engine errors (0x20001+) - JsErrorOutOfMemory = 0x20001, - JsErrorBadFPUState = 0x20002, - - // Script errors (0x30001+) - JsErrorScriptException = 0x30001, - JsErrorScriptCompile = 0x30002, - JsErrorScriptTerminated = 0x30003, - JsErrorScriptEvalDisabled = 0x30004, - - // Fatal (0x40001) - JsErrorFatal = 0x40001, - - // Misc/diagnostic (0x50000+) - JsErrorWrongRuntime = 0x50000, - JsErrorDiagAlreadyInDebugMode = 0x50001, - JsErrorDiagNotInDebugMode = 0x50002, - JsErrorDiagNotAtBreak = 0x50003, - JsErrorDiagInvalidHandle = 0x50004, - JsErrorDiagObjectNotFound = 0x50005, - JsErrorDiagUnableToPerformAction = 0x50006, - } - - [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)] - public static extern JsErrorCode JsCreateRuntime(uint attributes, IntPtr callback, out IntPtr runtime); - - [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)] - public static extern JsErrorCode JsCreateContext(IntPtr runtime, out IntPtr context); - - [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)] - public static extern JsErrorCode JsSetCurrentContext(IntPtr context); - - [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - public static extern JsErrorCode JsRunScript(string script, IntPtr sourceContext, string sourceUrl, out IntPtr result); - - [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)] - public static extern JsErrorCode JsConvertValueToString(IntPtr value, out IntPtr stringValue); - - [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)] - public static extern JsErrorCode JsStringToPointer(IntPtr value, out IntPtr buffer, out UIntPtr length); - - // Note: Unsetting is typically done via JsSetCurrentContext(IntPtr.Zero) - // Kept here only if your build exposes this symbol. - [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "JsSetCurrentContext")] - public static extern JsErrorCode JsUnSetCurrentContext(IntPtr zero); - - [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)] - public static extern JsErrorCode JsDisposeRuntime(IntPtr runtime); + ~JsCore() { Dispose(); } } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/JsNative.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/JsNative.cs new file mode 100644 index 0000000..3f44823 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/JsNative.cs @@ -0,0 +1,93 @@ +using System; +using System.Runtime.InteropServices; + +namespace WelsonJS.Launcher +{ + public static class JsNative + { + // === Enums / handles === + [Flags] + public enum JsRuntimeAttributes : uint + { + None = 0x00000000, + DisableBackgroundWork = 0x00000001, + AllowScriptInterrupt = 0x00000002, + EnableIdleProcessing = 0x00000004, + DisableNativeCodeGeneration = 0x00000008, + EnableExperimentalFeatures = 0x00000010, + } + + // ChakraCore typedefs are opaque pointers; represent as IntPtr + public struct JsRuntime { public IntPtr Handle; } + public struct JsContext { public IntPtr Handle; } + public struct JsValue { public IntPtr Handle; } + + // JsErrorCode (essential subset; expand as needed) + public enum JsErrorCode + { + JsNoError = 0, + + // Usage + JsErrorInvalidArgument = 0x10001, + JsErrorNullArgument = 0x10002, + JsErrorNoCurrentContext = 0x10003, + JsErrorInExceptionState = 0x10004, + JsErrorNotImplemented = 0x10005, + JsErrorWrongThread = 0x10006, + JsErrorRuntimeInUse = 0x10007, + + // Script + JsErrorScriptException = 0x30001, + JsErrorScriptCompile = 0x30002, + JsErrorScriptTerminated = 0x30003, + + // Engine + JsErrorOutOfMemory = 0x20001, + + // Fatal + JsErrorFatal = 0x40001, + } + + // Thread service callback: __stdcall + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate JsErrorCode JsThreadServiceCallback(IntPtr callback, IntPtr callbackState); + + // ======= FIXED SIGNATURES (StdCall + Unicode) ======= + + [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall)] + public static extern JsErrorCode JsCreateRuntime( + JsRuntimeAttributes attributes, + JsThreadServiceCallback threadService, // pass null if unused + out JsRuntime runtime); + + [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall)] + public static extern JsErrorCode JsCreateContext( + JsRuntime runtime, + out JsContext newContext); + + [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall)] + public static extern JsErrorCode JsSetCurrentContext(JsContext context); + + // JsSourceContext is size_t → UIntPtr; strings are wide-char + [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)] + public static extern JsErrorCode JsRunScript( + string script, + UIntPtr sourceContext, + string sourceUrl, + out JsValue result); + + [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall)] + public static extern JsErrorCode JsConvertValueToString(JsValue value, out JsValue stringValue); + + // Returns pointer to UTF-16 buffer + length (size_t) for a JsString value + [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall)] + public static extern JsErrorCode JsStringToPointer( + JsValue value, + out IntPtr buffer, + out UIntPtr length); + + // Unset by passing "invalid" context (JS_INVALID_REFERENCE is typically null) + [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.StdCall)] + public static extern JsErrorCode JsDisposeRuntime(JsRuntime runtime); + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs index 6d23016..be77132 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs @@ -60,13 +60,13 @@ namespace WelsonJS.Launcher }, TaskScheduler.Default); // Add resource tools - _tools.Add(new ResourceTools.Completion(this, _httpClient)); - _tools.Add(new ResourceTools.Settings(this, _httpClient)); - _tools.Add(new ResourceTools.ChromiumDevTools(this, _httpClient)); - _tools.Add(new ResourceTools.DnsQuery(this, _httpClient)); - _tools.Add(new ResourceTools.IpQuery(this, _httpClient)); - _tools.Add(new ResourceTools.TwoFactorAuth(this, _httpClient)); - _tools.Add(new ResourceTools.Whois(this, _httpClient)); + _tools.Add(new ResourceTools.Completion(this, _httpClient, _logger)); + _tools.Add(new ResourceTools.Settings(this, _httpClient, _logger)); + _tools.Add(new ResourceTools.ChromiumDevTools(this, _httpClient, _logger)); + _tools.Add(new ResourceTools.DnsQuery(this, _httpClient, _logger)); + _tools.Add(new ResourceTools.IpQuery(this, _httpClient, _logger)); + _tools.Add(new ResourceTools.TwoFactorAuth(this, _httpClient, _logger)); + _tools.Add(new ResourceTools.Whois(this, _httpClient, _logger)); // Register the prefix _listener.Prefixes.Add(prefix); diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/ChromiumDevTools.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/ChromiumDevTools.cs index 81feeb3..d56c5ec 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/ChromiumDevTools.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/ChromiumDevTools.cs @@ -19,13 +19,16 @@ namespace WelsonJS.Launcher.ResourceTools { private readonly ResourceServer Server; private readonly HttpClient _httpClient; + private readonly ICompatibleLogger _logger; private readonly WebSocketManager _wsManager = new WebSocketManager(); private const string Prefix = "devtools/"; - public ChromiumDevTools(ResourceServer server, HttpClient httpClient) + public ChromiumDevTools(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger) { Server = server; + _httpClient = httpClient; + _logger = logger; } public bool CanHandle(string path) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs index 57bb482..34abe58 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs @@ -22,13 +22,16 @@ namespace WelsonJS.Launcher.ResourceTools { private readonly ResourceServer Server; private readonly HttpClient _httpClient; + private readonly ICompatibleLogger _logger; private const string Prefix = "completion/"; private readonly ConcurrentBag DiscoveredExecutables = new ConcurrentBag(); - public Completion(ResourceServer server, HttpClient httpClient) + public Completion(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger) { Server = server; + _httpClient = httpClient; + _logger = logger; Task.Run(async () => await SafeDiscoverAsync(DiscoverFromInstalledSoftware)); Task.Run(async () => await SafeDiscoverAsync(DiscoverFromPathVariable)); @@ -160,7 +163,7 @@ namespace WelsonJS.Launcher.ResourceTools { if (!Directory.Exists(path)) { - Trace.TraceInformation("Directory does not exist: {0}", path); + _logger.Info("Directory does not exist: {0}", path); return; } @@ -175,7 +178,7 @@ namespace WelsonJS.Launcher.ResourceTools } catch (Exception ex) { - Trace.TraceInformation("Error enumerating executables in '{0}': {1}", path, ex.Message); + _logger.Info("Error enumerating executables in '{0}': {1}", path, ex.Message); } } @@ -195,7 +198,7 @@ namespace WelsonJS.Launcher.ResourceTools } catch (Exception ex) { - Trace.TraceError($"Discovery failed: {ex.Message}"); + _logger.Error($"Discovery failed: {ex.Message}"); } } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DnsQuery.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DnsQuery.cs index 3b4c8b1..831294b 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DnsQuery.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DnsQuery.cs @@ -18,16 +18,19 @@ namespace WelsonJS.Launcher.ResourceTools { private readonly ResourceServer Server; private readonly HttpClient _httpClient; + private readonly ICompatibleLogger _logger; private const string Prefix = "dns-query/"; private string DnsServer; private const int DnsPort = 53; private const int Timeout = 5000; private static readonly Random _random = new Random(); - public DnsQuery(ResourceServer server, HttpClient httpClient) + public DnsQuery(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger) { Server = server; + _httpClient = httpClient; + _logger = logger; DnsServer = Program.GetAppConfig("DnsServerAddress"); } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/IpQuery.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/IpQuery.cs index 3a3aa6b..68d7400 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/IpQuery.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/IpQuery.cs @@ -14,12 +14,15 @@ namespace WelsonJS.Launcher.ResourceTools { private readonly ResourceServer Server; private readonly HttpClient _httpClient; + private readonly ICompatibleLogger _logger; private const string Prefix = "ip-query/"; - public IpQuery(ResourceServer server, HttpClient httpClient) + public IpQuery(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger) { Server = server; + _httpClient = httpClient; + _logger = logger; } public bool CanHandle(string path) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Settings.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Settings.cs index 2f74940..6d6fd94 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Settings.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Settings.cs @@ -20,12 +20,15 @@ namespace WelsonJS.Launcher.ResourceTools { private readonly ResourceServer Server; private readonly HttpClient _httpClient; + private readonly ICompatibleLogger _logger; private const string Prefix = "settings"; - public Settings(ResourceServer server, HttpClient httpClient) + public Settings(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger) { Server = server; + _httpClient = httpClient; + _logger = logger; } public bool CanHandle(string path) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/TwoFactorAuth.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/TwoFactorAuth.cs index e7094e2..aa44ba6 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/TwoFactorAuth.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/TwoFactorAuth.cs @@ -19,14 +19,17 @@ namespace WelsonJS.Launcher.ResourceTools { private readonly ResourceServer Server; private readonly HttpClient _httpClient; + private readonly ICompatibleLogger _logger; private const string Prefix = "tfa/"; private const string Base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; private static readonly int[] ValidKeyCharLengths = new[] { 16, 32 }; - public TwoFactorAuth(ResourceServer server, HttpClient httpClient) + public TwoFactorAuth(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger) { Server = server; + _httpClient = httpClient; + _logger = logger; } public bool CanHandle(string path) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs index 45ae8b9..fc82e85 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs @@ -15,12 +15,15 @@ namespace WelsonJS.Launcher.ResourceTools { private readonly ResourceServer Server; private readonly HttpClient _httpClient; + private readonly ICompatibleLogger _logger; private const string Prefix = "whois/"; - public Whois(ResourceServer server, HttpClient httpClient) + public Whois(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger) { Server = server; + _httpClient = httpClient; + _logger = logger; } public bool CanHandle(string path) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs index 5f86237..6efbaf6 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs @@ -6,7 +6,9 @@ // We use the ICompatibleLogger interface to maintain a BCL-first style. // This allows for later replacement with logging libraries such as ILogger or Log4Net. // +using System; using System.Diagnostics; +using System.Linq; namespace WelsonJS.Launcher { @@ -21,7 +23,7 @@ namespace WelsonJS.Launcher _logFileName = (typeof(TraceLogger).Namespace ?? "WelsonJS.Launcher") + ".log"; Trace.Listeners.Add(new TextWriterTraceListener(_logFileName)); } - catch (System.Exception ex) + catch (Exception ex) { // Fallback when the process cannot write to the working directory Trace.Listeners.Add(new ConsoleTraceListener()); @@ -30,8 +32,27 @@ namespace WelsonJS.Launcher Trace.AutoFlush = true; } - public void Info(string message) => Trace.TraceInformation(message); - public void Warn(string message) => Trace.TraceWarning(message); - public void Error(string message) => Trace.TraceError(message); + public void Info(params object[] args) => Trace.TraceInformation(Format(args)); + public void Warn(params object[] args) => Trace.TraceWarning(Format(args)); + public void Error(params object[] args) => Trace.TraceError(Format(args)); + + private static string Format(object[] args) + { + if (args == null || args.Length == 0) return string.Empty; + + if (args.Length == 1) + return args[0]?.ToString() ?? string.Empty; + + string format = args[0]?.ToString() ?? string.Empty; + try + { + return string.Format(format, args.Skip(1).ToArray()); + } + catch + { + // In case of mismatched format placeholders + return string.Join(" ", args); + } + } } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj index c76f31e..a5b2e8e 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj @@ -89,8 +89,10 @@ + +