SDL3: Now works on Windows as well

Refactored the pseudo-custom SDL_main code a bit: SDL_win32_main.c
is now only used for SDL1.2, SDL2 and SDL3 have a WinMain() function
in win_main.cpp that works pretty much like the SDL2 SDL_main or SDL3
SDL_RunApp() code - except that the argv[] strings passed to the Doom3
main() function (now renamed to SDL_main()) are encoded in ANSI instead
of UTF-8, so paths passed as commandline arguments, like
 dhewm3 +set fs_basepath C:\SüperGämes\Doom3
work with the Win32 ANSI function used by Doom3 to handle paths and files.

For this I also moved the stdout/stderr redirection code from
SDL_win32_main.c to win_main.cpp and cleaned it up a bit
This commit is contained in:
Daniel Gibson 2024-10-09 17:34:59 +02:00
parent 19f28e3c2d
commit 6181f24c44
4 changed files with 250 additions and 159 deletions

View file

@ -1181,9 +1181,13 @@ elseif(WIN32)
sys/win32/win_net.cpp
sys/win32/win_shared.cpp
sys/win32/win_syscon.cpp
sys/win32/SDL_win32_main.c
)
if(NOT SDL2 AND NOT SDL3)
# only SDL1.2 still uses SDL_win32_main.c
set(src_sys_base ${src_sys_base} sys/win32/SDL_win32_main.c)
endif()
# adding the few relevant headers in sys/ manually..
set(src_sys_base ${src_sys_base}
sys/platform.h

View file

@ -47,7 +47,11 @@ If you have questions concerning this license or the applicable additional terms
#if defined(_WIN32) && defined(ID_ALLOW_TOOLS)
#include "sys/win32/win_local.h"
#include <SDL_syswm.h>
// SDL3 doesn't have SDL_syswm.h
#if ! SDL_VERSION_ATLEAST(3, 0, 0)
#include <SDL_syswm.h>
#endif
// from SDL_windowsopengl.h (internal SDL2 header)
#ifndef WGL_ARB_pixel_format
@ -632,6 +636,13 @@ try_again:
// then we know we are win32 and we have to include this
// config to get the editors to work.
#if SDL_VERSION_ATLEAST(3, 0, 0)
HWND hwnd = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
HDC hdc = (HDC)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HDC_POINTER, NULL);
if ( hwnd && hdc ) {
win32.hWnd = hwnd;
win32.hDC = hdc;
#else // SDL2
// Get the HWND for later use.
SDL_SysWMinfo sdlinfo;
SDL_version sdlver;
@ -640,6 +651,7 @@ try_again:
if (SDL_GetWindowWMInfo(window, &sdlinfo) && sdlinfo.subsystem == SDL_SYSWM_WINDOWS) {
win32.hWnd = sdlinfo.info.win.window;
win32.hDC = sdlinfo.info.win.hdc;
#endif
// NOTE: hInstance is set in main()
win32.hGLRC = qwglGetCurrentContext();

View file

@ -160,154 +160,6 @@ static void cleanup(void)
SDL_Quit();
}
/* Remove the output files if there was no output written */
static void cleanup_output(void) {
FILE *file;
int empty;
/* Flush the output in case anything is queued */
fclose(stdout);
fclose(stderr);
/* Without redirection we're done */
if (!stdioRedirectEnabled) {
return;
}
/* See if the files have any output in them */
if ( stdoutPath[0] ) {
file = fopen(stdoutPath, TEXT("rb"));
if ( file ) {
empty = (fgetc(file) == EOF) ? 1 : 0;
fclose(file);
if ( empty ) {
remove(stdoutPath);
}
}
}
if ( stderrPath[0] ) {
file = fopen(stderrPath, TEXT("rb"));
if ( file ) {
empty = (fgetc(file) == EOF) ? 1 : 0;
fclose(file);
if ( empty ) {
remove(stderrPath);
}
}
}
}
extern int Win_GetHomeDir(char *dst, size_t size);
/* Redirect the output (stdout and stderr) to a file */
static void redirect_output(void)
{
#ifdef _WIN32_WCE
wchar_t path[MAX_PATH];
#error "adapt homedir code for wchar_t!"
#else
char path[MAX_PATH];
struct _stat st;
/* DG: use "My Documents/My Games/dhewm3" to write stdout.txt and stderr.txt
* instead of the binary, which might not be writable */
Win_GetHomeDir(path, sizeof(path));
if (_stat(path, &st) == -1) {
/* oops, "My Documents/My Games/dhewm3" doesn't exist - does My Games/ at least exist? */
char myGamesPath[MAX_PATH];
char* lastslash;
memcpy(myGamesPath, path, MAX_PATH);
lastslash = strrchr(myGamesPath, '/');
if (lastslash != NULL) {
*lastslash = '\0';
}
if (_stat(myGamesPath, &st) == -1) {
/* if My Documents/My Games/ doesn't exist, create it */
_mkdir(myGamesPath);
}
_mkdir(path); /* create My Documents/My Games/dhewm3/ */
}
#endif
FILE *newfp;
#if 0 /* DG: don't do this anymore. */
DWORD pathlen;
pathlen = GetModuleFileName(NULL, path, SDL_arraysize(path));
while ( pathlen > 0 && path[pathlen] != '\\' ) {
--pathlen;
}
path[pathlen] = '\0';
#endif
#ifdef _WIN32_WCE
wcsncpy( stdoutPath, path, SDL_arraysize(stdoutPath) );
wcsncat( stdoutPath, DIR_SEPERATOR STDOUT_FILE, SDL_arraysize(stdoutPath) );
#else
SDL_strlcpy( stdoutPath, path, SDL_arraysize(stdoutPath) );
SDL_strlcat( stdoutPath, DIR_SEPERATOR STDOUT_FILE, SDL_arraysize(stdoutPath) );
#endif
{ /* DG: rename old stdout log */
#ifdef _WIN32_WCE
wchar_t stdoutPathBK[MAX_PATH];
wcsncpy( stdoutPathBK, path, SDL_arraysize(stdoutPath) );
wcsncat( stdoutPathBK, DIR_SEPERATOR TEXT("dhewm3log-old.txt"), SDL_arraysize(stdoutPath) );
_wrename( stdoutPath, stdoutpathBK );
#else
char stdoutPathBK[MAX_PATH];
SDL_strlcpy( stdoutPathBK, path, SDL_arraysize(stdoutPath) );
SDL_strlcat( stdoutPathBK, DIR_SEPERATOR TEXT("dhewm3log-old.txt"), SDL_arraysize(stdoutPath) );
rename( stdoutPath, stdoutPathBK );
#endif
} /* DG end */
/* Redirect standard input and standard output */
newfp = freopen(stdoutPath, TEXT("w"), stdout);
#ifndef _WIN32_WCE
if ( newfp == NULL ) { /* This happens on NT */
#if !defined(stdout)
stdout = fopen(stdoutPath, TEXT("w"));
#else
newfp = fopen(stdoutPath, TEXT("w"));
if ( newfp ) {
*stdout = *newfp;
}
#endif
}
#endif /* _WIN32_WCE */
#ifdef _WIN32_WCE
wcsncpy( stderrPath, path, SDL_arraysize(stdoutPath) );
wcsncat( stderrPath, DIR_SEPERATOR STDOUT_FILE, SDL_arraysize(stdoutPath) );
#else
SDL_strlcpy( stderrPath, path, SDL_arraysize(stderrPath) );
SDL_strlcat( stderrPath, DIR_SEPERATOR STDERR_FILE, SDL_arraysize(stderrPath) );
#endif
newfp = freopen(stderrPath, TEXT("w"), stderr);
#ifndef _WIN32_WCE
if ( newfp == NULL ) { /* This happens on NT */
#if !defined(stderr)
stderr = fopen(stderrPath, TEXT("w"));
#else
newfp = fopen(stderrPath, TEXT("w"));
if ( newfp ) {
*stderr = *newfp;
}
#endif
}
#endif /* _WIN32_WCE */
setvbuf(stdout, NULL, _IOLBF, BUFSIZ); /* Line buffered */
setbuf(stderr, NULL); /* No buffering */
stdioRedirectEnabled = 1;
}
#if defined(_MSC_VER) && !defined(_WIN32_WCE)
/* The VC++ compiler needs main defined */
#define console_main main
@ -397,6 +249,7 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
FreeLibrary(handle);
}
#if 0 // DG: output redirection is now done in dhewm3's main() aka SDL_main()
/* Check for stdio redirect settings and do the redirection */
if ((env_str = SDL_getenv("SDL_STDIO_REDIRECT"))) {
if (SDL_atoi(env_str)) {
@ -408,6 +261,7 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
redirect_output();
}
#endif
#endif // 0
#ifdef _WIN32_WCE
nLen = wcslen(szCmdLine)+128+1;

View file

@ -53,7 +53,15 @@ If you have questions concerning this license or the applicable additional terms
#include "tools/edit_public.h"
#include <SDL_main.h>
#include "sys/sys_sdl.h"
#ifdef D3_SDL3
#define SDL_MAIN_HANDLED // dhewm3 implements WinMain() itself
#include <SDL3/SDL_main.h>
#else // SDL1.2 or SDL2
#include <SDL_main.h>
#endif
idCVar Win32Vars_t::win_outputDebugString( "win_outputDebugString", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
idCVar Win32Vars_t::win_outputEditString( "win_outputEditString", "1", CVAR_SYSTEM | CVAR_BOOL, "" );
@ -1001,14 +1009,154 @@ int Win_ChoosePixelFormat(HDC hdc)
}
#endif
// stdout/stderr redirection, originally from SDL_win32_main.c
/* The standard output files */
#define STDOUT_FILE TEXT("dhewm3log.txt") /* DG: renamed this */
#define STDERR_FILE TEXT("stderr.txt")
/* Set a variable to tell if the stdio redirect has been enabled. */
static int stdioRedirectEnabled = 0;
static char stdoutPath[MAX_PATH];
static char stderrPath[MAX_PATH];
#define DIR_SEPERATOR TEXT("/")
/* Remove the output files if there was no output written */
static void cleanup_output(void) {
FILE *file;
int empty;
/* Flush the output in case anything is queued */
fclose(stdout);
fclose(stderr);
/* Without redirection we're done */
if (!stdioRedirectEnabled) {
return;
}
/* See if the files have any output in them */
if ( stdoutPath[0] ) {
file = fopen(stdoutPath, TEXT("rb"));
if ( file ) {
empty = (fgetc(file) == EOF) ? 1 : 0;
fclose(file);
if ( empty ) {
remove(stdoutPath);
}
}
}
if ( stderrPath[0] ) {
file = fopen(stderrPath, TEXT("rb"));
if ( file ) {
empty = (fgetc(file) == EOF) ? 1 : 0;
fclose(file);
if ( empty ) {
remove(stderrPath);
}
}
}
}
/* Redirect the output (stdout and stderr) to a file */
static void redirect_output(void)
{
char path[MAX_PATH];
struct _stat st;
/* DG: use "My Documents/My Games/dhewm3" to write stdout.txt and stderr.txt
* instead of the binary, which might not be writable */
Win_GetHomeDir(path, sizeof(path));
if (_stat(path, &st) == -1) {
/* oops, "My Documents/My Games/dhewm3" doesn't exist - does My Games/ at least exist? */
char myGamesPath[MAX_PATH];
char* lastslash;
memcpy(myGamesPath, path, MAX_PATH);
lastslash = strrchr(myGamesPath, '/');
if (lastslash != NULL) {
*lastslash = '\0';
}
if (_stat(myGamesPath, &st) == -1) {
/* if My Documents/My Games/ doesn't exist, create it */
_mkdir(myGamesPath);
}
_mkdir(path); /* create My Documents/My Games/dhewm3/ */
}
FILE *newfp;
#if 0 /* DG: don't do this anymore. */
DWORD pathlen;
pathlen = GetModuleFileName(NULL, path, SDL_arraysize(path));
while ( pathlen > 0 && path[pathlen] != '\\' ) {
--pathlen;
}
path[pathlen] = '\0';
#endif
SDL_strlcpy( stdoutPath, path, SDL_arraysize(stdoutPath) );
SDL_strlcat( stdoutPath, DIR_SEPERATOR STDOUT_FILE, SDL_arraysize(stdoutPath) );
{ /* DG: rename old stdout log */
char stdoutPathBK[MAX_PATH];
SDL_strlcpy( stdoutPathBK, path, SDL_arraysize(stdoutPath) );
SDL_strlcat( stdoutPathBK, DIR_SEPERATOR TEXT("dhewm3log-old.txt"), SDL_arraysize(stdoutPath) );
rename( stdoutPath, stdoutPathBK );
} /* DG end */
/* Redirect standard input and standard output */
newfp = freopen(stdoutPath, TEXT("w"), stdout);
if ( newfp == NULL ) { /* This happens on NT */
#if !defined(stdout)
stdout = fopen(stdoutPath, TEXT("w"));
#else
newfp = fopen(stdoutPath, TEXT("w"));
if ( newfp ) {
*stdout = *newfp;
}
#endif
}
SDL_strlcpy( stderrPath, path, SDL_arraysize(stderrPath) );
SDL_strlcat( stderrPath, DIR_SEPERATOR STDERR_FILE, SDL_arraysize(stderrPath) );
newfp = freopen(stderrPath, TEXT("w"), stderr);
if ( newfp == NULL ) { /* This happens on NT */
#if !defined(stderr)
stderr = fopen(stderrPath, TEXT("w"));
#else
newfp = fopen(stderrPath, TEXT("w"));
if ( newfp ) {
*stderr = *newfp;
}
#endif
}
setvbuf(stdout, NULL, _IOLBF, BUFSIZ); /* Line buffered */
setbuf(stderr, NULL); /* No buffering */
stdioRedirectEnabled = 1;
}
// end of stdout/stderr redirection code from old SDL
/*
==================
WinMain
The pseudo-main function called from real main (either in SDL_win32_main.c or WinMain() below)
NOTE: Currently argv[] are ANSI strings, not UTF-8 strings as usual in SDL2 and SDL3!
==================
*/
int main(int argc, char *argv[]) {
// SDL_win32_main.c creates the dhewm3log.txt and redirects stdout into it
// so here we can log its (approx.) creation time before anything else is logged:
int SDL_main(int argc, char *argv[]) {
// as the very first thing, redirect stdout to dhewm3log.txt (and stderr to stderr.txt)
// so we can log
redirect_output();
atexit(cleanup_output);
// now that stdout is redirected to dhewm3log.txt,
// log its (approx.) creation time before anything else is logged:
{
time_t tt = time(NULL);
const struct tm* tms = localtime(&tt);
@ -1017,8 +1165,6 @@ int main(int argc, char *argv[]) {
printf("Opened this log at %s\n", timeStr);
}
const HCURSOR hcurSave = ::SetCursor( LoadCursor( 0, IDC_WAIT ) );
InitializeCriticalSection( &printfCritSect );
#ifdef ID_DEDICATED
@ -1065,8 +1211,6 @@ int main(int argc, char *argv[]) {
SetThreadAffinityMask( GetCurrentThread(), 1 );
#endif
// ::SetCursor( hcurSave ); // DG: I think SDL handles the cursor fine..
// Launch the script debugger
if ( strstr( GetCommandLine(), "+debugger" ) ) {
@ -1204,3 +1348,80 @@ void idSysLocal::StartProcess( const char *exePath, bool doexit ) {
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" );
}
}
// the actual WinMain(), based on SDL2_main and SDL3's SDL_main_impl.h + SDL_RunApp()
// but modified to pass ANSI strings to SDL_main() instead of UTF-8,
// because dhewm3 doesn't use Unicode internally (except for Dear ImGui,
// which doesn't use commandline arguments)
// for SDL1.2, SDL_win32_main.c is still used instead
#if SDL_VERSION_ATLEAST(2, 0, 0)
/* Pop up an out of memory message, returns to Windows */
static BOOL OutOfMemory(void)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Out of memory - aborting", NULL);
return -1;
}
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
{
(void)hInst;
(void)hPrev;
(void)szCmdLine;
(void)sw;
LPWSTR *argvw;
char **argv;
int i, argc, result;
argvw = CommandLineToArgvW(GetCommandLineW(), &argc);
if (!argvw) {
return OutOfMemory();
}
/* Note that we need to be careful about how we allocate/free memory here.
* If the application calls SDL_SetMemoryFunctions(), we can't rely on
* SDL_free() to use the same allocator after SDL_main() returns.
*/
/* Parse it into argv and argc */
argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv));
if (!argv) {
return OutOfMemory();
}
for (i = 0; i < argc; ++i) {
// NOTE: SDL2+ uses CP_UTF8 instead of CP_ACP here (and in the other call below)
// but Doom3 needs ANSI strings on Windows (so paths work with the Windows ANSI APIs)
const int ansiSize = WideCharToMultiByte(CP_ACP, 0, argvw[i], -1, NULL, 0, NULL, NULL);
if (!ansiSize) { // uhoh?
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL);
return -1;
}
argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ansiSize); // this size includes the null-terminator character.
if (!argv[i]) {
return OutOfMemory();
}
if (WideCharToMultiByte(CP_ACP, 0, argvw[i], -1, argv[i], ansiSize, NULL, NULL) == 0) { // failed? uhoh!
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL);
return -1;
}
}
argv[i] = NULL;
LocalFree(argvw);
SDL_SetMainReady();
// Run the application main() code
result = SDL_main(argc, argv);
// Free argv, to avoid memory leak
for (i = 0; i < argc; ++i) {
HeapFree(GetProcessHeap(), 0, argv[i]);
}
HeapFree(GetProcessHeap(), 0, argv);
return result;
}
#endif