// NativeBootstrap.cs // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors // https://github.com/gnh1201/welsonjs // using System; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Collections.Generic; namespace WelsonJS.Launcher { public static class NativeBootstrap { // Win32 APIs [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)] private static extern IntPtr LoadLibrary(string lpFileName); [DllImport("kernel32", SetLastError = true)] private static extern bool SetDefaultDllDirectories(int DirectoryFlags); [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "AddDllDirectory")] private static extern IntPtr AddDllDirectory(string newDirectory); [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool SetDllDirectory(string lpPathName); private const int LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000; /// /// Tries to load native libraries in the following order: /// 1) %APPDATA%\{appDataSubdirectory}\{dllName} /// 2) Application base directory\{dllName} /// /// Must be called before any P/Invoke usage. /// public static void Init(IEnumerable dllNames, string appDataSubdirectory, ICompatibleLogger logger) { if (dllNames == null) throw new ArgumentNullException(nameof(dllNames)); if (logger == null) throw new ArgumentNullException(nameof(logger)); string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); string appDataPath = string.IsNullOrEmpty(appDataSubdirectory) ? appData : Path.Combine(appData, appDataSubdirectory); string asmLocation = Assembly.GetEntryAssembly()?.Location ?? Assembly.GetExecutingAssembly().Location ?? AppContext.BaseDirectory; string appBaseDirectory = Path.GetDirectoryName(asmLocation) ?? AppContext.BaseDirectory; var triedPaths = new List(); foreach (string dllName in dllNames) { if (string.IsNullOrWhiteSpace(dllName)) continue; // 1) %APPDATA% subdirectory string candidate1 = Path.Combine(appDataPath, dllName); triedPaths.Add(candidate1); if (TryLoad(candidate1, logger)) return; // 2) Application base directory string candidate2 = Path.Combine(appBaseDirectory, dllName); triedPaths.Add(candidate2); if (TryLoad(candidate2, logger)) return; } string message = "Failed to load requested native libraries.\n" + "Tried:\n " + string.Join("\n ", triedPaths); logger.Error(message); throw new FileNotFoundException(message); } private static bool TryLoad(string fullPath, ICompatibleLogger logger) { try { if (!File.Exists(fullPath)) { logger.Info($"Not found: {fullPath}"); return false; } string directoryPath = Path.GetDirectoryName(fullPath) ?? AppContext.BaseDirectory; if (!TryRegisterSearchDirectory(directoryPath, logger)) { logger.Warn($"Could not register search directory: {directoryPath}"); } logger.Info($"Loading: {fullPath}"); IntPtr handle = LoadLibrary(fullPath); if (handle == IntPtr.Zero) { int err = Marshal.GetLastWin32Error(); logger.Warn($"LoadLibrary failed for {fullPath} (Win32Error={err})."); return false; } logger.Info($"Successfully loaded: {fullPath}"); return true; } catch (Exception ex) { logger.Warn($"Exception while loading {fullPath}: {ex.Message}"); return false; } } private static bool TryRegisterSearchDirectory(string directoryPath, ICompatibleLogger logger) { try { bool ok = SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); if (!ok) { int e = Marshal.GetLastWin32Error(); logger.Warn($"SetDefaultDllDirectories failed (Win32Error={e}), fallback to SetDllDirectory."); return SetDllDirectory(directoryPath); } IntPtr cookie = AddDllDirectory(directoryPath); if (cookie == IntPtr.Zero) { int e = Marshal.GetLastWin32Error(); logger.Warn($"AddDllDirectory failed (Win32Error={e}), fallback to SetDllDirectory."); return SetDllDirectory(directoryPath); } logger.Info($"Registered native DLL search directory: {directoryPath}"); return true; } catch (EntryPointNotFoundException) { logger.Warn("DefaultDllDirectories API not available. Using SetDllDirectory fallback."); return SetDllDirectory(directoryPath); } catch (Exception ex) { logger.Warn($"Register search directory failed for {directoryPath}: {ex.Message}"); return false; } } } }