chore(bridge): 删除桥接包的提交记录和Git属性配置 删除了 .codely-bridge-commit 文件中存储的提交哈希值 移除了 .gitattributes 文件中的行结束符配置规则 ```
539 lines
23 KiB
C#
539 lines
23 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
using UnityTcp.Editor.Helpers;
|
|
|
|
namespace UnityTcp.Editor.Native
|
|
{
|
|
[InitializeOnLoad]
|
|
internal static class NativeDllLoader
|
|
{
|
|
|
|
#if UNITY_EDITOR_WIN || UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
|
|
|
|
// ---- Platform P/Invoke ----------------------------------------- //
|
|
|
|
#if UNITY_EDITOR_WIN
|
|
private const uint GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x00000002;
|
|
|
|
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
private static extern IntPtr LoadLibraryW(string lpLibFileName);
|
|
|
|
[DllImport("kernel32", SetLastError = true)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
private static extern bool FreeLibrary(IntPtr hModule);
|
|
|
|
[DllImport("kernel32", SetLastError = true)]
|
|
private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
|
|
|
|
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
private static extern bool GetModuleHandleExW(uint dwFlags, string lpModuleName, out IntPtr phModule);
|
|
|
|
private static string Win32Error() =>
|
|
new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()).Message;
|
|
|
|
private static IntPtr PlatformLoad(string path)
|
|
{
|
|
IntPtr h = LoadLibraryW(path);
|
|
if (h == IntPtr.Zero)
|
|
CodelyLogger.LogWarning($"NativeDllLoader: LoadLibraryW failed: {Win32Error()}");
|
|
return h;
|
|
}
|
|
private static void PlatformFree(IntPtr handle) { if (handle != IntPtr.Zero) FreeLibrary(handle); }
|
|
private static IntPtr PlatformGetProc(IntPtr handle, string symbol)
|
|
{
|
|
IntPtr ptr = GetProcAddress(handle, symbol);
|
|
if (ptr == IntPtr.Zero)
|
|
CodelyLogger.LogWarning($"NativeDllLoader: GetProcAddress('{symbol}') failed: {Win32Error()}");
|
|
return ptr;
|
|
}
|
|
// Path-based probe: returns true iff the module at `path` is still loaded with the
|
|
// given `expected` handle. UNCHANGED_REFCOUNT avoids bumping the refcount, so this
|
|
// is purely a query — never call FreeLibrary on the result.
|
|
private static bool PlatformIsHandleStillLoaded(string path, IntPtr expected)
|
|
{
|
|
if (expected == IntPtr.Zero) return false;
|
|
return GetModuleHandleExW(
|
|
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
|
path,
|
|
out IntPtr current)
|
|
&& current == expected;
|
|
}
|
|
|
|
#elif UNITY_EDITOR_OSX
|
|
private const int RTLD_NOW = 0x2;
|
|
private const int RTLD_NOLOAD = 0x10; // dyld value (differs from glibc)
|
|
|
|
[DllImport("libdl")]
|
|
private static extern IntPtr dlopen(string filename, int flags);
|
|
|
|
[DllImport("libdl")]
|
|
private static extern int dlclose(IntPtr handle);
|
|
|
|
[DllImport("libdl")]
|
|
private static extern IntPtr dlsym(IntPtr handle, string symbol);
|
|
|
|
[DllImport("libdl")]
|
|
private static extern IntPtr dlerror();
|
|
|
|
private static string DlError() { IntPtr p = dlerror(); return p != IntPtr.Zero ? Marshal.PtrToStringAnsi(p) : "unknown error"; }
|
|
|
|
private static IntPtr PlatformLoad(string path)
|
|
{
|
|
dlerror();
|
|
IntPtr h = dlopen(path, RTLD_NOW);
|
|
if (h == IntPtr.Zero)
|
|
CodelyLogger.LogWarning($"NativeDllLoader: dlopen failed: {DlError()}");
|
|
return h;
|
|
}
|
|
private static void PlatformFree(IntPtr handle) { if (handle != IntPtr.Zero) dlclose(handle); }
|
|
private static IntPtr PlatformGetProc(IntPtr handle, string symbol)
|
|
{
|
|
dlerror();
|
|
IntPtr ptr = dlsym(handle, symbol);
|
|
if (ptr == IntPtr.Zero)
|
|
CodelyLogger.LogWarning($"NativeDllLoader: dlsym('{symbol}') failed: {DlError()}");
|
|
return ptr;
|
|
}
|
|
// Path-based probe via dlopen(RTLD_NOLOAD): returns the handle iff the image is
|
|
// already mapped at `path`, NULL otherwise. NOLOAD still bumps the refcount on
|
|
// success, so we balance with dlclose immediately. Never call PlatformFree on
|
|
// anything other than handles we explicitly loaded.
|
|
private static bool PlatformIsHandleStillLoaded(string path, IntPtr expected)
|
|
{
|
|
if (expected == IntPtr.Zero) return false;
|
|
dlerror();
|
|
IntPtr probe = dlopen(path, RTLD_NOW | RTLD_NOLOAD);
|
|
if (probe == IntPtr.Zero) return false;
|
|
bool matches = probe == expected;
|
|
dlclose(probe);
|
|
return matches;
|
|
}
|
|
|
|
#else // UNITY_EDITOR_LINUX
|
|
private const int RTLD_NOW = 0x2;
|
|
private const int RTLD_NOLOAD = 0x4; // glibc value (differs from dyld)
|
|
|
|
[DllImport("libdl.so.2")]
|
|
private static extern IntPtr dlopen(string filename, int flags);
|
|
|
|
[DllImport("libdl.so.2")]
|
|
private static extern int dlclose(IntPtr handle);
|
|
|
|
[DllImport("libdl.so.2")]
|
|
private static extern IntPtr dlsym(IntPtr handle, string symbol);
|
|
|
|
[DllImport("libdl.so.2")]
|
|
private static extern IntPtr dlerror();
|
|
|
|
private static string DlError() { IntPtr p = dlerror(); return p != IntPtr.Zero ? Marshal.PtrToStringAnsi(p) : "unknown error"; }
|
|
|
|
private static IntPtr PlatformLoad(string path)
|
|
{
|
|
dlerror();
|
|
IntPtr h = dlopen(path, RTLD_NOW);
|
|
if (h == IntPtr.Zero)
|
|
CodelyLogger.LogWarning($"NativeDllLoader: dlopen failed: {DlError()}");
|
|
return h;
|
|
}
|
|
private static void PlatformFree(IntPtr handle) { if (handle != IntPtr.Zero) dlclose(handle); }
|
|
private static IntPtr PlatformGetProc(IntPtr handle, string symbol)
|
|
{
|
|
dlerror();
|
|
IntPtr ptr = dlsym(handle, symbol);
|
|
if (ptr == IntPtr.Zero)
|
|
CodelyLogger.LogWarning($"NativeDllLoader: dlsym('{symbol}') failed: {DlError()}");
|
|
return ptr;
|
|
}
|
|
// Path-based probe via dlopen(RTLD_NOLOAD): returns the handle iff the image is
|
|
// already mapped at `path`, NULL otherwise. NOLOAD still bumps the refcount on
|
|
// success, so we balance with dlclose immediately. Never call PlatformFree on
|
|
// anything other than handles we explicitly loaded.
|
|
private static bool PlatformIsHandleStillLoaded(string path, IntPtr expected)
|
|
{
|
|
if (expected == IntPtr.Zero) return false;
|
|
dlerror();
|
|
IntPtr probe = dlopen(path, RTLD_NOW | RTLD_NOLOAD);
|
|
if (probe == IntPtr.Zero) return false;
|
|
bool matches = probe == expected;
|
|
dlclose(probe);
|
|
return matches;
|
|
}
|
|
#endif
|
|
|
|
// ---- Event handlers ---------------------------------------------- //
|
|
|
|
static NativeDllLoader()
|
|
{
|
|
if (EditorAutomationGuard.ShouldSkipBridgeAutoStart()) return;
|
|
|
|
RegisterCleanupHandlers();
|
|
}
|
|
|
|
private static void RegisterCleanupHandlers()
|
|
{
|
|
#if UNITY_2020_1_OR_NEWER
|
|
UnityEditor.PackageManager.Events.registeringPackages -= OnPackagesRegistering;
|
|
UnityEditor.PackageManager.Events.registeringPackages += OnPackagesRegistering;
|
|
#endif
|
|
EditorApplication.quitting -= OnEditorQuitting;
|
|
EditorApplication.quitting += OnEditorQuitting;
|
|
AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload;
|
|
AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload;
|
|
}
|
|
private static void OnEditorQuitting() => Unload("unity_quit");
|
|
|
|
private static void OnBeforeAssemblyReload()
|
|
{
|
|
UnregisterLogCallback();
|
|
}
|
|
|
|
#if UNITY_2020_1_OR_NEWER
|
|
private static void OnPackagesRegistering(UnityEditor.PackageManager.PackageRegistrationEventArgs args)
|
|
{
|
|
// changedFrom / changedTo are paired. Restart only when this
|
|
// package's native plugin bytes actually change. Removal is
|
|
// args.removed only.
|
|
UnityEditor.PackageManager.PackageInfo current;
|
|
try { current = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(NativeDllLoader).Assembly); }
|
|
catch { return; }
|
|
string ourName = current?.name;
|
|
if (string.IsNullOrEmpty(ourName)) return;
|
|
|
|
int changeCount = Math.Min(args.changedFrom.Count, args.changedTo.Count);
|
|
for (int i = 0; i < changeCount; i++)
|
|
{
|
|
var from = args.changedFrom[i];
|
|
var to = args.changedTo[i];
|
|
if (from.name != ourName || to.name != ourName) continue;
|
|
|
|
if (BridgeUpdateRestartPrompt.IsNativeDllContentChanged(
|
|
PluginPathUnder(from.resolvedPath),
|
|
PluginPathUnder(to.resolvedPath)))
|
|
{
|
|
BridgeUpdateRestartPrompt.MarkUpdated();
|
|
Unload("package_updating");
|
|
}
|
|
return;
|
|
}
|
|
|
|
bool added = false;
|
|
foreach (var package in args.added)
|
|
{
|
|
if (package.name != ourName) continue;
|
|
added = true;
|
|
break;
|
|
}
|
|
if (added) return;
|
|
foreach (var package in args.removed)
|
|
{
|
|
if (package.name != ourName) continue;
|
|
Unload("package_removed");
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// ---- State ----------------------------------------------------- //
|
|
|
|
private const string HandleKey = "NativeDllLoader._handle";
|
|
|
|
private static IntPtr _handle = IntPtr.Zero;
|
|
|
|
internal static bool IsLoaded => _handle != IntPtr.Zero;
|
|
|
|
/// <summary>
|
|
/// Fired just before the native library is unloaded.
|
|
/// Subscribers (e.g. UnityTcpBridge) should call their own Stop() here
|
|
/// so higher-level teardown happens before the handle is freed.
|
|
/// The argument is why the unload is happening (e.g. "unity_quit",
|
|
/// "package_updating"), destined for the heartbeat file's "reason"
|
|
/// field; null means no specific reason (generic "stopped").
|
|
/// </summary>
|
|
internal static event Action<string> OnUnload;
|
|
|
|
// ---- Log callback ---------------------------------------------- //
|
|
|
|
// Native setter — resolved from "Log_SetCallback" and held here so callers can reference NativeDllLoader.SetLogCallback directly.
|
|
internal delegate void SetLogCallbackDelegate(IntPtr callback);
|
|
internal static SetLogCallbackDelegate SetLogCallback;
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate void NativeLogCallbackDelegate(int level, [MarshalAs(UnmanagedType.LPStr)] string message);
|
|
|
|
// Kept as a field to prevent GC collection while native holds the pointer.
|
|
private static NativeLogCallbackDelegate _logCallback;
|
|
|
|
private static void RegisterLogCallback()
|
|
{
|
|
if (!IsLoaded) return;
|
|
// Explicit starts need cleanup too, even when bridge auto-start is disabled.
|
|
RegisterCleanupHandlers();
|
|
try
|
|
{
|
|
_logCallback = (level, message) =>
|
|
{
|
|
switch (level)
|
|
{
|
|
case 0: CodelyLogger.Log($"[NativeTcpBridge] {message}"); break;
|
|
case 1: CodelyLogger.LogWarning($"[NativeTcpBridge] {message}"); break;
|
|
default: CodelyLogger.LogError($"[NativeTcpBridge] {message}"); break;
|
|
}
|
|
};
|
|
IntPtr ptr = Marshal.GetFunctionPointerForDelegate(_logCallback);
|
|
SetLogCallback?.Invoke(ptr);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CodelyLogger.LogWarning($"NativeDllLoader: RegisterLogCallback failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private static void UnregisterLogCallback()
|
|
{
|
|
if (!IsLoaded) return;
|
|
try { SetLogCallback?.Invoke(IntPtr.Zero); } catch { }
|
|
_logCallback = null;
|
|
}
|
|
|
|
// ---- Public API ------------------------------------------------ //
|
|
|
|
internal static bool Load()
|
|
{
|
|
if (_handle != IntPtr.Zero) return true;
|
|
|
|
string dllPath = FindDllPath();
|
|
if (string.IsNullOrEmpty(dllPath)) return false;
|
|
|
|
// On domain reload the DLL stays loaded in the process. Recover the handle
|
|
// from SessionState so we can re-resolve delegates without reloading the binary.
|
|
// Validate via a path-based probe first — a package update or other unload may
|
|
// have invalidated the handle, and calling FreeLibrary / dlclose on a freed
|
|
// handle is undefined behavior.
|
|
string stored = SessionState.GetString(HandleKey, "0");
|
|
if (long.TryParse(stored, out long handleValue) && handleValue != 0)
|
|
{
|
|
var storedHandle = new IntPtr(handleValue);
|
|
if (PlatformIsHandleStillLoaded(dllPath, storedHandle))
|
|
{
|
|
_handle = storedHandle;
|
|
if (ResolveAll())
|
|
{
|
|
CodelyLogger.Log("NativeDllLoader: resolved all symbols with stored handle");
|
|
RegisterLogCallback();
|
|
BridgeUpdateRestartPrompt.RememberNativeDllFromFile(dllPath);
|
|
return true;
|
|
}
|
|
|
|
// Handle is valid but symbols are missing (rare ABI mismatch). The handle
|
|
// really refers to our module, so freeing it here is safe.
|
|
PlatformFree(_handle);
|
|
_handle = IntPtr.Zero;
|
|
}
|
|
else
|
|
{
|
|
CodelyLogger.Log("NativeDllLoader: stored native handle is no longer loaded; reloading from disk");
|
|
}
|
|
SessionState.EraseString(HandleKey);
|
|
}
|
|
|
|
bool isLoaded = LoadFrom(dllPath);
|
|
if(isLoaded) {
|
|
RegisterLogCallback();
|
|
BridgeUpdateRestartPrompt.RememberNativeDllFromFile(dllPath);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
internal static bool Reload()
|
|
{
|
|
Unload();
|
|
return Load();
|
|
}
|
|
|
|
internal static void Unload(string reason = null)
|
|
{
|
|
if (_handle == IntPtr.Zero) return;
|
|
OnUnload?.Invoke(reason);
|
|
// Stop/join native workers even when no managed bridge subscribed to OnUnload.
|
|
NativeUnityTcpBridgeHost.StopServer(reason);
|
|
UnregisterLogCallback();
|
|
PlatformFree(_handle);
|
|
_handle = IntPtr.Zero;
|
|
NativeUnityTcpBridgeAPI.NTB_GetAbiVersion = null;
|
|
NativeUnityTcpBridgeAPI.NTB_StartBridge = null;
|
|
NativeUnityTcpBridgeAPI.NTB_Stop = null;
|
|
NativeUnityTcpBridgeAPI.NTB_StopWithReason = null;
|
|
NativeUnityTcpBridgeAPI.NTB_IsRunning = null;
|
|
NativeUnityTcpBridgeAPI.NTB_GetBoundPort = null;
|
|
NativeUnityTcpBridgeAPI.NTB_GetConnectionCount = null;
|
|
NativeUnityTcpBridgeAPI.NTB_TryDequeueCommand = null;
|
|
NativeUnityTcpBridgeAPI.NTB_TryBeginCommand = null;
|
|
NativeUnityTcpBridgeAPI.NTB_EnqueueResponse = null;
|
|
NativeUnityTcpBridgeAPI.NTB_GetClientsJson = null;
|
|
NativeUnityTcpBridgeAPI.NTB_PauseManagedCommandProcessing = null;
|
|
NativeUnityTcpBridgeAPI.NTB_ResumeManagedCommandProcessing = null;
|
|
NativeUnityTcpBridgeAPI.NTB_NotifyAll = null;
|
|
NativeUnityTcpBridgeAPI.NTB_SetStreamPort = null;
|
|
NativeUnityTcpBridgeAPI.NTB_SetEditorBusyProxyEnabled = null;
|
|
SetLogCallback = null;
|
|
SessionState.EraseString(HandleKey);
|
|
}
|
|
|
|
// ---- Private helpers ------------------------------------------- //
|
|
|
|
private static bool LoadFrom(string dllPath)
|
|
{
|
|
_handle = PlatformLoad(dllPath);
|
|
if (_handle == IntPtr.Zero)
|
|
{
|
|
CodelyLogger.LogWarning($"NativeDllLoader: failed to load '{dllPath}'.");
|
|
return false;
|
|
}
|
|
if (!ResolveAll())
|
|
{
|
|
PlatformFree(_handle);
|
|
_handle = IntPtr.Zero;
|
|
return false;
|
|
}
|
|
SessionState.SetString(HandleKey, _handle.ToInt64().ToString());
|
|
CodelyLogger.Log("NativeDllLoader: loaded DLL and resolved all symbols");
|
|
return true;
|
|
}
|
|
|
|
// The ABI handshake must come first: it is the only symbol whose
|
|
// signature is guaranteed to match across versions, so it has to be
|
|
// checked before any other symbol is *called*. (Resolving the rest is
|
|
// harmless on its own — GetDelegateForFunctionPointer does not call
|
|
// into native — but a mismatched call would read arguments that were
|
|
// never passed.) Failing here is the designed outcome on a version
|
|
// skew, not an error path worth recovering from.
|
|
private static bool ResolveAll()
|
|
{
|
|
if (!CheckAbiVersion()) return false;
|
|
|
|
bool resolved =
|
|
Resolve("NTB_StartBridge", ref NativeUnityTcpBridgeAPI.NTB_StartBridge) &&
|
|
Resolve("NTB_StopWithReason", ref NativeUnityTcpBridgeAPI.NTB_StopWithReason) &&
|
|
Resolve("NTB_Stop", ref NativeUnityTcpBridgeAPI.NTB_Stop) &&
|
|
Resolve("NTB_IsRunning", ref NativeUnityTcpBridgeAPI.NTB_IsRunning) &&
|
|
Resolve("NTB_GetBoundPort", ref NativeUnityTcpBridgeAPI.NTB_GetBoundPort) &&
|
|
Resolve("NTB_GetConnectionCount", ref NativeUnityTcpBridgeAPI.NTB_GetConnectionCount) &&
|
|
Resolve("NTB_TryDequeueCommand", ref NativeUnityTcpBridgeAPI.NTB_TryDequeueCommand) &&
|
|
Resolve("NTB_TryBeginCommand", ref NativeUnityTcpBridgeAPI.NTB_TryBeginCommand) &&
|
|
Resolve("NTB_EnqueueResponse", ref NativeUnityTcpBridgeAPI.NTB_EnqueueResponse) &&
|
|
Resolve("NTB_GetClientsJson", ref NativeUnityTcpBridgeAPI.NTB_GetClientsJson) &&
|
|
Resolve("NTB_PauseManagedCommandProcessing", ref NativeUnityTcpBridgeAPI.NTB_PauseManagedCommandProcessing) &&
|
|
Resolve("NTB_ResumeManagedCommandProcessing", ref NativeUnityTcpBridgeAPI.NTB_ResumeManagedCommandProcessing) &&
|
|
Resolve("NTB_NotifyAll", ref NativeUnityTcpBridgeAPI.NTB_NotifyAll) &&
|
|
Resolve("NTB_SetStreamPort", ref NativeUnityTcpBridgeAPI.NTB_SetStreamPort) &&
|
|
Resolve("Log_SetCallback", ref SetLogCallback);
|
|
if (!resolved) return false;
|
|
|
|
#if UNITY_EDITOR_WIN
|
|
// The busy-Editor proxy is optional; failure to bind it must not disable commands.
|
|
ResolveOptional("NTB_SetEditorBusyProxyEnabled", ref NativeUnityTcpBridgeAPI.NTB_SetEditorBusyProxyEnabled);
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
private static bool CheckAbiVersion()
|
|
{
|
|
if (!Resolve("NTB_GetAbiVersion", ref NativeUnityTcpBridgeAPI.NTB_GetAbiVersion))
|
|
{
|
|
CodelyLogger.LogWarning(
|
|
"NativeDllLoader: native plugin predates ABI versioning (no NTB_GetAbiVersion); refusing to bind.");
|
|
return false;
|
|
}
|
|
|
|
int nativeAbi;
|
|
try
|
|
{
|
|
nativeAbi = NativeUnityTcpBridgeAPI.NTB_GetAbiVersion();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CodelyLogger.LogWarning($"NativeDllLoader: ABI version probe failed: {ex.Message}");
|
|
return false;
|
|
}
|
|
|
|
if (nativeAbi != NativeUnityTcpBridgeAPI.ExpectedAbiVersion)
|
|
{
|
|
CodelyLogger.LogWarning(
|
|
$"NativeDllLoader: native ABI {nativeAbi} does not match the expected {NativeUnityTcpBridgeAPI.ExpectedAbiVersion}; " +
|
|
"refusing to bind. If this appears during startup right after a package update, it is expected and harmless: " +
|
|
"Unity is running a previously compiled assembly against the updated plugin, and the bridge will start normally " +
|
|
"once script compilation finishes and the domain reloads.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool Resolve<T>(string symbol, ref T del) where T : Delegate
|
|
{
|
|
IntPtr ptr = PlatformGetProc(_handle, symbol);
|
|
if (ptr == IntPtr.Zero) return false;
|
|
del = Marshal.GetDelegateForFunctionPointer<T>(ptr);
|
|
return true;
|
|
}
|
|
|
|
#if UNITY_EDITOR_WIN
|
|
private static void ResolveOptional<T>(string symbol, ref T del) where T : Delegate
|
|
{
|
|
IntPtr ptr = GetProcAddress(_handle, symbol);
|
|
del = ptr == IntPtr.Zero ? null : Marshal.GetDelegateForFunctionPointer<T>(ptr);
|
|
}
|
|
#endif
|
|
|
|
private static string PluginPathUnder(string packageRoot)
|
|
{
|
|
if (string.IsNullOrEmpty(packageRoot)) return null;
|
|
#if UNITY_EDITOR_WIN
|
|
string relative = Path.Combine("Plugins", "Win", "NativeTcpBridge.dll");
|
|
#elif UNITY_EDITOR_OSX
|
|
string relative = Path.Combine("Plugins", "macOS", "libNativeTcpBridge.dylib");
|
|
#else
|
|
string relative = Path.Combine("Plugins", "Linux", "x86_64", "libNativeTcpBridge.so");
|
|
#endif
|
|
return Path.GetFullPath(Path.Combine(packageRoot, relative));
|
|
}
|
|
|
|
private static string FindDllPath()
|
|
{
|
|
try
|
|
{
|
|
var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(NativeDllLoader).Assembly);
|
|
if (packageInfo == null)
|
|
{
|
|
CodelyLogger.LogWarning("NativeDllLoader: could not locate package via PackageInfo.");
|
|
return null;
|
|
}
|
|
|
|
string fullPath = PluginPathUnder(packageInfo.resolvedPath);
|
|
if (!string.IsNullOrEmpty(fullPath) && File.Exists(fullPath)) return fullPath;
|
|
|
|
CodelyLogger.LogWarning($"NativeDllLoader: DLL not found at '{fullPath}'.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CodelyLogger.LogWarning($"NativeDllLoader: path lookup failed. {ex.Message}");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
#else
|
|
// ---- Unsupported platform stubs -------------------------------- //
|
|
internal static bool IsLoaded => false;
|
|
internal static bool Load() => false;
|
|
internal static bool Reload() => false;
|
|
internal static void Unload(string reason = null) { }
|
|
#endif
|
|
}
|
|
}
|