Files
fastfetch/src/common/impl/io_windows.c
T

502 lines
16 KiB
C
Raw Normal View History

#include "fastfetch.h"
#include "common/io.h"
2026-05-28 15:58:08 +08:00
#include "common/strutil.h"
#include "common/windows/nt.h"
2026-03-11 23:28:41 +08:00
#include "common/windows/unicode.h"
2023-07-26 10:27:40 +08:00
#include <windows.h>
2026-03-29 09:28:49 +08:00
static bool createSubfolders(wchar_t* fileName) {
HANDLE hRoot = ffGetPeb()->ProcessParameters->CurrentDirectory.Handle;
bool closeRoot = false;
wchar_t* ptr = fileName;
// Absolute drive path: C:\...
2026-03-29 09:28:49 +08:00
if (ffCharIsEnglishAlphabet((char) ptr[0]) && ptr[1] == L':' && ptr[2] == L'\\') {
wchar_t saved = ptr[3];
ptr[3] = L'\0';
hRoot = CreateFileW(
fileName,
FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_FLAG_BACKUP_SEMANTICS,
2026-03-29 09:28:49 +08:00
NULL);
ptr[3] = saved;
2026-03-29 09:28:49 +08:00
if (hRoot == INVALID_HANDLE_VALUE) {
return false;
2026-03-29 09:28:49 +08:00
}
closeRoot = true;
ptr += 3; // skip "C:\"
}
// UNC path: \\server\share\...
2026-03-29 09:28:49 +08:00
else if (ptr[0] == L'\\' && ptr[1] == L'\\') {
wchar_t* serverEnd = wcschr(ptr + 2, L'\\');
2026-03-29 09:28:49 +08:00
if (serverEnd == NULL) {
return false;
2026-03-29 09:28:49 +08:00
}
wchar_t* shareEnd = wcschr(serverEnd + 1, L'\\');
2026-03-29 09:28:49 +08:00
if (shareEnd == NULL) {
return true; // no parent subfolder exists before file name
2026-03-29 09:28:49 +08:00
}
wchar_t saved = *shareEnd;
*shareEnd = L'\0';
hRoot = CreateFileW(
fileName,
FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_FLAG_BACKUP_SEMANTICS,
2026-03-29 09:28:49 +08:00
NULL);
*shareEnd = saved;
2026-03-29 09:28:49 +08:00
if (hRoot == INVALID_HANDLE_VALUE) {
return false;
2026-03-29 09:28:49 +08:00
}
closeRoot = true;
ptr = shareEnd + 1; // first component under share
}
// Rooted path on current drive: \foo\bar
2026-03-29 09:28:49 +08:00
else if (ptr[0] == L'\\') {
UNICODE_STRING* dosPath = &ffGetPeb()->ProcessParameters->CurrentDirectory.DosPath;
2026-03-30 10:27:23 +08:00
wchar_t driveRoot[] = { dosPath->Buffer[0], L':', L'\\', L'\0' };
hRoot = CreateFileW(
driveRoot,
FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_FLAG_BACKUP_SEMANTICS,
2026-03-29 09:28:49 +08:00
NULL);
if (hRoot == INVALID_HANDLE_VALUE) {
return false;
2026-03-29 09:28:49 +08:00
}
closeRoot = true;
ptr++; // skip leading '\'
}
2026-03-29 09:28:49 +08:00
while (true) {
wchar_t* token = wcschr(ptr, L'\\');
2026-03-29 09:28:49 +08:00
if (token == NULL) {
break;
2026-03-29 09:28:49 +08:00
}
// Skip empty path segments caused by duplicated '\'
2026-03-29 09:28:49 +08:00
if (token == ptr) {
ptr = token + 1;
continue;
}
HANDLE hNew = INVALID_HANDLE_VALUE;
IO_STATUS_BLOCK iosb = {};
NTSTATUS status = NtCreateFile(
&hNew,
FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE,
&(OBJECT_ATTRIBUTES) {
.Length = sizeof(OBJECT_ATTRIBUTES),
.RootDirectory = hRoot,
.ObjectName = &(UNICODE_STRING) {
.Buffer = ptr,
2026-03-29 09:28:49 +08:00
.Length = (USHORT) ((USHORT) (token - ptr) * sizeof(wchar_t)),
.MaximumLength = (USHORT) ((USHORT) (token - ptr) * sizeof(wchar_t)),
},
.Attributes = OBJ_CASE_INSENSITIVE,
},
&iosb,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN_IF,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
2026-03-29 09:28:49 +08:00
0);
2026-03-29 09:28:49 +08:00
if (!NT_SUCCESS(status)) {
if (closeRoot && hRoot != INVALID_HANDLE_VALUE) {
NtClose(hRoot);
2026-03-29 09:28:49 +08:00
}
return false;
}
2026-03-29 09:28:49 +08:00
if (closeRoot && hRoot != INVALID_HANDLE_VALUE) {
NtClose(hRoot);
2026-03-29 09:28:49 +08:00
}
hRoot = hNew;
closeRoot = true;
ptr = token + 1;
}
2026-03-29 09:28:49 +08:00
if (closeRoot && hRoot != INVALID_HANDLE_VALUE) {
NtClose(hRoot);
2026-03-29 09:28:49 +08:00
}
return true;
}
2026-03-29 09:28:49 +08:00
bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data) {
wchar_t fileNameW[MAX_PATH];
ULONG len = 0;
2026-03-29 09:28:49 +08:00
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, fileName, (ULONG) strlen(fileName) + 1))) {
return false;
2026-03-29 09:28:49 +08:00
}
2026-03-29 09:28:49 +08:00
for (ULONG i = 0; i < len / sizeof(wchar_t); ++i) {
if (fileNameW[i] == L'/') {
fileNameW[i] = L'\\';
2026-03-29 09:28:49 +08:00
}
}
HANDLE FF_AUTO_CLOSE_FD handle = CreateFileW(fileNameW, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
2026-03-29 09:28:49 +08:00
if (handle == INVALID_HANDLE_VALUE) {
if (GetLastError() == ERROR_PATH_NOT_FOUND) {
if (!createSubfolders(fileNameW)) {
return false;
2026-03-29 09:28:49 +08:00
}
handle = CreateFileW(fileNameW, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
2026-03-29 09:28:49 +08:00
if (handle == INVALID_HANDLE_VALUE) {
return false;
2026-03-29 09:28:49 +08:00
}
} else {
return false;
2026-03-29 09:28:49 +08:00
}
}
DWORD written;
2026-03-29 09:28:49 +08:00
return !!WriteFile(handle, data, (DWORD) dataSize, &written, NULL);
}
2026-03-29 09:28:49 +08:00
static inline void readWithLength(HANDLE handle, FFstrbuf* buffer, uint32_t length) {
2026-06-29 16:11:29 +08:00
ffStrbufEnsureFixedLengthFree(buffer, length);
DWORD bytesRead = 0;
2026-03-29 09:28:49 +08:00
while (
2023-12-22 16:04:56 +09:00
length > 0 &&
ReadFile(handle, buffer->chars + buffer->length, length, &bytesRead, NULL) != FALSE &&
2026-03-29 09:28:49 +08:00
bytesRead > 0) {
2023-12-22 16:04:56 +09:00
buffer->length += (uint32_t) bytesRead;
length -= (uint32_t) bytesRead;
}
2023-12-22 16:04:56 +09:00
}
2026-03-29 09:28:49 +08:00
static inline void readUntilEOF(HANDLE handle, FFstrbuf* buffer) {
2023-12-22 16:04:56 +09:00
ffStrbufEnsureFree(buffer, 31);
uint32_t available = ffStrbufGetFree(buffer);
DWORD bytesRead = 0;
2026-03-29 09:28:49 +08:00
while (
2023-12-22 16:04:56 +09:00
ReadFile(handle, buffer->chars + buffer->length, available, &bytesRead, NULL) != FALSE &&
2026-03-29 09:28:49 +08:00
bytesRead > 0) {
buffer->length += (uint32_t) bytesRead;
2026-03-29 09:28:49 +08:00
if ((uint32_t) bytesRead == available) {
2023-09-02 10:04:57 -04:00
ffStrbufEnsureFree(buffer, buffer->allocated - 1); // Doubles capacity every round. -1 for the null byte.
2026-03-29 09:28:49 +08:00
}
2023-12-22 16:04:56 +09:00
available = ffStrbufGetFree(buffer);
}
2023-12-22 16:04:56 +09:00
}
2026-03-29 09:28:49 +08:00
bool ffAppendFDBuffer(HANDLE handle, FFstrbuf* buffer) {
FILE_STANDARD_INFORMATION fileInfo;
IO_STATUS_BLOCK iosb;
2026-03-29 09:28:49 +08:00
if (!NT_SUCCESS(NtQueryInformationFile(handle, &iosb, &fileInfo, sizeof(fileInfo), FileStandardInformation))) {
fileInfo.EndOfFile.QuadPart = 0;
2026-03-29 09:28:49 +08:00
}
2023-12-22 16:04:56 +09:00
2026-03-29 09:28:49 +08:00
if (fileInfo.EndOfFile.QuadPart > 0) {
readWithLength(handle, buffer, (uint32_t) fileInfo.EndOfFile.QuadPart);
} else {
2023-12-22 16:04:56 +09:00
readUntilEOF(handle, buffer);
2026-03-29 09:28:49 +08:00
}
2023-12-22 16:04:56 +09:00
buffer->chars[buffer->length] = '\0';
2023-12-22 16:04:56 +09:00
return buffer->length > 0;
}
2026-03-29 09:28:49 +08:00
HANDLE openatW(HANDLE dfd, const wchar_t* fileName, uint16_t fileNameLen, bool directory) {
2025-12-09 14:37:57 +08:00
assert(fileNameLen <= 0x7FFF);
HANDLE hFile;
IO_STATUS_BLOCK iosb = {};
2026-03-29 09:28:49 +08:00
if (!NT_SUCCESS(NtOpenFile(&hFile,
(directory ? FILE_LIST_DIRECTORY | FILE_TRAVERSE : FILE_READ_DATA | FILE_READ_EA) | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
&(OBJECT_ATTRIBUTES) {
.Length = sizeof(OBJECT_ATTRIBUTES),
.RootDirectory = dfd,
.ObjectName = &(UNICODE_STRING) {
.Buffer = (PWSTR) fileName,
.Length = fileNameLen * (USHORT) sizeof(wchar_t),
.MaximumLength = (fileNameLen + 1) * (USHORT) sizeof(wchar_t),
},
.Attributes = OBJ_CASE_INSENSITIVE,
2025-12-09 14:37:57 +08:00
},
2026-03-29 09:28:49 +08:00
&iosb,
FILE_SHARE_READ | (directory ? FILE_SHARE_WRITE | FILE_SHARE_DELETE : 0),
FILE_SYNCHRONOUS_IO_NONALERT | (directory ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE)))) {
2024-10-12 10:41:58 +08:00
return INVALID_HANDLE_VALUE;
2026-03-29 09:28:49 +08:00
}
2024-10-12 10:41:58 +08:00
return hFile;
}
2026-03-29 09:28:49 +08:00
HANDLE openat(HANDLE dfd, const char* fileName, int oflag) {
2025-12-09 14:37:57 +08:00
wchar_t fileNameW[MAX_PATH];
ULONG len;
2026-03-29 09:28:49 +08:00
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, fileName, (ULONG) strlen(fileName) + 1))) {
return INVALID_HANDLE_VALUE;
2026-03-29 09:28:49 +08:00
}
// Implies `fileNameW[len] = L'\0';` and `len` includes the null terminator
len /= sizeof(wchar_t); // convert from bytes to characters
2025-12-09 14:37:57 +08:00
2026-03-29 09:28:49 +08:00
for (uint32_t i = 0; i < len - 1; ++i) {
if (fileNameW[i] == L'/') {
2025-12-09 14:37:57 +08:00
fileNameW[i] = L'\\';
2026-03-29 09:28:49 +08:00
}
2025-12-09 14:37:57 +08:00
}
2026-03-29 09:28:49 +08:00
return openatW(dfd, fileNameW, (uint16_t) (len - 1), !!(oflag & O_DIRECTORY));
}
2026-03-29 09:28:49 +08:00
bool ffPathExpandEnv(const char* in, FFstrbuf* out) {
2025-04-07 16:43:36 +08:00
if (in[0] == '~') {
if ((in[1] == '/' || in[1] == '\\' || in[1] == '\0') && !ffStrContainsC(in, '%')) {
ffStrbufSet(out, &instance.state.platform.homeDir);
ffStrbufAppendS(out, in + 1);
return true;
}
}
2026-03-11 23:28:41 +08:00
wchar_t pathInW[MAX_PATH], pathOutW[MAX_PATH];
ULONG len = (ULONG) strlen(in);
2026-03-29 09:28:49 +08:00
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(pathInW, (ULONG) sizeof(pathInW), &len, in, len))) {
2026-03-11 23:28:41 +08:00
return false;
2026-03-29 09:28:49 +08:00
}
2026-03-11 23:28:41 +08:00
len /= sizeof(wchar_t); // convert from bytes to characters
2026-04-05 14:40:38 +08:00
SIZE_T outLen; // in characters, including null terminator
2026-03-29 09:28:49 +08:00
if (!NT_SUCCESS(RtlExpandEnvironmentStrings(NULL, pathInW, len, pathOutW, ARRAY_SIZE(pathOutW), &outLen))) {
2026-03-11 23:28:41 +08:00
return false;
2026-03-29 09:28:49 +08:00
}
2026-03-11 23:28:41 +08:00
ffStrbufSetNWS(out, (uint32_t) outLen - 1, pathOutW);
return true;
}
2026-03-29 09:28:49 +08:00
bool ffSuppressIO(bool suppress) {
#ifndef NDEBUG
if (instance.config.display.debugMode) {
2025-03-23 21:56:32 +08:00
return false;
2026-03-29 09:28:49 +08:00
}
#endif
2025-03-23 21:56:32 +08:00
2024-06-26 13:50:42 +08:00
static bool init = false;
static HANDLE hOrigOut = INVALID_HANDLE_VALUE;
static HANDLE hOrigErr = INVALID_HANDLE_VALUE;
HANDLE hNullFile = ffGetNullFD();
2024-06-26 13:50:42 +08:00
static int fOrigOut = -1;
static int fOrigErr = -1;
static int fNullFile = -1;
2026-03-29 09:28:49 +08:00
if (!init) {
if (!suppress) {
2024-06-26 13:50:42 +08:00
return true;
2026-03-29 09:28:49 +08:00
}
2024-06-26 13:50:42 +08:00
hOrigOut = GetStdHandle(STD_OUTPUT_HANDLE);
hOrigErr = GetStdHandle(STD_ERROR_HANDLE);
fOrigOut = _dup(STDOUT_FILENO);
fOrigErr = _dup(STDERR_FILENO);
fNullFile = _open_osfhandle((intptr_t) hNullFile, 0);
init = true;
}
2026-03-29 09:28:49 +08:00
if (hNullFile == INVALID_HANDLE_VALUE || fNullFile == -1) {
2024-06-26 13:50:42 +08:00
return false;
2026-03-29 09:28:49 +08:00
}
2024-06-26 13:50:42 +08:00
fflush(stdout);
fflush(stderr);
SetStdHandle(STD_OUTPUT_HANDLE, suppress ? hNullFile : hOrigOut);
SetStdHandle(STD_ERROR_HANDLE, suppress ? hNullFile : hOrigErr);
_dup2(suppress ? fNullFile : fOrigOut, STDOUT_FILENO);
_dup2(suppress ? fNullFile : fOrigErr, STDERR_FILENO);
return true;
}
2023-01-28 17:19:45 +01:00
2026-03-29 09:28:49 +08:00
void listFilesRecursively(uint32_t baseLength, FFstrbuf* folder, uint8_t indentation, const char* folderName, bool pretty) {
2023-01-28 17:19:45 +01:00
uint32_t folderLength = folder->length;
2026-03-29 09:28:49 +08:00
if (pretty && folderName != NULL) {
for (uint8_t i = 0; i < indentation - 1; i++) {
2023-11-13 16:41:10 +08:00
fputs(" | ", stdout);
2026-03-29 09:28:49 +08:00
}
2023-01-28 17:19:45 +01:00
printf("%s/\n", folderName);
2023-11-13 16:41:10 +08:00
}
2023-01-28 17:19:45 +01:00
ffStrbufAppendC(folder, '*');
WIN32_FIND_DATAA entry;
HANDLE hFind = FindFirstFileA(folder->chars, &entry);
2023-11-13 16:41:10 +08:00
ffStrbufTrimRight(folder, '*');
2026-03-29 09:28:49 +08:00
if (hFind == INVALID_HANDLE_VALUE) {
2023-01-28 17:19:45 +01:00
return;
2026-03-29 09:28:49 +08:00
}
2023-01-28 17:19:45 +01:00
2026-03-29 09:28:49 +08:00
do {
if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (ffStrEquals(entry.cFileName, ".") || ffStrEquals(entry.cFileName, "..")) {
2023-01-28 17:19:45 +01:00
continue;
2026-03-29 09:28:49 +08:00
}
2023-01-28 17:19:45 +01:00
ffStrbufSubstrBefore(folder, folderLength);
ffStrbufAppendS(folder, entry.cFileName);
ffStrbufAppendC(folder, '/');
2023-11-13 16:41:10 +08:00
listFilesRecursively(baseLength, folder, (uint8_t) (indentation + 1), entry.cFileName, pretty);
2023-01-28 17:19:45 +01:00
ffStrbufSubstrBefore(folder, folderLength);
continue;
}
2026-03-29 09:28:49 +08:00
if (pretty) {
for (uint8_t i = 0; i < indentation; i++) {
2023-11-13 16:41:10 +08:00
fputs(" | ", stdout);
2026-03-29 09:28:49 +08:00
}
} else {
2023-11-13 16:41:10 +08:00
fputs(folder->chars + baseLength, stdout);
}
2023-01-28 17:19:45 +01:00
puts(entry.cFileName);
} while (FindNextFileA(hFind, &entry));
FindClose(hFind);
}
2026-03-29 09:28:49 +08:00
void ffListFilesRecursively(const char* path, bool pretty) {
FF_STRBUF_AUTO_DESTROY folder = ffStrbufCreateS(path);
2023-01-28 17:19:45 +01:00
ffStrbufEnsureEndsWithC(&folder, '/');
2023-11-13 16:41:10 +08:00
listFilesRecursively(folder.length, &folder, 0, NULL, pretty);
2023-01-28 17:19:45 +01:00
}
2023-07-26 10:27:40 +08:00
2026-03-29 09:28:49 +08:00
const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...) {
2023-07-26 10:27:40 +08:00
HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
FF_AUTO_CLOSE_FD HANDLE hConin = INVALID_HANDLE_VALUE;
2026-04-28 09:03:24 +08:00
DWORD inputMode = 0;
bool hasInputMode = !!GetConsoleMode(hInput, &inputMode);
if (!hasInputMode) {
hConin = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, NULL);
hInput = hConin;
2026-04-28 09:03:24 +08:00
hasInputMode = !!GetConsoleMode(hInput, &inputMode);
}
2023-07-26 10:27:40 +08:00
SetConsoleMode(hInput, 0);
FlushConsoleInputBuffer(hInput);
{
DWORD bytes = 0;
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
FF_AUTO_CLOSE_FD HANDLE hConout = INVALID_HANDLE_VALUE;
DWORD outputMode;
2026-03-29 09:28:49 +08:00
if (!GetConsoleMode(hOutput, &outputMode)) {
hConout = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, NULL);
hOutput = hConout;
}
WriteFile(hOutput, request, (DWORD) strlen(request), &bytes, NULL);
}
2023-07-26 10:27:40 +08:00
2026-03-29 09:28:49 +08:00
while (true) {
2026-03-30 10:27:23 +08:00
if (NtWaitForSingleObject(hInput, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 }) != STATUS_WAIT_0) {
SetConsoleMode(hInput, inputMode);
return "NtWaitForSingleObject() failed or timeout";
2023-07-26 10:27:40 +08:00
}
// Ignore all unexpected input events
INPUT_RECORD record;
DWORD len = 0;
2026-03-29 09:28:49 +08:00
if (!PeekConsoleInputW(hInput, &record, 1, &len)) {
2023-07-26 10:27:40 +08:00
break;
2026-03-29 09:28:49 +08:00
}
2023-07-26 10:27:40 +08:00
if (
record.EventType == KEY_EVENT &&
record.Event.KeyEvent.uChar.UnicodeChar != L'\r' &&
2026-03-29 09:28:49 +08:00
record.Event.KeyEvent.uChar.UnicodeChar != L'\n') {
2023-07-26 10:27:40 +08:00
break;
2026-03-29 09:28:49 +08:00
} else {
2023-07-26 10:27:40 +08:00
ReadConsoleInputW(hInput, &record, 1, &len);
2026-03-29 09:28:49 +08:00
}
2023-07-26 10:27:40 +08:00
}
va_list args;
va_start(args, format);
2023-07-26 10:27:40 +08:00
char buffer[1024];
uint32_t bytesRead = 0;
2023-07-26 10:27:40 +08:00
2026-03-29 09:28:49 +08:00
while (true) {
DWORD bytes = 0;
2026-04-28 19:45:19 +08:00
if (!ReadFile(hInput, buffer + bytesRead, (DWORD) (sizeof(buffer) - 1 - bytesRead), &bytes, NULL) || bytes == 0) {
2024-10-04 16:57:12 +08:00
va_end(args);
return "ReadFile() failed";
2024-10-04 16:57:12 +08:00
}
2023-07-26 10:27:40 +08:00
bytesRead += bytes;
2026-04-28 09:03:24 +08:00
if (__builtin_expect(bytesRead >= sizeof(buffer) - 1, false)) {
va_end(args);
return "terminal response buffer overflow";
}
buffer[bytesRead] = '\0';
2024-10-06 09:29:54 +08:00
va_list cargs;
va_copy(cargs, args);
2026-03-11 15:41:47 +08:00
int ret = vsscanf(buffer, format, cargs);
2024-10-06 09:29:54 +08:00
va_end(cargs);
2026-03-29 09:28:49 +08:00
if (ret <= 0) {
2024-10-04 16:57:12 +08:00
va_end(args);
return "vsscanf(buffer, format, args) failed";
2024-10-04 16:57:12 +08:00
}
2026-03-29 09:28:49 +08:00
if (ret >= nParams) {
break;
2026-03-29 09:28:49 +08:00
}
}
2026-04-28 09:03:24 +08:00
if (hasInputMode) {
SetConsoleMode(hInput, inputMode);
}
2023-07-26 10:27:40 +08:00
va_end(args);
return NULL;
}
2026-03-29 09:28:49 +08:00
FFNativeFD ffGetNullFD(void) {
static FFNativeFD hNullFile = INVALID_HANDLE_VALUE;
2026-03-29 09:28:49 +08:00
if (hNullFile != INVALID_HANDLE_VALUE) {
return hNullFile;
2026-03-29 09:28:49 +08:00
}
hNullFile = CreateFileW(
L"NUL",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
0,
2026-03-29 09:28:49 +08:00
&(SECURITY_ATTRIBUTES) {
.nLength = sizeof(SECURITY_ATTRIBUTES),
.lpSecurityDescriptor = NULL,
.bInheritHandle = TRUE,
});
return hNullFile;
}
2026-03-29 09:28:49 +08:00
bool ffRemoveFile(const char* fileName) {
return DeleteFileA(fileName) != FALSE;
}