Reimplement kernel32 time functions, fix HeapReAlloc, debug log improvements

This commit is contained in:
Luke Street 2025-10-01 00:59:11 -06:00
parent 02d26c7bb9
commit ccd79a256a
13 changed files with 996 additions and 241 deletions

View File

@ -163,6 +163,17 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/test/test_overlapped_io.c
${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h)
add_custom_command(
OUTPUT ${WIBO_TEST_BIN_DIR}/test_time.exe
COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2
-I${CMAKE_CURRENT_SOURCE_DIR}/test
-o test_time.exe
${CMAKE_CURRENT_SOURCE_DIR}/test/test_time.c
WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR}
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/test/test_time.c
${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h)
add_custom_target(wibo_test_fixtures
DEPENDS
${WIBO_TEST_BIN_DIR}/external_exports.dll
@ -171,7 +182,8 @@ if(BUILD_TESTING)
${WIBO_TEST_BIN_DIR}/test_resources.exe
${WIBO_TEST_BIN_DIR}/test_threading.exe
${WIBO_TEST_BIN_DIR}/test_heap.exe
${WIBO_TEST_BIN_DIR}/test_overlapped_io.exe)
${WIBO_TEST_BIN_DIR}/test_overlapped_io.exe
${WIBO_TEST_BIN_DIR}/test_time.exe)
if(CMAKE_CONFIGURATION_TYPES)
set(_wibo_fixture_build_command
@ -218,6 +230,12 @@ if(BUILD_TESTING)
set_tests_properties(wibo.test_overlapped_io PROPERTIES
WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR}
DEPENDS wibo.build_fixtures)
add_test(NAME wibo.test_time
COMMAND $<TARGET_FILE:wibo> ${WIBO_TEST_BIN_DIR}/test_time.exe)
set_tests_properties(wibo.test_time PROPERTIES
WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR}
DEPENDS wibo.build_fixtures)
endif()
endif()
endif()

View File

@ -59,6 +59,7 @@ typedef UCHAR *PUCHAR;
typedef size_t SIZE_T;
typedef SIZE_T *PSIZE_T;
typedef unsigned char BYTE;
typedef unsigned int UINT;
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
@ -87,6 +88,10 @@ typedef struct _OVERLAPPED {
#define MAX_PATH (260)
#define STD_INPUT_HANDLE ((DWORD) - 10)
#define STD_OUTPUT_HANDLE ((DWORD) - 11)
#define STD_ERROR_HANDLE ((DWORD) - 12)
namespace wibo {
extern uint32_t lastError;
extern char **argv;

File diff suppressed because it is too large Load Diff

View File

@ -769,12 +769,12 @@ namespace msvcrt {
}
char* WIN_ENTRY strcat(char *dest, const char *src) {
VERBOSE_LOG("strcat(%s, %s)\n", dest, src);
VERBOSE_LOG("strcat(%p, %s)\n", dest, src);
return std::strcat(dest, src);
}
char* WIN_ENTRY strcpy(char *dest, const char *src) {
VERBOSE_LOG("strcpy(%s, %s)\n", dest, src);
VERBOSE_LOG("strcpy(%p, %s)\n", dest, src);
return std::strcpy(dest, src);
}
@ -838,7 +838,7 @@ namespace msvcrt {
}
void WIN_ENTRY _mbccpy(unsigned char *dest, const unsigned char *src) {
DEBUG_LOG("_mbccpy(%s, %s)\n", dest, src);
DEBUG_LOG("_mbccpy(%p, %s)\n", dest, src);
if (!dest || !src) {
return;
}
@ -1234,7 +1234,7 @@ namespace msvcrt {
}
unsigned long WIN_ENTRY _ultoa(unsigned long value, char *str, int radix) {
DEBUG_LOG("_ultoa(%lu, %s, %d)\n", value, str ? str : "(null)", radix);
DEBUG_LOG("_ultoa(%lu, %p, %d)\n", value, str, radix);
if (!str || radix < 2 || radix > 36) {
errno = EINVAL;
return 0;
@ -1255,7 +1255,7 @@ namespace msvcrt {
}
char* WIN_ENTRY _ltoa(long value, char *str, int radix) {
DEBUG_LOG("_ltoa(%ld, %s, %d)\n", value, str ? str : "(null)", radix);
DEBUG_LOG("_ltoa(%ld, %p, %d)\n", value, str, radix);
if (!str || radix < 2 || radix > 36) {
errno = EINVAL;
return nullptr;
@ -2239,24 +2239,17 @@ namespace msvcrt {
return 0;
}
int WIN_ENTRY _get_wpgmptr(uint16_t** pValue){
int WIN_ENTRY _get_wpgmptr(uint16_t **pValue) {
DEBUG_LOG("_get_wpgmptr(%p)\n", pValue);
if(!pValue) return 22;
char exe_path[PATH_MAX];
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
if(len == -1){
return 2;
if (!pValue) {
return 22;
}
exe_path[len] = 0;
std::string exePathStr(exe_path);
if (_pgmptr) {
free(_pgmptr);
if (_wpgmptr) {
*pValue = _wpgmptr;
return 0;
}
_pgmptr = ::strdup(exePathStr.c_str());
std::vector<uint16_t> wStr = stringToWideString(exePathStr.c_str());
const auto wStr = stringToWideString(wibo::guestExecutablePath.c_str());
if (_wpgmptr) {
delete[] _wpgmptr;
}
@ -2269,6 +2262,8 @@ namespace msvcrt {
}
char** WIN_ENTRY __p__pgmptr() {
DEBUG_LOG("__p__pgmptr()\n");
_pgmptr = const_cast<char *>(wibo::guestExecutablePath.c_str());
return &_pgmptr;
}
@ -2688,7 +2683,6 @@ namespace msvcrt {
DEBUG_LOG("_wspawnvp(%d, %s)\n", mode, command.c_str());
std::vector<std::string> argStorage;
argStorage.emplace_back(command);
for (const uint16_t *const *cursor = argv; *cursor; ++cursor) {
argStorage.emplace_back(wideStringToString(*cursor));
}
@ -2746,7 +2740,6 @@ namespace msvcrt {
DEBUG_LOG("_spawnvp(%d, %s)\n", mode, command.c_str());
std::vector<std::string> argStorage;
argStorage.emplace_back(command);
for (const char * const *cursor = argv; *cursor; ++cursor) {
argStorage.emplace_back(*cursor);
}

View File

@ -2,7 +2,8 @@
namespace ole32 {
int WIN_FUNC CoInitialize(void *pvReserved) {
DEBUG_LOG("CoInitialize(...)\n");
DEBUG_LOG("STUB: CoInitialize(%p)\n", pvReserved);
(void) pvReserved;
return 0; // S_OK
}
@ -20,7 +21,7 @@ namespace ole32 {
const GUID *riid,
void **ppv
) {
DEBUG_LOG("CoCreateInstance 0x%x %p %d 0x%x %p\n", rclsid->Data1, pUnkOuter, dwClsContext, riid->Data1, *ppv);
DEBUG_LOG("STUB: CoCreateInstance(0x%x, %p, %d, 0x%x, %p)\n", rclsid->Data1, pUnkOuter, dwClsContext, riid->Data1, *ppv);
*ppv = 0;
// E_POINTER is returned when ppv is NULL, which isn't true here, but returning 1 results
// in a segfault with mwcceppc.exe when it's told to include directories that don't exist

View File

@ -4,7 +4,7 @@
namespace psapi {
BOOL WIN_FUNC EnumProcessModules(HANDLE hProcess, HMODULE *lphModule, DWORD cb, DWORD *lpcbNeeded) {
DEBUG_LOG("EnumProcessModules(hProcess=%p, cb=%u)\n", hProcess, cb);
DEBUG_LOG("EnumProcessModules(%p, %p, %u, %p)\n", hProcess, lphModule, cb, lpcbNeeded);
bool recognizedHandle = false;
if (hProcess == (HANDLE)0xFFFFFFFF) {

View File

@ -206,7 +206,7 @@ static bool loadVersionResource(const char *fileName, std::vector<uint8_t> &buff
namespace version {
unsigned int WIN_FUNC GetFileVersionInfoSizeA(const char *lptstrFilename, unsigned int *lpdwHandle) {
DEBUG_LOG("GetFileVersionInfoSizeA %s\n", lptstrFilename);
DEBUG_LOG("GetFileVersionInfoSizeA(%s, %p)\n", lptstrFilename, lpdwHandle);
if (lpdwHandle)
*lpdwHandle = 0;
@ -218,7 +218,7 @@ unsigned int WIN_FUNC GetFileVersionInfoSizeA(const char *lptstrFilename, unsign
unsigned int WIN_FUNC GetFileVersionInfoA(const char *lptstrFilename, unsigned int dwHandle, unsigned int dwLen, void *lpData) {
(void) dwHandle;
DEBUG_LOG("GetFileVersionInfoA %s len=%u\n", lptstrFilename, dwLen);
DEBUG_LOG("GetFileVersionInfoA(%s, %u, %p)\n", lptstrFilename, dwLen, lpData);
if (!lpData || dwLen == 0) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return 0;
@ -245,7 +245,7 @@ static unsigned int VerQueryValueImpl(const void *pBlock, const std::string &sub
if (!pBlock)
return 0;
const uint8_t *base = static_cast<const uint8_t *>(pBlock);
const auto *base = static_cast<const uint8_t *>(pBlock);
uint16_t totalLength = readU16(base);
if (totalLength < 6)
return 0;
@ -279,18 +279,20 @@ static unsigned int VerQueryValueImpl(const void *pBlock, const std::string &sub
}
unsigned int WIN_FUNC VerQueryValueA(const void *pBlock, const char *lpSubBlock, void **lplpBuffer, unsigned int *puLen) {
DEBUG_LOG("VerQueryValueA %p %s\n", pBlock, lpSubBlock ? lpSubBlock : "(null)");
DEBUG_LOG("VerQueryValueA(%p, %s, %p, %p)\n", pBlock, lpSubBlock ? lpSubBlock : "(null)", lplpBuffer, puLen);
if (!lpSubBlock)
return 0;
return VerQueryValueImpl(pBlock, lpSubBlock, lplpBuffer, puLen);
}
unsigned int WIN_FUNC GetFileVersionInfoSizeW(const uint16_t *lptstrFilename, unsigned int *lpdwHandle) {
DEBUG_LOG("GetFileVersionInfoSizeW -> ");
auto narrow = wideStringToString(lptstrFilename);
return GetFileVersionInfoSizeA(narrow.c_str(), lpdwHandle);
}
unsigned int WIN_FUNC GetFileVersionInfoW(const uint16_t *lptstrFilename, unsigned int dwHandle, unsigned int dwLen, void *lpData) {
DEBUG_LOG("GetFileVersionInfoW -> ");
auto narrow = wideStringToString(lptstrFilename);
return GetFileVersionInfoA(narrow.c_str(), dwHandle, dwLen, lpData);
}
@ -299,7 +301,7 @@ unsigned int WIN_FUNC VerQueryValueW(const void *pBlock, const uint16_t *lpSubBl
if (!lpSubBlock)
return 0;
auto narrow = wideStringToString(lpSubBlock);
DEBUG_LOG("VerQueryValueW %p %s\n", pBlock, narrow.c_str());
DEBUG_LOG("VerQueryValueW(%p, %s, %p, %p)\n", pBlock, narrow.c_str(), lplpBuffer, puLen);
return VerQueryValueImpl(pBlock, narrow, lplpBuffer, puLen);
}

View File

@ -123,22 +123,18 @@ namespace files {
FileHandle *fileHandleFromHandle(void *handle) {
handles::Data data = handles::dataFromHandle(handle, false);
if (data.type != handles::TYPE_FILE) {
return nullptr;
if (data.type == handles::TYPE_FILE) {
return reinterpret_cast<FileHandle *>(data.ptr);
}
return reinterpret_cast<FileHandle *>(data.ptr);
return nullptr;
}
FILE *fpFromHandle(void *handle, bool pop) {
handles::Data data = handles::dataFromHandle(handle, pop);
if (data.type == handles::TYPE_FILE) {
return reinterpret_cast<FileHandle *>(data.ptr)->fp;
} else if (data.type == handles::TYPE_UNUSED && pop) {
return nullptr;
} else {
printf("Invalid file handle %p\n", handle);
assert(0);
}
return nullptr;
}
void *allocFpHandle(FILE *fp, unsigned int desiredAccess, unsigned int shareMode, unsigned int flags, bool closeOnDestroy) {
@ -300,32 +296,32 @@ namespace files {
return result;
}
void *getStdHandle(uint32_t nStdHandle) {
HANDLE getStdHandle(DWORD nStdHandle) {
switch (nStdHandle) {
case ((uint32_t) -10): // STD_INPUT_HANDLE
return stdinHandle;
case ((uint32_t) -11): // STD_OUTPUT_HANDLE
return stdoutHandle;
case ((uint32_t) -12): // STD_ERROR_HANDLE
return stderrHandle;
default:
return (void *) 0xFFFFFFFF;
case STD_INPUT_HANDLE:
return stdinHandle;
case STD_OUTPUT_HANDLE:
return stdoutHandle;
case STD_ERROR_HANDLE:
return stderrHandle;
default:
return (void *)0xFFFFFFFF;
}
}
unsigned int setStdHandle(uint32_t nStdHandle, void *hHandle) {
BOOL setStdHandle(DWORD nStdHandle, HANDLE hHandle) {
switch (nStdHandle) {
case ((uint32_t) -10): // STD_INPUT_HANDLE
stdinHandle = hHandle;
break;
case ((uint32_t) -11): // STD_OUTPUT_HANDLE
stdoutHandle = hHandle;
break;
case ((uint32_t) -12): // STD_ERROR_HANDLE
stderrHandle = hHandle;
break;
default:
return 0; // fail
case STD_INPUT_HANDLE:
stdinHandle = hHandle;
break;
case STD_OUTPUT_HANDLE:
stdoutHandle = hHandle;
break;
case STD_ERROR_HANDLE:
stderrHandle = hHandle;
break;
default:
return 0; // fail
}
return 1; // success
}

View File

@ -1,5 +1,7 @@
#pragma once
#include "common.h"
#include <cstdio>
#include <filesystem>
#include <mutex>
@ -31,8 +33,8 @@ namespace files {
FileHandle *fileHandleFromHandle(void *handle);
IOResult read(FileHandle *handle, void *buffer, size_t bytesToRead, const std::optional<uint64_t> &offset, bool updateFilePointer);
IOResult write(FileHandle *handle, const void *buffer, size_t bytesToWrite, const std::optional<uint64_t> &offset, bool updateFilePointer);
void *getStdHandle(uint32_t nStdHandle);
unsigned int setStdHandle(uint32_t nStdHandle, void *hHandle);
HANDLE getStdHandle(DWORD nStdHandle);
BOOL setStdHandle(DWORD nStdHandle, HANDLE hHandle);
void init();
std::optional<std::filesystem::path> findCaseInsensitiveFile(const std::filesystem::path &directory, const std::string &filename);
std::filesystem::path canonicalPath(const std::filesystem::path &path);

View File

@ -3,24 +3,21 @@
#include <utility>
namespace handles {
static Data datas[0x10000];
static Data datas[MAX_HANDLES];
Data dataFromHandle(void *handle, bool pop) {
uintptr_t index = (uintptr_t)handle;
if (index > 0 && index < 0x10000) {
if (index > 0 && index < MAX_HANDLES) {
Data ret = datas[index];
if (pop)
datas[index] = Data{};
return ret;
}
if (pop)
return Data{};
printf("Invalid file handle %p\n", handle);
assert(0);
return Data{};
}
void *allocDataHandle(Data data) {
for (int i = 1; i < 0x10000; i++) {
for (size_t i = 1; i < MAX_HANDLES; i++) {
if (datas[i].type == TYPE_UNUSED) {
datas[i] = data;
return (void*)i;

View File

@ -3,6 +3,8 @@
#include <cstdlib>
namespace handles {
constexpr size_t MAX_HANDLES = 0x10000;
enum Type {
TYPE_UNUSED,
TYPE_FILE,

View File

@ -348,23 +348,32 @@ int main(int argc, char **argv) {
}
// Build guest arguments
if (guestArgs.empty()) {
int argIndex = -1;
bool skipProgramName = false;
if (programIndex != -1 && argc > programIndex + 1) {
argIndex = programIndex + 1;
// With "test.exe -- test 1 2 3", treat everything after -- as the full command line
if (strcmp(argv[argIndex], "--") == 0) {
argIndex++;
skipProgramName = true;
}
}
if (guestArgs.empty() && !skipProgramName) {
guestArgs.push_back(files::pathToWindows(resolvedGuestPath));
}
for (int i = programIndex + 1; i < argc; ++i) {
guestArgs.emplace_back(argv[i]);
if (argIndex != -1) {
for (int i = argIndex; i < argc; ++i) {
guestArgs.emplace_back(argv[i]);
}
}
// Build a command line
if (cmdLine.empty()) {
for (int i = 0; i < guestArgs.size(); ++i) {
std::string arg;
if (i == 0) {
arg = files::pathToWindows(resolvedGuestPath);
} else {
if (i != 0) {
cmdLine += ' ';
arg = guestArgs[i];
}
const std::string& arg = guestArgs[i];
bool needQuotes = arg.find_first_of("\" \t\n") != std::string::npos;
if (needQuotes)
cmdLine += '"';

194
test/test_time.c Normal file
View File

@ -0,0 +1,194 @@
#include <windows.h>
#include <stdint.h>
#include <stdio.h>
#include "test_assert.h"
static uint64_t filetime_to_u64(const FILETIME *ft) {
ULARGE_INTEGER li;
li.LowPart = ft->dwLowDateTime;
li.HighPart = ft->dwHighDateTime;
return li.QuadPart;
}
static FILETIME u64_to_filetime(uint64_t value) {
ULARGE_INTEGER li;
li.QuadPart = value;
FILETIME ft;
ft.dwLowDateTime = li.LowPart;
ft.dwHighDateTime = li.HighPart;
return ft;
}
static uint64_t abs_u64_diff(uint64_t a, uint64_t b) {
return (a > b) ? (a - b) : (b - a);
}
static void test_systemtime_roundtrip(void) {
SYSTEMTIME st = {
.wYear = 2023,
.wMonth = 7,
.wDay = 15,
.wHour = 12,
.wMinute = 34,
.wSecond = 56,
.wMilliseconds = 789
};
FILETIME ft;
TEST_CHECK(SystemTimeToFileTime(&st, &ft));
SYSTEMTIME converted = {0};
TEST_CHECK(FileTimeToSystemTime(&ft, &converted));
TEST_CHECK_EQ(st.wYear, converted.wYear);
TEST_CHECK_EQ(st.wMonth, converted.wMonth);
TEST_CHECK_EQ(st.wDay, converted.wDay);
TEST_CHECK_EQ(st.wHour, converted.wHour);
TEST_CHECK_EQ(st.wMinute, converted.wMinute);
TEST_CHECK_EQ(st.wSecond, converted.wSecond);
TEST_CHECK_EQ(st.wMilliseconds, converted.wMilliseconds);
SetLastError(0);
SYSTEMTIME invalid = st;
invalid.wMonth = 13;
TEST_CHECK(!SystemTimeToFileTime(&invalid, &ft));
TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError());
}
static void test_filetime_known_timestamp(void) {
/* 2023-01-01 00:00:00 UTC */
const uint64_t expected_ticks = 133170048000000000ULL;
FILETIME ft = u64_to_filetime(expected_ticks);
SYSTEMTIME st = {0};
TEST_CHECK(FileTimeToSystemTime(&ft, &st));
TEST_CHECK_EQ(2023, st.wYear);
TEST_CHECK_EQ(1, st.wMonth);
TEST_CHECK_EQ(1, st.wDay);
TEST_CHECK_EQ(0, st.wHour);
TEST_CHECK_EQ(0, st.wMinute);
TEST_CHECK_EQ(0, st.wSecond);
TEST_CHECK_EQ(0, st.wMilliseconds);
FILETIME back;
TEST_CHECK(SystemTimeToFileTime(&st, &back));
TEST_CHECK_U64_EQ(expected_ticks, filetime_to_u64(&back));
}
static void test_getsystemtimeasfiletime(void) {
FILETIME from_api;
GetSystemTimeAsFileTime(&from_api);
SYSTEMTIME sys_now;
GetSystemTime(&sys_now);
FILETIME from_system;
TEST_CHECK(SystemTimeToFileTime(&sys_now, &from_system));
uint64_t delta = abs_u64_diff(filetime_to_u64(&from_api), filetime_to_u64(&from_system));
/* allow 1 second of skew between calls */
TEST_CHECK_MSG(delta < 10000000ULL, "GetSystemTimeAsFileTime skew too large: %llu", (unsigned long long)delta);
}
static void test_gettickcount_progresses(void) {
DWORD start = GetTickCount();
Sleep(60);
DWORD end = GetTickCount();
DWORD diff = end - start;
TEST_CHECK_MSG(diff >= 40, "GetTickCount diff too small: %lu", (unsigned long)diff);
TEST_CHECK_MSG(diff <= 5000, "GetTickCount diff too large: %lu", (unsigned long)diff);
}
static void test_setfiletime_roundtrip(void) {
char temp_path[MAX_PATH];
char temp_file[MAX_PATH];
DWORD path_len = GetTempPathA(sizeof(temp_path), temp_path);
TEST_CHECK(path_len > 0 && path_len < sizeof(temp_path));
UINT unique = GetTempFileNameA(temp_path, "TST", 0, temp_file);
TEST_CHECK(unique != 0);
HANDLE handle = CreateFileA(temp_file, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
TEST_CHECK(handle != INVALID_HANDLE_VALUE);
FILETIME original_creation = {0}, original_access = {0}, original_write = {0};
TEST_CHECK(GetFileTime(handle, &original_creation, &original_access, &original_write));
SYSTEMTIME desired = {
.wYear = 2022,
.wMonth = 12,
.wDay = 31,
.wHour = 5,
.wMinute = 45,
.wSecond = 12,
.wMilliseconds = 123
};
FILETIME desired_ft;
TEST_CHECK(SystemTimeToFileTime(&desired, &desired_ft));
TEST_CHECK(SetFileTime(handle, NULL, NULL, &desired_ft));
FILETIME updated_creation = {0}, updated_access = {0}, updated_write = {0};
TEST_CHECK(GetFileTime(handle, &updated_creation, &updated_access, &updated_write));
TEST_CHECK_U64_EQ(filetime_to_u64(&desired_ft), filetime_to_u64(&updated_write));
FILETIME zero = {0, 0};
TEST_CHECK(SetFileTime(handle, NULL, &zero, NULL));
FILETIME final_creation = {0}, final_access = {0}, final_write = {0};
TEST_CHECK(GetFileTime(handle, &final_creation, &final_access, &final_write));
TEST_CHECK_U64_EQ(filetime_to_u64(&updated_access), filetime_to_u64(&final_access));
TEST_CHECK_U64_EQ(filetime_to_u64(&updated_write), filetime_to_u64(&final_write));
CloseHandle(handle);
DeleteFileA(temp_file);
}
static void test_local_filetime_conversions(void) {
/* Choose a time likely to be affected by DST in many zones */
SYSTEMTIME utc_time = {
.wYear = 2021,
.wMonth = 6,
.wDay = 15,
.wHour = 18,
.wMinute = 0,
.wSecond = 0,
.wMilliseconds = 0
};
FILETIME utc_ft;
TEST_CHECK(SystemTimeToFileTime(&utc_time, &utc_ft));
FILETIME local_ft;
TEST_CHECK(FileTimeToLocalFileTime(&utc_ft, &local_ft));
FILETIME roundtrip;
TEST_CHECK(LocalFileTimeToFileTime(&local_ft, &roundtrip));
TEST_CHECK_U64_EQ(filetime_to_u64(&utc_ft), filetime_to_u64(&roundtrip));
/* Local filetime should convert back to the original system time when interpreted locally */
SYSTEMTIME local_st = {0};
TEST_CHECK(FileTimeToSystemTime(&local_ft, &local_st));
SYSTEMTIME utc_from_roundtrip = {0};
TEST_CHECK(FileTimeToSystemTime(&roundtrip, &utc_from_roundtrip));
TEST_CHECK_EQ(utc_time.wYear, utc_from_roundtrip.wYear);
TEST_CHECK_EQ(utc_time.wMonth, utc_from_roundtrip.wMonth);
TEST_CHECK_EQ(utc_time.wDay, utc_from_roundtrip.wDay);
TEST_CHECK_EQ(utc_time.wHour, utc_from_roundtrip.wHour);
TEST_CHECK_EQ(utc_time.wMinute, utc_from_roundtrip.wMinute);
TEST_CHECK_EQ(utc_time.wSecond, utc_from_roundtrip.wSecond);
}
int main(void) {
test_systemtime_roundtrip();
test_filetime_known_timestamp();
test_getsystemtimeasfiletime();
test_gettickcount_progresses();
test_setfiletime_roundtrip();
test_local_filetime_conversions();
return 0;
}