W32_Ctx W32 = Zi; //////////////////////////////////////////////////////////// //~ Win32 embedded data BOOL W32_FindEmbeddedRcData(HMODULE module, LPCWSTR type, LPWSTR wstr_entry_name, LONG_PTR udata) { W32_FindEmbeddedDataCtx *ctx = (W32_FindEmbeddedDataCtx *)udata; TempArena scratch = BeginScratchNoConflict(); String entry_name = StringFromWstrNoLimit(scratch.arena, (LPWSTR)wstr_entry_name); String embedded_data_prefix = Lit(Stringize(W32_EmbeddedDataPrefix)); if (StringBeginsWith(entry_name, embedded_data_prefix)) { HRSRC hres = FindResourceW(module, wstr_entry_name, type); if (hres) { HGLOBAL hg = LoadResource(module, hres); if (hg) { if (ctx->embedded_strings_count < countof(ctx->embedded_strings)) { String embedded = Zi; embedded.len = SizeofResource(module, hres); embedded.text = LockResource(hg); ctx->embedded_strings[ctx->embedded_strings_count++] = embedded; } else { Panic(Lit("Maximum number of embedded resource entries exceeded")); } } } } EndScratch(scratch); return 1; } //////////////////////////////////////////////////////////// //~ @hookimpl Core api StringList GetRawCommandline(void) { return W32.raw_command_line; } void Echo(String msg) { HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE); if (console_handle && console_handle != INVALID_HANDLE_VALUE) { WriteFile(console_handle, msg.text, msg.len, 0, 0); } else if (IsRunningInDebugger()) { char msg_cstr[16384]; u64 len = MinU64(countof(msg_cstr) - 1, msg.len); CopyBytes(msg_cstr, msg.text, len); msg_cstr[len] = 0; OutputDebugStringA(msg_cstr); } } b32 Panic(String msg) { LogPanic(msg); char msg_cstr[4096]; CstrFromStringToBuff(StringFromFixedArray(msg_cstr), msg); { u32 mb_flags = MB_SETFOREGROUND | MB_ICONERROR; MessageBoxExA(0, msg_cstr, "Fatal error", mb_flags, 0); } HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE); if (console_handle != INVALID_HANDLE_VALUE) { WriteFile(console_handle, msg.text, msg.len, 0, 0); } if (IsRunningInDebugger()) { Assert(0); } else { ExitProcess(1); } return 0; } Callstack CaptureCallstack(u64 skip_frames) { Callstack result; result.count = CaptureStackBackTrace( 1 + skip_frames, countof(result.frames), result.frames, 0 // BackTraceHash ); return result; } b32 IsRunningInDebugger(void) { return IsDebuggerPresent(); } b32 IsRunningInWine(void) { return W32.wine_version.len > 0; } i64 TimeNs(void) { LARGE_INTEGER qpc; QueryPerformanceCounter(&qpc); i64 result = (qpc.QuadPart - W32.timer_start_qpc) * W32.ns_per_qpc; return result; } void TrueRand(String buffer) { BCryptGenRandom(BCRYPT_RNG_ALG_HANDLE, buffer.text, buffer.len, 0); } CpuTopologyInfo GetCpuTopologyInfo(void) { TempArena scratch = BeginScratchNoConflict(); CpuTopologyInfo res = Zi; { DWORD infos_buff_size = 0; u8 *infos_buff = 0; b32 ok = 0; { GetLogicalProcessorInformationEx(RelationProcessorCore, 0, &infos_buff_size); infos_buff = PushStructsNoZero(scratch.arena, u8, infos_buff_size); ok = GetLogicalProcessorInformationEx(RelationProcessorCore, (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *)infos_buff, &infos_buff_size); } if (ok) { // Determine max efficiency class i32 max_efficiency_class = 0; { DWORD pos = 0; while (pos < infos_buff_size) { SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *info = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *)&infos_buff[pos]; max_efficiency_class = MaxI32(max_efficiency_class, info->Processor.EfficiencyClass); pos += info->Size; } } // Generate physical core info { DWORD pos = 0; while (pos < infos_buff_size) { SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *info = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *)&infos_buff[pos]; ++res.num_physical_cores; ++res.num_logical_cores; if (info->Processor.Flags & LTP_PC_SMT) { // Core has SMT sibling ++res.num_logical_cores; } if (info->Processor.EfficiencyClass == max_efficiency_class) { // Core is P-core ++res.num_physical_performance_cores; } else { // Core is not a P-core ++res.num_physical_non_performance_cores; } pos += info->Size; } } } } res.num_logical_cores = MaxI32(res.num_logical_cores, 1); res.num_physical_cores = MaxI32(res.num_physical_cores, 1); res.num_physical_performance_cores = MaxI32(res.num_physical_performance_cores, 1); EndScratch(scratch); return res; } void SleepSeconds(f64 seconds) { f64 ms = seconds * 1000.0; if (ms > 4000000000) { Sleep(INFINITE); } else { Sleep((u32)ms); } } String GetAppDirectory(void) { return W32.appdir_path; } //////////////////////////////////////////////////////////// //~ @hookimpl Swap b32 IsSwappedIn(void) { return IsHotSwappingEnabled; } b32 IsSwappingOut(void) { return IsHotSwappingEnabled; } String SwappedStateFromName(Arena *arena, String name) { TempArena scratch = BeginScratch(arena); String result = Zi; String path = StringF(scratch.arena, "%F/swap/%F", FmtString(GetAppDirectory()), FmtString(name)); wchar_t *path_wstr = WstrFromString(scratch.arena, path); HANDLE handle = CreateFileW(path_wstr, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (handle != INVALID_HANDLE_VALUE) { u32 chunk_size = Kibi(64); result.text = ArenaNext(arena, u8); for (;;) { u8 *chunk = PushStructsNoZero(arena, u8, chunk_size); u32 chunk_bytes_read = 0; ReadFile(handle, chunk, chunk_size, (LPDWORD)&chunk_bytes_read, 0); result.len += chunk_bytes_read; if (chunk_bytes_read < chunk_size) { PopStructsNoCopy(arena, u8, chunk_size - chunk_bytes_read); break; } } } CloseHandle(handle); EndScratch(scratch); return result; } void WriteSwappedState(String name, String data) { TempArena scratch = BeginScratchNoConflict(); String dir_path = PathFromString(scratch.arena, StringF(scratch.arena, "%F/swap", FmtString(GetAppDirectory())), '\\'); String path = PathFromString(scratch.arena, StringF(scratch.arena, "%F/%F", FmtString(dir_path), FmtString(name)), '\\'); wchar_t *dir_path_wstr = WstrFromString(scratch.arena, dir_path); wchar_t *path_wstr = WstrFromString(scratch.arena, path); SHCreateDirectoryExW(0, dir_path_wstr, 0); HANDLE handle = CreateFileW(path_wstr, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (handle != INVALID_HANDLE_VALUE) { SetFilePointer(handle, 0, 0, FILE_BEGIN); SetEndOfFile(handle); WriteFile(handle, data.text, data.len, 0, 0); } CloseHandle(handle); EndScratch(scratch); } //////////////////////////////////////////////////////////// //~ @hookimpl Exit void OnExit(ExitFunc *func) { i32 index = Atomic32FetchAdd(&W32.num_exit_funcs, 1); if (index >= countof(W32.exit_funcs)) { Panic(Lit("Maximum on exit functions registered")); } W32.exit_funcs[index] = func; } void SignalExit(i32 code) { Atomic32Set(&W32.exit_code, code); SetEvent(W32.exit_event); } void ExitNow(i32 code) { ExitProcess(code); } //////////////////////////////////////////////////////////// //~ Log void W32_BootstrapLogs(void) { Arena *perm = PermArena(); W32.logs_arena = AcquireArena(Gibi(64)); W32.log_msgs_arena = AcquireArena(Gibi(64)); W32.readable_log_events = ArenaNext(W32.logs_arena, LogEvent); String logfile_path = StringF(perm, "%F/log.log", FmtString(GetAppDirectory())); wchar_t *path_wstr = WstrFromString(perm, logfile_path); W32.logfile = CreateFileW( path_wstr, FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 ); Atomic32Set(&W32.logs_initialized, 1); LogInfoF("Log file path: %F", FmtString(logfile_path)); } void W32_Log(i32 level, String msg) { TempArena scratch = BeginScratchNoConflict(); if (Atomic32Fetch(&W32.logs_initialized)) { LogLevelSettings settings = log_settings[level]; if (level < 0 || level >= LogLevel_COUNT) { Panic(Lit("Invalid log level")); } DateTime datetime = LocalDateTime(); i64 now_ns = TimeNs(); i32 thread_id = GetCurrentThreadId(); // TODO: Log asynchronously //- Log message to file { String shorthand = settings.shorthand; String msg_formatted = StringF( scratch.arena, "[%F:%F:%F.%F] <%F> [%F] %F\n", // Time FmtUint(datetime.hour, .z = 2), FmtUint(datetime.minute, .z = 2), FmtUint(datetime.second, .z = 2), FmtUint(datetime.milliseconds, .z = 3), // Thread id FmtUint(thread_id, .z = 5), // Level FmtString(shorthand), // Message FmtString(msg) ); WriteFile(W32.logfile, msg_formatted.text, msg_formatted.len, 0, 0); } //- Log message to queue LockTicketMutex(&W32.logs_tm); { // Get staged data LogEvent *ev = PushStruct(W32.logs_arena, LogEvent); ev->msg = PushString(W32.log_msgs_arena, msg); ev->datetime = datetime; ev->time_ns = now_ns; ev->level = level; ev->thread_id = thread_id; ev->id = W32.logs_count++; ev->level_id = W32.log_level_counts[level]++; Atomic64Set(&W32.readable_logs_count, W32.logs_count); } UnlockTicketMutex(&W32.logs_tm); } EndScratch(scratch); } //////////////////////////////////////////////////////////// //~ @hookimpl Log // Panic log function is separate to enforce zero side effects other than // immediately writing to log file. void LogPanic(String msg) { if (Atomic32Fetch(&W32.logs_initialized)) { String beg = Lit("******** PANICKING ********\n"); String end = Lit("\n***************************\n"); WriteFile(W32.logfile, beg.text, beg.len, 0, 0); WriteFile(W32.logfile, msg.text, msg.len, 0, 0); WriteFile(W32.logfile, end.text, end.len, 0, 0); } } void Log_(i32 level, String msg) { W32_Log(level, msg); } void LogF_(i32 level, String fmt, ...) { if (Atomic32Fetch(&W32.logs_initialized)) { TempArena scratch = BeginScratchNoConflict(); va_list args; va_start(args, fmt); { String msg = FormatString(scratch.arena, fmt, FmtArgsFromVaList(scratch.arena, args)); W32_Log(level, msg); } va_end(args); EndScratch(scratch); } } LogEventsArray GetLogEvents(void) { LogEventsArray result = Zi; result.count = Atomic64Fetch(&W32.readable_logs_count); if (result.count > 0) { result.logs = W32.readable_log_events; } return result; } //////////////////////////////////////////////////////////// //~ Main i32 W32_Main(void) { // Init time { LARGE_INTEGER qpf; QueryPerformanceFrequency(&qpf); W32.ns_per_qpc = 1000000000 / qpf.QuadPart; } { LARGE_INTEGER qpc; QueryPerformanceCounter(&qpc); W32.timer_start_qpc = qpc.QuadPart; } // Setup events W32.panic_event = CreateEventW(0, 1, 0, 0); W32.exit_event = CreateEventW(0, 1, 0, 0); W32.main_thread_id = GetCurrentThreadId(); SetThreadDescription(GetCurrentThread(), L"Main thread"); // Query system info GetSystemInfo(&W32.info); // Init main thread W32_InitCurrentThread(Lit("Main")); // Get raw args from command line Arena *perm = PermArena(); String cmdline_str = Zi; { StringList args_list = Zi; { wchar_t *cmdline_wstr = GetCommandLineW(); cmdline_str = StringFromWstrNoLimit(perm, cmdline_wstr); i32 argc = 0; LPWSTR *argv = CommandLineToArgvW(cmdline_wstr, &argc); for (i32 i = 0; i < argc; ++i) { wchar_t *arg_wstr = argv[i]; String arg = StringFromWstrNoLimit(perm, arg_wstr); PushStringToList(perm, &args_list, arg); } } W32.raw_command_line = args_list; } // Detect wine { HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); if (ntdll) { typedef char *W32_WineVersionFunc(void); W32_WineVersionFunc *wine_version_func = (W32_WineVersionFunc *)GetProcAddress(ntdll, "wine_get_version"); if (wine_version_func) { char *wine_version_cstr = wine_version_func(); if (wine_version_cstr) { W32.wine_version = PushString(perm, StringFromCstrNoLimit(wine_version_cstr)); } } } } ////////////////////////////// //- Bootstrap // Bootstrap tweak vars BootstrapTweakVars(); // Bootstrap command line BootstrapCmdline(); // Init app directory String appdir_error = Zi; { String appdir_path = Zi; CommandlineArg appdir_arg = CommandlineArgFromName(Lit("appdir")); CommandlineArg appname_arg = CommandlineArgFromName(Lit("appname")); if (appdir_arg.exists && appdir_arg.value.len > 0) { appdir_path = appdir_arg.value; } else { String appname = Lit(Stringize(DefaultAppName)); if (appname_arg.exists && appname_arg.value.len > 0) { appname = appname_arg.value; } wchar_t *path_wstr = 0; HRESULT hr = SHGetKnownFolderPath(&FOLDERID_LocalAppData, 0, 0, &path_wstr); if (!SUCCEEDED(hr)) { Panic(Lit("Failed to locate AppData directory")); } appdir_path = StringFromWstrNoLimit(perm, path_wstr); CoTaskMemFree(path_wstr); appdir_path = PathFromString(perm, StringF(perm, "%F\\Cabin\\%F\\", FmtString(appdir_path), FmtString(appname)), '/'); } // Create app dir { String path = PathFromString(perm, appdir_path, '\\'); wchar_t *path_wstr = WstrFromString(perm, path); i32 err_code = SHCreateDirectoryExW(0, path_wstr, 0); String err = StringF(perm, "Error code %F", FmtSint(err_code)); switch (err_code) { default: break; case ERROR_BAD_PATHNAME: { err = Lit("Bad path name"); } break; case ERROR_FILENAME_EXCED_RANGE: { err = Lit("Path name too long"); } break; case ERROR_CANCELLED: { err = Lit("User canceled the operation"); } break; case ERROR_PATH_NOT_FOUND: { err = Lit("The system cannot find the path specified."); } break; } if (err_code != ERROR_SUCCESS && err_code != ERROR_ALREADY_EXISTS && err_code != ERROR_FILE_EXISTS) { appdir_error = StringF( perm, "Failed to initialize app directory at \"%F\": %F", FmtString(path), FmtString(err) ); wchar_t *msg_wstr = WstrFromString(perm, appdir_error); MessageBoxExW(0, msg_wstr, L"Warning", MB_ICONWARNING | MB_SETFOREGROUND | MB_TOPMOST, 0); } } W32.appdir_path = appdir_path; } // Bootstrap log system W32_BootstrapLogs(); LogInfoF("Command line: [%F]", FmtString(cmdline_str)); if (W32.wine_version.len > 0) { LogInfoF("Wine version: \"%F\"", FmtString(W32.wine_version)); } if (appdir_error.len > 0) { LogError(appdir_error); } // Bootstrap resource system { W32_FindEmbeddedDataCtx ctx = Zi; EnumResourceNamesW(0, RT_RCDATA, &W32_FindEmbeddedRcData, (LONG_PTR)&ctx); BootstrapResources(ctx.embedded_strings_count, ctx.embedded_strings); } // Bootstrap async BootstrapAsync(); // Bootstrap layers if (!Atomic32Fetch(&W32.panicking)) { BootstrapLayers(); } ////////////////////////////// //- Wait for exit signal // Wait for exit start or panic if (!Atomic32Fetch(&W32.panicking)) { HANDLE handles[] = { W32.exit_event, W32.panic_event, }; DWORD wake = WaitForMultipleObjects(countof(handles), handles, 0, INFINITE); } ////////////////////////////// //- Shutdown // Run exit callbacks if (!Atomic32Fetch(&W32.panicking)) { i32 num_funcs = Atomic32Fetch(&W32.num_exit_funcs); for (i32 idx = num_funcs - 1; idx >= 0; --idx) { ExitFunc *func = W32.exit_funcs[idx]; func(); } } // Exit if (Atomic32Fetch(&W32.panicking)) { WaitForSingleObject(W32.panic_event, INFINITE); MessageBoxExW(0, W32.panic_wstr, L"Fatal error", MB_ICONSTOP | MB_SETFOREGROUND | MB_TOPMOST, 0); Atomic32FetchTestSet(&W32.exit_code, 0, 1); } return Atomic32Fetch(&W32.exit_code); } //////////////////////////////////////////////////////////// //~ CRT main #if IsConsoleApp int main(char **argc, int argv) { return W32_Main(); } #else int CALLBACK wWinMain(HINSTANCE instance, HINSTANCE prev_instance, LPWSTR cmdline_wstr, int show_code) { return W32_Main(); } #endif