sourcemod/tools/gdc-psyfork/gdc.cpp
Nicholas Hastings f757d5f09b Updated gdc-psyfork with current (NPOTB).
- 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.
2013-01-07 09:09:14 -05:00

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 &params)
{
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;
}