Merge pull request #361 from gnh1201/dev
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
Deploy Jekyll with GitHub Pages dependencies preinstalled / build (push) Waiting to run
Deploy Jekyll with GitHub Pages dependencies preinstalled / deploy (push) Blocked by required conditions

Introduce the Catswords.Phantomizer to load DLL files via network
This commit is contained in:
Namhyeon Go 2025-12-08 02:19:02 +09:00 committed by GitHub
commit 2040a02628
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 260 additions and 40 deletions

View File

@ -25,6 +25,7 @@ after_build:
#- cmd: xcopy /s /y WelsonJS.Toolkit\WelsonJS.Toolkit\bin\x86\%CONFIGURATION%\* artifacts\ #- cmd: xcopy /s /y WelsonJS.Toolkit\WelsonJS.Toolkit\bin\x86\%CONFIGURATION%\* artifacts\
- cmd: xcopy /s /y WelsonJS.Toolkit\WelsonJS.Service\bin\x86\%CONFIGURATION%\* artifacts\ - cmd: xcopy /s /y WelsonJS.Toolkit\WelsonJS.Service\bin\x86\%CONFIGURATION%\* artifacts\
- cmd: xcopy /s /y WelsonJS.Toolkit\WelsonJS.Launcher\bin\x86\%CONFIGURATION%\* artifacts\ - cmd: xcopy /s /y WelsonJS.Toolkit\WelsonJS.Launcher\bin\x86\%CONFIGURATION%\* artifacts\
- cmd: xcopy /s /y WelsonJS.Toolkit\Catswords.Phantomizer\bin\%CONFIGURATION%\netstandard2.0\* artifacts\
- cmd: nuget pack WelsonJS.Toolkit\WelsonJS.Toolkit\ -properties Configuration=%CONFIGURATION% -properties Platform=x86 -OutputDirectory artifacts\ - cmd: nuget pack WelsonJS.Toolkit\WelsonJS.Toolkit\ -properties Configuration=%CONFIGURATION% -properties Platform=x86 -OutputDirectory artifacts\
- ps: Start-BitsTransfer -Source "https://catswords.blob.core.windows.net/welsonjs/welsonjs_setup_unsigned.exe" -Destination "artifacts\welsonjs_setup.exe" - ps: Start-BitsTransfer -Source "https://catswords.blob.core.windows.net/welsonjs/welsonjs_setup_unsigned.exe" -Destination "artifacts\welsonjs_setup.exe"
- ps: Start-BitsTransfer -Source "https://catswords.blob.core.windows.net/welsonjs/chakracore-build/x86_release/ChakraCore.dll" -Destination "artifacts\ChakraCore.dll" - ps: Start-BitsTransfer -Source "https://catswords.blob.core.windows.net/welsonjs/chakracore-build/x86_release/ChakraCore.dll" -Destination "artifacts\ChakraCore.dll"

View File

@ -1,10 +1,11 @@
// AssemblyLoader.cs // AssemblyLoader.cs (Catswords.Phantomizer)
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Namhyeon Go <gnh1201@catswords.re.kr>, 2025 Catswords OSS and WelsonJS Contributors // SPDX-FileCopyrightText: Namhyeon Go <gnh1201@catswords.re.kr>, 2025 Catswords OSS and WelsonJS Contributors
// https://github.com/gnh1201/welsonjs // https://github.com/gnh1201/welsonjs
// //
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Net; using System.Net;
@ -12,14 +13,14 @@ using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace WelsonJS.Launcher namespace Catswords.Phantomizer
{ {
/// <summary> /// <summary>
/// Network-aware loader for managed (.NET) and native (C/C++) binaries. /// Network-aware loader for managed (.NET) and native (C/C++) binaries.
/// - Managed assemblies resolve via AssemblyResolve /// - Managed assemblies resolve via AssemblyResolve
/// - Native modules explicitly loaded via LoadNativeModules(...) /// - Native modules explicitly loaded via LoadNativeModules(...)
/// - All DLLs must have valid Authenticode signatures /// - All DLLs must have valid Authenticode signatures
/// - Cached at: %APPDATA%\WelsonJS\assembly\{Name}\{Version}\ /// - Cached at: %APPDATA%\Catswords\assembly\{Name}\{Version}\
/// - BaseUrl must be set by Main() before calling Register() /// - BaseUrl must be set by Main() before calling Register()
/// </summary> /// </summary>
public static class AssemblyLoader public static class AssemblyLoader
@ -30,12 +31,12 @@ namespace WelsonJS.Launcher
/// Must be set before Register() or LoadNativeModules(). /// Must be set before Register() or LoadNativeModules().
/// </summary> /// </summary>
public static string BaseUrl { get; set; } = null; public static string BaseUrl { get; set; } = null;
public static ICompatibleLogger Logger { get; set; } = null; public static string LoaderNamespace { get; set; } = typeof(AssemblyLoader).Namespace;
public static string AppName { get; set; } = "Catswords";
private static readonly object SyncRoot = new object(); private static readonly object SyncRoot = new object();
private static bool _registered; private static bool _registered;
private static readonly string LoaderNamespace = typeof(AssemblyLoader).Namespace ?? "WelsonJS.Launcher";
private static readonly HttpClientHandler LegacyHttpHandler = new HttpClientHandler private static readonly HttpClientHandler LegacyHttpHandler = new HttpClientHandler
{ {
AutomaticDecompression = DecompressionMethods.None AutomaticDecompression = DecompressionMethods.None
@ -168,20 +169,20 @@ namespace WelsonJS.Launcher
if (string.IsNullOrWhiteSpace(BaseUrl)) if (string.IsNullOrWhiteSpace(BaseUrl))
{ {
Logger?.Error("AssemblyLoader.Register() called but BaseUrl is not set."); Trace.TraceError("AssemblyLoader.Register() called but BaseUrl is not set.");
throw new InvalidOperationException("AssemblyLoader.BaseUrl must be configured before Register()."); throw new InvalidOperationException("AssemblyLoader.BaseUrl must be configured before Register().");
} }
if (!BaseUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) if (!BaseUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{ {
Logger?.Error("AssemblyLoader.BaseUrl must use HTTPS for security."); Trace.TraceError("AssemblyLoader.BaseUrl must use HTTPS for security.");
throw new InvalidOperationException("AssemblyLoader.BaseUrl must use HTTPS."); throw new InvalidOperationException("AssemblyLoader.BaseUrl must use HTTPS.");
} }
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve; AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
_registered = true; _registered = true;
Logger?.Info("AssemblyLoader: AssemblyResolve handler registered."); Trace.TraceInformation("AssemblyLoader: AssemblyResolve handler registered.");
} }
} }
@ -203,17 +204,17 @@ namespace WelsonJS.Launcher
lock (SyncRoot) lock (SyncRoot)
{ {
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string cacheDir = Path.Combine(appData, "WelsonJS", "assembly", ownerAssemblyName, versionString); string cacheDir = Path.Combine(appData, AppName, "assembly", ownerAssemblyName, versionString);
Directory.CreateDirectory(cacheDir); Directory.CreateDirectory(cacheDir);
try try
{ {
if (!SetDllDirectory(cacheDir)) if (!SetDllDirectory(cacheDir))
Logger?.Warn("SetDllDirectory failed for: {0}", cacheDir); Trace.TraceWarning("SetDllDirectory failed for: {0}", cacheDir);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger?.Warn("SetDllDirectory threw exception: {0}", ex.Message); Trace.TraceWarning("SetDllDirectory threw exception: {0}", ex.Message);
} }
foreach (string raw in fileNames) foreach (string raw in fileNames)
@ -228,11 +229,11 @@ namespace WelsonJS.Launcher
{ {
string url = $"{BaseUrl.TrimEnd('/')}/native/{ownerAssemblyName}/{versionString}/{fileName}"; string url = $"{BaseUrl.TrimEnd('/')}/native/{ownerAssemblyName}/{versionString}/{fileName}";
DownloadFile(url, localPath); DownloadFile(url, localPath);
Logger?.Info("Downloaded native module: {0}", fileName); Trace.TraceInformation("Downloaded native module: {0}", fileName);
} }
else else
{ {
Logger?.Info("Using cached native module: {0}", localPath); Trace.TraceInformation("Using cached native module: {0}", localPath);
} }
EnsureSignedFileOrThrow(localPath, fileName); EnsureSignedFileOrThrow(localPath, fileName);
@ -241,12 +242,12 @@ namespace WelsonJS.Launcher
if (h == IntPtr.Zero) if (h == IntPtr.Zero)
{ {
int errorCode = Marshal.GetLastWin32Error(); int errorCode = Marshal.GetLastWin32Error();
Logger?.Error("LoadLibrary failed for {0} with error code {1}", localPath, errorCode); Trace.TraceError("LoadLibrary failed for {0} with error code {1}", localPath, errorCode);
throw new InvalidOperationException($"Failed to load native module: {fileName} (error: {errorCode})"); throw new InvalidOperationException($"Failed to load native module: {fileName} (error: {errorCode})");
} }
else else
{ {
Logger?.Info("Loaded native module: {0}", fileName); Trace.TraceInformation("Loaded native module: {0}", fileName);
} }
} }
} }
@ -277,7 +278,7 @@ namespace WelsonJS.Launcher
private static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args) private static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{ {
Logger?.Info("AssemblyResolve: {0}", args.Name); Trace.TraceInformation("AssemblyResolve: {0}", args.Name);
AssemblyName req = new AssemblyName(args.Name); AssemblyName req = new AssemblyName(args.Name);
string simpleName = req.Name; string simpleName = req.Name;
@ -290,7 +291,7 @@ namespace WelsonJS.Launcher
var entryName = entry.GetName().Name; var entryName = entry.GetName().Name;
if (string.Equals(simpleName, entryName, StringComparison.OrdinalIgnoreCase)) if (string.Equals(simpleName, entryName, StringComparison.OrdinalIgnoreCase))
{ {
Logger?.Info("AssemblyResolve: skipping entry assembly {0}", simpleName); Trace.TraceInformation("AssemblyResolve: skipping entry assembly {0}", simpleName);
return null; return null;
} }
} }
@ -301,7 +302,7 @@ namespace WelsonJS.Launcher
lock (SyncRoot) lock (SyncRoot)
{ {
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string cacheDir = Path.Combine(appData, "WelsonJS", "assembly", simpleName, versionStr); string cacheDir = Path.Combine(appData, AppName, "assembly", simpleName, versionStr);
string dllPath = Path.Combine(cacheDir, simpleName + ".dll"); string dllPath = Path.Combine(cacheDir, simpleName + ".dll");
Directory.CreateDirectory(cacheDir); Directory.CreateDirectory(cacheDir);
@ -310,16 +311,16 @@ namespace WelsonJS.Launcher
{ {
string url = $"{BaseUrl.TrimEnd('/')}/managed/{simpleName}/{versionStr}/{simpleName}.dll"; string url = $"{BaseUrl.TrimEnd('/')}/managed/{simpleName}/{versionStr}/{simpleName}.dll";
DownloadFile(url, dllPath); DownloadFile(url, dllPath);
Logger?.Info("Downloaded managed assembly: {0}", simpleName); Trace.TraceInformation("Downloaded managed assembly: {0}", simpleName);
} }
else else
{ {
Logger?.Info("Using cached managed assembly: {0}", dllPath); Trace.TraceInformation("Using cached managed assembly: {0}", dllPath);
} }
if (!File.Exists(dllPath)) if (!File.Exists(dllPath))
{ {
Logger?.Warn("AssemblyResolve: managed assembly not found after download attempt: {0}", simpleName); Trace.TraceWarning("AssemblyResolve: managed assembly not found after download attempt: {0}", simpleName);
return null; return null;
} }
@ -345,13 +346,13 @@ namespace WelsonJS.Launcher
if (isDll && TryDownloadCompressedFile(gzUrl, dest)) if (isDll && TryDownloadCompressedFile(gzUrl, dest))
{ {
Logger?.Info("Downloaded and decompressed file to: {0}", dest); Trace.TraceInformation("Downloaded and decompressed file to: {0}", dest);
downloaded = true; downloaded = true;
} }
if (!downloaded) if (!downloaded)
{ {
Logger?.Info("Downloading file from: {0}", url); Trace.TraceInformation("Downloading file from: {0}", url);
res = Http.GetAsync(url).GetAwaiter().GetResult(); res = Http.GetAsync(url).GetAwaiter().GetResult();
res.EnsureSuccessStatusCode(); res.EnsureSuccessStatusCode();
@ -361,7 +362,7 @@ namespace WelsonJS.Launcher
s.CopyTo(fs); s.CopyTo(fs);
} }
Logger?.Info("Downloaded file to: {0}", dest); Trace.TraceInformation("Downloaded file to: {0}", dest);
} }
if (!File.Exists(dest)) if (!File.Exists(dest))
@ -371,12 +372,12 @@ namespace WelsonJS.Launcher
} }
catch (HttpRequestException ex) catch (HttpRequestException ex)
{ {
Logger?.Error("Network or I/O error downloading {0}: {1}", url, ex.Message); Trace.TraceError("Network or I/O error downloading {0}: {1}", url, ex.Message);
throw; throw;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger?.Error("Unexpected error downloading {0}: {1}", url, ex.Message); Trace.TraceError("Unexpected error downloading {0}: {1}", url, ex.Message);
throw; throw;
} }
finally finally
@ -396,7 +397,7 @@ namespace WelsonJS.Launcher
{ {
if (res.StatusCode == HttpStatusCode.NotFound) if (res.StatusCode == HttpStatusCode.NotFound)
{ {
Logger?.Info("No gzipped variant at {0}; falling back to uncompressed URL.", gzUrl); Trace.TraceInformation("No gzipped variant at {0}; falling back to uncompressed URL.", gzUrl);
return false; return false;
} }
@ -419,12 +420,12 @@ namespace WelsonJS.Launcher
} }
catch (HttpRequestException ex) catch (HttpRequestException ex)
{ {
Logger?.Warn("Network or I/O error downloading compressed file from {0}: {1}", gzUrl, ex.Message); Trace.TraceWarning("Network or I/O error downloading compressed file from {0}: {1}", gzUrl, ex.Message);
throw; throw;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger?.Error("Unexpected error downloading compressed file from {0}: {1}", gzUrl, ex.Message); Trace.TraceError("Unexpected error downloading compressed file from {0}: {1}", gzUrl, ex.Message);
throw; throw;
} }
finally finally
@ -437,7 +438,7 @@ namespace WelsonJS.Launcher
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger?.Info("Failed to delete temporary file {0}: {1}", tempFile, ex.Message); Trace.TraceInformation("Failed to delete temporary file {0}: {1}", tempFile, ex.Message);
} }
} }
} }
@ -461,7 +462,7 @@ namespace WelsonJS.Launcher
{ {
if (!File.Exists(path)) if (!File.Exists(path))
{ {
Logger?.Error("File does not exist for signature verification: {0}", logicalName); Trace.TraceError("File does not exist for signature verification: {0}", logicalName);
throw new FileNotFoundException("File not found for signature verification: " + logicalName, path); throw new FileNotFoundException("File not found for signature verification: " + logicalName, path);
} }
@ -469,17 +470,17 @@ namespace WelsonJS.Launcher
if (status == FileSignatureStatus.Valid) if (status == FileSignatureStatus.Valid)
{ {
Logger?.Info("Signature OK: {0}", logicalName); Trace.TraceInformation("Signature OK: {0}", logicalName);
return; return;
} }
if (status == FileSignatureStatus.NoSignature) if (status == FileSignatureStatus.NoSignature)
{ {
Logger?.Error("BLOCKED unsigned binary: {0}", logicalName); Trace.TraceError("BLOCKED unsigned binary: {0}", logicalName);
throw new InvalidOperationException("Unsigned binary blocked: " + logicalName); throw new InvalidOperationException("Unsigned binary blocked: " + logicalName);
} }
Logger?.Error("BLOCKED invalid signature: {0}", logicalName); Trace.TraceError("BLOCKED invalid signature: {0}", logicalName);
throw new InvalidOperationException("Invalid signature: " + logicalName); throw new InvalidOperationException("Invalid signature: " + logicalName);
} }

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Company>Catswords Research</Company>
<Authors>Namhyeon Go, 2025 Catswords OSS and WelsonJS Contributors</Authors>
<PackageProjectUrl>https://github.com/gnh1201/welsonjs</PackageProjectUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/gnh1201/welsonjs</RepositoryUrl>
<PackageTags>loader</PackageTags>
<Description>Catswords.Phantomizer is an HTTP-based dynamic-link library (DLL) loader designed for .NET applications. It allows your application to fetch and load assemblies directly from your CDN (Azure Blob, S3, Cloudflare R2, etc.) at runtime, with optional GZip compression support.</Description>
<Copyright>Namhyeon Go, 2025 Catswords OSS and WelsonJS Contributors</Copyright>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) Namhyeon Go <gnh1201@catswords.re.kr>, 2025 Catswords OSS and WelsonJS Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,117 @@
# Catswords.Phantomizer
**Catswords.Phantomizer** is an HTTP-based dynamic-link library (DLL) loader designed for .NET applications.
It allows your application to fetch and load assemblies directly from your CDN (Azure Blob, S3, Cloudflare R2, etc.) at runtime, with optional GZip compression support.
---
## 🚀 Features
* Load managed (`*.dll`) and native (`*.dll`) assemblies over HTTP
* Optional `.dll.gz` decompression for faster network delivery
* CDN-friendly URL structure
* Easy bootstrap through a small embedded loader
* Loader is implemented using **pure .NET BCL only**, ensuring stable operation without external dependencies
* Built-in **code-signing verification** support to ensure assemblies are trusted and tamper-free
---
## 📦 How to Use
### 1. Embed Phantomizer into your project
Add `Catswords.Phantomizer.dll.gz` to your `Resources.resx` file.
---
### 2. Initialize Phantomizer at application startup
Place the following code inside your `Main` method, static constructor, or any early entry point:
```csharp
static Program() {
// ...
InitializeAssemblyLoader();
// ...
}
private static void InitializeAssemblyLoader()
{
byte[] gzBytes = Properties.Resources.Phantomizer;
byte[] dllBytes;
using (var input = new MemoryStream(gzBytes))
using (var gz = new GZipStream(input, CompressionMode.Decompress))
using (var output = new MemoryStream())
{
gz.CopyTo(output);
dllBytes = output.ToArray();
}
Assembly phantomAsm = Assembly.Load(dllBytes);
Type loaderType = phantomAsm.GetType("Catswords.Phantomizer.AssemblyLoader", true);
loaderType.GetProperty("BaseUrl")?.SetValue(null, GetAppConfig("AssemblyBaseUrl")); // Set your CDN base URL
loaderType.GetProperty("LoaderNamespace")?.SetValue(null, typeof(Program).Namespace);
loaderType.GetProperty("AppName")?.SetValue(null, "WelsonJS"); // Set your application name
loaderType.GetMethod("Register")?.Invoke(null, null);
var loadNativeModulesMethod = loaderType.GetMethod(
"LoadNativeModules",
BindingFlags.Public | BindingFlags.Static,
binder: null,
types: new[] { typeof(string), typeof(Version), typeof(string[]) },
modifiers: null
);
if (loadNativeModulesMethod == null)
throw new InvalidOperationException("LoadNativeModules(string, Version, string[]) method not found.");
loadNativeModulesMethod.Invoke(null, new object[]
{
"ChakraCore",
new Version(1, 13, 0, 0),
new[] { "ChakraCore.dll" }
});
}
```
---
### 3. Upload your DLL files to a CDN
Upload your managed and native assemblies to your CDN following the URL pattern below.
#### 📁 URL Rules
| Type | Example URL | Description |
| ------------------ | ----------------------------------------------------------------------------------- | ------------------------------- |
| Managed DLL | `https://example.cdn.tld/packages/managed/MyManagedLib/1.0.0.0/MyManagedLib.dll` | Normal .NET assembly |
| Managed DLL (GZip) | `https://example.cdn.tld/packages/managed/MyManagedLib/1.0.0.0/MyManagedLib.dll.gz` | GZip-compressed .NET assembly |
| Native DLL | `https://example.cdn.tld/packages/native/MyNativeLib/1.0.0.0/MyNativeLib.dll` | Native assembly |
| Native DLL (GZip) | `https://example.cdn.tld/packages/native/MyNativeLib/1.0.0.0/MyNativeLib.dll.gz` | GZip-compressed native assembly |
---
### 4. 🎉 Start loading assemblies over HTTP
Once Phantomizer is initialized, your application will automatically fetch missing assemblies from your CDN.
---
## Download the pre-compiled file
* [Download Catswords.Phantomizer.dll.gz (catswords.blob.core.windows.net)](https://catswords.blob.core.windows.net/welsonjs/packages/managed/Catswords.Phantomizer/1.0.0.0/Catswords.Phantomizer.dll.gz)
---
## Report abuse
* [GitHub Security Advisories (gnh1201/welsonjs)](https://github.com/gnh1201/welsonjs/security)
* [abuse@catswords.re.kr](mailto:abuse@catswords.re.kr)
## Join the community
I am always open. Collaboration, opportunities, and community activities are all welcome.
* ActivityPub [@catswords_oss@catswords.social](https://catswords.social/@catswords_oss)
* XMPP [catswords@conference.omemo.id](xmpp:catswords@conference.omemo.id?join)
* [Join Catswords OSS on Microsoft Teams (teams.live.com)](https://teams.live.com/l/community/FEACHncAhq8ldnojAI)
* [Join Catswords OSS #welsonjs on Discord (discord.gg)](https://discord.gg/XKG5CjtXEj)

View File

@ -8,7 +8,9 @@ using System.Collections.Generic;
using System.Configuration; using System.Configuration;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading; using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
using WelsonJS.Launcher.Telemetry; using WelsonJS.Launcher.Telemetry;
@ -33,10 +35,7 @@ namespace WelsonJS.Launcher
_logger = new TraceLogger(); _logger = new TraceLogger();
// load external assemblies // load external assemblies
AssemblyLoader.BaseUrl = GetAppConfig("AssemblyBaseUrl"); InitializeAssemblyLoader();
AssemblyLoader.Logger = _logger;
AssemblyLoader.Register();
AssemblyLoader.LoadNativeModules("ChakraCore", new Version(1, 13, 0, 0), new[] { "ChakraCore.dll" });
// telemetry // telemetry
try try
@ -114,6 +113,49 @@ namespace WelsonJS.Launcher
_mutex.Dispose(); _mutex.Dispose();
} }
private static void InitializeAssemblyLoader()
{
byte[] gzBytes = Properties.Resources.Phantomizer;
byte[] dllBytes;
using (var input = new MemoryStream(gzBytes))
using (var gz = new GZipStream(input, CompressionMode.Decompress))
using (var output = new MemoryStream())
{
gz.CopyTo(output);
dllBytes = output.ToArray();
}
Assembly phantomAsm = Assembly.Load(dllBytes);
Type loaderType = phantomAsm.GetType("Catswords.Phantomizer.AssemblyLoader", true);
loaderType.GetProperty("BaseUrl")?.SetValue(null, GetAppConfig("AssemblyBaseUrl"));
loaderType.GetProperty("LoaderNamespace")?.SetValue(null, typeof(Program).Namespace);
loaderType.GetProperty("AppName")?.SetValue(null, "WelsonJS");
loaderType.GetMethod("Register")?.Invoke(null, null);
var loadNativeModulesMethod = loaderType.GetMethod(
"LoadNativeModules",
BindingFlags.Public | BindingFlags.Static,
binder: null,
types: new[] { typeof(string), typeof(Version), typeof(string[]) },
modifiers: null
);
if (loadNativeModulesMethod == null)
{
throw new InvalidOperationException("LoadNativeModules(string, Version, string[]) method not found.");
}
loadNativeModulesMethod.Invoke(null, new object[]
{
"ChakraCore",
new Version(1, 13, 0, 0),
new[] { "ChakraCore.dll" }
});
}
public static void RecordFirstDeployTime(string directory, string instanceId) public static void RecordFirstDeployTime(string directory, string instanceId)
{ {
// get current time // get current time

View File

@ -342,6 +342,16 @@ namespace WelsonJS.Launcher.Properties {
} }
} }
/// <summary>
/// System.Byte[] 형식의 지역화된 리소스를 찾습니다.
/// </summary>
internal static byte[] Phantomizer {
get {
object obj = ResourceManager.GetObject("Phantomizer", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary> /// <summary>
/// https://github.com/gnh1201/welsonjs과(와) 유사한 지역화된 문자열을 찾습니다. /// https://github.com/gnh1201/welsonjs과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary> /// </summary>

View File

@ -241,4 +241,7 @@
<data name="AssemblyBaseUrl" xml:space="preserve"> <data name="AssemblyBaseUrl" xml:space="preserve">
<value>https://catswords.blob.core.windows.net/welsonjs/packages</value> <value>https://catswords.blob.core.windows.net/welsonjs/packages</value>
</data> </data>
<data name="Phantomizer" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Catswords.Phantomizer.dll.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root> </root>

View File

@ -88,7 +88,6 @@
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="AssemblyLoader.cs" />
<Compile Include="ICompatibleLogger.cs" /> <Compile Include="ICompatibleLogger.cs" />
<Compile Include="IResourceTool.cs" /> <Compile Include="IResourceTool.cs" />
<Compile Include="JsCore.cs" /> <Compile Include="JsCore.cs" />
@ -171,6 +170,7 @@
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Resources\Catswords.Phantomizer.dll.gz" />
<None Include="Resources\icon_link_128.png" /> <None Include="Resources\icon_link_128.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -17,6 +17,8 @@ Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "WelsonJS.Cryptography", "We
EndProject EndProject
Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "WelsonJS.Cryptography.Test", "WelsonJS.Cryptography.Test\WelsonJS.Cryptography.Test.vbproj", "{C65EC34B-71C7-47CF-912E-D304283EB412}" Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "WelsonJS.Cryptography.Test", "WelsonJS.Cryptography.Test\WelsonJS.Cryptography.Test.vbproj", "{C65EC34B-71C7-47CF-912E-D304283EB412}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Catswords.Phantomizer", "Catswords.Phantomizer\Catswords.Phantomizer.csproj", "{59C67003-C14E-4703-927F-318BFF15F903}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -81,6 +83,14 @@ Global
{C65EC34B-71C7-47CF-912E-D304283EB412}.Release|Any CPU.Build.0 = Release|Any CPU {C65EC34B-71C7-47CF-912E-D304283EB412}.Release|Any CPU.Build.0 = Release|Any CPU
{C65EC34B-71C7-47CF-912E-D304283EB412}.Release|x86.ActiveCfg = Release|x86 {C65EC34B-71C7-47CF-912E-D304283EB412}.Release|x86.ActiveCfg = Release|x86
{C65EC34B-71C7-47CF-912E-D304283EB412}.Release|x86.Build.0 = Release|x86 {C65EC34B-71C7-47CF-912E-D304283EB412}.Release|x86.Build.0 = Release|x86
{59C67003-C14E-4703-927F-318BFF15F903}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{59C67003-C14E-4703-927F-318BFF15F903}.Debug|Any CPU.Build.0 = Debug|Any CPU
{59C67003-C14E-4703-927F-318BFF15F903}.Debug|x86.ActiveCfg = Debug|Any CPU
{59C67003-C14E-4703-927F-318BFF15F903}.Debug|x86.Build.0 = Debug|Any CPU
{59C67003-C14E-4703-927F-318BFF15F903}.Release|Any CPU.ActiveCfg = Release|Any CPU
{59C67003-C14E-4703-927F-318BFF15F903}.Release|Any CPU.Build.0 = Release|Any CPU
{59C67003-C14E-4703-927F-318BFF15F903}.Release|x86.ActiveCfg = Release|Any CPU
{59C67003-C14E-4703-927F-318BFF15F903}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE