2024-04-21 20:12:42 +08:00
|
|
|
#include "fastfetch.h"
|
2026-03-11 16:19:49 +08:00
|
|
|
#include "common/io.h"
|
2026-01-06 09:48:38 +08:00
|
|
|
#include "common/stringUtils.h"
|
2026-03-11 16:19:49 +08:00
|
|
|
#include "common/windows/nt.h"
|
2026-03-11 23:28:41 +08:00
|
|
|
#include "common/windows/unicode.h"
|
2023-01-28 12:40:12 +01:00
|
|
|
|
2023-07-26 10:27:40 +08:00
|
|
|
#include <windows.h>
|
|
|
|
|
|
2026-03-11 16:19:49 +08:00
|
|
|
static bool createSubfolders(wchar_t* fileName)
|
2023-01-28 12:40:12 +01:00
|
|
|
{
|
2026-03-19 15:16:50 +08:00
|
|
|
HANDLE hRoot = ffGetPeb()->ProcessParameters->CurrentDirectory.Handle;
|
2026-03-11 16:19:49 +08:00
|
|
|
bool closeRoot = false;
|
|
|
|
|
wchar_t* ptr = fileName;
|
|
|
|
|
|
|
|
|
|
// Absolute drive path: C:\...
|
|
|
|
|
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,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
ptr[3] = saved;
|
|
|
|
|
if (hRoot == INVALID_HANDLE_VALUE)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
closeRoot = true;
|
|
|
|
|
ptr += 3; // skip "C:\"
|
|
|
|
|
}
|
|
|
|
|
// UNC path: \\server\share\...
|
|
|
|
|
else if (ptr[0] == L'\\' && ptr[1] == L'\\')
|
|
|
|
|
{
|
|
|
|
|
wchar_t* serverEnd = wcschr(ptr + 2, L'\\');
|
|
|
|
|
if (serverEnd == NULL)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
wchar_t* shareEnd = wcschr(serverEnd + 1, L'\\');
|
|
|
|
|
if (shareEnd == NULL)
|
|
|
|
|
return true; // no parent subfolder exists before file name
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
*shareEnd = saved;
|
|
|
|
|
if (hRoot == INVALID_HANDLE_VALUE)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
closeRoot = true;
|
|
|
|
|
ptr = shareEnd + 1; // first component under share
|
|
|
|
|
}
|
|
|
|
|
// Rooted path on current drive: \foo\bar
|
|
|
|
|
else if (ptr[0] == L'\\')
|
|
|
|
|
{
|
2026-03-19 15:16:50 +08:00
|
|
|
UNICODE_STRING* dosPath = &ffGetPeb()->ProcessParameters->CurrentDirectory.DosPath;
|
2026-03-11 16:19:49 +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,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
if (hRoot == INVALID_HANDLE_VALUE)
|
|
|
|
|
return false;
|
|
|
|
|
closeRoot = true;
|
|
|
|
|
ptr++; // skip leading '\'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (true)
|
2023-01-28 12:40:12 +01:00
|
|
|
{
|
2026-03-11 16:19:49 +08:00
|
|
|
wchar_t* token = wcschr(ptr, L'\\');
|
|
|
|
|
if (token == NULL)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// Skip empty path segments caused by duplicated '\'
|
|
|
|
|
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,
|
|
|
|
|
.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,
|
|
|
|
|
0
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!NT_SUCCESS(status))
|
|
|
|
|
{
|
|
|
|
|
if (closeRoot && hRoot != INVALID_HANDLE_VALUE)
|
2026-03-13 10:58:38 +08:00
|
|
|
NtClose(hRoot);
|
2026-03-11 16:19:49 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (closeRoot && hRoot != INVALID_HANDLE_VALUE)
|
2026-03-13 10:58:38 +08:00
|
|
|
NtClose(hRoot);
|
2026-03-11 16:19:49 +08:00
|
|
|
hRoot = hNew;
|
|
|
|
|
closeRoot = true;
|
|
|
|
|
|
|
|
|
|
ptr = token + 1;
|
2023-01-28 12:40:12 +01:00
|
|
|
}
|
2026-03-11 16:19:49 +08:00
|
|
|
|
|
|
|
|
if (closeRoot && hRoot != INVALID_HANDLE_VALUE)
|
2026-03-13 10:58:38 +08:00
|
|
|
NtClose(hRoot);
|
2026-03-11 16:19:49 +08:00
|
|
|
|
|
|
|
|
return true;
|
2023-01-28 12:40:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data)
|
|
|
|
|
{
|
2026-03-11 16:19:49 +08:00
|
|
|
wchar_t fileNameW[MAX_PATH];
|
|
|
|
|
ULONG len = 0;
|
|
|
|
|
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, fileName, (ULONG)strlen(fileName) + 1)))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
for (ULONG i = 0; i < len / sizeof(wchar_t); ++i)
|
|
|
|
|
{
|
|
|
|
|
if (fileNameW[i] == L'/')
|
|
|
|
|
fileNameW[i] = L'\\';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HANDLE FF_AUTO_CLOSE_FD handle = CreateFileW(fileNameW, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
2023-12-18 21:00:19 +08:00
|
|
|
if (handle == INVALID_HANDLE_VALUE)
|
2023-01-28 12:40:12 +01:00
|
|
|
{
|
2023-12-18 21:00:19 +08:00
|
|
|
if (GetLastError() == ERROR_PATH_NOT_FOUND)
|
|
|
|
|
{
|
2026-03-11 16:19:49 +08:00
|
|
|
if (!createSubfolders(fileNameW))
|
|
|
|
|
return false;
|
|
|
|
|
handle = CreateFileW(fileNameW, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
2023-12-18 21:00:19 +08:00
|
|
|
if (handle == INVALID_HANDLE_VALUE)
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
else
|
2023-01-28 12:40:12 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DWORD written;
|
2023-02-23 13:58:39 +08:00
|
|
|
return !!WriteFile(handle, data, (DWORD)dataSize, &written, NULL);
|
2023-01-28 12:40:12 +01:00
|
|
|
}
|
|
|
|
|
|
2023-12-22 16:04:56 +09:00
|
|
|
static inline void readWithLength(HANDLE handle, FFstrbuf* buffer, uint32_t length)
|
2023-01-28 12:40:12 +01:00
|
|
|
{
|
2024-10-11 23:44:49 +08:00
|
|
|
ffStrbufEnsureFree(buffer, length);
|
2023-11-18 15:26:07 +01:00
|
|
|
DWORD bytesRead = 0;
|
2023-12-22 16:04:56 +09:00
|
|
|
while(
|
|
|
|
|
length > 0 &&
|
|
|
|
|
ReadFile(handle, buffer->chars + buffer->length, length, &bytesRead, NULL) != FALSE &&
|
|
|
|
|
bytesRead > 0
|
|
|
|
|
) {
|
|
|
|
|
buffer->length += (uint32_t) bytesRead;
|
|
|
|
|
length -= (uint32_t) bytesRead;
|
2023-12-03 10:52:39 +09:00
|
|
|
}
|
2023-12-22 16:04:56 +09:00
|
|
|
}
|
2023-01-28 12:40:12 +01:00
|
|
|
|
2023-12-22 16:04:56 +09:00
|
|
|
static inline void readUntilEOF(HANDLE handle, FFstrbuf* buffer)
|
|
|
|
|
{
|
|
|
|
|
ffStrbufEnsureFree(buffer, 31);
|
|
|
|
|
uint32_t available = ffStrbufGetFree(buffer);
|
|
|
|
|
DWORD bytesRead = 0;
|
2023-01-28 12:40:12 +01:00
|
|
|
while(
|
2023-12-22 16:04:56 +09:00
|
|
|
ReadFile(handle, buffer->chars + buffer->length, available, &bytesRead, NULL) != FALSE &&
|
2023-11-18 15:26:07 +01:00
|
|
|
bytesRead > 0
|
2023-01-28 12:40:12 +01:00
|
|
|
) {
|
2023-11-18 15:26:07 +01:00
|
|
|
buffer->length += (uint32_t) bytesRead;
|
2023-12-22 16:04:56 +09: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.
|
2023-12-22 16:04:56 +09:00
|
|
|
available = ffStrbufGetFree(buffer);
|
2023-01-28 12:40:12 +01:00
|
|
|
}
|
2023-12-22 16:04:56 +09:00
|
|
|
}
|
2023-01-28 12:40:12 +01:00
|
|
|
|
2023-12-22 16:04:56 +09:00
|
|
|
bool ffAppendFDBuffer(HANDLE handle, FFstrbuf* buffer)
|
|
|
|
|
{
|
2026-03-11 16:19:49 +08:00
|
|
|
FILE_STANDARD_INFORMATION fileInfo;
|
|
|
|
|
IO_STATUS_BLOCK iosb;
|
|
|
|
|
if(!NT_SUCCESS(NtQueryInformationFile(handle, &iosb, &fileInfo, sizeof(fileInfo), FileStandardInformation)))
|
|
|
|
|
fileInfo.EndOfFile.QuadPart = 0;
|
2023-12-22 16:04:56 +09:00
|
|
|
|
2026-03-11 16:19:49 +08:00
|
|
|
if (fileInfo.EndOfFile.QuadPart > 0)
|
|
|
|
|
readWithLength(handle, buffer, (uint32_t)fileInfo.EndOfFile.QuadPart);
|
2023-12-22 16:04:56 +09:00
|
|
|
else
|
|
|
|
|
readUntilEOF(handle, buffer);
|
2023-01-28 12:40:12 +01:00
|
|
|
|
2023-12-22 16:04:56 +09:00
|
|
|
buffer->chars[buffer->length] = '\0';
|
2023-01-28 12:40:12 +01:00
|
|
|
|
2023-12-22 16:04:56 +09:00
|
|
|
return buffer->length > 0;
|
2023-01-28 12:40:12 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-09 14:37:57 +08:00
|
|
|
HANDLE openatW(HANDLE dfd, const wchar_t* fileName, uint16_t fileNameLen, bool directory)
|
2024-10-11 23:44:49 +08:00
|
|
|
{
|
2025-12-09 14:37:57 +08:00
|
|
|
assert(fileNameLen <= 0x7FFF);
|
|
|
|
|
|
|
|
|
|
HANDLE hFile;
|
2024-10-11 23:44:49 +08:00
|
|
|
IO_STATUS_BLOCK iosb = {};
|
2025-12-09 14:37:57 +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,
|
|
|
|
|
},
|
|
|
|
|
&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;
|
|
|
|
|
|
|
|
|
|
return hFile;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 10:41:10 +08:00
|
|
|
HANDLE openat(HANDLE dfd, const char* fileName, int oflag)
|
2025-12-09 14:37:57 +08:00
|
|
|
{
|
|
|
|
|
wchar_t fileNameW[MAX_PATH];
|
2026-03-11 14:55:09 +08:00
|
|
|
ULONG len;
|
|
|
|
|
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, fileName, (ULONG)strlen(fileName) + 1)))
|
|
|
|
|
return INVALID_HANDLE_VALUE;
|
2025-12-17 09:38:06 +08:00
|
|
|
// Implies `fileNameW[len] = L'\0';` and `len` includes the null terminator
|
2026-03-11 14:55:09 +08:00
|
|
|
len /= sizeof(wchar_t); // convert from bytes to characters
|
2025-12-09 14:37:57 +08:00
|
|
|
|
2026-03-11 14:55:09 +08:00
|
|
|
for (uint32_t i = 0; i < len - 1; ++i)
|
2025-12-09 14:37:57 +08:00
|
|
|
{
|
|
|
|
|
if (fileNameW[i] == L'/')
|
|
|
|
|
fileNameW[i] = L'\\';
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 10:41:10 +08:00
|
|
|
return openatW(dfd, fileNameW, (uint16_t)(len - 1), !!(oflag & O_DIRECTORY));
|
2024-10-11 23:44:49 +08:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 10:18:16 +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);
|
|
|
|
|
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(pathInW, (ULONG) sizeof(pathInW), &len, in, len)))
|
|
|
|
|
return false;
|
|
|
|
|
len /= sizeof(wchar_t); // convert from bytes to characters
|
|
|
|
|
|
|
|
|
|
size_t outLen; // in characters, including null terminator
|
|
|
|
|
if (!NT_SUCCESS(RtlExpandEnvironmentStrings(NULL, pathInW, len, pathOutW, ARRAY_SIZE(pathOutW), &outLen)))
|
|
|
|
|
return false;
|
2023-08-13 10:18:16 +08:00
|
|
|
|
2026-03-11 23:28:41 +08:00
|
|
|
ffStrbufSetNWS(out, (uint32_t) outLen - 1, pathOutW);
|
2023-08-13 10:18:16 +08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-11 12:34:57 +08:00
|
|
|
bool ffSuppressIO(bool suppress)
|
2023-01-28 12:40:12 +01:00
|
|
|
{
|
2025-03-23 21:56:32 +08:00
|
|
|
#ifndef NDEBUG
|
|
|
|
|
if (instance.config.display.debugMode)
|
|
|
|
|
return false;
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-06-26 13:50:42 +08:00
|
|
|
static bool init = false;
|
|
|
|
|
static HANDLE hOrigOut = INVALID_HANDLE_VALUE;
|
|
|
|
|
static HANDLE hOrigErr = INVALID_HANDLE_VALUE;
|
2025-07-18 10:39:11 +08:00
|
|
|
HANDLE hNullFile = ffGetNullFD();
|
2024-06-26 13:50:42 +08:00
|
|
|
static int fOrigOut = -1;
|
|
|
|
|
static int fOrigErr = -1;
|
|
|
|
|
static int fNullFile = -1;
|
|
|
|
|
|
|
|
|
|
if (!init)
|
|
|
|
|
{
|
|
|
|
|
if(!suppress)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
if (hNullFile == INVALID_HANDLE_VALUE || fNullFile == -1)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
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 12:40:12 +01:00
|
|
|
}
|
2023-01-28 17:19:45 +01:00
|
|
|
|
2023-11-13 16:41:10 +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;
|
|
|
|
|
|
2023-11-13 16:41:10 +08:00
|
|
|
if(pretty && folderName != NULL)
|
|
|
|
|
{
|
|
|
|
|
for(uint8_t i = 0; i < indentation - 1; i++)
|
|
|
|
|
fputs(" | ", stdout);
|
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, '*');
|
2023-01-28 17:19:45 +01:00
|
|
|
if(hFind == INVALID_HANDLE_VALUE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
|
|
|
{
|
2023-06-19 16:07:23 +08:00
|
|
|
if(ffStrEquals(entry.cFileName, ".") || ffStrEquals(entry.cFileName, ".."))
|
2023-01-28 17:19:45 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-13 16:41:10 +08:00
|
|
|
if (pretty)
|
|
|
|
|
{
|
|
|
|
|
for(uint8_t i = 0; i < indentation; i++)
|
|
|
|
|
fputs(" | ", stdout);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fputs(folder->chars + baseLength, stdout);
|
|
|
|
|
}
|
2023-01-28 17:19:45 +01:00
|
|
|
|
|
|
|
|
puts(entry.cFileName);
|
|
|
|
|
} while (FindNextFileA(hFind, &entry));
|
|
|
|
|
FindClose(hFind);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-13 16:41:10 +08:00
|
|
|
void ffListFilesRecursively(const char* path, bool pretty)
|
2023-01-28 17:19:45 +01:00
|
|
|
{
|
2023-04-15 15:29:54 +08:00
|
|
|
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
|
|
|
|
2024-09-10 14:48:47 +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);
|
2024-06-26 10:50:25 +08:00
|
|
|
FF_AUTO_CLOSE_FD HANDLE hConin = INVALID_HANDLE_VALUE;
|
|
|
|
|
DWORD inputMode;
|
|
|
|
|
if (!GetConsoleMode(hInput, &inputMode))
|
|
|
|
|
{
|
|
|
|
|
hConin = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, NULL);
|
|
|
|
|
hInput = hConin;
|
|
|
|
|
}
|
2023-07-26 10:27:40 +08:00
|
|
|
SetConsoleMode(hInput, 0);
|
|
|
|
|
|
|
|
|
|
FlushConsoleInputBuffer(hInput);
|
|
|
|
|
|
2024-06-26 10:50:25 +08:00
|
|
|
{
|
|
|
|
|
DWORD bytes = 0;
|
|
|
|
|
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
|
FF_AUTO_CLOSE_FD HANDLE hConout = INVALID_HANDLE_VALUE;
|
|
|
|
|
DWORD outputMode;
|
|
|
|
|
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
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
2026-03-27 14:37:41 +08:00
|
|
|
if (NtWaitForSingleObject(hInput, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 }) != STATUS_WAIT_0)
|
2023-07-26 10:27:40 +08:00
|
|
|
{
|
2024-06-26 10:50:25 +08:00
|
|
|
SetConsoleMode(hInput, inputMode);
|
2026-03-14 23:08:25 +08:00
|
|
|
return "NtWaitForSingleObject() failed or timeout";
|
2023-07-26 10:27:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ignore all unexpected input events
|
|
|
|
|
INPUT_RECORD record;
|
|
|
|
|
DWORD len = 0;
|
|
|
|
|
if (!PeekConsoleInputW(hInput, &record, 1, &len))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
record.EventType == KEY_EVENT &&
|
|
|
|
|
record.Event.KeyEvent.uChar.UnicodeChar != L'\r' &&
|
|
|
|
|
record.Event.KeyEvent.uChar.UnicodeChar != L'\n'
|
|
|
|
|
)
|
|
|
|
|
break;
|
|
|
|
|
else
|
|
|
|
|
ReadConsoleInputW(hInput, &record, 1, &len);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-10 14:48:47 +08:00
|
|
|
va_list args;
|
|
|
|
|
va_start(args, format);
|
2023-07-26 10:27:40 +08:00
|
|
|
|
2024-09-10 14:48:47 +08:00
|
|
|
char buffer[1024];
|
|
|
|
|
uint32_t bytesRead = 0;
|
2023-07-26 10:27:40 +08:00
|
|
|
|
2024-09-10 14:48:47 +08:00
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
DWORD bytes = 0;
|
|
|
|
|
if (!ReadFile(hInput, buffer, sizeof(buffer) - 1, &bytes, NULL) || bytes == 0)
|
2024-10-04 16:57:12 +08:00
|
|
|
{
|
|
|
|
|
va_end(args);
|
2024-09-10 14:48:47 +08:00
|
|
|
return "ReadFile() failed";
|
2024-10-04 16:57:12 +08:00
|
|
|
}
|
2023-07-26 10:27:40 +08:00
|
|
|
|
2024-09-10 14:48:47 +08:00
|
|
|
bytesRead += bytes;
|
|
|
|
|
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);
|
|
|
|
|
|
2024-09-10 14:48:47 +08:00
|
|
|
if (ret <= 0)
|
2024-10-04 16:57:12 +08:00
|
|
|
{
|
|
|
|
|
va_end(args);
|
2024-09-10 14:48:47 +08:00
|
|
|
return "vsscanf(buffer, format, args) failed";
|
2024-10-04 16:57:12 +08:00
|
|
|
}
|
2024-09-10 14:48:47 +08:00
|
|
|
if (ret >= nParams)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetConsoleMode(hInput, inputMode);
|
2023-07-26 10:27:40 +08:00
|
|
|
|
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2025-07-18 10:39:11 +08:00
|
|
|
|
|
|
|
|
FFNativeFD ffGetNullFD(void)
|
|
|
|
|
{
|
|
|
|
|
static FFNativeFD hNullFile = INVALID_HANDLE_VALUE;
|
|
|
|
|
if (hNullFile != INVALID_HANDLE_VALUE)
|
|
|
|
|
return hNullFile;
|
|
|
|
|
hNullFile = CreateFileW(
|
|
|
|
|
L"NUL",
|
|
|
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
|
|
|
FILE_SHARE_WRITE,
|
|
|
|
|
0,
|
|
|
|
|
OPEN_EXISTING,
|
|
|
|
|
0,
|
|
|
|
|
&(SECURITY_ATTRIBUTES){
|
|
|
|
|
.nLength = sizeof(SECURITY_ATTRIBUTES),
|
|
|
|
|
.lpSecurityDescriptor = NULL,
|
|
|
|
|
.bInheritHandle = TRUE,
|
|
|
|
|
});
|
|
|
|
|
return hNullFile;
|
|
|
|
|
}
|
2025-10-22 13:51:36 +08:00
|
|
|
|
|
|
|
|
bool ffRemoveFile(const char* fileName)
|
|
|
|
|
{
|
|
|
|
|
return DeleteFileA(fileName) != FALSE;
|
|
|
|
|
}
|