ohmu
·
2026-10-15
1#include "fastfetch.h"
2#include "common/library.h"
3#include "common/debug.h"
4
5#if _WIN32
6 #include "common/windows/nt.h"
7 #include <errno.h>
8 #include <ntstatus.h>
9#endif
10
11#ifndef FF_DISABLE_DLOPEN
12
13 #include <stdarg.h>
14
15 // Clang doesn't define __SANITIZE_ADDRESS__ but defines __has_feature(address_sanitizer)
16 #if !defined(__SANITIZE_ADDRESS__) && defined(__has_feature)
17 #if __has_feature(address_sanitizer)
18 #define __SANITIZE_ADDRESS__
19 #endif
20 #endif
21
22 #ifndef FF_DLOPEN_FLAGS
23 #ifdef __SANITIZE_ADDRESS__
24 #define FF_DLOPEN_FLAGS RTLD_LAZY | RTLD_NODELETE
25 #else
26 #define FF_DLOPEN_FLAGS RTLD_LAZY
27 #endif
28 #endif
29
30void* ffLibraryLoadSingle(const char* path, int maxVersion) {
31 void* result = dlopen(path, FF_DLOPEN_FLAGS);
32
33 #if _WIN32
34
35 // libX.dll.1 never exists on Windows, while libX-1.dll may exist
36 FF_UNUSED(maxVersion)
37
38 if (result != nullptr) {
39 return result;
40 }
41
42 uint32_t pathLen = ffStrbufLastIndexC(&instance.state.platform.exePath, '/');
43 if (pathLen == instance.state.platform.exePath.length) {
44 return result;
45 }
46
47 char absPath[MAX_PATH * 2];
48 strcpy(mempcpy(absPath, instance.state.platform.exePath.chars, pathLen + 1), path);
49 return dlopen(absPath, FF_DLOPEN_FLAGS);
50
51 #else
52
53 if (result == nullptr) {
54 FF_DEBUG("dlopen(\"%s\"): %s", path, dlerror());
55 }
56
57 if (result != nullptr || maxVersion < 0) {
58 return result;
59 }
60
61 FF_STRBUF_AUTO_DESTROY pathbuf = ffStrbufCreateA(64);
62 ffStrbufAppendS(&pathbuf, path);
63 ffStrbufAppendC(&pathbuf, '.');
64
65 for (int i = maxVersion; i >= 0; --i) {
66 uint32_t originalLength = pathbuf.length;
67 ffStrbufAppendSInt(&pathbuf, i);
68
69 result = dlopen(pathbuf.chars, FF_DLOPEN_FLAGS);
70 if (result != nullptr) {
71 break;
72 } else {
73 FF_DEBUG("dlopen(\"%s\"): %s", pathbuf.chars, dlerror());
74 }
75
76 ffStrbufSubstrBefore(&pathbuf, originalLength);
77 }
78
79 #endif
80
81 return result;
82}
83
84void* ffLibraryLoadMulti(const char* path, int maxVersion, ...) {
85 void* result = ffLibraryLoadSingle(path, maxVersion);
86
87 if (!result) {
88 va_list defaultNames;
89 va_start(defaultNames, maxVersion);
90
91 do {
92 const char* pathRest = va_arg(defaultNames, const char*);
93 if (pathRest == nullptr) {
94 break;
95 }
96
97 int maxVersionRest = va_arg(defaultNames, int);
98 result = ffLibraryLoadSingle(pathRest, maxVersionRest);
99 } while (!result);
100
101 va_end(defaultNames);
102 }
103
104 return result;
105}
106
107#endif
108
109#if _WIN32
110
111void* dlopen(const char* path, [[maybe_unused]] int mode) {
112 wchar_t pathW[MAX_PATH + 1];
113 ULONG pathWBytes = 0;
114
115 NTSTATUS status = RtlUTF8ToUnicodeN(pathW, sizeof(pathW), &pathWBytes, path, (uint32_t) strlen(path) + 1);
116 if (!NT_SUCCESS(status)) {
117 FF_DEBUG("RtlUTF8ToUnicodeN failed for path %s with status 0x%08lX: %s", path, status, ffDebugNtStatus(status));
118 return nullptr;
119 }
120
121 PVOID module = nullptr;
122 status = LdrLoadDll(nullptr, nullptr, &(UNICODE_STRING) {
123 .Length = (USHORT) (pathWBytes - sizeof(wchar_t)), // Exclude null terminator
124 .MaximumLength = (USHORT) pathWBytes,
125 .Buffer = pathW,
126 },
127 &module);
128
129 if (!NT_SUCCESS(status)) {
130 FF_DEBUG("LdrLoadDll failed for path %s with status 0x%08lX: %s", path, status, ffDebugNtStatus(status));
131 return nullptr;
132 }
133
134 return module;
135}
136
137int dlclose(void* handle) {
138 NTSTATUS status = LdrUnloadDll(handle);
139 if (!NT_SUCCESS(status)) {
140 FF_DEBUG("LdrUnloadDll failed for handle %p with status 0x%08lX: %s", handle, status, ffDebugNtStatus(status));
141 return -1;
142 }
143 return 0;
144}
145
146void* dlsym(void* handle, const char* symbol) {
147 void* address;
148 USHORT symbolBytes = (USHORT) (strlen(symbol) + 1);
149 NTSTATUS status = LdrGetProcedureAddress(handle, &(ANSI_STRING) {
150 .Length = symbolBytes - sizeof(char),
151 .MaximumLength = symbolBytes,
152 .Buffer = (char*) symbol,
153 },
154 0,
155 &address);
156 if (!NT_SUCCESS(status)) {
157 FF_DEBUG("LdrGetProcedureAddress failed for symbol %s with status 0x%08lX: %s", symbol, status, ffDebugNtStatus(status));
158 return nullptr;
159 }
160 return address;
161}
162
163void* ffLibraryGetModule(const wchar_t* libraryFileName) {
164 // `libraryFileName` is non-null per the `nonnull(1)` contract.
165 // Prefer `ffGetPeb()->ImageBaseAddress` over looking the main module up by name.
166 void* module = nullptr;
167 USHORT libraryFileNameBytes = (USHORT) (wcslen(libraryFileName) * sizeof(wchar_t) + sizeof(wchar_t));
168 NTSTATUS status = LdrGetDllHandle(nullptr, nullptr, &(UNICODE_STRING) {
169 .Length = libraryFileNameBytes - sizeof(wchar_t),
170 .MaximumLength = libraryFileNameBytes,
171 .Buffer = (wchar_t*) libraryFileName,
172 },
173 &module);
174 if (!NT_SUCCESS(status)) {
175 FF_DEBUG("LdrGetDllHandle failed for library %ls with status 0x%08lX: %s", libraryFileName, status, ffDebugNtStatus(status));
176 return nullptr;
177 }
178 return module;
179}
180#endif
181
182struct LibraryIterateDynamicLibsBundle {
183 FFLibraryIterateCallback callback;
184 void* userData;
185};
186
187#if _WIN32
188
189static void ffLibraryIterateDynamicLibsCallback(PLDR_DATA_TABLE_ENTRY DataTableEntry, PVOID Context, BOOLEAN* StopEnumeration) {
190 if (DataTableEntry->FullDllName.Buffer == nullptr || DataTableEntry->FullDllName.Buffer[0] == L'\0') {
191 return;
192 }
193
194 char path[PATH_MAX * 3];
195 ULONG outBytes;
196 if (NT_SUCCESS(RtlUnicodeToUTF8N(path, sizeof(path), &outBytes, DataTableEntry->FullDllName.Buffer, DataTableEntry->FullDllName.Length))) {
197 path[outBytes] = '\0';
198 struct LibraryIterateDynamicLibsBundle* bundle = (struct LibraryIterateDynamicLibsBundle*) Context;
199 *StopEnumeration = !bundle->callback(path, bundle->userData);
200 }
201}
202
203bool ffLibraryIterateDynamicLibs(FFLibraryIterateCallback callback, void* userData) {
204 struct LibraryIterateDynamicLibsBundle bundle = {
205 .callback = callback,
206 .userData = userData,
207 };
208 return NT_SUCCESS(LdrEnumerateLoadedModules(FALSE, ffLibraryIterateDynamicLibsCallback, &bundle));
209}
210
211#elif defined(__APPLE__)
212
213#include <mach-o/dyld.h>
214
215bool ffLibraryIterateDynamicLibs(FFLibraryIterateCallback callback, void* userData) {
216 uint32_t imageCount = _dyld_image_count();
217 for (uint32_t i = 0; i < imageCount; ++i) {
218 const char* name = _dyld_get_image_name(i);
219 if (name == nullptr || name[0] == '\0') {
220 continue;
221 }
222
223 if (!callback(name, userData)) {
224 break;
225 }
226 }
227
228 return true;
229}
230
231#elif __has_include(<link.h>)
232 #include <link.h>
233
234static int ffLibraryIterateDynamicLibsCallback(struct dl_phdr_info* info, size_t, void* data) {
235 if (info->dlpi_name == nullptr || info->dlpi_name[0] == '\0') {
236 return 0;
237 }
238
239 struct LibraryIterateDynamicLibsBundle* bundle = (struct LibraryIterateDynamicLibsBundle*) data;
240 return !bundle->callback(info->dlpi_name, bundle->userData);
241}
242
243bool ffLibraryIterateDynamicLibs(bool (*callback)(const char* name, void* userData), void* userData) {
244 dl_iterate_phdr(
245 ffLibraryIterateDynamicLibsCallback,
246 &(struct LibraryIterateDynamicLibsBundle) {
247 .callback = callback,
248 .userData = userData,
249 });
250 return true;
251}
252
253#else
254
255bool ffLibraryIterateDynamicLibs(FFLibraryIterateCallback, void*) {
256 // Not implemented for this platform
257 return false;
258}
259
260#endif