646 lines
17 KiB
C
646 lines
17 KiB
C
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
|