среда, 6 июня 2018 г.

MikroTik SIP Helper и зависшие регистрации

Как это обычно бывает, ты тратишь время на поиск истины в RFC, зарывшись в дампах трафика и исходников протокола, а кто-то за тебя это уже сделал. Ну что же, будет мне уроком. Все тоже самое, что тут, но на видео и с дополнительными картинками - https://www.youtube.com/watch?v=Anu6ZYg25yM

--------------------------------------------------------------------------------------------------------------------------
Пришло время расставить точки над И, и пояснить почему sip helper - это хорошо, и надо знать, когда его нужно отключать, а когда нет. А так же разобрать причины, по которым на MikroTik зависают сессии регистрации, в некоторых случаях падения канала связи.

Прежде всего давайте разберем основы SIP протокола и установки связи на примере Asterisk.
SIP - это транспортный протокол для SDP, и вот уже SDP определяет куда надо отвечать второму абоненту.

Давайте посмотрим на схему ниже:




На Asterisk NAT выключен, SIP Helper на микротике выключен. На микротике включен SRC-NAT masquerade, и DST-NAT TCP 5060, UDP 10000-20000 в сторону телефона.

SIP телефон успешно зарегистрировался на сервере, микротик создал трансляции ната, и держит соединения в connection tracking.

С номера 100 (192.168.1.100) совершают звонок на номер 200.

В SIP пакете, в поле Via будет стоять адрес 192.168.1.100 и порт 5060, а вот в IP - UDP/TCP заголовках благодаря SRC-NAT у нас будет стоять внешний IP адрес  - 1.1.1.1, и порт к примеру 5066
Согласно спецификации SIP протокола, он должен послать ответ на адрес и порт, указанные в поле Via. Получается, что телефон с номером 200 пошлет ответ на 192.168.1.100 5060, и естественно звонок не состоится.

Варианты решения данной проблемы:
1. Включаем на телефоне с номером 100 rport (RFC 3581), теперь в поле Via будет стоять метка rport, это сообщает серверу, что следует отвечать на порт из UDP/TCP заголовков, а не на значение порта поля Via. Если на телефоне включить rport нельзя, то следует на сервере Asterisk установить значение nat=force_rport. Хорошо с портом разобрались, а как быть с адресом, он ведь по прежнему будет его брать из поля Via. Для этого на Asterisk включается nat=comedia, или nat=force_rport,comedia (если на телефоне rport включить нельзя) таким образом сервер будет брать адрес из заголовков UDP/TCP. Ну и разумеется нам надо настроить DST-NAT на микротике на порты 5060 для SIP и 10000-20000 для голоса.

2. Включаем SIP Helper на микротике. Теперь когда телефон с номером 100 решит позвонить на 200, sip helper изменит ip и порт в поле Via на значения из IP - UDP/TCP заголовков. Так же он изменит значения полей SDP протокола, а именно owner, connection information на значения из IP - UDP/TCP заголовков. И по необходимости порт для принятия медиа потока, если таковой занят в таблице nat трансляций.  И все! Не надо DST-NAT, не надо трогать настройки nat в Asterisk.

Важный момент: SIP Helper работает только с SIP на порту 5060, он разумеется не сможет работать с SIP TLS, так как шифруется на оконечных устройствах. И он НИКАК не влияет на RTP!



SIP Helper + SIP Direct Media
Данная настройка позволит гонять напрямую медиа поток в следующей схеме:

При такой схеме и работающем SIP Helper мы получаем картину, при которой наш микротик получит пакеты, где адрес назначения будет одинаковый. Если включен SIP Direct Media - то он даст возможность телефонам напрямую общаться. Если выключен, то подобные пакеты будут просто дропаться.




Рассмотрим теперь проблему зависающих сессий.
Рассмотрим схему ниже:

















Теперь у нас есть туннель между нашими роутерами. Классическая схема основной офис - филиал.
Телефон 200 регистрируется на сервере 192.168.1.10
Никакого NAT, SIP-HELPER.
И тут падает интернет канал.. Первым делом естественно падает туннель. И если оно проваляется минут 5, то TCP сессия оборвется по таймауту. Телефон начнет слать пакеты на регистрацию, и они улетают по маршруту по умолчанию, который смотрит в с интерфейс провайдера. Создается запись в connection-tracking. И как только туннель поднимается обратно у вас ничего не работает, потому что соединение уже есть. Да нерабочее, но есть.

И тут несколько вариантов решения:
1. (Плохой вариант) Чистим все соединения с портом 5060 при падении туннеля.
2. (Хороший вариант) Делаем blackhole маршрут до 192.168.1.0/24 с метрикой больше, чем у оригинального маршрута до 192.168.1.0/24. Таким образом, если туннель упадет, blackhole маршрут станет приоритетным для данной подсети, соединение не поднимется, так как роутер будет дропать пакеты по этому маршруту до того, как для них создастся запись NAT трансляции в connection tracking!

вторник, 5 июня 2018 г.

RocketChat как мессенджер корпоративной сети

В связи с удорожанием доллара, экономического (очередного) кризиса в стране - все стремятся экономить. Наша организация тоже.

По сей день мы пользовались программой от Microsoft: Lync, которая ныне Skype for buisness.
И в принципе все хорошо, свой функционал она выполняет. Вот только каждая учетная запись заведенная на этом сервере - это CAL лицензия, которая стоит денег крайне не кислых, особенно, когда в организации порядка 5000 человек. Исходя из всего вышесказанного было принято решение искать замену.

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

И тут я натыкаюсь на бесплатный аналог Slack - RocketChat. Поднимаю быстренько сервер, начинаю смотреть, ковырять. Есть интеграция с LDAP - супер! Прикручиваю, логинюсь и первые грабли...

Из LDAP каталога нашего домена для каждого пользователя должны существовать и быть заполненны 2 поля: sAMAccountName, email. У части пользователей email не заполнен. К счастью разработчики предусмотрели подобный сценарий, можно указать домен по умолчанию в настройках интеграции LDAP. И тогда у пользователя будет учетка вида login@домен_по_умолчанию. Я указал rocket.chat. Все, первая проблема решена. Пользователи смогли зайти.
Но тут снова проблема для имени в чате пользователя берется параметр cname из LDAP каталога. Но по какой-то причине он отображается в транслитерации на английский язык и с точками, как разделитель. То есть sizov.sergey.viktorovich вместо Сизов Сергей Викторович. Что сами понимаете не очень... Я потратил несколько дней, но нашел решение проблемы:

Заходим в администрирование:
1. Внешний вид - Пользовательский интерфейс - Использовать настоящее имя (включаем)
2. Общие настройки - UTF-8 - приводим к таком виду: [0-9a-zA-Z-_.а-яА-Я]+
3. LDAP - Sync / Import - Синхронизация пользовательских данных (включаем)

Вот эти пункты решили проблемы с отображением имени.

Обязательно отключаем возможность регистрации и восстановления пароля для пользователей. Иначе поверьте мне, они наплодят локальных учеток, а нам это не надо, так как у нас LDAP! =)

В целом чат хороший, функционал, внешний вид, все есть.
Из минусов:
1. Непривычный пользователям интерфейс (не классический стиль мессенджера, аля аська, жаббер, lync)
2. Пока что не сделали Kerberos аунтефикацию, следовательно не получится пользователей пускать прозрачно, надо вводить логин и пароль доменный. Но в Roadmap у них это есть, так что ждемс..

Хреновые провайдеры или MikroTik спасет мир

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

Чаще всего доступ дают через VPN, но не редки случаи, когда доступ предоставляется тупо через серую подсеть...То есть вам по DHCP выдают некий серый адрес, и далее вас NATят.
Про предоставление объединений я вообще молчу...Для некоторых L3VPN, VRF, или на худой конец VLAN - это что-то инопланетное.
Но в прочем статья не об этом. Не так давно столкнулся с интересной ситуацией. У меня на каждом объекте стоит микротик, который поднимает туннель до головного офиса и проблем, как правило не возникает. Не так давно с одного из объектов начали жаловаться. Мол беда беда, интернет есть, сервисы организации не работают. Начинаю смотреть, пинги бегают внутри туннеля без потерь, задержки вполне себе хорошие, порядка 80 мс. Но при попытке открыть корпоративный портал или иной сервис действительно вижу затык...Подсказал мне браузер, при открытии страницы нашего сервиса, он мгновенно прогрузил заголовки, я увидел <title> страницы. А вот содержимого нет...И тут меня озарило, конечно же MTU. Дабы не ломать туннель путем изменения MTU, я решил пойти другим путем - а именно поправить MSS налету. В современных прошивках MSS сразу выставляется под MTU интерфейса. Но мы то не пролезаем, в связи с чем я добавил новые правила:

/ip firewall mangle add chain=forward protocol=tcp tcp-flags=syn tcp-mss=1361-65535 action=change-mss new-mss=1360 disabled=no out-interface=my-tunnel-ppp

/ip firewall mangle add chain=forward protocol=tcp tcp-flags=syn tcp-mss=1361-65535 action=change-mss new-mss=1360 disabled=no in-interface=my-tunnel-ppp

Таким образом я зажимаю все данные (кроме заголовков) TCP пакетов в 1360 байт. Почему именно это значение? Опытным путем, понижал с 1390 до данного значения, пока не стабилизировалась работа. И это решило проблему. Убедившись, что странички наших сервисов и приложения стали работать корректно, я понизил MTU на туннельном интерфейсе, он переподнялся, и правила MSS отключил. Все работает. Все счастливы.


четверг, 6 августа 2015 г.

Mikrotik VRF, PBR, или как обеспечить удаленный доступ при совпадении подсетей.


Итак, есть задача, обеспечить доступ в 1с через VPN в удаленном филиале.
 
Задача привычная, плевая, настроил Mikrotik 951, поставил, profit!
Но появилась первая проблема после приезда в филиал - у нас совпадают подсети.
Получается такая картина:
Филиал - 192.168.8.0/24, Наш сервер терминалов 1с - 192.168.8.1

С этим я уже сталкивался, решается просто, создаем иллюзию того, что обращаться надо на хост другой подсети, например 192.168.9.1. (Разумеется по возможности мы стараемся привести в порядок сети, но не всегда это возможно сделать быстро и без потерь. Плюс политические моменты.)
Следовательно обычный dst-nat.
И вот тут то и вылезла новая проблема, уж я никак не ожидал, что где то в забытом богом месте, на заводе будет целая инфраструктура на ОС Windows.
Куча виртуалок на HYPER-V, SCVMM, DFS и тому подобное.

В частности шлюз TMG Forefront. А о нем я слышал только от коллег и в основном нецензурную брань, и это люди с сертификатами от Microsoft...
Оказалось, чтобы прописать маршрут на нем необходимо ему обеспечить ICMP доступность хоста. 
Не привычно, но ладно.
Плюс к этому, адрес у него оказался 192.168.8.1, прямо как у нашего терминального сервера, надо вешать PBR(Police Based Routing) иначе трафик будет бегать петлей обратно в локалку филиала.

И вот тут то потенциал железки за какие то 3-4 т.р показал себя в полную мощь!
VRF(Virtual Routing Forwarding) и PBR наше все! Вопрос решился довольно просто, я добавил pptp интерфейс в VRF, назовем ее vpn-vrf, туда же зарулил все маршруты пришедшие по OSPF.
Повесил правила в mangle, с целью вешать в prerouting'е routing-mark vpn-vrf на пакеты их локальной сети, а ответные пакеты заруливать в main routing table.

Таким образом пакет из их локальной сети сначала перемещался в vpn-vrf, потом отрабатывал dst-nat и вуаля, пакет убегает предварительно обработанный src-nat(mascarading) в туннель.
Обратно пакет от нашего терминально сервера добегает до микротика, отрабатывает src-nat(mascarading), далее еще один src-nat с целью подменить реальный адрес нашей терминалки на вымышленный, для корректной маршрутизации, и до хоста.

Все проблемы я отлавливал Wiresharkом, благо в mikrotik можно очень быстро настроить packet-sniffer и направить выхлоп на нужный хост.
Надо признаться что это была более менее интересная задача за последнее время.
И я в очередной раз понял насколько сильно люблю Микротики. Такой функционал, за такие смешные деньги!

среда, 27 мая 2015 г.

Проброс DLNA в удаленную сеть

Предистория:

Итак, появилась необходимость дать возможность просматривать фильмы с моего сервера на телевизоре. Ну казалось бы, поднимаем DLNA, например miniDLNA и проблема решена. Так и было, пока не появилась нужда дать такую же возможность родителям, которые живут в другом месте, и ставить им там сервер или простенький nas не совсем правильный путь. Было принято решение объединить наши сети путем туннелирования траффика и дать доступ к моей фильмотеке.

Подготовка:

У родителей я уже давно поставил отличный роутер, с которым я давно работаю, и доверяю - Mikrotik 951Ui 12HnD. Кто не знаком с этим великолепным маршрутизатором, советую познакомиться. Ценовая политика позволяет подобрать решения как  для дома, так и для офиса. При этом получаем функционал, как у дорогих enterprise решений.
У меня в квартире так же стоял Mikrotik, лишь с одним отличием, у меня были все порты гигабитные. Я не долго думая поднял pptp туннель и тут началось...

Первые проблемы:

В отличии от классического способа передачи потокового аудио и видео сегмента данных DLNA несколько отличается. И это сразу стало понятно, после того как я посниффил траффик. Через PPTP некоторые запросы пробегали, некоторые нет. После того как я изучил дамп траффика, пришел к следующим выводам:
  1. Со стороны сервера мы должны увеличить ttl траффика от DLNA сервера, я увеличил на 100.
  2. Установить пакет multicast на микротики, и включить PIM на интерфейсы туннеля.
  3. Не забываем прописать маршруты до локальных сетей за туннелями.
  4. Со стороны сервера DLNA прописываем маршрут 239.255.255.250 в качестве шлюза указываем туннельный интерфейс.
Казалось бы, все предусмотрел, я на телевизоре родителей увидел свой DLNA сервер, подключился к нему, открыл фильм, и тут я успел увидеть 2 кадра и все. Он просто отключился от сервера. Я начал заново, пробежался по всей конфигурации на обоих маршрутизаторах, грешил на фаервол, на все. Потом опять взглянул на дамп траффика и увидел то самое.."don't fragment". И тут меня осенило! Размер пакета превышает MTU, который нам предоставляет PPTP, а фрагментировать нельзя! К сожалению в настройках miniDLNA сервера я не смог найти возможность ограничить длину пакета.

Победное решение:

В итоге самый простой IP-IP туннель, но сталкиваемся с проблемой динамической адресации от провайдера, если у вас на обоих концах статика, вам повезло!

Еще можно попробовать ограничить mtu на интерфейсе, к которому подключен NAS. В таком случае пакеты будут заведомо с небольшим MTU, который пролезет в любой туннель.


Дополнение:

Не стоит забывать что просмотр фильмов - это нехилая нагрузка на сеть в плане ширины канала. Когда в пределах локальной сети, не страшно, а вот между сетями, когда ширину канала контролирует провайдер...В общем всем советую включить QoS, отдать приоритет своему серверу, и любым подключениям к нему, а мы можем и подождать дополнительных 10 мс для открытия странички =)
Если кого интересуют подробности, пишите, помогу с настройкой, поделюсь конфигами.

суббота, 20 сентября 2014 г.

Space Engineers документация по API

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

И так доступ к игровому миру и ее механике мы получаем через класс MyAPIGateway.
Который в себе содержит следующие  интерфейсы:

         IMyCubeBuilder CubeBuilder;
         IMyEntities Entities;
         IMyMultiplayer Multiplayer;
         IMyPlayerCollection Players;
         IMySession Session;
         IMyTerminalActionsHelper TerminalActionsHelper;
         IMyUtilities Utilities;

По скольку все они объявлены через модификатор static нам не нужно создавать экземпляр данного класса, мы просто будем обращаться напрямую.

Рассмотрим каждый.

1) CubeBuilder - содержит методы и объекты для управления процессов создания блоков.

 public interface IMyCubeBuilder
    {
        bool BlockCreationIsActivated { get; }
        bool CopyPasteIsActivated { get; }
        bool FreezeGizmo { get; set; }
        bool IsActivated { get; }
        bool ShipCreationIsActivated { get; }
        bool ShowRemoveGizmo { get; set; }
        bool UseSymmetry { get; set; }
        bool UseTransparency { get; set; }

        void Activate();
        void ActivateShipCreationClipboard(MyObjectBuilder_CubeGrid grid, VRageMath.Vector3 centerDeltaDirection, float dragVectorLength);
        void ActivateShipCreationClipboard(MyObjectBuilder_CubeGrid[] grids, VRageMath.Vector3 centerDeltaDirection, float dragVectorLength);
        bool AddConstruction(IMyEntity buildingEntity);
        void Deactivate();
        void DeactivateBlockCreation();
        void DeactivateCopyPaste();
        void DeactivateShipCreationClipboard();
        IMyCubeGrid FindClosestGrid();
        void StartNewGridPlacement(MyCubeSize cubeSize, bool isStatic);
    }

2) Entities - этот интерфейс я думаю будет интересней всего для создателей модов. Он содержит в себе методы для создания и управления всеми подконтрольными нам сущностями игрового мира, сенсоры, двигатели, орудия, кнопки и т.д.

    public interface IMyEntities
    {
        event Action OnCloseAll;
        event Action<IMyEntity, string, string> OnEntityNameSet;
        event Action<IMyEntity> OnEntityRemove;

        void AddEntity(IMyEntity entity, bool insertIntoScene = true);
        IMyEntity CreateFromObjectBuilder(MyObjectBuilder_EntityBase objectBuilder);
        IMyEntity CreateFromObjectBuilderAndAdd(MyObjectBuilder_EntityBase objectBuilder);
        IMyEntity CreateFromObjectBuilderNoinit(MyObjectBuilder_EntityBase objectBuilder);
        void EnableEntityBoundingBoxDraw(IMyEntity entity, bool enable, VRageMath.Vector4? color = null, float lineWidth = 0.01f, VRageMath.Vector3? inflateAmount = null);
        bool EntityExists(string name);
        bool Exist(IMyEntity entity);
        bool ExistsById(long entityId);
        VRageMath.Vector3? FindFreePlace(VRageMath.Vector3 basePos, float radius, int maxTestCount = 20, int testsPerDistance = 5, float stepSize = 1f);
        List<IMyEntity> GetElementsInBox(ref VRageMath.BoundingBox boundingBox);
        void GetEntities(HashSet<IMyEntity> entities, Func<IMyEntity, bool> collect = null);
        List<IMyEntity> GetEntitiesInAABB(ref VRageMath.BoundingBox boundingBox);
        List<IMyEntity> GetEntitiesInSphere(ref VRageMath.BoundingSphere boundingSphere);
        IMyEntity GetEntity(Func<IMyEntity, bool> match);
        IMyEntity GetEntityById(long entityId);
        IMyEntity GetEntityByName(string name);
        void GetInflatedPlayerBoundingBox(ref VRageMath.BoundingBox playerBox, float inflation);
        IMyEntity GetIntersectionWithSphere(ref VRageMath.BoundingSphere sphere);
        IMyEntity GetIntersectionWithSphere(ref VRageMath.BoundingSphere sphere, IMyEntity ignoreEntity0, IMyEntity ignoreEntity1);
        List<IMyEntity> GetIntersectionWithSphere(ref VRageMath.BoundingSphere sphere, IMyEntity ignoreEntity0, IMyEntity ignoreEntity1, bool ignoreVoxelMaps, bool volumetricTest);
        IMyEntity GetIntersectionWithSphere(ref VRageMath.BoundingSphere sphere, IMyEntity ignoreEntity0, IMyEntity ignoreEntity1, bool ignoreVoxelMaps, bool volumetricTest, bool excludeEntitiesWithDisabledPhysics = false, bool ignoreFloatingObjects = true, bool ignoreHandWeapons = true);
        bool IsInsideVoxel(VRageMath.Vector3 pos, VRageMath.Vector3 hintPosition, out VRageMath.Vector3 lastOutsidePos);
        bool IsInsideWorld(VRageMath.Vector3 pos);
        bool IsNameExists(IMyEntity entity, string name);
        bool IsRaycastBlocked(VRageMath.Vector3 pos, VRageMath.Vector3 target);
        bool IsSelectable(IMyEntity entity);
        bool IsSpherePenetrating(ref VRageMath.BoundingSphere bs);
        bool IsTypeHidden(Type type);
        bool IsTypeSelectable(Type type);
        bool IsVisible(IMyEntity entity);
        bool IsWorldLimited();
        void MarkForClose(IMyEntity entity);
        void RegisterForDraw(IMyEntity entity);
        void RegisterForUpdate(IMyEntity entity);
        void RemapObjectBuilder(MyObjectBuilder_EntityBase objectBuilder);
        void RemapObjectBuilderCollection(IEnumerable<MyObjectBuilder_EntityBase> objectBuilders);
        void RemoveEntity(IMyEntity entity);
        void RemoveFromClosedEntities(IMyEntity entity);
        void RemoveName(IMyEntity entity);
        void SetEntityName(IMyEntity IMyEntity, bool possibleRename = true);
        void SetTypeHidden(Type type, bool hidden);
        void SetTypeSelectable(Type type, bool selectable);
        bool TryGetEntityById(long id, out IMyEntity entity);
        bool TryGetEntityByName(string name, out IMyEntity entity);
        void UnhideAllTypes();
        void UnregisterForDraw(IMyEntity entity);
        void UnregisterForUpdate(IMyEntity entity, bool immediate = false);
        float WorldHalfExtent();
        float WorldSafeHalfExtent();
    }

3) Multiplayer - данный интерфейс предоставляет нам скудные возможности по получению информации о сетевой игре.

    public interface IMyMultiplayer
    {
        bool IsServer { get; }
        bool MultiplayerActive { get; }
        ulong MyId { get; }
        string MyName { get; }
        IMyPlayerCollection Players { get; }
        ulong ServerId { get; }

        bool IsServerPlayer(IMyPlayer player);
    }

4) Players - немаловажный интерфейс, благодаря которому скоро появятся NPC в игре стараниями моддеров. Предоставляет возможность добавлять, убивать, удалять игроков.

     public interface IMyPlayerCollection
    {
        DictionaryReader<long, MyPlayerInfo> AllPlayers { get; }
        int Count { get; }

        IMyPlayer this[ulong steamUserId] { get; }

        void Add(IMyPlayer player);
        long AddNewNpc(string npcName);
        bool Exists(ulong steamUserId);
        void ExtendControl(IMyEntity entityWithControl, IMyEntity entityGettingControl);
        IMyPlayer GetPlayerControllingEntity(IMyEntity entity);
        void GetPlayers(List<IMyPlayer> players, Func<IMyPlayer, bool> collect = null);
        bool HasExtendedControl(IMyEntity firstEntity, IMyEntity secondEntity);
        void KillPlayer(long playerId);
        void ReduceControl(IMyEntity entityWhichKeepsControl, IMyEntity entityWhichLoosesControl);
        void Remove(IMyPlayer player);
        void Remove(ulong steamUserId);
        void RemoveControlledEntity(IMyEntity entity);
        void RevivePlayer(long playerId);
        void SendControlledEntities(ulong sendTo);
        SerializableDictionary<long, ulong> Serialize();
        void SetControlledEntity(ulong steamUserId, IMyEntity entity);
        void TryExtendControl(IMyEntity entityWithControl, IMyEntity entityGettingControl);
        bool TryGetPlayer(ulong steamUserId, out IMyPlayer player);
        bool TryReduceControl(IMyEntity entityWhichKeepsControl, IMyEntity entityWhichLoosesControl);
        bool TrySetControlledEntity(ulong steamUserId, IMyEntity entity);
        void UpdateFromPlayer(long playerId);
        void UpdateFromPlayers();
        void UpdateFromSession(List<MyObjectBuilder_Checkpoint.PlayerItem> playersFromSession);
    }

5) Session - данный интерфейс позволяет работать с текущей игровой сессией и ее параметрами ( творческий ли режим, максимальное кол-во игроков и т.д.)

    public interface IMySession
    {
        float AssemblerEfficiencyMultiplier { get; }
        float AssemblerSpeedMultiplier { get; }
        bool AutoHealing { get; }
        uint AutoSaveInMinutes { get; }
        IMyCameraController CameraController { get; }
        bool CargoShipsEnabled { get; }
        bool ClientCanSave { get; }
        IMyControllableEntity ControlledObject { get; }
        bool CreativeMode { get; }
        string CurrentPath { get; }
        string Description { get; set; }
        TimeSpan ElapsedPlayTime { get; }
        bool EnableCopyPaste { get; }
        MyEnvironmentHostilityEnum EnvironmentHostility { get; }
        DateTime GameDateTime { get; set; }
        float GrinderSpeedMultiplier { get; }
        float HackSpeedMultiplier { get; }
        float InventoryMultiplier { get; }
        bool IsCameraAwaitingEntity { get; set; }
        short MaxFloatingObjects { get; }
        short MaxPlayers { get; }
        bool MultiplayerAlive { get; set; }
        bool MultiplayerDirect { get; set; }
        double MultiplayerLastMsg { get; set; }
        string Name { get; set; }
        float NegativeIntegrityTotal { get; set; }
        MyOnlineModeEnum OnlineMode { get; }
        string Password { get; set; }
        IMyLocalPlayer Player { get; }
        float PositiveIntegrityTotal { get; set; }
        float RefinerySpeedMultiplier { get; }
        bool ShowPlayerNamesOnHud { get; }
        bool SurvivalMode { get; }
        bool ThrusterDamage { get; }
        string ThumbPath { get; }
        TimeSpan TimeOnBigShip { get; }
        TimeSpan TimeOnFoot { get; }
        TimeSpan TimeOnJetpack { get; }
        TimeSpan TimeOnSmallShip { get; }
        IMyVoxelMaps VoxelMaps { get; }
        bool WeaponsEnabled { get; }
        float WelderSpeedMultiplier { get; }
        ulong? WorkshopId { get; }
        string WorldID { get; set; }

        void BeforeStartComponents();
        void Draw();
        void GameOver();
        void GameOver(MyTextsWrapperEnum? customMessage);
        MyObjectBuilder_Checkpoint GetCheckpoint(string saveName);
        MyObjectBuilder_Sector GetSector();
        Dictionary<string, byte[]> GetVoxelMapsArray();
        MyObjectBuilder_World GetWorld();
        bool IsPausable();
        void RegisterComponent(MySessionComponentBase component, MyUpdateOrder updateOrder, int priority);
        bool Save(string customSaveName = null);
        void SetAsNotReady();
        void Unload();
        void UnloadDataComponents();
        void UnloadMultiplayer();
        void UnregisterComponent(MySessionComponentBase component);
        void Update(float timeInSeconds);
        void UpdateComponents();
    }

6) TerminalActionsHelper - еще не разобрался, но думаю это как то связано с терминалами в игре..

    public interface IMyTerminalActionsHelper
    {
        void GetActions(Type blockType, List<ITerminalAction> resultList, Func<ITerminalAction, bool> collect = null);
    }

7) Utilities - интерфейс утилит, тут мы имеем доступ к чату, уведомлениям, и к неким хранилищам данных, мне еще предстоит разобраться с этим.

    public interface IMyUtilities
    {
        IMyConfigDedicated ConfigDedicated { get; }

        event MessageEnteredDel MessageEntered;

        string GetTypeName(Type type);
        TextReader ReadFileInGlobalStorage(string file);
        TextReader ReadFileInLocalStorage(string file, Type callingType);
        void SendMessage(string messageText);
        void ShowMessage(string sender, string messageText);
        void ShowNotification(string message, int disappearTimeMs = 2000, MyFontEnum font = MyFontEnum.White);
        TextWriter WriteFileInGlobalStorage(string file);
        TextWriter WriteFileInLocalStorage(string file, Type callingType);
    }


Space Engineers API C#

Я в кратце расскажу как писать собственные моды под игру Space Engineers на языке C# (Sharp).
Статья больше ориентирована на более менее опытных людей, знакомых с азами программирования на одном из объектно ориентированных языков.

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

Открываем видео от разработчиков: http://www.youtube.com/watch?v=gAh1bNfRLPw

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

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Linq;
using Sandbox.ModAPI;
using Sandbox.Common;
using Sandbox.Common.ObjectBuilders;
using Sandbox.Common.Components;



Далее мы указываем пространство имен и имя нашего класса.

namespace Bot
{
    public class Bot : MySessionComponentBase
    {
    }
}


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

There are several ways to this
You add handler on MyAPIGateway.Utils.MessageEntered and execute your code depending on player input to chat
You can subclass MySessionComponentBase, add Attribute and it will get loaded before world and then updated each frame.
You can subclass MyGameLogicComponent, add Attribute and it will get hooked to any object


1) Можно повесить обработчик события MyAPIGateway.Utils.MessageEntered (отправлено сообщение в чат) и по срабатыванию триггера этого события вызывать вашу функцию с кодом.
2) Можно расширить класс MySessionComponentBase, в таком случае ваш код может выполняться при каждой перерисовке, либо при загрузке единожды, что мы и сделаем в примере ниже.
3) И можно расширить класс MyGameLogicComponent который позволит привящать к любому игровому объекту. Об этом напишу если будут желающие.

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

using Sandbox.ModAPI;
using Sandbox.Common;
using Sandbox.Common.ObjectBuilders;
using Sandbox.Common.Components;
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Linq;

namespace Bot
{
    [Sandbox.Common.MySessionComponentDescriptor(Sandbox.Common.MyUpdateOrder.BeforeSimulation)]
    public class Bot : MySessionComponentBase
    {
        public override void BeforeStart()
        {
            base.BeforeStart();
            MyAPIGateway.Utilities.MessageEntered += new MessageEnteredDel((string messageText, ref bool sendToOthers) => onMessage(messageText));
        }

        private void onMessage(String messageText)
        {
            MyAPIGateway.Utilities.ShowMessage("Bot", "player say: " + messageText);
            MyAPIGateway.Utilities.ShowNotification("My notification!");
        }
    }
}


Давайте теперь разберем более подробно.
public class Bot : MySessionComponentBase - Мы расширили базовый класс

public override void BeforeStart() - переопределили базовый метод

MyAPIGateway.Utilities.MessageEntered += new MessageEnteredDel((string messageText, ref bool sendToOthers) => onMessage(messageText)); - И добавили обработчик событий, который при появлении сообщения в чате вызывает метод onMessage

Внутри метода onMessage мы обращаемся к 2 методам:
1) MyAPIGateway.Utilities.ShowMessage() - Пишет сообщение в чат, в качестве первого аргумента принимается имя отправителя (ник), второй аргумент - само сообщение.

2) MyAPIGateway.Utilities.ShowNotification() - Отображает текстовое уведомление на экране игрока.

Сохраняем полученный код, как bot.cs,
Заходим по пути - (У вас буква диска может быть другой)C:\Users\[Ваш пользователь]\AppData\Roaming\Space Engineers\
Создаем папку Mods, если ее еще нет, в ней создаем папки Bot\Data\Scripts\BotScript\ сюда кладем наш bot.cs
Полный путь у меня выглядит так:
C:\Users\sudoroot\AppData\Roaming\Space Engineers\Bot\Data\Scripts\BotScript\bot.cs
Запускаем игру, либо создаем новый мир, либо загружаем уже созданный, до загрузки вам надо лишь зайти в настройки этого мира - mods - и добавить наш мод.

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