Помните «Шарарам»? Я перенёс его с Flash на Ruffle, и он перестал лагать
«Шарарам» – это старая, но до сих пор живая MMORPG по «Смешарикам». Огромный срез моего поколения, родившегося в нулевых, нашёл там первых друзей и приобрёл первые социальные навыки. Я до сих пор, в свои 23 года, возвращаюсь туда раз в один-два месяца: пообщаться с ровесниками и повспоминать золотые годы игры.
Но чем старше я становлюсь, тем сильнее желание «просто поиграть» превращается в желание помочь родному проекту. Отчасти это даже страх за него: все аналоги и конкуренты, включая западных вдохновителей, давно умерли, из чего очевидно, что и в Шарараме экономического смысла осталось немного. Думаю, создатели поддерживают его примерно из тех же чувств, что теплятся у меня.
Это желание вылилось в эксперимент, после которого Шарарам почти перестал лагать. Честно замерить FPS сложно – разброс между локациями огромный, – но разница видна невооружённым глазом: играть комфортно даже там, где раньше было слайд-шоу. А заодно игра снова заработала в современных браузерах!
Поиграть можно через альтернативный клиент shararam.sadfun.dev (или его десктопную версию). Надеюсь, когда-нибудь это попадёт в апстрим, и игра станет приятнее для всех. А ниже будет технический разбор, как я это сделал.
Стек Шарарама
Шарарам работает на Adobe Flash Player. Для браузерной игры, запущенной в 2009 году, это естественный выбор. Но сейчас из-за этого у Шарарама есть две проблемы (обе из которых я и решал своим клиентом).
Первая - игра перестала работать в браузерах. Adobe Flash выпилили из всех браузеров в 2020 году.
Шарарам попытался выкрутиться, оставив в браузере очень обрезанную Unity-версию, и предлагая "скачать полную версию":
"Полная версия" - это Electron, в который "насильно" засунули поддержку флеш плеера. По сути это кастомный браузер специально для Шарарама. Так игра работает и по сей день.
Вторая проблема - очень низкий FPS. Сейчас в локациях, где много смешариков, даже самый мощный ПК начинает надрываться и выдавать единичные FPS.
В этом виноват сам Flash Player. Шарарам полностью состоит из векторной графики, и флеш рисует её прямо на процессоре. На запуске это, видимо, было терпимо из-за разрешений экрана времен нулевых. Но сегодня играть на макбуке или 4K мониторе, даже снизив настройки графики, невыносимо.
Ruffle
Окончание поддержки Flash оставило в подвешенном состоянии не только Шарарам, но и кучу игрового наследия нулевых-десятых. Поэтому неравнодушные сделали Ruffle - современный эмулятор флеш-плеера. Он решает почти все наши проблемы:
Нативно работает в браузере через WASM
Рисует графику на GPU
Совместим с AVM 1, на котором написан Шарарам, на 99%, и с его API на 82% (в 2020 это было не так, но сейчас пришло время)
Так что, неужели вся эта статья сводится к тому, что я подменил рантайм флеша рантаймом Ruffle?
Нет, была одна часть, к которой Ruffle все еще не готов. Шарарам - онлайн-игра, а потому сейчас нам придется реанимировать его работу с сервером.
RTMP
Какие протоколы мы с вами привыкли использовать из браузера? HTTP для RPC, WebSocket для bidi-коммуникации, WebRTC для позвонить, WebTransport для близжайшего будущего (да и то это формально HTTP/3). А вот вы задумывались когда-нибудь, что использовали во времена Flash игр?
Познакомьтесь с RTMP. На Википедии пишут, что в основном он используется для передачи потокового видео и аудиопотоков с веб-камер. Но в случае с флеш-играми (я не буду приводить примеры из Шарарама, потому что их ToS запрещает перехватывать трафик) я вижу, что поверх него работает полноценный RPC!
Итак, игра открывает обычный незашифрованный TCP-сокет, что для нее выглядит примерно так:
// ActionScript
var responder = {
onResult: function(value) {
trace(value); // 70
}
};
connection.call("sum", responder, 20, 50);Flash превращает это в RTMP Command Message типа 0x14, сериализованный через AMF0 (просто название формата сериализации), и получается что то такое:
03 # chunk stream ID 3
00 00 00 # timestamp
00 00 22 # payload length: 34 bytes
14 # message type: AMF0 Command
00 00 00 00 # message stream ID
02 00 03 73 75 6D # String "sum"
00 40 00 00 00 00 00 00 00 # Number 2 — transaction ID
05 # Null
00 40 34 00 00 00 00 00 00 # Number 20
00 40 49 00 00 00 00 00 00 # Number 5002 00 07 5F 72 65 73 75 6C 74 # String "_result"
00 40 00 00 00 00 00 00 00 # Number 2 — transaction ID
05 # Null
00 40 51 80 00 00 00 00 00 # Number 70Веб-разработчики уже наверняка догадались, в чем тут проблема. Даже не в том, что в Ruffle нет поддержки этого протокола, а в том, что из современного браузерного стека никто нам не даст открыть "голый" TCP сокет, сколько кода ты не пиши. А я напоминаю, что менять код Шарарама мы тоже не можем, потому что это нарушит их ToS.
Проксируем Шарарам
Авторы Ruffle почти полностью предусмотрели вышеописанную проблему. В нём есть бэкенд "сырых" сокетов с опцией socketProxy: когда WASM-сборка хочет открыть TCP-соединение, она вместо этого стучится бинарным WebSocket-ом по заданному адресу и дальше живет так, будто это и есть сокет. Расчет, тут очевидно, на проксирование.
Но вот чего авторы Ruffle еще не сделали - это не поддержали само по себе кодирование-декодирование RTMP команд. Неудивительно, зацените фрагмент с Википедии:
В 2009 году Adobe выпустила документ, названный «спецификацией RTMP»[1], однако описание было нарочито неполно для сдерживания развития альтернативных серверов. Кроме того, для прочтения этого документа требовалось согласиться с лицензионным соглашением, согласно которому допустимо создание RTMP-сервера исключительно по спецификации от Adobe без каких-либо отступлений. В некоторых местах в спецификации указаны намеренно неверные данные
С тех пор для RTMP появился разве что нормальный реверс-инжиниринг. Тут я не буду умничать и скажу прямо - я взял свой Codex и поручил ему:
Взять несколько спецификаций + опенсорсных библиотек с реализацией rtmp-клиентов
Написать для Ruffle точно такую же сериализацию-десериализацию без единого отклонения, с несколькими циклами самопроверки
Когда реализация по его "мнению" будет закончена – поднять весь проект, залогиниться в Шарарам, погулять по нему, поиграть в мини-игры, и при малейшем отклонении поведения от оригинала проверять, не в сериализации ли дело.
Codex работал над этим делом 11 часов, и на утро я впервые увидел Шарарам в 4K 60FPS! Итоговая архитектура получилась не очень сложной:
Хотя авторизация Смешарика работает через обычный POST shararam.ru/api/user/loqin (орфография оригинала), она привязывает выданный токен к клиенту и проверяет его при загрузке ассетов и даже RTMP подключении (причем, как я понял аж через TLS/HTTP2 фингерпринтинг) - поэтому Rust-прокси всю дорогу эмулирует один и тот же официальный клиент. То есть да, при игре через shararam.sadfun.dev ваш пароль пройдет через мой сервер - если вам по понятным причинам не нравится такой вариант, на гитхабе есть standalone-сборка, которая поднимет прокси локально, и все остается строго у вас на машине.
Итог
Для меня этот рассказ неотделим от Шарарама, но в целом получился скорее общий кейс переноса онлайн-игры с Flash на Ruffle. Надеюсь, это будет полезно кому-нибудь, кто столкнется с такой же задачей - или хотя бы разработчикам Шарарама, если они захотят вернуть одной из самых культурно значимых игр работу в браузерах. Тем более, "иконка" Шарарама перестанет работать на macOS уже в следующем году, когда Apple отключит Rosetta 2. Потому что закрытый Flash уже точно не пересоберёшь под ARM.
Все права на "Шарарам" и "Смешариков" принадлежат ООО "Смешарики" и являются зарегистрированными товарными знаками.