cHAm, NETRAT

WarCraft 3: Часть вторая

Создаем загрузочный код
В первой части я рассказал, как создавать простые загрузочные коды. Однако я не уделил нужное внимание шифровке предметов и безопасности кода. Сейчас я буду долго и упорно это внимание уделять =). Поехали…

Глава первая «Что мы знаем» или «Я и так не нуб!»

В первой части я вкратце рассказал о шифровке айтемов и безопасности. А именно, что:
1.Для шифровки предметов можно дать каждой вещи свой номер. Для этого есть функция SetItemNumber(integer id, integer n).
2.После определения номера можно получить как номер предмета по типу ( функция GetItemNumber(integer id) ), так и тип по номеру ( функция GetItemId(integer n) ).
3.Ввиду неизбежного столкновения с агрессивной разновидностью геймеров – читерами, нужно вставлять в пасс контрольные суммы.
И на этом, вроде, все. Если ты знаешь больше, значит тебе незачем читать дальше =).

Глава вторая «Шифруем предметы» или «Шестерых одним ударом»

Уверен, следуя моей статье, ты создал карту, где у тебя создается загрузочный код и сейчас она у тебя на винте. Нет? Ну тогда качай карту-пример из аттача к первой части. В общем, как бы то ни было, карта с генерацией пасса у тебя должна быть открыта. Открыл? Тогда поехали!
Для начала мы будем делать поддержку трех айтемов – зелья хелсов, зелья маны и креста перерождения. Кидай их все на карту, рядом с хиром. Теперь открываешь редактор объектов на вкладке «Предметы», ищешь эти три айтема там. Когда найдешь первый айтем выдели его и жми Ctrl+D. После этого перед названием появится id предмета, запиши его куда-нить. И снова жми Ctrl+D.

И так со всеми тремя айтемами. Сделал? Ок! Теперь открываешь триггер InitPvP и добавляешь там три действия:

Custom script:   call SetItemNumber('ankh',1)
Custom script:   call SetItemNumber('phea',2)
Custom script:   call SetItemNumber('pman',3)
Как видишь, мы присваиваем кресту перерождения и зельям номера 1,2 и 3 соответственно. Заметь, что ид айтема пишется в ОДИНАРНЫХ кавычках. Номера мы поставили, теперь будем это все шифровать.
Создаешь новую переменную ItemId – массив типа Тип предмета (Item type). Открываешь триггер Saving и добавляешь перед циклом преобразования действия:

For each (Integer A) from 1 to 6, do (Set ItemId[(Integer A)] = 
      (Item-type of (Item carried by Hero in slot (Integer A))))
For each (Integer A) from 1 to 6, do (Custom script:set udg_str[bj_forLoopAIndex+10]=
      GetCharCode(GetItemNumber(udg_ItemId[bj_forLoopAIndex])))
Разъясняю, если ты не понял: Первый цикл сохраняет типы айтемов у героя в переменную ItemId с номерами 1-6. Второй цикл заносит в переменную str с номерами 11-16 номера предметов, занесенные в ItemId. Для расширения кругозора, скажу, что в jass типы «тип юнита», «тип предмета» и «целочисленная» - абсолютно идентичны, поэтому их можно заменять без опасения вылета вара. Теперь в конце триггера заменяем вывод пасса на два действия:

Set str[102] = (str[11] + (str[12] + (str[13] + (str[14] + (str[15] + str[16])))))
Игра - Display to (All) the text: (str[100] + (- + (str[101] + (- + str[102]))))
То есть добавляем пассу третью часть с типами айтемов. В итоге должно получиться так:
Готово? Отлично! Теперь приступим к загрузке айтемов. Открываем триггер Loading и меняем действие

If ((Length of str[0]) не равно 15) then do 
     (Skip remaining actions) 
else do 
     (Do nothing)
На

If ((Length of str[0]) не равно 22) then do 
     (Skip remaining actions) 
else do 
     (Do nothing)
То есть увеличиваем длину пасса. И перед первым циклом вставляем действия:

Set str[11] = (Substring(str[0], 17, 17))
Set str[12] = (Substring(str[0], 18, 18))
Set str[13] = (Substring(str[0], 19, 19))
Set str[14] = (Substring(str[0], 20, 20))
Set str[15] = (Substring(str[0], 21, 21))
Set str[16] = (Substring(str[0], 22, 22))
For each (Integer A) from 1 to 6, do (Actions)
    Цикл - Действия
        Custom script: set udg_ItemId[bj_forLoopAIndex]=
           GetItemId(Decode(udg_str[bj_forLoopAIndex+10]))
Здесь мы извлекаем зашифрованные айтемы из пасса, а затем расшифровываем их с сохраняем в ItemId с номерами 1-6. Теперь в действиях If прописываем действия:

For each (Integer A) from 1 to 6, do (Actions)
    Цикл - Действия
        Герой - Create ItemId[(Integer A)] and give it to Hero
То есть добавляем хиру сохраненные айтемы. Должен получиться такой триггер:
Ну, вот и готово сохранение айтемов. Едем дальше, видим мост, на мосту ворона сохнет, я ее беру за хвост, и пускай она помокнет…

Глава третья «Безопасность кода» или «Как бородануть читеров»

Я думаю, хорош уже практики. Карту закрываем и займемся теорией. Щас я буду рассказывать про приемы безопасности кода.
Прием 1. Контрольная сумма.
Про этот прием я уже рассказывал. То есть создается число равное сумме (а может, и разности) некоторых параметров. Например controlsumm=p1+p2+p3-p4+p5. Контрольная сумма весит 1-2 символа, но это незаменимая штука. Она позволяет избежать неприятности с читерами и опечатками.
Прием 2. Именной пасс.
Некоторые «умники» догадываются списывать чужие пассы и забирать их себе. Чтобы этого не происходило, нужно в пассе зашифровать имя игрока. Как это сделать? Легко! Ставим три символа (не обязательно подряд) в пасс. Один символ – первый символ имени игрока, набравшего «-save», второй символ, соответственно второй символ имени, а третий – длинна имени игрока. Этого хватит вполне. Вероятность, что найдутся два разных имени, соответствующих одному шаблону около 3%, поэтому больше заморачиваться не стоит. При расшифровке проверяем, чтобы символы соответствовали реальным символам имени игрока.
Прием 3. Переменный словарь. !!!Не для нубов!!!
Этот прием работает только в CCS. Если ты CCS не юзаешь, можешь не читать. Суть состоит вот в чем: У тебя есть массив строк – словарей, скажем, таких
**S[0]=”0918273645”
S[1]=”0123456789”
S[2]=”9876543210”
S[3]=”9081726354”**
Перед генерацией кода берешь целочисленную переменную (например, I) и ставишь ей RandomNumer(1,3), далее пишешь

Custom script: call CustomGlossary(udg_S[0])
Custom script: set str[1]=GetCharCode(udg_I)
Custom script: call CustomGlossary(udg_S[udg_I])
То есть, ставишь словарь ”0918273645” и шифруешь переменную I по нему. Далее ставишь один из словарей массива S, а именно, который имеет номер I. Все! Теперь каждый раз, даже при одинаковых данных пасс будет разный. А узнать, какой именно легко – надо только разкодировать первый символ по словарю ”0918273645”!
Вот три кита, на которых держится безопасность пароля. Усё.

Глава четвертая «Оптимизация по методу NETRAT’a» или «Подать сюда микроскоп!»

Слово предоставляется NETRAT’у.
Рассмотрим теперь более полную модель - пускай нам нужно сохранить героя в такой карте, в которой могут быть использованы книжки ловкости, силы, жизни - то есть такого героя, которому необходимо сохранять дополнительно атрибуты и очки жизни. Разумеется, ни один нормальный герой не может обойтись без инвентаря - ему будет очень обидно, если супер-пупер меч, который он заработал в бесчисленных схватках ценой своих постоянно ноющих и незаживающих ран и ссадин, не перенесется в другую карту, а попросту будет потерян. То есть добавим так же к этой модели и 6-слотовый инвентарь. Возьмем именно шесть слотов, так как это стандартный размер, следовательно такой инвентарь можно считать самым популярным. Если у вас инвентарь более 6 слотов, то вы можете оформить спецзаказ, обращаясь к авторам данной статьи (Сомневаюсь, что найдутся такие люди, которые юзают инвентарь больше 6 слотов и не смогут сделать генератор пассов сами – прим. cHAm), либо сделать все по аналогии самому. Разумеется, помимо уровня героя, неплохо бы еще запоминать уровни его скиллов, добавим и этот пункт в модель.
Итак, входные данные:
  1. Уровень героя
  2. Уровни скиллов
  3. Атрибуты
  4. Дополнительные очки жизни
  5. Инвентарь
  6. Ресурсы
Теперь опишем входные данные более подробно. Пределы будем брать для среднестатистической RPG карты - то есть такие, которых хватает хорошей карте.
  1. Уровень героя - целое число, находится между 0 и 100.
  2. Уровни скиллов - пускай у нашего героя 5 скиллов, но сумма уровней скиллов не может превосходить уровень героя - это при нормальной прокачке - то есть когда за уровень дается одно очко скилла. Разумеется, скиллы могут быть неравнозначными - то есть могут быть ультимейты, которые мы получаем не так уж и часто. Тогда предположим, что уровень каждого из скиллов не может превосходить 30. Тогда рассчитать максимальное число, которое может понадобиться для хранения данных о скиллах нам поможет комбинаторика. Это число равно 30*30*30*30*30 = 3^5 * 10^5 = 243 * 10^5. Это достаточно большое число, поэтому, пока, оставим его в таком виде.
  3. Аттрибуты - три аттрибута, каждый из которых не превосходит 500. Использовать значения больше не рекомендуется, потому что это уже попахивает извращением, в всяком случае карта из рпг может перейти в разряд аркадных. Таким образом все та же комбинаторика дает нам верхний предел числа, которое может однозначно определять все 3 аттрибута как 500*500*500 = 125 * 10^6. Аналогично и это число оставим в таком виде.
  4. Дополнительные очки жизни - это очки жизни, которые могут быть получены при помощи томов жизни. Колеблются в пределах от 0 до 20000. Однако эти очки будут
кратны 50 так как том жизни поднимает очки на 50. Значит, мы можем заменить это поле на очки жизни, поделенные на модификатор жизни. В данном случае, модификатор очков жизни равен 50, значит верхний предел для этого поля будет равен 20000/50 = 400. Если вы будете использовать значения больше 20000, то можете увеличить модификатор жизни. Таким образом, мы можем сказать, что значение этого поля колеблется в промежутке между 0 и 400 с модификатором 50.
  1. Инвентарь - не думаю, что в своей карте вы будете использовать более, чем 400 вещей. Использовать строковые коды(или соответствующие им численные дескрипторы) для идентификации вещей - это верх расточительства. Объясняю почему - строковые коды используют для идентификации объекта 4 символа(эквивалентные им числовые дескрипторы являются числами порядка 10) - то есть один слот инвентаря будет кодироваться числами десятого порядка - то есть число будет колебаться от 0 до 10000000000, и это только один слот. Нетрудно посчитать что 6 слотов таким образом будут кодироваться 10^60 числом, и в итоге выйдет, что один только инвентарь кодируется 30 символами. Почему так получается - да потому что игра Warcraft использует универсальную кодировку всех своих объектов - то есть каждый игровой объект - будь то дерево, камень, казармы, грант, олень или маска Соби, имеет свой идентификатор типа, причем этот идентификатор уникален. А поскольку в игре используется огромное количество различных объектов, а еще существует возможность добавления пользовательских объектов, то для идентификации используются числа типа longint с огромным верхним пределом. Нам же нужна не универсальная идентификация, а идентификация только вещей. Для этого необходимо создать базу данных вещей - нечто вроде двух массивов - один массив типа int, а второй - item-type. Первый будет содержать
идентификатор вещи по нашей системе, а второй - по системе игры Warcraft. Это может показаться непосильной задачей, однако база такого типа уже создавалась в наработке DimonT'а "Полноэкранный инвентарь", ее нетрудно адаптировать для наших нужд (разумеется при некоторых навыках жасс-скриптера). Таким образом, весь инвентарь можно закодировать числом, находящимся между 0 и 400^6 = 4096 * 10^12.
  1. Ресурсы - тут все достаточно просто. Пусть золото находится в пределах от 0 до 100000, тогда возьмем это поле с модификатором 10, получим предел равным 10000 с модификатором 10. Если у вас предел больше, то модификатор тоже можно взять больше. Дерево аналогично - от 0 до 10000 с модификатором 10. Таким образом золото и дерево мы можем закодировать числом, предел которого равен 10^8.
Теперь посмотрим, какое число у нас получается в итоге:
100 * 243 * 10^5 * 125 * 10^6 * 400 * 4096 * 10^12 * 10^8 = 243 * 125 * 4 * 4096 * 10^34 = 497664000 * 10^34 = 497664 * 10^37
Немаленькое число получается, неправда ли?!
Теперь, возьмем максимально возможный словарь - 90 символов.(такой словарь в пассе смотрится крайне некрасиво, поэтому-то я и сделал длину по умолчанию 60. Не спеши всегда ставить 90 символов. - прим. cHAm) И возведем это число в 22-юю степень, получим число, равное 90^22 = 984770902183611232881 * 10^22 ] 984770 * 10^37 а, значит большее числа, которое нам нужно чтобы закодировать всю модель. Таким образом получаем, что для кодирования описанной выше модели, нам необходимо всего 22 символа - то есть код может состоять из 22х символов. Однако это нереализуемо. Для того, чтобы получить оптимальный код, нам придется работать с очень большими числами - более чем 40го порядка. Игра Warcraft неспособна на такие манипуляции. Поэтому нам придется уменьшить эти числа и увеличить длину кода.
Словарь 90 символов позволяет нам наиболее оптимально кодировать числа, ограниченные числами, степени 90, то есть:
90 - один символ
8100 - два символа
729000 - три символа
Числа больше брать не рекомендуется, так как они могут оказаться слишком большими для расчетов.
Объясню теперь, как наиболее оптимально использовать словарь в 90 символов в описанной системе:
  1. Для начала, мы можем объединить два поля - поле уровня и поле дополнительных очков жизни - то есть число, представляющее объединение этих полей будет колебаться от 0 до 100*400 = 400000, а такое число может быть закодировано по словарю тремя символами.
  2. Далее закодируем объединение поля скилла и поля золота. Оно может колебаться от 0 до 30*10000 = 300000, и, значит, может быть закодировано тремя символами. Такую операцию можно проделать со вторым полем скилла и с количеством ресурсов дерева. То есть еще два объединения по три символа.
  3. Затем объединим поле скилла с полем вещи из инвентаря. Такое объединение кодируется числом от 0 до 30*400 = 12000. Объединим таким образом оставшееся три
поля скиллов и первые три поля вещей инвентаря. Три объединения, по три символа каждое.
  1. Нам остается только объединить каждую из трех оставшихся вещей, с каждым из трех аттрибутов. Максимальное число, представляющее объединение будет равно 400*500 = 200000. Три объединения, каждое по три символа.
Таким образом получаем длину кода, равную 1*3 + 2*3 + 3*3 + 3*3 = 27 символов. Разумеется, длинна этого кода больше чем длинна оптимального кода, но оптимальный код намного труднее реализовать и генерация такого кода может занять достаточно много процессорного времени, и, значит, вызывать значительные лаги.
Это не самый оптимальный вариант объединения полей, зато достаточно понятный.
Теперь напомню, чем можно пользоваться для уменьшения длинны кода:
  1. Модификаторы - то есть множители для чисел, значения которых могут быть достаточно велики, и небольшие ошибки в этих числах не имеют значения. То есть
если у меня 50037 золота, то если я во время сохранения/загрузки потеряю из них 37 единиц, то я не буду сильно о них жалеть. Зато это уменьшит длину кода.
  1. Объединения нескольких полей - такой метод не только уменьшает длину, но и делает код более устойчивым к взлому.
  2. Уменьшение максимально допустимого значения поля - это означает, что можно "подгонять" максимумы всех полей под длину словаря - например, сделать
максимальное число уровней не 100, а 90 и кодировать ее отдельным символом, или максимальное количество ресурсов сделать не 10000 (с модификатором 50), а 8100 (с модификатором 50) и кодировать это число двумя символами.
В модели отсутствует сохранение накопленного опыта - то есть при загрузке вы получите героя с опытом, соответствующему началу уровня. Поле опыта может принимать различные значения - от нескольких тысяч, до сотен миллионов, поэтому это поле нужно выбирать относительно карты, для которой строится система. Из общих принципов я могу посоветовать только разделить уровень на несколько частей и заносить номер части в дополнительное поле. Вообще говоря, это поле можно и опустить, ну а если оно так важно для вашей карты, то, думаю, вы сможете сделать его по аналогии с другими описанными мной полями.
Так же в модели отсутствует такое поле как Тип героя, но это поле тоже достаточно специфично - сложно предположить сколько типов героев вы будете в нем использовать, в любом случае оно не должно занимать более одного символа. Идентифицировать это поле придется опять же таблицей, вроде той, которую я предлагал для идентификации вещей, но тут она будет намного короче.

Глава пятая «Защита кода по методу NETRAT’a» или «Open Source в студию!»

Пускай у нас есть готовый код - код, который выдается генератором. Так вот предлагаю его перекодировать невырожденным(то есть восстановить оригинальный код вы сможете) преобразованием. Преобразование такого вида - пробегаем все буквы кода, получаем их номер по словарю, добавляем к нему определенное число, находим это число в словаре и вместо взятой буквы, подставляем букву из словаря, соответствующую полученному числу.
Обьясню это все на примере:
Пускай у нас есть словарь:
"0WVUYXZRQSTqPONMLponmlKkJjIiHhGgFfEeDdCcBbAa1r2s3t4u5v6w7x8y9z~`!@#$%^&*()-_=+;:[]{},.[]​/?" - это словарь по умолчанию из системы cHAm'а. И есть некоторый 8-символьный код "Netrat?!" . Преобразуем его по описанному правилу, с добавлением ключевого числа 21.
Код символа "N" в словаре равен его позиции - то есть 15, добавим к нему число 21, получаем 36, буква с номер 36 из словаря - это "e", далее символ "e" имеет код 36, с добавлением 21 получаем 57, соответственно символ "7", далее, аналогично из символа "t" получаем символ "&", из символа "r" получаем символ "#", из символа "a" получаем символ "!", из символа "t" мы уже получали "&", далее для символа "?" код по словарю равен 90, при добавлении 21 получаем 111, в этом случае число превосходит размер словаря и предлагаю просто поделить на 90 (длину словаря) по модулю - то есть взять остаток от деления на 90, а он будет равен 21 и полученный символ будет "m", для символа "!" получаем символ ".". Тогда исходный код будет кодироваться строкой "e7&#!&m.". Это все достаточно просто реализуется и достаточно эффективно защищает от взлома.(Лично я понял, о чем здесь говорится с третьего раза =) – прим. cHAm) Для обратного преобразования достаточно будет отнять ключевое число от каждого из символов, а если получится отрицательное число, то добавить длину словаря - в данном случае это 90. Для более извращенного кодирования, предлагается добавлять к числу кодирования(то есть к 21) еще и номер позиции символа в коде, тогда код получится еще более безопасным - то есть вот таким "D8)&^=iU". Если вам нужно ввести в код защиту от использования кода другим игроком, предлагаю вместо ключевого числа вводить код буквы ника - либо всегда одной(скажем первой), либо по очередности.(ИМХО пустая трата времени– прим. cHAm)
Однако описанные методы могут раскрываться то есть взламываться открытием j файла карты. Для того чтобы еще сильнее обезопасить код от взлома, предлагаю вместо(или вместе) с ключевым числом ввести какой-нибудь атрибут статического объекта на карте, например угол поворота одного из зданий на карте, или одну из его координат, или положение на карте какого-нибудь специфического дерева. Человеку, вскрывшему *.j файл будет непонятно что это за атрибут и какой единице он соответствует. Даже если он вскроет карту и получит слой дудадов и единиц, то ему придется найти соответствие между объектом на карте и файлом скрипта, а это, если и возможно, то достаточно трудно.
Обращаю ваше внимание, что вышеописанные методы защиты кода от взлома не увеличивают размер самого кода, то есть просто трансформируют код, без увеличения его длинны. Именно поэтому они так удобны.

Глава шестая. «Немного практики» или «Пошлем теорию подальше!»

Последние три главы была исключительно теория. Пора бы это все на практике применить. Итак открываем мапу, что мы сделали. Теперь открываем триггер Saving. Сейчас мы его будем ужимать… Чтобы не переписывать триггер заново кидаем еще два действия перед циклом:

Set int[2] = (int[2] + (int[3] x 4))
Set int[4] = (int[4] + (int[5] x 4))
Здеся, мы сбиваем четыре абилки в два числа. Теперь кидаем еще 2 действия:

Set int[6] = (int[6] / 10)
Set int[7] = (int[7] / 10)
То есть делим золото и дерево на 10. Теперь меняем количество символов, выделяемых на ресурсы с 4 на 3:

Custom script:   set udg_str[6]=GetCharCodeWithLength(udg_int[6],3)
Custom script:   set udg_str[7]=GetCharCodeWithLength(udg_int[7],3)
И изменяем переменные str[100] и str[101]:

Set str[100] = (str[1] + (str[2] + str[6]))
Set str[101] = (str[4] + (str[7] + str[8]))
Код после этого уменьшится на 4 символа. Неплохо, да? Теперь ужимаем пердметы. Поскольку их только 3, можно использовать на весь инвентарь 3 символа (если хочешь, можешь увеличить их количество до 7). Вместо второго цикла кидаем действа:

Custom script:set udg_int[10]=GetItemNumber(udg_ItemId[1])+
                                             GetItemNumber(udg_ItemId[2])*4
Custom script:set udg_int[11]=GetItemNumber(udg_ItemId[3])+
                                             GetItemNumber(udg_ItemId[4])*4
Custom script:set udg_int[12]=GetItemNumber(udg_ItemId[5])+
                                             GetItemNumber(udg_ItemId[6])*4
For each (Integer A) from 10 to 12, do 
     (Custom script: set udg_str[bj_forLoopAIndex]=
            GetCharCode(udg_int[bj_forLoopAIndex]))
Разъясняю: здесь мы объединяем айтемы в 3 числа, а потом шифруем эти числа. Соответственно, предпоследние действие будет выглядеть так:

Set str[102] = (str[10] + (str[11] + str[12]))
Еще не все. Как ты, помнишь, здесь существует контрольная сумма. Удаляем ее объявление и перед 1 циклом пишем:

Set int[8] = (Abs((int[2] - (int[4] + int[1]))))
Усе. Теперь код стал меньше еще на 3 символа. Итого – 15 символов вместо 22. Для перестраховки сравни твой триггер с моим:
Тэкс, теперь приступим к загрузке. Открываем триггер Loading и перестраиваем его под новый триггер сохранения. Я думаю, здесь ты и сам справишься. Скажу только, что для извлечения, скажем второго предмета, нужно первый символ инвентаря разделить на 4, а для первого- узнать остаток от деления на 4. На случай, если у тебя не получается, можешь скатать мой триггер:
Ну вот. Теперь тестим. Работает? Ну тогда ты крутой мэпмейкер!

Эпилог. «Вот и все» или «Концы в воду!»

Ну вот ты и научился создавать полноценный пасс. Конечно можно этот пасс доработать и сделать меньше и безопаснее, но это ты уже сам делай. У меня и так мозги загружены =). Вобщем, основы изучены и дальше ты можешь пробывать создавать пассы самостоятельно. Если есть вопросы, ори, мы с NETRAT'ом услышим :bis:!

Просмотров: 8 802

» Лучшие комментарии


Lord_Nan #1 - 4 года назад 0
Сдраствуйте я все понемаю но немагли бы вы обьяснить For each (Integer A) from 1 to 6, do (Set ItemId[(Integer A)] =
(Item-type of (Item carried by Hero in slot (Integer A)))) на русскую версию world editor
Именно как делать "Для каждого числа А делать ..... " что дальше делать
1 пункт от : 1.2 (учим русский язык)
Hate #2 - 4 года назад 4
удаляем русификатор с триггеров, делаем как в статье, потом еще спасибо скажете
SomeFire #3 - 4 года назад 2
Lord_Nan, для начала я бы посоветовал ознакомиться с учебником русского языка. А потом уже делать по предыдущему комментарию.
Lord_Nan #4 - 4 года назад 1
Большое спасибо буду учить
спасибо
VboX #5 - 3 года назад 0
В прикрепленном примере нет функции сохранения предметов... Если я не ошибаюсь, то он полностью идентичен примеру из первой части (((
xANDYx #6 - 3 года назад 0
VboX, статья уже старенькая в твоём случае будет продуктивнее писать в ЛС автору, если, конечно, он ещё сидит на сайте, там ответ и получишь.