master ohmu/fastfetch-openbsd / src / common / windows / registry.c
ohmu  ·  2026-10-15
  1#include "registry.h"
  2#include "unicode.h"
  3#include "common/mallocHelper.h"
  4#include "common/debug.h"
  5#include "common/windows/nt.h"
  6
  7#include <stdalign.h>
  8#include <ntstatus.h>
  9
 10static HANDLE hRootKeys[8 /*(uintptr_t) HKEY_CURRENT_USER_LOCAL_SETTINGS - (uintptr_t) HKEY_CLASSES_ROOT + 1*/];
 11
 12static const char* hKey2Str(HANDLE hRootKey) {
 13#define HKEY_CASE(compareKey) \
 14    if (hRootKey == hRootKeys[(uintptr_t) compareKey - (uintptr_t) HKEY_CLASSES_ROOT]) return #compareKey;
 15    HKEY_CASE(HKEY_CLASSES_ROOT)
 16    HKEY_CASE(HKEY_CURRENT_USER)
 17    HKEY_CASE(HKEY_LOCAL_MACHINE)
 18    HKEY_CASE(HKEY_USERS)
 19    HKEY_CASE(HKEY_PERFORMANCE_DATA)
 20    HKEY_CASE(HKEY_CURRENT_CONFIG)
 21    HKEY_CASE(HKEY_DYN_DATA)
 22    HKEY_CASE(HKEY_CURRENT_USER_LOCAL_SETTINGS)
 23#undef HKEY_CASE
 24
 25    return "UNKNOWN";
 26}
 27
 28HANDLE ffRegGetRootKeyHandle(HKEY hKey) {
 29    assert(hKey);
 30    assert((uintptr_t) hKey >= (uintptr_t) HKEY_CLASSES_ROOT && (uintptr_t) hKey <= (uintptr_t) HKEY_CURRENT_USER_LOCAL_SETTINGS);
 31
 32    FF_DEBUG("Getting root key handle for HKEY %08llx", (uint64_t) (uintptr_t) hKey);
 33
 34    HANDLE result = hRootKeys[(uintptr_t) hKey - (uintptr_t) HKEY_CLASSES_ROOT];
 35    if (result) {
 36        FF_DEBUG("Found cached root key handle for %s -> %p", hKey2Str(result), result);
 37        return result;
 38    }
 39
 40    switch ((uintptr_t) hKey) {
 41        case (uintptr_t) HKEY_CURRENT_USER: {
 42            NTSTATUS status = RtlOpenCurrentUser(KEY_READ, &result);
 43            if (!NT_SUCCESS(status)) {
 44                FF_DEBUG("RtlOpenCurrentUser() failed: %s", ffDebugNtStatus(status));
 45                return nullptr;
 46            }
 47            break;
 48        }
 49
 50        case (uintptr_t) HKEY_LOCAL_MACHINE: {
 51            UNICODE_STRING path = RTL_CONSTANT_STRING(L"\\Registry\\Machine");
 52            NTSTATUS status = NtOpenKey(&result, KEY_READ, &(OBJECT_ATTRIBUTES) {
 53                                                               .Length = sizeof(OBJECT_ATTRIBUTES),
 54                                                               .RootDirectory = nullptr,
 55                                                               .ObjectName = &path,
 56                                                               .Attributes = OBJ_CASE_INSENSITIVE,
 57                                                           });
 58            if (!NT_SUCCESS(status)) {
 59                FF_DEBUG("NtOpenKey(%ls) failed: %s (0x%08lx)", path.Buffer, ffDebugNtStatus(status), status);
 60                return nullptr;
 61            }
 62            break;
 63        }
 64        default:
 65            // Unsupported
 66            FF_DEBUG("Unsupported root key: %p", hKey);
 67            assert(false);
 68            return nullptr;
 69    }
 70    hRootKeys[(uintptr_t) hKey - (uintptr_t) HKEY_CLASSES_ROOT] = result;
 71    FF_DEBUG("Opened root key %s -> %p", hKey2Str(result), result);
 72    return result;
 73}
 74
 75bool ffRegOpenSubkeyForRead(HANDLE hKey, const wchar_t* subKeyW, HANDLE* result, FFstrbuf* error) {
 76    assert(hKey);
 77    assert(subKeyW);
 78    assert(result);
 79
 80    FF_DEBUG("Opening subkey %s\\%ls for read", hKey2Str(hKey), subKeyW);
 81
 82    USHORT subKeyLen = (USHORT) (wcslen(subKeyW) * sizeof(wchar_t));
 83    if (!NT_SUCCESS(NtOpenKey(result, KEY_READ, &(OBJECT_ATTRIBUTES) {
 84                                                    .Length = sizeof(OBJECT_ATTRIBUTES),
 85                                                    .RootDirectory = hKey,
 86                                                    .ObjectName = &(UNICODE_STRING) {
 87                                                        .Length = subKeyLen,
 88                                                        .MaximumLength = subKeyLen + (USHORT) sizeof(wchar_t),
 89                                                        .Buffer = (wchar_t*) subKeyW,
 90                                                    },
 91                                                }))) {
 92        FF_DEBUG("NtOpenKey(%s\\<subkey>) failed", hKey2Str(hKey));
 93        if (error) {
 94            FF_STRBUF_AUTO_DESTROY subKeyA = ffStrbufCreateWS(subKeyW);
 95            ffStrbufAppendF(error, "NtOpenKey(%s\\%s) failed", hKey2Str(hKey), subKeyA.chars);
 96        }
 97        return false;
 98    }
 99    FF_DEBUG("Opened subkey under %s -> %p", hKey2Str(hKey), *result);
100    return true;
101}
102
103static bool processRegValue(const FFRegValueArg* arg, const ULONG regType, const void* regData, ULONG regDataLen, FFstrbuf* error) {
104    switch (arg->type) {
105        case FF_ARG_TYPE_STRBUF: {
106            if (regType != REG_SZ && regType != REG_EXPAND_SZ) {
107                goto type_mismatch;
108            }
109
110            FFstrbuf* strbuf = (FFstrbuf*) arg->value;
111            uint32_t strLen = regDataLen / sizeof(wchar_t);
112            if (strLen == 0) {
113                ffStrbufClear(strbuf);
114            } else {
115                const wchar_t* ws = (const wchar_t*) regData;
116                if (ws[strLen - 1] == L'\0') {
117                    --strLen;
118                }
119                ffStrbufSetNWS(strbuf, strLen, ws);
120            }
121            break;
122        }
123
124        case FF_ARG_TYPE_UINT:
125        case FF_ARG_TYPE_UINT64:
126        case FF_ARG_TYPE_UINT16:
127        case FF_ARG_TYPE_UINT8:
128        case FF_ARG_TYPE_BOOL: {
129            uint64_t value = 0;
130
131            if (regType == REG_DWORD) {
132                if (regDataLen < sizeof(uint32_t)) {
133                    goto type_mismatch;
134                }
135                value = *(uint32_t*) regData;
136            } else if (regType == REG_QWORD) {
137                if (regDataLen < sizeof(uint64_t)) {
138                    goto type_mismatch;
139                }
140                value = *(uint64_t*) regData;
141            } else {
142                goto type_mismatch;
143            }
144
145            if (arg->type == FF_ARG_TYPE_UINT) {
146                *(uint32_t*) arg->value = (uint32_t) value;
147            } else if (arg->type == FF_ARG_TYPE_UINT64) {
148                *(uint64_t*) arg->value = (uint64_t) value;
149            } else if (arg->type == FF_ARG_TYPE_UINT16) {
150                *(uint16_t*) arg->value = (uint16_t) value;
151            } else if (arg->type == FF_ARG_TYPE_UINT8) {
152                *(uint8_t*) arg->value = (uint8_t) value;
153            } else if (arg->type == FF_ARG_TYPE_BOOL) {
154                *(bool*) arg->value = value != 0;
155            }
156            break;
157        }
158
159        case FF_ARG_TYPE_FLOAT: {
160            if (regDataLen < sizeof(float)) {
161                goto type_mismatch;
162            }
163            *(float*) arg->value = *(float*) regData;
164            break;
165        }
166
167        case FF_ARG_TYPE_DOUBLE: {
168            if (regDataLen < sizeof(double)) {
169                goto type_mismatch;
170            }
171            *(double*) arg->value = *(double*) regData;
172            break;
173        }
174
175        case FF_ARG_TYPE_LIST: {
176            if (regType != REG_MULTI_SZ) {
177                goto type_mismatch;
178            }
179
180            FFlist* list = (FFlist*) arg->value;
181            ffListClear(list);
182
183            for (
184                const wchar_t* ptr = (const wchar_t*) regData;
185                (const uint8_t*) ptr < (const uint8_t*) regData + regDataLen && *ptr;
186                ptr++) {
187                uint32_t strLen = (uint32_t) wcsnlen(ptr, regDataLen / sizeof(wchar_t) - (size_t) (ptr - (const wchar_t*) regData));
188                ffStrbufInitNWS(FF_LIST_ADD(FFstrbuf, *list), strLen, ptr);
189                ptr += strLen;
190            }
191            break;
192        }
193
194        case FF_ARG_TYPE_BUFFER: {
195            FFArgBuffer* buffer = (FFArgBuffer*) arg->value;
196            if (buffer->length == 0) {
197                buffer->data = malloc(regDataLen);
198            } else if (buffer->length < regDataLen) {
199                if (error) {
200                    FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)");
201                    ffStrbufAppendF(error, "ffRegReadValues(%s) buffer too small (%u): expected %u", nameA.chars, (unsigned) buffer->length, (unsigned) regDataLen);
202                }
203                return false;
204            }
205            buffer->length = regDataLen;
206            memcpy(buffer->data, regData, regDataLen);
207            break;
208        }
209
210        case FF_ARG_TYPE_INT: // Use UINT instead
211        case FF_ARG_TYPE_STRING:
212        case FF_ARG_TYPE_NULL:
213        default:
214            if (error) {
215                FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)");
216                ffStrbufAppendF(error, "processRegValue(%s) unsupported FFArgType %u", nameA.chars, (unsigned) arg->type);
217            }
218            return false;
219    }
220
221    return true;
222
223type_mismatch:
224    FF_DEBUG("ffRegReadValues(%ls) type mismatch: regType=%u, argType=%u, dataLen=%u",
225        arg->name ?: L"(default)",
226        (unsigned) regType,
227        (unsigned) arg->type,
228        (unsigned) regDataLen);
229    if (error) {
230        FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)");
231        ffStrbufAppendF(error, "ffRegReadValues(%s) type mismatch: regType=%u, argType=%u, dataLen=%u", nameA.chars, (unsigned) regType, (unsigned) arg->type, (unsigned) regDataLen);
232    }
233    return false;
234}
235
236bool ffRegReadValue(HANDLE hKey, const FFRegValueArg* arg, FFstrbuf* error) {
237    UNICODE_STRING* valueNameU = &(UNICODE_STRING) {
238        .Length = arg->name ? (USHORT) (wcslen(arg->name) * sizeof(wchar_t)) : 0 /*(default)*/,
239        .MaximumLength = 0,
240        .Buffer = (wchar_t*) arg->name,
241    };
242
243    alignas(KEY_VALUE_PARTIAL_INFORMATION) uint8_t staticBuffer[128 + sizeof(KEY_VALUE_PARTIAL_INFORMATION)];
244    FF_AUTO_FREE uint8_t* dynamicBuffer = nullptr;
245
246    KEY_VALUE_PARTIAL_INFORMATION* buffer = (KEY_VALUE_PARTIAL_INFORMATION*) &staticBuffer;
247    DWORD bufSize = sizeof(staticBuffer);
248    if (NT_SUCCESS(NtQueryValueKey(hKey, valueNameU, KeyValuePartialInformation, buffer, bufSize, &bufSize))) {
249        goto process_value;
250    }
251
252    if (bufSize == 0) {
253        FF_DEBUG("NtQueryValueKey(%p, %ls) failed (bufSize=0)", hKey, arg->name ?: L"(default)");
254        if (error) {
255            FF_STRBUF_AUTO_DESTROY valueNameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)");
256            ffStrbufAppendF(error, "NtQueryValueKey(%p, %s) failed", hKey, valueNameA.chars);
257        }
258        return false;
259    }
260
261    dynamicBuffer = (uint8_t*) malloc(bufSize);
262    buffer = (KEY_VALUE_PARTIAL_INFORMATION*) dynamicBuffer;
263
264    if (!NT_SUCCESS(NtQueryValueKey(hKey, valueNameU, KeyValuePartialInformation, buffer, bufSize, &bufSize))) {
265        FF_DEBUG("NtQueryValueKey(%p, %ls, buffer=%u) failed", hKey, arg->name ?: L"(default)", (unsigned) bufSize);
266        if (error) {
267            FF_STRBUF_AUTO_DESTROY valueNameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)");
268            ffStrbufAppendF(error, "NtQueryValueKey(%p, %s, buffer) failed", hKey, valueNameA.chars);
269        }
270        return false;
271    }
272
273process_value:
274    FF_DEBUG("Read value from %p (%ls), type=%u, len=%u", hKey, arg->name ?: L"(default)", (unsigned) buffer->Type, (unsigned) buffer->DataLength);
275    return processRegValue(arg, buffer->Type, buffer->Data, buffer->DataLength, error);
276}
277
278bool ffRegReadValues(HANDLE hKey, uint32_t argc, const FFRegValueArg argv[], FFstrbuf* error) {
279    if (__builtin_expect(argc == 0, false)) {
280        return true;
281    }
282
283    assert(argv);
284
285    FF_AUTO_FREE UNICODE_STRING* names = (UNICODE_STRING*) calloc(argc, sizeof(*names));
286    FF_AUTO_FREE KEY_VALUE_ENTRY* entries = (KEY_VALUE_ENTRY*) calloc(argc, sizeof(*entries));
287
288    for (uint32_t i = 0; i < argc; ++i) {
289        if (__builtin_expect(!argv[i].value, false)) {
290            FF_DEBUG("ffRegReadValues(argv[%u].value) is nullptr", (unsigned) i);
291            if (error) {
292                ffStrbufAppendF(error, "ffRegReadValues(argv[%u].pVar) is nullptr", (unsigned) i);
293            }
294            return false;
295        }
296
297        names[i] = (UNICODE_STRING) {
298            .Length = argv[i].name ? (USHORT) (wcslen(argv[i].name) * sizeof(wchar_t)) : 0 /*(default)*/,
299            .MaximumLength = 0,
300            .Buffer = (wchar_t*) argv[i].name,
301        };
302        entries[i].ValueName = &names[i];
303    }
304
305    ULONG bufferSize = argc * 128;
306    if (bufferSize < 512) {
307        bufferSize = 512;
308    }
309
310    FF_AUTO_FREE uint8_t* buffer = nullptr;
311
312    while (true) {
313        buffer = (uint8_t*) realloc(buffer, bufferSize);
314
315        ULONG writtenSize = bufferSize;
316        ULONG requiredSize = 0;
317        NTSTATUS status = NtQueryMultipleValueKey(hKey, entries, argc, buffer, &writtenSize, &requiredSize);
318
319        if (!NT_SUCCESS(status)) {
320            // Buffer too small: docs guarantee requiredSize is returned when provided.
321            if (requiredSize > bufferSize) {
322                FF_DEBUG("NtQueryMultipleValueKey(%p) resize buffer: %u -> %u", hKey, (unsigned) bufferSize, (unsigned) requiredSize);
323                bufferSize = requiredSize;
324                continue;
325            }
326
327            FF_DEBUG("NtQueryMultipleValueKey(%p, argc=%u) failed, status=0x%08X", hKey, (unsigned) argc, (unsigned) status);
328            if (error) {
329                ffStrbufAppendF(error, "NtQueryMultipleValueKey(%p, argc=%u) failed, status=0x%08X", hKey, (unsigned) argc, (unsigned) status);
330            }
331            return false;
332        }
333
334        break;
335    }
336
337    for (uint32_t i = 0; i < argc; ++i) {
338        const FFRegValueArg* arg = &argv[i];
339        const KEY_VALUE_ENTRY* entry = &entries[i];
340
341        FF_DEBUG("Read value[%u] from %p: type=%u, len=%u", (unsigned) i, hKey, (unsigned) entry->Type, (unsigned) entry->DataLength);
342        if (!processRegValue(arg, entry->Type, buffer + entry->DataOffset, entry->DataLength, error)) {
343            return false;
344        }
345    }
346
347    return true;
348}
349
350bool ffRegGetSubKey(HANDLE hKey, uint32_t index, FFstrbuf* result, FFstrbuf* error) {
351    assert(hKey);
352    assert(result);
353
354    alignas(KEY_BASIC_INFORMATION) uint8_t buffer[sizeof(KEY_BASIC_INFORMATION) + MAX_PATH * sizeof(wchar_t)];
355    ULONG bufSize = (ULONG) sizeof(buffer);
356    KEY_BASIC_INFORMATION* keyInfo = (KEY_BASIC_INFORMATION*) buffer;
357
358    // Every failure mode fails NT_SUCCESS, and the read below depends on that: on a too-small buffer
359    // the kernel returns STATUS_BUFFER_OVERFLOW, writes the *full* NameLength but copies only part of
360    // the name (measured), so treating that status as success would read past what was written.
361    // The longest name the CM accepts, 255 characters, still fits: 255 * 2 + 16 <= sizeof(buffer).
362    if (!NT_SUCCESS(NtEnumerateKey(hKey, index, KeyBasicInformation, keyInfo, bufSize, &bufSize))) {
363        FF_DEBUG("NtEnumerateKey(hKey=%p, index=%u) failed", hKey, (unsigned) index);
364        if (error) {
365            ffStrbufAppendF(error, "NtEnumerateKey(hKey=%p, %u, keyInfo) failed", hKey, (unsigned) index);
366        }
367        return false;
368    }
369
370    ffStrbufSetNWS(result, keyInfo->NameLength / sizeof(wchar_t), keyInfo->Name);
371    return true;
372}
373
374// Reads the counters the key object already caches. KeyFullInformation would return the same numbers
375// but also marshals the key's class string out; measured per query on the ARP and MSIX keys:
376// 837 ns vs 895 ns (interleaved, 200k queries each). The open dominates the total -- 22.10 us vs
377// 22.40 us for open + query -- so this is a small win, not a big one.
378//
379// The kernel writes exactly sizeof(KEY_CACHED_INFORMATION) bytes and rejects a smaller buffer with
380// STATUS_BUFFER_TOO_SMALL without writing anything, so pass a whole KEY_CACHED_INFORMATION.
381bool ffRegQueryKey(HANDLE hKey, KEY_CACHED_INFORMATION* result, FFstrbuf* error) {
382    assert(hKey);
383    assert(result);
384
385    ULONG bufSize = (ULONG) sizeof(*result);
386
387    if (!NT_SUCCESS(NtQueryKey(hKey, KeyCachedInformation, result, bufSize, &bufSize))) {
388        FF_DEBUG("NtQueryKey(hKey=%p, KeyCachedInformation) failed", hKey);
389        if (error) {
390            ffStrbufAppendF(error, "NtQueryKey(hKey=%p, KeyCachedInformation, keyInfo) failed", hKey);
391        }
392        return false;
393    }
394
395    return true;
396}