diff --git a/CMakeLists.txt b/CMakeLists.txt index cc4a8431..5b6eb802 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1328,19 +1328,45 @@ IF(FTE_PLUG_OPENXR) ENDIF() ENDIF() -#cef plugin -#libcef itself can be obtained from http://opensource.spotify.com/cefbuilds/index.html (minimal builds, which still ends up with a 940mb libcef.so - yes, actual size) -#(be sure to manually strip the binary of its debug info) +##cef plugin +##libcef itself can be obtained from https://cef-builds.spotifycdn.com/index.html#linux64 (minimal builds, which still ends up with a 1,162,752,744 byte libcef.so - yes, actual size) +##(be sure to manually strip the binary of its debug info) +##to get this cmake stuff to recognise the headers etc: +## cd $FTEQW-REPO && ln -s $FOO/cef_binary_$FOO+chromium-$FOO_linux64_minimal plugins/cef/cef_linux64 +##(note that other systems use other subdir names) SET(FTE_PLUG_CEF true CACHE BOOL "Compile libcef (webbrowser) plugin.") +SET(CEF_PATH ${CEF_PATH} CACHE PATH "Base location of libcef for target platform.") IF(FTE_PLUG_CEF) - FIND_PATH (CEF_PATH include/cef_version.h /tmp/cef/cef_binary_81.3.1+gb2b49f1+chromium-81.0.4044.113_linux64_minimal) + IF(CEF_PATH MATCHES "") + UNSET(CEF_PATH CACHE) + IF(WIN32) + IF(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "AMD64") + FIND_PATH (CEF_PATH include/cef_version.h plugins/cef/cef_windows64) + ELSEIF(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86") + FIND_PATH (CEF_PATH include/cef_version.h plugins/cef/cef_windows32) + ELSEIF(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "ARM64") + FIND_PATH (CEF_PATH include/cef_version.h plugins/cef/cef_windowsarm64) + ENDIF() + ELSEIF("${CMAKE_SYSTEM}" MATCHES "Linux") + IF(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64") + FIND_PATH (CEF_PATH include/cef_version.h plugins/cef/cef_linux64) + ELSEIF(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "i686") + FIND_PATH (CEF_PATH include/cef_version.h plugins/cef/cef_linux32) + ENDIF() + ENDIF() + ENDIF() #FIND_LIBRARY(CEF_LIBRARIES cef ${CEF_PATH}/Release) IF (CEF_PATH) + ##statically link only for release builds. debug builds probably don't want to have to wait for ages for the debugger to finish loading debug info unless we're actually using this stuff. + IF(CMAKE_BUILD_TYPE MATCHES "Release") + SET(CEF_LIBRARIES "${CMAKE_BINARY_DIR}/libcef.so") + ENDIF() ADD_LIBRARY(plug_cef MODULE plugins/plugin.c plugins/cef/cef.c ) - TARGET_INCLUDE_DIRECTORIES(plug_cef PRIVATE ${CEF_PATH}/..) + TARGET_INCLUDE_DIRECTORIES(plug_cef PRIVATE ${CEF_PATH}) + SET_TARGET_PROPERTIES(plug_cef PROPERTIES BUILD_RPATH_USE_ORIGIN true) if (CEF_LIBRARIES) SET_TARGET_PROPERTIES(plug_cef PROPERTIES COMPILE_DEFINITIONS "FTEPLUGIN;${FTE_LIB_DEFINES};${FTE_DEFINES};LIBCEF_STATIC") TARGET_LINK_LIBRARIES(plug_cef ${SYS_LIBS} ${CEF_LIBRARIES} ${CMAKE_DL_LIBS}) @@ -1348,6 +1374,34 @@ IF(FTE_PLUG_CEF) SET_TARGET_PROPERTIES(plug_cef PROPERTIES COMPILE_DEFINITIONS "FTEPLUGIN;${FTE_LIB_DEFINES};${FTE_DEFINES};LIBCEF_DYNAMIC") TARGET_LINK_LIBRARIES(plug_cef ${SYS_LIBS} ${CMAKE_DL_LIBS}) ENDIF() + + IF(NOT ${UNIX}) + ADD_CUSTOM_COMMAND( + TARGET plug_cef PRE_LINK + COMMAND cp ${CEF_PATH}/Release/* ${CMAKE_BINARY_DIR} + COMMAND cp ${CEF_PATH}/Resources/* ${CMAKE_BINARY_DIR} + ) + ELSE() + IF(CMAKE_BUILD_TYPE MATCHES "Release") + #sigh, cef ain't stripped properly on linux. + ADD_CUSTOM_COMMAND( + TARGET plug_cef PRE_LINK + COMMAND ln -f -s ${CEF_PATH}/Release/* ${CMAKE_BINARY_DIR} + COMMAND strip ${CMAKE_BINARY_DIR}/libcef.so -o libcef.so + COMMAND strip ${CMAKE_BINARY_DIR}/libEGL.so -o libEGL.so + COMMAND strip ${CMAKE_BINARY_DIR}/libGLESv2.so -o libGLESv2.so + COMMAND strip ${CMAKE_BINARY_DIR}/chrome-sandbox -o chrome-sandbox + COMMAND ln -f -s ${CEF_PATH}/Resources/* ${CMAKE_BINARY_DIR} + ) + ELSE() + ADD_CUSTOM_COMMAND( + TARGET plug_cef PRE_LINK + COMMAND ln -f -s ${CEF_PATH}/Release/* ${CMAKE_BINARY_DIR} + COMMAND ln -f -s ${CEF_PATH}/Resources/* ${CMAKE_BINARY_DIR} + ) + ENDIF() + ENDIF() + SET_TARGET_PROPERTIES(plug_cef PROPERTIES PREFIX "fteplug_") SET_TARGET_PROPERTIES(plug_cef PROPERTIES OUTPUT_NAME "cef") SET_TARGET_PROPERTIES(plug_cef PROPERTIES LINK_FLAGS "-Wl,--no-undefined") diff --git a/engine/client/console.c b/engine/client/console.c index 323a4bce..3190d030 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -3134,7 +3134,7 @@ void Con_DrawConsole (int lines, qboolean noback) #ifdef QUAKETC //total conversions should have their own website. ENGINEWEBSITE #else //otherwise use some more useful page, for quake mods. - "https://www.quakeworld.nu/wiki/Overview" + "cmd:home" #endif , NULL, NULL}; float tw; diff --git a/engine/client/sys_linux.c b/engine/client/sys_linux.c index f23dd088..bf09a8ec 100644 --- a/engine/client/sys_linux.c +++ b/engine/client/sys_linux.c @@ -63,6 +63,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" #include "netinc.h" +#ifdef PLUGINS +#define FTEENGINE +#include "../plugins/plugin.h" +#endif #undef malloc @@ -559,6 +563,8 @@ void Sys_Init(void) } void Sys_Shutdown(void) { + Z_Free((char*)(host_parms.binarydir)); + host_parms.binarydir = NULL; } void Sys_Error (const char *error, ...) @@ -993,6 +999,8 @@ dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs) lib = dlopen (name, RTLD_LOCAL|RTLD_LAZY); if (!lib && !strstr(name, ".so")) lib = dlopen (va("%s.so", name), RTLD_LOCAL|RTLD_LAZY); + if (!lib && !strstr(name, ".so") && !strncmp(name, "./", 2) && host_parms.binarydir) + lib = dlopen (va("%s%s.so", host_parms.binarydir, name+2), RTLD_LOCAL|RTLD_LAZY); if (!lib) { Con_DLPrintf(2,"%s\n", dlerror()); @@ -1256,6 +1264,55 @@ static void DoSign(const char *fname, int signtype) } //end meta helpers +static char *Sys_GetBinary(void) +{ +#ifdef __linux__ + #define SYMLINK_TO_BINARY "/proc/self/exe" +#elif defined(__bsd__) + #define SYMLINK_TO_BINARY "/proc/curproc/file" +#endif + +#ifdef KERN_PROC_PATHNAME + //freebsd favours this over /proc, for good reason. + int id[] = + { + CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 + }; + size_t sz=0; + if (sysctl(id, countof(id), NULL, &sz, NULL, 0) && sz > 0) + { + char *bindir = Z_Malloc(sz+1); + if (sysctl(id, countof(id), bindir, &sz, NULL, 0) && sz > 0) + { + bindir[sz] = 0; + return bindir; + } + Z_Free(bindir); + } +#endif +#ifdef SYMLINK_TO_BINARY + { + //attempt to figure out where the exe is located + size_t sz = 64; + for(sz = 64; ; sz += 64) + { + char *bindir = Z_Malloc(sz+1); + ssize_t i = readlink(SYMLINK_TO_BINARY, bindir, sz); + if (i > 0 && i < sz) + { + bindir[i] = 0; + return bindir; + } + Z_Free(bindir); + if (i == sz) + continue; //continue + break; //some other kind of screwup + } + } +#endif + return NULL; +} + #ifdef _POSIX_C_SOURCE static void SigCont(int code) { @@ -1271,8 +1328,6 @@ int main (int c, const char **v) quakeparms_t parms; int i; - char bindir[1024]; - signal(SIGFPE, SIG_IGN); signal(SIGPIPE, SIG_IGN); #ifdef _POSIX_C_SOURCE @@ -1289,6 +1344,9 @@ int main (int c, const char **v) #ifdef CONFIG_MANIFEST_TEXT parms.manifest = CONFIG_MANIFEST_TEXT; #endif + parms.binarydir = Sys_GetBinary(); + if (parms.binarydir) + *COM_SkipPath(parms.binarydir) = 0; COM_InitArgv(parms.argc, parms.argv); #ifdef USE_LIBTOOL @@ -1300,7 +1358,26 @@ int main (int c, const char **v) uid_t ruid, euid, suid; getresuid(&ruid, &euid, &suid); if (!ruid || !euid || !suid) - printf("WARNING: you should NOT be running this as root!\n"); + fprintf(stderr, "WARNING: you should NOT be running this as root!\n"); + } +#endif + +#ifdef PLUGINS + c = COM_CheckParm("--plugwrapper"); + if (c) + { + int (QDECL *thefunc) (plugcorefuncs_t *corefuncs); + dllhandle_t *lib; + host_parms = parms;//not really initialising, but the filesystem needs it + lib = Sys_LoadLibrary(com_argv[c+1], NULL); + if (lib) + { + thefunc = Sys_GetAddressForName(lib, com_argv[c+2]); + + if (thefunc) + return thefunc(&plugcorefuncs); + } + return 0; } #endif @@ -1361,27 +1438,6 @@ int main (int c, const char **v) parms.basedir = "./"; #endif - memset(bindir, 0, sizeof(bindir)); //readlink does NOT null terminate, apparently. -#ifdef __linux__ - //attempt to figure out where the exe is located - i = readlink("/proc/self/exe", bindir, sizeof(bindir)-1); - if (i > 0) - { - bindir[i] = 0; - *COM_SkipPath(bindir) = 0; - parms.binarydir = bindir; - } -/*#elif defined(__bsd__) - //attempt to figure out where the exe is located - i = readlink("/proc/self/file", bindir, sizeof(bindir)-1); - if (i > 0) - { - bindir[i] = 0; - *COM_SkipPath(bindir) = 0; - parms.binarydir = bindir; - } -*/ -#endif TL_InitLanguages(parms.binarydir); if (!isatty(STDIN_FILENO)) diff --git a/engine/common/plugin.c b/engine/common/plugin.c index aa6b283c..286b1a1b 100644 --- a/engine/common/plugin.c +++ b/engine/common/plugin.c @@ -1771,27 +1771,26 @@ void Plug_Shutdown(qboolean preliminary) - +plugcorefuncs_t plugcorefuncs = +{ + PlugBI_GetEngineInterface, + PlugBI_ExportFunction, + PlugBI_ExportInterface, + PlugBI_GetPluginName, + Plug_Con_Print, + Plug_Sys_Error, + Plug_Sys_Milliseconds, + Sys_LoadLibrary, + Sys_GetAddressForName, + Sys_CloseLibrary, +}; static void *QDECL PlugBI_GetEngineInterface(const char *interfacename, size_t structsize) { if (!strcmp(interfacename, plugcorefuncs_name)) { - static plugcorefuncs_t funcs = - { - PlugBI_GetEngineInterface, - PlugBI_ExportFunction, - PlugBI_ExportInterface, - PlugBI_GetPluginName, - Plug_Con_Print, - Plug_Sys_Error, - Plug_Sys_Milliseconds, - Sys_LoadLibrary, - Sys_GetAddressForName, - Sys_CloseLibrary, - }; - if (structsize == sizeof(funcs)) - return &funcs; + if (structsize == sizeof(plugcorefuncs)) + return &plugcorefuncs; } if (!strcmp(interfacename, plugcmdfuncs_name)) { diff --git a/plugins/cef/cef.c b/plugins/cef/cef.c index e85e3ddf..a8afaa61 100644 --- a/plugins/cef/cef.c +++ b/plugins/cef/cef.c @@ -32,7 +32,7 @@ static plugclientfuncs_t *clientfuncs; #define cef_addref(ptr) (ptr)->base.add_ref(&(ptr)->base) #define cef_release(ptr) (((ptr)->base.release)(&(ptr)->base)) -#ifndef LIBCEF_STATIC +#if !defined(LIBCEF_STATIC) && !defined(LIBCEF_DYNAMIC) #define LIBCEF_DYNAMIC #endif #ifdef LIBCEF_DYNAMIC @@ -96,89 +96,7 @@ static int (*cef_string_multimap_value)(cef_string_multimap_t map, size_t i static void (*cef_string_multimap_free)(cef_string_multimap_t map); static size_t (*cef_string_list_size)(cef_string_list_t list); static int (*cef_string_list_value)(cef_string_list_t list, size_t index, cef_string_t* value); - -#ifdef _WIN32 -//we can't use pSys_LoadLibrary, because plugin builtins do not work unless the engine's plugin system is fully initialised, which doesn't happen in the 'light weight' sub processes, so we'll just roll our own (consistent) version. -dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs) -{ - int i; - HMODULE lib; - - lib = LoadLibrary(name); - if (!lib) - { - { //.dll implies that it is a system dll, or something that is otherwise windows-specific already. - char libname[MAX_OSPATH]; -#ifdef _WIN64 - Q_snprintf(libname, sizeof(libname), "%s_64", name); -#elif defined(_WIN32) - Q_snprintf(libname, sizeof(libname), "%s_32", name); -#else -#error wut? not win32? #endif - lib = LoadLibrary(libname); - } - if (!lib) - return NULL; - } - - if (funcs) - { - for (i = 0; funcs[i].name; i++) - { - *funcs[i].funcptr = GetProcAddress(lib, funcs[i].name); - if (!*funcs[i].funcptr) - break; - } - if (funcs[i].name) - { - Con_DPrintf("Missing export \"%s\" in \"%s\"\n", funcs[i].name, name); - FreeModule((dllhandle_t*)lib); - lib = NULL; - } - } - - return (dllhandle_t*)lib; -} -#else -#include -dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs) -{ - int i; - dllhandle_t *lib; - - lib = NULL; - if (!lib) - lib = dlopen (va("%s.so", name), RTLD_LAZY); - if (!lib && !strstr(name, ".so")) - lib = dlopen (va("./%s.so", name), RTLD_LAZY); - if (!lib) - { - Con_DPrintf("%s\n", dlerror()); - return NULL; - } - - if (funcs) - { - for (i = 0; funcs[i].name; i++) - { - *funcs[i].funcptr = dlsym(lib, funcs[i].name); - if (!*funcs[i].funcptr) - break; - } - if (funcs[i].name) - { - Con_DPrintf("Unable to find symbol \"%s\" in \"%s\"\n", funcs[i].name, name); -// Sys_CloseLibrary((dllhandle_t*)lib); - lib = NULL; - } - } - - return (dllhandle_t*)lib; -} -#endif -#endif - static cvar_t *cef_incognito; static cvar_t *cef_allowplugins; @@ -810,7 +728,7 @@ browser_subs(context_menu_handler); nb->render_handler.on_popup_show = NULL; nb->render_handler.on_popup_size = NULL; nb->render_handler.on_paint = browser_on_paint; - nb->render_handler.on_cursor_change = NULL; +// nb->render_handler.on_cursor_change = NULL; nb->render_handler.start_dragging = NULL; nb->render_handler.update_drag_cursor = NULL; nb->render_handler.on_scroll_offset_changed = NULL; @@ -934,24 +852,28 @@ static void CEF_CALLBACK browser_process_handler_on_context_initialized(cef_brow } static void CEF_CALLBACK browser_process_handler_on_before_child_process_launch(cef_browser_process_handler_t* self, cef_command_line_t* command_line) { - char arg[2048]; + char *arg = "--plugwrapper"; + char *funcname = "CefSubprocessInit"; cef_string_t cefisannoying = {NULL}; - Q_snprintf(arg, sizeof(arg), "--plugwrapper %s CefSubprocessInit", plugname); cef_string_from_utf8(arg, strlen(arg), &cefisannoying); command_line->append_argument(command_line, &cefisannoying); - cef_string_clear(&cefisannoying); + cef_string_from_utf8(plugname, strlen(plugname), &cefisannoying); + command_line->append_argument(command_line, &cefisannoying); + cef_string_from_utf8(funcname, strlen(funcname), &cefisannoying); + command_line->append_argument(command_line, &cefisannoying); // MessageBoxW(NULL, command_line->GetCommandLineString().c_str(), L"CEF", 0); - + cef_string_clear(&cefisannoying); cef_release(command_line); } static void CEF_CALLBACK render_process_handler_on_context_created(cef_render_process_handler_t* self, cef_browser_t* browser, cef_frame_t* frame, cef_v8context_t* context) { - cef_string_t key = makecefstring("fte_query"); cef_v8value_t *jswindow = context->get_global(context); +//eg: window.fte_query("getstats", function(req,res){console.log("health: "+JSON.parse(res)[0/*STAT_HEALTH*/]);}); + cef_string_t key = makecefstring("fte_query"); jswindow->set_value_bykey(jswindow, &key, cef_v8value_create_function(&key, &v8handler_query), V8_PROPERTY_ATTRIBUTE_READONLY | V8_PROPERTY_ATTRIBUTE_DONTENUM | V8_PROPERTY_ATTRIBUTE_DONTDELETE); cef_string_clear(&key); @@ -1669,17 +1591,17 @@ static void *Cef_Create(const char *name, struct mediacallbacks_s *callbacks) // settings.command_line_args_disabled = true; // settings.persist_session_cookies = false; - { +/* { char *s; strcpy(utf8, FULLENGINENAME "/" STRINGIFY(FTE_VER_MAJOR) "." STRINGIFY(FTE_VER_MINOR)); while((s = strchr(utf8, ' '))) *s = '_'; cef_string_from_utf8(utf8, strlen(utf8), &settings.product_version); } - +*/ cefwasinitialised = !!cef_initialize(&mainargs, &settings, &app, NULL); cef_string_clear(&settings.browser_subprocess_path); - cef_string_clear(&settings.product_version); +// cef_string_clear(&settings.product_version); cef_string_clear(&settings.cache_path); cef_string_clear(&settings.log_file); } @@ -1970,6 +1892,8 @@ static void VARGS Cef_ChangeStream (void *ctx, const char *streamname) browser->thebrowser->go_back(browser->thebrowser); else if (!strcmp(cmd, "forward")) browser->thebrowser->go_forward(browser->thebrowser); + else if (!strcmp(cmd, "home")) + Cef_ChangeStream(ctx, "http://fte.triptohell.info"); else { frame = browser->thebrowser->get_focused_frame(browser->thebrowser); @@ -2145,9 +2069,11 @@ static void SetupArgv(cef_main_args_t *a) } #endif -//if we're a subprocess and somehow failed to add the --dllwrapper arg to the engine, then make sure we're not starting endless processes. +//if we're a subprocess and somehow failed to add the --plugwrapper arg to the engine, then make sure we're not starting endless processes. static qboolean Cef_Init(qboolean engineprocess) { + static qboolean cefwasloaded = qfalse; + #ifdef _WIN32 cef_main_args_t args = {GetModuleHandle(NULL)}; #else @@ -2155,6 +2081,9 @@ static qboolean Cef_Init(qboolean engineprocess) SetupArgv(&args); #endif + if (cefwasloaded) + return qtrue; + { int result; @@ -2191,7 +2120,8 @@ static qboolean Cef_Init(qboolean engineprocess) {(void **)&cef_string_list_value, "cef_string_list_value"}, {NULL} }; - if (!Sys_LoadLibrary("libcef", ceffuncs)) + + if (plugfuncs && !plugfuncs->LoadDLL("./libcef", ceffuncs)) { if (engineprocess) Con_Printf("Unable to load libcef (version "CEF_VERSION")\n"); @@ -2201,7 +2131,7 @@ static qboolean Cef_Init(qboolean engineprocess) if (engineprocess) { - Con_Printf("libcef %i.%i: chrome %i.%i (%i)\n", cef_version_info(0), cef_version_info(1), cef_version_info(2), cef_version_info(3), cef_version_info(4)); + Con_DPrintf("libcef %i.%i.%i.%i (chrome %i.%i.%i.%i)\n", cef_version_info(0), cef_version_info(1), cef_version_info(2), cef_version_info(3), cef_version_info(4), cef_version_info(5), cef_version_info(6), cef_version_info(7)); if (cef_version_info(0) != CEF_VERSION_MAJOR|| cef_version_info(1) != CEF_VERSION_MINOR|| @@ -2218,21 +2148,24 @@ static qboolean Cef_Init(qboolean engineprocess) } } - app_initialize(); - result = cef_execute_process(&args, &app, 0); - if (result >= 0 || !engineprocess) - { //result is meant to be the exit code that the child process is meant to exit with - //either way, we really don't want to return to the engine because that would run a second instance of it. - exit(result); - return qfalse; + if (!engineprocess) + { + result = cef_execute_process(&args, &app, 0); + if (result >= 0 || !engineprocess) + { //result is meant to be the exit code that the child process is meant to exit with + //either way, we really don't want to return to the engine because that would run a second instance of it. + exit(result); + return qfalse; + } } } - return qtrue; + return cefwasloaded=qtrue; } -//works with the --dllwrapper engine argument -int NATIVEEXPORT CefSubprocessInit(void) +//works with the --plugwrapper engine argument +int NATIVEEXPORT CefSubprocessInit(plugcorefuncs_t *corefuncs) { + plugfuncs = corefuncs; return Cef_Init(false); } @@ -2242,7 +2175,7 @@ static qintptr_t Cef_ExecuteCommand(qintptr_t *args) cmdfuncs->Argv(0, cmd, sizeof(cmd)); if (!strcmp(cmd, "cef")) { - if (confuncs) + if (confuncs && Cef_Init(true)) { static int sequence; char f[128]; @@ -2270,12 +2203,21 @@ static qintptr_t Cef_ExecuteCommand(qintptr_t *args) return false; } +static qboolean QDECL Cef_PluginMayUnload(void) +{ + if (cefwasinitialised) + return false; //cef is a piece of shite. we have to leave it running or the threads it spawns will crash+burn... + return true; +} + qboolean Plug_Init(void) { fsfuncs = (plugfsfuncs_t*)plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*fsfuncs)); //for fte://data/ scheme confuncs = (plugsubconsolefuncs_t*)plugfuncs->GetEngineInterface(plugsubconsolefuncs_name, sizeof(*confuncs)); //for cef command etc. clientfuncs = (plugclientfuncs_t*)plugfuncs->GetEngineInterface(plugclientfuncs_name, sizeof(*clientfuncs)); //for weird people trying to use xml requests to query game status (for hud stuff) if (!fsfuncs || !confuncs || !clientfuncs + || !plugfuncs->GetPluginName(-1, plugname, sizeof(plugname)) + || !plugfuncs->ExportFunction("MayUnload", Cef_PluginMayUnload) || !plugfuncs->ExportFunction("Tick", Cef_Tick) || !plugfuncs->ExportFunction("Shutdown", Cef_Shutdown)) { diff --git a/plugins/plugin.h b/plugins/plugin.h index 00d7fd40..be8ed821 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -357,7 +357,9 @@ extern plugcorefuncs_t *plugfuncs; extern plugcmdfuncs_t *cmdfuncs; extern plugcvarfuncs_t *cvarfuncs; -#ifndef FTEENGINE +#ifdef FTEENGINE +extern plugcorefuncs_t plugcorefuncs; +#else void Q_strlncpy(char *d, const char *s, int sizeofd, int lenofs); void Q_strlcpy(char *d, const char *s, int n); void Q_strlcat(char *d, const char *s, int n);