- Added support for custom symbols.txt path (-s <path>). - Added support for dlsym symbol lookup in linux bins (-n). - Updated gdc_core.sh example script to account for new options above. - Updated symbols.txt.
		
			
				
	
	
		
			552 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			552 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <stdio.h>
 | |
| #include <unistd.h>
 | |
| #include <dlfcn.h>
 | |
| #include <fcntl.h>
 | |
| #include <math.h>
 | |
| #include <iostream>
 | |
| #include "gdc.h"
 | |
| #include "GameConfigs.h"
 | |
| #include "MemoryUtils.h"
 | |
| 
 | |
| MemoryUtils mu;
 | |
| 
 | |
| char *game = NULL;
 | |
| char *engine = NULL;
 | |
| char *game_binary = NULL;
 | |
| char *engine_binary = NULL;
 | |
| char *wgame_binary = NULL;
 | |
| char *wengine_binary = NULL;
 | |
| char *symbols_file = NULL;
 | |
| 
 | |
| bool use_symtab = true;
 | |
| 
 | |
| inline bool IsDigit( char c )
 | |
| {
 | |
| 	return c < 58 && c > 47;
 | |
| }
 | |
| 
 | |
| void PrintUsage()
 | |
| {
 | |
| 	printf("Usage: ./gdc -g <game> -e <engine name> -f <gamedata file> -b <game binary> -x <engine binary> -w <win game binary> -y <win engine binary> [-s <symbols.txt file>]\n");
 | |
| 	printf("Other options:\n");
 | |
| 	printf(" -d\tAdd debug output\n");
 | |
| 	printf(" -n\tDon't use symbol table for lookups (falls back to dlsym)\n");
 | |
| }
 | |
| 
 | |
| int main(int argc, char **argv)
 | |
| {
 | |
| 	char *gamedata = NULL;
 | |
| 	bool debug = false;
 | |
| 
 | |
| 	opterr = 0;
 | |
| 	int opt;
 | |
| 	while ((opt = getopt(argc, argv, "b:nde:f:g:x:w:y:s:")) != -1)
 | |
| 	{
 | |
| 		switch (opt)
 | |
| 		{
 | |
| 			case 'd':
 | |
| 				debug = true;
 | |
| 				break;
 | |
| 
 | |
| 			case 'n':
 | |
| 				use_symtab = false;
 | |
| 				break;
 | |
| 
 | |
| 			case 'g':
 | |
| 				game = optarg;
 | |
| 				break;
 | |
| 
 | |
| 			case 'e':
 | |
| 				engine = optarg;
 | |
| 				break;
 | |
| 
 | |
| 			case 'f':
 | |
| 				gamedata = optarg;
 | |
| 				break;
 | |
| 
 | |
| 			case 'b':
 | |
| 				game_binary = optarg;
 | |
| 				break;
 | |
| 
 | |
| 			case 'x':
 | |
| 				engine_binary = optarg;
 | |
| 				break;
 | |
| 
 | |
| 			case 'w':
 | |
| 				wgame_binary = optarg;
 | |
| 				break;
 | |
| 
 | |
| 			case 'y':
 | |
| 				wengine_binary = optarg;
 | |
| 				break;
 | |
| 
 | |
| 			case 's':
 | |
| 				symbols_file = optarg;
 | |
| 				break;
 | |
| 
 | |
| 			case '?':
 | |
| 				PrintUsage();
 | |
| 				return 0;
 | |
| 
 | |
| 			default:
 | |
| 				printf("WHAT\n");
 | |
| 				return 0;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (!game || !engine || !gamedata || !game_binary || !engine_binary || !wgame_binary || !wengine_binary)
 | |
| 	{
 | |
| 		PrintUsage();
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	printf("Game: %s\n", game);
 | |
| 	if (debug)
 | |
| 	{
 | |
| 		printf("Engine: %s\nGame binary: %s\nEngine binary: %s\nWin game binary: %s\nWin engine binary: %s\n",
 | |
| 			engine, game_binary, engine_binary, wgame_binary, wengine_binary
 | |
| 			);
 | |
| 	}
 | |
| 	printf("Gamedata: %s\n\n", gamedata);
 | |
| 
 | |
| 	void *ghandle;
 | |
| 	ghandle = dlopen(game_binary, RTLD_LAZY);
 | |
| 	if (!ghandle)
 | |
| 	{
 | |
| 		printf("Couldn't open %s (%s)\n", game_binary, dlerror());
 | |
| 			return 0;
 | |
| 	}
 | |
| 	void *ehandle;
 | |
| 	ehandle = dlopen(engine_binary, RTLD_LAZY);
 | |
| 	if (!ehandle)
 | |
| 	{
 | |
| 		printf("Couldn't open %s (%s)\n", engine_binary, dlerror());
 | |
| 			return 0;
 | |
| 	}
 | |
| 
 | |
| 	int wgfile = open(wgame_binary, O_RDONLY);
 | |
| 	if (wgfile == -1)
 | |
| 	{
 | |
| 		printf("Couldn't open %s\n", wgame_binary);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	int wefile = open(wengine_binary, O_RDONLY);
 | |
| 	if (wefile == -1)
 | |
| 	{
 | |
| 		printf("Couldn't open %s\n", wengine_binary);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	CGameConfig gc;
 | |
| 	char err[512];
 | |
| 	if (!gc.EnterFile(gamedata, err, sizeof(err)))
 | |
| 	{
 | |
| 		printf("%s: %s\n", gamedata, err);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	CGameConfig symbols;
 | |
| 	if (!symbols.EnterFile(symbols_file ? symbols_file : "symbols.txt", err, sizeof(err)))
 | |
| 	{
 | |
| 		printf("symbols.txt: %s\n", err);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	for (list<Offset>::iterator it = gc.m_Offsets.begin(); it != gc.m_Offsets.end(); it++)
 | |
| 	{
 | |
| 		if (debug)
 | |
| 		{
 | |
| 			printf("DEBUG %s - l: %d w: %d\n", it->name, it->lin, it->win);
 | |
| 		}
 | |
| 
 | |
| 		const char *symbol = symbols.GetKeyValue(it->name);
 | |
| 		if (symbol)
 | |
| 		{
 | |
| 			char symvt[128] = "_ZTV";
 | |
| 			bool bGotFirstNumbers = false;
 | |
| 			bool bLastNumberDigit = false;
 | |
| 			unsigned int symlen = strlen(symbol);
 | |
| 			unsigned int pos = strlen(symvt);
 | |
| 			
 | |
| 			for ( unsigned int i = 0; i < symlen && pos < (sizeof(symvt)-1); ++i )
 | |
| 			{
 | |
| 				bool isDigit = IsDigit( symbol[i] );
 | |
| 				if( !isDigit && bLastNumberDigit )
 | |
| 				{
 | |
| 					bGotFirstNumbers = true;
 | |
| 				}
 | |
| 				
 | |
| 				// we're before the class len
 | |
| 				if( !bGotFirstNumbers && !isDigit )
 | |
| 					continue;
 | |
| 				
 | |
| 				// we're at the function name len
 | |
| 				if( bGotFirstNumbers && isDigit )
 | |
| 				{
 | |
| 					symvt[pos] = 0;
 | |
| 					break;
 | |
| 				}
 | |
| 				
 | |
| 				// we're at the class len or class name. we want these
 | |
| 				symvt[pos++] = symbol[i];
 | |
| 				if( isDigit )
 | |
| 				{
 | |
| 					bLastNumberDigit = isDigit;
 | |
| 				}
 | |
| 			}
 | |
| 			symvt[pos] = 0;
 | |
| 			
 | |
| 			int newOffset;
 | |
| 			if (symvt[0])
 | |
| 			{
 | |
|                 void **newvt = NULL;
 | |
| 				if( use_symtab )
 | |
| 				{
 | |
| 					newvt = (void**)mu.ResolveSymbol(ghandle, symvt);
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					newvt = (void **)dlsym(ghandle, symvt);
 | |
| 				}
 | |
| 				
 | |
| 			    if (!newvt)
 | |
| 				{
 | |
| 					printf("? O: %-22s - can't find, skipping\n", symvt);
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				newOffset = findVFunc(ghandle, newvt, symbol);
 | |
| 			}
 | |
| 
 | |
| 			if (newOffset == it->lin)
 | |
| 			{
 | |
| 				printf("  O: %-22s - GOOD.    current [ w: %3d, l: %3d ].\n", it->name, it->win, it->lin);
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				printf("! O: %-22s - CHANGED. old [ w: %3d, l: %3d ]. new [ w: %3d, l: %3d ].\n",
 | |
| 					it->name,
 | |
| 					it->win, it->lin,
 | |
| 					newOffset - (it->lin - it->win), newOffset
 | |
| 					);
 | |
| 			}
 | |
| 		}
 | |
| 		else // !symbol
 | |
| 		{
 | |
| 			printf("  O: %-22s - no Linux symbol, skipping\n", it->name);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	printf("\nWindows offsets are (semi-)wild guesses!\n\n");
 | |
| 
 | |
| 	for (list<Sig>::iterator it = gc.m_Sigs.begin(); it != gc.m_Sigs.end(); it++)
 | |
| 	{
 | |
| 		if (debug)
 | |
| 		{
 | |
| 			printf("DEBUG %s - %s - l: %s\n", it->name, it->lib == Server ? "server" : "engine", it->lin);
 | |
| 		}
 | |
| 
 | |
| 		const char *linSymbol = it->lin;
 | |
| 		const char *winSymbol = it->win;
 | |
| 		bool hasLinux   = (linSymbol && linSymbol[0]);
 | |
| 		bool hasWindows = (winSymbol && winSymbol[0]);
 | |
| 		if (!hasLinux && !hasWindows)
 | |
| 		{
 | |
| 			printf("  S: %-22s - hasn't linux nor windows data, skipping\n", it->name);
 | |
| 			continue;
 | |
| 		}
 | |
| 		
 | |
| 		int  winFile;
 | |
| 		void *linHandle;
 | |
| 		if (it->lib == Server)
 | |
| 		{
 | |
| 			winFile   = wgfile;
 | |
| 			linHandle = ghandle;
 | |
| 		}
 | |
| 		else if (it->lib == Engine)
 | |
| 		{
 | |
| 			winFile   = wefile;
 | |
| 			linHandle = ehandle;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			printf("  S: %-22s - isn't from server nor engine, skipping\n", it->name);
 | |
| 			continue;
 | |
| 		}
 | |
| 		
 | |
| 		int linuxMatches = 0, windowsMatches = 0;
 | |
| 		if( hasLinux )
 | |
| 			linuxMatches = checkSigStringL(linHandle, linSymbol);
 | |
| 		if( hasWindows )
 | |
| 			windowsMatches = checkSigStringW(winFile,   winSymbol);
 | |
| 			
 | |
| 		if (linuxMatches == 1 && windowsMatches == 1)
 | |
| 		{
 | |
| 			// too much clutter to print current data if it matches anyway, unlike with offsets
 | |
| 			/*
 | |
| 			printf("%-22s (%s) - GOOD. current:\n\t[w:%s]\n\t[l:%s]\n",
 | |
| 				it->name,
 | |
| 				(it->lib == Server) ? "server" : "engine",
 | |
| 				winSymbol ? winSymbol : "",
 | |
| 				linSymbol ? linSymbol : ""
 | |
| 				);
 | |
| 			*/
 | |
| 			printf("  S: %-22s (%s) - w: GOOD     - l: GOOD    \n",
 | |
| 				it->name,
 | |
| 				(it->lib == Server) ? "server" : "engine"
 | |
| 				);
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			char winStatus[16];
 | |
| 			// right now the only option is to ignore
 | |
| 			const char *options = symbols.GetOptionValue(it->name);
 | |
| 			bool allowMulti = ( options && strstr(options, "allowmultiple") != NULL );
 | |
| 			bool allowMidfunc = ( options && strstr(options, "allowmidfunc") != NULL );
 | |
| 			
 | |
| 			bool bWinGood = false;
 | |
| 			if( !hasWindows ) {
 | |
| 				snprintf( winStatus, sizeof(winStatus), "UNKNOWN" );
 | |
| 			}
 | |
| 			else if( windowsMatches == -1 && !allowMidfunc) {
 | |
| 				snprintf( winStatus, sizeof(winStatus), "MIDFUNC" );
 | |
| 			}
 | |
| 			else if( windowsMatches == 0 ) {
 | |
| 				snprintf( winStatus, sizeof(winStatus), "NOTFOUND" );
 | |
| 			}
 | |
| 			else if( windowsMatches > 1 && !allowMulti) {
 | |
| 				snprintf( winStatus, sizeof(winStatus), "MULTIPLE" );
 | |
| 			}
 | |
| 			else {
 | |
| 				bWinGood = true;
 | |
| 				snprintf( winStatus, sizeof(winStatus), "GOOD" );
 | |
| 			}
 | |
| 			
 | |
| 			char linStatus[16];
 | |
| 			bool bLinGood = false;
 | |
| 			if( !hasLinux ) {
 | |
| 				snprintf( linStatus, sizeof(linStatus), "UNKNOWN" );
 | |
| 			}
 | |
| 			else if( linuxMatches == 0 ) {
 | |
| 				snprintf( linStatus, sizeof(linStatus), "NOTFOUND" );
 | |
| 			}
 | |
| 			else if( linuxMatches == 1 ) {
 | |
| 				bLinGood = true;
 | |
| 				snprintf( linStatus, sizeof(linStatus), "GOOD" );
 | |
| 			}
 | |
| 			else {
 | |
| 				snprintf( linStatus, sizeof(linStatus), "CHECK" );
 | |
| 			}
 | |
| 			
 | |
| 			bool showWinExtra = (hasWindows && !bWinGood);
 | |
| 			bool showLinExtra = ( ( hasLinux && !bLinGood ) || showWinExtra );
 | |
| 			
 | |
| 			printf("%s S: %-22s (%s)", (showWinExtra || showLinExtra) ? "!" : " ", it->name, (it->lib == Server) ? "server" : "engine" );
 | |
| 				printf(" - w: %-8s - l: %-8s\n", winStatus, linStatus );
 | |
| 			
 | |
| 			if( showWinExtra || showLinExtra )
 | |
| 				printf( "!    current:\n" );
 | |
| 			
 | |
| 			if( showWinExtra )
 | |
| 			{
 | |
| 				printf("!    w: \"%s\"\n",
 | |
| 					winSymbol ? winSymbol : ""
 | |
| 					);
 | |
| 			}
 | |
| 			
 | |
| 			if( showLinExtra )
 | |
| 			{
 | |
| 				// extra \n at end is intentional to add buffer after possibly long sigs
 | |
| 				printf("!    l: \"%s\"\n\n",
 | |
| 					linSymbol ? linSymbol : ""
 | |
| 					);
 | |
| 			}			
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int checkSigStringW(int file, const char* symbol)
 | |
| {
 | |
| 	int matches = 0;
 | |
| 	bool atFuncStart = true;
 | |
| 	bool isAt = (symbol[0] == '@');
 | |
| 	// we can't support this on windows from here
 | |
| 	if (isAt)
 | |
| 		return false;
 | |
| 	
 | |
| 	unsigned char real_sig[511];
 | |
| 	size_t real_bytes = UTIL_DecodeHexString(real_sig, sizeof(real_sig), symbol);
 | |
| 
 | |
| 	if (real_bytes >= 1)
 | |
| 	{
 | |
| 		mu.FindPatternInFile(file, (char*)real_sig, real_bytes, matches, atFuncStart);
 | |
| 	}
 | |
| 
 | |
| 	if( !atFuncStart )
 | |
| 		return -1;
 | |
| 	
 | |
| 	return matches;
 | |
| }
 | |
| 
 | |
| int checkSigStringL(void *handle, const char* symbol)
 | |
| {
 | |
| 	bool isAt = (symbol[0] == '@' && symbol[1] != '\0');
 | |
| 	int matches = 0;
 | |
| 	bool dummy;
 | |
| 	
 | |
| 	if (isAt)
 | |
| 	{
 | |
| 		if( use_symtab && mu.ResolveSymbol(handle, &symbol[1]) )
 | |
| 			matches = 1;
 | |
| 		else if( !use_symtab && dlsym(handle, &symbol[1]) )
 | |
| 			matches = 1;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		unsigned char real_sig[511];
 | |
| 		size_t real_bytes = UTIL_DecodeHexString(real_sig, sizeof(real_sig), symbol);
 | |
| 
 | |
| 		if (real_bytes >= 1)
 | |
| 		{
 | |
| 			mu.FindPattern(handle, (char*)real_sig, real_bytes, matches, dummy);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return matches;
 | |
| }
 | |
| 
 | |
| /* 
 | |
|    takes a mangled member function symbol and returns the position where the function name and parameters start
 | |
|    01234567890123456789012345678
 | |
|    _ZN9CTFPlayer12ForceRespawnEv
 | |
|                 ^13            ^28
 | |
| */
 | |
| void findFuncPos(const char *sym, int &func, int ¶ms)
 | |
| {
 | |
| 	int i = 0;
 | |
| 	while ((sym[i] < '0') || (sym[i] > '9')) i++;
 | |
| 	int classLen = atoi(sym + i);
 | |
| 	func = i + (int)ceil(log10(classLen)) + classLen;
 | |
| 	int funcLen = atoi(sym + func);
 | |
| 	params = func + (int)ceil(log10(funcLen)) + funcLen;
 | |
| }
 | |
| 
 | |
| int findVFunc(void *handle, void **vt, const char *symbol)
 | |
| {
 | |
| 	if( !use_symtab )
 | |
| 	{
 | |
| 		for (int i = 0; i < 1000; i++ )
 | |
| 		{
 | |
| 			void *pFunc = vt[i];
 | |
| 			if( !pFunc )
 | |
| 				continue;
 | |
| 			
 | |
| 			if( pFunc == dlsym(handle, symbol ) )
 | |
| 				return i - 2;
 | |
| 		}
 | |
| 		
 | |
| 		return -1;
 | |
| 	}
 | |
| 	
 | |
| 	int funcPos, paramPos;
 | |
| 	findFuncPos(symbol, funcPos, paramPos);
 | |
| 
 | |
| 	for (int i = 0; i < 1000; i++)
 | |
| 	{
 | |
| 		void *pFunc = vt[i];
 | |
| 		if( !pFunc )
 | |
| 			continue;
 | |
| 		
 | |
| 		const char *s;
 | |
| 
 | |
| 		if (!(s = mu.ResolveAddr(handle, pFunc)))
 | |
| 			continue;
 | |
| 
 | |
| 		if ((i > 1) && (strncmp(s, "_ZTI", 4) == 0))
 | |
| 			break;
 | |
| 
 | |
| 
 | |
| 		int tempFuncPos, tempParamPos;
 | |
| 		findFuncPos(s, tempFuncPos, tempParamPos);
 | |
| 
 | |
| 		if (strcmp(s + tempFuncPos, symbol + funcPos) == 0)
 | |
| 		{
 | |
| 			return i - 2;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| unsigned int strncopy(char *dest, const char *src, size_t count)
 | |
| {
 | |
| 	if (!count)
 | |
| 	{
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	char *start = dest;
 | |
| 	while ((*src) && (--count))
 | |
| 	{
 | |
| 		*dest++ = *src++;
 | |
| 	}
 | |
| 	*dest = '\0';
 | |
| 
 | |
| 	return (dest - start);
 | |
| }
 | |
| 
 | |
| size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
 | |
| {
 | |
| 	va_list ap;
 | |
| 	va_start(ap, fmt);
 | |
| 	size_t len = vsnprintf(buffer, maxlength, fmt, ap);
 | |
| 	va_end(ap);
 | |
| 
 | |
| 	if (len >= maxlength)
 | |
| 	{
 | |
| 		buffer[maxlength - 1] = '\0';
 | |
| 		return (maxlength - 1);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		return len;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| size_t UTIL_DecodeHexString(unsigned char *buffer, size_t maxlength, const char *hexstr)
 | |
| {
 | |
| 	size_t written = 0;
 | |
| 	size_t length = strlen(hexstr);
 | |
| 
 | |
| 	for (size_t i = 0; i < length; i++)
 | |
| 	{
 | |
| 		if (written >= maxlength)
 | |
| 			break;
 | |
| 		
 | |
| 		buffer[written++] = hexstr[i];
 | |
| 		if (hexstr[i] == '\\' && hexstr[i + 1] == 'x')
 | |
| 		{
 | |
| 			if (i + 3 >= length)
 | |
| 				continue;
 | |
| 			/* Get the hex part. */
 | |
| 			char s_byte[3];
 | |
| 			int r_byte;
 | |
| 			s_byte[0] = hexstr[i + 2];
 | |
| 			s_byte[1] = hexstr[i + 3];
 | |
| 			s_byte[2] = '\0';
 | |
| 			/* Read it as an integer */
 | |
| 			sscanf(s_byte, "%x", &r_byte);
 | |
| 			/* Save the value */
 | |
| 			buffer[written - 1] = r_byte;
 | |
| 			/* Adjust index */
 | |
| 			i += 3;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return written;
 | |
| }
 | |
| 
 |