#include #include #include #include #include #include #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 -e -f -b -x -w -y [-s ]\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::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::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; }