From 5bc4359a2f39d622a7d4b636d5f1931c56eb0f17 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 20 Jun 2021 19:54:50 +0300 Subject: [PATCH] engine: implement platform neutral saves, now we can load saves made in Linux on Windows and vice-versa --- engine/common/lib_common.c | 345 ++++++++++++++++++++++++++++++ engine/common/library.h | 27 +++ engine/platform/posix/lib_posix.c | 7 +- 3 files changed, 376 insertions(+), 3 deletions(-) diff --git a/engine/common/lib_common.c b/engine/common/lib_common.c index 6a9fd715..630a7b91 100644 --- a/engine/common/lib_common.c +++ b/engine/common/lib_common.c @@ -17,6 +17,7 @@ GNU General Public License for more details. #include "library.h" #include "filesystem.h" #include "server.h" +#include static char s_szLastError[1024] = ""; @@ -39,10 +40,35 @@ void COM_PushLibraryError( const char *error ) void *COM_FunctionFromName_SR( void *hInstance, const char *pName ) { + char **funcs = NULL; + size_t numfuncs, i; + void *f = NULL; + #ifdef XASH_ALLOW_SAVERESTORE_OFFSETS if( !memcmp( pName, "ofs:",4 ) ) return (byte*)svgame.dllFuncs.pfnGameInit + Q_atoi(pName + 4); #endif + +#if XASH_MSVC && XASH_X86 + funcs = COM_ConvertToLocalPlatform( MANGLE_VALVE, pName, &numfuncs ); +#elif XASH_POSIX + funcs = COM_ConvertToLocalPlatform( MANGLE_ITANIUM, pName, &numfuncs ); +#endif + + if( funcs ) + { + for( i = 0; i < numfuncs; i++ ) + { + f = COM_FunctionFromName( hInstance, funcs[i] ); + Z_Free( funcs[i] ); + if( f ) + break; + } + Z_Free( funcs ); + + if( f ) return f; + } + return COM_FunctionFromName( hInstance, pName ); } @@ -175,3 +201,322 @@ void COM_GetCommonLibraryPath( ECommonLibraryType eLibType, char *out, size_t si break; } } + +/* +============================================================================= + + C++ MANGLE CONVERSION + +============================================================================= +*/ +#define MAX_NESTED_NAMESPACES 16 /* MSVC limit */ + +static EFunctionMangleType COM_DetectMangleType( const char *str ) +{ + // Itanium C++ ABI mangling always start with _Z + // namespaces start with N, therefore _ZN + if( !Q_strncmp( str, "_ZN", 3 ) ) + return MANGLE_ITANIUM; + + // MSVC C++ mangling always start with ? and have + if( str[0] == '?' && Q_strstr( str, "@@" )) + return MANGLE_MSVC; + + // allow offsets, we just silently ignore them on conversion + if( !Q_strncmp( str, "ofs:", 4 )) + return MANGLE_OFFSET; + + // don't get confused by MSVC C mangling + if( str[0] != '@' && Q_strchr( str, '@' )) + return MANGLE_VALVE; + + // not technically an error + return MANGLE_UNKNOWN; +} + +char *COM_GetMSVCName( const char *in_name ) +{ + static string out_name; + char *pos; + + if( in_name[0] == '?' ) // is this a MSVC C++ mangled name? + { + if(( pos = Q_strstr( in_name, "@@" )) != NULL ) + { + ptrdiff_t len = pos - in_name; + + // strip off the leading '?' + Q_strncpy( out_name, in_name + 1, sizeof( out_name )); + out_name[len-1] = 0; // terminate string at the "@@" + return out_name; + } + } + + Q_strncpy( out_name, in_name, sizeof( out_name )); + + return out_name; +} + +static char *COM_GetItaniumName( const char * const in_name ) +{ + static string out_name; + const char *f = in_name; + string symbols[16]; + uint len = 0; + int i; + int remaining; + + remaining = Q_strlen( f ); + + if( remaining < 3 ) + goto invalid_format; + + out_name[0] = 0; + + // skip _ZN + f += 3; + remaining -= 3; + + for( i = 0; i < MAX_NESTED_NAMESPACES; i++ ) + { + // parse symbol length marker + len = 0; + for( ; isdigit( *f ) && remaining > 0; f++, remaining-- ) + len = len * 10 + ( *f - '0' ); + + // sane value + len = min( remaining, len ); + + if( len == 0 ) + goto invalid_format; + + Q_strncpy( symbols[i], f, min( len + 1, sizeof( out_name ))); + f += len; + remaining -= len; + + // end marker + if( *f == 'E' ) + break; + + if( !isdigit( *f ) || remaining <= 0 ) + goto invalid_format; + } + + if( i == MAX_NESTED_NAMESPACES ) + { + Con_DPrintf( "%s: too much nested namespaces: %s\n", __FUNCTION__, in_name ); + return NULL; + } + + for( ; i >= 0; i-- ) + { + Q_strncat( out_name, symbols[i], sizeof( out_name )); + if( i > 0 ) + Q_strncat( out_name, "@", sizeof( out_name )); + } + + return out_name; + +invalid_format: + Con_DPrintf( "%s: invalid format: %s\n", __FUNCTION__, in_name ); + return NULL; +} + +char **COM_ConvertToLocalPlatform( EFunctionMangleType to, const char *from, size_t *numfuncs ) +{ + string symbols[MAX_NESTED_NAMESPACES], temp, temp2; + const char *prev; + const char *postfix[3]; + int i = 0; + char **ret; + + // TODO: + if( to == MANGLE_MSVC ) + return NULL; + + switch( to ) + { + case MANGLE_ITANIUM: + postfix[0] = "Ev"; + postfix[1] = "EP11CBaseEntity"; + postfix[2] = "EP11CBaseEntityS1_8USE_TYPEf"; + break; + default: + ASSERT( 0 ); + return NULL; + } + + prev = from; + + for( i = 0; i < MAX_NESTED_NAMESPACES; i++ ) + { + const char *at = Q_strchr( prev, '@' ); + uint len; + + if( at ) len = (uint)( at - prev ); + else len = (uint)Q_strlen( prev ); + Q_strncpy( symbols[i], prev, min( len + 1, sizeof( symbols[i] ))); + prev = at + 1; + + if( !at ) + break; + } + + if( i == MAX_NESTED_NAMESPACES ) + { + Con_DPrintf( "%s: too much nested namespaces: %s\n", __FUNCTION__, from ); + return NULL; + } + + // only three possible variations + *numfuncs = ARRAYSIZE( postfix ); + ret = Z_Malloc( sizeof( char * ) * ARRAYSIZE( postfix ) ); + + Q_strncpy( temp, "_ZN", sizeof( temp )); + + for( ; i >= 0; i-- ) + { + Q_snprintf( temp2, sizeof( temp2 ), "%u%s", (uint)Q_strlen( symbols[i] ), symbols[i] ); + Q_strncat( temp, temp2, sizeof( temp )); + } + + for( i = 0; i < ARRAYSIZE( postfix ); i++ ) + { + Q_snprintf( temp2, sizeof( temp2 ), "%s%s", temp, postfix[i] ); + ret[i] = copystring( temp2 ); + } + + return ret; +} + +const char *COM_GetPlatformNeutralName( const char *in_name ) +{ + EFunctionMangleType type = COM_DetectMangleType( in_name ); + + switch( type ) + { + case MANGLE_ITANIUM: return COM_GetItaniumName( in_name ); + case MANGLE_MSVC: return COM_GetMSVCName( in_name ); + default: return in_name; + } +} + +#if XASH_ENGINE_TESTS +#include "tests.h" + +static void Test_DetectMangleType( void ) +{ + TASSERT(COM_DetectMangleType( "asdf" ) == MANGLE_UNKNOWN ); + TASSERT(COM_DetectMangleType( "012345" ) == MANGLE_UNKNOWN ); + TASSERT(COM_DetectMangleType( "?asdf" ) == MANGLE_UNKNOWN ); + TASSERT(COM_DetectMangleType( "_Zasdf" ) == MANGLE_UNKNOWN ); + + TASSERT(COM_DetectMangleType( "ofs:1234" ) == MANGLE_OFFSET ); + TASSERT(COM_DetectMangleType( "ofs:asdf" ) == MANGLE_OFFSET ); + TASSERT(COM_DetectMangleType( "ofs:" ) == MANGLE_OFFSET ); + + TASSERT(COM_DetectMangleType( "_ZN1f1fEv" ) == MANGLE_ITANIUM ); + TASSERT(COM_DetectMangleType( "_ZN3foo3barEv" ) == MANGLE_ITANIUM ); + + TASSERT(COM_DetectMangleType( "?f@f@@msvcsucks" ) == MANGLE_MSVC ); + TASSERT(COM_DetectMangleType( "?foo@bar@@IHATEMSVC" ) == MANGLE_MSVC ); + + TASSERT(COM_DetectMangleType( "f@f" ) == MANGLE_VALVE ); + TASSERT(COM_DetectMangleType( "foo@bar" ) == MANGLE_VALVE ); + + // Xash3D FWGS extensions test + TASSERT(COM_DetectMangleType( "_ZN1f1f1fEv" ) == MANGLE_ITANIUM ); + TASSERT(COM_DetectMangleType( "_ZN3foo3bar3bazEv" ) == MANGLE_ITANIUM ); + + TASSERT(COM_DetectMangleType( "?f@f@f@@msvcsucks" ) == MANGLE_MSVC ); + TASSERT(COM_DetectMangleType( "?foo@bar@@IHATEMSVC" ) == MANGLE_MSVC ); + + TASSERT(COM_DetectMangleType( "f@f@f" ) == MANGLE_VALVE ); + TASSERT(COM_DetectMangleType( "foo@bar@baz" ) == MANGLE_VALVE ); +} + +static void Test_GetMSVCName( void ) +{ + const char *symbols[] = + { + "", "", + "?f@f@@XYZA", "f@f", + "?foo@bar@@QAEXXZ", "foo@bar", + "foo", "foo", + "?foo", "?foo", + "?foo@@", "foo", // not an error? + "?foo@bar@baz@@gotstrippedanyway","foo@bar@baz" + }; + int i; + + for( i = 0; i < ARRAYSIZE( symbols ); i += 2 ) + { + Msg( "Checking if MSVC '%s' converts to '%s'...\n", symbols[i], symbols[i+1] ); + + TASSERT( !Q_strcmp( COM_GetMSVCName( symbols[i] ), symbols[i+1] )); + } +} + +static void Test_GetItaniumName( void ) +{ + const char *symbols[] = + { + "", NULL, + "_", NULL, + "_Z", NULL, + "_ZN", NULL, + "_ZNv", NULL, + "_ZN4barr3foo", NULL, + "_ZN3bar3foov", NULL, + "_ZN4bar3fooEv", NULL, + "_ZN3bar3fooEv", "foo@bar", + "_Z3foov", NULL, + "_ZN3fooEv", "foo", // not possible? + "_ZN3baz3bar3fooEdontcare", "foo@bar@baz", + }; + int i; + + for( i = 0; i < ARRAYSIZE( symbols ); i += 2 ) + { + Msg( "Checking if Itanium '%s' converts to '%s'...\n", symbols[i], symbols[i+1] ); + + TASSERT( !Q_strcmp( COM_GetItaniumName( symbols[i] ), symbols[i+1] )); + } +} + +static void Test_ConvertFromValveToLocal( void ) +{ + const char *symbols[] = + { + "", "_ZN", + "foo", "_ZN3foo", + "xash3d@fwgs", "_ZN4fwgs6xash3d", + "foo@bar@bazz", "_ZN4bazz3bar3foo" + }; + int i; + + for( i = 0; i < ARRAYSIZE( symbols ); i += 2 ) + { + char **ret; + size_t numfuncs; + size_t symlen = Q_strlen( symbols[i + 1] ); + + Msg( "Checking if Valve '%s' converts to Itanium '%s'...\n", symbols[i], symbols[i+1] ); + + ret = COM_ConvertToLocalPlatform( MANGLE_ITANIUM, symbols[i], &numfuncs ); + + TASSERT( numfuncs == 3 ); + TASSERT( !Q_strncmp( ret[0], symbols[i+1], symlen )); + TASSERT( !Q_strncmp( ret[1], symbols[i+1], symlen )); + TASSERT( !Q_strncmp( ret[2], symbols[i+1], symlen )); + } +} + +void Test_RunLibCommon( void ) +{ + TRUN( Test_DetectMangleType() ); + TRUN( Test_GetMSVCName() ); + TRUN( Test_GetItaniumName() ); + TRUN( Test_ConvertFromValveToLocal() ); +} +#endif /* XASH_ENGINE_TESTS */ diff --git a/engine/common/library.h b/engine/common/library.h index 6d7afdbb..c9738b07 100644 --- a/engine/common/library.h +++ b/engine/common/library.h @@ -58,4 +58,31 @@ typedef enum void COM_GetCommonLibraryPath( ECommonLibraryType eLibType, char *out, size_t size ); +typedef enum +{ + MANGLE_UNKNOWN = 0, + + /* binary offset, when NameForFunction isn't implemented */ + MANGLE_OFFSET, + + /* Itanium C++ ABI mangling, native for most operating systems */ + MANGLE_ITANIUM, + + /* MSVC "decoration" */ + MANGLE_MSVC, + + /* Valve's silly mangle for crossplatform saves */ + MANGLE_VALVE, +} EFunctionMangleType; + +// converts to MANGLE_VALVE if possible +const char *COM_GetPlatformNeutralName( const char *in_name ); + +// converts to native mangling, result must be freed +char **COM_ConvertToLocalPlatform( EFunctionMangleType to, const char *from, size_t *numfuncs ); + +// used by lib_win.c +char *COM_GetMSVCName( const char *in_name ); + + #endif//LIBRARY_H diff --git a/engine/platform/posix/lib_posix.c b/engine/platform/posix/lib_posix.c index de1d8f8a..9e6af461 100644 --- a/engine/platform/posix/lib_posix.c +++ b/engine/platform/posix/lib_posix.c @@ -220,15 +220,16 @@ const char *COM_NameForFunction( void *hInstance, void *function ) #ifdef XASH_DLL_LOADER void *wm; if( host.enabledll && (wm = Loader_GetDllHandle( hInstance )) ) +#error ConvertMangledName return Loader_GetFuncName_int(wm, function); else #endif // NOTE: dladdr() is a glibc extension { Dl_info info = {0}; - dladdr((void*)function, &info); - if(info.dli_sname) - return info.dli_sname; + dladdr( (void*)function, &info ); + if( info.dli_sname ) + return COM_GetPlatformNeutralName( info.dli_sname ); } #ifdef XASH_ALLOW_SAVERESTORE_OFFSETS return COM_OffsetNameForFunction( function );