1 //
2 // Copyright (c) 2025 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using System.Collections;
9 using System.IO;
10 using System.Linq;
11 using System.Reflection;
12 using System.Runtime.InteropServices;
13 using System.Xml.Linq;
14 
15 namespace Antmicro.Renode.UI
16 {
17     public static class DllMap
18     {
19         // Register a call-back for native library resolution.
Register(Assembly assembly)20         public static void Register(Assembly assembly)
21         {
22             NativeLibrary.SetDllImportResolver(assembly, MapAndLoad);
23         }
24 
25         // The callback which loads the mapped libray in place of the original.
MapAndLoad(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath)26         private static IntPtr MapAndLoad(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath)
27         {
28             var wasMapped = MapLibraryName(assembly.Location, libraryName, out var mappedName);
29             if(!wasMapped)
30             {
31                 mappedName = libraryName;
32             }
33             // First try loading the library normally, then retry falling back to libraries from homebrew, which
34             // are installed to /opt/homebrew/lib on macOS/ARM64, which is not on the dyld search path.
35             if(NativeLibrary.TryLoad(mappedName, assembly, dllImportSearchPath, out var handle))
36             {
37                 return handle;
38             }
39             return NativeLibrary.Load(Path.Combine("/opt/homebrew/lib", mappedName), assembly, dllImportSearchPath);
40         }
41 
42         // Parse the dll.config file and map the old name to the new name of a library.
MapLibraryName(string assemblyLocation, string originalLibName, out string mappedLibName)43         private static bool MapLibraryName(string assemblyLocation, string originalLibName, out string mappedLibName)
44         {
45             string xmlPath = Path.Combine(Path.GetDirectoryName(assemblyLocation), Path.GetFileNameWithoutExtension(assemblyLocation) + ".dll.config");
46             mappedLibName = null;
47 
48             if (!File.Exists(xmlPath))
49             {
50                 return false;
51             }
52 
53             var currOsAttr = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "osx" :
54                              RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "windows" :
55                              RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "linux" : null;
56 
57             XElement root = XElement.Load(xmlPath);
58             var map = (
59                 from el in root.Elements("dllmap")
60                 where (string)el.Attribute("dll") == originalLibName
61                 let osAttr = (string)el.Attribute("os")
62                 where (osAttr == null || osAttr == currOsAttr || (osAttr.StartsWith("!") && !osAttr.Contains(currOsAttr)))
63                 select el
64             ).SingleOrDefault();
65 
66             if (map != null)
67             {
68                 mappedLibName = map.Attribute("target").Value;
69             }
70 
71             return (mappedLibName != null);
72         }
73     }
74 }