Удаление Whitelist в bios ноутбуков на примере Lenovo X230

Недавно понадобилось поставить новую wifi карточку стандарта ac в свой ноутбук Lenovo x230, в котором есть whitelest для wlan карточек. Ниже опишу свои изыскания по отключению whitelist’а.



Для начала нам надо слить дамп нашего биоса, сделать это можно с помощью утилиты FPT
fpt -d bios.rom -BIOS
Далее нам понадобится утилита PhoenixTool 2.52. Запускаем её, выбираем наш bios.rom и ждём, пока она его распакует, далее в поле Manufacturer выбираем Lenovo и жмём кнопку Advanced, там ставим галочки «Allow user to modify other modules» и «No SLIC»



Нажимаем «Done» и «Go», когда откроется окошко

ничего не нажимаем и идём в папку DUMP, куда распакован наш биос. Далее нужно найти файл отвечающий за whitelist. Ищем просто по тексту ошибки (в кодировке UTF-16), которая выводится на экран при вставленном модуле не из вайтлиста «1802: Unauthorized network card is plugged in»



Находим файл 79E0EDD7-9D1D-4F41-AE1A-F896169E5216_2207.ROM — это модуль биоса для вайтлиста.
Далее грузим его в IDA, смотрим код и ищем нашу строку «1802: Unauthorized network card is plugged in»



К этой строке идёт обращение в процедуре Sub_A0C. Т.е. процедура Sub_A0C предположительно занимается выводом ошибки на экран, смотрим откуда она вызывается (кликаем по имени процедуры и нажимем клавишу X на клавиатуре)



Видим что это процедура Sub_B20. Идём в эту процедуру и нажимаем пробел для перехода к схематичному представления кода.



Видим что процедура Sub_A0C вызывается из блока кода на метке Loc_BDD. Далее можно проследить условные переходы к этой метке и т.д. Но я решил, чтобы лучше понять код работы этой процедуры, пройти код от начала процедуры.
Итак, первое условное ветвление проверяет регистр edx на ноль
test edx, edx jz Loc_C6E


т.к. регистр edx выше в этой процедуре нигде не инициализируется, то понятно, что ему должно присваиваться значение перед вызовом процедуры Sub_B20. Это можно увидеть в коде
и
Что означает значение в регистре edx я не понял, поэтому пройдёмся по обоим веткам ветвления.
Пойдём для начала по правой веке, на Loc_C6E



В eax кладётся адрес данных qword_270, двойным щелчком по qword_270 переходим к просмотру этой записи, далее переходим на вкладку Hex View-A. Это и есть наш whitelist, он идёт с адреса 270h до 3FFh. С форматом тоже всё просто, каждая запись длинной в 16 байт (4 слова по 32bit) соответствует одному устройству: первое слово — какой-то флаг, принимающий значение 0, 1, 5 или 6, второе слово — это system id, третье — subsystem id, четвёртое — ещё один флаг, имеющий значение 0 или 1. Предполагаю что первый флаг определяет тип устройства: 0 — wifi карточка, 1 — модем, 5 — ?, 6 — конец списка.



Вернёмся к коду. Сравниваем eax с шестёркой, а шестёрка у нас — это конец вайтлиста, т.е. если вайтлист получается пустой, то идём сразу на Loc_BDD, которая у нас вызывает ошибку. Это условие нам не интересно, т.к. вайтлист у нас не пустой, и условие не выполнится. Далее кладём адрес qword_270 в регистр rdx, проверяем eax (первый флаг записи в вайтлисте) на ноль, если флаг отличный от нуля, переходим к ошибке. Это условие нас тоже не интересует. Это же условие является началом цикла обхода вайтлиста.
Дальше командами

movzx   ecx, word ptr [r8+rdx+6] movzx   eax, word ptr [r8+rdx+4] shl     ecx, 10h or      ecx, eax


Помещаем system id из вайтлиста в регистр ecx. Регистр r8 здесь выступает в роли инкремента в цикле, изначально он равен нулю.
Далее сравниваем ecx (system id из вайтлиста) и значение в памяти по адресу в регистре rdi.

cmp     [rdi], ecx


Несложно догадаться, что по адресу в регистре rdi у нас лежит идентификатор нашей вставленной wifi карточки, а в начале процедуры в регистр rdi мы кладём r8, т.е. процедуре Sub_B20 в качестве параметра использется регистр r8, где должен лежать адрес ячейки памяли с идентификатором нашей карточки.
Если идентификатор не совпал, то идём на Loc_CBA, там мы увеличиваем инкрементный регистр r8 на 10h (для этого в регистре r13w у нас заранее должна лежать еденица)

loc_CBA: add     r9w, r13w movzx   r8d, r9w shl     r8, 4


и проверяем, не в конце ли мы вайтлиста

mov     eax, [r8+rdx] cmp     eax, 6 jz      loc_BDD


Если в конце, то выходим из цикла и выдаём ошибку, если не в конце, то идём в начало цикла.
Понятно, что для обхода вайлиста, нам надо убрать условный переход при сравнении system id, т.е. заменить jnz short loc_CBA на jmp $+2.
Далее, если system id совпал, идёт аналогичный код для сравнения subsystem id

movzx   ecx, word ptr [r8+rdx+0Ah] movzx   eax, word ptr [r8+rdx+8] shl     ecx, 10h or      ecx, eax cmp     [rdi+4], ecx jz      short loc_CD5


Если subsystem id не совпал, то перебираем по циклу идентификаторы дальше. Тут нам тоже необходимо поправить условные переход на безусловный jz short loc_CD5 на jmp short loc_CD5.
Дальше по коду уже нет переходов на ошибку, но есть интересный код, проверяющий второй флаг в записи вайтлиста, про который я писал ранее, он сравнивается с регистром r13d, и если совпадает, то выполняется некий дополнительный кусок кода. Что делает этот код понять трудно, по анализу вайтлиста можно заметить, что этот флаг стоит только у карточек фирмы Intel.

Так, с правой веткой разобрались, теперь пройдёмся по левой ветке.Там у нас код работы с вайтлистом начинается с метки Loc_C18. Аналогично проверяется, не пустой ли whitelist:

loc_C18: mov     eax, dword ptr cs:qword_270 xor     r9w, r9w cmp     eax, 6 jz      short loc_BDD


Далее сравнивается eax (первый флаг) и r13d (что за параметр лежит в r13d я так и не разобрался, в начале процедуры него кладётся значение по адресу [rbx+1]), далее в зависимости от выполнения этого условия, попадаем сразу на код проверки system id, либо выполняем интересное сравнение

cmp     eax, 5 jnz     short loc_C54


Проверяем первый флаг в записи вайтлиста на 5, если условие выполняется, то пропускаем эту запись вайтлиста (т.е. игнорируем wifi карточку) и идём далее по циклу проверки.
Чтобы понять что же это за такой интересный флаг, надо понимать, что передаётся в процедуру Sub_B20. Если помните, в правой ветке кода, если этот флаг был отличный от нуля, то выдавалась ошибка о неподдерживаемой карте. Т.е. чтобы карточка с флагом 5 загрузилась, должны выполниться следующие условия вызова процедуры Sub_B20: регистр edx должен быть отличным от нуля, а регистр r13d не должен быть равен пяти (cmp eax, r13d, а в eax у нас лежит наш флаг равный пяти). Можно лишь догадаться, что для запуска карточки с таким флагом, требуется ещё какое-то условие, кроме нахождения её в вайтлисте. Дальше выполнять анализ для меня было довольно сложно, поэтому, что же это за условие, узнать не удалось.

Далее по коду, мы аналогично правой ветке, проверяем в цикле system id

loc_C3B: movzx   ecx, word ptr [r8+rdx+6] movzx   eax, word ptr [r8+rdx+4] shl     ecx, 10h or      ecx, eax cmp     [rdi], ecx jz      loc_DF3


Здесь нам надо также заменить условный переход jz loc_DF3 на безусловный jmp loc_DF3.
Что интересно, далее в левой ветке нет проверки subsystem id, т.е. достаточно совпадения system id. Из вышеизложенного можно предположить, что правая ветка — проверка wifi карточек, а левая проверка модемов. А в регистре edx при вызове функции содержится тип устройства: 0 — wifi, 1 — модем. Но тут тоже непонятка, т.к. каточка 0087:8086 с флагом 5 — это wifi+wimax, а не модем, да и в edx может быть больше единицы, т.к. из edx вычитается r13d, а потом сравнивается с четвёркой:

sub     edx, r13d jz      short loc_B4F cmp     edx, 4 jz      short loc_B4F 


Собственно на этом анализ можно заканчивать. Нужные места для патча мы нашли.
Переводим команды в опкоды и делаем замены в hex редакторе, в итоге получаем такой патч:

C4E:	0F84->90E9 (jz to jmp) CA3:	16->00 (jnz loc_CBA to jnz $+2) CB8:	74->EB (jnz to jmp) 


Сохраняем пропатченый файл 79E0EDD7-9D1D-4F41-AE1A-F896169E5216_2207.ROM и нажимеам в окне PhoenixTool кнопку Ok.
PhoenixTool соберёт новый биос с именем bios_SLIC.rom.

Осталось дело за малым, необходимо прошить новый биос. В свежих ноутбуках Lenovo, включая мой x230, изменённый bios нельзя прошить программно, поэтому шьём его программатором. Фото процесса прошивки через программатор к сожалению не делал. Биос находится в микросхеме MX25L3206E рядом с разъёмом ExpressCard. Эта микросхема представляет из себя обычный SPI EEPROM 25ой серии, простой программатор для которой стоит 300р.

На этом всё, наслаждаемся работой новой wifi карточки.

Использовались материалы с форума bios-mods.com
www.bios-mods.com/forum/Thread-TUTORIAL-Lenovo-X230-Tablet-BIOS-Whitelist-Removal-Hardware-Flash
www.bios-mods.com/forum/Thread-REQUEST-Whitelist-wifi-removal-for-T430-got-hardware-flash-programmer?page=4

ссылка на оригинал статьи http://habrahabr.ru/post/219893/

PHP-расширение dom_varimport: быстрое преобразования вложенных массивов в DOMDocument

Некоторые проекты используют XSLT в качестве основного «движка» шаблонов. Помимо известных недостатков XSLT (например, его многословности, относительной медлительности и т.д.) у него есть и преимущества: «стандартность» языка, его идеология отсутствия «побочных эффектов» и pattern matching, возможность при необходимости вызывать методы helper-классов из шаблонов (через exslt-расширение). Какое-то время назад я выкладывал библиотеку ShortXSLT, позволяющую вместо громоздких <xsl:value-of select="/root/abc"/> и <xsl:choose>…</xsl:choose> писать просто {/root/abc} и {if…}…{elseif}…{/if} без потери производительности, так что проблема многословности отчасти решается.

Но сейчас речь не о преимуществах и недостатках XSLT (я уверен, и противники, и сторонники этой технологии найдутся в изобилии). Я бы хотел описать один прием, который удобно применять в существующих проектах с XSLT-шаблонами, и привести ссылку на библиотеку, реализующую данный прием с хорошей производительностью.


Передаем данные в XSLT, минуя генерацию текстового представления XML


Представьте, что у нас есть контроллер, генерирующий некоторый вложенный PHP-список объектов для отображения на странице. Он должен этот массив преобразовать в XML, который потом пойдет на вход XSLT-шаблону. Хорошо бы, чтобы данное преобразование из структур PHP в XML выполнялось не вручную в каждом контроллере, а был некоторый промежуточный слой абстракции, который умеет применять XSLT-шаблон прямо к PHP-данным, минуя текстовое XML-представление. Так мы уменьшим вероятность ошибок, да и письмо сократится. Мы сможем работать с XSLT-шаблонами напрямую, минуя XML-представление данных.

Некоторое время назад я написал на Си PHP-расширение dom_varimport (также выложено на GitHub). Оно содержит одноименную функцию, на вход которой подается объект DOMDocument и PHP-массив любой вложенности. Функция заполняет переданный ей DOMDocument XML-представлением входного массива, и делает она это очень быстро — примерно в 20 раз быстрее, чем делал бы код, написанный на чистом PHP. Большой документ размером около 1 МБ с тысячами вложенных свойств и объектов формируется примерно за 1-2 миллисекунды.

Например, вызов:

 $doc = new DOMDocument(); dom_varimport(     $doc,     array(         "some_key" => 111,         123,         0.5,         "arr" => array("1a" => "1a"),         "obj" => (object)array("prop" => "val"),         true,         false,         "b" => null,         "empty" => array(),     ),     "root" // optional, defaults to "root" ); $doc->formatOutput = true; echo $doc->saveXML(); // это только для отладки: на практике вам не нужно будет вызывать saveXML() 


напечатает вот такой XML-документ:

 <?xml version="1.0"?> <root>     <some_key key="some_key">111</some_key>   <!-- plain key=value -->     <item key="0">123</item>         <!-- numeric keys are "item" tags -->     <item key="1">0.5</item>         <!-- double -->     <arr key="arr">                  <!-- nested array -->         <item key="1a">1a</item>         <!-- invalid tag names are converted to "item" -->     </arr>     <obj key="obj">                  <!-- nested object -->         <prop key="prop">val</prop>     </obj>     <item key="2">1</item>           <!-- true converts to 1 -->     <item key="3"/>                  <!-- false converts to an empty string -->     <b key="b"/>                     <!-- null also converts to an empty string -->     <empty key="empty"/>             <!-- empty array is an empty element --> </root> 


Все достаточно прозрачно: ключи массива и свойства объектов становятся XML-элементами, по возможности с теми же именами (но если имя недопустимо для XML-элемента, то вместо него используется «item»). Такой XML-документ очень легко читать при отладке, он весьма компактен. Итак, мы получаем на выходе объект DOMDocument, который уже можем передать XSLTProcessor-у. Текстовое представление XML нигде не фигурирует, нигде не парсится.


Как установить расширение


Расширение написано на Си, поэтому его нужно откомпилировать на машине, на которой установлены GCC и пакеты типа php5-src (или php5-devel). Это совсем не страшно:

 git clone https://github.com/DmitryKoterov/dom_varimport.git cd dom_varimport phpize ./configure make make test make install  # or copy modules/dom_varimport.so manually phpize --clean 


Этап «make install» можно и не делать: достаточно взять бинарный файл modules/dom_varimport.so и скопировать его в директорию с расширениями PHP (например, /usr/lib/php5), в том числе и на других машинах. Наконец, нужно в подключить расширение /etc/php5/conf.d/dom_varimport.ini и перезапустить php5-fpm или apache:

 extension = /usr/lib/php5/dom_varimport.so 

ссылка на оригинал статьи http://habrahabr.ru/post/219907/

VSDCT на OpenGL ES 3

Давно хотел сделать демку VSDCT на мобильном телефоне. VSDCT (Virtual Shadow Depth Cubemap Texture) это представление cubemap текстуры, когда вместо 6 отдельных граней используется одна обычная 2D текстура-атлас, в котором исходные грани кубической карты помещены в виде плотно упакованных тайлов. Посмотрим, как сделать тени от точечного источника света, использую эту технику.

image




Введение



Не буду останавливаться на базовом алгоритме omni-directional shadow mapping, в ссылках можно прочитать соответствующие ресурсы. Мы будем использовать 6 линейных проекций и сразу перейдём к реализации VSDCT.

Разберёмся, что же мы хотим сделать. Надо получить 6 теневых карт, которые покрыли бы все направления видимые из точечного источника освещения. Т.е. по одной карте на каждое из направлений ±X, ±Y, ±Z. Чтобы между картами не было разрывов, мы установим FOV 90 градусов для каждой из проекций:

image

6 model-view матриц строятся таким образом (P — координаты источника света):

	Math::ViewMatrix( vec3(  0.0f, 0.0f,  1.0f ), vec3( 0.0f, 1.0f,  0.0f ), vec3( -1.0f,  0.0f,  0.0f ), P ); 	Math::ViewMatrix( vec3(  0.0f, 0.0f, -1.0f ), vec3( 0.0f, 1.0f,  0.0f ), vec3(  1.0f,  0.0f,  0.0f ), P ); 	Math::ViewMatrix( vec3(  1.0f, 0.0f,  0.0f ), vec3( 0.0f, 0.0f,  1.0f ), vec3(  0.0f, -1.0f,  0.0f ), P ); 	Math::ViewMatrix( vec3(  1.0f, 0.0f,  0.0f ), vec3( 0.0f, 0.0f, -1.0f ), vec3(  0.0f,  1.0f,  0.0f ), P ); 	Math::ViewMatrix( vec3( -1.0f, 0.0f,  0.0f ), vec3( 0.0f, 1.0f,  0.0f ), vec3(  0.0f,  0.0f, -1.0f ), P ); 	Math::ViewMatrix( vec3(  1.0f, 0.0f,  0.0f ), vec3( 0.0f, 1.0f,  0.0f ), vec3(  0.0f,  0.0f,  1.0f ), P );  	LMatrix4 ViewMatrix( const LVector3& X, const LVector3& Y, const LVector3& Z, const LVector3& Position ) 	{ 		LMatrix4 Matrix; 		Matrix[0][0] = X.x; 		Matrix[1][0] = X.y; 		Matrix[2][0] = X.z; 		Matrix[3][0] = -X.Dot( Position ); 		Matrix[0][1] = Y.x; 		Matrix[1][1] = Y.y; 		Matrix[2][1] = Y.z; 		Matrix[3][1] = -Y.Dot( Position ); 		Matrix[0][2] = Z.x; 		Matrix[1][2] = Z.y; 		Matrix[2][2] = Z.z; 		Matrix[3][2] = -Z.Dot( Position ); 		Matrix[0][3] = 0.0f; 		Matrix[1][3] = 0.0f; 		Matrix[2][3] = 0.0f; 		Matrix[3][3] = 1.0f; 		return Matrix; 	} 



Все проекции одинаковые, перспективные с аспектом 1:1 и углом 90 градусов:

float NearCP = 0.5f; float FarCP   = 512.0f; Math::Perspective( 90.0f, 1.0f, NearCP, FarCP ); 



При отрисовке каждой из 6 теневых карт мы сохраняем расстояние от источника света до текущего пикселя и упаковываем его в формат 8-бит RGBA.

void main() { 	float D = distance( v_WorldPosition, u_LightPosition.xyz ); 	out_FragColor = Pack( D / 512.0 ); }  vec4 Pack(float Value) { 	const vec4 BitSh  = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0); 	const vec4 BitMsk = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 ); 	vec4 Comp = fract( Value * BitSh ); 	Comp -= Comp.xxyz * BitMsk; 	return Comp; } 



При отрисовке в отдельные регионы атласа надо просто установить соответствующий viewport и scissor. Для разделения произвольного атласа на N одинаковых регионов (нам не всегда нужно именно 6) мы используем вот такой код:

	LRectDivider( int Size, int NumSubRects ) 		: FSize( Size ) 		, FNumSubRects( NumSubRects ) 		, FCurrentX( 0 ) 		, FCurrentY( 0 ) 	{ 		float Sqrt = sqrt( float( FNumSubRects ) ); 		FNumSlotsWidth  = ( int )ceil( Sqrt ); 		FNumSlotsHeight = ( int )Sqrt; 		FSlotWidth  = FSize / FNumSlotsWidth; 		FSlotHeight = FSize / FNumSlotsHeight; 	} 	void GetNextRect( int* X, int* Y, int* W, int* H ) 	{ 		if ( X ) { *X = FCurrentX * FSlotWidth; } 		if ( Y ) { *Y = FCurrentY * FSlotHeight; } 		if ( W ) { *W = FSlotWidth; } 		if ( H ) { *H = FSlotHeight; } 		NextRect(); 	} private: 	void NextRect() 	{ 		if ( ++FCurrentX >= FNumSlotsWidth ) 		{ 			FCurrentX = 0; 			FCurrentY++; 		} 	}  



Примерно (примерно, потому что на самом деле мы упаковали 32-битное float расстояние в 4 канала, включая альфу) так будет выглядеть атлас для данной сцены:

image

Теперь мы можем всё это сами отрисовать. Во многих реализациях VSDCT применяется дополнительная кубическая карта, indirection cubemap, которая преобразовывает 3D координаты в 2D координаты внутри текстурного атласа. Было решено обойтись без неё и преобразовывать координаты прямо во фрагментном шейдере. Сначала обратимся к параграфу 8.13 Cube Map Texture Selection из OpenGL 4.4 Core Profile Specification. Таблица 8.18 говорит нам, что делать с 3D координатами:


Major Axis Direction Target Sc Tc Ma
+Rx POSITIVE_X -Rz -Ry Rx
-Rx NEGATIVE_X Rz -Ry Rx
+Ry POSITIVE_Y Rx Rz Ry
-Ry NEGATIVE_Y Rx -Rz Ry
+Rz POSITIVE_Z Rx -Ry Rz
-Rz NEGATIVE_Z -Rx -Ry Rz



Полученные Sc, Tc и Ma подставляем в эти формулы и получаем 2D-координаты s,t:

s = 0.5 * ( Sc / abs(Ma) + 1 ) t = 0.5 * ( Tc / abs(Ma) + 1 ) 



Вот код шейдера на GLSL, который вополняет все трансформации текстурных координат и загоняет полученные s и t внутрь атласа:

vec2 GetShadowTC( vec3 Dir ) { 	float Sc; 	float Tc; 	float Ma; 	float FaceIndex;  	float rx = Dir.x; 	float ry = Dir.y; 	float rz = Dir.z; 	vec3 adir = abs(Dir); 	Ma = max( max( adir.x, adir.y ), adir.z ); 	if ( adir.x > adir.y && adir.x > adir.z ) 	{ 		Sc = ( rx > 0.0 ) ? rz : -rz; 		Tc = ry; 		FaceIndex = ( rx > 0.0 ) ? 0.0 : 1.0; 	} 	else if ( adir.y > adir.x && adir.y > adir.z ) 	{ 		Sc = rx; 		Tc = ( ry > 0.0 ) ? rz : -rz; 		FaceIndex = ( ry > 0.0 ) ? 2.0 : 3.0; 	} 	else 	{ 		Sc = ( rz > 0.0 ) ? -rx : rx; 		Tc = ry; 		FaceIndex = ( rz > 0.0 ) ? 4.0 : 5.0; 	} 	float s = 0.5 * ( Sc / Ma + 1.0 ); 	float t = 0.5 * ( Tc / Ma + 1.0 );  	// кладём в атлас  	s = s / 3.0; 	t = t / 2.0; 	float Flr = floor(FaceIndex / 3.0); 	float Rmd = FaceIndex - (3.0 * Flr); 	s += Rmd / 3.0; 	t += Flr / 2.0;  	return vec2( s, t ); } 



Сама отрисовка тени в сцене предельно простая:

float ComputePointLightShadow() { 	vec3 LightDirection = v_WorldPosition - u_LightPosition.xyz; 	vec2 IndirectTC = GetShadowTC( normalize( LightDirection ) ); 	vec4 Light = texture( Texture7, IndirectTC ); 	float LightD = Unpack( Light ) * 512.0; 	if ( LightD < length( LightDirection ) + u_ShadowDepthBias ) return u_ShadowIntensity; 	return 1.0; }  float Unpack(vec4 Value) { 	const vec4 BitShifts = vec4( 1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0 ); 	return dot( Value, BitShifts ); } 



Вот и всё!

У такой техники есть проблема с артефактами на границах текстурных карт. Они будут особенно заметны если применять фильтрацию тени PCF или подобную. Чтобы уменьшить такие проблемы, можно добавлять чёрную рамку между отдельными текстурами текстурами внутри атласа.


Демка



Если у вас есть андроидный девайс с OpenGL ES 3.0, то вы можете попробовать запустить приложение: play.google.com/store/apps/details?id=com.linderdaum.engine.vsdct


Ссылки



Linderdaum Engine
ShaderX3: Advanced Rendering with DirectX and OpenGL
VSDCT for omnidirectional shadow mapping
Omnidirectional shadows and VSDCT on OpenGL ES 3
Omni-directional shadow mapping

ссылка на оригинал статьи http://habrahabr.ru/post/219327/

Основные правила инди-геймдева. Часть 3

image


С вами вновь дядя Док. В прошлый раз я постарался рассказать вам основные «правила» работы с энтузиастами, на деле их гораздо больше, но слишком многие из них основаны на личных предпочтениях. В этот раз мне бы хотелось рассмотреть основы работы с уже собранной командой.
Работа игрового дизайнера помимо «виденья» игрового мира и написания диздока, заключается в менеджменте команды. И некоторые из вас, вероятно подумают: «Что же это за халтура такая?». Я бы сейчас посмеялся, но боюсь вы не услышите, главная проблема работы с узконаправленными спецами, это то что они фактически не понимают друг друга, а при возникновении претензий начинаются раздоры в команде т.к. программист никогда не будет обращать внимание на работу художника пока не станет слишком поздно, 3д модельеру пофиг на ограниченные возможности движка, а звуковику вообще фиолетого на всех и вся. И тут в дело вступает геймдиз, он является связующим звеном между этими, по сути разными мирами. Если 3д модельер говорит что текстурка слезает на НПСе при движении, именно геймдиз должен понять чья тут ошибка, программиста или же художника.

//Небольшая оговорка, вся эта иерархия что я перечисляю относится, только к инди-гейдеву. На счет крупного игростроя, к несчастью ничего сказать не могу т.к. пока что не довелось поработать там. //


Вы наверняка спросите, «А откуда я должен это знать?». А вот с этого момента начинаются профессиональные знания игрового дизайнера. Не смотря на свою управляющую роль в команде, он должен знать все дисциплины, с которыми ему приходится работать, теоретически хороший геймдиз должен уметь делать игры в одиночку. Невозможно требовать от команды то чего сами не знаете. Для примера, ваш 3д модельер говорит, что делать для всех ста видов животных отдельный скелет будет муторно, и немного эффективнее сделать только 1 но с огромным количеством кадров. А программист утверждает, что это сильно усложнит ему задачу и будет шанс случайных багов с анимацией. Что вы в такой ситуации будете делать?


Оууу я начинаю понимать куда ты клонишь…


Вот именно для этого игровой дизайнер и должен знать базу всех профессий задействованных в игрострое. Разумеется, чем больше вы знаете, тем более плотно вы сможете работать с определенными специалистами. И тем более рационально сможете использовать их способности, кто же знал, что тот же программист может быть спецом по: процедурной генерации, настройке АИ, игровой физике или же вообще первый раз работать с играми.
И вот мы плавно перешли к самой работе со спецами, я буду рассчитывать и давать советы в основном ориентируясь на то что у вас в команде по одному спецу каждого вида (как обычно и бывает).

В первую очередь программисты:
1. Программисты – это ребята которые очень боятся сложных задач (хотя и утверждают обратное), настоятельно советую перед тем как выставлять перед ним цели, пообщаться за кружечкой пи…хм… газировки и выяснить чем он до этого занимался, и что именно его больше всего интересует. И только после того как вы будете уверены от чего именно ваш спец балдеет, выставляйте задачи по уровню его «любви», оставляя все самое скучное и нудное в конце, когда уже сам факт почти готового проекта будет тянуть его доделать работу.
2. Когда вы даете задачу программисту, будьте очень очень аккуратны в выражении своих мыслей. Дабы точнее объяснить, в чем проблема, приведу пример: Однажды работая с шутером я выставил перед программистом задачу реализовать падение пули и рикошет от некоторых поверхностей, на что он с очень серьезным лицом согласился. Через неделю, когда я пришел к нему проверять результаты тот очень взъелся на меня и сказал что недели не просто мало, а слишком мало даже чтоб сделать основу. Немного прифигев от такого счастья, я проверил его наработки и обнаружил что вместо реализации примитивного полета по дуге и отражения от определенных поверхностей, господин программист стал реализовавывать полную физическую модель пули на основе ее предполагаемой массы, скорости «ветра» на карте, начальной скорости и т.д. Теперь понимаете? У многих программистов есть небольшая черта перфекционизма, они всегда сами того не зная стараются усложнить себе задачу. Старайтесь контролировать, что именно он выполняет, и как это реализовывает.


Что он написал? Я думаю это была кнопка…


3. По большей части его знания всегда выше ваших, не бойтесь советоваться с ним. Он знает про используемое вами двигло не просто больше, а даже то, что вы считали черной магией. И перед тем как добавить или изменить нечто в геймплее, посоветуйтесь с ним, насколько это сильно затронет его исходники.

Дальше веселее, художники:
1. Если вы себе можете представить узнаконаправленного спеца, значит вы представляете себе художника – энтузиаста. Набирая их в команду, знайте что они свой стиль поменять не смогут, даже если угрожать паяльником. Так что, очень внимательно осматривайте заготовки которые они принесут вам при тестовом задании, если стиль отличается от финального варианта игры которого вы пытаетесь достичь, можете смело говорить ему «Удачи, мы вам перезвоним» и искать дальше. Только не спутайте несоответствие вида персонажа и несоответствие стиля игры, это хоть и похожие, но очень разные вещи.
2. Работая с художником помните основные правила, бывают Художники а бывают художники. Художник умеет все, но любит только свой стиль, так что если припрет, то он может быть и пиксель-арт и векторным и т.д. художник же наоборот, умеет рисовать только своим стилем, скажем крупнопиксельным пиксель-артом и потребуй вы изометрию, он уже не сможет ее реализовать. Это достаточно сложно определить, но по своим наблюдениям могу сказать что Художники отдаются рисованию полностью, если его руки свободны, то скорее всего они связаны, художники же наоборот подходят к рисованию как к работе. Если у вас будет выбор, лучше взять вечно рисующего с растрепанной прической неадеквата нежели хорошо одетого серьезного рисоваку.
3. Ставя ему задачу, расписывайте ее буквально по «движениям пальцев ног», он все равно реализует ее иначе. И чем точнее вы объясните ему цель, тем ближе к ней будет финальный результат.

Самое простое, звуковики:
1. Чаще всего эти ребята очень сильно зависимы от музы, и хотя они способы работать и без ее участия. Если вы желаете по-настоящему эпичные саундтреки, вылавливайте их вдохновленное настроение. На все остальное время старайтесь распределять мелочи, вроде дыхание ГГ, шагов зомби и т.д.
2. Учитесь вдохновлять людей, сложность работы с музыкой состоит в том, что если бы вы знали как она будет звучать, вы написали бы ее сами. Старайтесь натолкнуть вашего музыканта на определенную тематику, скажем если вам нужна отчаянная битва с боссом, расскажите ему про: «Героическое сопротивление, отряда космодесанта окруженного зергами. За спиной гражданские загружаются, в космический корабль, а впереди только бесконечные орды проклятых ксеносов. И вот последний работник, заходит в него, корабль вздымается в воздух…и его сбивают! Космодесантники понимают, что за ними больше никто не вернется, и сражаются уже не за жизнь, а из отчаяния…». Именно от вашего навыка вдохновлять людей, будет зависеть качество музыки.

И самое сложное, сценаристы:
1. Запомните главную проблему, большая часть сценаристов это потенциальные или неудавшиеся геймдизы. Если есть возможность, то лучше выполнять его работу самому, но коль вы рискнули или вынуждены взять его, внимательно перечитывайте все, что он пишет, начиная от приветственных речей НПСов, заканчивая победоносными восклицаниями врагов. Он работает с самой сложной частью геймдева, с игровой вселенной. Игроки увидят ее через ЕГО тексты. И если вы прохалтурите, не удивляйтесь, когда окажется что: Марио был сыном Луиджи который путешествовал во времени с помощью силы драконорожденного используя тайный прием варваров «Каме-хаме-ха».


Я не вполне уверен что я читаю…


2. Коль вы взяли его на работу, старайтесь уделять ему наибольшую долю своего внимания. Исходя из первого пункта, он как потенциальный дизайнер видит не просто другой проект, он видит свой проект. Очень редки сценаристы которые могут легко настроится на чужую вселенную не принеся в нее собственных черт. И едва он ощутит свободу, ваша задница почувствует нестыковки во вселенной…
3. Не бойтесь выгонять, как я уже говорил сценарист это потенциальный геймдиз. А два геймдиза в одной индюшке не уживутся. Если вы видите что сценарист слишком своевольный, или же просто не понимает суть вашей вселенной. Не пытайтесь переучить его, поверьте гораздо проще найти другого, нежели изменить уже существующего.

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




Благодарю вас за прочтение, не забывайте что ваше внимание для меня как хлеб, а за комменты меня возможно поощрят печенькой (иногда даже не из картона!). В дальнейшем мы рассмотрим сам проект с точки зрения игрового дизайнера, и насколько отличается менеджмент различных жанров.

ссылка на оригинал статьи http://habrahabr.ru/post/219909/

Создание игры с нуля

Пост о воплощении мечты и о создании игры с нуля. И о граблях разной величины.



0. Дано

Когда –то, во времена не столь от нас отдаленные, когда интернет в Уфе был ещё с помегабайтной оплатой, я занимался созданием и разработкой локальных «фришных» серверов Lineage и WoW. Но вот появились безлимитные тарифы, а локальные сервисы начали терять свою актуальность и я ушёл в вебразработку.
В некотором роде, как хобби я даже сохранил своё отношение в Геймдеву, правда, с другой стороны – разработка ботов и всего такого.
Шли года, а мысль написать что-нибудь свое, с преферансом и поэтессами не переставала меня терзать. В какой-то момент я понял, что либо сейчас, либо уже никогда.

1. Идея и Прототип

С чего начинается разработка? C прототипа. А прототип — с идеи. Для первого проекта стоит создать что-то небольшое и простое. Питая любовь к головоломкам и пошаговым стратегиям, я увлекся мыслью, что нужно сделать какую-нибудь пошаговую головоломку. Поразмыслив, вспомнил одну интересную игру времён Спектрума (стоит отметить, что автор не так стар, как может показаться) идея которой была прекрасна в своей простоте. Игрок ходит на 1 клетку, обходя препятствия, а враги его преследуют и ходят на 2 клетки, но препятствия не обходят.

Если взять ситуацию “A,” то мы можем спокойно ходить верх и вниз, а монстр просто будет упираться в стену. В ситуации “B” монстр находится в загончике, а мы можем спокойно ходить справа, сверху и снизу от него. Поскольку игра у нас детерминированная, то существуют два типа монстров, которые отличаются по цветам и алгоритму поиска пути. Это для того чтобы не было неопределённости куда пойдёт монстр, когда он стоит наискосок. Красный всегда сначала сокращает расстояние до нуля по оси X ситуация “C”, в ней монстр зайдёт в загончик и упрётся в стенку. Фиолетовый же по оси Y ситуация “D”, в ней монстр пойдёт вниз, а потом уже направо и съедает главного героя.

Тут ещё мог бы быть большой абзац про выбор «движка» для написания игры. Но, пожалуй, можно обойтись и парой слов; перед тем как попробовать Unity3D, я поковырялся в нативном Obj-C, Corona SDK, UE, Cocos2d и ещё пару 2D Фреймворках, название которых, так глубоко в стеке памяти, что уже и не откопать.
В итоге, выбор пал на Unity3D, он мне показался оптимальным и я неспешно начал писать. Через месяц появилось вот такое приложение для iPad.



В нём мы играли за мужичка в красном и убегали от мужичков в синем, параллельно собирали сердца – читай звезды. В тот момент мы ещё использовали кубы как стены, но затем для экономии пространства на экране заменили их.
Хотелось бы отметить, что до этого момента, я никогда не писал на C#, но писал на JS, поэтому прототип был написан UnityScript(это такой диалект JS в рамках Unity3D). И это были первые большие грабли, послужившие нам уроком. Пришлось переписывать всё на C# и это заняло пару месяцев.

2. Команда

Как верно то, что я писал на десятке языков, так верно и то, что я не в состоянии нарисовать даже прямую линию. Поэтому после создания прототипа я начал искать художника\3Dшника, для этого я зашёл на сайт по фрилансу и посмотрел первых три сотни человек, с десятком — списался. Найти человека, за долю в довольно рискованном проекте, задача не из легких. Но она разрешилась, и в проекте появился талантливый художник Рамиль; итак – нас стало двое.
Параллельно я искал инвестора. Дело не в отсутствии денег, чтобы купить плагины или устройства для теста, а в том, чтобы создать обязательства, которые помогут не забить(не сложить с себя полномочия) при определенных обстоятельствах. И такой инвестор нашелся, в виде друга, решившего вложить свои кровные, в эту сомнительную, как ему казалось, затею. Итак, нас стало трое. Отмечу, что к моменту запуска, бюджет игры составил всего около 100.000р., при этом, в эту сумму входит покупка нескольких Android устройств для тестирования, плагинов и озвучки.

3. Расширение базовой концепции игры

С самого начала предполагалось, что невзирая на то, что игра будет максимально простой, необходимо будет привнести несколько новых идей в базовую концепцию. Так у нас появились односторонние телепорты(позже станет ботинком), ловушки и одноразовые грибы съедающие тех кто к ним подойдёт.

Концепт Арт



Основной фишкой игры мы планировали сделать то, что персонаж может вернуться во времени, на несколько ходов назад. Т.е. не отмену хода, а возможность переместиться 1 раз за уровень на клетку, где персонаж был 3 хода назад. Это давало огромное количество вариантов хода, но на первых же тестах выяснилось, что обычные игроки не в состоянии пройти такие уровни. Слишком сложно. Может стоить таки сделать Brainiac Edition?

Также мы много экспериментировали с размером уровней, в итоге остановились на том что весь уровень должен вмещаться на экран, даже на устройствах с небольшим экраном. Так у нас получились уровни 5х8 клеток. Но при этом у нас есть pinch zoom, для большепальцых или уровней 6х10. Кроме сокращения размера уровни, отключения возврата во времени, для упрощения была ещё добавлена отмена хода, до 5 раз за попытку.

4. 2D vs 3D

В какой то момент наши тестовые билды выглядели так:

Ужас ужас


Приходило осознание того, что мы не в состоянии сделать картинку, которую мы хотели бы видеть в 3D с нормальным fps. Тестовая сцена для сравнения производительность 2D и 3D показывала не утешительные результаты.

Скриншоты из сцены

В итоге на iphone4, слева(3D) было всего 20 кадров в секунду, а справа(2D), стабильные 60. При этом, справа, качество картинки, как вы видите, на порядок лучше. Сейчас конечно если ориентироваться на минимальную производительность уровня iphone5+ и делать специально несколько квадратную графику, кошерные шейдеры + lightmapping + lightp robes, можно и для 3D сделать отличную графику



В итоге было принято решение всё переделывать на 2D. Но тут мы совершили ошибку, которую уже потом было не исправить. 2D анимация может быть 2 типов, первый это последовательность кадров, второй это наложение и смещение спрайтов.

Наглядное сравнение



Поскольку у нас были 3D модели, мы выбрали первый вариант. В итоге это выльется в большой вес приложения, даже с учётом огромного кол-ва оптимизаций мы не сможем уложиться в 50 мегабайт.

5. Форс-мажоры — это возможность

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

Для тех кто не понял о чём речь


На этом приложении я смог протестировать интеграцию с соц. сетями, несколько рекламных сетей, внутреннею аналитику и покупки внутри приложения.

6. Техническая часть

Думаю, настало время поднять градус гиковости. Значимую часть времени разработки заняло создание генератора уровней. Генератор писался на питоне, потому как он был мне духовно близок и numpy позволял быстро оперировать матрицами.
Сам генератор выходе создавал txt файл(который парсился Юнити для создания сцены) и серию картинок, показывающих пошаговое прохождение уровня.

gif анимация

Здесь зеленый квадрат — это игрок, жёлтый — враг, череп- это ловушка, которая убивает игрока, либо усыпляет врага на 3 хода.



Сейчас я могу сказать, что писание внешнего генератора на другом языке, это — плохая идея. Главным образом, тем, что игровая логика начинает прописываться в двух разных местах. А это — риск рассинхрона. Особенно, если учесть, что внутри игры, есть такие понятия, как анимация и время её исполнения. И в генераторе, и в игре каждая клетка сама следила за тем, что в ней происходит: кто вошёл, кто вышел, кто умер и т.д. Но клетки, вида телепорт или мухобойка(атакует близлежащие клетки), связывали несколько клеток между собой. И если в генераторе это не вызывало проблем, то в игре повторить то же было сложнее т.к. анимации персонажей из разных клеток должны были засинхронизироваться между собой.

Огромный кусок времени заняла интеграция нативных библиотек. Cкажу прямо: если можно купить готовую реализацию того, что вам нужно, смело покупайте. При этом желательно всё брать у одного продавца, потому как в Unity3D в манифесте(Андроид), есть такая штука как mainactivity и это место где разные плагины могут конфликтовать. Потом для iOS, скорее всего, у вас будут плагины, которые занимаются различным постпроцессингом, это тоже место для конфликтов. В целом нативные либы, это место где вы легко можете получить очень трудно выявляемые ошибки. Поэтому, почти всё, что связанно с нативным кодом, мы покупали у Prime31.

Полный список плагинов и несколько слов о каждом
Prime31 (Android+iOS) Admob 95$ — реклама с большим fillrate, так уж получилось, что не все сети могут показать баннер, когда их просят…
Prime31 (Android+iOS) Flurry 100$ — аналитика + реклама.
Prime31 (Android+iOS) InApp Purchases 130$ — покупки внутри приложения.
Prime31 (Android+iOS) Social 120$ — социальная интеграция Twitter и FaceBook, чтобы можно было запостить результат или просто лайкнуть приложение.
Prime31 (Android+iOS)Etcetera 130$ — нативное окно оценки приложения + всякая мелочь.

Так как речь зашла о плагинах, также был куплен NGUI 50$, лучший плагин для создания интерфейса. В AssetsStore много всяких плагинов для создания GUI, но имхо это самый лучший, к тому же его разработчик сейчас работает в самой команде Unity и на форуме пообещал, что когда выйдет официальный GUI, он постарается упростить миграцию на него. Must Have

2DToolKit 65$ для игровой анимации, сейчас ему появилась альтернатива, встроенная поддержка 2D от Юнити. Но 2DToolKit заточен для работы с последовательностями кадров, к тому же в нём есть несколько очень важных фич. Например, он может разрезать на квадраты большую картинку и сложить их в один атлас, это нужно, если у вас есть какая-то текстура 2048+ на 1ххх пикселей. Это позволит вписать её в текстуру 2048х2048(Ограничение размера андроид текстур). Из коробки в нём есть поддержка x1 x2 x4 мульти атласов. Например, у вас есть персонаж; у него очень много анимаций и они, ну никак, не влезают в текстуру 2048х2048, это позволит автоматически разбить на несколько атласов, которые из кода, вы будете видеть, как одну коллекцию. Must Have.

UniWebView 15$ плагин который позволяет показать браузер и получать с него, нормальные колбеки. Нужен был для того, чтобы общаться с VK api
Также несколько бесплатных плагинов:

Официальный Google Play Games plugin for Unity, для ачивментов, лидербордов и, в дальнейшем, системы синхронизации прогресса между устройствами.

Pushwoosh сервис для посылки пушнотификаций, у которого есть пакет для Unity3D.

Google Play OBB Downloader плагин который позволяет создавать приложения больше 50 мегабайт для Google Play Store. Поставляется с исходниками, был частично переписан для совместимости с другими нативными плагинами.

PoolManager by Path-o-logical Games хороший пуллменеджер, для того чтобы избежать лагов в момент создания объекта.

Audio Toolkit — для работы со звуком.



7. Статистика

В релизе около 400кб, написанного мной кода. Ещё 150кб — это генератор и некоторые смежные скрипты.
В самой игре, на данный момент, 3 главы по 16 отобранных уровней. Также есть режим Испытаний, где ещё около 700 уровней, динамически распределённых по сложности.
В среднем уровень без звёзд можно пройти за 25 ходов. Самые сложные уровни со звёздами около 55.

8. Монетизация и продвижение

Пока нет каких то точных цифр, но у нас как и у сайлонов есть план.
На примере угадайки фильмов мы поняли, что на рекламе можно заработать, только если у вас от 300к+ пользователей. Поскольку проект у нас, как ни крути нишевой, думаю это не вариант. Но при этом делать платное приложение для Андроида — самоубийство. В итоге была выбрана стандартная нынче модель: показ рекламы и продажа монет. С любой покупкой монет идёт в комплекте полное отключение рекламы. Монеты даются при прохождении уровней и за них можно узнать идеальное прохождение уровня.

Рекламироваться мы решили через обзоры, потому как покупка установок выходит очень дорого и ROI будет отрицательным. Думаем воспользоваться сервисом ispreadnews.com
iOS версия также готова, но по ней пока не принято решение как её выпускать. Может имеет смысл договориться с каким-нибудь издателем.

9. Небольшое ингейм видео.



Правда, в игре портретная ориентация экрана.

ссылка на оригинал статьи http://habrahabr.ru/post/219747/