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}