C++ инжект модов в Electron - эволюция решения
Summary
Если совсем коротко: я хочу уйти от переделывания файлов Яндекс Музыки и прийти к отдельному лаунчеру. Пользователь запускает не модифицированный клиент, а маленькую программу-обертку. Она открывает обычную Яндекс Музыку, аккуратно подключает моды во время запуска и хранит сами моды отдельно от файлов приложения.
О чем вообще речь
Яндекс Музыка на Windows сделана как Electron-приложение. Это значит, что внутри обычного .exe живет веб-интерфейс: JavaScript, CSS, HTML и немного нативной обвязки. Поэтому многие визуальные доработки технически похожи не на “взлом программы”, а на расширение сайта: добавить стили, изменить поведение кнопок, повесить наблюдатель на появление элементов, открыть свою панель настроек.
Проблема начинается там, где лежат эти файлы. В Electron они часто упакованы в архив app.asar. Раньше самый прямой способ моддинга был такой: распаковали архив, добавили свои файлы, собрали обратно. Для первых экспериментов это удобно. Для нормальной жизни - так себе.
Почему:
- после обновления клиента все приходится проверять заново;
- структура файлов внутри
app.asarможет поменяться; - Electron может заметить, что архив уже не тот;
- моды оказываются перемешаны с официальными файлами приложения;
- пользователю сложнее объяснить, что именно было изменено и как это откатить.
Идеальная цель звучит проще: официальный клиент лежит как лежал, а моды подключаются снаружи.
Как менялась идея
Сначала решение было довольно грубым: поменять app.asar и положить моды прямо внутрь клиента. Это работало, но каждый апдейт превращался в маленькую лотерею. Сегодня файл называется так, завтра иначе, послезавтра Electron ругается на целостность.
Потом появилась мысль обойти проверку целостности. То есть не спорить с тем, что архив изменен, а заставить Electron не падать из-за этого. Для исследования полезно, но для обычного пользователя выглядит слишком тяжело: патчить .exe, зависеть от версии Electron, надеяться, что очередное обновление не сдвинуло нужные байты.
Следующая идея была красивее: не трогать файлы на диске вообще, а подменять их “на лету” через DLL. Программа думает, что читает свой обычный файл, а мы в нужный момент отдаём другой JavaScript. Но тут выяснилась важная деталь: Electron не открывает main/index.js внутри app.asar как отдельный файл.
Он открывает сам архив app.asar, смотрит таблицу внутри него, находит нужный диапазон байтов и читает именно этот кусок. Поэтому простой перехват по имени main/index.js почти бесполезен: такого открытия на уровне Windows может вообще не быть.
После этого появился более умный вариант: ASAR-aware hook. Он уже понимает, где внутри архива лежит нужный файл, и подменяет не “путь к файлу”, а конкретный диапазон байтов. Инженерно это интересно, но для первой версии слишком капризно. Нужно правильно учитывать смещения, ReadFile, ReadFileEx, SetFilePointerEx, OVERLAPPED, закрытие и дублирование handles. Ошибка в такой логике может сломать запуск клиента очень неприятным способом.
Поэтому финальное направление сейчас другое: отдельный bootstrap launcher.
Что такое bootstrap launcher по-человечески
Представь обычный ярлык, только чуть умнее. Ты нажимаешь “запустить модифицированную Музыку”, а дальше происходит примерно следующее:
- Лаунчер находит установленную Яндекс Музыку.
- Проверяет, что рядом есть свежий набор модов.
- Запускает обычный клиент.
- Дожидается нужного процесса Electron.
- Подключает маленький нативный bootstrap.
- Bootstrap уже в окне клиента запускает JS/CSS-моды.
Главная разница с патчем app.asar: моды живут отдельно. Например, в папке вроде:
%AppData%/YMusicBootstrap/payload/А внутри нее уже лежит понятная структура:
payload/
manifest.json
runtime/
bootstrap.js
bootstrap.css
mods/
some-mod/
index.js
style.cssТо есть сам клиент не превращается в “особую сборку”. Он остается клиентом, а моды становятся внешним слоем поверх него.
Что сможет первая версия
Первая версия не должна обещать все на свете. Самый здоровый вариант - начать с того, что реально можно поддерживать стабильно:
- визуальные правки интерфейса;
- CSS-темы;
- небольшие JS-моды для renderer-части;
- наблюдение за изменениями страницы;
- реакция на переходы внутри приложения;
- простая панель настроек для модов;
- включение и отключение модов без пересборки клиента.
Это значит, что V1 больше похожа на систему внешних UI-модов, а не на полную замену старого app.asar-патча.
Минимальный SDK может выглядеть примерно так:
window.__YMBootstrap = {
register(mod),
injectStyle(id, css),
onRouteChange(callback),
observe(selector, callback),
getConfig(modId),
logger
}Для автора мода смысл простой: не нужно думать, куда встроиться в main/index.js или как пережить очередной апдейт архива. Мод регистрируется через общий API, добавляет стили или поведение и работает в renderer-окне.
Что переносить из старого ModYandexClient
Из старого проекта стоит брать не все подряд, а только то, что нормально живет в интерфейсе:
- DOM/UI helpers;
- CSS injection;
- повторное применение правок через
MutationObserver; - обработчики переходов в SPA;
- overlay для настроек;
- чистые функции, которые меняют внешний вид или поведение страницы.
А вот это лучше не тащить в первую версию:
- прямую работу с
electronиipcRenderer; - main-process hooks;
- deep links;
- FFmpeg updater;
- старый
modUpdater; - функции, которым нужен измененный
preload.js.
Не потому что это невозможно навсегда. Просто если сразу пытаться перенести весь старый мир, первая версия утонет в совместимости. Лучше сначала сделать небольшой, понятный и надежный слой для UI-модов.
Чем это будет лучше для обычного пользователя
Пользователю не нужно знать, что такое ASAR, preload или Electron fuse. В идеале весь опыт должен быть таким:
- Установил bootstrap.
- Запустил Яндекс Музыку через новый ярлык.
- Включил нужные моды.
- Если что-то сломалось - выключил мод или запустил обычный клиент без bootstrap.
Это важный момент. Старый подход менял внутренности клиента, и из-за этого было сложнее понять, где заканчивается официальная программа и начинается мод. Новый подход делает границу видимой: вот клиент, вот внешний слой модов, вот папка с payload.
Таблица решений
| Версия | Идея | Что дала | Почему не финал |
|---|---|---|---|
| V0 | Патчить app.asar | Быстро заработали первые моды | Ломается на обновлениях |
| V1 | Отключать integrity/fuse | Можно обойти проверку архива | Слишком зависит от Electron и .exe |
| V2 | Перехватывать файловые API | Не надо менять файлы на диске | Наивный hook не видит файлы внутри ASAR |
| V3 | Понимать ASAR и подменять байты | Технически точнее | Сложно и рискованно для первой версии |
| V4 | Внешний launcher + payload | Моды отдельно от клиента | Это выбранное направление |
Планы
План я бы разделил на несколько спокойных этапов.
Сначала - собрать минимальный launcher: найти клиент, запустить его, дождаться процесса, подключить bootstrap и вывести понятный лог. Без магии и лишнего UI.
Потом - сделать payload-слой: manifest.json, загрузку bootstrap.js, подключение CSS, регистрацию модов и безопасное выключение проблемного мода.
После этого - нормальный SDK для авторов модов: события роутов, наблюдение за DOM, настройки, логгер, версионирование API.
Дальше - менеджер модов: установка, обновление, включение/выключение, совместимость с версиями клиента, понятные ошибки для пользователя.
И только после этого есть смысл возвращаться к более глубоким вещам: интеграции с preload/main, ASAR-aware hook как fallback, расширенным возможностям, которые нельзя сделать только через renderer.
Итог
Самое правильное направление сейчас - не пытаться бесконечно чинить патчинг app.asar. Это был хороший этап для исследования, но плохая основа для удобного инструмента.
Новая архитектура должна быть проще для пользователя и честнее по устройству: официальный клиент отдельно, моды отдельно, запуск через launcher, вся логика модов в payload. Первая версия пусть будет скромной, зато устойчивой: UI, JS, CSS, настройки и нормальный способ отключить то, что сломалось.
А ASAR-aware hook я бы оставил как инженерный запасной путь. Он полезен для понимания Electron и может пригодиться позже, но не должен быть фундаментом первой публичной версии.