Это могла бы быть рождественская история со счастливым концом, но он не такой.
В канун рождества 2018 года заметил в списке проверенных приложений для ReactOS установщик .NET 4.0 и был приятно удивлён, что он успешно устанавливается и приложения запускаются. Но так как 4.0 давно не актуален меня посетила безумная идея — а что будет если попытаться установить версию 4.5?
Установщик сразу заявил что версия ОС не подходит. Запуск в режиме совместимости так же определялся им и устанавливаться отказался. Дела, всё закончилось не начавшись…
Да сейчас версию поменяю и все заработает!
ReactOS разрабатывается как аналог Windows 2003 и версию он сообщает как 5.2. Для установки требуется как минимум 6.0, но тогда я решил что целиться лучше сразу на Windows 7 и стал искать как менять версию на 6.1. В коде по словам version/MajorVersion/MinorVersion нашлось несколько мест по всей системе, даже там где не ожидаешь. Заменив все пары 5.2 на 6.1 результат был нулевой — не стартовал даже установщик ОС. Дальнейший поиск вывел на freeldr и ntldr в boot — в 4 местах надо было заменить _WIN32_WINNT_WS03 на _WIN32_WINNT_WIN7. ОС установилась и сообщает нам что это 6.1. Отлично?! Нет. Почему-то с такой заменой ничего толком не устанавливается — ни VirtualBox Guest Additions, ни Firefox, ни .NET 4.0 и даже проводник падает через раз. Окей, значит надо создать ISO со всем необходимым и подключить его к виртуалке. Слава байтам, привод ещё работает и снова стартуем установщик с него. Ура! Он хотя бы стартует, но жалуется на отсутствие неких компонентов ОС:
Первый это сервис автоматических обновлений, второй — Trusted Installer. Ничего подобного у нас нет — действительно, зачем в ReactOS обновления Windows, а Trusted Installer не может быть по определению ибо он появился вместе с Windows 7. Так же в логе нашлась строка, что отсутствует некий wusa.exe. Ок, создаём заглушки для wusa.exe и wuauserv.dll для сервиса обновлений PR 355. Trusted Installer просто скопировал из Windows 7 32bit вместе с ключами реестра. Теперь установщик определяет наличие всего необходимого и остаётся лишь скрепить договор капелькой крови.
Процесс верификации файлов проходит успешно и стартует установка. Сразу получаем сообщение, что не хватает функции LCMapStringEx в модуле kernel32.
Ок, ищу функцию в коде и, вы удивитесь, она есть но почему-то не добавлена в экспорт-лист (spec-файлы рядом с CMakeLists.txt в корне каждой DLL). Сборка/Установка/Запуск и опять подобная ошибка. Что же, сценарий известен. После повторения этой процедуры 5-10 раз от прощёлкивания Enter-ом установщика нагугливается, что у ОС есть режим unattend-установки. Для этого нужно в файле bootbootdatabootcdunattend.inf включить UnattendSetupEnabled = yes. Супер, сани едут сами! Едут так быстро, что за чаем не успеваешь сходить — минуты две вся установка.
Так как ReactOS пишется как 2003, то код для 6.0+ никто особо не поддерживает. Такие места закрыты условиями #if _WIN32_WINNT >= 0x600, а то и вовсе #if 0 и их приходится приводить в рабочий вид. Недостающую функцию (хотя код её есть @$&^%!) добавить в экспорт (SleepConditionVariableCS требует RtlSleepConditionVariableCS) или где-то в SDK-заголовках разблокировать структуры/поля. Ещё более странный костыль с ntdll, kernel32 и advapi32 — для них зачем-то созданы дополнения в виде ntdll_vista, kernel32_vista и advapi32_vista в каждой из которых максимум 10-15 процедур, при этом в kernel32 есть целых два файла vista.c. Лебедь, рак и щука, не иначе, принимали такие решения. Сейчас определённости так же нет — при выкладке ПРа на гитхабе один просит вынести код в *_vista-либу, второй пишет что достаточно закрыть экспорт условием -version=0x600+ в spec-файле. Больше всего тут удивляет то, что все такие функции это новое API и вполне могут мирно сосуществовать с основным кодом, непонятно зачем городить такой огород.
Продолжались эти попытки на протяжении пары месяцев, но в итоге всё остановилось на том, что установщик зависал в середине процесса ни ругаясь, ни падая.
Быть, а не казаться
Летом решил вернуться к снаряду. Всё-таки столько времени потрачено, а результат нулевой. На этот раз решил делать всё иначе — раз при подмене сообщаемой версии с 5.2 на 6.1 всё работает вкривь и вкось, то надо менять версию совсем с другой стороны — попытаться собрать ReactOS полностью в режиме NT6.
Для этого надо в корневом CMakeLists.txt заменить следующие условия 0x502 на 0x600. Да, тут уже не до жиру, хотя бы 6.0 получить на выходе.
# Version Options
add_definitions(-DWINVER=0x502
-D_WIN32_IE=0x600
-D_WIN32_WINNT=0x502
-D_WIN32_WINDOWS=0x502
-D_SETUPAPI_VER=0x502)
Ожидания успеха от этой затеи изначально были так себе, хотя идея в целом верная.
Суть проблемы в том, что в ядре NT6 многие функции изменили сигнатуры, структуры данных отличаются составом и порядком полей.
#if (_WIN32_WINNT >= 0x600)
NTSTATUS
RxConstructSrvCall(
_In_ PRX_CONTEXT RxContext,
_In_ PIRP Irp,
_In_ PSRV_CALL SrvCall,
_Out_ PLOCK_HOLDING_STATE LockHoldingState);
#else
NTSTATUS
RxConstructSrvCall(
_In_ PRX_CONTEXT RxContext,
_In_ PSRV_CALL SrvCall,
_Out_ PLOCK_HOLDING_STATE LockHoldingState);
#endif
Ошибки сборки сыпались как из рога изобилия, самые простые из которых были в том что в CMake-файлах некоторых dll WINVER и _WIN32_WINNT явно переназначались на иные значения, например 0x602. При этом в ReactOS полно dll, у которых и сейчас WINVER переопределён с 0х502 на 0х600. Нашлось и несколько настоящих багов #356 #359 #747 #814 #815.
Длилось это так же месяц-два и в итоге со всеми костылями образ собрался, но вот установщик не подавал признаков жизни совсем. Помучавшись немного ещё запал пропал
Логика не железная
Грядёт новое рождество, а .NET 4.5 не даёт покоя. Вновь возвращаюсь к первому варианту, повторяются все правки, но с некоторыми изменениями. Если раньше вместо недостающих функций (для которых нет кода в ОС) делал просто заглушки, то теперь решил поискать их в коде Wine и, о чудо, там они были. Перенести, адаптировать под ReactOS, к следующей #1045. Где-то вместо заглушки можно и настоящего кода написать :) #1046. С такими правками установщик работал уже по-бодрее и даже завершался «успешно», но в отличие установщика .NET 4.0 не предлагал выполнить перезагрузку после установки — я списал на то, что все таки софт для нового поколения ОС и нет нужды перезагружаться каждый раз (ха ха, святая наивность). При попытке запустить helloWorld-приложение на экране не происходило ничего, в task manager тоже не успевал заметить какую-то активность.
Немного позже решил покопаться в ресурсах установщика, где нашел текст сообщений об ошибках и ВОТ СЮРПРИЗ — все ограничения указаны в файле ParameterInfo.xml! Достаточно было закомментировать StopBlockers-условие IsInOSCompatibilityMode и всё стартует без проблем. Убираю правки, которые меняют версию ОС с 5.2 на 6.0, включаю в ярлыке к setup-у режим совместимости с Vista (так он не ожидает наличие Trusted Installer) и добавив ещё немного функций установщик завершался так же «успешно». Однако если сперва установить 4.0 и потом запустить установщик 4.5, то процесс завершился уже с просьбой перезагрузиться! Победа!? Нет. Я же говорил что это сказ без хэппи-энда. При попытке запустить HelloWorld результат немного отличается, но не сильно — процесс занимает 11-12МБ памяти и, повисев секунд 20, завершается. Запуск в режиме совместимости не помогает (всё-таки CLR-runtime запускается в каждом процессе отдельно, а не одна общесистемная среда, которая стартует вместе с ОС с версией ОС 5.2).
В логе видим вызовы на определение версии:
Добавил в RtlVerifyVersionInfo хак, что если запрашивается версия 6.*, то подменить версию ОС на 6.0. Отмеченные стрелкой строки ушли, но результат тот же.
Не хэппи-энд.
Заключение
Возможно до успеха осталось совсем немного и вы, вдохновились этим рассказом, готовы подхватить флаг и закончить начатое скачав код ReactOS и собрав её? Не так быстро. Почти по всем ПР на сделанные изменения при их выкладке сперва идёт какое-то обсуждение, но потом они висят без внимания основных разрабов ОС.
Для того что бы повторить описанное надо на мастер-ветку накатить ПРы по ссылкам в тексте, разблокировать пачку функций в spec-файлах (заменить версию в условии -version на 0x500+) и несколько 0x600-условий в заголовках
advapi32
- EventWrite (stub)
- EventRegister (stub)
- EventUnregister (stub)
- RegLoadMUIStringA
- RegLoadMUIStringW
msvcrt
- _except_handler4_common
kernel32
- AcquireSRWLockExclusive
- AcquireSRWLockShared
- CloseThreadpool
- CloseThreadpoolCleanupGroup
- CloseThreadpoolCleanupGroupMembers
- CloseThreadpoolIo
- CloseThreadpoolTimer
- CloseThreadpoolWait
- CloseThreadpoolWork
- SetThreadpoolTimer
- SetThreadpoolWait
- CompareStringEx
- CreateSemaphoreExA (stub)
- CreateSemaphoreExW (stub)
- CreateThreadpool
- CreateThreadpoolCleanupGroup
- CreateThreadpoolIo
- CreateThreadpoolTimer
- CreateThreadpoolWait
- CreateThreadpoolWork
- EnumCalendarInfoExEx
- EnumDateFormatsExEx
- EnumSystemLocalesEx
- EnumTimeFormatsExEx
- FlushProcessWriteBuffers(stub)
- GetCalendarInfoEx
- GetDateFormatEx
- GetLocaleInfoEx
- IsValidLocaleName (stub)
- GetNLSVersionEx (stub)
- GetNumberFormatEx
- GetTickCount64
- GetTimeFormatEx
- GetUserDefaultLocaleName
- LCMapStringEx
- InitOnceExecuteOnce
- InitializeCriticalSectionEx
- InitializeSRWLock
- ReleaseSRWLockExclusive
- ReleaseSRWLockShared
- WerSetFlags (stub)
Некогда собирать, давай сюда сборку!
Если вы следите на развитием ReactOS и пробуете установить свежее приложение, то наверняка сталкивались с тем, что не хватает каких-то функций либо интересный/полезный ПР висит бесконечно. Мне это тоже знакомо и потому решил регулярно составлять сборку ОС с недостающими кусками и ПРами (хочу держать график еженедельно или раз в две недели). Попробуйте эту сборку, возможно она будет полезна! Пишите если столкнётесь с очередной отсутствующей функцией — высока вероятность что она уже лежит в коде ReactOS или есть в Wine. USB-драйвера в ней пока нет.
Скачать можно здесь
С новым годом и стабильного ReactOS-а!
Автор: Getequ