Smertig
04.03.2014, 21:45
Доброго времени суток всем читателям.
В этой статье я попытаюсь наиболее понятным языком рассказать об основах редактирования клиента на примере удаления котов из него. Результат тут ([Ссылки могут видеть только зарегистрированные и активированные пользователи]).
Основные инструменты и требования:
Клиент PW (куда без него)
Cheat Engine (далее сокращение - СЕ)
CFF Explorer
Olly Debugger
Опыт использования СЕ
Pandora Box или небольшой опыт в OOG
Знать, чем отличается int (dword) и byte
Желание понять ассемблер
Статья разбита на три части для /dgs восприятия:
1. Вступление, или составляем план.
2. Ищем код, отвечающий за появление кота
3. Редактируем код под себя - удаляем котов
Часть 1.
С чего начать? Такой вопрос возникает всегда. Давайте рассуждать. Нужно удалить кота из клиента. Как? Как кот вообще попадает в клиент? Когда мы подходим к коту (или к персонажу) на достаточное расстояние (рассматривать только ситуации, когда мы изначально не видели кота), он появляется на экране, следовательно сервер присылает пакет с информацией об игроке и клиент прорисовывает его. Как клиент отличает кота от игрока? Информация в пакете, правильно. С этим разобрались, проверим потом.
Пакет попал в клиент, дальше клиент его должен обработать и нарисовать нам кота (или игрока обычного). Значит нам нужно найти место в памяти, где клиент проверяет, пришёл пакет с котом или игроком, и немножко поменять это место.
План есть, приступим!
Часть 2.
Начнём с определения флага "кота" в пакете. Тут первая проблема - кто не умеет пользоваться пандорой, тот вряд ли быстро разберется в этом. Поэтому тут я сразу выложу готовый результат:
Пакет 0x04 контейнера 0х00
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Порядок байт обратный в маске, поэтому "00 10 00 00" == 0x00001000 или 0x1000 просто. Следовательно клиент должен как-то проверять маску. Поищем же.
При получении и обработке пакета, идёт хоть одно обращение к базовому адресу клиента (ведь клиент должен записать полученного игрока куда нужно). Запускаем клиент в два окна, заходим за двух персов, улетаем куда-нибудь в пустынное место (чтобы не было других игроков) и расходимся так, чтобы с первого перса не было видно второго перса. Открываем СЕ, подключаемся к первому процессу пв. [Ссылки могут видеть только зарегистрированные и активированные пользователи] добавить адрес, вводим BA нашего клиента. [Ссылки могут видеть только зарегистрированные и активированные пользователи] ПКМ по нему - Find Out What accesses this address и ждём. Сразу [Ссылки могут видеть только зарегистрированные и активированные пользователи] появляться сотни адресов и клиент начнёт виснуть. Нужно дождаться, пока адреса перестанут появляться, при этом можно побегать в клиенте за первого, понажимать что-нибудь, но при этом поблизости никого не должно быть и никто не должен появиться. Когда адреса перестанут появляться, [Ссылки могут видеть только зарегистрированные и активированные пользователи] последний адрес в этом списке, подойдите вторым окном к первому так, чтобы появиться в зоне видимости. В списке адресов тут же [Ссылки могут видеть только зарегистрированные и активированные пользователи] несколько новых адресов (или не несколько, смотря сколько ждали). Сразу жмите Stop. Лично у меня появилось около сотни лишних, но нас интересуют только те, что вызвались первыми и по одному разу (скрин выше). Скопируйте эти адреса в блокнот (на случай случайного закрытия окошка).
Итак, у нас есть адреса функций, который вызвались при появлении другого игрока. И еще мы знаем, что в одной из них должна быть проверка флага. Если кликнуть по адресу в окошке - Show disassembler, мы перейдём к самой функции. Мы знаем, что где-то должна идти проверка маски. Маска - 4 байта. Ассемблер любит сравнивать командами test или cmp. В нём вместо переменных регистры:
eax
ebx
ecx и другие.
Это 4-х байтовые регистры. Почитать можно тут ([Ссылки могут видеть только зарегистрированные и активированные пользователи] D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81%D0%BE%D1%80%D0 %B0). Видим примерно следующее:
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Это означает следующее. Если процессор засунет нашу маску в регистр eax, то получится следующее:
eax = 00 00 10 00
ax = 10 00
ah = 10
al = 00
где ah, al - "четвертинки регистра", а ax - половинка. Для ebx аналогично, только не "а", а "b". Следовательно процессору выгоднее всего при сравнении маски пользоваться сравнением ah (bh или другие) с 10. После сравнения обычно идёт условный прыжок JE (Jump if Equal). А перед сравнением должно идти:
mov #, * (# - регистр eax, ebx, ecx или другой, а * - какой-либо адрес)
Следовательно мы ищем среди этих функций конструкцию:
mov eax, *адрес*
test ah, 10
je #адрес#
Повторюсь, вместо eax может быть любой другой регистр. Помимо этой конструкции, там должно быть много
test **, **
je **
так как проверка маски идёт не один раз.
Мне повезло, эта функция была пятой по счёту (чуть пролистать еще вниз) и выглядела вот [Ссылки могут видеть только зарегистрированные и активированные пользователи]
?"]Небольшое отступление в ассемблер:
mov - команда копирования. Есть несколько видов:
1) mov *, #
Копирует из регистра # в регистр *.
Пример: mov eax, ebx - делает eax равным ebx
2) mov *, [#]
Читает значение по адресу # и копирует его в *
Пример: mov eax, [ebx]
3) mov , #
Вместо того, что лежит по адресу * ставит туда #
Пример: mov [eax], ebx
Итак, мы видим много раз повторяющуюся конструкцию mov eax, [ebp+16] (16 - это в hex ([Ссылки могут видеть только зарегистрированные и активированные пользователи] 0%B0%D1%82%D0%B5%D1%80%D0%B8%D1%87%D0%BD%D0%B0%D1% 8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D1% 81%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F )'e, dec - 22). А теперь вернемся к нашим пакетам. Как [Ссылки могут видеть только зарегистрированные и активированные пользователи] ровно на 22-м месте стоит маска. Значит мы на верном пути! Записываем в блокнот адрес начала функции (у [Ссылки могут видеть только зарегистрированные и активированные пользователи] 00453D40)
Проверить, реально ли мы нашли то место, можно [Ссылки могут видеть только зарегистрированные и активированные пользователи] (далее бряк) в любом месте функции (ф5 в СЕ), вызвать функцию (для этого вторым окном отойти и подойти снова). Выполнение [Ссылки могут видеть только зарегистрированные и активированные пользователи] там, где вы поставили бряк. Копируем ebp, внизу в памяти жмём на любой байт, Ctrl-G, [Ссылки могут видеть только зарегистрированные и активированные пользователи] адрес и видим наш [Ссылки могут видеть только зарегистрированные и активированные пользователи] Кстати, чтобы продолжить выполнение после остановки на бряке, снимите заодно его (ф5) и жмите ф9 для продолжения выполнения команд.
Часть 3.
Мало кто до сюда доживает. Но ведь здесь происходит самое интересное!
Нам известна функция, известно, что пакет (а точнее его кусок с информацией об одном персонаже) хранится по адресу ebp. Я предлагаю модифицировать код так, чтобы "у" координата обнулялась, если персонаж == кот. Приступим! (первое окно можно закрыть, бегающего твина лучше оставить)
Чтобы писать свой код в клиенте, нам нужно место. В бой вступает CFF Explorer. Запускаем, открываем наш ехешник, жмём слева Section Headers и видим [Ссылки могут видеть только зарегистрированные и активированные пользователи] Дальше [Ссылки могут видеть только зарегистрированные и активированные пользователи] пкм по пустому месту, Add Section (Empty space), размер 1000 - хватит, ок. Внизу [Ссылки могут видеть только зарегистрированные и активированные пользователи] еще одна строчка (я назвал её .zhyk - начинаться должно с точки). [Ссылки могут видеть только зарегистрированные и активированные пользователи] адрес нам понадобится. Сохраняем под новым именем. Итак, место для наших каляк-маляк у нас есть.
Теперь открываем Olly Debugger (далее оля), [Ссылки могут видеть только зарегистрированные и активированные пользователи] в нём новый ехе'шник, переходим по адресу функции (Ctrl-G), найденному в конце 2-й части. У меня 00453D40. Если олька ругается [Ссылки могут видеть только зарегистрированные и активированные пользователи] просто допишите пару нулей перед адресом. Теперь нужно реализовать следующую схему:
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Поэтому выбираем какое-нибудь место в функции (после push'ов всяких), я выбрал [Ссылки могут видеть только зарегистрированные и активированные пользователи] Переписываем в блокнот все эти инструкции (дабл-клик, копипаст). Вместо первой инструкции пишем
JMP 00C75000
где С75000 = адрес, который выписали из CFF Explorer'а + 0x400000 (почему именно 0х400000? это адрес образа файла при копировании его в память)Смотрим, какие инструкции мы [Ссылки могут видеть только зарегистрированные и активированные пользователи] этим джампом.
Не хватает:
LEA EDI,DWORD PTR SS:[EBP+4]
MOV DWORD PTR DS:[ESI+6E8],EAX
Запомним этот факт.
Запишем [Ссылки могут видеть только зарегистрированные и активированные пользователи] адрес (нам ведь сюда нужно вернуться).
Теперь переходим на нашу память. Жмём Ctrl-G и тот адрес, на который мы прописали джамп. Ммм.. [Ссылки могут видеть только зарегистрированные и активированные пользователи]!
Пишем следующий код (построчно подряд, получится [Ссылки могут видеть только зарегистрированные и активированные пользователи])
PUSH EAX
MOV EAX,DWORD PTR SS:[EBP+16]
TEST AH,10
JE SHORT *наш адрес (который CFF+400000) плюс 10* (у меня получилось JE SHORT 00C75010)
MOV DWORD PTR SS:[EBP+8],0
POP EAX
LEA EDI,DWORD PTR SS:[EBP+4]
MOV DWORD PTR DS:[ESI+6E8],EAX
JMP 00453D5B
Эти строчки нужны для того, чтобы не потерять регистр eax и смысл начального кода. Так как мы его в дальнейшем будем использовать, то мы его сохраняем (командой push, в стеке), дальше используем его для своих делишек, потом вытаскиваем обратно. Две строчки после pop eax - это те строки, которые мы потеряли, когда прописали в основном коде jmp.
Этот джамп возвращает нас на тот адрес, который мы записали ранее ([Ссылки могут видеть только зарегистрированные и активированные пользователи]).
Рассмотрим оставшиеся 4 строчки отдельно.
MOV EAX,DWORD PTR SS:[EBP+16]
кратко mov eax, [ebp+16] - записываем в регистр (кому удобнее - переменную) eax значение по адресу ebp+16 (наша маска)
TEST AH,10
см. ниже
JE SHORT 00C75010 (мой адрес)
проверка на "кота" и если "не кот", то перепрыгнуть на команду pop eax если в test'е выше байты не совпали (ah AND 10 == 0), тогда происходит джамп на адрес команды pop eax
MOV DWORD PTR SS:[EBP+8],0
кратко mov [ebp+8], 0 - если предыдущий джамп не произошёл, то есть перс оказался котом, тогда мы устанавливаем y=0 (или z, везде по разному называется, хотя на самом деле у китайцев высота это у)
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Теперь переходим обратно по адресу начальной функции (где писали jmp), выделяем наш код, подсвеченный красным, пкм, Copy to executable -> All modifications - [Ссылки могут видеть только зарегистрированные и активированные пользователи] Далее Copy all. В появившемся окошке пкм - [Ссылки могут видеть только зарегистрированные и активированные пользователи] Сохраняем. Готово!
Эпилог
Планировалась короткая статья, но получилось что-то длинное /nyan
Давно, когда я был новичком, я перечитывал статьи TBX1n'а, Dinmaite'а, FreePvP, dwa83 и многих других десятки раз и каждый раз вычитывал что-то новое. А теперь я сам владею тем, чем могу поделиться с людьми в своих статьях. Надеюсь, что донёс до тебя, читатель, хоть какие-нибудь полезные мысли. Информация - это наиважнейший ресурс.
Сделано по просьбе ([Ссылки могут видеть только зарегистрированные и активированные пользователи]).
Флуд буду тереть нещадно, о неточностях в лс, о важных уточнениях в комментарии.
P.S. Планирую написать ещё пару статей на тему редактирования клиента, только подкиньте идеи.
UPDATE: Картинки все сломались, восстанавливать долго, извините уж :)
В этой статье я попытаюсь наиболее понятным языком рассказать об основах редактирования клиента на примере удаления котов из него. Результат тут ([Ссылки могут видеть только зарегистрированные и активированные пользователи]).
Основные инструменты и требования:
Клиент PW (куда без него)
Cheat Engine (далее сокращение - СЕ)
CFF Explorer
Olly Debugger
Опыт использования СЕ
Pandora Box или небольшой опыт в OOG
Знать, чем отличается int (dword) и byte
Желание понять ассемблер
Статья разбита на три части для /dgs восприятия:
1. Вступление, или составляем план.
2. Ищем код, отвечающий за появление кота
3. Редактируем код под себя - удаляем котов
Часть 1.
С чего начать? Такой вопрос возникает всегда. Давайте рассуждать. Нужно удалить кота из клиента. Как? Как кот вообще попадает в клиент? Когда мы подходим к коту (или к персонажу) на достаточное расстояние (рассматривать только ситуации, когда мы изначально не видели кота), он появляется на экране, следовательно сервер присылает пакет с информацией об игроке и клиент прорисовывает его. Как клиент отличает кота от игрока? Информация в пакете, правильно. С этим разобрались, проверим потом.
Пакет попал в клиент, дальше клиент его должен обработать и нарисовать нам кота (или игрока обычного). Значит нам нужно найти место в памяти, где клиент проверяет, пришёл пакет с котом или игроком, и немножко поменять это место.
План есть, приступим!
Часть 2.
Начнём с определения флага "кота" в пакете. Тут первая проблема - кто не умеет пользоваться пандорой, тот вряд ли быстро разберется в этом. Поэтому тут я сразу выложу готовый результат:
Пакет 0x04 контейнера 0х00
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Порядок байт обратный в маске, поэтому "00 10 00 00" == 0x00001000 или 0x1000 просто. Следовательно клиент должен как-то проверять маску. Поищем же.
При получении и обработке пакета, идёт хоть одно обращение к базовому адресу клиента (ведь клиент должен записать полученного игрока куда нужно). Запускаем клиент в два окна, заходим за двух персов, улетаем куда-нибудь в пустынное место (чтобы не было других игроков) и расходимся так, чтобы с первого перса не было видно второго перса. Открываем СЕ, подключаемся к первому процессу пв. [Ссылки могут видеть только зарегистрированные и активированные пользователи] добавить адрес, вводим BA нашего клиента. [Ссылки могут видеть только зарегистрированные и активированные пользователи] ПКМ по нему - Find Out What accesses this address и ждём. Сразу [Ссылки могут видеть только зарегистрированные и активированные пользователи] появляться сотни адресов и клиент начнёт виснуть. Нужно дождаться, пока адреса перестанут появляться, при этом можно побегать в клиенте за первого, понажимать что-нибудь, но при этом поблизости никого не должно быть и никто не должен появиться. Когда адреса перестанут появляться, [Ссылки могут видеть только зарегистрированные и активированные пользователи] последний адрес в этом списке, подойдите вторым окном к первому так, чтобы появиться в зоне видимости. В списке адресов тут же [Ссылки могут видеть только зарегистрированные и активированные пользователи] несколько новых адресов (или не несколько, смотря сколько ждали). Сразу жмите Stop. Лично у меня появилось около сотни лишних, но нас интересуют только те, что вызвались первыми и по одному разу (скрин выше). Скопируйте эти адреса в блокнот (на случай случайного закрытия окошка).
Итак, у нас есть адреса функций, который вызвались при появлении другого игрока. И еще мы знаем, что в одной из них должна быть проверка флага. Если кликнуть по адресу в окошке - Show disassembler, мы перейдём к самой функции. Мы знаем, что где-то должна идти проверка маски. Маска - 4 байта. Ассемблер любит сравнивать командами test или cmp. В нём вместо переменных регистры:
eax
ebx
ecx и другие.
Это 4-х байтовые регистры. Почитать можно тут ([Ссылки могут видеть только зарегистрированные и активированные пользователи] D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81%D0%BE%D1%80%D0 %B0). Видим примерно следующее:
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Это означает следующее. Если процессор засунет нашу маску в регистр eax, то получится следующее:
eax = 00 00 10 00
ax = 10 00
ah = 10
al = 00
где ah, al - "четвертинки регистра", а ax - половинка. Для ebx аналогично, только не "а", а "b". Следовательно процессору выгоднее всего при сравнении маски пользоваться сравнением ah (bh или другие) с 10. После сравнения обычно идёт условный прыжок JE (Jump if Equal). А перед сравнением должно идти:
mov #, * (# - регистр eax, ebx, ecx или другой, а * - какой-либо адрес)
Следовательно мы ищем среди этих функций конструкцию:
mov eax, *адрес*
test ah, 10
je #адрес#
Повторюсь, вместо eax может быть любой другой регистр. Помимо этой конструкции, там должно быть много
test **, **
je **
так как проверка маски идёт не один раз.
Мне повезло, эта функция была пятой по счёту (чуть пролистать еще вниз) и выглядела вот [Ссылки могут видеть только зарегистрированные и активированные пользователи]
?"]Небольшое отступление в ассемблер:
mov - команда копирования. Есть несколько видов:
1) mov *, #
Копирует из регистра # в регистр *.
Пример: mov eax, ebx - делает eax равным ebx
2) mov *, [#]
Читает значение по адресу # и копирует его в *
Пример: mov eax, [ebx]
3) mov , #
Вместо того, что лежит по адресу * ставит туда #
Пример: mov [eax], ebx
Итак, мы видим много раз повторяющуюся конструкцию mov eax, [ebp+16] (16 - это в hex ([Ссылки могут видеть только зарегистрированные и активированные пользователи] 0%B0%D1%82%D0%B5%D1%80%D0%B8%D1%87%D0%BD%D0%B0%D1% 8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D1% 81%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F )'e, dec - 22). А теперь вернемся к нашим пакетам. Как [Ссылки могут видеть только зарегистрированные и активированные пользователи] ровно на 22-м месте стоит маска. Значит мы на верном пути! Записываем в блокнот адрес начала функции (у [Ссылки могут видеть только зарегистрированные и активированные пользователи] 00453D40)
Проверить, реально ли мы нашли то место, можно [Ссылки могут видеть только зарегистрированные и активированные пользователи] (далее бряк) в любом месте функции (ф5 в СЕ), вызвать функцию (для этого вторым окном отойти и подойти снова). Выполнение [Ссылки могут видеть только зарегистрированные и активированные пользователи] там, где вы поставили бряк. Копируем ebp, внизу в памяти жмём на любой байт, Ctrl-G, [Ссылки могут видеть только зарегистрированные и активированные пользователи] адрес и видим наш [Ссылки могут видеть только зарегистрированные и активированные пользователи] Кстати, чтобы продолжить выполнение после остановки на бряке, снимите заодно его (ф5) и жмите ф9 для продолжения выполнения команд.
Часть 3.
Мало кто до сюда доживает. Но ведь здесь происходит самое интересное!
Нам известна функция, известно, что пакет (а точнее его кусок с информацией об одном персонаже) хранится по адресу ebp. Я предлагаю модифицировать код так, чтобы "у" координата обнулялась, если персонаж == кот. Приступим! (первое окно можно закрыть, бегающего твина лучше оставить)
Чтобы писать свой код в клиенте, нам нужно место. В бой вступает CFF Explorer. Запускаем, открываем наш ехешник, жмём слева Section Headers и видим [Ссылки могут видеть только зарегистрированные и активированные пользователи] Дальше [Ссылки могут видеть только зарегистрированные и активированные пользователи] пкм по пустому месту, Add Section (Empty space), размер 1000 - хватит, ок. Внизу [Ссылки могут видеть только зарегистрированные и активированные пользователи] еще одна строчка (я назвал её .zhyk - начинаться должно с точки). [Ссылки могут видеть только зарегистрированные и активированные пользователи] адрес нам понадобится. Сохраняем под новым именем. Итак, место для наших каляк-маляк у нас есть.
Теперь открываем Olly Debugger (далее оля), [Ссылки могут видеть только зарегистрированные и активированные пользователи] в нём новый ехе'шник, переходим по адресу функции (Ctrl-G), найденному в конце 2-й части. У меня 00453D40. Если олька ругается [Ссылки могут видеть только зарегистрированные и активированные пользователи] просто допишите пару нулей перед адресом. Теперь нужно реализовать следующую схему:
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Поэтому выбираем какое-нибудь место в функции (после push'ов всяких), я выбрал [Ссылки могут видеть только зарегистрированные и активированные пользователи] Переписываем в блокнот все эти инструкции (дабл-клик, копипаст). Вместо первой инструкции пишем
JMP 00C75000
где С75000 = адрес, который выписали из CFF Explorer'а + 0x400000 (почему именно 0х400000? это адрес образа файла при копировании его в память)Смотрим, какие инструкции мы [Ссылки могут видеть только зарегистрированные и активированные пользователи] этим джампом.
Не хватает:
LEA EDI,DWORD PTR SS:[EBP+4]
MOV DWORD PTR DS:[ESI+6E8],EAX
Запомним этот факт.
Запишем [Ссылки могут видеть только зарегистрированные и активированные пользователи] адрес (нам ведь сюда нужно вернуться).
Теперь переходим на нашу память. Жмём Ctrl-G и тот адрес, на который мы прописали джамп. Ммм.. [Ссылки могут видеть только зарегистрированные и активированные пользователи]!
Пишем следующий код (построчно подряд, получится [Ссылки могут видеть только зарегистрированные и активированные пользователи])
PUSH EAX
MOV EAX,DWORD PTR SS:[EBP+16]
TEST AH,10
JE SHORT *наш адрес (который CFF+400000) плюс 10* (у меня получилось JE SHORT 00C75010)
MOV DWORD PTR SS:[EBP+8],0
POP EAX
LEA EDI,DWORD PTR SS:[EBP+4]
MOV DWORD PTR DS:[ESI+6E8],EAX
JMP 00453D5B
Эти строчки нужны для того, чтобы не потерять регистр eax и смысл начального кода. Так как мы его в дальнейшем будем использовать, то мы его сохраняем (командой push, в стеке), дальше используем его для своих делишек, потом вытаскиваем обратно. Две строчки после pop eax - это те строки, которые мы потеряли, когда прописали в основном коде jmp.
Этот джамп возвращает нас на тот адрес, который мы записали ранее ([Ссылки могут видеть только зарегистрированные и активированные пользователи]).
Рассмотрим оставшиеся 4 строчки отдельно.
MOV EAX,DWORD PTR SS:[EBP+16]
кратко mov eax, [ebp+16] - записываем в регистр (кому удобнее - переменную) eax значение по адресу ebp+16 (наша маска)
TEST AH,10
см. ниже
JE SHORT 00C75010 (мой адрес)
проверка на "кота" и если "не кот", то перепрыгнуть на команду pop eax если в test'е выше байты не совпали (ah AND 10 == 0), тогда происходит джамп на адрес команды pop eax
MOV DWORD PTR SS:[EBP+8],0
кратко mov [ebp+8], 0 - если предыдущий джамп не произошёл, то есть перс оказался котом, тогда мы устанавливаем y=0 (или z, везде по разному называется, хотя на самом деле у китайцев высота это у)
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Теперь переходим обратно по адресу начальной функции (где писали jmp), выделяем наш код, подсвеченный красным, пкм, Copy to executable -> All modifications - [Ссылки могут видеть только зарегистрированные и активированные пользователи] Далее Copy all. В появившемся окошке пкм - [Ссылки могут видеть только зарегистрированные и активированные пользователи] Сохраняем. Готово!
Эпилог
Планировалась короткая статья, но получилось что-то длинное /nyan
Давно, когда я был новичком, я перечитывал статьи TBX1n'а, Dinmaite'а, FreePvP, dwa83 и многих других десятки раз и каждый раз вычитывал что-то новое. А теперь я сам владею тем, чем могу поделиться с людьми в своих статьях. Надеюсь, что донёс до тебя, читатель, хоть какие-нибудь полезные мысли. Информация - это наиважнейший ресурс.
Сделано по просьбе ([Ссылки могут видеть только зарегистрированные и активированные пользователи]).
Флуд буду тереть нещадно, о неточностях в лс, о важных уточнениях в комментарии.
P.S. Планирую написать ещё пару статей на тему редактирования клиента, только подкиньте идеи.
UPDATE: Картинки все сломались, восстанавливать долго, извините уж :)