engine: implement platform neutral saves, now we can load saves made in Linux on Windows and vice-versa

This commit is contained in:
Alibek Omarov 2021-06-20 19:54:50 +03:00
parent 17d3eaa590
commit 5bc4359a2f
3 changed files with 376 additions and 3 deletions

View File

@ -17,6 +17,7 @@ GNU General Public License for more details.
#include "library.h"
#include "filesystem.h"
#include "server.h"
#include <ctype.h>
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 */

View File

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

View File

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