InGameShop (IGS) - внутриигровая менюшка для Garry's Mod серверов, через которую игроки донатят их владельцам с помощью gm-donate.ru. До недавней поры установка происходила через Steam Workshop. Сейчас готова версия, которая 1 запросом скачивает и выполняет код. Внизу будет реализация

Разработка такой версии изначально казалась задачкой на вечерок под кофеек, но много раз откладывалась из-за каких-то нюансов. Было много идей, как все реализовать, но выжила только одна, о которой пост

Что получилось

Open Source репозиторий на GitHub, где создание нового тега приводит к автоматической сборке билда IGS, который можно скачать и установить тупым простым HTTP GET запросом

Преимущества

Вкратце: проще для пользователей и разработчиков
  • Пользователям не нужно учиться, как создавать коллекцию в Steam Workshop, добавить в нее ваш/наш аддон, а потом еще привязать к серверу. Для установки достаточно 1 файла с http.Fetch + RunString
  • Разработчикам с базовым знанием Git проще релизить аддон, потому что упаковать его в .gma, затем загружать в Workshop после малейших фиксов это геморрой. Разработчики на Linux/Mac без установленного Steam и вовсе не смогут залить обновление в Workshop, зато с этим способом это простой git tag NAME -am "Hello world!" + git push origin NAME
  • Когда Workshop лежит или на аддон кто-то пожаловался или вдруг Steam изменил API (а мы сталкивались со всем из перечисленного), то страдают все. Аддон становится недоступным и отключается на всех серверах
  • Пользователи могут сделать форк репозитория, делать свои модификации IGS и использовать свои версии вместо нашего репозитория. Код открытый, любой может его изменить или убедиться в безопасности
  • Возможность поддержки сразу нескольких версий скрипта без необходимости выкладывать новый аддон в воркшоп, когда происходят изменения без обратной совместимости. Каждый обновляется как хочет и когда хочет

Недостатки

Вкратце: для работы нужен интернет и отсутствие параноидальных античитов
  • Без интернета аддон не загрузится. Если IGS без интернета и бесполезен и для нас это не критично, то для других скриптов это может быть важно
  • Код после упаковки становится однострочным и если где-то возникнет ошибка, то вызывайте экстрасенсов (возможно, в будущем будет новый json упаковщик, который решит эту проблему)
  • Если на серверах есть блокираторы RunString/CompileString, то это не гуд
  • Кастомные модельки придется загружать сторонним аддоном. В веб скрипт их засунуть не получится
  • Если у вас используются .dll, то придется искать им альтернативу. Для замены пушей через socket.dll мы сделали long polling microservice.
  • Некоторые хостинги могут блокировать запросы к GitHub

В чем сложность?

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

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

Сама web загрузка тоже с приколдесом и срабатывает после ПОЛНОЙ загрузки сервера и всех загрузочных хуков, вроде InitPostEntity, PostGamemodeLoaded, GM:Initialize и т.д., а значит, что если в коде будут использоваться эти хуки, то они сами по себе уже не будут вызваны.

Кроме того нужно было сделать, чтобы скрипт умел автоматически обновляться до свежей версии и сам понимать, какую версию ему можно установить, а какую нет. Мы же не хотим, чтобы наша любимая Windows XP превратилась в Windows 10 без нашего желания, правда? Ну вот и некоторые клиенты не очень любят обновляться.

Если этого мало, то учитывайте еще то, что путь в include может быть как относительный (init.lua), так и абсолютный (igs/interface/vgui/init.lua), а код должен понимать, где искать файл.

Теперь можно выдохнуть? Нет. Пора думать про безопасность и про то, чтобы клиенты не могли изменить какой-то файл и добавить в него клиентский скрипт (например, чит). Вообще-то, мы тупо решили не кешировать файлы, но если захотите сделать такую глупость вместо нас, то сравнивайте хотя бы util.CRC файлов на клиенте и сервере.


Реализация

Было несколько идей реализации, но все они оказались говном. Эта, конечно, тоже пахнет, но не так, как другие. Честно

Вкратце: аддон упаковывается в много строк в 1 superfile.txt в формате путь код, гмод скачивает его, а специальный include(путь) выполняет RunString, если путь есть в файле

Упаковщик

Нужно написать программку (вот), которая упакует ваш аддон в 1 файл. В нашем случае это superfile.txt. На момент написания поста это происходит так: скрипт рекурсивно "бегает" по папке addons/igs-core, читает каждый файл, превращает его в одну строку, а затем добавляет в superfile.txt путь к этому файлу и его содержимое.

Частичка superfile.txt. Много строк типа igs/init.lua print("я очечко")

Автоматизатор + Доставщик

GitHub будет сам делать superfile.txt и создавать с ним релиз каждый раз, когда будет создаваться новый git tag.

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

Для удобства GitHub еще создаст igs-mod.zip (архив этой папки), который владельцы серверов должны установить себе на сервер и тоже добавит его к релизу. Это позволяет легко скачать настройщик, не скачивая весь репозиторий

Получалка

Это простой http.Fetch, который скачивает и запускает главный файл. Он встроен в igsmodification (Настройщик), который пользователям так или иначе все равно придется установить на сервер, поэтому Получалку я встроил именно туда

В самом упрощенном виде выглядит вот так:

http.Fetch("https://raw.githubusercontent.com/GM-DONATE/IGS/main/addons/igs-core/lua/autorun/l_ingameshop.lua", RunString, error)

Загрузчик

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

Загрузчик находит и скачивает superfile.txt, парсит его и засовывает в таблицу IGS.CODEMOUNT.

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

В Загрузчике есть 3 основных функции: IGS.sh, IGS.sv и IGS.cl. По сути это обертки для include(). Эти функции вызываются примерно так: IGS.sv("igs/core_sv.lua") а функция потом ищет указанный путь в IGS.CODEMOUNT (кеш superfile.txt) и если такой путь есть, то запускает код с него, а если нет, то ищет файл в аддонах и грузит оттуда обычным include. Это позволяет запускать модули, которые уже есть на сервере


Бонус

Можно не читать, если не собираетесь делать свою реализацию веб загрузки
  • git action для создания gma. Сначала планировалось просто делать .gma из аддона, затем скачивать именно .gma с GitHub, потом распаковывать на сервере вместо superfile.txt, но этот способ показался мне избыточным
  • gma unpacker. Именно этот скрипт должен был распаковывать скачанный .gma. Автор IVogel.
  • Если все же хотите использовать кастом модельки в скриптах, то добавляйте их в свой аддон-Получалку, заливайте в воркшоп и комбинируйте с resource.AddWorkshop
  • Нет смысла скачивать superfile.txt, кешировать его и потом грузиться с него вместо скачивания при каждой загрузке сервера или клиента. Это уменьшает защиту от читеров и усложняет вам поддержание такого решения. Кода получится намного больше, а выгоды от этого нет
  • Был вариант с двумя SH+CL и SH+SV файлами, чтобы "сэкономить трафик", но это экономия на копейках, потеря в рублях. Тяжело будет отделять и поддерживать сразу 2 файла, а выгода будет в паре килобайт, что в современном мире – ничто
  • Одна из идей реализации - упаковка кода в файл построчно таким образом, чтобы код выполнялся один за другим и нормально работал. Но тут и проблема "как упаковать" и проблема "что делать с энтити и сторонними модулями"
  • Еще одна из идей реализации это через реплейс инклюдов в коде сразу содержимым из файлов, но не все файлы всегда находились бы на сервере. Отсутствовала бы возможность работы с модулями и само решение неоправданно более сложное
  • Идею можно совершенствовать, сделать полноценный фреймворк для упаковки и доставки аддонов через GitHub. Можно сделать, чтобы superfile.txt был в json формате и тогда не будет проблем с определением ошибки в коде. Можно прикрутить поддержку загрузки тулов и даже при сильном желании кастом моделек

Если пост понравился, может вам понравятся и эти?