TouYinger Q13W: как мы получили root через Magisk и заменили штатный экран на Projectivy
Перед копированием команд
Ниже полный разбор эксперимента и его ограничений. Встроенные блоки команд служат пояснениями к шагам. Не копируйте их для запуска: редактор Пикабу меняет часть пробелов и отступов. Для выполнения скачайте мои исходные TXT и копируйте команды из них.
Полный разбор с командами, backup и предупреждениями: https://t.me/the220iq/17?single
Проверка структуры boot, полный код и ограничения: https://t.me/the220iq/18?single
Особенно важны два ASCII-пробела между хешем и именем файла в строке формирования SHA256SUMS. В TXT они сохранены. Предупреждения о совместимости, собственном backup и проверенном аварийном восстановлении ниже обязательны.
Хотелось поставить нормальный домашний экран на проектор. В процессе выяснилось, что root там уже лежит с завода, но пользоваться им могут не все. Потом нашёлся второй лаунчер, который запускался поверх выбранного HOME. А в финале всё упёрлось в один невидимый символ из Windows.
Ниже наш реальный путь: заводской su → резервные копии → патч собственного boot.img в Magisk 30.7 → проверка записи → root для приложений. Отдельно покажу замену лаунчера и грабли, чтобы следующий человек собирал кинотеатр, а не коллекцию ошибок.
Фото моего Q13W: меню настроек Projectivy на проекционном экране.
Фото моего Q13W: меню Projectivy с пунктами «Настройки Projectivy» и «Настройки Android».
Фото моего Q13W: категории приложений на главном экране Projectivy.
1. Сначала границы применимости
Это журнал успешной работы на одном TouYinger Q13W, а не универсальная прошивка для всех «Туянгеров». Одинаковая надпись на корпусе не гарантирует одинаковые плату, загрузчик и разметку.
В наших текстовых логах:
- модель: Q13W;
- persist.zvt.boardname: ZJ_V660_KLS518R00;
- ro.build.display.id: tv_hi3751v660 HuanglongV100R002C01SPC004B010;
- платформа в свойствах разделов: huanglong/tv_hi3751v660/tv;
- boot/system/vendor fingerprint: huanglong/tv_hi3751v660/tv:12/SP1A.210812.016/eng_00211291647:userdebug/dev-keys;
- ro.build.description: tv_hi3751v660-userdebug 12 SP1A.210812.016 eng.eng_00.20251129.164805 dev-keys;
- boot: /dev/block/by-name/boot → /dev/block/mmcblk0p22, размер 62 914 560 байт;
- отдельные разделы ramdisk, recovery и recoverybak; слот ro.boot.slot_suffix пустой;
- Magisk после установки: 30.7:MAGISK:R;
- Projectivy в финале: 4.71, versionCode=95.
У прошивки есть странности: общий ro.build.fingerprint указывает на MStar и Android 4.4.4, а fingerprints разделов на Huanglong и Android 12. Поэтому по одной красивой строке «версия Android» совместимость не определяем. Имя платы подтверждено сохранённым диагностическим файлом проекта. Физическую маркировку и ревизию платы по фотографии мы не проверяли.
Особенно важно: до записи boot у меня уже была аварийная флешка с заводской прошивкой, и я ранее лично восстанавливал с неё этот проектор. Но точные файлы, их контрольные суммы и вся процедура восстановления в нашем журнале не сохранены. Я не стану придумывать их задним числом.
Если у вас нет подходящей именно вашему экземпляру заводской прошивки и понятного рабочего восстановления без загрузки Android, остановитесь до записи boot. Наличие backup на компьютере само по себе не означает, что его получится вернуть в не загружающийся проектор.
Все команды ниже для PowerShell в папке официальных Android SDK Platform-Tools. IP заменён переменной; префиксы PS C:\..., значки приглашения и вывод команд копировать не нужно. Команды очищены от артефактов выгрузки и адаптированы для PowerShell: адрес и имя patched вынесены в переменные, некоторые диагностические команды объединены. Дополнительные проверки и непроверенный откат отмечены отдельно.
2. Подключаемся и фиксируем исходное состояние
Platform-Tools берём у Google:
https://developer.android.com/tools/releases/platform-tools
У нас заработал сетевой ADB после включения отладки в инженерном меню. Универсальную комбинацию входа я не обещаю: ранние экраны остались во вложениях, а расположение пунктов может зависеть от прошивки. Не меняйте настройки панели и заводские параметры ради поиска ADB.
USB-A ↔ USB-A в нашем опыте подключения не дал. Обычный кабель между двумя host-портами здесь не предлагаю: назначение порта и правильная разводка должны быть подтверждены производителем.
На компьютере задаём адрес своего проектора:
$Target = "IP_ПРОЕКТОРА:5555"
.\adb connect $Target
.\adb devices -l
.\adb -s $Target shell id
Нужен именно ваш проектор со статусом device. При offline, unauthorized, другом адресе или нескольких непонятных устройствах дальше не идём.
Сетевой ADB на порту 5555 используйте только в доверенной локальной сети, без проброса в интернет. Это не защищённое pairing-подключение современных Android.
Дополнительная предосторожность перед сохранением: если в этих каталогах уже лежат образы предыдущего прохода, не запускайте backup поверх них. Сохраните исходный stock boot отдельно и используйте новые пути для нового снимка. Повторный снимок после установки Magisk уже не будет исходным boot.
Сохраняем сведения. Версию ADB, модель и fingerprints добавляю как явную проверку перед повторением:
New-Item -ItemType Directory -Force .\q13w_backup | Out-Null
.\adb version
.\adb -s $Target shell getprop ro.product.model
.\adb -s $Target shell getprop persist.zvt.boardname
.\adb -s $Target shell getprop ro.build.display.id
.\adb -s $Target shell getprop ro.build.description
.\adb -s $Target shell getprop ro.bootimage.build.fingerprint
.\adb -s $Target shell getprop ro.system.build.fingerprint
.\adb -s $Target shell getprop ro.build.version.sdk
.\adb -s $Target shell getprop > .\q13w_backup\q13w_getprop.txt
.\adb -s $Target shell pm list packages -f > .\q13w_backup\q13w_packages.txt
.\adb -s $Target shell cmd package resolve-activity --brief -a android.intent.action.MAIN -c android.intent.category.HOME
Последний ответ запишите: это исходный HOME для вашего устройства. У нас на этом этапе оказался уже установленный Monet, com.klevico.monet/.HomeActivity, а не Projectivy.
3. Root был, но с нюансом
Проверили наличие su и SELinux:
.\adb -s $Target shell "which su; ls -lZ /system/bin/su /system/xbin/su /vendor/bin/su 2>/dev/null; getenforce"
В ответе был /system/xbin/su с правами -rwsr-x---, владельцем root, группой shell и контекстом su_exec. SELinux был Permissive. Мы его таким не делали.
В ранней переписке разбирались отказ adb root и ошибка setgid при попытке заводского su сменить группу на root. Исходные ответы были на скриншотах, поэтому не привожу их как заново проверенный текстовый лог. Зато результат следующей команды сохранился непосредственно в тексте. Рабочей оказалась форма, которая оставляет группу shell:
.\adb -s $Target shell su 0,2000 id
Наш вывод, сокращённо:
uid=0(root) gid=2000(shell) euid=0(root) ... context=u:r:su:s0
Это ключ к дальнейшей работе на нашем экземпляре. Если ваша команда такого результата не даёт, нижеследующая цепочка для вас ещё не работает. Не заменяйте её на случайные способы root.
И это ещё не Magisk. Более того, диагностика показала ограниченные capabilities у заводского su: uid=0 здесь не означал «разрешено вообще всё». Поэтому часть диагностических команд получала Permission denied, хотя чтение разделов и последующая запись boot работали.
4. Резервные копии до модификации загрузки
Сначала читаем свою разметку:
.\adb -s $Target shell "su 0,2000 ls -l /dev/block/by-name"
.\adb -s $Target shell "su 0,2000 cat /proc/partitions"
.\adb -s $Target shell "su 0,2000 cat /proc/cmdline"
.\adb -s $Target shell getprop ro.boot.slot_suffix
Не подставляйте mmcblk0p22 в чужой проектор по памяти. Для команд использован проверенный на этом экземпляре путь by-name/boot. Само наличие раздела с именем fastboot не доказывает поддержку привычного fastboot flash.
Первая попытка снять backup в /sdcard провалилась: заводской root не смог писать через этот путь. Сработал /data/local/tmp. Команды в этом разделе читают разделы и пишут обычные файлы; if и of менять местами нельзя.
.\adb -s $Target shell df -h /data/local/tmp
.\adb -s $Target shell "mkdir -p /data/local/tmp/q13w_raw_critical"
.\adb -s $Target shell "su 0,2000 dd if=/dev/block/by-name/fastboot of=/data/local/tmp/q13w_raw_critical/fastboot.img bs=1048576"
.\adb -s $Target pull /data/local/tmp/q13w_raw_critical/fastboot.img .\q13w_backup\
У нас тест дал 1 048 576 байт и успешный pull. Только после этого пошёл набор критических разделов. Следующий список относится именно к нашей карте; при отсутствующем или иначе устроенном разделе не продолжайте вслепую.
$parts = @(
"fastboot","bootargs","bootargsbak",
"sbl","sblbak","batt_nv","ddrparam",
"dtbo","dtbobak","hrf","sensorhub","dmcu",
"slaveboot","slavebootbak","reserved1",
"atf","atfbak","hhee","hheebak",
"trustedcore","trustedcorebak",
"boot","ramdisk","recovery","recoverybak",
"reserved2","deviceinfo","misc","versioninfo",
"logo","bootmusic","bootmusicsec",
"panel","panelbak","demura","baseparam",
"reserved3","vbmeta_system","vbmeta_vendor",
"securestore","dfx","eng","reserved4","metadata"
)
$remote = "/data/local/tmp/q13w_raw_critical"
foreach ($p in $parts) {
Write-Host "BACKUP: $p"
.\adb -s $Target shell "su 0,2000 dd if=/dev/block/by-name/$p of=$remote/$p.img bs=1048576"
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: $p"
break
}
}
Если цикл остановился с ERROR, сначала разбираемся с этим разделом. Успех следующих команд не исправляет неполный backup.
.\adb -s $Target shell "su 0,2000 dd if=/dev/block/mmcblk0boot0 of=/data/local/tmp/q13w_raw_critical/mmcblk0boot0.img bs=1048576"
.\adb -s $Target shell "su 0,2000 dd if=/dev/block/mmcblk0boot1 of=/data/local/tmp/q13w_raw_critical/mmcblk0boot1.img bs=1048576"
.\adb -s $Target shell "su 0,2000 sh -c 'cd /data/local/tmp/q13w_raw_critical && sha256sum *.img'" | Out-File -Encoding ascii .\q13w_backup\SHA256SUMS_device.txt
.\adb -s $Target pull /data/local/tmp/q13w_raw_critical .\q13w_backup\
Результат у нас: 46 образов, суммарно 709 885 952 байта. Это контроль нашей конфигурации, а не норматив для всех Q13W.
Проверка копий на ПК. Первые две проверки числа и общего размера файлов добавлены для повторения; далее идёт расчёт и сравнение хешей из журнала:
$dir = ".\q13w_backup\q13w_raw_critical"
(Get-ChildItem $dir -Filter *.img).Count
(Get-ChildItem $dir -Filter *.img | Measure-Object Length -Sum).Sum
Get-ChildItem $dir -Filter *.img | Sort-Object Name | ForEach-Object {
$h = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower()
"$h $($_.Name)"
} | Set-Content -Encoding ascii .\q13w_backup\SHA256SUMS_pc.txt
Compare-Object (Get-Content .\q13w_backup\SHA256SUMS_device.txt | Sort-Object) (Get-Content .\q13w_backup\SHA256SUMS_pc.txt | Sort-Object)
Пустой Compare-Object имеет смысл только после проверки, что оба списка содержат ожидаемые имена всех 46 файлов и по 46 настоящих записей SHA-256, а не пустые строки или ошибки. Он подтверждает совпадение вычисленных хешей файлов на устройстве и на ПК, но не является проверкой восстановления.
Дополнительно у нас были скопированы odm, product и super. Ниже те же успешные команды, по одному образу. Перед pull и после него сравнивайте SHA-256; временные копии до проверки не удаляйте.
.\adb -s $Target shell "su 0,2000 dd if=/dev/block/by-name/odm of=/data/local/tmp/odm.img bs=1048576"
.\adb -s $Target shell "su 0,2000 sha256sum /data/local/tmp/odm.img"
.\adb -s $Target pull /data/local/tmp/odm.img .\q13w_backup\
Get-FileHash .\q13w_backup\odm.img -Algorithm SHA256
.\adb -s $Target shell "su 0,2000 dd if=/dev/block/by-name/product of=/data/local/tmp/product.img bs=1048576"
.\adb -s $Target shell "su 0,2000 sha256sum /data/local/tmp/product.img"
.\adb -s $Target pull /data/local/tmp/product.img .\q13w_backup\
Get-FileHash .\q13w_backup\product.img -Algorithm SHA256
.\adb -s $Target shell "su 0,2000 dd if=/dev/block/by-name/super of=/data/local/tmp/super.img bs=1048576"
.\adb -s $Target shell "su 0,2000 sha256sum /data/local/tmp/super.img"
.\adb -s $Target pull /data/local/tmp/super.img .\q13w_backup\
Get-FileHash .\q13w_backup\super.img -Algorithm SHA256
Размеры у нас: odm 56 623 104, product 419 430 400, super 3 816 816 640 байт. Для сохранения всех временных образов одновременно нужно свободное место с запасом, более 5 ГБ. Это дополнительное условие для приведённого здесь варианта без промежуточного удаления.
Не запускайте исходный backup повторно в ту же папку после патча: так можно затереть единственный stock boot уже модифицированным образом. Исходный набор держите отдельно, а для новых снимков создавайте другие каталоги.
Это не полный образ eMMC: userdata и cache мы не снимали. Индивидуальные разделы вроде securestore и deviceinfo нельзя выкладывать вместе с постом. Вторую копию backup стоит держать на отдельном носителе.
5. Патчим свой boot в Magisk
У нашего boot была Android-заголовочная сигнатура ANDROID!, размер 60 MiB. Перед записью изучались заголовок, ramdisk и хвост образа. Поиск AVB/verity не дал основания объявить устройство «без защиты»: часть диагностики упиралась в ограниченные права.
Мы не разблокировали загрузчик через fastboot, не прошивали чужой vbmeta и не отключали проверки готовой командой из чужой инструкции. Это важно: ниже описан уже подтверждённый на нашем экземпляре путь через доступный заводской su, а не замена общей инструкции Magisk для других устройств.
Magisk берём только у разработчика:
https://github.com/topjohnwu/Magisk/releases
Официальная инструкция и ограничения:
https://topjohnwu.github.io/Magisk/install.html
У нас использовался Magisk 30.7. Сам APK ещё не даёт root. Сначала на самом целевом проекторе патчится собственный исходный образ, не скачанный «готовый root для такой же модели».
Передали stock boot из нашего backup:
.\adb -s $Target push .\q13w_backup\q13w_raw_critical\boot.img /sdcard/Download/Q13W_stock_boot.img
.\adb -s $Target shell "sha256sum /sdcard/Download/Q13W_stock_boot.img"
Get-FileHash .\q13w_backup\q13w_raw_critical\boot.img -Algorithm SHA256
Обе суммы должны совпадать между собой. У нас исходный образ имел размер 62 914 560 байт и SHA-256:
2779dd3981266aa6bb051659fdc0733f962555c9f613f3af9286be44d7ed2fe4
Это идентификатор нашего файла, не предложение найти в интернете любой файл с похожим названием.
На проекторе в приложении Magisk: «Установка» в блоке Magisk → «Выбрать и исправить файл» → Download/Q13W_stock_boot.img. Дождались завершения патча и появления файла в Download.
Наш файл назывался magisk_patched-30700_IYiMI.img. У вас окончание будет другим; дальше переменная должна содержать точное имя вашего результата:
$PatchedName = "magisk_patched-30700_ВАШЕ_ИМЯ.img"
.\adb -s $Target shell "ls -lh /sdcard/Download/$PatchedName"
.\adb -s $Target shell "sha256sum /sdcard/Download/$PatchedName"
.\adb -s $Target pull /sdcard/Download/$PatchedName .\q13w_backup\
(Get-Item ".\q13w_backup\$PatchedName").Length
Get-FileHash ".\q13w_backup\$PatchedName" -Algorithm SHA256
У нас размер остался 62 914 560 байт, SHA-256 получился:
7dcff684c777430d69ff09be5fe6e9295fc070c82c613233f4572ee0dc6290a7
Чужой патч с нашим хешем вам не нужен. Нужны согласованные размеры и суммы собственного stock, собственного patched и копий файлов. Неожиданный размер, ошибка патча или непонятная структура образа являются причиной остановиться.
Дополнительно сравнили структуру двух файлов локальным Inspect-Boot. В сохранённом пользовательском выводе:
- ANDROID!, HeaderVersion=0, PageSize=16384 у обоих;
- KernelSize=45054464 у обоих, это совпадение размера, не доказательство идентичности ядра;
- RamdiskSize вырос с 1369094 до 1443114 байт;
- ramdisk остался gzip, а PayloadEnd сдвинулся с 46448640 до 46530560.
Полный read-only код вынесен в приложение «Q13W: проверка структуры boot». Он рассчитан на нашу структуру v0, не является универсальным валидатором и не разрешает прошивку сам по себе. Отдельное сравнение хешей ядра обсуждалось, но его исходный скриншот сейчас недоступен, поэтому независимо подтверждённое совпадение всего ядра здесь не заявляю.
6. Запись boot: здесь заканчивается безобидная диагностика
Следующая команда dd меняет загрузочный раздел. Неверный файл, путь, сборка или прерывание питания могут оставить проектор без загрузки. До неё должны быть проверены совместимость, собственный backup, аварийное восстановление, результат патча и устойчивое питание. Несовпадение любого условия означает остановку, а не попытку «всё равно попробовать».
Сначала только перенос файла и контроль:
.\adb -s $Target push ".\q13w_backup\$PatchedName" /data/local/tmp/Q13W_magisk_boot.img
.\adb -s $Target shell "sha256sum /data/local/tmp/Q13W_magisk_boot.img"
.\adb -s $Target shell "su 0,2000 sha256sum /dev/block/by-name/boot"
У нас хеш временного patched совпал с копией на ПК, а хеш ещё не изменённого раздела boot с сохранённым stock. Если у вас второе сравнение не сходится, не записывайте образ: исходное состояние уже другое.
Вот именно та запись, которая была выполнена у нас:
.\adb -s $Target shell "su 0,2000 dd if=/data/local/tmp/Q13W_magisk_boot.img of=/dev/block/by-name/boot bs=1048576"
Получено:
60+0 records in
60+0 records out
62914560 bytes (60 M) copied
После записи, ещё до перезагрузки:
.\adb -s $Target shell "su 0,2000 sync"
.\adb -s $Target shell "su 0,2000 sha256sum /dev/block/by-name/boot"
Раздел дал тот же 7dcff684…6290a7, что и наш patched. Мы сравнивали полную строку, не только начало и конец.
При ошибке dd или несовпадении хеша не делайте обычную перезагрузку «посмотреть, заведётся ли». Остановитесь и разбирайтесь с записью и своим подготовленным восстановлением. Проверенного сценария отката повреждённой записи наш журнал не содержит.
Только после успешной сверки:
.\adb -s $Target reboot
После полной загрузки:
.\adb connect $Target
.\adb -s $Target shell "su -c id"
.\adb -s $Target shell "su -c 'magisk -v'"
.\adb -s $Target shell "su -c 'sha256sum /dev/block/by-name/boot'"
Результат:
uid=0(root) gid=0(root) groups=0(root) context=u:r:magisk:s0
30.7:MAGISK:R
Хеш boot после загрузки тоже сохранился. Вот это уже работающий Magisk, а не просто установленная иконка приложения.
7. Почему root в ADB работал, а в приложении нет
Activity Launcher всё ещё отвечал:
Cannot run program "su": error=13, Permission denied
Для повторной проверки соберём использованные диагностические команды в одну:
.\adb -s $Target shell "su -c 'ls -ldZ /system/xbin; ls -lZ /system/xbin/su /system/xbin/magisk; cat /proc/mounts | grep system/xbin'"
su уже был ссылкой ./magisk, но каталог /system/xbin имел права 0750, root:shell. Обычное приложение не могло пройти через каталог к исполняемому файлу. В нашем случае /system/xbin был именно Magisk tmpfs, а не просто произвольным каталогом физического system.
Следующее исправление относится только к этой подтверждённой комбинации: Magisk работает, su ведёт на Magisk, /system/xbin является его tmpfs, права 0750 мешают приложениям. Если у вас другая картина, не меняйте права наугад и не ставьте этот скрипт «для профилактики».
Изначально этот mount был read-only. В переписке порядок был таким: сначала перемонтирование только подтверждённого Magisk tmpfs /system/xbin, затем проверка состояния, затем chmod. Сам вызов remount не повторён в пользовательской вставке, но выводы до и после показывают переход ro → rw:
.\adb -s $Target shell "su -c 'mount -o remount,rw /system/xbin'"
.\adb -s $Target shell "su -c 'cat /proc/self/mountinfo'" | Select-String -SimpleMatch "/system/xbin"
Продолжаем только если строка самого /system/xbin показывает rw и это по-прежнему tmpfs magisk. При ошибке или другой схеме остановитесь. Весь system перемонтировать не нужно. Далее:
.\adb -s $Target shell "su -c 'chmod 0755 /system/xbin'"
.\adb -s $Target shell "su -c 'ls -ldZ /system/xbin'"
Получили drwxr-xr-x. После этого в Activity Launcher появился запрос Magisk на предоставление root, и запуск заработал.
Но после reboot права вернулись к 0750. Поэтому на нашем устройстве понадобилось повторять исправление при старте через Magisk service.d.
Сначала проверили, что каталог существует. Ниже сокращённая диагностическая команда из исходной проверки:
.\adb -s $Target shell "su -c 'ls -ld /data/adb/service.d'"
Здесь создаётся постоянный загрузочный скрипт. Не перезаписывайте одноимённый существующий файл без проверки его назначения. Ниже исправленный вариант, который у нас прошёл проверку. Именно WriteAllText с явными LF, а не первая неудачная версия через Set-Content:
$content = "#!/system/bin/sh`nmount -o remount,rw /system/xbin`nchmod 0755 /system/xbin`nmount -o remount,ro /system/xbin`n"
[System.IO.File]::WriteAllText("$PWD\99-q13w-su-access.sh", $content, [System.Text.Encoding]::ASCII)
.\adb -s $Target push .\99-q13w-su-access.sh /data/local/tmp/99-q13w-su-access.sh
.\adb -s $Target shell "su -c 'cp /data/local/tmp/99-q13w-su-access.sh /data/adb/service.d/99-q13w-su-access.sh; chown root:root /data/adb/service.d/99-q13w-su-access.sh; chmod 0755 /data/adb/service.d/99-q13w-su-access.sh; od -An -tx1 /data/adb/service.d/99-q13w-su-access.sh'"
При любой ошибке push, cp, chown или chmod остановитесь. Команды установки разделены точками с запятой, поэтому успешный od в конце не доказывает успех предыдущих операций. Дополнительно сверяем содержимое, владельца root:root и права 0755:
.\adb -s $Target shell "su -c 'ls -l /data/adb/service.d/99-q13w-su-access.sh; cat /data/adb/service.d/99-q13w-su-access.sh'"
В полном дампе файла не должно быть 0d. Перевод строки LF выглядит как 0a. Проверяем весь файл: у нас ошибка пряталась после первых 64 байт.
Ручная проверка перед reboot:
.\adb -s $Target shell "su -c '/data/adb/service.d/99-q13w-su-access.sh'"
.\adb -s $Target shell "su -c 'ls -ldZ /system/xbin; cat /proc/mounts | grep system/xbin'"
Успех у нас выглядел так: каталог drwxr-xr-x, а строка mount для /system/xbin снова tmpfs ro. Скрипт открывает на запись только этот Magisk-mount, выставляет 0755 и возвращает его в read-only.
Если есть ошибка mount, неправильные права или mount остался rw, до обычной перезагрузки сначала исправьте причину. Успешный вывод chmod сам по себе ещё не подтверждает выполнение всей цепочки.
После успешной ручной проверки перезагрузили проектор и, ничего не исправляя через ADB, снова попробовали root-запуск из приложения. Он заработал. Это и было проверкой сохранения результата после загрузки.
Откат этого workaround в журнале не проверялся. Логика отката при сохранённом Magisk-доступе: убрать только этот скрипт из автозапуска, затем проверить поведение после reboot. Не удалять весь /data/adb и не считать это удалением Magisk.
8. Projectivy вместо штатного экрана
Хронологически это получилось ещё до установки Magisk. Поэтому для одной замены лаунчера проделывать весь root-квест может вообще не понадобиться.
Projectivy был заранее установлен на проектор. Программа разработчика:
https://github.com/spocky/miproja1
Релизы:
https://github.com/spocky/miproja1/releases
Точная первоначальная установка APK в доступном тексте не показана, поэтому отдельную команду adb install за «исторически проверенную» не выдаю. Перед изменением HOME установленный Projectivy нужно открыть вручную и проверить управление пультом.
У нас его HOME-компонент:
com.spocky.projengmenu/.ui.home.MainActivity
Добавим проверку пути пакета через pm path; затем проверим HOME-компонент и назначим его только после успешного ручного запуска:
.\adb -s $Target shell pm path com.spocky.projengmenu
.\adb -s $Target shell cmd package query-activities --brief -a android.intent.action.MAIN -c android.intent.category.HOME -p com.spocky.projengmenu
.\adb -s $Target shell cmd package set-home-activity --user 0 com.spocky.projengmenu/.ui.home.MainActivity
.\adb -s $Target shell cmd package resolve-activity --brief -a android.intent.action.MAIN -c android.intent.category.HOME
Ожидаем Projectivy. Нажимаем HOME на пульте. Если он не открывается, штатные пакеты пока не отключаем.
Первая попытка убрать штатный экран была через пакет TyyLauncher. Перед отключением предлагалось сохранить APK и сведения командами ниже. Отдельного текстового подтверждения их успешного выполнения в журнале нет, поэтому проверку наличия полученных файлов при повторении пропускать нельзя:
.\adb -s $Target pull /system/app/TyyLauncher/TyyLauncher.apk .\q13w_backup\
.\adb -s $Target shell dumpsys package com.chijimlee.tyylauncher > .\q13w_backup\TyyLauncher_dumpsys.txt
Только при совпадении пакета с нашей конфигурацией и рабочей замене:
.\adb -s $Target shell pm disable-user --user 0 com.chijimlee.tyylauncher
.\adb -s $Target shell pm list packages -d
Но после reboot старый экран всё равно появлялся. HOME открывал Projectivy, а автоматический старт жил отдельно.
Здесь помогло не гадание по названию APK, а проверка реально открытой Activity. Сразу после загрузки, до нажатия HOME:
.\adb -s $Target shell dumpsys activity activities | Select-String -Pattern "mResumedActivity|topResumedActivity"
.\adb -s $Target shell dumpsys window | Select-String -Pattern "mCurrentFocus|mFocusedApp"
Select-String здесь редакционная замена фильтра findstr из журнала; команды Android те же.
Виновник оказался другим:
com.zeasn.whale.saas/com.zeasn.whale.open.launcher.technical.ui.MainActivity
Важно: хотя в разговоре мы называли это «отключить активити», фактически применялась команда отключения всего пакета Whale, не одной Activity. Это может затронуть другие функции пакета.
Перед отключением Whale также предлагалось сохранить сведения. Здесь тоже нет отдельного текстового подтверждения выполненного сохранения:
.\adb -s $Target shell pm path com.zeasn.whale.saas
.\adb -s $Target shell dumpsys package com.zeasn.whale.saas > .\q13w_backup\WhaleLauncher_dumpsys.txt
И только после совпадения диагностического результата отключили именно его:
.\adb -s $Target shell pm disable-user --user 0 com.zeasn.whale.saas
.\adb -s $Target shell am force-stop com.zeasn.whale.saas
.\adb -s $Target shell pm list packages -d
Экран переключился на Projectivy сразу. После перезагрузки он открылся автоматически. Monet на этапе эксперимента оставался запасным HOME.
Для возврата доступности этих двух пакетов в истории предлагался pm enable. Ниже к нему редакционно добавлен явный --user 0. Выполнение как отдельный тест отката не зафиксировано:
.\adb -s $Target shell pm enable --user 0 com.chijimlee.tyylauncher
.\adb -s $Target shell pm enable --user 0 com.zeasn.whale.saas
enable возвращает доступность, но не обязательно первоначальное manifest-default состояние и не назначает прежний HOME. Исходный HOME возвращается отдельным set-home-activity с записанным вами компонентом, не с чужим именем из поста.
Ничего из этого не означает физическое удаление APK из системного образа. Даже встречавшееся позже pm uninstall -k --user 0 означает удаление установки для пользователя 0 с сохранением данных, а не гарантированное стирание системного APK. Массовый список «удалить всё китайское» здесь намеренно не привожу.
9. Грабли, коротко и по делу
- В разборе ранних скриншотов adb root не сработал; наличие userdebug в свойствах не гарантировало root у adbd. Заводской su оказался отдельным рабочим путём.
- Там же обсуждалось setgid failed при попытке su сменить группу на root. На нашем бинарнике сработало su 0,2000, но это не универсальный синтаксис для всех su.
- su не сделал все диагностические операции доступными. Были ошибки lpdump/dmctl и ограничения capabilities; отсутствие прочитанной информации нельзя превращать в вывод «проверок загрузки нет».
- Backup в /sdcard не записывался. Через /data/local/tmp получилось, после чего проверяли размеры и хеши.
- pm однажды был введён прямо в PowerShell. Это команда Android: ей нужен adb shell. Строки PS C:\... и Markdown-разметку также не копируем.
- Вложенные кавычки, пайпы и переменные PowerShell ломали Android-команды. Для фильтра путей надёжнее оказался Select-String -SimpleMatch, а длинные скрипты проще передавать файлом.
- set-home-activity изменил HOME, но не убрал OEM-автостарт. TyyLauncher отключили, а реально стартовал Whale. Выяснили через dumpsys.
- disabled-user не гарантировал исчезновение всех уже работающих или persistent-процессов. Ещё и опечатка tylauncher вместо tyylauncher однажды дала ложный «пустой» поиск.
- После Magisk root в ADB ещё не означал root для приложений: мешали права 0750 на /system/xbin.
- Одноразовый chmod пережил только текущую загрузку. Исправление перенесли в service.d.
- Первый скрипт имел CRLF только в последней строке. Проверка первых 64 байт ошибочно объявила файл чистым; полный od нашёл 0d 0a. После явного LF скрипт отработал правильно.
- При анализе образа переменная $input столкнулась с автоматической переменной PowerShell и дала пустой файл; помогло другое имя потока. В сtar случайно попала кириллическая буква. Эти опечатки и неудачные варианты в рабочую последовательность не включены.
- В поздней диагностике были неудачные эксперименты с thermal-командой. К root и Projectivy они отношения не имеют. Разгон, отключение термозащиты и изменение вентиляторов здесь не нужны.
10. Что в итоге действительно проверили
После перезагрузок подтвердились:
- root через Magisk и запрос прав из приложения;
- Projectivy автоматически открывается после загрузки;
- кнопка HOME возвращает Projectivy;
- автофокус работает;
- автоматическая коррекция трапеции работает;
- TorrServe запускается автоматически.
Фирменные сервисы фокуса, геометрии, пульта и входов нельзя удалять только потому, что имя пакета непонятное. Перед повторением нужен собственный контроль: настройки проектора, HOME, холодный старт, сон/пробуждение, HDMI, звук и пульт. Полная проверка всех этих режимов в доступном журнале не зафиксирована, поэтому не буду приписывать её нашему эксперименту.
После работы выключите сетевую отладку на проекторе и проверьте, что подключение больше не доступно. adb disconnect или adb kill-server выключают не слушающий сервис на проекторе, а соединение/сервер на ПК. Они не закрывают вопрос безопасности устройства.
Получился свой удобный экран и управляемый root, а не кастомная прошивка с нуля. Самой сложной частью оказался не Magisk, а необходимость каждый раз проверять, что реально произошло. В одном месте между «почти работает» и «работает после reboot» стоял ровно один невидимый CR.
Материал подготовлен с помощью ИИ по моему журналу команд и результатам эксперимента. Где нет проверенного шага или результата, это указано отдельно.
Q13W: историческая проверка структуры stock и patched boot
Это приложение к полному разбору, а не самостоятельная инструкция прошивки.
Код ниже читает локальные файлы на ПК и выводит поля заголовка и несколько участков образа. Он не подключается к проектору и не записывает его разделы. Код взят из рабочего этапа переписки; последующий пользовательский вывод подтверждает его выполнение на нашей паре образов. Здесь заново на образах он не запускался: сами файлы boot в этой выгрузке отсутствуют.
Ограничения:
- Это узкий диагностический скрипт для нашей структуры Android boot image v0, не универсальный parser/validator любых boot/init_boot/recovery.
- Перед применением подтвердите формат и версию заголовка. При другом формате, отсутствии файлов или неожиданном выводе остановитесь, а не используйте результаты для разрешения прошивки.
- Сравниваются поля размера, сигнатуры и выбранные участки, не весь kernel. Равенство KernelSize не доказывает равенство содержимого ядра.
- Хвост проверяется в нескольких местах. Нулевые показанные участки не означают, что весь хвост нулевой.
- Скрипт не устанавливает совместимость с аппаратной ревизией и не проверяет возможность восстановления.
- Замените имя magisk_patched-30700_IYiMI.img на имя собственного результата Magisk. Не используйте чужой patched image.
- Выполнять в PowerShell из папки, содержащей q13w_backup. Не копировать приглашения консоли или текст пояснений.
В нашем сохранённом пользовательском выводе:
- Length: 62914560 у обоих файлов.
- Magic: ANDROID! у обоих.
- HeaderVersion: 0 у обоих.
- PageSize: 16384 у обоих.
- KernelSize: 45054464 у обоих. Это только размер.
- RamdiskSize: 1369094 → 1443114.
- SecondSize: 0 у обоих.
- RamdiskOffset: 45072384 у обоих.
- PayloadEnd: 46448640 → 46530560.
- RamdiskMagic начинается с 1f 8b: gzip у обоих.
- TailStart64 у stock заполнен FF, у patched нулями.
- After208K64 и Last64 показаны нулевыми у обоих.
КОД:
function Align-Up([long]$Value, [long]$Alignment) {
return [long]([Math]::Ceiling($Value / [double]$Alignment) * $Alignment)
}
function Inspect-Boot([string]$Path) {
$fs = [System.IO.File]::OpenRead($Path)
try {
$hdr = New-Object byte[] 64
$null = $fs.Read($hdr, 0, 64)
$magic = [System.Text.Encoding]::ASCII.GetString($hdr,0,8)
$kernelSize = [BitConverter]::ToUInt32($hdr,8)
$ramdiskSize= [BitConverter]::ToUInt32($hdr,16)
$secondSize = [BitConverter]::ToUInt32($hdr,24)
$pageSize = [BitConverter]::ToUInt32($hdr,36)
$headerVer = [BitConverter]::ToUInt32($hdr,40)
$kernelOffset = $pageSize
$ramdiskOffset = $pageSize + (Align-Up $kernelSize $pageSize)
$secondOffset = $ramdiskOffset + (Align-Up $ramdiskSize $pageSize)
$payloadEnd = $secondOffset + (Align-Up $secondSize $pageSize)
$null = $fs.Seek($ramdiskOffset,[System.IO.SeekOrigin]::Begin)
$rmagic = New-Object byte[] 8
$null = $fs.Read($rmagic,0,8)
$null = $fs.Seek($payloadEnd,[System.IO.SeekOrigin]::Begin)
$tail1 = New-Object byte[] 64
$null = $fs.Read($tail1,0,64)
$after208 = $payloadEnd + 212992
$null = $fs.Seek($after208,[System.IO.SeekOrigin]::Begin)
$tail2 = New-Object byte[] 64
$null = $fs.Read($tail2,0,64)
$null = $fs.Seek(-64,[System.IO.SeekOrigin]::End)
$last = New-Object byte[] 64
$null = $fs.Read($last,0,64)
[PSCustomObject]@{
File = [System.IO.Path]::GetFileName($Path)
Length = $fs.Length
Magic = $magic
KernelSize = $kernelSize
RamdiskSize = $ramdiskSize
SecondSize = $secondSize
PageSize = $pageSize
HeaderVersion = $headerVer
RamdiskOffset = $ramdiskOffset
PayloadEnd = $payloadEnd
RamdiskMagic = (($rmagic | ForEach-Object { $_.ToString("x2") }) -join " ")
TailStart64 = (($tail1 | ForEach-Object { $_.ToString("x2") }) -join " ")
After208K64 = (($tail2 | ForEach-Object { $_.ToString("x2") }) -join " ")
Last64 = (($last | ForEach-Object { $_.ToString("x2") }) -join " ")
}
}
finally {
$fs.Close()
}
}
$stock = ".\q13w_backup\q13w_raw_critical\boot.img"
$patched = ".\q13w_backup\magisk_patched-30700_IYiMI.img"
Inspect-Boot $stock | Format-List
Inspect-Boot $patched | Format-List
Авторские материалы 220 IQ
Мой канал: https://t.me/the220iq
Полный разбор с точными командами, backup и предупреждениями: https://t.me/the220iq/17?single
Проверка структуры boot, полный код и ограничения: https://t.me/the220iq/18?single
















