master ohmu/fastfetch-openbsd / src / common / impl / FFPlatform_windows.c
ohmu  ·  2026-10-15
  1#include "FFPlatform_private.h"
  2#include "common/io.h"
  3#include "common/library.h"
  4#include "common/strutil.h"
  5#include "common/windows/folders.h"
  6#include "common/windows/unicode.h"
  7#include "common/windows/registry.h"
  8#include "common/windows/nt.h"
  9
 10#include <stdalign.h>
 11#include <windows.h>
 12#include <sddl.h>
 13
 14#define SECURITY_WIN32 1 // For secext.h
 15#include <secext.h>
 16
 17static void getExePath(FFPlatform* platform) {
 18    wchar_t exePathW[MAX_PATH];
 19
 20    FF_AUTO_CLOSE_FD HANDLE hPath = CreateFileW(
 21        ffGetPeb()->ProcessParameters->ImagePathName.Buffer,
 22        GENERIC_READ,
 23        FILE_SHARE_READ,
 24        nullptr,
 25        OPEN_EXISTING,
 26        FILE_FLAG_BACKUP_SEMANTICS,
 27        nullptr);
 28    if (hPath != INVALID_HANDLE_VALUE) {
 29        DWORD len = GetFinalPathNameByHandleW(hPath, exePathW, MAX_PATH, FILE_NAME_NORMALIZED);
 30        if (len > 0 && len < MAX_PATH) {
 31            ffStrbufSetNWS(&platform->exePath, len, exePathW);
 32            if (ffStrbufStartsWithS(&platform->exePath, "\\\\?\\")) {
 33                ffStrbufSubstrAfter(&platform->exePath, 3);
 34            }
 35        }
 36    }
 37
 38    if (platform->exePath.length == 0) {
 39        PCUNICODE_STRING imagePathName = &ffGetPeb()->ProcessParameters->ImagePathName;
 40        ffStrbufSetNWS(&platform->exePath, imagePathName->Length / sizeof(wchar_t), imagePathName->Buffer);
 41    }
 42
 43    ffStrbufReplaceAllC(&platform->exePath, '\\', '/');
 44}
 45
 46static void getCacheDir(FFPlatform* platform) {
 47    if (!ffGetKnownFolderPath(FF_KNOWN_FOLDER_LOCAL_APP_DATA, &platform->cacheDir)) {
 48        ffStrbufAppend(&platform->cacheDir, &platform->homeDir);
 49        ffStrbufAppendS(&platform->cacheDir, "AppData/Local/");
 50        return;
 51    }
 52
 53    ffStrbufReplaceAllC(&platform->cacheDir, '\\', '/');
 54    ffStrbufEnsureEndsWithC(&platform->cacheDir, '/');
 55}
 56
 57static void platformPathAddKnownFolder(FFlist* dirs, FFKnownFolder folder) {
 58    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
 59    if (!ffGetKnownFolderPath(folder, &buffer)) {
 60        return;
 61    }
 62
 63    ffStrbufReplaceAllC(&buffer, '\\', '/');
 64    ffStrbufEnsureEndsWithC(&buffer, '/');
 65    if (!FF_LIST_CONTAINS(*dirs, &buffer, ffStrbufEqual)) {
 66        ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *dirs), &buffer);
 67    }
 68}
 69
 70static void platformPathAddEnvSuffix(FFlist* dirs, const char* env, const char* suffix) {
 71    const char* value = getenv(env);
 72    if (!ffStrSet(value)) {
 73        return;
 74    }
 75
 76    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateA(64);
 77    ffStrbufAppendS(&buffer, value);
 78    ffStrbufReplaceAllC(&buffer, '\\', '/');
 79    ffStrbufEnsureEndsWithC(&buffer, '/');
 80    if (suffix) {
 81        ffStrbufAppendS(&buffer, suffix);
 82        ffStrbufEnsureEndsWithC(&buffer, '/');
 83    }
 84
 85    if (ffPathExists(buffer.chars, FF_PATHTYPE_DIRECTORY) && !FF_LIST_CONTAINS(*dirs, &buffer, ffStrbufEqual)) {
 86        ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *dirs), &buffer);
 87    }
 88}
 89
 90static void getConfigDirs(FFPlatform* platform) {
 91    if (getenv("MSYSTEM")) {
 92        // We are in MSYS2 / Git Bash
 93        platformPathAddEnvSuffix(&platform->configDirs, "HOME", ".config/");
 94        platformPathAddEnvSuffix(&platform->configDirs, "HOME", nullptr);
 95        platformPathAddEnvSuffix(&platform->configDirs, "MINGW_PREFIX", "etc");
 96    }
 97
 98    ffPlatformPathAddHome(&platform->configDirs, platform, ".config/");
 99    platformPathAddKnownFolder(&platform->configDirs, FF_KNOWN_FOLDER_PROGRAM_DATA);
100    platformPathAddKnownFolder(&platform->configDirs, FF_KNOWN_FOLDER_ROAMING_APP_DATA);
101    platformPathAddKnownFolder(&platform->configDirs, FF_KNOWN_FOLDER_LOCAL_APP_DATA);
102    ffPlatformPathAddHome(&platform->configDirs, platform, "");
103}
104
105static void getDataDirs(FFPlatform* platform) {
106    if (getenv("MSYSTEM") && getenv("HOME")) {
107        // We are in MSYS2 / Git Bash
108        platformPathAddEnvSuffix(&platform->dataDirs, "HOME", ".local/share/");
109        platformPathAddEnvSuffix(&platform->dataDirs, "HOME", nullptr);
110        platformPathAddEnvSuffix(&platform->dataDirs, "MINGW_PREFIX", "share");
111    }
112    ffPlatformPathAddHome(&platform->dataDirs, platform, ".local/share/");
113    platformPathAddKnownFolder(&platform->dataDirs, FF_KNOWN_FOLDER_PROGRAM_DATA);
114    platformPathAddKnownFolder(&platform->dataDirs, FF_KNOWN_FOLDER_ROAMING_APP_DATA);
115    platformPathAddKnownFolder(&platform->dataDirs, FF_KNOWN_FOLDER_LOCAL_APP_DATA);
116    ffPlatformPathAddHome(&platform->dataDirs, platform, "");
117}
118
119static void getUserName(FFPlatform* platform) {
120    wchar_t buffer[256];
121    DWORD size = ARRAY_SIZE(buffer);
122    if (GetUserNameExW(NameDisplay, buffer, &size)) {
123        ffStrbufSetWS(&platform->fullUserName, buffer);
124    }
125
126    NTSYSAPI NTSTATUS NTAPI LsaGetUserName(
127        _Outptr_ PLSA_UNICODE_STRING * UserName,
128        _Outptr_opt_ PLSA_UNICODE_STRING * DomainName);
129    PLSA_UNICODE_STRING userName = nullptr;
130    if (NT_SUCCESS(LsaGetUserName(&userName, nullptr))) {
131        ffStrbufSetNWS(&platform->userName, userName->Length / sizeof(wchar_t), userName->Buffer);
132        RtlFreeUnicodeString(userName); // Required. userName.Buffer is allocated separately
133        LsaFreeMemory(userName);
134    } else {
135        ffStrbufSetS(&platform->userName, getenv("USERNAME"));
136    }
137
138    alignas(TOKEN_USER) char buf[SECURITY_MAX_SID_SIZE + sizeof(TOKEN_USER)];
139    if (NT_SUCCESS(NtQueryInformationToken(NtCurrentProcessToken(), TokenUser, buf, sizeof(buf), &size))) {
140        TOKEN_USER* tokenUser = (TOKEN_USER*) buf;
141        UNICODE_STRING sidString = { .Buffer = buffer, .Length = 0, .MaximumLength = sizeof(buffer) };
142        if (NT_SUCCESS(RtlConvertSidToUnicodeString(&sidString, tokenUser->User.Sid, FALSE))) {
143            uint32_t sidLength = sidString.Length / sizeof(wchar_t);
144            ffStrbufSetNWS(&platform->sid, sidLength, sidString.Buffer);
145
146            // The SID is already in hand, so the profile directory costs one registry read rather
147            // than a second NtQueryInformationToken(). ffGetProfilePath() needs a null-terminated
148            // string, which RtlConvertSidToUnicodeString() does not produce.
149            if (sidLength < ARRAY_SIZE(buffer)) {
150                buffer[sidLength] = L'\0';
151                ffGetProfilePath(buffer, &platform->homeDir);
152            }
153        }
154    }
155
156    if (platform->homeDir.length == 0) {
157        ffStrbufSetS(&platform->homeDir, getenv("USERPROFILE")); // Last resort; ffGetProfilePath() covers the normal case
158    }
159
160    ffStrbufReplaceAllC(&platform->homeDir, '\\', '/');
161    ffStrbufEnsureEndsWithC(&platform->homeDir, '/');
162}
163
164static void getHostName(FFPlatform* platform) {
165    wchar_t buffer[256];
166    DWORD len = ARRAY_SIZE(buffer);
167    if (GetComputerNameExW(ComputerNameDnsHostname, buffer, &len) && len > 0) {
168        ffStrbufSetNWS(&platform->hostName, len, buffer);
169    } else {
170        len = ARRAY_SIZE(buffer);
171        if (GetComputerNameExW(ComputerNameNetBIOS, buffer, &len) && len > 0) {
172            ffStrbufSetNWS(&platform->hostName, len, buffer);
173        }
174    }
175}
176
177static void getUserShell(FFPlatform* platform) {
178    // Works in MSYS2
179    const char* userShell = getenv("SHELL");
180    if (userShell) {
181        ffStrbufAppendS(&platform->userShell, userShell);
182        ffStrbufReplaceAllC(&platform->userShell, '\\', '/');
183    }
184}
185
186static const char* detectWine(void) {
187    const char* __cdecl wine_get_version(void);
188    void* hntdll = ffLibraryGetModule(L"ntdll.dll");
189    if (!hntdll) {
190        return nullptr;
191    }
192    FF_LIBRARY_LOAD_SYMBOL_LAZY(hntdll, wine_get_version);
193    if (!ffwine_get_version) {
194        return nullptr;
195    }
196    return ffwine_get_version();
197}
198
199static void getSystemReleaseAndVersion(FFPlatformSysinfo* info) {
200    FF_AUTO_CLOSE_FD HANDLE hKey = nullptr;
201    if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", &hKey, nullptr)) {
202        return;
203    }
204
205    uint32_t ubr = 0;
206    ffRegReadValues(hKey, 2, (FFRegValueArg[]) {
207                                 FF_ARG(ubr, L"UBR"),
208                                 FF_ARG(info->version, L"BuildLabEx"),
209                             },
210        nullptr);
211
212    PPEB_FULL peb = ffGetPeb();
213
214    ffStrbufSetF(&info->release,
215        "%u.%u.%u.%u",
216        (unsigned) peb->OSMajorVersion,
217        (unsigned) peb->OSMinorVersion,
218        (unsigned) peb->OSBuildNumber,
219        (unsigned) ubr);
220
221    const char* wineVersion = detectWine();
222    if (wineVersion) {
223        ffStrbufSetF(&info->name, "Wine_%s", wineVersion);
224    } else {
225        ffStrbufSetStatic(&info->name, "WIN32_NT");
226    }
227}
228
229static void getSystemPageSize(FFPlatformSysinfo* info) {
230    SYSTEM_BASIC_INFORMATION sbi;
231    if (NT_SUCCESS(NtQuerySystemInformation(SystemBasicInformation, &sbi, sizeof(sbi), nullptr))) {
232        info->pageSizeShift = (uint32_t) __builtin_ctzl(sbi.PhysicalPageSize);
233    }
234}
235
236static void getSystemArchitecture(FFPlatformSysinfo* info) {
237    switch (SharedUserData->NativeProcessorArchitecture) {
238        case PROCESSOR_ARCHITECTURE_AMD64:
239            ffStrbufSetStatic(&info->architecture, "x86_64");
240            break;
241        case PROCESSOR_ARCHITECTURE_IA64:
242            ffStrbufSetStatic(&info->architecture, "ia64");
243            break;
244        case PROCESSOR_ARCHITECTURE_INTEL:
245            ffStrbufSetStatic(&info->architecture, "i686");
246            break;
247        case PROCESSOR_ARCHITECTURE_ARM64:
248            ffStrbufSetStatic(&info->architecture, "aarch64");
249            break;
250        case PROCESSOR_ARCHITECTURE_ARM:
251            ffStrbufSetStatic(&info->architecture, "arm");
252            break;
253        case PROCESSOR_ARCHITECTURE_PPC:
254            ffStrbufSetStatic(&info->architecture, "ppc");
255            break;
256        case PROCESSOR_ARCHITECTURE_MIPS:
257            ffStrbufSetStatic(&info->architecture, "mips");
258            break;
259        case PROCESSOR_ARCHITECTURE_ALPHA:
260            ffStrbufSetStatic(&info->architecture, "alpha");
261            break;
262        case PROCESSOR_ARCHITECTURE_ALPHA64:
263            ffStrbufSetStatic(&info->architecture, "alpha64");
264            break;
265        case PROCESSOR_ARCHITECTURE_UNKNOWN:
266        default:
267            ffStrbufSetStatic(&info->architecture, "unknown");
268            break;
269    }
270}
271
272static void getCwd(FFPlatform* platform) {
273    PCURDIR cwd = &ffGetPeb()->ProcessParameters->CurrentDirectory;
274    ffStrbufSetNWS(&platform->cwd, cwd->DosPath.Length / sizeof(WCHAR), cwd->DosPath.Buffer);
275    ffStrbufReplaceAllC(&platform->cwd, '\\', '/');
276    ffStrbufEnsureEndsWithC(&platform->cwd, '/');
277}
278
279void ffPlatformInitImpl(FFPlatform* platform) {
280    platform->pid = (uint32_t) (uintptr_t) ffGetTeb()->ClientId.UniqueProcess;
281    getExePath(platform);
282    getCwd(platform);
283
284    getUserName(platform);
285
286    getCacheDir(platform);
287    getConfigDirs(platform);
288    getDataDirs(platform);
289    getHostName(platform);
290    getUserShell(platform);
291
292    getSystemReleaseAndVersion(&platform->sysinfo);
293    getSystemArchitecture(&platform->sysinfo);
294    getSystemPageSize(&platform->sysinfo);
295}