XGM Forum
Сайт - Статьи - Проекты - Ресурсы - Блоги

Форуме в режиме ТОЛЬКО ЧТЕНИЕ. Вы можете задать вопросы в Q/A на сайте, либо создать свой проект или ресурс.
Вернуться   XGM Forum > Warcraft> Академия: форум для вопросов> Желтая пресса: обучающие статьи
Ник
Пароль
Войти через VK в один клик
Сайт использует только имя.

 
Алексей
Где кошачья мята?!
offline
Опыт: 26,543
Активность:
War: взгляд изнутри.
WAR: ВЗГЛЯД ИЗНУТРИ
Вот давно обещанная статья по «внутренностям» War’а. Она предназначена для программистов-модмейкеров и рассказывает, как организовать взаимодействие War’а с внешней программой.
Конечный продукт
Чтобы не отнимать время у всех читателей-непрограммистов, я сразу начну с готовых программ, а потом уж приступлю к их разбору.
Итак, первый из написанных мной «модов» к War’у – программа waradd.exe (лежит в архиве waradd.zip). Она позволяет отслеживать позицию курсора мыши в War’е. Просто скопируйте её в папку с War’ом и запустите. При этом War запустится автоматически. В том же архиве лежит пример карты, использующей эту программу. После запуска карты на экране выводится позиция курсора мыши (триггер срабатывает каждые 0,5 сек).
Как это работает?
Прежде всего, в редакторе триггеров необходимо объявить переменную целочисленного типа. Она может идти под любым именем, но должна равняться 98765432. Мод сканирует память War’а, и, найдя это число, начинает записывать туда позицию курсора мыши (в «сжатой» форме = Y*65526+X).
.
Вторая программка предназначена для тех модмейкеров, которые хотят дополнить War собственным MPQ-архивом. Вроде бы такие архивы замещают файл war3patch.mpq. Я всегда считал абсурдной эту идею – чтобы War не вылетал после такой операции, необходимо положить в новый MPQ кучу всякого хлама (у меня при попытке перезаписи этого файла War и WE начинали вылетать). И даже существуют программы, которые на время запуска заменяют war3patch.mpq “модовским” архивом, а после выхода из War’а – восстанавливают исходный архив. Тут есть одно НО: современные компьютеры иногда виснут. И если War повиснет, когда war3patch.mpq перезаписан, то потом мод уже не запустится, а сам War начнёт глючить. Идеальным решением было бы научить War вместо war3patch.mpq «заглатывать» нечто иное – безо всякой перезаписи и переименования файлов. Итак, в архиве warmod.zip лежат две программки – warmod.exe и newWE.exe. Их опять-таки нужно скопировать в каталог с War’ом. При запуске первой из них запустится War, но вместо war3patch.mpq он будет пытаться загрузить war3mod.mpq. При запуске второй запустится WE, и тоже будет грузить war3mod.mpq.
.
Продолжаем! Что нужно для работы?
Для работы нам понадобятся:
  • Мозги (с КПД>75%).
  • DirectHands самой последней версии.
  • Умение программировать на Delphi, C или ASM.
  • Среда Delphi 5 или выше (т.е. программы-примеры написаны на Delphi, т.к. он пользуется большей популярностью, нежели C++).
  • Наличие ассемблера приветствуется, но если его нет – обойдёмся и так ;).
  • Да, чуть не забыл! Ещё нужен War (не ниже III-TFT), иначе не на чём будет испытывать наши замечательные программы.
Ну, вот вроде и всё. Можете запастись ещё ящиком пива, но я обойдусь без него.
.
Акт 1: Запуск War и «отлов» событий
Начнём с того, что напишем программу, запускающую War и отслеживающую некоторые события, происходящие внутри него. (Готовый пример лежит в каталоге Ex1 архива exs.zip). Ни для кого не секрет, что сам «движок» War’а заводится при запуске файла war3.exe. Все остальные экзешники – это лишь обёртки, выбирающие режим его работы. Если запустить просто war3.exe, то War запустится как TFT. На Delphi запуск War’а реализуется так:
Код:
FillChar(sti,SizeOf(sti),0);     //забиваем нулями
 FillChar(pi,SizeOf(pi),0);
 sti.cb:=SizeOf(sti);             //размер структуры
 sti.dwFlags:=STARTF_USESHOWWINDOW;//флаги запуска
 sti.wShowWindow:=SW_SHOWNORMAL;
 if not CreateProcess('war3.exe',nil,nil,nil,false,
               DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS,
               nil,nil,sti,pi) then begin
  MessageBox(0,'Ошибка запуска War',’Ошибка’,MB_ICONSTOP);
  exit;
 end;//of if
Итак, мы заполняем все необходимые структуры и пускаем War.exe вызовом CreateProcess. Обратите внимание на флаги DEBUG_PROCESS и DEBUG_ONLY_THIS_PROCESS. Ими мы указываем, что хотим запустить War «изнутри» нашей программы, иметь полный доступ к нему (иметь его по полной программе :)), и получать сообщения о некоторых событиях, происходящих в его недрах.
Кстати, такой способ контроля посторонней программы используется всеми отладчиками. И теперь наша программа получает возможность вызывать особые «отладочные» функции (Debug API) для управления запущенным процессом.
Далее мы просто ждём наступления какого-либо отладочного события:
Код:
WaitForDebugEvent(de,INFINITE)
Когда в недрах War’а произойдёт некоторое событие, функция вернёт его полное описание в структуре de. Проанализировав de.dwDebugEventCode, можно определить, что за событие мы получили. Их довольно много (см. Delphi HELP), но нас особо интересуют вот эти:
  • RIP_EVENT – отладка накрылась. Отлаживаемый процесс понял, что его отлаживают, и «вырубил» наш отладчик. К счастью, War не содержит никаких антиотладочных и антихакерских трюков, что сильно облегчит нам жизнь.
  • CREATE_PROCESS_DEBUG_EVENT – процесс создан. Мы получим такое событие, когда War загрузит все DLL’ки, создаст главный поток и будет готов к выполнению.
  • CREATE_THREAD_DEBUG_EVENT – War создал новый поток. Как известно, War – многопоточное приложение, поэтому такое событие будет происходить довольно часто.
  • EXIT_THREAD_DEBUG_EVENT – Какой-либо из потоков завершился (или завершён принудительно – как известно, War вырубает подвисшие потоки, что несколько затрудняет жизнь JASS’ерам).
  • EXIT_PROCESS_DEBUG_EVENT – War завершился (или вылетел, без разницы).
  • EXCEPTION_DEBUG_EVENT – в недрах War’а возникло исключение (некая особая ситуация). Например, такое событие мы получим при выполнении первой инструкции War’а, при срабатывании точек останова (установленных нами), при выполнении некоторых наперёд заданных условий, при возникновении критической ошибки War’а и т.д. Проанализировав de.Exception.ExceptionRecord.ExceptionCode можно установить, чем вызвано исключение.
Получив какое-то событие, мы можем его обработать (если хотим). Причём выполнение War’а временно приостанавливается, и чтобы он продолжил работу, необходимо вызвать функцию ContinueDebugEvent с аргументом DBG_CONTINUE. Особую проблему представляет обработка исключений. Если мы, обрабатывая исключение, вызовем ContinueDebugEvent с аргументом DBG_CONTINUE, то исключение тут же возникнет вновь (т.к. условия для его возникновения никуда не исчезли). И ещё, и ещё раз… В общем, дело так и не сдвинется с мёртвой точки. Чтобы рестартовать War после исключения, нужно использовать DBG_EXCEPTION_NOT_HANDLED.
Стоп, всё не так просто! Оказывается, существует ещё одно, совершенно особое исключение – EXCEPTION_BREAKPOINT. При его первом возникновении нужно использовать DBG_CONTINUE, а во все последующие разы – DBG_EXCEPTION_NOT_HANDLED.
.
Что ж, вся теория позади, переходим к практике. Напишем программу, которая будет отслеживать создание/удаление потоков War’а и выводить их количество в файл zz.txt. В общем, пускаем War, затем создаём наш лог:
Код:
CountOfThreads:=0;IsFirstRun:=true;
 AssignFile(f,'zz.txt');
 Rewrite(f);
Пока что количество потоков – 0 (War только-только запущен). Переменная IsFirstRun позволит нам отследить первое исключение типа EXCEPTION_BREAKPOINT, чтобы воспользоваться волшебной «гасилкой» DBG_CONTINUE.
Далее начинаем обрабатывать отладочные события. Обработка организована в виде цикла:
Код:
while WaitForDebugEvent(de,INFINITE) do begin
  case de.dwDebugEventCode of
Т.е. ждём событие/анализируем тип/снова ждём (это, конечно, только заголовок цикла – самое его начало). В зависимости от события, ведём себя по-разному:
Код:
RIP_EVENT:begin //Отладка накрылась
    CloseFile(f);
    MessageBox(0,'Ошибка внедрения в War','Ошибка',mb_iconstop);
    exit;
   end;//of RIP_EVENT
Да, если отладке крышка - выходим. То же самое – если War завершился (см. исходник – повторяться тут нет смысла). Если же создан поток, отметим этот факт в логе:
Код:
CREATE_THREAD_DEBUG_EVENT:begin //создан новый поток
    inc(CountOfThreads);           //увеличить на 1 счётчик потоков
    Write(f,'THR:');               //вывести эту информацию
    Writeln(f,CountOfThreads);
   end;//of CREATE_THREAD_DEBUG_EVENT
Аналогично отмечается и уничтожение потока (см. исходник).
Теперь перейдём к обработке исключений. Их мы тоже будем отмечать в логе – за исключением сакраментального EXCEPTION_BREAKPOINT:
Код:
EXCEPTION_DEBUG_EVENT:with de.Exception.ExceptionRecord do begin
    //тип исключения - первая инструкция
    if (ExceptionCode=EXCEPTION_BREAKPOINT) and IsFirstRun then begin
     IsFirstRun:=false;           //сбросим флаг
     ContinueDebugEvent(de.dwProcessId,de.dwThreadId,DBG_CONTINUE);
    end else
     ContinueDebugEvent(de.dwProcessId,de.dwThreadId,DBG_EXCEPTION_NOT_HANDLED);
    //Обрабатываем только исключение отладки
    writeln(f,'Exc');
    continue;                     //перезапуск цикла
   end;//of EXCEPTION_DEBUG_EVENT
Как видите, основная проблема тут – выбрать, что передавать в ContinueDebugEvent.
Ну, вроде всё. Компилируем, пускаем (только не из-под Delphi! Он сам содержит отладчик и будет конфликтовать с нашей программой. Поэтому компилируем её по Ctrl+F9, копируем в папку с War’ом и запускаем). По окончании игры смотрим листинг. Основной результат такого исследования состоит в том, что War в момент запуска создаёт 19 потоков, а затем их только использует! Большинство потоков висят замороженными, и «размораживаются» по мере надобности. Взамен каждого подвисшего потока тотчас создаётся новый.
Кстати, на самом деле потоков 20, а не 19 – т.к. создание главного потока фиксируется событием CREATE_PROCESS_DEBUG_EVENT, а его мы не обрабатывали.
.
Акт 2: ищем триггерную переменную
Теперь усложним нашу программу – сделаем так, чтобы она искала в недрах War’а нужную нам переменную и выводила в лог её адрес. Готовый исходник лежит в Ex2.
Прежде всего, цикл ожидания событий вынесен в отдельную процедуру - ProcessEvents. Там же содержится проверка – а не завершился ли War. И если завершился, выходим:
Код:
GetExitCodeProcess(pi.hProcess,ec);
 if ec<>STILL_ACTIVE then halt(0);
Кроме того, события ожидаются не вечно, а в течение указанного (далее) времени – чтобы наша программа тоже периодически получала управление. Ещё одна деталь – теперь мы объявляем новый тип:
Код:
type Thr=record
 handle,id:cardinal;
end;//of Thr
и массив:
Код:
WarThreads:array [1..100] of Thr;
В этом массиве мы будем сохранять хэндлы всех создаваемых War’ом потоков. Зачем? Ну, так ведь хэндл («регулятор») – волшебный ключик к потоку. Имея хэндл потока, мы можем иметь и сам поток (причём по полной). Так что при создании каждого потока его хэндл сохраняется в вышеуказанном массиве, а при уничтожении потока – удаляется из массива. Т.о., массив всегда будет содержать хэндлы всех действующих потоков. В этом примере (как и паре последующих) хэндлы нам не потребуются. Но лучше заранее позаботиться об их накоплении.
Итак, начало программы – стандартное: пускаем War, создаём лог-файл… А дальше выдерживаем 7-секундную паузу (ждём события):
Код:
for i:=1 to 7 do ProcessEvents(1000);
Т.е. ждём 7 раз по 1000мс, что в итоге даёт 7 сек. Почему нельзя было поставить ProcessEvents(7000) ? Ну, так ведь эта процедура – самопальная. Мне лень было её синхронизировать по таймеру, и чем меньше интервал, тем точнее он отрабатывается (парадокс, не правда ли?). Для чего нужна эта пауза? Дело в том, что War грузится не мгновенно, да и на загрузку карты какое-то время уходит. А пока War и карта грузятся, сканировать память нет смысла – нужной переменной там заведомо нет. За 7 сек. War успевает загрузиться, пользователь – выбрать карту, и сама карта уже на подходе. Самое время начать сканирование. Для него я создал особую процедуру – MemScan, которая ищет некоторую строку, начиная с указанного адреса. Разумеется, ищем с 0. Увы, War использует немыслимо большие объёмы памяти, так что полный поиск будет идти ну о-очень долго. А ведь нам нужно вести поиск регулярно – карту-то могут в любой момент загрузить. К счастью, есть способ ускорить дело – блочный поиск.
Прежде всего получаем информацию об очередном блоке памяти:
Код:
VirtualQueryEx(pi.hProcess,pointer(StartAddress),mbi,SizeOf(mbi));
Эта функция возвращает информацию об указанном блоке памяти. А далее большинство блоков просто отсеиваем. Во-первых, искать переменную в пустых (свободных) блоках памяти бессмысленно, как и в зарезервированных. Поэтому такие блоки пропускаем, ощутимо сэкономив на их сканировании:
Код:
//Проверить, что в этой области лежит
  if ((mbi.State and MEM_FREE)<>0) or
     ((mbi.state and MEM_RESERVE)<>0) then begin //область пута
   StartAddress:=StartAddress+mbi.RegionSize;
   continue;                      //пропускаем эту область
  end;//of if
Далее анализируем флаги блока памяти. Если там хранится программный код, то этот блок опять-таки пропускаем. Сканируются только блоки данных:
Код:
//ищем данные (доступ на запись)
  if ((mbi.AllocationProtect and 1)=0) or
     ((mbi.Type_9 and MEM_PRIVATE)=0) then begin
   StartAddress:=StartAddress+mbi.RegionSize;
   continue;
  end;//of if
Если же подходящий для сканирования блок найден, переводим его в текстовую строку и просматриваем инструкцией pos, которая работает очень быстро:
Код:
//Подходящая для сканирования область найдена
  SetLength(dta,mbi.RegionSize);
  num:=0;
  ReadProcessMemory(pi.hProcess,mbi.BaseAddress,@dta[1],mbi.RegionSize,num);
  //Вариант: 98765432
  StartAddress:=StartAddress+mbi.RegionSize;
  ps:=pos(ss,dta);
  if ps<>0 then begin //найдено!
Вот так. Теперь осталось вычислить адрес найденной переменной (чтобы потом записывать туда нужные данные) и вывести параметры блока переменных в лог-файл. Кроме того, одновременно создаётся дамп памяти – т.е. содержимое всего блока переменных записывается в файл dmp.bin для дальнейшего изучения:
Код:
if ps<>0 then begin
   Result:=ps+cardinal(mbi.BaseAddress)-1;
   writeln(f,'----');
   write(f,'ADR:');writeln(f,Result);
   write(f,'TYP:');writeln(f,mbi.Type_9);
   write(f,'AP:');writeln(f,mbi.AllocationProtect);
   write(f,'ST:');writeln(f,mbi.State);
   assignFile(fb,'dmp.bin');
   rewrite(fb,1);
   BlockWrite(fb,dta[1],length(dta));
   CloseFile(fb);
   if IsOne then IsOne:=false
   else exit;
  end;//of if
Вы, наверное, удивились – что это за странные манипуляции с флагом IsOne? Всё дело в том, что коварный War хранит переменные в двух экземплярах – в самой карте и в области данных скрипта. Причём нам нужна именно вторая копия, т.к. именно она «рабочая», тогда как первая используется для быстрого перезапуска карты (пункт War’овского меню «начать заново»). Поэтому первую найденную переменную мы спокойно пропускаем, а вот адрес второй уже используем.
Итак, процедура сканирования памяти готова, пауза 7 сек. выдержана, самое время приступить к сканированию! Оно будет проводиться каждые 2 сек (чтобы не занимать ресурсы) до нахождения переменной:
Код:
//4. Каждые 2 секунды сканируем всю память
 while MemScan(0,#120#10#227#05)<100 do begin
  ProcessEvents(2000);
 end;
Маловразумительная строка #120#10#227#05 – это символьное представление числа 98765432, которое мы выбрали в качестве удобного шаблона для поиска (т.е. вероятность того, что War использует его ещё где-то, практически нулевая). Ну, а после нахождения переменной получаем её адрес и пишем его в лог:
Код:
i:=MemScan(0,#120#10#227#05);
 writeln(f,i);
Итак, переменная найдена, дамп памяти снят, лог заполнен… Теперь осталось только дождаться выхода из War’а:
Код:
ProcessEvents(INFINITE);
Всё. Компилируем программу, пускаем её, грузим карту-пример (zzz.w3m), и затем может сразу выйти из игры. После этого в папке War’а оказывается лог примерно следующего содержания:
Код:
----
ADR:250880332
TYP:131072
AP:1
ST:4096
Отсюда видно, что War использует для своих данных защиту – вся память, используемая под данные, обозначается как NOACCESS, т.е. просто взять и записать туда что-то нельзя – это обеспечивает защиту от неправильно работающих скриптов. Впрочем, мы будем пользоваться функцией WriteProcessMemory, которая может записывать и в защищённые участки памяти ;).
Кроме того, в том же каталоге появится небольшой файл дампа области переменных (обычно не более 100Кб). Он обладает двоичной структурой, и анализировать его можно только в HEX-редакторе (я предпочитаю HexEd, т.к. он идёт в комплекте RadASM, небольшой по размеру и совершенно бесплатен).
.
Акт 3: передаём данные в War
Открываем каталог Ex3 и смотрим исходник. От Ex2 он отличается только тем, что в найденную переменную мы пишем некое число:
Код:
j:=$00100010;
 WriteProcessMemory(pi.hProcess,pointer(i),@j,4,tmp);
Команды записи стоят сразу после цикла поиска переменной. Если откомпилировать программу и запустить её, а потом загрузить карту-пример, то она выведет на экран “x=16,y=16”, а не тот мусор, что раньше. Т.е. контакт с War’ом уже есть! Мы можем передавать туда всё, что угодно.
.
Акт 4: надёжный поиск
.
Анализ дампов памяти, созданных предыдущими примерами, показал, что каждая целочисленная переменная занимает целых 40 байт памяти, лишь 4 из которых содержат число. Ещё 8 указывают тип переменной. Например, целочисленный тип описывается так:
04 00 00 00 – 04 00 00 00 Т.е. теперь мы можем повысить надёжность поиска, не только сканируя память на указанное число (мало ли, где оно может встретиться?), но ещё и проверяя тип переменной. Кроме того, вы, вероятно, были в некотором недоумении – почему число 98765432 в виде строки представлено как #120#10#227#05 ? А если вам хочется использовать другой шаблон поиска, что тогда? Или если нужно записывать что-то сразу в несколько переменных, ведь им нельзя присваивать одинаковые значения… Поэтому открываем каталог Ex4 и смотрим. Новая программа отличается от Ex3 лишь небольшими изменениями процедуры MemScan. Теперь она принимает не строку, а сразу ЧИСЛО, которое нужно найти в памяти. Строку из него она формирует самостоятельно, да ещё и дополняет её типом переменной для более надёжного поиска. Для этого там прежде всего объявляется константная строка, содержащая тип переменной:
Код:
Const stp=#4#0#0#0#4#0#0#0;
Затем число (vr) переводится в строку и добавляется к строке типа:
Код:
s:=stp+'abcd';     
 Move(vr,s[9],4);
Всё! Теперь сканировать память можно так:
Код:
adr:=MemScan(0,98765432)

.
Акт 5: Наконец-то добрались до курсора!
Ну, теперь уже можно приступить к отслеживанию курсора мыши (не прошло и полугода…). Готовый пример находится в каталоге Ex5. Прежде всего, убираем все строки кода, связанные с созданием и ведением логов (в самом деле, зачем они нам теперь-то?). И – после нахождения переменной каждые 40мс будем записывать туда позицию курсора мыши (в сжатой форме):
Код:
repeat
  ProcessEvents(40);
  GetCursorPos(pt);
  j:=(pt.y shl 16)+pt.x;
  WriteProcessMemory(pi.hProcess,pointer(i),@j,4,tmp);
 until false;
Т.е. используется бесконечный цикл, который завершается только после завершения War’а. Запустите программу+карту… И увидите, как при движении курсора мыши меняются числа в табличке. Правда, триггер срабатывает довольно редко, так что числа меняются рывками. Но вы можете легко это исправить (в редакторе триггеров уменьшить интервал срабатывания).
.
Акт 5а: это реально!
От целочисленных переменных переходим к реальному типу. Возможно, вам интересно, как можно находить «реальные» переменные и чего-либо в них записывать. Готовый пример находится в каталоге Ex5. Эта программа напоминает Ex3, т.е. ничего не отслеживает, а только находит переменную со значением 123.45 (удобный шаблон, не правда ли?) и заменяет её на 11.11. Прежде всего, анализ дампов памяти показал, что реальный тип в War’е обозначается как
05 00 00 00 – 05 00 00 00 и соответствует Delphi’шному типу single. Соответственно, он конвертируется в строку… И всё – аналогично предыдущим примерам. См. исходник. Да, кстати, карта zzz.w3m для тестирования этого примера не подходит, т.к. она пользуется только целочисленными переменными. Поэтому в том же каталоге находится карта real.w3m (содержит переменную с шаблоном и выводит на экран её значение).
.
Акт 6: это только начало…
Вы думаете, всё так просто? Ха-ха! Как вы считаете, что произойдёт, если игрок выйдет в главное меню War’а или загрузит новую карту? Ведь мы каждые 40мс пишем координаты курсора в найденную переменную, а при выходе из карты освободившаяся память занимается War’ом под что-то другое. И не факт, что даже перезапуск карты оставит переменную на том же месте! Т.е. при перезапуске карты или выходе из неё War может та-ак глюкануть… Вывод: для безглючной работы программы жизненно необходимо отслеживать момент выгрузки карты и начинать сканирование сызнова.
Это непросто, но что делать… Основная проблема – поймать момент «выгрузки» карты. Я перебрал несколько вариантов и остановился на следующем.
1. Ставим на нашу переменную ТОЧКУ ОСТАНОВА.
2. Как только War попытается занять эту память под что-то другое, она сработает (возникнет исключение), и мы поймём, что нужно сканировать заново.
Что такое точки останова, программисту объяснять не нужно – любой отладчик (тот же Delphi) умеет их ставить. А теперь мы сами выступаем в роли отладчика и будем расставлять точки останова в War’е.
Главная проблема заключается в том, что точка останова действует только в контексте потока. Т.е. нам нужно будет ставить точки останова на каждый поток War. Вот теперь-то нам их список и пригодится. Кстати, главный поток теперь тоже отлавливается:
Код:
CREATE_PROCESS_DEBUG_EVENT:begin
    inc(CountOfThreads);
    WarThreads[CountOfThreads].handle:=de.CreateProcessInfo.hThread;
    WarThreads[CountOfThreads].id:=de.dwThreadId;
   end;//of CREATE_PROCESS_DEBUG_EVENT
Чтобы упростить процесс установки и удаления точек останова, я создал 2 процедуры. Первая устанавливает точку по указанному адресу, а вторая удаляет её:
Код:
//поставить точку останова на поток (на запись)
procedure SetBPW(hThread,Adr:cardinal);
Var cont:_CONTEXT;
Begin
 cont.ContextFlags:=CONTEXT_DEBUG_REGISTERS;
 cont.dr7:=3+$D0000;              //тип ловушки - на запись
 cont.dr0:=Adr;                   //адрес точки останова
 SetThreadContext(hThread,cont);  //ставим точку в контекст
End;

//Удаление точки останова
procedure DeleteBP(hThread:cardinal);
Var cont:_CONTEXT;
Begin
 cont.ContextFlags:=CONTEXT_DEBUG_REGISTERS;
 cont.dr7:=0;                     //сброс всех точек потока
 SetThreadContext(hThread,cont);  //сбрасываем...
End;
Далее вводится флаг AutoSet. Когда он равен true, это означает, что переменная уже найдена и далее точки останова ставятся на каждый вновь созданный поток. Итак, мы находим переменную, ставим на неё точки останова (во всех потоках) и указываем, что дальше они должны ставиться автоматом:
Код:
//5. Устанавливаем точки останова (на запись)
  for ii:=1 to CountOfThreads do SetBPW(WarThreads[ii].handle,Adr);
  AutoSet:=true;                   //автоустановка точек
Далее до тех пор, пока одна из точек не сработает, просто отслеживаем мышь:
Код:
//6. До перезапуска карты
  //регулярно записываем позицию курсора мыши
  while AutoSet do begin
   ProcessEvents(40);
   GetCursorPos(pt);
   j:=(pt.y shl 16)+pt.x;
   WriteProcessMemory(pi.hProcess,pointer(Adr),@j,4,tmp);
  end;//of while
Несколько изменена и процедура ProcessEvents. Вы, надеюсь, ещё не забыли, что именно она обрабатывает все события? И сообщение о сработавшей точке останова получит она же. Оно будет оформлено в виде исключения и кодом EXCEPTION_SINGLE_STEP. И как только таковое случается, мы сразу же удаляем все до единой точки останова (т.к. они свою роль уже сыграли) и начинаем сканирование памяти сызнова (обратите внимание на бесконечный цикл repeat…until, которым обрамлён основной блок программы). Итак, вот как обрабатывается срабатывание точки останова:
Код:
if de.Exception.ExceptionRecord.ExceptionCode=EXCEPTION_SINGLE_STEP
    then begin //сработала точка останова - грузится карта
     //1. Удалить все точки останова
     for i:=1 to CountOfThreads do DeleteBP(WarThreads[i].handle);
     //2. Указать, что пора повторить цикл
     AutoSet:=false;
     ContinueDebugEvent(de.dwProcessId,de.dwThreadId,DBG_CONTINUE);
     exit;
    end;//of if (EXCEPTION_SINGLE_STEP)
Вот теперь практически всё. Готовый исходник находится в каталоге Ex6. Запустите его – и он будет стабильно отслеживать мышь, безо всяких вылетов. Даже после перезагрузки карты.
Если вы пишете что-то в несколько переменных, достаточно поставить точку останова лишь на одну из них – все остальные находятся в том же блоке памяти. Более того: так можно сильно ускорить поиск. Если в блоке найдена одна из переменных, то все остальные нужно искать там же, а не просматривать всю память с самого начала. Ну разве это не круто?
.
Часть II: ЗАТОЛКАТЬ ДРЯНЬ
Да, теперь мы напишем программу, при запуске которой War (и WE) вместо файла war3patch.mpq будут грузить War3mod.mpq. Боюсь, правда, что подобный «мод» представляет исключительно теоретический интерес, т.к. мне ещё не встречались случаи замены War’овских MPQ самопальными.
Этот таинственный ассемблер
Ну вот, к этому моменту все читатели уже разбежались (или уснули) :end:, так что самое время пускать в ход тяжёлую артиллерию. Держитесь крепче: эту программу мы напишем на чистом ассемблере! Причём сделаем её универсальной – достаточно поменять пару строк, и она будет запускать программу X, отслеживать обращения к файлу Y и перенаправлять их на файл Z.
Вначале немного об ассемблере. Не бойтесь – в нём нет ничего страшного. Современный ассемблер – это уже не тот монстр, который ещё 10 лет назад был кошмаром любого программиста. [Одна линковка чего стоила, с бесконечным подбором многочисленных ключей…] Теперь это – довольно простой язык, во всяком случае, не сложнее Си. Более того: ассемблер – самый доступный язык программирования. Достаточно закачать 780Кб (бесплатный пакет!) – и у вас в руках будет средство, способное создавать программы для DOS/Windows/Linux/BeOS/MeOS, obj-файлы, драйвера всех видов и под любые x86-процессоры (16/32/64-разрядные); всякие специфические штучки (бут-сектора, программы BIOS и пр.). Если же у кого-то проблемы с трафиком, можно закачать уменьшенный пакет (150Кб, без редактора – только компилятор и набор включаемых модулей).
Почему же asm’ом так редко пользуются? Первая и основная причина – практически полное отсутствие документации. Ну вот не привлекает он почему-то внимания авторов книг серии «для чайников». Если в комплекте со всеми остальными языками и средами разработки идёт здоровенный HELP (который подчас весит больше всего остального), то для asm’а ничего такого не предусмотрено. Согласитесь, что «методом тыка» изучить даже самый простой язык невозможно. В сети нормальной инфы тоже нет – сведения приходится собирать буквально по крупицам. Их основным источникам служат всё те же Microsoft SDK/DDK, AMD Architecture Programmer’s manual (вроде бы есть ещё аналогичный мануал от Intel, но я его не нашёл) и, конечно, ассемблерные исходники.
Ещё одна проблема – чуть повышенное время разработки (написание программы на asm’е отнимает примерно на 10-15% больше времени, чем на Си). Впрочем, эти недостатки окупаются исключительной миниатюрностью программ: простейшие «безоконные» программы занимают около 2Кб, а программы, имеющие собственное окошко – 4Кб. Разумеется, это минимальные цифры – чем больше функциональность, тем больше и вес. Но всё равно он будет значительно меньшим, чем у Delphi-приложений. Более того: даже эти миниатюрные программы легко сжимаются архиваторами (в zip-архиве - примерно вдвое).
.
Так что открываем каталог ExMod и смотрим…
А там есть целых 3 файла. Причём 2 из них стандартные:
  • standart.inc – набор макросов, упрощающих программирование. Их я написал уже давно и теперь всё время пользуюсь (вроде как собственная библиотека).
  • debug.inc – файл, содержащий описания констант и структур Debug API. Представляет собой конверсию сишных SDK/DDK-включаемых файлов под ASM.
  • warmod.asm – собственно программа.
Для компиляции потребуется ассемблер fasm версии не ниже 1.65 (сейчас доступна 1.65.17 – новые версии выходят раз в 2-3 месяца).
Ну ладно. Прежде чем лезть в дебри ассемблерного кода, разберём собственно алгоритм – как можно заставить War заглотить «не тот» архив. Всё просто: прежде чем работать с файлом, его нужно открыть. War открывает файлы с помощью функции CreateFileA. Всё, что нам нужно сделать – отследить вызов этой функции, проверить её параметры, и если имя открываемого файла равно war3patch.mpq, заменить его на war3mod.mpq. И всё! Теперь о том, как отследить вызов нужной функции. Самый простой метод – сплайсинг, но он не годится для локального перехвата (т.е. сплайсингом мы перехватим вызов функции ВСЕМИ процессами Windows, что нам совершенно не нужно). Поэтому воспользуемся другим способом. Он тоже довольно прост: ставим точку останова на функцию, и когда она сработает – смотрим, что там с параметрами. Как уже говорилось ранее, точки останова должны ставиться на каждый поток индивидуально, поэтому ничего лишнего мы не затронем.
Тут, правда, есть одна проблема: в предыдущем примере точки останова удалялись нами сразу после их срабатывания. Здесь так поступить нельзя – точка должна висеть всё время работы War’а, т.к. он в любой момент может «связаться» с MPQ. Поэтому нам придётся заставлять War «проскакивать» точки останова (т.е. заставить его всё-таки выполнить функцию с изменёнными параметрами, несмотря на то, что точка по-прежнему стоит). Делается это так:
  1. Убираем (временно) сработавшую точку останова;
  2. Устанавливаем в контексте потока флаг TF;
  3. После этого, когда то место, где стояла точка останова, будет пройдено, возникнет исключение EXCEPTION_SINGLE_STEP, и мы возвращаем точку останова на её законное место.
Вот. Ну что ж, приступим…
Ну, любая программа начинается с заголовка, и ассемблерная – не исключение. В заголовке указывается, под какую ОС мы будем компилировать программу, под какой процессор и что это вообще за программа (библиотека, драйвер, линкуемый файл, простой exe-файл и т.д.). Там же указывается точка входа (т.е. позиция, с которой начнётся выполнение программы).
Далее подключаются необходимые файлы (директивой include). Напоминает Си, не правда ли?
В самом конце программы я объявляю все необходимые переменные (хотя их можно объявить в любом месте программы). ASM может работать с виртуальными переменными (память под них выделяется динамически), чем я и пользуюсь:
Код:
virtual at ebp
 C_1:                                   ;для вычисления размеров
 pi     PROCESS_INFORMATION             ;информация о процессе
 stui   STARTUPINFO                     ;стартовая информация
 de     DEBUG_EVENT                     ;отладочная информация
 cont   CONTEXT                         ;контекс потока
 V_SIZE = $-C_1                         ;размер блока виртуальных переменных
end virtual
Такой приём позволяет сэкономить на размере экзешника, хотя работать с виртуальными переменными чуть сложнее, чем с обычными.
Итак, в начале программы провожу всевозможную инициализацию. В частности, помещаю 0 в ebx (зачем – объясню позже), выделяю память под виртуальные переменные и очищаю её (забиваю нулями).
Код:
;1. Всякая инициализация
 xor    ebx,ebx                         ;помещаем 0 в ebx
 sub    esp,V_SIZE                      ;выделяем память
 mov    ebp,esp
 xor    eax,eax                         ;для очистки блока
 mov    edi,ebp
 mov    ecx,V_SIZE/4                    ;кол-во двойных слов
 rep    stosd                           ;очистка
Теперь нужно выделить память под динамический массив, где будут храниться хэндлы потоков (вам это знакомо по предыдущим примерам):
Код:
invoke HeapAlloc,<invoke GetProcessHeap>,HEAP_ZERO_MEMORY,4096
 mov    [hWarThreads],eax               ;выделить память под массив
Следующее наше действие – получить адрес функции CreateFileA, чтобы после запуска War’а спокойно поставить туда точку останова. Найденный адрес сохраняется в переменной dwEntry:
Код:
;2. Получим адрес точки входа функции CreateFileA
 invoke GetProcAddress,<invoke GetModuleHandle,szKernel32>,szCrFile
 mov    [dwEntry],eax                   ;сохранить полученное
Всё, что начинается с префикса “sz” – строки (имена). Я их все вынес в таблицу строк – в конце файла. Можно было бы подставлять и непосредственно строки, но использование таблицы строк уменьшает размер экзешника и увеличивает скорость его работы (ещё один плюс – все строки собраны в кучу, в случае чего их не надо разыскивать по всему коду).
Теперь – пускаем War. Это осуществляется всё той же функцией CreateProcess:
Код:
;3. Пускаем War
 mov    [stui.cb],sizeof.STARTUPINFO    ;размер структуры
 mov    [stui.dwFlags],STARTF_USESHOWWINDOW
 mov    [stui.wShowWindow],SW_SHOWNORMAL
 lea    eax,[pi]
 push   eax
 lea    eax,[stui]
 push   eax
 invoke CreateProcess,szName,ebx,ebx,ebx,\
        ebx,DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS,\
        ebx,ebx
Как видим, виртуальные переменные нельзя передавать непосредственно в качестве параметров функции, это делается посредством команд lea/push. Как вы помните, в ebx ещё с начала программы лежит 0. А это число очень часто используется в качестве параметров функции. Передача непосредственно нуля требует 2 байт кода, а передача содержимого регистра (в котором лежит тот же ноль) – только одного байта. Т.е. на вызове CreateProcess мы сэкономили 6 байт! Я часто пользуюсь таким трюком, благо это несложно.
Итак, War запущен. Теперь начинаем обрабатывать события:
Код:
lea    eax,[de]
  invoke WaitForDebugEvent,eax,INFINITE ;ждать события отладки
  test   eax,eax
  jz     l_exit                         ;ошибка
Как вы помните, de – тоже виртуальная переменная, передаётся через eax (по lea). Как известно, в случае какой-либо ошибки WaitForDebugEvent возвращает 0, чем мы и пользуемся (выходим из цикла. test/jz – укороченная разновидность IF, занимает на 3 байта меньше).
Далее идёт анализ событий. Прежде всего, отслеживается создание потоков. Хэндлы всех созданных потоков собираются в массиве hWarThreads:
Код:
.if   [de.dwDebugEventCode]=CREATE_PROCESS_DEBUG_EVENT
   mov  edx,[de.CreateProcessInfo.hThread] ;хэндл потока (параметр)
   call AddThread                       ;добавить поток в список
  .endif                                ;of CREATE_PROCESS_DEBUG_EVENT

  .if   [de.dwDebugEventCode]=CREATE_THREAD_DEBUG_EVENT
   mov  edx,[de.CreateThread.hThread]   ;хэндл потока
   call AddThread                       ;добавить поток в список
  .endif                                ;of CREATE_THREAD_DEBUG_EVENT
Здесь AddThread – процедура, добавляющая хэндл потока в массив и устанавливающая в контексте этого потока точку останова. На входе в процедуру в edx должен лежать хэндл потока для добавления:
Код:
AddThread:                              ;процедура
 inc    [dwCountOfThreads]              ;увеличить кол-во потоков
 mov    eax,[dwCountOfThreads]
 mov    ecx,[de.dwThreadId]             ;сохраняем ID потока
 push   edi
 mov    edi,[hWarThreads]
 mov    [edi+eax*8],ecx
 mov    [edi+eax*8+4],edx               ;сохраняем хэндл потока
 pop    edi
SetBP:                                  ;разделяемая процедура (edx - хэндл)
 mov    [cont.ContextFlags],CONTEXT_DEBUG_REGISTERS
 mov    [cont.iDr7],3                   ;точка останова на выполнение
 mov    eax,[dwEntry]
 mov    [cont.iDr0],eax                 ;адрес точки останова
 lea    eax,[cont]
 invoke SetThreadContext,edx,eax        ;установить
ret
Интересная особенность ассемблера – «короткость» многих инструкций. Поэтому ассемблерные программы выглядят довольно оригинально – «столбиком». Если поток завершился, его хэндл нужно удалить из списка:
Код:
.if   [de.dwDebugEventCode]=EXIT_THREAD_DEBUG_EVENT
   ;удаляем поток из списка:
   call FindThreadHandle
   ;поток найден. Удаляем из массива...
   mov  edx,[dwCountOfThreads]          ;номер последнего элемента
   mov  ecx,[hWarThreads]               ;адрес массива
   push dword [ecx+edx*8]
   pop  dword [eax]
   push dword [ecx+edx*8+4]
   pop  dword [eax+4]
   dec  [dwCountOfThreads]              ;уменьшить кол-во потоков
  .endif                                ;of EXIT_THREAD_DEBUG_EVENT
Манипуляции push/pop обеспечивают удаление хэндла, затем счётчик потоков уменьшается на 1. Как вы уже догадались, FindThreadHandle – функция, которая ищет хэндл в списке (для последующего удаления). Она находится в начале кода:
Код:
FindThreadHandle:
 mov    eax,[hWarThreads]               ;адрес массива
 mov    edx,[de.dwThreadId]             ;ID потока для сравнения
 ;ищем нужный поток (по хэндлу)
 .for   ecx=[dwCountOfThreads],ecx>0,ecx--,eax+=8
  .exitf edx=[eax]                      ;выход: нашли поток!
 .endf
ret
Цикл for – наиболее оригинальный фрагмент всей функции (хотя те, кто программирует на Си, к такому уже привыкли). На Delphi этот цикл можно перевести как
Код:
for ecx:=dwCountOfThreads downto 0 do begin
 if edx=eax^ then exit;
 eax:=eax+8;
end;
Согласитесь, что asm-версия гораздо нагляднее (Си пользуется аналогичной конструкцией).
Теперь анализируем событие типа EXCEPTION_DEBUG_EVENT. Прежде всего, если это исключение типа EXCEPTION_BREAKPOINT, то продолжить выполнение процесса (используя флаг DBG_CONTINUE):
Код:
;проверим, какое возникло исключение:
   .if [de.Exception.pExceptionRecord.ExceptionCode]=EXCEPTION_BREAKPOINT
    invoke ContinueDebugEvent,[de.dwProcessId],[de.dwThreadId],DBG_CONTINUE
    jmp l_loop                          ;continue
   .endif                               ;of EXCEPTION_BREAKPOINT
Теперь обработаем исключение типа EXCEPTION_SINGLE_STEP – его возникновение означает срабатывание точки останова. Прежде всего получим хэндл потока, вызвавшего исключение:
Код:
;получим хэндл потока, вызвавшего исключение
    call FindThreadHandle               ;найти хэндл потока
    mov esi,[eax+4]                     ;считываем найденный хэндл
Затем проверим, что послужило причиной исключения. Для этого мы читаем содержимое контекста потока. И если там нет точки останова – значит, исключение пришло от флага TF (прохождение того места, где эта точка была). И точку надо установить заново:
Код:
;определим причину исключения
    mov [cont.ContextFlags],CONTEXT_DEBUG_REGISTERS
    lea eax,[cont]
    invoke GetThreadContext,esi,eax     ;получаем содержимое регистров
    .if [cont.iDr0]=0
     mov edx,esi                        ;хэндл потока
     call SetBP                         ;установить точку останова (заново)
     jmp l_continue                     ;продолжить
    .endif                              ;of Dr0=0
В противном случае исключение пришло от точки останова. А значит, War пытается открыть некий файл. Прежде всего, снимем эту точку останова и установим флаг TF (трассировочный, для последующей установки точки):
Код:
;установим флаг трассировки и снимем точку останова
    mov [cont.iDr0],ebx                 ;0
    lea eax,[cont]
    invoke SetThreadContext,esi,eax     ;установить

    mov [cont.ContextFlags],CONTEXT_CONTROL
    lea eax,[cont]
    invoke GetThreadContext,esi,eax
    or  [cont.regFlag],FLAG_TF          ;TF
Теперь начинаем анализировать параметры функции. Вначале читаем указатель на имя файла, а затем и само имя:
Код:
;проверим параметры функции
    mov eax,[cont.regEsp]               ;считать содержимое esp
    add eax,4                           ;получить указатель на имя файла
    invoke ReadProcessMemory,[pi.hProcess],eax,dwAddr,4,ebx
    invoke ReadProcessMemory,[pi.hProcess],[dwAddr],uBuf,14,ebx
А теперь проверим, не war3patch.mpq ли это. Для сравнения строк используем lstrcmpi, которая сравнивает строки без учёта регистра символов (Си тоже так умеет). И если это – war3patch.mpq, записываем туда другое имя:
Код:
invoke lstrcmpi,uBuf,szFile         ;сравним строки
    .ifz eax                            ;это - war3patch.mpq
     invoke WriteProcessMemory,[pi.hProcess],[dwAddr],szNewFile,N_SIZE,ebx
    .endif
Затем просто доустанавливаем контекст.
Вот, в принципе, и всё – далее идут лишь всякие «обёртки» цикла и завершение программы. Как видите, всё не так уж сложно.
Компилируем программу… И видим, что её размер равен 2Кб! (Кстати, размер программы всегда округляется asm’ом до величины, кратной 512 байтам).
.
Так, вижу, я остался в гордом одиночестве – все читатели уже потерялись. Что ж, пойду и я… :end:
Прикрепленные файлы
Тип файла: zip exs.zip (51.9 Кбайт, 127 просмотров )
Тип файла: zip waradd.zip (25.5 Кбайт, 108 просмотров )
Тип файла: zip warmod.zip (2.1 Кбайт, 111 просмотров )
Старый 03.04.2006, 13:37
PotioN

offline
Опыт: 1,204
Активность:
Действительно, такое написать не каждый способен.
Старый 03.04.2006, 15:28
zibada

offline
Опыт: отключен
да... 5 баллов.
кстати, если продолжить тему слежения за варом снаружи...
есть идея, как написать 100% надежный генератор листфайлов карт и других mpq-архивов без брутфорса, поиска имен по файлам карты и т.д.
идея проста: каким-то образом перехватывать все вызовы варом функций открытия файла по имени (SFileOpenFile и SFileOpenFileEx вроде) из storm.dll и дампить передаваемые имена файлов в лог.
тогда получение листфайла сведется к тому, что достаточно запустить с этой прогой-перехватчиком карту и немного ее погонять, чтобы гарантированно задействовать все файлы из нее =)
(если какие-то останутся неиспользованными, можно предположить, что они нафиг не нужны)
если кто-то такое напишет, это будет замечательно, моего скромного опыта, боюсь, на такие раскопки не хватит...
Старый 03.04.2006, 16:47
NETRAT

offline
Опыт: 83,762
Активность:
DimonT На самом деле подобного рода мегалоги займут очень много времени, когда я копался со Storm.dll заметил такую фигню что при большой частоте запросов она часто отвечает отказом, то есть просто пропускает файлы. Не вижу смысла отлавливать эти события ибо MPQ Recover делает то же самое - проверяет наличие файлов, к которым вар МОЖЕТ обращаться. Только описанный глюк делает саму программу чутка глючной, особенно это заметно при брутфорсе - это почти нереально
Старый 03.04.2006, 18:23
Sir Lothar

offline
Опыт: 5,740
Активность:
Алексей, моё тебе уважение. Хоть я в программировании, к сожалению, ничего и не смыслю, но вижу, что статья замечательная.
Старый 03.04.2006, 19:31
Nomad

offline
Опыт: 6,677
Активность:
Ничерта не понял, но всё равно класс! :D
Старый 03.04.2006, 21:01
NETRAT

offline
Опыт: 83,762
Активность:
Алексей Andrew II не так давно спрашивал как запихнуть между архивами еще один, что ты думаешь по этому поводу? Мне бы тоже было интересно возможно ли war3patch переписать сверху своим архивом, то есть дополнить его...

[Кстати, надо бы повесить глобальный хук на ShellExecute и поймать параметры коммандной строки вара когда мы запускаем его из WE - может там чего интересного есть]

По поводу сплайсинга - мы же можем определить модуль, который был перехвачен и отсеять варовские библиотеки - то есть, кажется, тут все будет работать, разве что перехват будет проходить чаще

Алексей, а разве нам обязательно запускаться из-под дебаггера? Работать с памятью - выставить разрешения и работать вроде бы можно из обычного процесса, проблема вроде бы в другом - найти дескриптор процесса вара и отследить блок памяти в котором он запустился...

Погодь, а почему вар открывает файлы с помощью CreateFile? Почему не OpenFile(Ex)?

Цитата:
Передача непосредственно нуля требует 2 байт кода, а передача содержимого регистра (в котором лежит тот же ноль) – только одного байта.
Хе, хе, а нас учат тому как в компе реализованы регистры на транзисторах и почему они такие быстрые =) Откуда такие подробности?

Цитата:
«короткость» многих инструкций
Думаю что дело еще и в том что многие параметры функции передаются в отдельных строках - через стек или регистры

Жаль что для многих ассемблер это всего лишь непонятные слова на три буквы =\

NETRAT добавил:
Поэтому-то меня больше интересует High-Level - потому что так глубоко в Low-Level я залезть не смогу - на это нужно намного больше времени и сил
Старый 04.04.2006, 01:56
Алексей
Где кошачья мята?!
offline
Опыт: 26,543
Активность:
Думаю, что можно, хотя придётся постараться. Для этого нужно:
1. Самостоятельно открыть новый архив (с помощью storm.dll)
2. Отслеживать открытие файла, находящегося в недрах MPQ.
3. Пробовать открыть этот файл в новом MPQ. Если он там есть - возвращать его данные, иначе - передавать управление стандартной SFileFileOpen.
4. По окончании работы - закрыть архив.
.
Далее - WE вроде бы не запускает War. Зачем? Разве что когда карту тестируешь...
Насчёт сплайсинга - да, можно отследить модуль, и всё будет работать. Вероятно, код был бы даже проще, нагляднее и компактнее. Но программы, пользующиеся сплайсингом, труднее отлаживать - при любой ошибке неторопливо падает вся система (думаю, тебе тоже приходилось испытывать эти незабываемые ощущения ;)). А при неверном использовании Debug API - падает только отлаживаемый процесс.
Почему нельзя пустить War как независимый процесс? Ну, а как тогда отследить отвобождение памяти (перезагрузку карты)? А так - ставим точку останова и ждём...
.
Разумеется, War не использует OpenFile. В Microsoft SDK написано: "Эта функция оставлена для совместимости с ранними версиями Windows. Использовать её не рекомендуется. Используйте CreateFile для открытия файла".
.
Сколько какие команды занимают - это описано в "AMD x86 Code Optimization Guide". Там вообще неплохо описано, как оптимизировать ассемблерный код. Между прочим, учёт особенностей процессоров помогает и при программировании на языках высокого уровня. Вот классический пример: объяснить, почему цикл
Код:
for (a=0; a<BLOCK_SIZE; a+=8*sizeof(int))
{
          x+=*(int *)((int)p+a);
          x+=*(int *)((int)p+a+1*sizeof(int));
          x+=*(int *)((int)p+a+2*sizeof(int));
          x+=*(int *)((int)p+a+3*sizeof(int));
          x+=*(int *)((int)p+a+4*sizeof(int));
          x+=*(int *)((int)p+a+5*sizeof(int));
          x+=*(int *)((int)p+a+6*sizeof(int));
          x+=*(int *)((int)p+a+7*sizeof(int));
}
работает вдвое быстрее конструкции
Код:
for (a=0; a<BLOCK_SIZE; a+=sizeof(int))
{
          x+=*(int *)((int)p+a);
}
хотя оба этих фрагмента делают одно и то же, причём вторая версия куда компактнее и нагляднее (но работает вдвое медленнее!).
Старый 04.04.2006, 10:57
NETRAT

offline
Опыт: 83,762
Активность:
Да, забавно, шелл намертво подвисает и потихоньку начинают отказывать службы. Просто даун =)
Просто с помощью такой утилиты можно значительно сьэкономить место засчет того что лишних файлов в установочном MPQ мода не будет
Мда, мне нужно время чтобы это обдумать...
Старый 04.04.2006, 11:58
exploder
iOS zealot
offline
Опыт: 19,394
Активность:
Автору респект зп подробную и простую статью...
Старый 04.04.2006, 21:29
Iron
Листовой
offline
Опыт: 23,587
Активность:
Это ппц, все какие-то сплошные программеры ...
Каким-то неполноценным идиотом себя чуствуешь. Все, складываю шмот и мотаю с xgm-а.
Старый 05.04.2006, 01:11
NETRAT

offline
Опыт: 83,762
Активность:
На самом деле их не так уж и много, но хотелось бы больше
Старый 05.04.2006, 01:25
Toadcop

offline
Опыт: 53,013
Активность:
Алексей спасибо ! буду изучать :) статья объемная это хорошо !
Старый 05.04.2006, 17:58
Mefist
Is it cocktail hour yet?
offline
Опыт: 98,240
Активность:
я статью не читал, но нетра или алексей, как посчитаете ее готовой - кидайте на сайт
Старый 05.04.2006, 18:08
NETRAT

offline
Опыт: 83,762
Активность:
Вполне готова, я немного не успеваю
Старый 05.04.2006, 19:35
exploder
iOS zealot
offline
Опыт: 19,394
Активность:
Можно чем извращатся и делать инвентари варовскими методами, можно отслеживать мышь (для того чтобы узнать действия над инвентарем), а сам инвентарь рисовать прямо по Варовскому контексту :)= Единственное гавны так это что необходимо будет много переменных для общения прога-варик и синхронизация прорисовки своих графических элементов и прорисовки сцены в Варе...
Старый 07.04.2006, 08:18
N.Sy.Prophet
Йа Байан
offline
Опыт: 13,122
Активность:
Цитата:
Сообщение от A2m_Nomad
Ничерта не понял, но всё равно класс! :D

истину глаголишь :)
Старый 08.04.2006, 19:31
NETRAT

offline
Опыт: 83,762
Активность:
Статья добавлена по ссылке http://xgm.guru/articles.php?section=wc3&name=inside_of_warcraft
Старый 08.04.2006, 21:31
Toadcop

offline
Опыт: 53,013
Активность:
Код:
for (a=0; a<BLOCK_SIZE; a+=8*sizeof(int))
{
x+=*(int *)((int)p+a);
x+=*(int *)((int)p+a+1*sizeof(int));
x+=*(int *)((int)p+a+2*sizeof(int));
x+=*(int *)((int)p+a+3*sizeof(int));
x+=*(int *)((int)p+a+4*sizeof(int));
x+=*(int *)((int)p+a+5*sizeof(int));
x+=*(int *)((int)p+a+6*sizeof(int));
x+=*(int *)((int)p+a+7*sizeof(int));
}работает вдвое быстрее конструкции
Код:
for (a=0; a<BLOCK_SIZE; a+=sizeof(int))
{
x+=*(int *)((int)p+a);
}
интересно... да кстати я на асемблере не писал но не уж такой и сложный и мне нравитьса что он компактный :)
ЗЫ где можно компилятор закачать и малинький хелп ?! :) был бы очень благодарен !
ЗЫ статья очень интересная !!!
Старый 09.04.2006, 16:28
Markiz

offline
Опыт: 11,392
Активность:
Статья - супер! Я почитал пока только процентов 50, но готов оценить статью на 100 из 100 баллов.
Дочитаю и буду просматривать внутренности вара.
Старый 09.04.2006, 17:41

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы можете скачивать файлы

BB-коды Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход



Часовой пояс GMT +3, время: 02:06.