Коротко о dll, Unity, Mono, некоторых методах взлома и крутости сообщества разработчиков открытого кода.
Я часто играю в BeatSabre в качестве мода, но в какой-то момент мне пришлось создать свой собственный. Beat Sabre создана в Unity, поэтому я покопался в ее коде, коде существующих модов, чтобы понять, как все работает, и написал об этом длинный текст.
Это довольно большой материал, поэтому я разделил его на две части. В этой части я объясню, как заставить вашу игру запускать сторонние моды, а во второй части вы создадите свои собственные моды. Несмотря на то, что в какой-то момент этот дневник погружается в технические лабиринты, он рассчитан на широкую аудиторию.
Моей целью было показать, насколько далеко может зайти механика, чтобы добавить в игру что-то новое. Я постарался объяснить все, что может показаться сложным для человека, не знакомого с программированием, но если вам что-то покажется непонятным, не стесняйтесь спрашивать.
Если у вас есть VR-шлем, вы почти наверняка знаете, что такое BeatSabre. Если нет, то вы наверняка видели хотя бы одно видео об этой игре.
Beat Sabre поддерживает только пользовательские уровни из мода. В игре есть официальный редактор, но нет никакого сервиса (даже Steam Labs) для распространения ваших творений, и они вряд ли появятся. В основе почти каждого уровня лежит песня. Разрешение размещения таких файлов на официальных сервисах означает, что непрекращающиеся просьбы удалить что-то или даже вообще все закрыть будут доведены до сведения владельцев авторских прав.
Поэтому неудивительно, что вокруг BeatSabre образовалось огромное сообщество: BeatSabreModdingGroup (BSMG). Сообщество отвечает за большую часть содержания игры.
-
-Коллекция пользовательских уровней, созданных с помощью официальных или неофициальных редакторов. Десятки уровней добавляются сюда каждый день. -https://beatsaver.com. Избранные предложения, еженедельные топы, поиск по жанрам и многое другое. -Список лидеров на уровне пользователя. -Слово модели, платформы и аватары. Здесь хранится визуальный 3D-контент без дополнительного кода. -Коллекция модов, изменяющих код игры. -ModAssistant, программа для установки модов. Игрок просто выбирает нужный мод и нажимает кнопку Install. Нет необходимости даже копировать файлы вручную.
Все это поддерживается сообществом. Большинство модов и сам сайт также являются проектами с открытым исходным кодом и могут быть найдены на GitHub, сайте, где разработчики публикуют свой код. Это то, что мы будем использовать дальше.
Начните с основ. Когда вы пишете программу на языке программирования, вы пишете код, называемый исходным кодом. Чтобы компьютер мог выполнять эти программы, ему необходимо создать исполняемый файл из исходного кода. Этот процесс сборки называется компиляцией. На практике все немного сложнее, используются привязки и другие процессы, но на первый взгляд все работает именно так. Исходный код компилируется в исполняемый файл.
Исполняемый файл — это файл, содержащий код в формате, понятном компьютеру — как набор инструкций процессора. В большинстве случаев в Windows это файлы с расширением .exe. Исполняемый файл может быть выполнен внезапно. Он содержит точку входа и набор подпрограмм (или процедур, функций или методов).
При запуске файла команды загружаются в оперативную память, и процессор переходит к адресу в памяти, соответствующему точке входа, откуда считывает первую команду и начинает выполнять их одну за другой. При вызове подпрограммы процессор запоминает адрес текущей команды и переходит по адресу, соответствующему принципу этой подпрограммы, а при выходе возвращается на прежнее место.
Файлы .dll — это файлы динамических библиотек соединений. Они выглядят очень похоже на свою структуру, естественно, в исполняемом файле, но у них нет точки входа. Они представляют собой просто набор подпрограмм, которые могут быть загружены в память вместе с исполняемым файлом программы и использованы процессором.
У этих библиотек есть много преимуществ. Такие библиотеки могут использоваться многими программами одновременно. Достаточно загрузить их один раз, и они будут доступны во всех программах одновременно. Или же, при определенных условиях, программа может вообще не нуждаться в библиотеке, поэтому ее не нужно загружать в память (это называется "динамической").
Такие библиотеки могут быть предоставлены и другими разработчиками. Если вы видели сообщение при запуске игры о необходимости установить Microsoft Visual C ++ Redistributable 2015, вы знаете, что это именно так.
В дополнение к простым .dll, существуют очень .NET наборы управления. Это также файлы DLL, но вместо команд процессора они содержат команды общего промежуточного языка (CIL или иногда просто IL).
Процессор не знает, что такое CIL, и не может самостоятельно выполнять такие команды. Для этого Windows запускает отдельную программу, которая преобразует команды CIL по мере их выполнения в команды, понятные процессору. Этот процесс называется компиляцией Just-in-Time (JIT), а программа, которую он выполняет, называется Common Language Runtime (CLR). Существует множество языков, в которых программы строятся на таких сборках, самым популярным и распространенным является C# (используется в Unity).
Существует множество различных реализаций Common Language Runtime. По сути, это просто стандарт, и каждый может написать свою собственную версию. Существует всего три популярные версии.
- NET framework, основная платформа, из которой родились все C#, CIL и CLR. Она разрабатывается компанией Microsoft с 1999 года и работает под управлением Windows. Считается, что фреймворк .NET появился в ответ на Java, популярность которой в то время росла.
- NET Core — это платформа с открытым исходным кодом, также разработанная компанией Microsoft, но для различных платформ Windows, Linux и MacOS. Он находится в разработке с 2014 года. Очевидно, что это попытка Microsoft охватить серверы Linux.
- Mono — это еще одна платформа с открытым исходным кодом, которая работает на Windows, Linux, MacOS, мобильных устройствах и консолях. Он используется программой Unity. Он был разработан компанией Ximian, которая была приобретена Novell в 2003 году и куплена Attachmate в 2011 году; AttachMate уволила всех, но разработчики Mono хотели продолжать разработку Mono, поэтому в том же году компания под названием Xamarin Была создана еще одна компания. В 2016 году компания была приобретена Microsoft.
Поэтому все основные реализации CLR теперь находятся под защитой Microsoft. Однако это не обязательно плохо, поскольку звание "Хорошая компания" недавно перешло от Google к Microsoft.
Представив его на время, теперь пора перейти к основной теме. Программные моды (также называемые плагинами) — это DLL-файлы, добавляемые в игру и загружаемые в ее память. Проблема в том, что Beat Saber не поддерживает сторонние плагины и сам по себе ничего не загружает и не использует. Поэтому необходимо что-то для реализации игрового кода и обучения ему. Это BSIPA.
BSIPA (Beat Saber Illusion Plugin Architecture) — это набор библиотек, которые изменяют ритм Saber Files, чтобы игра могла загружать сторонние моды. Эти библиотеки иногда называют аддитивными менеджерами.
Небольшие лирические помехи. Чтобы облегчить объяснение некоторых вещей, необходимо пояснить, что подразумевается под термином "вилка".
Разработка программного обеспечения с открытым исходным кодом — это явление, которое оказывает большое влияние на развитие технологий. Разработчики пишут программное обеспечение и размещают его исходный код на GitHub, например, размещая его под свободной лицензией. Другие разработчики могут свободно использовать это программное обеспечение, предлагать некоторые улучшения существующих программ (pool requests, pull requests), копировать другие проекты или создавать что-то на их основе. на них. Последние называются "вилками".
Таким образом, BSIPA (C #, исходный код: написан на GitHub) является ветвью IPA (GitHub). Другими словами, моддеры взяли существующий менеджер Unity и улучшили его специально для нужд Beat Saber. Давайте обратимся к исходному коду и посмотрим, как все это работает. Мы выделяем три основных раздела — IPA, IPA.Loader и IPA.Injector. На самом деле, это еще не все, но остальное не так важно.
Игроки обычно устанавливают моды с помощью ModAssistant или соответствующей программы, но делают это вручную. Для этого скачайте последнюю версию BSIPA, распакуйте все файлы в папку с игрой и запустите IPA.exe. Это исполняемый файл, который копирует файлы из недавно распакованных файлов в исходную точку. И это, по сути, единственное, что она делает. Она просто копирует файл, а если файл уже существует в игре, он делает резервную копию. Давайте посмотрим на список файлов.
beat saber_data managed i18n.dll
beat saber_data managed i18n.west.dll beat saber_data managed i18n.west.dll
beat saber_data managed pa.injector.dll
beat saber_data managed pa.injector.pdb
beat saber_data managed ipa.leader.dll.
beat saber_data managed ipa.loader.pdb
beat saber_data managed pa.loader.xml
beat saber_data managed microsoft.charp.dll
beat saber_data managed system.runtime.serialisation.dll.
libs 0harmony.1.2.0.1.dll
libs ionic.zip.1.9.1.8.dll
libs mono.cecil.0.10.4.0.dll.
libs mono.cecil.mdb.0.10.4.0.dll
libs mono.cecil.pdb.0.10.4.0.dll
libs mono.cecil.rocks.0.10.4.0.dll.
libs newtonosoft.json.12.0.0.0.dll.
libs semver.1.2.0.0.dll
winhttp.dll
Здесь вы видите два блока BSIPA, упомянутые выше: ipa.loader.dll и ipa.injector.dll. Для их работы требуются другие библиотеки. Некоторые из них подробно описаны в этой большей и второй части.
Как следует из названия этого модуля, он отвечает за загрузку и управление плагинами. Класс PluginComponent определен здесь. Это компонент Unity, отвечающий за хранение модов и передачу событий из игры. Например, если активная сцена в основной игре меняется (например, если вы начинаете уровень или, наоборот, возвращаетесь в главное меню), PluginComponent будет выполнен для всех модов и обновлен соответствующим образом. Более подробно об этом рассказывается в части 2.
Название Injector указывает на то, что этот модуль отвечает за реализацию в оригинальной игре. Начнем с интересного факта: BSIPA добавляет в игру защиту от пиратства.
Защита очень примитивна: нет ни Denuvo, ни виртуализации, только проверка на наличие в папке с игрой файлов, явно указывающих на крэки (SmartSteamEmu.ini, BSteam crack.dll, huhuvr_steam_api64.dll и т.д.). BSIPA нуждается в этом не из какого-то практического интереса. Вставка модов в код игры и добавление к нему модов — это, по сути, взлом. Если игра пиратская, она взламывается дважды, и не гарантируется, что оба хака будут работать вместе.
IPA.Injector использует стороннюю библиотеку Mono.Cecil (источник: GitHub). Его автор: Jb Evain раньше работал в Novell, которая разрабатывает Mono, а теперь работает в Microsoft, где возглавляет VisualStudioTools для разработки Unity. Mono.Cecil существует с 2004 года, когда была выпущена первая версия Mono.
Он позволяет читать и обновлять сборки .NET. Это означает, что с его помощью можно изменять различные параметры библиотек C# и редактировать хранящийся в них код. Здесь нет ничего особенно сложного — Mono.Cecil обрабатывает файлы dll так же, как MicrosoftOffice обрабатывает файлы docx. Вам нужно знать структуру файла, что он читает и что записывает. Если что, я бы не стал сбрасывать со счетов JbEvain. Библиотека по-прежнему очень сложна и полезна. Изучать его со стороны не так интересно.
И поскольку я уже не в первый раз упоминаю Mono в этом блоге, мне нужно написать о нем больше. В начале этой статьи я упомянул, что Mono является одной из реализаций Common Language Runtime. Это программа, необходимая для запуска кода из управляемой структуры .NET. Проще говоря, в данном случае Mono нужен для того, чтобы иметь возможность писать код на C# в Unity. Ядро движка Unity написано на C++, но отдельные части движка и почти все разрабатываемые игровые сценарии написаны на C#. Когда вы запускаете игру, сначала загружается код C++. Этот код запускает Mono, который передает код движка C# и код игры. После этого сама игра начинает функционировать и становится пригодной для игры.
Mono.Cecil IPA.Injector используется для обработки библиотеки UnityEngine.CoreModule.dll. Как следует из названия, это одна из библиотек Unity. Он написан на C# (иначе его нельзя редактировать) и содержит основные сущности движка. Например, существует класс GameObject. Все игровые объекты, размещенные в игровой сцене, являются объектами этого класса.
Существует также класс MonoBehaviour, который представляет собой класс для компонентов с игровой логикой (с Mono в названии). IPA.Injector обнаруживает класс UnityEngine.CoreModule.dll UnityEngine.Application и добавляет код из IPA.Loader для изменения (или создания, если он отсутствует) статического конструктора. Статический конструктор подробно не описан, но после загрузки класса Application создается PluginComponent и загружается плагин из папки Plugins. Оказалось, что мы взломали не код BeatSabre для загрузки модификации, а сам код Unity.
Если вместо BSIPA использовался оригинальный IPA, то на нем можно остановиться. В оригинальном файле IPA.exe, который запускает инжектор, UnityEngine.CoreModule.dll была изменена, и в игру были внесены изменения. У этого подхода есть недостатки. Каждый раз, когда оригинальная игра обновляется, IPA.exe необходимо запускать снова, чтобы применить патч к игре.
Это может показаться не очень серьезной проблемой, но нужно проверить, сколько жалоб получили игроки от разработчиков игр на то, что моды были сломаны в различных обновлениях. BSIPA решает эту проблему, но, как отмечалось выше, BSIPA’s IPA.exe только копирует файлы и больше ничего не делает. Так неужели не ясно, кто управляет инжектором в этом случае? И вот здесь начинается самое интересное.
В конце концов, библиотека UnityDoorstop-BSIPA отвечает за вставку в код Unity. Он находится в файле BSIPA и написан на чистом C. UnityDoorstop-BSIPA (источник: GitHub) также является форком. Оригинальный проект можно найти на GitHub. В дальнейшем, для большей ясности, Doorstop будет называться Doorstop, а не UnityDoorstop-BSIPA. Девиз Doorstop — ‘Run managed code before Unity runs’, что в широком смысле можно интерпретировать как ‘Run managed code before Unity runs’.
Напомним, что "управляемый код" в данном случае — это код на C#. Мы уже видели, что ядро движка Unity написано на C++, но игровая логика и некоторые пользовательские скрипты в самом Unity написаны на C#. Поэтому Doorstop можно использовать для того, чтобы каким-то образом вмешаться в логику, когда ядро Unity уже загружено, но C # скрипты еще не загружены.
Далее перейдите к исходному коду UnityDoorstop-BSIPA, чтобы посмотреть, как это делается. Далее — более продвинутый уровень. Я постарался объяснить все простым языком, но достаточно подробно, чтобы вы могли понять техническую глубину происходящей там магии.
Чтобы объяснить, как работает Doorstop, давайте вернемся к "основам информатики", которые уже не являются основами. Выше мы уже писали, что исполняемые файлы могут использовать функции из других библиотек, переходя по адресам этих функций. Давайте уточним, откуда берутся эти адреса. Файлы .exe и .dll имеют такие параметры, как таблица входных адресов (IAT) и таблица выходных адресов (EAT). Импорт — это то, что нужно файлу из других библиотек, а экспорт — это то, что делает файл доступным для использования в других файлах. Очевидно, что .exe имеет только импорт, потому что никто не может его использовать, а .dll может импортировать и то, и другое, потому что он сам использует что-то и позволяет другим использовать это.
Теперь давайте вернемся к Unity и рассмотрим конкретный пример, чтобы показать, как работает Doorstop. Когда вы запускаете игру в Unity (например, Beat Sabre.exe), библиотека UnityPlayer.dll является одной из первых библиотек, загружаемых в память. Он включен во все игры Unity и отвечает за запуск самой игры. Кажется, что все написано на C++, где находится код движка, но я не проверял.
В библиотеке есть таблица импорта, и UnityPlayer использует функцию GetProcAddress в библиотеке kernel32.dll. kernel32.dll является системной библиотекой ядра Windows и отвечает за основные функции, такие как управление памятью и ввод/вывод. Системная библиотека — это библиотека, предоставляемая Windows и обычно хранящаяся в папке C:/ Windows/System32.
GetProcAddress — это очень важная для нас функция, которая предоставляет адрес конкретной библиотечной функции и ее имя. Помните, мы впервые написали, что библиотеки dll называются динамическими, потому что их можно загружать по мере необходимости? Представьте, что у вас есть библиотека HelloWorld.dll и функция hello_world (). Затем вы загружаете функцию динамически и вызываете ее следующим образом
dll = LoadLibrary("HelloWorld.dll"); //загружаем библиотеку hello_world=GetProcAddress(dll, "hello_world"); //ищем функцию по ее имени hello_world (); //находим Вызовите функцию FreeLibrary (dll); //Выгрузите библиотеку из памяти, если она больше не нужна
Файл UnityPlayer.dll содержит практически идентичный код. Обратите внимание, что Unity должна сначала запустить среду выполнения общего языка (в данном случае Mono), чтобы выполнить код C#. UnityPlayer делает следующее:.
mono_dll = LoadLibrary ("Mono.dll"); init_mono = GetProcAddress (mono_dll, "mono_jit_init_version"); mono = init_mono (.) ; //Следующее, используйте mono для загрузки и запуска кода игры.
mono_jit_init_version — это функция, отвечающая за загрузку mono. Прежде чем Unity выполнит C # код, Doorstop вмешивается в выполнение игры, поэтому он должен быть связан с Mono. Да, это так. Это делается в два этапа.
После загрузки библиотеки Doorstop.dll в память компьютера выполняется код. Код ищет уже загруженную в память библиотеку UnityPlayer.dll, извлекает ее таблицу импорта, ищет GetProcAddress из kernel32.dll и заменяет ее поддельной функцией GetProcAddress из Doorstop.dll Заменить на. Имитируемая функция GetProcAddress ищет имя запрашиваемой функции. Если это не mono_jit_init_version, поддельный GetProcAddress вызывает настоящий GetProcAddress и предоставляет запрашиваемый адрес, не мешая нормальной работе. Однако, если запрашивается mono_jit_init_version, возвращается false mono_jit_init_version.
Поддельная mono_jit_init_version сначала загружает Mono, вызывая настоящую mono_jit_init_version. Затем он использует этот Mono для загрузки и запуска библиотеки IPA.Injector.dll. Мы уже объясняли, что IPA.Injector содержит код для вставки плагина в BeatSabre. Когда IPA.Injector завершает свою работу, поддельная mono_jit_init_version передает Mono в Unity.
Устройство даже не может понять, что что-то не так. Когда вызывается фактическая версия mono_jit_init_version, Mono извлекается и начинает работать снова. Когда Unity выполняет поддельную mono_jit_init_version, Mono также извлекается; он не понимает, что Mono успел быть использован для других целей.
Все работает точно так же, как и в слогане Doorstop "Запустите код управления перед Unity". Unity выполнила управляемый код C# из IPA.Injector до того, как Unity выполнила код C#.
Несколько лирических отступлений. Обмен адресами в таблице импорта — один из способов, которым пользуются хакеры (подробнее здесь (на английском языке): https: //pentest.blog/offensive-iat-hooking/). Еще один интересный факт: в коде UnityDoorstop-BSIPA, благодаря ez (профиль на GitHub), мы нашли код для изменения таблицы ввода. Это еще один пример того, как сообщество может работать вместе для решения технических проблем и использования чужого кода.
Одна проблема остается нерешенной. Сначала мы написали о библиотеке Doorstop.dll в описании Doorstop. Давайте еще раз посмотрим на файлы, которые IPA.exe устанавливает в игру.
beat saber_data managed i18n.dll
beat saber_data managed i18n.west.dll beat saber_data managed i18n.west.dll
beat saber_data managed pa.injector.dll
beat saber_data managed pa.injector.pdb
beat saber_data managed ipa.leader.dll.
beat saber_data managed ipa.loader.pdb
beat saber_data managed pa.loader.xml
beat saber_data managed microsoft.charp.dll
beat saber_data managed system.runtime.serialisation.dll.
libs 0harmony.1.2.0.1.dll
libs ionic.zip.1.9.1.8.dll
libs mono.cecil.0.10.4.0.dll.
libs mono.cecil.mdb.0.10.4.0.dll
libs mono.cecil.pdb.0.10.4.0.dll
libs mono.cecil.rocks.0.10.4.0.dll.
libs newtonosoft.json.12.0.0.0.dll.
libs semver.1.2.0.0.dll
winhttp.dll
Как вы можете видеть, Doorstop.dll находится не здесь. Во-вторых, даже если Doorstop.dll здесь, зачем BeatSabre или Unity загружать ее в память? Beat Sabre.exe распознает UnityPlayer.dll, поэтому UnityPlayer.dll загружается. UnityPlayer.dll распознает kernel32.dll, поэтому kernel32.dll также загружается. Однако они ничего не знают о Doorstop.dll и не загружают его, так что же мне делать?
Решение: притвориться другой библиотекой, загруженной Unity, и загрузить ее перед настоящей библиотекой (кстати, это еще один метод, используемый в хаке, вместе с манипуляцией с таблицей импорта. Дополнительная информация (на английском языке).
В данном случае такой библиотекой является winhttp.dll. Это системная библиотека Windows для http-запросов (хранится в C: / Windows / System32). Unity указывает, что библиотека winhttp.dll требуется для некоторых библиотек в таблице импорта, поэтому Windows загружает библиотеку, когда Unity загружается, и Unity начинает работать.
Doorstop встроен в библиотеку с тем же именем: winhttp.dll, которая содержит код для всех операций с использованием функций GetProcAddress и mono_jit_init_version и те же функции, что и оригинальный winhttp.dll Включен экспорт таблицы адресов с использованием тех же функций, что и в оригинальной winhttp.dll. В рамках процесса загрузки библиотеки Windows сначала проверяет, есть ли нужная библиотека в папке программы, а затем пытается переместить ее в System32, если не находит ни одной библиотеки. Таким образом, когда вы запускаете игру, Windows сначала найдет этот файл.
Поскольку поддельная библиотека имеет такое же имя и таблицу экспорта, Windows считает, что это нужная библиотека для игры, и загружает ее в память. Затем поддельная библиотека динамически загружает настоящую библиотеку winhttp (используя LoadLibrary) и перенаправляет все вызовы из таблицы экспорта на адрес настоящей функции (используя GetProcAddress). Размеры также можно сравнивать. Поддельная библиотека весит 16 кб, а настоящая — 960 кб.
Вместо Winhttp.dll можно использовать любую другую библиотеку, используемую в Unity. Это просто выбор, сделанный разработчиками Doorstop.
На этом этапе я хотел бы поблагодарить сообщество BSMG, особенно DaNike, за ответы на мои вопросы о Discord и помощь в понимании того, как работает Doorstop и зачем нужна winhttp.dll. Вряд ли они прочтут его, но все же.
Если вы уже окончательно запутались, ничего страшного. Повторите все шаги в хронологическом порядке.
- Запустите файл IPA.exe. Все библиотеки копируются в нужную папку с игрой. Код игры еще не был изменен.
- Запустите игру (Beat Sabre.exe), и
- Загрузите библиотеку UnityPlayer.dll, написанную на C++.
- Windows ищет системные библиотеки, необходимые Unity. Одной из таких библиотек является winhttp.dll. Windows находит и загружает поддельную библиотеку winhttp.dll в папке с играми, а затем загружает
- Поддельный файл winhttp.dll вызывает код Doorstop. Она подделывает функцию GetProcAddress из системной библиотеки kernel32.dll.
- Unity вызывает GetProcAddress и находит функцию mono_jit_init_version. Обнаружена поддельная версия mono_jit_init_version, потому что GetProcAddress был подделан.
- Fake mono_jit_init_version загружает Mono.
- Поддельный mono_jit_init_version загружает IPA.Injector.dll с Mono.
- IPA.Injector использует библиотеку Mono.Cecil для модификации класса Application в библиотеке UnityEngine.CoreModule.dll и использует код в IPA.Loader.dll.
- Поддельная mono_jit_init_version передает Mono в Unity.
- Unity использует Mono для запуска частей движка, написанных на C#.
- Загружается измененный класс Application. Код вызывается из IPA.Loader.dll.
- IPA.Loader загружает мод и
- Unity выполняет код BeatSabre с помощью Mono.
- Оригинальный код игры и код мода теперь существуют вместе.
- ?
- Прибыль.
На этом первая часть завершена. Теперь вы понимаете, как работать с библиотеками Windows и Unity для добавления модов в вашу игру Unity.
В следующей части вы создадите свой собственный мод для Beat Sabre, подробно рассмотрев, как моды обмениваются информацией с игрой, как они изменяют поведение оригинальной игры и Harmony (используемая библиотека модов с кодом C#). RimWorld, BATTLETECH, Cities: Skylines, Kerbal Space Program, Oxygen Not Included, Stardew Valley, Subnautica и другие, созданные моддерами (по крайней мере, по словам автора Harmony на GitHub). (по словам автора на GitHub Harmony).
Я начинаю вести свой собственный блог здесь, на DTF. Речь пойдет о разработке, создании игр и информационных технологиях в целом. Моя цель — сделать так, чтобы людям было легко понять, что технологии — это интересно. Ширяев уже сделал это со своим "Нейроном" и, похоже, добился успеха. Так что подписывайтесь! Они также иногда находятся далеко.
