fteqw/engine/client/sys_sdl.c
Shpoike ff1a2299f4 megacommit.
adds qtv relay support.
lots of other misc tweaks.
2024-07-14 19:58:26 +01:00

1471 lines
32 KiB
C

#include "quakedef.h"
#include <SDL.h>
#ifdef MULTITHREAD
#include <SDL_thread.h>
#endif
#include <SDL_loadso.h>
#ifndef WIN32
#include <fcntl.h>
#include <sys/stat.h>
#if defined(__unix__) || defined(__unix) ||defined(__HAIKU__) || (defined(__APPLE__) && defined(__MACH__)) //apple make everything painful.
#include <unistd.h>
#endif
#else
#include <direct.h>
#endif
#ifdef FTE_TARGET_WEB
#include <emscripten/emscripten.h>
#endif
#if SDL_MAJOR_VERSION >= 2
extern SDL_Window *sdlwindow;
#endif
#ifndef isDedicated
qboolean isDedicated;
#endif
void Sys_Error (const char *error, ...)
{
va_list argptr;
char string[1024];
va_start (argptr,error);
vsnprintf (string, sizeof (string), error, argptr);
va_end (argptr);
COM_WorkerAbort(string);
fprintf(stderr, "Error: %s\n", string);
Sys_Printf ("Quake Error: %s\n", string);
#if SDL_MAJOR_VERSION >= 2
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Sys_Error", string, sdlwindow);
#endif
if (COM_CheckParm("-crashonerror"))
*(int*)-3 = 0;
Host_Shutdown ();
exit (1);
}
void Sys_RecentServer(char *command, char *target, char *title, char *desc)
{
}
#if defined(__linux__) || defined(BSD)
qboolean Sys_RandomBytes(qbyte *string, int len)
{
qboolean res = false;
int fd = open("/dev/urandom", 0);
if (fd != -1)
{
res = (read(fd, string, len) == len);
close(fd);
}
return res;
}
#else
qboolean Sys_RandomBytes(qbyte *string, int len)
{
return false;
}
#endif
static void ApplyColour(unsigned int chrflags)
{
//on win32, SDL usually redirected stdout to a file (as it won't get printed anyway.
//win32 doesn't do ascii escapes, and text editors like to show the gibberish too, so just don't bother emitting any.
#ifndef _WIN32
static const int ansiremap[8] = {0, 4, 2, 6, 1, 5, 3, 7};
static int oldflags = CON_WHITEMASK;
int bg, fg;
if (oldflags == chrflags)
return;
oldflags = chrflags;
printf("\e[0;"); // reset
if (chrflags & CON_BLINKTEXT)
printf("5;"); // set blink
bg = (chrflags & CON_BGMASK) >> CON_BGSHIFT;
fg = (chrflags & CON_FGMASK) >> CON_FGSHIFT;
// don't handle intensive bit for background
// as terminals differ too much in displaying \e[1;7;3?m
bg &= 0x7;
if (chrflags & CON_NONCLEARBG)
{
if (fg & 0x8) // intensive bit set for foreground
{
printf("1;"); // set bold/intensity ansi flag
fg &= 0x7; // strip intensive bit
}
// set foreground and background colors
printf("3%i;4%im", ansiremap[fg], ansiremap[bg]);
}
else
{
switch(fg)
{
//to get around wierd defaults (like a white background) we have these special hacks for colours 0 and 7
case COLOR_BLACK:
printf("7m"); // set inverse
break;
case COLOR_GREY:
printf("1;30m"); // treat as dark grey
break;
case COLOR_WHITE:
printf("m"); // set nothing else
break;
default:
if (fg & 0x8) // intensive bit set for foreground
{
printf("1;"); // set bold/intensity ansi flag
fg &= 0x7; // strip intensive bit
}
printf("3%im", ansiremap[fg]); // set foreground
break;
}
}
#endif
}
//#include <wchar.h>
void Sys_Printf (char *fmt, ...)
{
va_list argptr;
char text[2048];
conchar_t ctext[2048];
conchar_t *c, *e;
wchar_t w;
unsigned int codeflags, codepoint;
// if (nostdout)
// return;
va_start (argptr,fmt);
vsnprintf (text,sizeof(text)-1, fmt,argptr);
va_end (argptr);
if (strlen(text) > sizeof(text))
Sys_Error("memory overwrite in Sys_Printf");
e = COM_ParseFunString(CON_WHITEMASK, text, ctext, sizeof(ctext), false);
for (c = ctext; c < e; )
{
c = Font_Decode(c, &codeflags, &codepoint);
if (codeflags & CON_HIDDEN)
continue;
if (codepoint == '\n' && (codeflags&CON_NONCLEARBG))
codeflags &= CON_WHITEMASK;
ApplyColour(codeflags);
w = codepoint;
if (w >= 0xe000 && w < 0xe100)
{
/*not all quake chars are ascii compatible, so map those control chars to safe ones so we don't mess up anyone's xterm*/
if ((w & 0x7f) > 0x20)
putc(w&0x7f, stdout);
else if (w & 0x80)
{
static char tab[32] = "---#@.@@@@ # >.." "[]0123456789.---";
putc(tab[w&31], stdout);
}
else
{
static char tab[32] = ".####.#### # >.." "[]0123456789.---";
putc(tab[w&31], stdout);
}
}
else if (w < ' ' && w != '\t' && w != '\r' && w != '\n')
putc('?', stdout); //don't let anyone print escape codes or other things that could crash an xterm.
else
{
/*putwc doesn't like me. force it in utf8*/
if (w >= 0x80)
{
if (w > 0x800)
{
putc(0xe0 | ((w>>12)&0x0f), stdout);
putc(0x80 | ((w>>6)&0x3f), stdout);
}
else
putc(0xc0 | ((w>>6)&0x1f), stdout);
putc(0x80 | (w&0x3f), stdout);
}
else
putc(w, stdout);
}
}
ApplyColour(CON_WHITEMASK);
fflush(stdout);
}
//#define QCLOCK(e,n,q,f,i) //q must have t=
#if SDL_VERSION_ATLEAST(2,0,18) //less wrappy... still terrible precision.
#define CLOCKDEF_SDL_TICKS QCLOCK(TICKS, "ticks", t=SDL_GetTicks64(), 1000,;)
#else
#define CLOCKDEF_SDL_TICKS QCLOCK(TICKS, "ticks", t=SDL_GetTicks(), 1000,;)
#endif
static quint64_t sdlperf_freq;
#define CLOCKDEF_SDL_PERF QCLOCK(PERF, "perf", t=SDL_GetPerformanceCounter(), sdlperf_freq,sdlperf_freq=SDL_GetPerformanceFrequency())
#if defined(CLOCK_MONOTONIC) && (_POSIX_C_SOURCE >= 200112L)
#define CLOCKDEF_LINUX_MONOTONIC QCLOCK(MONOTONIC, "monotonic", { \
struct timespec ts; \
clock_gettime(CLOCK_MONOTONIC, &ts); \
t = (ts.tv_sec*(quint64_t)1000000000) + ts.tv_nsec; \
}, 1000000000,;)
#else
#define CLOCKDEF_LINUX_MONOTONIC
#endif
#if defined(CLOCK_REALTIME) && (_POSIX_C_SOURCE >= 200112L)
#define CLOCKDEF_LINUX_REALTIME QCLOCK(REALTIME, "realtime", { \
struct timespec ts; \
clock_gettime(CLOCK_REALTIME, &ts); \
t = (ts.tv_sec*(quint64_t)1000000000) + ts.tv_nsec; \
}, 1000000000,;)
#else
#define CLOCKDEF_LINUX_REALTIME
#endif
#if _POSIX_C_SOURCE >= 200112L
#include <sys/time.h>
#define CLOCKDEF_POSIX_GTOD QCLOCK(GTOD, "gettimeofday", { \
struct timeval tp; \
gettimeofday(&tp, NULL); \
t = tp.tv_sec*(quint64_t)1000000 + tp.tv_usec; \
}, 1000000,;)
#else
#define CLOCKDEF_POSIX_GTOD
#endif
#define CLOCKDEF_ALL \
CLOCKDEF_LINUX_MONOTONIC CLOCKDEF_LINUX_REALTIME /*linux-specific clocks*/\
CLOCKDEF_SDL_PERF CLOCKDEF_SDL_TICKS /*sdl clocks*/\
CLOCKDEF_POSIX_GTOD /*posix clocks*/
static quint64_t timer_basetime; //used by all clocks to bias them to starting at 0
static quint64_t timer_nobacksies; //used by all clocks to bias them to starting at 0
static void Sys_ClockType_Changed(cvar_t *var, char *oldval);
#define QCLOCK(e,n,q,f,i) n"\n"
static cvar_t sys_clocktype = CVARFCD("sys_clocktype", "", CVAR_NOTFROMSERVER, Sys_ClockType_Changed, "Controls which system clock to base timings from.\nAvailable Clocks:\n"
CLOCKDEF_ALL);
#undef QCLOCK
static enum
{
#define QCLOCK(e,n,q,f,i) CLOCKID_##e,
CLOCKDEF_ALL
#undef QCLOCK
CLOCKID_INVALID
} timer_clocktype=CLOCKID_INVALID;
static quint64_t Sys_GetClock(quint64_t *freq)
{
quint64_t t;
switch(timer_clocktype)
{
default:
#define QCLOCK(e,n,q,f,i) case CLOCKID_##e: *freq = f; q; break;
CLOCKDEF_ALL
#undef QCLOCK
}
t -= timer_basetime;
if (t < timer_nobacksies && t-*freq>timer_nobacksies)
{
quint64_t back = timer_nobacksies-t;
t = timer_nobacksies;
if (back > 0)//*freq/1000) //warn if the clock went backwards by more than 1ms.
Con_Printf("Warning: clock went backwards by %g secs.\n", back/(double)*freq);
if (back > 0x8000000 && *freq==1000)
{ //32bit value wrapped?
timer_basetime -= 0x8000000;
t += 0x8000000;
}
}
else
timer_nobacksies = t;
return t;
}
static void Sys_ClockType_Changed(cvar_t *var, char *oldval)
{
int newtype;
#define QCLOCK(e,n,q,f,i) if (!strcasecmp(var->string, n)) newtype = CLOCKID_##e; else
CLOCKDEF_ALL
#undef QCLOCK
{
quint64_t freq;
if (!*var->string || !strcasecmp(var->string, "auto"))
;
else
{
Con_Printf("%s: Unknown clock name %s available clocks", var->name, var->string);
#define QCLOCK(e,n,q,f,i) Con_Printf(", %s", n);
CLOCKDEF_ALL
#undef QCLOCK
Con_Printf("\n");
}
//look for any that work.
for(newtype = 0; ; newtype++)
{
switch(newtype)
{
default:
freq = 0;
Sys_Error("No usable clocks");
#define QCLOCK(e,n,q,f,i) case CLOCKID_##e: freq = f; break;
CLOCKDEF_ALL
#undef QCLOCK
}
if (freq)
break; //this clock seems usable.
}
}
if (newtype != timer_clocktype)
{
quint64_t oldtime, oldfreq;
quint64_t newtime, newfreq;
oldtime = Sys_GetClock(&oldfreq);
timer_clocktype = newtype;
timer_nobacksies = timer_basetime = 0; //make sure we get the raw clock.
newtime = Sys_GetClock(&newfreq);
timer_basetime = newtime - (newfreq * ((long double)oldtime) / oldfreq);
timer_nobacksies = newtime - timer_basetime;
Sys_GetClock(&newfreq);
}
}
static void Sys_InitClock(void)
{
quint64_t freq;
Cvar_Register(&sys_clocktype, "System vars");
#define QCLOCK(e,n,q,f,i) i;
CLOCKDEF_ALL
#undef QCLOCK
//calibrate it, and apply.
timer_clocktype = CLOCKID_INVALID;
Sys_ClockType_Changed(&sys_clocktype, NULL);
timer_basetime = timer_nobacksies = 0;
timer_basetime = Sys_GetClock(&freq);
timer_nobacksies = 0;
}
double Sys_DoubleTime (void)
{
quint64_t denum, num = Sys_GetClock(&denum);
return num / (long double)denum;
}
unsigned int Sys_Milliseconds (void)
{
quint64_t denum, num = Sys_GetClock(&denum);
num *= 1000;
return num / denum;
}
//create a directory
void Sys_mkdir (const char *path)
{
#if WIN32
_mkdir (path);
#else
//user, group, others
mkdir (path, 0755); //WARNING: DO NOT RUN AS ROOT!
#endif
}
qboolean Sys_rmdir (const char *path)
{
int ret;
#if WIN32
ret = _rmdir (path);
#else
ret = rmdir (path);
#endif
if (ret == 0)
return true;
// if (errno == ENOENT)
// return true;
return false;
}
//unlink a file
qboolean Sys_remove (const char *path)
{
remove(path);
return true;
}
qboolean Sys_Rename (const char *oldfname, const char *newfname)
{
return !rename(oldfname, newfname);
}
#if _POSIX_C_SOURCE >= 200112L
#include <sys/statvfs.h>
#endif
qboolean Sys_GetFreeDiskSpace(const char *path, quint64_t *freespace)
{
#if _POSIX_C_SOURCE >= 200112L
//posix 2001
struct statvfs inf;
if(0==statvfs(path, &inf))
{
*freespace = inf.f_bsize*(quint64_t)inf.f_bavail;
return true;
}
#endif
return false;
}
//someone used the 'quit' command
void Sys_Quit (void)
{
Host_Shutdown();
SDL_free((char*)host_parms.binarydir);
host_parms.binarydir = NULL;
exit (0);
}
//enumerate the files in a directory (of both gpath and match - match may not contain ..)
//calls the callback for each one until the callback returns 0
//SDL provides no file enumeration facilities.
#if defined(_WIN32)
#include <windows.h>
//outlen is the size of out in _BYTES_.
wchar_t *widen(wchar_t *out, size_t outbytes, const char *utf8)
{
size_t outlen;
wchar_t *ret = out;
//utf-8 to utf-16, not ucs-2.
unsigned int codepoint;
int error;
outlen = outbytes/sizeof(wchar_t);
if (!outlen)
return L"";
outlen--;
while (*utf8)
{
codepoint = utf8_decode(&error, utf8, (void*)&utf8);
if (error || codepoint > 0x10FFFFu)
codepoint = 0xFFFDu;
if (codepoint > 0xffff)
{
if (outlen < 2)
break;
outlen -= 2;
codepoint -= 0x10000u;
*out++ = 0xD800 | (codepoint>>10);
*out++ = 0xDC00 | (codepoint&0x3ff);
}
else
{
if (outlen < 1)
break;
outlen -= 1;
*out++ = codepoint;
}
}
*out = 0;
return ret;
}
char *narrowen(char *out, size_t outlen, wchar_t *wide)
{
char *ret = out;
int bytes;
unsigned int codepoint;
if (!outlen)
return "";
outlen--;
//utf-8 to utf-16, not ucs-2.
while (*wide)
{
codepoint = *wide++;
if (codepoint >= 0xD800u && codepoint <= 0xDBFFu)
{ //handle utf-16 surrogates
if (*wide >= 0xDC00u && *wide <= 0xDFFFu)
{
codepoint = (codepoint&0x3ff)<<10;
codepoint |= *wide++ & 0x3ff;
}
else
codepoint = 0xFFFDu;
}
bytes = utf8_encode(out, codepoint, outlen);
if (bytes <= 0)
break;
out += bytes;
outlen -= bytes;
}
*out = 0;
return ret;
}
static int Sys_EnumerateFiles2 (const char *match, int matchstart, int neststart, int (QDECL *func)(const char *fname, qofs_t fsize, time_t mtime, void *parm, searchpathfuncs_t *spath), void *parm, searchpathfuncs_t *spath)
{
qboolean go;
HANDLE r;
WIN32_FIND_DATAW fd;
int nest = neststart; //neststart refers to just after a /
qboolean wild = false;
while(match[nest] && match[nest] != '/')
{
if (match[nest] == '?' || match[nest] == '*')
wild = true;
nest++;
}
if (match[nest] == '/')
{
char submatch[MAX_OSPATH];
char tmproot[MAX_OSPATH];
if (!wild)
return Sys_EnumerateFiles2(match, matchstart, nest+1, func, parm, spath);
if (nest-neststart+1> MAX_OSPATH)
return 1;
memcpy(submatch, match+neststart, nest - neststart);
submatch[nest - neststart] = 0;
nest++;
if (neststart+4 > MAX_OSPATH)
return 1;
memcpy(tmproot, match, neststart);
strcpy(tmproot+neststart, "*.*");
{
wchar_t wroot[MAX_OSPATH];
r = FindFirstFileExW(widen(wroot, sizeof(wroot), tmproot), FindExInfoStandard, &fd, FindExSearchNameMatch, NULL, 0);
}
strcpy(tmproot+neststart, "");
if (r==(HANDLE)-1)
return 1;
go = true;
do
{
char utf8[MAX_OSPATH];
char file[MAX_OSPATH];
narrowen(utf8, sizeof(utf8), fd.cFileName);
if (*utf8 == '.'); //don't ever find files with a name starting with '.'
else if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //is a directory
{
if (wildcmp(submatch, utf8))
{
int newnest;
if (strlen(tmproot) + strlen(utf8) + strlen(match+nest) + 2 < MAX_OSPATH)
{
Q_snprintfz(file, sizeof(file), "%s%s/", tmproot, utf8);
newnest = strlen(file);
strcpy(file+newnest, match+nest);
go = Sys_EnumerateFiles2(file, matchstart, newnest, func, parm, spath);
}
}
}
} while(FindNextFileW(r, &fd) && go);
FindClose(r);
}
else
{
const char *submatch = match + neststart;
char tmproot[MAX_OSPATH];
if (neststart+4 > MAX_OSPATH)
return 1;
memcpy(tmproot, match, neststart);
strcpy(tmproot+neststart, "*.*");
{
wchar_t wroot[MAX_OSPATH];
r = FindFirstFileExW(widen(wroot, sizeof(wroot), tmproot), FindExInfoStandard, &fd, FindExSearchNameMatch, NULL, 0);
}
strcpy(tmproot+neststart, "");
if (r==(HANDLE)-1)
return 1;
go = true;
do
{
char utf8[MAX_OSPATH];
char file[MAX_OSPATH];
narrowen(utf8, sizeof(utf8), fd.cFileName);
if (*utf8 == '.')
; //don't ever find files with a name starting with '.' (includes .. and . directories, and unix hidden files)
else if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //is a directory
{
if (wildcmp(submatch, utf8))
{
if (strlen(tmproot+matchstart) + strlen(utf8) + 2 < MAX_OSPATH)
{
Q_snprintfz(file, sizeof(file), "%s%s/", tmproot+matchstart, utf8);
go = func(file, qofs_Make(fd.nFileSizeLow, fd.nFileSizeHigh), 0, parm, spath);
}
}
}
else
{
if (wildcmp(submatch, utf8))
{
if (strlen(tmproot+matchstart) + strlen(utf8) + 1 < MAX_OSPATH)
{
Q_snprintfz(file, sizeof(file), "%s%s", tmproot+matchstart, utf8);
go = func(file, qofs_Make(fd.nFileSizeLow, fd.nFileSizeHigh), 0, parm, spath);
}
}
}
} while(FindNextFileW(r, &fd) && go);
FindClose(r);
}
return go;
}
int Sys_EnumerateFiles (const char *gpath, const char *match, int (QDECL *func)(const char *fname, qofs_t fsize, time_t modtime, void *parm, searchpathfuncs_t *spath), void *parm, searchpathfuncs_t *spath)
{
char fullmatch[MAX_OSPATH];
int start;
if (strlen(gpath) + strlen(match) + 2 > MAX_OSPATH)
return 1;
strcpy(fullmatch, gpath);
start = strlen(fullmatch);
if (start && fullmatch[start-1] != '/')
fullmatch[start++] = '/';
fullmatch[start] = 0;
strcat(fullmatch, match);
return Sys_EnumerateFiles2(fullmatch, start, start, func, parm, spath);
}
#elif defined(linux) || defined(__unix__) || defined(__MACH__) || defined(__HAIKU__)
#include <dirent.h>
#include <errno.h>
static int Sys_EnumerateFiles2 (const char *truepath, int apathofs, const char *match, int (*func)(const char *, qofs_t, time_t modtime, void *, searchpathfuncs_t *), void *parm, searchpathfuncs_t *spath)
{
DIR *dir;
char file[MAX_OSPATH];
const char *s;
struct dirent *ent;
struct stat st;
const char *wild;
const char *apath = truepath+apathofs;
//if there's a * in a system path, then we need to scan its parent directory to figure out what the * expands to.
//we can just recurse quicklyish to try to handle it.
wild = strchr(apath, '*');
if (!wild)
wild = strchr(apath, '?');
if (wild)
{
char subdir[MAX_OSPATH];
for (s = wild+1; *s && *s != '/'; s++)
;
while (wild > truepath)
{
if (*(wild-1) == '/')
break;
wild--;
}
memcpy(file, truepath, wild-truepath);
file[wild-truepath] = 0;
dir = opendir(file);
memcpy(subdir, wild, s-wild);
subdir[s-wild] = 0;
if (dir)
{
do
{
ent = readdir(dir);
if (!ent)
break;
if (*ent->d_name != '.')
{
#ifdef _DIRENT_HAVE_D_TYPE
if (ent->d_type != DT_DIR && ent->d_type != DT_UNKNOWN)
continue;
#endif
if (wildcmp(subdir, ent->d_name))
{
memcpy(file, truepath, wild-truepath);
Q_snprintfz(file+(wild-truepath), sizeof(file)-(wild-truepath), "%s%s", ent->d_name, s);
if (!Sys_EnumerateFiles2(file, apathofs, match, func, parm, spath))
{
closedir(dir);
return false;
}
}
}
} while(1);
closedir(dir);
}
return true;
}
dir = opendir(truepath);
if (!dir)
{
Con_DLPrintf((errno==ENOENT)?2:1, "Failed to open dir %s\n", truepath);
return true;
}
do
{
ent = readdir(dir);
if (!ent)
break;
if (*ent->d_name != '.')
{
if (wildcmp(match, ent->d_name))
{
Q_snprintfz(file, sizeof(file), "%s/%s", truepath, ent->d_name);
if (stat(file, &st) == 0 || lstat(file, &st) == 0)
{
Q_snprintfz(file, sizeof(file), "%s%s%s", apath, ent->d_name, S_ISDIR(st.st_mode)?"/":"");
if (!func(file, st.st_size, st.st_mtime, parm, spath))
{
// Con_DPrintf("giving up on search after finding %s\n", file);
closedir(dir);
return false;
}
}
// else
// printf("Stat failed for \"%s\"\n", file);
}
}
} while(1);
closedir(dir);
return true;
}
int Sys_EnumerateFiles (const char *gpath, const char *match, int (*func)(const char *, qofs_t, time_t modtime, void *, searchpathfuncs_t *), void *parm, searchpathfuncs_t *spath)
{
char apath[MAX_OSPATH];
char truepath[MAX_OSPATH];
char *s;
if (!gpath)
gpath = "";
*apath = '\0';
Q_strncpyz(apath, match, sizeof(apath));
for (s = apath+strlen(apath)-1; s >= apath; s--)
{
if (*s == '/')
{
s[1] = '\0';
match += s - apath+1;
break;
}
}
if (s < apath) //didn't find a '/'
*apath = '\0';
Q_snprintfz(truepath, sizeof(truepath), "%s/%s", gpath, apath);
return Sys_EnumerateFiles2(truepath, strlen(gpath)+1, match, func, parm, spath);
}
#else
int Sys_EnumerateFiles (const char *gpath, const char *match, int (*func)(const char *, qofs_t, time_t mtime, void *, void *), void *parm, void *spath)
{
Con_Printf("Warning: Sys_EnumerateFiles not implemented\n");
return false;
}
#endif
//blink window if possible (it's not)
void Sys_ServerActivity(void)
{
#if SDL_VERSION_ATLEAST(2,0,16)
SDL_FlashWindow(sdlwindow, SDL_FLASH_BRIEFLY);
#endif
}
void Sys_CloseLibrary(dllhandle_t *lib)
{
SDL_UnloadObject((void*)lib);
}
dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs)
{
int i;
void *lib;
lib = SDL_LoadObject(name);
if (!lib)
{
char libpath[MAX_OSPATH];
Q_snprintfz(libpath, sizeof(libpath), "%s"ARCH_DL_POSTFIX, name);
lib = SDL_LoadObject(libpath);
}
if (!lib)
return NULL;
if (funcs)
{
for (i = 0; funcs[i].name; i++)
{
*funcs[i].funcptr = SDL_LoadFunction(lib, funcs[i].name);
if (!*funcs[i].funcptr)
break;
}
if (funcs[i].name)
{
Sys_CloseLibrary((dllhandle_t*)lib);
lib = NULL;
}
}
return (dllhandle_t*)lib;
}
void *Sys_GetAddressForName(dllhandle_t *module, const char *exportname)
{
if (!module)
return NULL;
return SDL_LoadFunction((void *)module, exportname);
}
//used to see if a file exists or not.
int Sys_FileTime (char *path)
{
FILE *f;
f = fopen(path, "rb");
if (f)
{
fclose(f);
return 1;
}
return -1;
}
char *Sys_URIScheme_NeedsRegistering(void)
{ //no support.
return NULL;
}
void Sys_Init(void)
{
SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);
Sys_InitClock();
}
void Sys_Shutdown(void)
{
SDL_Quit();
}
int VARGS Sys_DebugLog(char *file, char *fmt, ...)
{
FILE *fd;
va_list argptr;
static char data[1024];
va_start(argptr, fmt);
vsnprintf(data, sizeof(data)-1, fmt, argptr);
va_end(argptr);
#if defined(CRAZYDEBUGGING) && CRAZYDEBUGGING > 1
{
static int sock;
if (!sock)
{
struct sockaddr_in sa;
netadr_t na;
int _true = true;
int listip;
listip = COM_CheckParm("-debugip");
NET_StringToAdr(listip?com_argv[listip+1]:"127.0.0.1", &na);
NetadrToSockadr(&na, (struct sockaddr_qstorage*)&sa);
sa.sin_port = htons(10000);
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (-1==connect(sock, (struct sockaddr*)&sa, sizeof(sa)))
Sys_Error("Couldn't send debug log lines\n");
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&_true, sizeof(_true));
}
send(sock, data, strlen(data), 0);
}
#endif
fd = fopen(file, "wt");
if (fd)
{
fwrite(data, 1, strlen(data), fd);
fclose(fd);
return 0;
}
return 1;
};
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#endif
#ifdef _WIN32
static qboolean gotconsole;
static HANDLE con_stdin;
qboolean Sys_InitTerminal(void)
{
gotconsole = AllocConsole(); //failure is okay if we already had one.
con_stdin = CreateFile("CONIN$", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
freopen("CON", "w", stdout); //unfuck the stdout too.
return true;
}
char *Sys_ConsoleInput(void)
{ //NET_Sleep won't sleep on this handle, so expect it to be sluggish.
DWORD numevents, i;
while (GetNumberOfConsoleInputEvents(con_stdin, &numevents) && numevents>0)
{
static char text[256];
static int textlen;
INPUT_RECORD event[1]; //longer might miss presses. especially with delays.
if (numevents > countof(event))
numevents = countof(event);
if (ReadConsoleInputW(con_stdin, event, numevents, &numevents))
{
for(i = 0; i < numevents; i++)
{
if (event[i].EventType == KEY_EVENT && event[i].Event.KeyEvent.bKeyDown && event[i].Event.KeyEvent.uChar.UnicodeChar)
{
if (textlen >= countof(text))
textlen = countof(text)-1; //don't overflow.
text[textlen] = event[i].Event.KeyEvent.uChar.UnicodeChar;
if (text[textlen] == '\r')
{
text[textlen] = 0; //caller will add its own \n
printf("\r]%s\n", text);
textlen = 0; //start from the start
fflush(stdout);
return text;
}
textlen++;
text[textlen] = 0; //caller will add its own \n
printf("\r]%s", text);
fflush(stdout);
}
}
}
}
return NULL;
}
void Sys_CloseTerminal (void)
{
if (gotconsole)
{ //don't close our initial one. don't detach that way.
FreeConsole();
gotconsole = false;
}
if (con_stdin)
{
CloseHandle(con_stdin);
con_stdin = NULL;
}
}
#elif defined(__unix__) && !defined(__ANDROID__)
static qbyte noconinput;
qboolean Sys_InitTerminal(void)
{
if (COM_CheckParm("-nostdin"))
noconinput = true;
if (noconinput)
return true; //they okayed it, let it start regardless.
if (isatty(STDIN_FILENO))
return true;
Con_Printf(CON_WARNING"Sys_InitTerminal: not started from a tty\n"); //no easy way to kill it otherwise.
return false;
}
char *Sys_ConsoleInput(void)
{
static char text[256];
char *nl;
if (noconinput)
return NULL;
#if defined(__linux__) && defined(_DEBUG)
{
int fl = fcntl (STDIN_FILENO, F_GETFL, 0);
if (!(fl & FNDELAY))
{
fcntl(STDIN_FILENO, F_SETFL, fl | FNDELAY);
// Sys_Printf(CON_WARNING "stdin flags became blocking - gdb bug?\n");
}
}
#endif
if (!fgets(text, sizeof(text), stdin))
{
if (errno == EIO)
{
Sys_Printf(CON_WARNING "Backgrounded, ignoring stdin\n");
noconinput |= 2;
}
return NULL;
}
nl = strchr(text, '\n');
if (!nl) //err? wut?
return NULL;
*nl = 0;
return text;
}
void Sys_CloseTerminal (void)
{
}
#else
qboolean Sys_InitTerminal(void)
{
Con_Printf(CON_WARNING"Sys_InitTerminal: not implemented in this build.\n");
return false; //Sys_ConsoleInput cannot work, so return false here.
}
char *Sys_ConsoleInput(void)
{
return NULL;
}
void Sys_CloseTerminal (void)
{
}
#endif
#ifdef FTE_TARGET_WEB
void Sys_MainLoop(void)
{
static float oldtime;
float newtime, time;
newtime = Sys_DoubleTime ();
if (!oldtime)
oldtime = newtime;
time = newtime - oldtime;
Host_Frame (time);
oldtime = newtime;
}
#endif
int QDECL main(int argc, char **argv)
{
float time, newtime, oldtime;
quakeparms_t parms;
memset(&parms, 0, sizeof(parms));
parms.basedir = "./";
parms.binarydir = SDL_GetBasePath();
parms.argc = argc;
parms.argv = (const char**)argv;
#ifdef CONFIG_MANIFEST_TEXT
parms.manifest = CONFIG_MANIFEST_TEXT;
#endif
#if !defined(WIN32)
fcntl(0, F_SETFL, fcntl (0, F_GETFL, 0) | O_NDELAY);
#endif
COM_InitArgv (parms.argc, parms.argv);
TL_InitLanguages(parms.basedir);
if (parms.binarydir)
Sys_Printf("Binary is located at \"%s\"\n", parms.binarydir);
#ifdef HAVE_CLIENT
if (COM_CheckParm ("-dedicated"))
isDedicated = true;
if (isDedicated) //compleate denial to switch to anything else - many of the client structures are not initialized.
{
float delay;
SV_Init (&parms);
if (!Sys_InitTerminal())
Con_Printf(CON_WARNING"Stdin unavailable\n");
delay = SV_Frame();
while (1)
{
if (!isDedicated)
Sys_Error("Dedicated was cleared");
NET_Sleep(delay, false);
delay = SV_Frame();
}
return EXIT_FAILURE;
}
#endif
Host_Init (&parms);
oldtime = Sys_DoubleTime ();
//client console should now be initialized.
/* main window message loop */
while (1)
{
#ifndef CLIENTONLY
if (isDedicated)
{
float delay;
// find time passed since last cycle
newtime = Sys_DoubleTime ();
time = newtime - oldtime;
oldtime = newtime;
delay = SV_Frame();
NET_Sleep(delay, false);
}
else
#endif
{
double sleeptime;
// yield the CPU for a little while when paused, minimized, or not the focus
#if SDL_MAJOR_VERSION >= 2
if (!vid.activeapp)
SDL_Delay(1);
#else
if (!(SDL_GetAppState() & SDL_APPINPUTFOCUS))
SDL_Delay(1);
#endif
newtime = Sys_DoubleTime ();
time = newtime - oldtime;
sleeptime = Host_Frame (time);
oldtime = newtime;
if (sleeptime)
Sys_Sleep(sleeptime);
}
}
return 0;
}
#ifdef _MSC_VER
//our version of sdl_main.lib, which doesn't fight c runtimes.
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
int argc;
int i, l, c;
LPWSTR *argvw;
char **argv;
char utf8arg[1024];
argvw = CommandLineToArgvW(GetCommandLineW(), &argc);
argv = malloc(argc * sizeof(char*));
for (i = 0; i < argc; i++)
{
for(l = 0, c = 0; argvw[i][l]; l++)
c += utf8_encode(utf8arg+c, argvw[i][l], sizeof(utf8arg) - c-1);
utf8arg[c] = 0;
argv[i] = strdup(utf8arg);
}
return main(argc, argv);
}
#endif
qboolean Sys_GetDesktopParameters(int *width, int *height, int *bpp, int *refreshrate)
{
#if SDL_MAJOR_VERSION >= 2
SDL_DisplayMode mode;
if (!SDL_GetDesktopDisplayMode(0, &mode))
{
*width = mode.w;
*height = mode.h;
*bpp = (SDL_PIXELTYPE(mode.format) == SDL_PIXELTYPE_PACKED32)?32:16;
*refreshrate = mode.refresh_rate;
return true;
}
#endif
return false;
}
#if SDL_MAJOR_VERSION >= 2 //probably could include 1.3
#include <SDL_clipboard.h>
void Sys_Clipboard_PasteText(clipboardtype_t cbt, void (*callback)(void *cb, const char *utf8), void *ctx)
{
char *txt;
#if SDL_VERSION_ATLEAST(2,26,0)
if (cbt == CBT_SELECTION)
txt = SDL_GetPrimarySelectionText();
else
#endif
txt = SDL_GetClipboardText();
callback(ctx, txt);
SDL_free(txt);
}
void Sys_SaveClipboard(clipboardtype_t cbt, const char *text)
{
#if SDL_VERSION_ATLEAST(2,26,0)
if (cbt == CBT_SELECTION)
SDL_SetPrimarySelectionText(text);
else
#endif
SDL_SetClipboardText(text);
}
#else
static char *clipboard_buffer;
void Sys_Clipboard_PasteText(clipboardtype_t cbt, void (*callback)(void *cb, const char *utf8), void *ctx)
{
callback(ctx, clipboard_buffer);
}
void Sys_SaveClipboard(clipboardtype_t cbt, const char *text)
{
free(clipboard_buffer);
clipboard_buffer = strdup(text);
}
#endif
#ifdef MULTITHREAD
/*Thread management stuff*/
static SDL_threadID mainthread;
static SDL_TLSID tls_threadinfo;
struct threadinfo_s
{
jmp_buf jmpbuf; //so we can actually abort our threads...
int (*threadfunc)(void *args);
void *args;
};
void Sys_ThreadsInit(void)
{
mainthread = SDL_ThreadID();
}
qboolean Sys_IsThread(void *thread)
{
return SDL_GetThreadID(thread) == SDL_ThreadID();
}
qboolean Sys_IsMainThread(void)
{
return mainthread == SDL_ThreadID();
}
void Sys_ThreadAbort(void)
{
//SDL_KillThread(NULL) got removed... so we have to do things the shitty way.
struct threadinfo_s *tinfo = SDL_TLSGet(tls_threadinfo);
if (!tinfo)
{ //erk... not created via Sys_CreateThread?!?
SDL_Delay(10*1000);
exit(0);
}
longjmp(tinfo->jmpbuf, 1);
}
static int FTESDLThread(void *args)
{ //all for Sys_ThreadAbort
struct threadinfo_s *tinfo = args;
int r;
SDL_TLSSet(tls_threadinfo, tinfo, NULL);
if (setjmp(tinfo->jmpbuf))
r = 0; //aborted...
else
r = tinfo->threadfunc(tinfo->args);
SDL_TLSSet(tls_threadinfo, NULL, NULL);
Z_Free(tinfo);
return r;
}
void *Sys_CreateThread(char *name, int (*func)(void *), void *args, int priority, int stacksize)
{
// SDL threads do not support setting thread stack size
struct threadinfo_s *tinfo = Z_Malloc(sizeof(*tinfo));
tinfo->threadfunc = func;
tinfo->args = args;
#if SDL_MAJOR_VERSION >= 2
return (void *)SDL_CreateThread(FTESDLThread, name, tinfo);
#else
return (void *)SDL_CreateThread(FTESDLThread, tinfo);
#endif
}
void Sys_WaitOnThread(void *thread)
{
SDL_WaitThread((SDL_Thread *)thread, NULL);
}
/* Mutex calls */
#if SDL_MAJOR_VERSION >= 2
void *Sys_CreateMutex(void)
{
return (void *)SDL_CreateMutex();
}
qboolean Sys_TryLockMutex(void *mutex)
{
return !SDL_TryLockMutex(mutex);
}
qboolean Sys_LockMutex(void *mutex)
{
return !SDL_LockMutex(mutex);
}
qboolean Sys_UnlockMutex(void *mutex)
{
return !SDL_UnlockMutex(mutex);
}
void Sys_DestroyMutex(void *mutex)
{
SDL_DestroyMutex(mutex);
}
#else
// SDL mutexes don't have try-locks for mutexes in the spec so we stick with 1-value semaphores
void *Sys_CreateMutex(void)
{
return (void *)SDL_CreateSemaphore(1);
}
qboolean Sys_TryLockMutex(void *mutex)
{
return !SDL_SemTryWait(mutex);
}
qboolean Sys_LockMutex(void *mutex)
{
return !SDL_SemWait(mutex);
}
qboolean Sys_UnlockMutex(void *mutex)
{
return !SDL_SemPost(mutex);
}
void Sys_DestroyMutex(void *mutex)
{
SDL_DestroySemaphore(mutex);
}
#endif
/* Conditional wait calls */
typedef struct condvar_s
{
SDL_mutex *mutex;
SDL_cond *cond;
} condvar_t;
void *Sys_CreateConditional(void)
{
condvar_t *condv;
SDL_mutex *mutex;
SDL_cond *cond;
condv = (condvar_t *)malloc(sizeof(condvar_t));
if (!condv)
return NULL;
mutex = SDL_CreateMutex();
cond = SDL_CreateCond();
if (mutex)
{
if (cond)
{
condv->cond = cond;
condv->mutex = mutex;
return (void *)condv;
}
else
SDL_DestroyMutex(mutex);
}
free(condv);
return NULL;
}
qboolean Sys_LockConditional(void *condv)
{
return !SDL_mutexP(((condvar_t *)condv)->mutex);
}
qboolean Sys_UnlockConditional(void *condv)
{
return !SDL_mutexV(((condvar_t *)condv)->mutex);
}
qboolean Sys_ConditionWait(void *condv)
{
return !SDL_CondWait(((condvar_t *)condv)->cond, ((condvar_t *)condv)->mutex);
}
qboolean Sys_ConditionSignal(void *condv)
{
return !SDL_CondSignal(((condvar_t *)condv)->cond);
}
qboolean Sys_ConditionBroadcast(void *condv)
{
return !SDL_CondBroadcast(((condvar_t *)condv)->cond);
}
void Sys_DestroyConditional(void *condv)
{
condvar_t *cv = (condvar_t *)condv;
SDL_DestroyCond(cv->cond);
SDL_DestroyMutex(cv->mutex);
free(cv);
}
#endif
void Sys_Sleep (double seconds)
{
SDL_Delay(seconds * 1000);
}
#ifdef WEBCLIENT
qboolean Sys_RunInstaller(void)
{ //not implemented
return false;
}
#endif
#ifdef HAVEAUTOUPDATE
//returns true if we could sucessfull overwrite the engine binary.
qboolean Sys_SetUpdatedBinary(const char *fname)
{
return false;
}
//says whether the system code is able/allowed to overwrite itself.
//(ie: return false if we don't know the binary name or if its write-protected etc)
qboolean Sys_EngineMayUpdate(void)
{
return false; //sorry
}
#endif