MasterGH Опубликовано 12 декабря, 2009 Поделиться Опубликовано 12 декабря, 2009 ООП состоит на основе трёх концепций: инкапсуляция, наследование и полиморфизм. Но модель ООП на этом не заканчивается. В каждом отдельном языке программирования добавлены свои "прибамбасы" относительно наследования и полиморфизма, что формирует модель ООП соответствующую языку программирования. Из всех языков которых я знаю или сталкивался С++ и его продолжения наиболее сильные в ООП.О концепциях ООПКласс - это тип или описание объекта. На основе этого описания выделятеся память и строится объект. Когда один объект строит другой возникает цепочка указателей. Всегда есть статический адрес начального объекта, по которому можно добраться через цепочку указателей до данных вложенных объектов.Также существуют:vftable - таблица виртуальных функцийvbtable (virtual base class table) - таблица базового виртуального классаОбе таблицы находятся в статической памяти и могут служить описанием работы с данными и методами как для одного так и нескольких объектов.Инкапсуляция характеризует вложенные в объект данные, например, это - жизнь, здоровье, опыт и т.п. Напрямую извне к этим данным в понятии инкапсуляции обращаться нельзя. К ним можно обращаться только через его методы и свойства - нам это только в пользу. Можно например сделать чит-код "убийство" с одного удара внедрением отладочного кода в метод класса игрока. Этот метод работает как для вашего героя так и для ботов и всё это благодаря тому что разработчик игры использовал ООП в программировании игры.Наследование характеризует ситуацию, когда некоторый объект был создан на основе своего описания и описания наследуемого родительского класса. В языке С++ наследовать можно как один класс так и несколько. При простом наследовании, где наследование происходит от одного класса, возможно наследование виртуальных функций, а при множественном наследование возможно наследование виртуальных классов. Существует малая вероятность что в играх сразу встретится наследование виртуальных функций и классов. Обычно, что-то одно и чаще наследование виртуальных функций.Сложное наследование некоторых современных стратегиях это головная боль тех кто умеет внедрять чит-коды.Полиморфизм существует при наследовании объектов при определении виртуальных методов или классов. Также стоит обратить внимание на два специфичных метода. Конструктор - создаёт объект и деструктор - разрушает его для того чтобы тот не занимал места в памяти операционной системы. Насчёт "прибамбасов" таких как перегрузка методов, перегрузка операторов, шаблоны(в С++)... и т.д. Об этом я, может быть, напишу, на примере конкретной игры. Например, в СТАЛКЕРЕ управление объектами осуществятся, скорее всего, через описание шаблона и итератора...ООП в играх да и в целом позволяет строить модель программы в удобном виде. В книгах пишут, что если код размеров от 10000-100000 строк, то в нём невозможно ориентироваться без применения ООП чего не говорить уже о большем размере кода. Дело в том не понятно к какой абстракции принадлежит та или иная функция, тот или иной метод. Если программа размером например не более 1000 строк, то вполне можно обойтись без ООП - программа будет работать быстрее, если будет правильно написана, но иногда быстрее что-то написать с ОПП - и получится 400 строк вместо 1000.В современной игре в которой очень много объектов обязательно применяется ООП. В этом случае в ней может быть множество объектов разных типов, но лишь один метод (типа B или C) будет работать со здоровьем всех игроков или урона зданий, или даже уменьшение и увеличения опыта, или даже чего-нибудь другого. Может быть, даже не уменьшение, а просто копирование уменьшенного параметра. Пару слов думаю, стоит сказать про скрипты LUA и другие. Они позволяют задавать новые свойства и поведения объектов без перекпомпляции классов, методов, всего кода. Если вы знаете как построить запрос LUA-интерпритатору, то вы можете написать достаточно полезные чит-коды - тоже самое бессмертие или добавление ресурсов. Также снова напомню, что скриптовые моды, например могут не позволить, например, сделать стрельбу без перезарядки и в некоторых случаях нельзя сделать кого-то бессмертным, может быть и можно, но понять как это сделать не просто - копаться в распакованных ресурсов игры в надежде найти параметр бессмертия или нужно очень хорошо уметь пользоваться дизассемблерером - перехватывать запросы к интерпритатору и анализировать структуры на входе и на выходе из интерпретатора.О том как обманывать игры с сложным ООП программированием будет в моих статьях, я надеюсь =) Пока не все игры мне поддаются, просто на это я выделяю мало времени. Сначала надо разобраться как выглядит виртуальные таблицы при виртуальном наследовании классов.Что касается применения ООП в написании трейнера. Могу сказать, что это дело вкуса. Есть люди которые от и до пишут трейнер на ассемблере приследуя цель вылизанной от и до программы меньшего размера. Только это не драйвер, который требует лучшей производительности, потраченный труд уходит в пустую.Как бы там ни было, для мозгов человека удобнее воспринимать наиболее структурированную информацию - где чит-код является классом иди структурой. А ими управляет специальный класс. С этой стороны трейнеры лучше писать с применением ООП, чем на ассемблере или процедурном с WinApi. Однако, если бы трейнер был очень нужной полезной программой-драйвером, то его без сомнений лучше писать на ассемблере, в "тяжелом" случае на С.По поводу языка на котором лучше писать. Это дело вкуса, но если интересно моё мнение, то писать нужно на языке С++ с применением ООП. С++ это системный язык. Дельфи, Байсик больше прикладного характера, но системные программы можно также писать и они будут также работать. Использовать ли визуальное оформление, писать ли на VC++? Да, можно, это не критично. Вам не придётся писать дополнительного кода.В завершении статьи. Трейнеры для синголовых игр на сегодняшний день ещё не использовали весь возможный потенциал в обмане игр: 1. Нет универсальности трейнеров для одного вида игры с разными патчами и локализациями.2. Думаю, что нет ни одного трейнера с инжектом игрового скрипта или заданием скрипта для игрового скрипт-интерпритатора в играх где он используется - я довольно много дизассемблировал трейнеров и этого не видел (другое дело моды). Предпосылки к этому есть у MHS и CE - и там и там можно создавать удалённый поток и подгружать библиотеки, которые будут своего рода входным вводом задания скриптов для игрового скрит-интерпретатора.Если вы с жадностью хотите ещё прочитать информацию, то читайте её по ссылкам: презентация, более подробная версия реверсинга С++Прочтение этой статьи позволит ориентироваться в связях структур игровых объектов – позволит осмысленно подходить к поиску многоуровневых указателей, а также внедрять чит-коды с использованием фильтров.Часть1. Как выглядит объект в памяти. Первое знакомствоКласс можно представить так:Название класса:Начало: Описание данных класса; Описание методов класса; Конструктор; Деструктор;Конец описания класса.Начало и конец в дальнейшим обозначается фигурными скобками {…}«Описание данных класса» - это описание переменных определенного типа. Например, переменная "int а" имеет тип int (это целое число 4 байта со знаком (подробнее о типах см. в Интернете)Методы класса представляют собой функции, которые возвращают или не возвращают значения. Например, void fun(){printf("Hello World!“}. В данном примере присутствие void, говорит о том, что функция не возвращает значения.Привязка данных и методов к классу называется инкапсуляцией.Существует такое понятие как ограничение видимости при инкапсуляции. Ограничение описывается основными директивами: private, protected, public:private – доступ к данным только изнутри объекта,protected – доступ к данным изнутри объекта и из производных классов.public – доступ к данным из любого места.Давайте посмотрим наконец как выглядит класс в С++. int c; //по умолчанию доступ будет как privatepublic: void pc(){printf("Method C %dn",c);}};class C{Этот класс имеет описание закрытой переменной с и открытую функцию pc, которая выводит на «досовский» экран значение переменной с.Напомню, что это всего лишь описание будущего объекта. Любой объект инициализируется «конструктором» и разрушается «деструктором». В примере выше конструктор и деструктор не объявлены, но они вызываются по умолчанию.Давайте посмотрим наш пример с конструктором, который инициализирует данные с равной единице. int c; //тут может быть здоровья игрока к примеруpublic: C(int aa=100){c=aa;} // конструктор void pc(){printf("Method C %dn",c);}};class C{Конструктор, принимает по умолчанию входной параметр аа равный 1 и присваивает его с. Пару слов о пользе конструкторов. Конструктор в играх может создавать объект по входным параметрам. В читинге конструктор может пригодиться при принудительном создании нового объекта или создании объекта с изменёнными свойствами. Ну, например, когда игра загружается вы, скорее всего, можете подменить скин своего персонажа, или изменить его характиристику - что он как бы является торговцем, которого убить невозможно. Ну, ладно... идём дальше, т.к. это отдельная тема.Деструктор выглядел бы так: ~C(…);Более подробно см. в Интернете. Подведём итог. Мы знаем, как выглядят и работают класс, конструктор, деструктор, данные, методы.Создадим проект в VC++ проект консольного варианта:class Player{ int NameID,Health,Ammo; public: void set(int a, int b, int c){NameID=a; Health=b; Ammo=c;} void getInfoX(){ printf("NameID= %dn",NameID); printf("Health= %dn",Health); printf("Ammo= %dn",Ammo); }};void main(){ Player *obj=new(Player); obj->set(1,100,20); obj->getInfoX(); delete(obj);};#include Оператор –> применяется для обращения к функциям внутри объекта, который был создан в выделнной памяти.Результат работы программы:Рис.1Данные объекта были построены таким образомРис.2А вот что мы увидим в отладчике VC++-----#include class Player{int NameID,Health,Ammo;public:void set(int a, int b, int c){NameID=a;Health=b;Ammo=c;}void getInfoX(){printf("NameID= %dn",NameID);printf("Health= %dn",Health);printf("Ammo= %dn",Ammo);}};void main(){00401000 push esiPlayer *obj=new(Player);00401001 push 0Ch//0Сh – кол-во байт выделенной памяти.00401003 call operator new (401189h)00401008 mov esi,eaxobj->set(1,100,20);0040100A xor eax,eax0040100C inc eaxobj->getInfoX();0040100D push eax0040100E push offset string "NameID= %dn" (40BB08h)00401013 mov dword ptr [esi],eax //esi – адрес объекта00401015 mov dword ptr [esi+4],64h0040101C mov dword ptr [esi+8],14h00401023 call printf (40104Fh)00401028 push dword ptr [esi+4]0040102B push offset string "Health= %dn" (40BB14h)00401030 call printf (40104Fh)00401035 push dword ptr [esi+8]00401038 push offset string "Ammo= %dn" (40BB20h)0040103D call printf (40104Fh)delete(obj);00401042 push esi00401043 call operator delete (401114h)00401048 add esp,20h};0040104B xor eax,eax0040104D pop esi0040104E ret---Тоже самое в IDA рис. 3А вот так выглядит в псевдокодерис. 4При обмане игр у нас не будет исходника С++ и нам нужно научится понимать псевдокод.Сделаем немного понятнее псевдокод.Нажмём shift+F9 и опишем структуру класса Player с 3*4 байтрис. 5Затем в псевдокоде нажмём на Y и определим тип структуры:рис.6И получим более лучшее представление псевдокодаРис.7И ещё давайте посмотрим, что будет если мы не иницилизируем некоторые данные объекта.рис 8Результат такой:рис 9Переменные x1.x2.x3 не инициализировались и остался мусорный код выделенной памяти. Когда выделяется память, то она не «подчищена», если вы не знали.Подведём итог. Познакомились с С++ (если не были знакомы) увидели на простом примере как оформляется структура объекта в С++ и как она выглядит в памяти и в псевдокоде. Увидели как псевдокод облегчает анализ игровых объектов.Главное научиться проводить аналогии между псевдокодом и дизассемблерным кодом для того чтобы представить как устроен объект.Если у вас возникли трудности в С++ то существует много информации в Интернете, которую вы можете легко найти самостоятельно.Часть2. Дальнейшее исследования игрового объекта в памятиНаследование.В этой части рассмотрим как выглядит структура объекта:При наследовании базового классаПри иерархии наследования классовПри иерархии наследования класса с инкапсулированным объектом2.1 Структура объекта при простом наследовании базового класса.Рис. 1Посмотрим дизассемблерный код...Рис. 2Псевдокод будет такимРис. 32.2 Структура объекта при иерархии наследования классов.Ниже представлен пример классов «вложеннных» друг в друга по принципу «матрёшки». Базовый класс «стремиться» вверх структурыРис. 4Псевдокод для этого кода приведён ниже.рис. 5По аналогии можно представить как будут располагаться данные при различных наследованиях.2.3 Структура объекта при иерархии наследования классов с инкапсулированным объектом.Тут всё просто. Структура одного объекта должна содержать указатель на созданный объект. Красная стрелка это указатель на созданный инкапсулированный объект. Получается что один объект, содержит указатель на другой объект.рис. 6Ниже находится псевдокод (к сожалению картинка сдохла на сервере, а у меня нет оригинала, но осталась рука - пока не знаю как она сюда попала ).рис. 7Ниже находится дизассемблерный код.рис. 8Подведём итог вы узнали:как выглядит структура инкапсулирующая объект,как выглядит структура при наследовании и при иерархии наследования.Часть 3. Дальнейшее исследования игрового объекта в памятиВ этой части мы рассмотрим.Виртуальные функцииВиртуальные классы и множественное наследование3.1 Виртуальные функции.Если вы не сталкивались с полиморфизмом ни разу и вообще с ООП знакомы мало, то понять эту тему будет, скорее всего, сложно. Виртуальные функции это одинаково объявленные функции в иерархии наследования, которые позволяют работать с данными объекта в контексте нужного класса. Объяснил как смог.. =)Очень важно понять, как это работает для исследования игровых объектов в дальнейшем, т.к. виртуальные функции попадаются сплошь и рядом.Разберём пример «на пальцах», показывающий выгоду виртуальных функции перед похожими, но не виртуальными одинаковыми функциями. Программа перемещает графические объекты по нажатым клавишам клавиатуры, но писать мне её некогда, поэтому разберу основное.{ int x,y;public: Dot() {x=100; y=100;} // просто зададим координаты по умолчанию void show(){сделать пиксел белым}; void hide() {сделать пиксел черным}; void move (int dx,dy){ hide(); x+=dx; y+=dy; show()};};class DotМы создали класс Точки, у которой описаны координаты положения и описаны функции перемещения с угасанием и отображения точки. Ключевое место здесь в том, что в функции перемещения присутствуют локальные вызовы угасания и отображения точки. Именно из-за локальных вызовов появится проблема в производном классе, т.к. в базовом классе будут вызываться свои локальные функции с тем же именем. Выходом будет определение виртуальных функций. Рассмотрим эту проблему.{ int radius;public: Ring () {radius=200;}; //зададим радиус круга по умолчанию void show(){сделать окружность белой}; void hide(){сделать окружность чёрной};};class Ring: public DotВидим, что Круг наследует функцию перемещения, в которой происходят локальные вызовы hide и show. Т.е. если мы зададим движение Кругу, то двигаться будет Точка, т.к. вызываются локальные функции в контексте класса Точки.Ring *ring=new(Ring);ring->mov(50,60); // вызовет перемещение только точки,а не кругаЧтобы решить эту проблему был введен механизм виртуальных функций. Вызов той или иной функции задается смещением (генерируемым компилятором), по которому в созданной таблице виртуальных методов (vftable) выбирается нужная функция, которая и вызовется.{ int x,y;public: Dot() {x=100; y=100;} // просто зададим координаты по умлочанию virtual void show(){сделать пиксел белым}; virtual void hide() {сделать пиксел черным}; void move (int dx,dy){ hide(); x+=dx; y+=dy; show()};};class Ring: public Dot{ int radius;public: Ring () {radius=200;}; // также просто зададим радиус круга void show(){сделать окружность белой}; void hide(){сделать окружность чёрной};};class DotТеперь ring->mov(50,60); // вызовет перемещение только круга в заданные координаты 50 и 60ring->Dot::mov(50,60); // этот метод в принципе бесполезный, т.к. координаты точки уже были равны 50 и 60Ring *ring=new(Ring);Вызывать метод перемещения точки было не обязательно, главное то, что круг наследовал координаты x и y и можно и нужно перемещать только круг. Однако, если вы вызовете ring->Dot::mov(100,100), то это распространится и на круге... т.е. и точка и круг взаимосвязаны и буквально слеплены в одно целое как в симбиозе (кто биологию хорошо знает? )Как мы увидели механизм «виртуальных функций (полиморфизм)» нужен для правильной работы с данными объекта в требуемых контекстах его описывающих классов. Чаще всего виртуальные функции объявляются без входных параметров, таким образом, их применение наиболее удобно и более расположено к полиморфизму.При полиморфизме в статичной области кода возникает таблица виртуальных функций vftable. Указатель на таблицу виртуальных функций находится по первому адресу объекта (см. рис. 1)рис. 1Следует отметить, что vftable характерна только для определённого типа класса. Если вы найдёте в памяти игры адрес таблицы виртуальных функций (vftable) некоторого объекта и затем в сканере памяти найдёте 6 указателей на ту же vftable, то можете считать что вы «вышли» ещё на 6 объектов, которые наследовали vftable класса или некоторой иерархии классов. Соответственно по указателю на vftable в чит-кодах можно делать фильтры по изменению данных игровых объектов построенных по описанию конкретного класса или по описанию классовой иерархии. Учитывая всё выше известное, напишем программу с игровыми объектами и проанализируем формирование объектов с vftable.Результат программы будет таким.рис. 2. (Координат нет, это вы поймете на рис 6 )В данной программе и в случае, кода виртуальные функции наследуются, то vftable сильно усложняется. Нам будет очень полезно рассмотреть случай ниже для обмана игр.Сначала посмотрим расположение данных двух объектов, после того как мы их заполнили. Так мы увидим данные после того как CE сама расструктуризовала их.рис. 3Как мы видим, структуры объектов заполнены непонятными данными по смещениям 0x18-2С, 4C-0x64, 0x68-0x6C. Адреса по этим смещениям рассмотрим позже. Сейчас важно понять, как устроены таблицы на рисунке по смещениям 0x0, 0x30 и как они работают.Они выглядят вот так (так как и расструктуризавала CE)рис. 4Для удобства переведём эти значения в hex- видрис. 5Я пока не знаю, правильно ли я определил, что по смещению 0хС значение 0х48 указывает на конец таблицы виртуальных функций в контексте всей иерархии классов...00401017 – это указатель на функцию Player:: GetInfo0040102B – это указатель на функцию Building::GetInfoОбращение к указателям компилируется в коде (вы это можете посмотреть в дизассемблерном листинге ниже), т.е. в объектах не содержится данных о том к какой именно функции, в каком описании класса в какой момент обращаться.Залезем в псевдокод в функцию main, т.к. именно по нему нам надо учиться обманывать игры и смотрим рис 5 и рис.6В псевдокоде смещения в структурах объектов десятичные,но там не сложные числа можно перевести в 16-ричную систему в уме для того чтобы соотнести смещения структур на рис. 5рис. 6GetInfo в контексте класса control не сработала, из-за виртуальных функций, так и должно быть.Псевдокод показывает всё что необходимо и это гораздо нагляднее, чем смотреть дизассемблерный код в отладке. Для интересующихся (маньяков) привожу дизассемблерный код. Комментировать не буду, т.к. основное всё разобрал. Скопируйте его в отдельное место и можете тщательно разобраться как что и куда.class abstr{virtual void GetInfo()=0;};class control: abstr{protected: int x,y,z,ID;public: control(){x=y=z=0;} void move(int dx,int dy, int dz) {x+=dx;y+=dy;x+=dz;GetInfo();} void GetInfo(){printf("x= %d y= %d z= %dn",x,y,z);}00401000 push dword ptr [ecx+0Ch] 00401003 push dword ptr [ecx+8] 00401006 push dword ptr [ecx+4] 00401009 push offset string "x= %d y= %d z= %dn" (40BB0Ch) 0040100E call printf (4010EAh) 00401013 add esp,10h 00401016 ret };class Player: public control{protected: int Health;public: void Set(int id, int health){ID=id; Health=health; GetInfo();} void GetInfo(){printf("PlayerID= %d Health= %dn",ID,Health);00401017 push dword ptr [ecx+14h] 0040101A push dword ptr [ecx+10h] 0040101D push offset string "PlayerID= %d Health= %dn" (40BB20h) 00401022 call printf (4010EAh) 00401027 add esp,0Ch }0040102A ret };class Building: public Player{int SpeedOfC;public: void Set(int id, int health, int Speed) {ID=id; Health=health;SpeedOfC=Speed; GetInfo();} void GetInfo(){printf ("Building ID= %d Health= %d SpeedOfC= %dn",ID,Health,SpeedOfC);}0040102B push dword ptr [ecx+18h] 0040102E push dword ptr [ecx+14h] 00401031 push dword ptr [ecx+10h] 00401034 push offset string "Building ID= %d Health= %d Speed"... (40BB3Ch) 00401039 call printf (4010EAh) 0040103E add esp,10h 00401041 ret };void main(){ 00401042 push ebx 00401043 push esi 00401044 push edi Player *objP=new(Player);00401045 push 18h 00401047 call operator new (401255h) 0040104C xor esi,esi 0040104E pop ecx 0040104F cmp eax,esi 00401051 je main+24h (401066h) 00401053 mov dword ptr [eax+0Ch],esi 00401056 mov dword ptr [eax+8],esi 00401059 mov dword ptr [eax+4],esi 0040105C mov dword ptr [eax],offset Player::`vftable' (40BB6Ch) 00401062 mov edi,eax 00401064 jmp main+26h (401068h) 00401066 xor edi,edi Building *objB=new(Building);00401068 push 1Ch 0040106A call operator new (401255h) 0040106F pop ecx 00401070 cmp eax,esi 00401072 je main+43h (401085h) 00401074 mov dword ptr [eax+0Ch],esi 00401077 mov dword ptr [eax+8],esi 0040107A mov dword ptr [eax+4],esi 0040107D mov dword ptr [eax],offset Building::`vftable' (40BB74h) 00401083 mov esi,eax objP->Set(1,200);00401085 mov eax,dword ptr [edi] 00401087 xor ebx,ebx 00401089 inc ebx 0040108A mov ecx,edi 0040108C mov dword ptr [edi+10h],ebx 0040108F mov dword ptr [edi+14h],0C8h 00401096 call dword ptr [eax] objB->Set(1,1000,5);00401098 mov eax,dword ptr [esi] 0040109A push 5 0040109C mov dword ptr [esi+10h],ebx 0040109F pop ebx 004010A0 mov ecx,esi 004010A2 mov dword ptr [esi+14h],3E8h 004010A9 mov dword ptr [esi+18h],ebx 004010AC call dword ptr [eax] objP->move(5,5,0);004010AE mov eax,dword ptr [edi] 004010B0 add dword ptr [edi+8],ebx 004010B3 add dword ptr [edi+4],ebx 004010B6 mov ecx,edi 004010B8 call dword ptr [eax] objB->move(6,6,0);004010BA mov eax,dword ptr [esi] 004010BC add dword ptr [esi+8],6 004010C0 add dword ptr [esi+4],6 004010C4 mov ecx,esi 004010C6 call dword ptr [eax] objP->control::GetInfo();004010C8 mov ecx,edi 004010CA call control::GetInfo (401000h) objB->control::GetInfo();004010CF mov ecx,esi 004010D1 call control::GetInfo (401000h) delete(objP);004010D6 push edi 004010D7 call operator delete (4011AFh) delete(objB);004010DC push esi 004010DD call operator delete (4011AFh) 004010E2 pop ecx 004010E3 pop ecx 004010E4 pop edi 004010E5 pop esi };004010E6 xor eax,eax 004010E8 pop ebx 004010E9 ret С виртуальными функциями более менее познакомились. Возможно, в следующих статья разберём ещё более тяжёлые случаи (адские )Продолжение следует. В следующей частях возможно будет рассмотрено.Множественное наследование. Виртуальные функции при множественном наследовании. Виртуальное наследование классов. Виртуальные функции при виртуальном наследовании классов.Статичные данные и функции в объектах. Дружественные функции в объектах. Массивы объектов. Объекты по шаблону. Объекты по стандартному шаблону .Сообщения. Методы дизассемблирования и отладки.Объекты совместно с lua – интерпритатором.Создаём объекты через конструкторы и команды lua.Обход антиотладочных ловушек xlive.Распаковка игровых ресурсов.Обман flash игр.Обман .NET игр.Обман JAVA игр.Обман эмуляторных игр, поиск нулевого адреса. 2 Ссылка на комментарий Поделиться на другие сайты Поделиться
sooqua Опубликовано 14 августа, 2012 Поделиться Опубликовано 14 августа, 2012 Статья - супер, но я как новичок в ООП пока многое не понимаю)) буду разбираться Спасибо Andrey (: Ссылка на комментарий Поделиться на другие сайты Поделиться
VoLT Опубликовано 25 января, 2013 Поделиться Опубликовано 25 января, 2013 Del Ссылка на комментарий Поделиться на другие сайты Поделиться
MasterGH Опубликовано 25 января, 2013 Автор Поделиться Опубликовано 25 января, 2013 Эти книги читать не советую.Советую читать эти. Искать в книгах методы применения структурного программирования так и ООП... и конечно изучать в дизассемблере примеры. Ссылка на комментарий Поделиться на другие сайты Поделиться
Coder Опубликовано 26 января, 2013 Поделиться Опубликовано 26 января, 2013 Отличная статья!P.S. но однако было бы проще, если бы это было в видео формате. Ссылка на комментарий Поделиться на другие сайты Поделиться
Гость dimigor Опубликовано 27 марта, 2015 Поделиться Опубликовано 27 марта, 2015 Статья прость супер!Такого эталонного разбора я нигде не видел. Прошу автора продолжить тему. Ссылка на комментарий Поделиться на другие сайты Поделиться
partoftheworlD Опубликовано 13 января, 2016 Поделиться Опубликовано 13 января, 2016 Отличная статья, особенно для начинающих. Все понятно и написано доступным языком. Ссылка на комментарий Поделиться на другие сайты Поделиться
Рекомендуемые сообщения