Добро пожаловать на Pro Pawn - Портал о PAWN-скриптинге.

Реклама


**Как получить V.I.P** (Перейти)
Чтобы заказать рекламу на Pro-Pawn.Ru, обращайтесь в Skype.
Баннерная реклама 200руб/мес, Текстовая 100руб/мес.
Показано с 1 по 8 из 8
  1. #1
    Аватар для Daniel_Cortez
    new fuck_logic[0] = EOS;

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

    Перехват функций, часть 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).

    Перехват коллбэков реализуется следующим образом:
      Открыть/закрыть
    PHP код:
    // CallbackFunc - название перехватываемой функции
    // param1, param2 - параметры перехватываемой функции
    public CallbackFunc(param1param2)
    {
        
    // здесь место для Вашего кода, ради которого и осуществляется перехват
        // ...

        // если перехватываемая функция существует - нужно вызвать и её,
        // после чего вернуть значение, которая вернёт перехватываемая функция
        // (LibName - название Вашей библиотеки (в случае с Pawn - инклуда),
        // в котором перехватывается функция CallbackFunc)
    #if defined LibName__CallbackFunc
        
    return Libname__CallbackFunc(param1param2);
    #endif
    }

    // если объявлен макрос с префиксом "_ALS_" и именем функции после него -
    // это значит, что такая функция уже была где-то перехвачена

    // если функция CallbackFunc уже была перехвачена - уберём макрос, сделанный
    // в предыдущем перехвате (назначение этого макроса будет объяснено далее)
    #if defined _ALS_CallbackFunc
        #undef CallbackFunc    

    // если же функция ещё не была перехвачена,
    // дадим знать о её перехвате, объявив макрос "_ALS_CallbackFunc"
    #else
        #define    _ALS_CallbackFunc
    #endif

    // сделаем макрос "CallbackFunc", чтобы заменить название перехватываемой
    // функции CallbackFunc на Libname__CallbackFunc во время её объявления в основном скрипте
    #define CallbackFunc LibName__CallbackFunc

    // отныне перехватываемая функция будет называться не "CallbackFunc", а "LibName__CallbackFunc",
    // а название "CallbackFunc" достанется функции-перехватчику;
    // остаётся лишь сделать для перехваченной функции опережающее объявление
    // с её новым названием, чтобы избежать лишних предупреждений от компилятора
    #if defined LibName__CallbackFunc
    forward LibName__CallbackFunc(param1param2);
    #endif 


    Перехват нативных функций производится похожим образом, но, в отличие от первого способа, перехватываемая функция не переименовывается, поэтому не требуется делать для неё новое предварительное объявление (forward).
      Открыть/закрыть
    PHP код:
    // функция-перехватчик
    stock LibName__NativeFunc(param1param2)
    {
        
    // место для кода перехвата
        // ...

        // вызов перехватываемой функции, на этот раз без проверки существования функции
        
    return NativeFunc(param1param2);
    }
    #if defined _ALS_NativeFunc
        #undef NativeFunc
    #else
        #define _ALS_NativeFunc
    #endif
    #define NativeFunc LibName__NativeFunc 


    Теперь разберёмся, как всё это выглядит в деле.
    Методы для коллбэков и нативных функций разные, поэтому рассмотрены они будут отдельно.
    1. Перехват коллбэков на примере OnDialogResponse:
      PHP код:
      // массив, в котором записаны ID диалогов ,показанных другим игрокам
      static player_dialog_ids[MAX_PLAYERS] = {-1, ...};

      // перехватчик
      public OnDialogResponse(playeriddialogidresponselistiteminputtext[])
      {
          
      // если игрок посылает сигнал об ответе на диалог,
          // который ему не показывали - это признак использования читов
          // (и да, я знаю, что сейчас этой проблемы в SA:MP нет, это всего лишь пример!)
          
      if(player_dialog_ids[playerid] != dialogid)
              
      Kiсk(playerid);
          
      player_dialog_ids[playerid] = -1;
      #if defined spd__OnDialogResponse
          
      return spd__OnDialogResponse(playeriddialogidresponselistiteminputtext);
      #endif
      }
      #if defined _ALS_OnDialogResponse
          #undef    OnDialogResponse
      #else
          #define    _ALS_OnDialogResponse
      #endif
      #define    OnDialogResponse    spd__OnDialogResponse
      #if defined spd__OnDialogResponse
      forward spd__OnDialogResponse(playeriddialogidresponselistiteminputtext[]);
      #endif 
    2. Перехват нативной функции на примере ShowPlayerDialog:
      PHP код:
      static player_dialog_ids[MAX_PLAYERS] = {-1, ...};

      // перехватчик
      stock spd__ShowPlayerDialog(playeriddialogidtypeheader[], text[], button1[], button2[])
      {
          
      player_dialog_ids[playerid] = dialogid;
          return 
      ShowPlayerDialog(playeriddialogidtypeheadertextbutton1button2);
      }
      #if defined _ALS_ShowPlayerDialog
          #undef    ShowPlayerDialog
      #else
          #define    _ALS_ShowPlayerDialog
      #endif
      #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?
      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 и постите говнокод там.



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


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

  2. 20 пользователя(ей) сказали cпасибо:
    #ball (26.09.2014)$continue$ (25.09.2014)BadPawn (02.05.2016)DeimoS (25.09.2014)Edwin (12.04.2016)Elaid (27.09.2014)Glant (01.08.2016)Guldan (27.11.2016)Londlem (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)untonyst (23.03.2015)vovandolg (08.03.2016)
  3. #2
    Аватар для Nurick
    Пользователь

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

  4. #3
    Аватар для Daniel_Cortez
    new fuck_logic[0] = EOS;

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    1,652
    Репутация:
    2144 ±
    Добавил в начало статьи объяснение сути перехватов, дабы больше не было вопросов.
    Вторую часть выложу на следующей неделе, как только доделаю пару-тройку примеров.
    Индивидуально в PM и Skype по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).
    SA-MP 0.4 is a lie

  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,438
    Репутация:
    261 ±
    Цитата Сообщение от 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
    new fuck_logic[0] = EOS;

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    1,652
    Репутация:
    2144 ±
    Цитата Сообщение от 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-ю часть урока и оставляли комментарии там.
    Индивидуально в PM и Skype по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).
    SA-MP 0.4 is a lie

  10. #8
    Аватар для Daniel_Cortez
    new fuck_logic[0] = EOS;

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    1,652
    Репутация:
    2144 ±
    Обновил 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 и постите говнокод там.
    Индивидуально в PM и Skype по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).
    SA-MP 0.4 is a lie

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

 

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

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

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

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

Ваши права

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