1
0
Fork 0
forked from fte/fteqw

Fix up CEF plugin a little.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@6066 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2021-10-05 05:05:15 +00:00
parent 0eecce227e
commit b91273a930
6 changed files with 206 additions and 153 deletions

View file

@ -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")

View file

@ -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;

View file

@ -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))

View file

@ -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))
{

View file

@ -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 <dlfcn.h>
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))
{

View file

@ -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);