Дилетант и back-инжиниринг. Часть 1: Пути и файлы
Как много из прогеров делали проекты по мотивам игр, в которые они играли? Думаю, что много. Вам могут говорить, что игра так себе, что не нужно тратить на бессмысленный проект время, все равно он никому не пригодится, но вам без разницы, потому что это было круто, и хоть как-то хочется повторить те эмоции.В свое время я очень много играл в «Tales of pirates» (далее TOP) от Moli, точнее в ее русскую локализацию от Nival-а — «Пиратию». Шикарная, как на меня, игра. Да, не WOW, но ведь и я о нем ничего не знал. Прошло много лет, «Пиратию» закрыли, я вырос, но в океане памяти всё равно плавает забытый покоритель морей на кораблике 54-го уровня.
После того, как я узнал, что закрыли русский сервер, а я в тот момент уже не играл пару лет, вновь проснулся азарт. Через пару месяцев я немного разобрался в мире фан-серверов, пытался настроить свой на локалке, начал разбираться с структурой файлов. И тут понеслась. Сначала решил научится работать с моделями. За пару часов нашел на Pastebin какой-то код, который после напильника мог открывать, правда с проблемами, модельки. Но кроме моделей в игре были зашифрованы текстуры.
К счастью была одна программка, Gemini Decompiler, умеющая преобразовывать текстуры игры в нормальные и общепринятые картинки. Еще более повезло, что программка эта была написана на .Net. После декомпилятора оказалось, что нормальная текстура разбивалась на 3 блока — первые 44 байта, последние 44 байта, и остальное — в зашифрованной текстуре сначала шли последние 44 байта, потом основная часть, потом первые 44 байта, и еще 4 байта неизвесно для чего.
На основе кода с Pastebin и преобразователя текстур получилось сделать просмотрщик моделей. Ужасный конечно, но все же. Вот только читал он только простейшие модели, вроде мечей\посохов\кинжалов, персонажей и элементы декора без анимации.
А мне хотелось движения. И я начал искать дизассемблеры, чтобы разобрать по кусочкам библиотеку движка TOP. Нашел пиратский IDA Pro. И начал ломать… Удобная вообще программка, особенно тем, что она умеет адекватно разыменовывать имена после обработки компилятором и переводит результат дизасембера на псевдокод, крайне похожий на С. В статье про Caesar 3 о этом вспоминалось.
Я работал с переменным успехом около двух месяцев. Ну как работал, игрался. Нашел код с математическими операциями, начал всовывать структуры с просмотрщика моделей в базу IDA, потом нашел, что можно импортировать .pdb (а он есть, видимо для обратной связи, если есть ошибки), правил код и структуры, что выдавались конструктором псевдокода. Но чувствовалось, что все не то. У меня же есть .pdb, а там, насколько знал, есть информация о проекте. Я нашел кучу разных программ, но все они выдавали лишь общую информацию. Тогда начал рассматривать Debug Interface Access (далее DIA), если бы еще он поставлялся с Express версией Visual studio. Короче теперь я пишу на Professional редакции. Скомпилировав примерчик dia2dump я офигел. У меня под рукой была куча служебной информации. Немногим больше 30 мегабайтов текста. Например там есть список всех .obj файлов, что скармливаются линковщику, кроме того для каждого .obj файла есть список исходников, что туда входят. В общем за один день был готов код на создание файлов из проекта движка TOP. А дальше будут создание структур и подключение инкудов по .pdb…
Такие дела.
На закуску — дополнительный код для dia2dump. Код для подсчета количества .obj в проекте, вывода файлов в каждом .obj и создания папок и файлов проекта по .pdb.
Открыть результат работы кодера int total=0;
bool AQLCreateDirectory (WCHAR * sPathTo) { while (CreateDirectory (sPathTo, NULL) == FALSE) { WCHAR sTemp[MAX_PATH]; int k = wcslen (sPathTo); wcscpy (sTemp, sPathTo); while (CreateDirectory (sTemp, NULL) != TRUE) { while (sTemp[--k] != L'\\') { if (k<=1) return FALSE; sTemp[k] = NULL; } } } return TRUE; };
void Process (IDiaSession *pSession, IDiaSymbol *pGlobal) { int total=0; IDiaEnumSymbols *pEnumSymbols; if (FAILED (pGlobal→findChildren (SymTagCompiland, NULL, nsNone, &pEnumSymbols))) return; IDiaSymbol *pCompiland; ULONG celt = 0;
while (SUCCEEDED (pEnumSymbols→Next (1, &pCompiland, &celt)) && (celt == 1)) { pCompiland→Release (); total++; } fwprintf (pFileout, L»%i\n», total); pEnumSymbols→Release (); if (FAILED (pGlobal→findChildren (SymTagCompiland, NULL, nsNone, &pEnumSymbols))) return; celt = 0;
while (SUCCEEDED (pEnumSymbols→Next (1, &pCompiland, &celt)) && (celt == 1)) { BSTR bstrName; if (pCompiland→get_name (&bstrName) == S_OK) { fwprintf (pFileout, L»%s\n», bstrName); SysFreeString (bstrName); }
int num=0; IDiaEnumSourceFiles *pEnumSourceFiles; if (SUCCEEDED (pSession→findFile (pCompiland, NULL, nsNone, &pEnumSourceFiles))) { IDiaSourceFile *pSourceFile; while (SUCCEEDED (pEnumSourceFiles→Next (1, &pSourceFile, &celt)) && (celt == 1)) { num++; pSourceFile→Release (); } pEnumSourceFiles→Release (); fwprintf (pFileout, L»%i\n», num); }
if (SUCCEEDED (pSession→findFile (pCompiland, NULL, nsNone, &pEnumSourceFiles))) { IDiaSourceFile *pSourceFile; while (SUCCEEDED (pEnumSourceFiles→Next (1, &pSourceFile, &celt)) && (celt == 1)) { BSTR bstrSourceName; if (pSourceFile→get_fileName (&bstrSourceName) == S_OK) { fwprintf (pFileout, L»%s\n», bstrSourceName);
WCHAR *path = new WCHAR[wcslen (bstrSourceName)+8]; wcscpy (path, L«c:\\test\\»); wcscat (path, bstrSourceName+2); WCHAR *filename = new WCHAR[wcslen (path)+1]; wcscpy (filename, path); for (int k=wcslen (path)-1; k>=0&&path[k]!=L'\\'; k--)path[k]=0;
bool ok = AQLCreateDirectory (path); FILE *file = _wfopen (filename, L«w»); fclose (file); delete (filename); delete (path);
SysFreeString (bstrSourceName); } pSourceFile→Release (); } pEnumSourceFiles→Release (); } pCompiland→Release (); } pEnumSymbols→Release (); }