Добро пожаловать на Pro Pawn - Портал о PAWN-скриптинге.
Показано с 1 по 8 из 8
  1. #1
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±

    Перехват функций, часть 1: основы

    Оглавление:
    Внимание! Эта тема закрыта от комментариев. Если у вас есть вопросы по поводу перехватов - внимательно прочтите вторую часть урока, и если что-то останется для вас непонятным, задавайте вопросы в той теме.


    Всем привет.

    Сегодня я расскажу вам о перехвате функций в Pawn.

    Для начала разберёмся, что такое перехваты и с чем их едят.
    Перехваты в SA:MP обычно используется для того, чтобы изменить поведение каких-либо функций.
    Классическим примером может послужить написание античита на HP.
    Предположим, у нас есть глобальный массив, в котором мы будем записывать HP игроков после его выдачи с помощью SetPlayerHealth.
    Без перехватов придётся выискивать в моде все вызовы SetPlayerHealth и под каждым из них вручную записывать выдаваемое количество HP в ячейку массива.

    В то же время, используя технику перехвата, достаточно всего лишь написать 1 перехватчик, в котором происходит та же запись в массив, и поместить его в самое начало мода, перед подключением a_samp.inc. В дальнейшем перехваты вместе с остальными переменными и функциями античита можно вынести в отдельный инклуд (например, "ac_hp.inc"), который при необходимости можно будет отключить, всего лишь закомментировав строку #include "ac_hp.inc".
    Таким образом, техника перехватов даёт возможность "прицепить" свой код к стандартным функциям и коллбэкам без вмешательства в сам мод, в разы упростив дальнейшую разработку.

    В этом уроке будет описан только один метод, который был открыт благодаря ipsBruno и Y_Less.
    Вся суть данного метода состоит в том, что макропроцессор обрабатывает выражения "#if defined X" в 2 прохода, поэтому если "X" - название ещё не известной компилятору функции/переменной/константы, во время 1-го прохода макропроцессор пропустит такой макрос и сначала соберёт информацию о функциях, переменных и константах в скрипте, завершив 1-й проход.
    После этого на 2-м проходе компилятор уже будет знать обо всех переменных, функциях и константах, расположенных не только перед строкой с "#if defined", но и после неё. В результате перехватчик подменит собой перехватываемую функцию, а затем вызовет её, если она существует. Если же перехватываемой функции не существует, будет вызван только перехватчик.


    Преимущества такого способа перед другими методами:
    1. Не требуется вызывать CallLocalFunction, благодаря чему не происходит увеличения нагрузки из-за использования нативных функций.
    2. Не используется модификация кода во время его выполнения (из-за таких модификаций могла возникнуть несовместимость кода с JIT).


    Рассматриваемый нами метод перехвата работает примерно так:
    Код:
    <перехватчик>
    	<код перехвата>
    	<вызов перехватываемой функции>
    <конец перехватчика>
    <функция уже была перехвачена?>
    	<удалить старый перехват>
    <иначе>
    	<дать знать, что теперь функция перехвачена>
    <конец ветвления>
    <вставить перехватчик в цепочку вызовов>
    <предварительно объявить функцию>
    В результате при умелом использовании одна или несколько функций-перехватчиков могут быть выстроены в цепочку: сначала вызывается первый объявленный перехватчик, а за ним и все остальные, пока очередь не дойдёт до оригинальной перехватываемой функции:
    Код:
    <перехватчик1>
    	<выполнить набор действий перехватчика>
    	<вызвать перехватчик2>
    <конец перехватчика>
    
    <перехватчик2>
    	<выполнить набор действий перехватчика>
    	<вызвать перехватчик3>
    <конец перехватчика>
    
    <...>
    
    <перехватчикN>
    	<выполнить набор действий перехватчика>
    	<вызвать перехватываемую функцию>
    <конец перехватчика>
    
    <перехватываемая функция>
    	<...>
    <конец функции>
    В Pawn существуют 2 способа реализации этого метода: один для перехвата коллбэков (функций, автоматически вызываемых сервером; пример: OnPlayerConnect) и один для нативных функций (функций, реализованных в самом сервере; пример: printf, SendClientMessage).

    Перехват коллбэков реализуется следующим образом:
      Открыть/закрыть
    1. // CallbackFunc - название перехватываемой функции
    2. // param1, param2 - параметры перехватываемой функции
    3. public CallbackFunc(param1, param2)
    4. {
    5. // здесь место для Вашего кода, ради которого и осуществляется перехват
    6. // ...
    7.  
    8. // если перехватываемая функция существует - нужно вызвать и её,
    9. // после чего вернуть значение, которая вернёт перехватываемая функция
    10. // (LibName - название Вашей библиотеки (в случае с Pawn - инклуда),
    11. // в котором перехватывается функция CallbackFunc)
    12. #if defined LibName__CallbackFunc
    13. return Libname__CallbackFunc(param1, param2);
    14. #endif
    15. }
    16.  
    17. // если объявлен макрос с префиксом "_ALS_" и именем функции после него -
    18. // это значит, что такая функция уже была где-то перехвачена
    19.  
    20. // если функция CallbackFunc уже была перехвачена - уберём макрос, сделанный
    21. // в предыдущем перехвате (назначение этого макроса будет объяснено далее)
    22. #if defined _ALS_CallbackFunc
    23. #undef CallbackFunc
    24.  
    25. // если же функция ещё не была перехвачена,
    26. // дадим знать о её перехвате, объявив макрос "_ALS_CallbackFunc"
    27. #else
    28. #define _ALS_CallbackFunc
    29. #endif
    30.  
    31. // сделаем макрос "CallbackFunc", чтобы заменить название перехватываемой
    32. // функции CallbackFunc на Libname__CallbackFunc во время её объявления в основном скрипте
    33. #define CallbackFunc LibName__CallbackFunc
    34.  
    35. // отныне перехватываемая функция будет называться не "CallbackFunc", а "LibName__CallbackFunc",
    36. // а название "CallbackFunc" достанется функции-перехватчику;
    37. // остаётся лишь сделать для перехваченной функции опережающее объявление
    38. // с её новым названием, чтобы избежать лишних предупреждений от компилятора
    39. #if defined LibName__CallbackFunc
    40. forward LibName__CallbackFunc(param1, param2);
    41. #endif


    Перехват нативных функций производится похожим образом, но, в отличие от первого способа, перехватываемая функция не переименовывается, поэтому не требуется делать для неё новое предварительное объявление (forward).
      Открыть/закрыть
    1. // функция-перехватчик
    2. stock LibName__NativeFunc(param1, param2)
    3. {
    4. // место для кода перехвата
    5. // ...
    6.  
    7. // вызов перехватываемой функции, на этот раз без проверки существования функции
    8. return NativeFunc(param1, param2);
    9. }
    10. #if defined _ALS_NativeFunc
    11. #undef NativeFunc
    12. #else
    13. #define _ALS_NativeFunc
    14. #endif
    15. #define NativeFunc LibName__NativeFunc


    Теперь разберёмся, как всё это выглядит в деле.
    Методы для коллбэков и нативных функций разные, поэтому рассмотрены они будут отдельно.
    1. Перехват коллбэков на примере OnDialogResponse:
      1. // массив, в котором записаны ID диалогов ,показанных другим игрокам
      2. static player_dialog_ids[MAX_PLAYERS] = {-1, ...};
      3.  
      4. // перехватчик
      5. public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
      6. {
      7. // если игрок посылает сигнал об ответе на диалог,
      8. // который ему не показывали - это признак использования читов
      9. // (и да, я знаю, что сейчас этой проблемы в SA:MP нет, это всего лишь пример!)
      10. if(player_dialog_ids[playerid] != dialogid)
      11. Kiсk(playerid);
      12. player_dialog_ids[playerid] = -1;
      13. #if defined spd__OnDialogResponse
      14. return spd__OnDialogResponse(playerid, dialogid, response, listitem, inputtext);
      15. #endif
      16. }
      17. #if defined _ALS_OnDialogResponse
      18. #undef OnDialogResponse
      19. #else
      20. #define _ALS_OnDialogResponse
      21. #endif
      22. #define OnDialogResponse spd__OnDialogResponse
      23. #if defined spd__OnDialogResponse
      24. forward spd__OnDialogResponse(playerid, dialogid, response, listitem, inputtext[]);
      25. #endif

    2. Перехват нативной функции на примере ShowPlayerDialog:
      1. static player_dialog_ids[MAX_PLAYERS] = {-1, ...};
      2.  
      3. // перехватчик
      4. stock spd__ShowPlayerDialog(playerid, dialogid, type, header[], text[], button1[], button2[])
      5. {
      6. player_dialog_ids[playerid] = dialogid;
      7. return ShowPlayerDialog(playerid, dialogid, type, header, text, button1, button2);
      8. }
      9. #if defined _ALS_ShowPlayerDialog
      10. #undef ShowPlayerDialog
      11. #else
      12. #define _ALS_ShowPlayerDialog
      13. #endif
      14. #define ShowPlayerDialog spd__ShowPlayerDialog


    На сегодня всё, удачного скриптинга!

    Для тех, кто любит стрелять себе в ногу:
      Открыть/закрыть

    1. Если вы только начинаете практиковаться с перехватами, сверяйте свой код с примерами из этого урока.

    2. Не выдумывайте своих префиксов вместо "_ALS_".
      Директива "#if defined _ALS_<название функции>" используется специально, чтобы ваш перехват правильно работал с другими перехватами той же функции.
      Если вместо "_ALS_" использовать другой префикс, потеряется совместимость с другими перехватчиками.
      Пример того, как не нужно делать:
      Код:
      public OnGameModeInit()
      {
      	// ...
      #if	defined	my_lib_OnGameModeInit
      	my_lib_OnGameModeInit();
      #endif
      }
      #if defined _my_prefix_OnGameModeInit
      	#undef	OnGameModeInit
      #else
      	#define	_my_prefix_OnGameModeInit
      #endif
      #if	defined	my_lib_OnGameModeInit
      forward my_lib_OnGameModeInit();
      #endif
      #define	OnGameModeInit	my_lib_OnGameModeInit
      Правильный пример:
      Код:
      public OnGameModeInit()
      {
      	// ...
      #if	defined	my_lib_OnGameModeInit
      	my_lib_OnGameModeInit();
      #endif
      }
      #if defined _ALS_OnGameModeInit
      	#undef	OnGameModeInit
      #else
      	#define	_ALS_OnGameModeInit
      #endif
      #if	defined	my_lib_OnGameModeInit
      forward my_lib_OnGameModeInit();
      #endif
      #define	OnGameModeInit	my_lib_OnGameModeInit
    3. Как-то давно один "профессионал" спросил меня, мол зачем вся эта чепуха с перехватами, когда всё можно уместить в один #define?
      1. stock my_AddStaticVehicle(modelid, Float:spawn_x, Float:spawn_y, Float:spawn_z, Float:angle, color1, color2)
      2. {
      3. print('Функция AddStaticVehicle перехвачена');
      4. return AddStaticVehicle(modelid, spawn_x, spawn_y, spawn_z, angle, color1, color2);
      5. }
      6. #define AddStaticVehicle my_AddStaticVehicle

      Так вот, этот метод в корне неправильный.

      Почему? Очень просто, им нельзя навесить больше одного перехвата на одну и ту же функцию.
      Не верите? Тогда попробуйте скомпилировать такой код:
      1. // 2-й перехватчик для AddStaticVehicle
      2. stock my2_AddStaticVehicle(modelid, Float:spawn_x, Float:spawn_y, Float:spawn_z, Float:angle, color1, color2)
      3. {
      4. print('Функция AddStaticVehicle перехвачена ещё раз');
      5. return AddStaticVehicle(modelid, spawn_x, spawn_y, spawn_z, angle, color1, color2);
      6. }
      7. #define AddStaticVehicle my2_AddStaticVehicle
      8.  
      9. // 1-й перехватчик для AddStaticVehicle
      10. // Здесь будет "warning 201: redefinition of constant/macro (symbol "AddStaticVehicle")".
      11. stock my1_AddStaticVehicle(modelid, Float:spawn_x, Float:spawn_y, Float:spawn_z, Float:angle, color1, color2)
      12. {
      13. print('Функция AddStaticVehicle перехвачена');
      14. return AddStaticVehicle(modelid, spawn_x, spawn_y, spawn_z, angle, color1, color2);
      15. }
      16. #define AddStaticVehicle my1_AddStaticVehicle

      Получилось скомпилировать код без варнингов и ошибок?

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

      Но ведь и так сойдёт! А кому не нравится, пусть сами переделывают!!
      Отправляйтесь обратно н***й на govno-info и постите говнокод там.



    P.S.: Скоро будет готова 2-я часть урока, в которой будет рассматриваться использование перехватов на практике.
    Часть 2.


    Автор: Daniel_Cortez
    Специально для Pro-Pawn.ru
    Копирование данной статьи на других ресурсах без разрешения автора запрещено!
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

  2. 21 пользователя(ей) сказали cпасибо:
    #ball (26.09.2014) $continue$ (25.09.2014) BadPawn (02.05.2016) DeimoS (25.09.2014) Desulaid (23.03.2015) Edwin (12.04.2016) Elaid (27.09.2014) Glant (01.08.2016) Guldan (27.11.2016) L0ndl3m (25.09.2014) MaKcuM (27.09.2014) MR_BEN (25.09.2014) Mr_LemoneZ (13.01.2017) nekaz (25.09.2014) Nurick (25.09.2014) Osetin (25.09.2014) Reim (24.06.2015) Salvacore (25.09.2014) Sp1ke (26.08.2015) Vasiliy (19.02.2018) vovandolg (08.03.2016)
  3. #2
    Аватар для Nurick
    Пользователь

    Статус
    Оффлайн
    Регистрация
    19.04.2014
    Адрес
    Уфа, Россия
    Сообщений
    319
    Репутация:
    52 ±
    Хотелось бы увидеть на практике. Подожду 2-ю часть.

  4. #3
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±
    Добавил в начало статьи объяснение сути перехватов, дабы больше не было вопросов.
    Вторую часть выложу на следующей неделе, как только доделаю пару-тройку примеров.
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

  5. 2 пользователя(ей) сказали cпасибо:
    #ball (27.09.2014) Nurick (26.09.2014)
  6. #4
    Аватар для H_Less
    Пользователь

    Статус
    Оффлайн
    Регистрация
    03.07.2015
    Сообщений
    2
    Репутация:
    0 ±
    А что, если нет необходимости выискивать все вызовы SetPlayerHealth и сделать просто так?

    PHP код:
    #define h_SetPlayerHealth(%0,%1) (SetPlayerHealth(%0,%1);ac_health[%0]=%1) 
    Не воспринимайте мой ответ всерьез. Просто сложилось такое впечатление, что автору лень выискивать все вызовы и из-за этого пишет перехватчик.

    По поводу перехватов. А что если нативная функция имеет всего один параметр? К примеру GetVehicleModel. То как ее перехватить?

  7. #5
    Аватар для $continue$
    Пользователь

    Статус
    Оффлайн
    Регистрация
    02.08.2014
    Адрес
    г. Киров (aka Вятка)
    Сообщений
    1,487
    Репутация:
    276 ±
    Цитата Сообщение от H_Less Посмотреть сообщение
    А что, если нет необходимости выискивать все вызовы SetPlayerHealth и сделать просто так?

    PHP код:
    #define h_SetPlayerHealth(%0,%1) (SetPlayerHealth(%0,%1);ac_health[%0]=%1) 
    Не воспринимайте мой ответ всерьез. Просто сложилось такое впечатление, что автору лень выискивать все вызовы и из-за этого пишет перехватчик.

    По поводу перехватов. А что если нативная функция имеет всего один параметр? К примеру GetVehicleModel. То как ее перехватить?
    Руками и перехватываем
    И кстати, Вам рассказали в первом посте почему лучше всего использовать перехваты.
    PHP код:
    stock _GetVehicleModel_(vehicleid)
    {
        
    // Код
        
    return GetVehicleModel(vehicleid);
    }
    #if defined _ALS_GetVehicleModel
        #undef    GetVehicleModel
    #else
        #define    _ALS_GetVehicleModel
    #endif
    #define    GetVehicleModel    _GetVehicleModel_ 

  8. #6
    Аватар для H_Less
    Пользователь

    Статус
    Оффлайн
    Регистрация
    03.07.2015
    Сообщений
    2
    Репутация:
    0 ±
    Цитата Сообщение от Bublik_Public Посмотреть сообщение
    Руками и перехватываем
    И кстати, Вам рассказали в первом посте почему лучше всего использовать перехваты.
    PHP код:
    stock _GetVehicleModel_(vehicleid)
    {
        
    // Код
        
    return GetVehicleModel(vehicleid);
    }
    #if defined _ALS_GetVehicleModel
        #undef    GetVehicleModel
    #else
        #define    _ALS_GetVehicleModel
    #endif
    #define    GetVehicleModel    _GetVehicleModel_ 
    Перехват руками? Это BDSM уже какой то :D

  9. #7
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±
    Цитата Сообщение от Bublik_Public Посмотреть сообщение
    Почему то не отчищает строку до =
    PHP код:
    stock fread_ex(Filehandlestring[], size sizeof stringboolpack false)
    {
        
    GetStringDataAfterChar(string);
        return 
    fread(handlestringsizepack);
    }
    #if defined _ALS_fread
        #undef    fread
    #else
        #define    _ALS_fread
    #endif
    #define    fread    fread_ex 
    Могу я поинтересоваться, зачем здесь вообще нужен перехват?
    И что будет, если вам вдруг понадобится использовать оригинальную функцию fread, без считывания данных после знака "=" ?
    Или у вас так и задумано в корне поменять принцип работы функции и сделать недоступным оригинал? Если да, то перехваты созданы совсем не для этого.
    Кстати, влияние на логику функций таки обсуждается во 2-й части урока, среди остальных правил.
    Цитата Сообщение от Daniel_Cortez Посмотреть сообщение
    • Перехватчик не должен менять логику работы перехватываемой функции.
      Изменение возвращаемых значений допускается только в том случае, если это не нарушает совместимости с оригинальной функцией.
      Например, в античите на HP перехватчик GetPlayerHealth может возвращать не то значение, которое возвратит оригинальная функция, а кол-во HP игрока, хранящееся в античите. Но в то же время перехватчик не должен всегда возвращать, например, сумму HP и брони, это уже совсем другая логика.
      Иными словами, работа перехватчика должна быть незаметной для того кода, который использует перехватываемую функцию, как будто того перехватчика и нет.
      Если же вам нужна функция, работающая по-другому - сделайте отдельную функцию, но не нужно путать её с оригиналом с помощью перехвата. Вмешательство в стандартную логику обычно приводит только к проблемам.
    Я думал, это должно быть очевидно, что не нужно наступать на грабли, но раз так...
    Ок, закрою эту тему и оставлю в 1-м посте примечание, чтобы, прежде чем задавать какие-то вопросы, читали 2-ю часть урока и оставляли комментарии там.
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

  10. #8
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±
    Обновил 1-й пост. Добавлен новый пункт "Для тех, кто любит стрелять себе в ногу".
    Отдельное спасибо Untonyst за то, что указал на ошибки в примере перехвата OnDialogResponse.

    Цитата Сообщение от Daniel_Cortez Посмотреть сообщение
    Для тех, кто любит стрелять себе в ногу:
    1. Если вы только начинаете практиковаться с перехватами, сверяйте свой код с примерами из этого урока.

    2. Не выдумывайте своих префиксов вместо "_ALS_".
      Директива "#if defined _ALS_<название функции>" используется специально, чтобы ваш перехват правильно работал с другими перехватами той же функции.
      Если вместо "_ALS_" использовать другой префикс, потеряется совместимость с другими перехватчиками.
      Пример того, как не нужно делать:
      Код:
      public OnGameModeInit()
      {
      	// ...
      #if	defined	my_lib_OnGameModeInit
      	my_lib_OnGameModeInit();
      #endif
      }
      #if defined _my_prefix_OnGameModeInit
      	#undef	OnGameModeInit
      #else
      	#define	_my_prefix_OnGameModeInit
      #endif
      #if	defined	my_lib_OnGameModeInit
      forward my_lib_OnGameModeInit();
      #endif
      #define	OnGameModeInit	my_lib_OnGameModeInit
      Правильный пример:
      Код:
      public OnGameModeInit()
      {
      	// ...
      #if	defined	my_lib_OnGameModeInit
      	my_lib_OnGameModeInit();
      #endif
      }
      #if defined _ALS_OnGameModeInit
      	#undef	OnGameModeInit
      #else
      	#define	_ALS_OnGameModeInit
      #endif
      #if	defined	my_lib_OnGameModeInit
      forward my_lib_OnGameModeInit();
      #endif
      #define	OnGameModeInit	my_lib_OnGameModeInit
    3. Как-то давно один "профессионал" спросил меня, мол зачем вся эта чепуха с перехватами, когда всё можно уместить в один #define?
      PHP код:
      stock my_AddStaticVehicle(modelidFloat:spawn_xFloat:spawn_yFloat:spawn_zFloat:anglecolor1color2)
      {
          print(
      'Функция AddStaticVehicle перехвачена');
          return 
      AddStaticVehicle(modelidspawn_xspawn_yspawn_zanglecolor1color2);
      }
      #define AddStaticVehicle my_AddStaticVehicle 
      Так вот, этот метод в корне неправильный.

      Почему? Очень просто, им нельзя сделать больше одного перехвата на одну и ту же функцию.
      Не верите? Тогда попробуйте скомпилировать такой код:
      PHP код:
      // 2-й перехватчик для AddStaticVehicle
      stock my2_AddStaticVehicle(modelidFloat:spawn_xFloat:spawn_yFloat:spawn_zFloat:anglecolor1color2)
      {
          print(
      'Функция AddStaticVehicle перехвачена ещё раз');
          return 
      AddStaticVehicle(modelidspawn_xspawn_yspawn_zanglecolor1color2);
      }
      #define AddStaticVehicle my2_AddStaticVehicle

      // 1-й перехватчик для AddStaticVehicle
      // Здесь будет "warning 201: redefinition of constant/macro (symbol "AddStaticVehicle")".
      stock my1_AddStaticVehicle(modelidFloat:spawn_xFloat:spawn_yFloat:spawn_zFloat:anglecolor1color2)
      {
          print(
      'Функция AddStaticVehicle перехвачена');
          return 
      AddStaticVehicle(modelidspawn_xspawn_yspawn_zanglecolor1color2);
      }
      #define AddStaticVehicle my1_AddStaticVehicle 
      Получилось скомпилировать код без варнингов и ошибок? Я предупреждал.

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


      Отправляйтесь обратно н***й на govno-info и постите говнокод там.
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

  11. Пользователь сказал cпасибо:
    Desulaid (25.08.2015)
 

 

Информация о теме

Пользователи, просматривающие эту тему

Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)

Метки этой темы

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •