WarCraft 3: Переход на Jass

[HR][/HR]
На вопрос: Почему вы не пишете на Jass? – отвечают, что это сильно тяжело и вообще не понятно, что там. Эта статья предназначена, что бы люди научились писать на Jass.
Я не буду показывать, как лучше делать. Просто покажу, как писать на Jass, а оптимизация, качественный текст и т.п. изучат потом. Статья рассчитана на людей, которые уже изучили GUI (триггеры) и знают, что такое глобальные переменные.

Спойлер: Что такое локальные переменные?
Тоже самое, что и глобальные переменные, только используются в пределах одной функции.

Названия триггеров, глобальных или локальных переменных и функция – могут быть произвольными.
Строки которые начинаются на local пишутся в самом начале функции (кроме cJass).

Спойлер: А как узнать тип локальное переменной?

Создаёте глобальную переменную, нужного типа. В уже конвертированный текст добавляете любую английскую букву – вам выбит окно ошибки. Прокручиваете до самого верха, вы увидите Global Variables, а чуть ниже globals. По центру – название ваших переменных, а слева тип переменной.

World Editor 33 урок осваиваем jass


Или экспортируете код и точно также ищите.

Первая часть: От глобальных до локальных.​

Хотите создать способность, что бы после применения цель обжигало? Ну вот вы раскинули мозгами и сделали такой триггер:

Но вас не устраивает, что использовать способность можно раз в 5 секунд.
Что делать?
Правка>Конвертировать в текст.
Что это за функции?

function Trig_FIRE_Conditions takes nothing returns boolean
function Trig_GUI_FIRE_Conditions takes nothing returns boolean if ( not ( GetSpellAbilityId() == ‘A000’ ) ) then return false endif return true endfunction //Спокойно можете всё стирать. Оставить только: function Trig_LOCAL_FIRE_Conditions takes nothing returns boolean return GetSpellAbilityId() == ‘A000’ endfunction function Trig_LOCAL_FIRE_Actions takes nothing returns nothing
function InitTrig_LOCAL_FIRE takes nothing returns nothing

И это событие.
Нам понадобится только функция действия. Не тяжело догадаться, что какая строка означает.
Заменяем начало первых трёх строк:

set udg_fire_cast на local unit cast set udg_fire_target на local unit target call AddSpecialEffectTargetUnitBJ… на local effect eff = AddSpecialEffectTargetUnitBJ…

Что мы сделали? Мы создали три локальных переменных: боевая единица (cast), боевая единица (target) и спецэффект(eff) и присвоили им значения:
cast — применяющий юнит
target — цель — применяемой способности
eff – спецэффект созданный над юнитом target
Дальше нам надо просто заменять:
udg_fire_cast на cast
udg_fire_target на target
udg_fire_effect на eff
Вот и всё. Теперь способность можно применять сколько хочешь раз.

Вторая часть: Хэш и таймер.​

[HR][/HR]
Если вы сделаете работу как указано в первой части, то вам скажут что лучше использовать таймеры. А как? Ведь локальные только в пределах одной функции пашут, но тут к нам на помощь прейдёт Хэш-таблица.

[JASS] Practice 1. Кодим изи скилл для паладоса! Уроки по World Editor


Сначала создадим новую функцию в которой будет наше действие. После функции(условия) пишем:

function Trig_LOCAL_FIRE_Timer takes nothing returns nothing endfunction

Trig_LOCAL_FIRE_Timer Название функции. Оно может быть любым, главное что бы не повторялось.
В функции(действия) копируем строку

А потом стираем все, кроме первых 3 и последних 2 строк.

local unit cast = GetSpellAbilityUnit() local unit target = GetSpellTargetUnit() local effect eff = AddSpecialEffectTargetUnitBJ( «chest», target, «Abilities\Spells\Other\Doom\DoomTarget.mdl» ) set cast = null set target = null

В функции Trig_LOCAL_FIRE_Timer вставляем строку(которую скопировали).
Создаём таймер.Для этого нужно в начале функции(действия) дописать:

local timer t = CreateTimer()

Таймер записывается в локальную t

Что бы запустить таймер используем:

call TimerStart (переменная таймера, время, тип таймера, функция)
Время – как часто срабатывает.
Тип таймера – false или true:
false – однократный
true – многократный
функция – та функция в которой будут нужные действия.
Значит пишем перед set cast = null такую строку:

call TimerStart(t,1.0,true,function Trig_LOCAL_FIRE_Timer)
Теперь время использования ХЕШ(
Пожалуйста, авторизуйтесь для просмотра ссылки.

).
Прочли? Продолжим. Создаём глобальную переменную Хэш-таблица с названием Hash. Создаём новый триггер с названием Hash, стираем всё и пишем:

function InitTrig_Hash takes nothing returns nothing set udg_Hash = InitHashtableBJ( ) endfunction

Теперь можно забыть про этот триггер.
Вернёмся к нашим функциям.
В функции (действия) сохраним наши данные.
Перед call TimerStart… пишем

call SaveUnitHandle(udg_Hash,GetHandleId(t),1,cast)// Сохраняем применяющего юнита в 1 ячейку. call SaveUnitHandle(udg_Hash,GetHandleId(t),2,target) )// Сохраняем цель-способности во 2 ячейку. call SaveEffectHandle(udg_Hash,GetHandleId(t),3,eff) )// Сохраняем созданный спецэффект в 3 ячейку.

и в функции Trig_LOCAL_FIRE_Timer загрузим их:

local timer t = GetExpiredTimer()// Использованный таймер local unit cast = LoadUnitHandle(udg_Hash,GetHandleId(t),1)// Загружаем применяющего юнита из 1 ячейки. local unit target = LoadUnitHandle(udg_Hash,GetHandleId(t),2) )// Загружаем цель-способности из 2 ячейки. local effect eff = LoadEffectHandle(udg_Hash,GetHandleId(t),3) )// Загружаем созданный спецэффект из 3 ячейки.

Мы сразу записываем их в переменные. Нечего страшного, что их названия идентичны — это же локальные. Если вы сейчас проверите карту, то урон будет наноситься постоянно. Что бы этого избежать добавим целое число(integer) в функцию(действия):

local integer i = 0 и после call SaveEffectHandle…. Сохраним в хэш call SaveInteger(udg_Hash,GetHandleId(t),4,i)// Сохраняем i в 4 ячейку. а в функции Trig_LOCAL_FIRE_Timer после local effect eff … загрузим: local integer i = LoadInteger(udg_Hash,GetHandleId(t),4)//Загружаем i из 4 ячейки.

Теперь создадим условие, которое будет определять, когда надо закончить наносить урон.

if i == 5 then Если i равно 5 То call FlushChildHashtable(udg_Hash,GetHandleId(t))// уничтожаем все ячейки call DestroyEffect(eff)//уничтожить эффект call DestroyTimer(t)//уничтожить таймер set cast = null обнулить переменную set target = null// обнулить переменную set eff = null// обнулить переменную set t = null// обнулить переменную else//Иначе call UnitDamageTargetBJ( cast, target, 20.00, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE ) Нанести урон set i = i + 1// Увеличить значение call SaveInteger(udg_Hash,GetHandleId(t),4,i) Сохранить в хэш endif
Спойлер: Весь код:
function Trig_TIMER_FIRE_Conditions takes nothing returns boolean return GetSpellAbilityId() == ‘A000’ endfunction function Trig_LOCAL_FIRE_Timer takes nothing returns nothing local timer t = GetExpiredTimer() local unit cast = LoadUnitHandle(udg_Hash,GetHandleId(t),1) local unit target = LoadUnitHandle(udg_Hash,GetHandleId(t),2) local effect eff = LoadEffectHandle(udg_Hash,GetHandleId(t),3) local integer i = LoadInteger(udg_Hash,GetHandleId(t),4) if i == 5 then call DestroyEffect(eff) call DestroyTimer(t) set cast = null set target = null set eff = null set t = null else call UnitDamageTargetBJ( cast, target, 20.00, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE ) set i = i + 1 call SaveInteger(udg_Hash,GetHandleId(t),4,i) endif endfunction function Trig_TIMER_FIRE_Actions takes nothing returns nothing local timer t = CreateTimer() local unit cast = GetSpellAbilityUnit() local unit target = GetSpellTargetUnit() local effect eff = AddSpecialEffectTargetUnitBJ( «chest», target, «Abilities\Spells\Other\Doom\DoomTarget.mdl» ) local integer i = 0 call SaveUnitHandle(udg_Hash,GetHandleId(t),1,cast) call SaveUnitHandle(udg_Hash,GetHandleId(t),2,target) call SaveEffectHandle(udg_Hash,GetHandleId(t),3,eff) call SaveInteger(udg_Hash,GetHandleId(t),4,i) call TimerStart(t,1.0,true,function Trig_LOCAL_FIRE_Timer) set cast = null set target = null endfunction =========================================================================== function InitTrig_TIMER_FIRE takes nothing returns nothing set gg_trg_TIMER_FIRE = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ( gg_trg_TIMER_FIRE, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition( gg_trg_TIMER_FIRE, Condition( function Trig_TIMER_FIRE_Conditions ) ) call TriggerAddAction( gg_trg_TIMER_FIRE, function Trig_TIMER_FIRE_Actions ) endfunction
Третья часть: GetLocalPlayer()​

Читайте также:  Как собирать артефакты варкрафте

[HR][/HR]
Возможно, вы часто задавались вопросом: почему я вижу этот объект так, а мой другой игрок по-другому? Приведу пример из карты “DOTA”, не потому что она нереальная, а потому что она популярная и каждые знает, про что идёт речь.
1)Герой Invoker > способность Sun Strike.
2)Герой Mortred > способность Blur.
3)Герой Admiral> способность Гейзер и Корабль — точнее, точка его появляния.
Подробней о GetLocalPlayer() можно узнать (тут). Но лучше пример — будем создавать вышеуказанную способность Sun Strike.

Что за “Личный сценарий” и RemoveLocation()?
Личный сценарий — строка из Jass кода.
call RemoveLocation() – уничтожает точку.
Наш триггер конвертируем в текст и аналогично Первой части заменяем переменные.
В локальную eff – сразу не записываем созданный эффект,а делаем это на след строке:
Set eff = AddSpecial….
Второй спецэффект мы сразу удаляем. Мы используем для этого две строки, а можно одну:

call AddSpecialEffectLocBJ(loc, «Abilities\Spells\Other\Doom\DoomDeath.mdl» ) call DestroyEffectBJ( GetLastCreatedEffectBJ() ) на call DestroyEffectBJ( AddSpecialEffectLocBJ( loc, «Abilities\Spells\Other\Doom\DoomDeath.mdl» ) )

Заменили?
Должно получиться так:

function Trig_Jass_Sun_Strike_Actions takes nothing returns nothing local unit cast = GetSpellAbilityUnit() local location loc = GetSpellTargetLoc() local effect eff set eff = AddSpecialEffectLocBJ( loc, «Abilities\Spells\Items\VampiricPotion\VampPotionCaster.mdl» ) call TriggerSleepAction( 2 ) call DestroyEffectBJ( eff ) call UnitDamagePointLoc( cast, 0.01, 250.00, loc, 200.00, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_DEATH ) call DestroyEffectBJ( AddSpecialEffectLocBJ( loc, «Abilities\Spells\Other\Doom\DoomDeath.mdl» ) ) call RemoveLocation(loc) set cast = null set loc = null//Дописать эту строку endfunction

Сейчас добавим строки, которые для союзников – создадут эффект, а для врагов — нечего. Для этого создадим группу игроков-союзников и запишем туда союзников:

Конвертируем в текст,допишем и изменим:

local force for = CreateForce()// Создаём пустую группу игроков и записываем её в for local string str//Локальная строка set bj_forLoopAIndex = 1 // От 1 set bj_forLoopAIndexEnd = 12 // До 12 loop //Начало цикла действий, повторяем действия пока exitwhen не будет правдив. exitwhen bj_forLoopAIndex > bj_forLoopAIndexEnd // Если AIndex> AIndexEnd то exitwhen правдив if ( IsUnitAlly(cast, ConvertedPlayer(GetForLoopIndexA())) == true ) then// Если // То call ForceAddPlayerSimple( ConvertedPlayer(GetForLoopIndexA()), for ) else//Иначе, можно стереть endif//Конец условия set bj_forLoopAIndex = bj_forLoopAIndex + 1//Если exitwhen не правдив, то прибавляет к AIndex единицу endloop// Конец границы цикла. if (IsPlayerInForce(GetLocalPlayer(),for)) then //Если set str = «Abilities\Spells\Items\VampiricPotion\VampPotionCaster.mdl» // То else//Иначе set str = » » endif//Конец условия set eff = AddSpecialEffectLocBJ( loc, str )

Всё. Но, у нас опять присутствует

call TriggerSleepAction( 2 )

А это – не очень хорошо!
Мы уже умеем сохранять и загружать значения из Хеша, но сначала создадим функцию Trig_Jass_Sun_Strike_Boom.

local timer t = CreateTimer()//В начале
После set eff = …. Пишем: call SaveUnitHandle(udg_Hash,GetHandleId(t),1,cast) call SaveLocationHandle(udg_Hash,GetHandleId(t),2,loc) call SaveEffectHandle(udg_Hash,GetHandleId(t),3,eff) call TimerStart(t,2.00,false,function Trig_Jass_Sun_Strike_Boom) Остальное копируем, кроме: call DestroyForce(for)// дописываем set for = null // дописываем set cast = null set loc = null И вставляем в функция Trig_Jass_Sun_Strike_Boom и загружаем наши значения. local timer t = GetExpiredTimer() local unit cast = LoadUnitHandle(udg_Hash,GetHandleId(t),1) local location loc = LoadLocationHandle(udg_Hash,GetHandleId(t),2) local effect eff = LoadEffectHandle(udg_Hash,GetHandleId(t),3) call DestroyEffectBJ( eff ) call UnitDamagePointLoc( cast, 0.01, 250.00, loc, 200.00, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_DEATH ) call DestroyEffectBJ( AddSpecialEffectLocBJ( loc, «Abilities\Spells\Other\Doom\DoomDeath.mdl» ) ) call FlushChildHashtable(udg_Hash,GetHandleId(t)) call DestroyTimer(t) call RemoveLocation(loc) call DestroyForce(for) set for = null set cast = null set t = null set loc = null

Вот теперь – точно всё.

Спойлер: Весь код:

function Trig_Jass_Sun_Strike_Conditions takes nothing returns boolean
return GetSpellAbilityId() == ‘A001’
endfunction
function Trig_Jass_Sun_Strike_Boom takes nothing returns nothing
local timer t = GetExpiredTimer()
local unit cast = LoadUnitHandle(udg_Hash,GetHandleId(t),1)
local location loc = LoadLocationHandle(udg_Hash,GetHandleId(t),2)
local effect eff = LoadEffectHandle(udg_Hash,GetHandleId(t),3)
call DestroyEffectBJ( eff )
call UnitDamagePointLoc( cast, 0.01, 250.00, loc, 200.00, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_DEATH )
call DestroyEffectBJ( AddSpecialEffectLocBJ( loc, «Abilities\Spells\Other\Doom\DoomDeath.mdl» ) )
call FlushChildHashtable(udg_Hash,GetHandleId(t))
call DestroyTimer(t)
call RemoveLocation(loc)
call DestroyForce(for)
set for = null
set cast = null
set t = null
set loc = null
endfunction
function Trig_Jass_Sun_Strike_Actions takes nothing returns nothing
local unit cast = GetSpellAbilityUnit()
local location loc = GetSpellTargetLoc()
local effect eff
local force for = CreateForce()
local string str
local timer t = CreateTimer()
set bj_forLoopAIndex = 1
set bj_forLoopAIndexEnd = 12
loop
exitwhen bj_forLoopAIndex > bj_forLoopAIndexEnd
if ( IsUnitAlly(cast, ConvertedPlayer(GetForLoopIndexA())) == true ) then
call ForceAddPlayerSimple( ConvertedPlayer(GetForLoopIndexA()), for )
endif
set bj_forLoopAIndex = bj_forLoopAIndex + 1
endloop
if (IsPlayerInForce(GetLocalPlayer(),for)) then

set str = «Abilities\Spells\Items\VampiricPotion\VampPotionCaster.mdl»
else
set str = » »
endif
set eff = AddSpecialEffectLocBJ( loc, str )
call SaveUnitHandle(udg_Hash,GetHandleId(t),1,cast)
call SaveLocationHandle(udg_Hash,GetHandleId(t),2,loc)
call SaveEffectHandle(udg_Hash,GetHandleId(t),3,eff)
call TimerStart(t,2.00,false,function Trig_Jass_Sun_Strike_Boom)
set cast = null
set loc = null
endfunction

//===========================================================================
function InitTrig_Jass_Sun_Strike takes nothing returns nothing
set gg_trg_Jass_Sun_Strike = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_Jass_Sun_Strike, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( gg_trg_Jass_Sun_Strike, Condition( function Trig_Jass_Sun_Strike_Conditions ) )
call TriggerAddAction( gg_trg_Jass_Sun_Strike, function Trig_Jass_Sun_Strike_Actions )
endfunction

Спасибо Doc, ScorpioT1000
Карта пример —
Пожалуйста, авторизуйтесь для просмотра ссылки.

Статья скопирована с сайта:

Источник: yougame.biz

[Jass] Для новичков

//===========================================================================
function InitTrig_Test takes nothing returns nothing
set gg_trg_Test = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_Test, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( gg_trg_Test, Condition( function Trig_Test_Conditions ) )
call TriggerAddAction( gg_trg_Test, function Trig_Test_Actions )
endfunction

—>Всего комментариев : 10 Порядок вывода комментариев:

1 Спам

1 Tiny • 10:49:50, 27-08-2012
УРРРРРА. Наконец то нашел чтото о джазе спасибо ставлю большой плюс

Читайте также:  Как установить warcraft 3 reforged

-1 Спам

2 Tapos • 12:58:30, 07-09-2012
Нуб пишет статьи для новичков

1 Спам

3 tRuStyle • 12:46:54, 10-09-2012
Тапос, фингю спорол.

0 Спам

4 влад10011 • 17:28:26, 12-09-2012

«и все bj функции утечны.»
ересь!
не все они утечны единственное что происходит это «функция в функции» лишь некоторые бж вызывают утечки такие как bjdebugmsg, бж по созданию текст тага, bj создание юнита, случайный юнит из группы и ещё некоторые..
остальные лишь вызывают антологичную нативку
«потому-что в gui мы используем глобальные переменные (udg) эти переменные с каждым использованием способности заносятся в переменную, которая не будет очищаться и будет»
тоже ересь!
если знать функции по удалению такие как DestroyTimer DestroyGroup и тд то можно оптимизировать почти всё
единственное что локальные переменные избавляют нас от точек тк мы сможем использовать XY хранить ссылки на объекты и тд тк при использовании в GUI например «Выбранный юнит» мы получаем ссылку на объект и эта ссылка не удаляется!
а если на юнита ещё остались ссылки, то он не сможет спокойно удалится и будет висеть в памяти

0 Спам

5 влад10011 • 17:36:05, 12-09-2012

Эта статья самая непонятная и самая ужасная
Люди прочитайте статью Сергея и Адольфа там всё понятно расписано и без ереси(ну у Сергея есть немног но все же понятно)

1 Спам

Источник: wc3-maps.ru

История и описание уже исправленной уязвимости в игре WarCraft 3

Было это 2009 году, в марте месяце, я тогда увлекался созданием карт к игре WarCraft 3. Однажды мне показали карту, которая при запуске игры каким-то хитрым образом создавала консоль и писала в ней какой-то «Привет Мир!». Я мягко говоря был ошарашен — это значило, что есть возможность выполнять произвольный код в системе.

Под катом описание уязвимости и история ее закрытия.

Один мой знакомый, тоже из, как мы это называли «модмейкерской» тусовки выложил перевод статьи, в которой рассказывалось, как с помощью внешней программы можно расширять возможности игры. Тогда некто, чей ник я называть не буду, написал ему в личку «Это можно делать и без внешней программы» и выслал ему данную карту. Я попросил какие-либо его контакты, и у меня получилось связаться с ним по ICQ. На мое удивление он дал исчерпывающую информацию в чем именно заключается уязвимость и как ее использовать. Но, для полной картины мне придется начать с объяснения некоторых нюансов написания скриптов для игры.

Return Bug

Скрипты в WarCraft 3 пишутся на разработанном Blizzard недоязыке JASS (Just Another Script System). В целом он достаточно гибок (вся стандартная кампания сделана на визуальной надстройке этого языка), но беда: нет структур, и стандартный Sleep отсчитывает время даже когда игра стоит на паузе, да и еще с пониженной точностью, одним словом сделать плавное движение юнита например, перемещая его каждые .03 секунды невозможно. Зато есть таймеры, которые можно запускать, и которые достаточно точны, и по окончанию отсчета которых может быть вызвана указанная в аргументе callback функция. Но они тоже проблемы не решают: например, способность, которая бы делает отсроченно некоторые действия не будет работать корректно, если ее использовать дважды: в функцию, которая вызывается по окончанию отсчета таймера невозможно передать какие либо аргументы, и поэтому невозможно выяснить, какой именно экземпляр способности эта функция должна обработать.

И тут был обнаружен Return Bug (это было еще давно, думаю в 2005 году, а может и раньше. Этот момент я не застал). JASS — язык со строгой типизацией, но этот баг позволял ее обойти. Выглядело это так:

function RB takes unit u return integer return u // тут бы выдать ошибку, что функция должна вернуть число, // а она возвращает юнита return 0 // игра проверяла только последнюю инструкцию возврата endfunction

Но ошибка не выдавалась — судя по всему в отдельное поле записывался тип возвращаемого значения, но при следующей инструкции return это поле перезаписывалось опять. В результате выполнения данного кода функция вернет некоторое число, которое будет являться внутриигровым дескриптором переданного функции юнита. И для каждого юнита это число будет уникально.

В языке также было такое средство как кеш — он предназначался для сохранений данных в кампании и переноса данных их из одной миссии в другую, и нормально в него писать можно было только числа, строки и логические переменные, а в качестве индекса выступали две строки. В результате был придуман метод, что в кеш по индексу преобразованного в строку дескриптора таймера записывался дескриптор юнита, с которым надо было совершить отсроченные действия, а потом в callback функции можно было получить дескриптор таймера, и выполнить действия именно над нужным юнитом. Это была победа, заметно расширившая возможности создателей карт.

Странно и то, что разработчики игры не закрыли эту лазейку, и ее использование стало распространенным. Собственно, любые интересные нестандартные способности юнитов делались так.

Принцип работы интерпретатора JASS

На основе скрипта карты при загрузке игра создает псевдокод, в чем-то напоминающий ассемблер. Игра использует 8 байтовые опкоды, где первые 4 байта указывают код операции и опционально виртуальный регистр (их 256), а вторые 4 байта — аргумент, например при загрузке значения в регистр там будет непосредственно само значение. ИД регистров для каждой следующей по коду операции увеличиваются, например foo = 0; bar = 2; — для присвоения значения foo будет использован регистр #00, а для bar #01, после использования регистра #FF будет снова использован #00. Точно также, индексы глобальных переменных тоже увеличиваются, и идут по порядку — первая объявленная переменная будет иметь индекс n, следующая n+1.

Читайте также:  Как пройти варкрафт 4

Есть также большая разница между обычной переменной и массивом: если обычная переменная просто содержит непосредственно значение, то массив содержит ссылку на структуру, которая содержит данные о размере массива а также ссылку на область памяти, в которой содержатся непосредственно данные массива. Массив, имея изначально небольшой размер при записи по большим индексам делает realloc, однако размер массива ограничен 8192 элементами.

Собственно уязвимость

Есть в JASS такой тип, как code, это — относительный указатель на функцию, он используется, что-бы передать callback функции, например по использованию предмета вызвать такую-то функцию. С ним по идее нельзя делать никакие математические операции, и задается он только литералом, например code c = function foo. И он не указывает прямо на память, где расположен псевдокод, а лишь задает отступ от начала всего обработонного псевдокода. И тут на помощь приходит return bug:

function StubFunc takes nothing returns nothing endfunction function I2C takes integer ic returns code return ic return (function StubFunc) // Просто пустая функция, нужна для проверки синтаксиса. endfunction function C2I takes code c returns integer return c return 0 endfunction

Такой код вернет относительный адрес первого опкода передаваемой функции в виде числа.

function HackArrayW takes nothing returns nothing local code ctcode = I2C(C2I(function zOmgFunc2) + 1) // .

А вот так можно вызвать функцию, точнее не ее саму, а ее со смещением. Далее, путем подбора за счет объявления переменных по порядку и написания бессмысленных операций присвоения можно заставить игру генерировать свой, особенный псевдокод.

Аналогичный метод кстати используется для антиотладки, когда в x86 ассемблере пишется инструкция вызова, а в качестве адреса указывается другая инструкция, и переход совершается на нее. В JASS заметно облегчает эту задачу то, что интерпретатор при встрече с незнакомой инструкцией… просто перескакивает на следующую! Так вот, благодаря такому коду мы можем присвоить значение обычной переменной переменной массива и наоборот.

Известно, что игра использует одну библиотеку, которая не обновляется с патчами — Storm.dll. В ее адресном пространстве можно найти данные, адрес которых присвоить переменной массива, и выйдет, что мы получим массив, данные которого будут находиться не в специально выделенном месте, а прямо в исполняемом коде процесса. Нужно, что бы поле размера было больше, и не было произведено realloc при попытке записать что-либо по высоким адресам, а второе поле указывало на адрес, по которому мы собираемся писать. В результате появляется возможность писать и читать память процесса. Простая запись в такой массив:

function Inj_PrepareInjector takes nothing returns nothing set zg0oI[0x200]=0xE8575653 set zg0oI[0x201]=0x000000F3 set zg0oI[0x202]=0x9F2DC88B set zg0oI[0x203]=0xFF000006 set zg0oI[0x204]=0x72656BE0 // .

Будет писать в память процесса по нужному адресу. Дальше же дело техники… Автор уязвимости находил стек, и писал что-то в него, что позволяло перехватить управление. Если я правильно помню в архив карты можно было засунуть и свою *.dll, но код можно было зашифровать и в скрипте.

История

Некоторое время мы обсуждали, как же можно использовать находку. Помещать вирусы в карты показалось неблагородным. Разрабатывать библиотеку для расширения функционала игры не хотелось по двум причинам: возможность того, что уязвимость прикроют и возможность злоупотребления (опять же кто-то разберется как оно работает и начнет писать вирусы).

Не найдя применения карта была показана еще некоторым людям. И дошла до Blizzard. И тут начался цирк — они так и не смогли понять, как уязвимость работает. Сначала они техническими средствами запретили хостить в Battle.net карты, в которых были функции, возвращающие тип code. Это решало проблему, но урезало функциональность языка.

Я взялся писать свой патч, в результате появилась маленькая программа, которая копировала одну из библиотек игры, и перезаписывала в ней… 5 байтов. Да, это было полное спасение. А сделал я (адрес мне подсказал разработчик уязвимости) перехват обработки типа code. Игра постоянно использует преобразование относительного игрового адреса опкода (тот, который можно получить с помощью Return Bug) в реальный адрес, а я просто напросто добавил операцию and 0xfffffff8, попросту не давая выполнить некорректный псевдокод. Я выложил его на форумах, посвященных созданию карт для WarCraft 3. И уехал на дачу, где интернета у меня не было. Понимаю, поступил я безответственно 😉

Пытались ли кто-либо связаться с разработчиками игры, не чуть позже они выпустили патч 1.24, в котором полностью запретили Return Bug и добавили легальный его аналог, который правда не позволял обратные преобразования — из числа получить объект теперь стало невозможно. Как результат часть карт, которые были написаны на JASS толково — быстро заменили функции и стали работать корректно, другая же часть, которая использовала обратные преобразования из числа в объект поломалась. Карты же, созданные на визуальной надстройке вообще никак не среагировали на это событие. В целом Blizzard выбрали не самый оптимальный вариант, да и к тому же поломали совместимость.

Что я хочу сказать напоследок?

Используя недокументированные возможности надо быть осторожным и понимать, что лавочку могут прикрыть.

Будучи ближе к пользователям можно заметно облегчить себе жизнь — автор уязвимости ранее имел негативный опыт общения с техподдержкой Blizzard, которая ему для решения всех проблем предлагала переустановить игру. И он не захотел сообщать им о уязвимости.

И самое главное: всегда, всегда, всегда проверять входные данные. Уязвимости бы не было, если бы return bug был сразу заменен на функцию, встроенную в движок. Ее не было бы, если бы интерпретатор начинал панику, наткнувшись на неизвестный опкод.

Добавлено:

Источник: habr.com