Что такое extra mirror

Обновлено: 05.07.2024

ExtraMirror - cистема защиты клиента Counter-Strike 1.6 (теперь сервера не смогут навредить вашей контре).

Установка: Переместить в папку Half-Life

P.S. ВАЖНО! Не работает с кастовыми шейдерами, работает на non-steam версию игры.


Актуальная версия: 2.95
Автор плагина: shel

[+] Новая система фильтрации
[+] Новый способ обработки плохих комманд*(Спасибо за пинок Контеру конечно же, благодаря ему была найдена брешь и исправлена)
[+] Песочница кваров
[+] Возможность установки спарсенного модулем тикета
[+] Возможность получить список команд(dump_cmd)

Исправлен запуск на нонстим билдах. (нет офф поддержки работы на них).
Квар sid_random заменен на steamid

steamid:
0 - Стандартный стим ид
1 - Рандом RevEmu
2 - Steam Ticket


Возможные типы: BAD - Отсылает Bad cvar request при запросе квара сервером.
FAKE - Отсылает заранее заданное значение квара.
SERVERSIDE- Отсылает заранее заданное значение первый раз, а последующие запросы отсылает измененные сервером значения(если таковы были), обход всяческих анти протекторов. (Некоторые ФПС детекторы не обойдет) Тикет задается командой set_ticket "Путь"
К примеру:

С помощью тикет коллектора вы сможете играть под видом Реального стим игрока

1. Настройка окружения

Для статьи будем использовать Unity 2020.3.0f1 и Mirror 32.1.4. Добавляем Mirror себе через Asset Store, создаем проект, импортируем Mirror (Window -> Package Manager -> Packages -> My Assets -> Mirror -> Import).

Для начала нам нужно создать префаб игрока. Создаем пустой GameObject (назовем его Player), вешаем на него SpriteRenderer, задаем sprite Knob и масштабируем чтобы лучше его рассмотреть. Далее создаем скрипт Player.cs и вешаем его на тот же GameObject. Редактируем скрипт следующим образом:

Подробнее про NetworkBehaviour и NetworkIdentity

Компонент NetworkIdentity добавится автоматически при добавлении скрипта, наследуемого от NetworkBehaviour.

В одном GameObject (и всех его потомках) может быть только один NetworkIdentity.

NetworkIdentity позволяет отличить один сетевой объект от другого (для этого используем netId - его значение всегда будет уникальным).

Добавляем компонент NetworkTransform, чтобы положение нашего игрока синхронизировалось между всеми игроками. Ставим галочку ClientAuthority, чтобы изменения произведенные клиентом, считались валидными.

Подробнее про NetworkTransform

Компонент NetworkIdentity также добавится автоматически при добавлении NetworkTransform (если его еще не было).

Если вам нужно синхронизировать потомков, добавляйте NetworkTransformChild на тот же объект, где уже есть NetworkIdentity, и указывайте в Target тот transform, который нужно синхронизировать.

Делаем из нашего GameObject префаб. Получилось что-то такое:


Далее создаем скрипт NetMan.cs, создаем пустой GameObject (назовем его NetMan) и вешаем на него скрипт. Это будет наш скрипт, который отвечает за старт сервера и подключение игроков.

Пока просто наследуем класс от NetworkManager, на этом этапе этого будет достаточно.

У нас в инспекторе появятся настройки сервера и добавится компонент KcpTransport. Докидываем на тот же GameObject компонент NetworkManagerHUD (он создает необходимое для подключения GUI).

Остановимся подробнее на настройках:

Don’t Destroy On Load. Будет ли объект существовать между сценами?

Run In Background. Будет ли компонент продолжать работать когда окно программы неактивно?

Auto Start Server Build. Будет ли сервер стартовать автоматически, если была выбрана опция билда «Server Build»?

Show Debug Messages. По этой опции не удалось разобраться или найти какую-то информацию.

Server Tick Rate. Количество обновлений сервера в секунду.

Server Batch Interval. Чем выше это значение, тем реже будет отправляться сетевая информация.

Теперь нам нужно указать префаб, который будет спавниться в качестве игрока. Перетаскиваем префаб Player в поле Player Prefab и после этого убираем его со сцены (оставляем только камеру и NetMan).


Первый этап готов. Выставляем выполнение в неполном экране (чтобы несколько экземпляров помещалось), делаем сборку, запускаем 2 экземпляра и проверяем. Один экземпляр стартуем как сервер, второй как клиент. На wasd двигаем своего персонажа, он успешно синхронизируется с другим экземпляром.

4. Синхронизация переменных посредством SyncList

Синхронизировать одну переменную это конечно хорошо, но для серьезных проектов нам понадобится инструмент посерьезнее. SyncList позволяет синхронизировать массивы данных. Разберемся с ним на примере сохранения пройденного пути по нажатию кнопки (просто для наглядности). Редактируем скрипт Player.cs по аналогии с SyncVar.

Изменение массива на сервере:

Команда для запроса с клиента на сервер:

И обработчик события изменения массива на клиенте:

Теперь перегрузим метод старта клиента:

Синхронизация готова, но нам нужно задать условия изменения массива и визуализировать данные. Создадим пустой GameObject + SpriteRenderer + Knob + меняем цвет. Сохраняем как префаб Point.

Добавим компонент LineRenderer на префаб Player, выставим ему ноль позиций и немного уменьшим ширину. Отредактируем скрипт Player.cs:

Как будут выглядеть Player и Point




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

Fastestmirror

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

В dnf тоже используется fastestmirror из набора библиотек RPM, но там это решено через libcurl и в целом всё сложнее устроено.

На практике получились следующие результаты первого места на недельном опросе через каждые 10 минут. Цифры — количество раз сколько узел выигрывал при сравнении, для fastestmirror и fping :

Видно что выбор не прямо однозначный, но тут скорее всего играет роль близость точки с которой я делал опрос — до нескольких зеркал, разница меньше миллисекунды. Второй момент, IPv6 список отличается и он хуже — ближайший IPv6 узел дальше чем ближайший IPv4 и выбор меньше. С учётом приоритета IPv6 получается не очень. Результаты fping лаконичнее, меньше выигрывавших узлов, но в целом одинаковы с fastestmirror .

5. Spawn предмета и взаимодействие с ним

На последнем этапе мы посмотрим как спавнить предметы и взаимодействовать с ними. Добавим нашему игроку возможность стрелять пулями.

Создадим новый скрипт Bullet.cs:

Также создадим пустой GameObject + SpriteRenderer + Knob + меняем цвет. Вешаем на него скрипт Bullet.cs. Добавляем компонент NetworkTransform. Сохраняем как префаб Bullet.

В скрипт Player.cs добавляем спавн пули на сервере:

И запрос на свапн со стороны клиента:

Выставляем условие появления пули:

Добавим еще уничтожение игрока, если жизни закончились:

В настройках NetMan выставляем префаб Bullet как доступный для спавна:


Не забываем выставить префаб Bullet в переменную BulletPrefab префаба Player. Напоследок добавляем на префаб Player компонент CircleCollider2D и ставим галочку IsTrigger, чтобы пуля могла отловить попадание.

Последний этап завершен. Проверяем. По нажатию правой кнопки мыши из игрока вылетает пуля и летит туда, где стоял курсор. Если по пути пуля встречает другого игрока – он теряет одну жизнь. Все пули синхронизированы, даже если игрок подключился после их спавна.

2. NetworkMessage и spawn игрока в выбранной точке

В настройках NetMan убираем галочку AutoCreatePlayer, дальше мы будем контролировать спавн игрока сами. Для этого мы изменим скрипт NetMan.cs. Начнем с создания struct с данными о позиции:

Далее создадим метод непосредственно спавна, который будет выполняется только на сервере:

Создадим метод, который будет активировать спавн (и выполняться локально на клиенте):

И напоследок зададим условия для активации спавна:

В итоге получаем такой скрипт NetMan.cs:

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

Список зеркал

Известно, что список репозиториев задаётся в файлах в каталоге /etc/yum.repos.d/ если не указано иное. Вот так выглядят настройки репозитория Base в файле /etc/yum.repos.d/Centos-Base.repo :


Здесь видно две опции которые задают место откуда можно получать данные. Первая baseurl непосредственно указывает зеркало, никакого выбора тут не нужно. Вторая mirrorlist указывает ссылку по которой будет возвращён список из 10 зеркал из которых и будет делаться выбор, именно эта опция активна. Также мы видим несколько переменных внутри ссылки, все они в конечном итоге отражают конкретное место в дереве каталогов репозитория:

И всё это вместе соответствует иерархии каталогов непосредственно на сервере.

Реализация

Проверка осуществляется по списку в следующем порядке (цитирую из кода):


Пункты 1 и 2 работают или-или, чтобы проверить не только код страны, но и код штата. По сути происходит попытка выбрать ближайшие географически серверы. Для каждого шага выполняется запрос из базы данных, например:


Потом идёт проверка живости зеркал по этому списку путём сравнения хешей, как написано выше. Если набирается 10 живых, то на этом требуемая задача выполнена, работа завершена. Если не набирается то переходим к следующему шагу чтобы добрать общий список до 10 или пока не исчерпаем все варианты. Результат сохраняется в файле и отдаётся на откуп фронтенд части представленной Python скриптом ml.py, задача которого выбрать и отдать нужный файл, предварительно выяснив страну источника запроса по IP адресу. Также учитывается и тип протокола IPv4 или IPv6, для которых формируются разные списки.

Вернёмся к запросу в котором используется ORDER BY RAND() , означает ли это что список будет случайным? Да, насколько случайна само реализация в СУБД, но это касается только порядка внутри каждого шага. То есть, если для конкретной страны набирается больше 10 зеркал нужного типа репозитория и архитектуры (для России всего 17), то в итоге каждый раз мы будем получать перетасованный список из 10 разных зеркал в одной стране. Если их меньше, то вверху списка всегда будут перетасованные репозитории из одной страны, дальше перетасованные репозитории из ближайших стран и так далее по шагам. В итоге получаем не совсем случайный список состоящий из нескольких случайных частей. Это имеет значение когда рабочих зеркал внутри одной страны не так много, например IPv6 зеркал в России всего 7 и они всегда будут вверху списка:

Ещё хуже дело обстоит с архитектурой i386 это альтернативная архитектура для Centos 7 и отдельная система зеркал. В России всего один такой сервер который поддерживает альтернативные архитектуры, он всегда будет на первом месте:

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

Сканирование репозиториев происходит постоянно и не учитывая механизмы кеширования результат обновляется примерно раз в 3 часа. Цитата из mirrorlist_crawler_deployment_notes.txt:

- A full scan of all repos and all versions without any cached data is going to take close to 3 hours, but typically altarch is <10min, C6 <25min, C7 <25min with all the repos enabled

mirrors list updates count

altmirrors list updates count

Можно ожидать что каждые 15 минут у нас будет новый список, за 30 минут какие-то изменения в нём произойдут обязательно. Но! Помним что чем меньше активных зеркал тем менее случаен порядок и на первом месте сейчас в России всегда одно и то же зеркало.

Про зеркала репозиториев Centos и выбор лучшего из них

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

yum repolist

Технически это rsync , обычно по расписанию. Кто-то для этого предоставляет готовый набор скриптов, как Fedora, а кто-то просто говорит что надо синхронизироваться вот с этого сервера и рекомендуемый набор параметров. Самый затратный ресурс это место, мы недавно добрались до 4 терабайт и это дорого в нашем случае для того что не генерирует никакой прибыли. Взамен мы получили локальную доступность используемых нами дистрибутивов, это позволило упростить первоначальную настройку серверов исключив из неё обязательный доступ к Интернет. А ещё конечно мы рады что приобщились к чем-то большому, даже если наше участие в этом не сильно заметно.

Наше зеркало публичное, с него могут получать обновления все желающие, что собственно и происходит если судить по статистике обращений. В основном это Россия, но не только. Про то как так получается и как вообще происходит выбор конкретного сервера для обновлений на примере Centos седьмой версии этот пост.

Будем говорить о пакетном менеджере yum с установленным по умолчанию плагином fastestmirror и нас будет интересовать только процесс выбора конкретного зеркала.

3. Синхронизация переменных посредством SyncVar

Переходим к очень интересной фиче – SyncVar. Она позволяет избежать ручной синхронизации данных. Главное правило – меняем переменную только на сервере и не используем ее как данные (только как временное хранилище для данных, которые нам нужно обработать).

Для начала подготовим объекты, которые мы будем использовать для наглядной синхронизации. Например, здоровье в виде красных кружков. Открываем редактирование префаба Player и добавляем ему несколько объектов, представляющих собой жизнь (Knob + красный цвет). Располагаем их так, чтобы было хорошо видно.

Редактируем скрипт Player.cs, добавляем переменные:

Сохраняем, закидываем объекты-жизни в переменную HealthGos и выставляем такое же количество в переменной Health.


Добавляем в Update обновление объектов-жизней в соответствии с количеством жизней:

И переходим к методу на клиенте, который будет выставлять Health в соответствии с синхронизированным значением:

Теперь нам нужно сделать метод, который будет менять переменную _SyncHealth. Этот метод будет выполняться только на сервере.

Далее переходим к методу, который также будет выполняться на сервере, но клиент сможет запросить его выполнение:

Подробнее про Command и Rpc

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

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

Command можно вызывать на сервере+клиенте, но Rpc нельзя вызывать на клиенте.

Передавать в Rpc и Command можно только ограниченный набор типов.

Вызов Rpc в режиме сервер+клиент также выполнится на нем самом.

Все готово для синхронизации, зададим условия изменения жизней. На этом этапе сделаем простую схему – каждый игрок может только уменьшить свои жизни. Для этого дополним Update:

Этап завершен. Теперь у игроков всегда будет актуальное количество жизней, даже у тех, кто присоединяется позднее (после изменения количества жизней у других игроков).

RIPE Atlas

mirrors and probes map


Пробники (синие маркеры) и зеркала (красные маркеры) распределены слишком неравномерно и тяготеют к столицам, поэтому результаты не стоит воспринимать как что-то значимое. Сырые данные доступны начиная с измерения номер 23159879 по 23159901, при желании их можно проанализировать более строго. Обобщённый итог количество первых мест для IPv4:

Лучшие результаты в районе одной или нескольких миллисекунд, а самый худший результат по отклику в Салехарде, пробник 22767:

Основы Unity + Mirror


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

Итак, для написания сетевой игры на Unity сейчас есть несколько вариантов:

UNet. Устаревшая сетевая технология. На данный момент deprecated и поддержка закончится в ближайшие пару лет. Но что же Unity предлагает взамен?

NetCode. Потенциально крутая технология, которая будет работать в связке с Entity Component System. Но очень уж медленно она развивается, за пару лет существования вышло 6 версий разной степени багованности, api постоянно меняется и делать что-то серьезное на нем пока рановато. Когда ее доделают – неизвестно. Я слежу за ней уже около года и особого прогресса не заметил.

Что тогда остается? Из бесплатных решений это:

MLAPI. Альтернатива UNet с широким спектром возможностей. Достойное решение, стоит к нему присмотреться.

Mirror. Доведенный до ума UNet, который потенциально может использоваться даже в MMO. Может работать как Клиент+Сервер, так и NoGUI-Сервер.

И платные решения (ознакомится с ними не удалось, напишите у кого был опыт как они):

Таблица преимуществ этих решений от Unity:


Мой выбор пал на Mirror, как на ближайший потомок UNet, использующий большинство принципов уже знакомого UNet. На примере простого проекта мы посмотрим основы Mirror, а именно:

NetworkMessage и spawn игрока в выбранной точке

Синхронизация переменных посредством SyncVar

Синхронизация переменных посредством SyncList

Spawn предмета и взаимодействие с предметом

Заключение

Надеюсь эти примеры помогут разобраться с азами работы с сетью в Unity. Знатоков этой темы призываю к обсуждению недочетов (про производительность и GC сейчас речь не идет). Полный проект можно скачать на гитхабе по этой ссылке.

Послесловие

mirrors.powernet.com.ru data traffic

mirrors.powernet.com.ru bandwidth traffic

При этом доля этого трафика изнутри нашей сети ничтожна.

Вот так на нём заканчивается место:

mirrors.powernet.com.ru disk

Хорошо различимые ступеньки — моменты когда мы добавляем новый дистрибутив. Когда всё начиналось это был VirtualBox работающий под Windows на офисном компьютере на котором хранились видеоархивы нашего рекламного отдела. Потом мы перебрались на боевую систему виртуализации и стало чуть полегче:

image

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

Читайте также: