commit 45967534aa87adf974c4178f74c8579a6f5dc793 Author: Paravis Date: Sat Nov 7 20:40:49 2026 +0300 Windows installer 2.0 (C++ port) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..87920e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Build output +build/ +build_x64/ +build_x86/ +out/ +bin/ +obj/ +*.exe +*.dll +*.lib +*.exp +*.obj +*.pdb +*.ilk +*.res +*.tlog +*.idb + +# Installation images and archives (never committed) +Data.pf +*.wim +*.esd +*.iso +*.pf + +# CMake +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake + +# Visual Studio +.vs/ +*.vcxproj* +*.sln +*.user +*.suo + +# Logs, temporary and debug files +*.log +*.tmp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..04c88db --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.20) + +project(WindowsSetupCpp LANGUAGES CXX) + +enable_language(RC) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +add_executable(WindowsSetup WIN32 + src/main.cpp + src/util.cpp + app.rc +) + +set_property(TARGET WindowsSetup PROPERTY MSVC_RUNTIME_LIBRARY + "MultiThreaded$<$:Debug>") + +target_compile_definitions(WindowsSetup PRIVATE + UNICODE + _UNICODE + WIN32_LEAN_AND_MEAN + NOMINMAX +) + +target_compile_options(WindowsSetup PRIVATE + /utf-8 + /W4 + /permissive- +) + +target_link_libraries(WindowsSetup PRIVATE + user32 + gdi32 + gdiplus + ole32 +) + +target_link_options(WindowsSetup PRIVATE /MANIFEST:NO) + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set_target_properties(WindowsSetup PROPERTIES OUTPUT_NAME "setup_x64") +else() + set_target_properties(WindowsSetup PROPERTIES OUTPUT_NAME "setup_x86") +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..814759f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Paravis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b49acd6 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# Windows Setup by Paravis — C++ port + +Native Win32 port of `Windows_installer_NEW\setup.au3`. + +## Build + +Requirements: Visual Studio 2022 (Desktop C++) and CMake 3.20+. + +```bat +build.cmd +``` + +Outputs: + +``` +build_x64\Release\setup_x64.exe +build_x86\Release\setup_x86.exe +``` + +The executables use a static CRT, are GUI subsystem, and use the same +`asInvoker` manifest / Common Controls 6 as the AutoIt builds. + +## Preview + +- `setup_x64.exe /preview` or an executable named `setup_preview*.exe` + starts preview mode: fixed 307×259 position, all buttons enabled, no + `src_*.exe` helper, preview wallpaper. +- The ported `setup_preview.exe` in `..\Windows_installer_NEW` is the x64 + build used this way. + +## Deployment + +On a PARAVIS flash the two Windows Setup WIMs are: + +- `\efi\Paravis\Boot\ws64.wim`, image 1, `\Windows\Setup.exe` — x64 build +- `\efi\Paravis\Boot\ws86.wim`, image 1, `\Windows\Setup.exe` — x86 build + +`Winpeshl.ini` in both WIMs already launches `%SYSTEMROOT%\setup.exe`. +The launcher starts the real setup from `X:\sources\setup.exe`. + +The window is layered with color key `0xABCDEF`. The Montserrat fonts are +embedded as RCDATA; the 1024×768 wallpaper is embedded as a lossless PNG and +decoded with GDI+ at startup. This keeps each executable around 1 MB. +The UI is the same 376/410/360-wide layout as the AutoIt version, with native +buttons. GDI+ and ole32 are standard components of the Windows Setup boot.wim +(verified in the target image). + +## Behaviour + +- Searches all drives for `\WindowsESD\NOT.DEL` or + `\efi\Paravis\Windows\NOT.DEL`. +- Warns and exits if both image folders are present. +- Finds `\efi\Paravis\Boot\NOT.DEL` for `src_x64.exe` / `src_x86.exe` + and Total Commander. +- Uses `%WINDIR%\SYSWOW64` to select the x64 UI; `firmware_type` selects + Legacy vs UEFI for the x86 UI. +- Builds the same `X:\sources\setup.exe /installfrom:... /m:... [/unattend:...]` + command lines as AutoIt. +- Disables Windows buttons when the corresponding ESD image is missing. +- Reboot button exits the launcher (the WinPE startup script performs the + reboot). +- Debug log: set `PARAVIS_DEBUG_LOG=1` to write + `%TEMP%\winsetup_cpp_debug.log`. + +## Repository layout + +``` +src/ C++ sources and resources +assets/ fonts, wallpapers and UI images (incl. Montserrat OFL) +legacy-au3/ original AutoIt scripts +app.rc resource script +app.manifest application manifest +build.cmd release build (x64 + x86) +``` + +## License + +MIT for the program code. See [`LICENSE`](LICENSE). + +Montserrat is licensed under the SIL Open Font License 1.1; see +[`assets/OFL.txt`](assets/OFL.txt) and [`THIRD-PARTY.md`](THIRD-PARTY.md). + +This repository contains source code only. Windows installation images +(`*.wim`, `*.esd`, `Data.pf`), Microsoft font derivatives and third-party +binaries are not distributed here. diff --git a/THIRD-PARTY.md b/THIRD-PARTY.md new file mode 100644 index 0000000..fb6da6f --- /dev/null +++ b/THIRD-PARTY.md @@ -0,0 +1,35 @@ +# Third-party components + +## Montserrat + +Files: + +- `assets/Montserrat-Regular.ttf` +- `assets/Montserrat-Bold.ttf` + +Copyright 2024 The Montserrat.Git Project Authors +(https://github.com/JulietaUla/Montserrat.git) + +Licensed under the SIL Open Font License, Version 1.1. +The full license text is in [`assets/OFL.txt`](assets/OFL.txt) and is also +available at . + +The fonts are embedded into the built executables as `RCDATA` resources so +that the installer does not depend on system fonts. + +## Images and icons + +`assets/*.png`, `assets/*.bmp`, `assets/*.jpg` and `sec.ico` are Paravis +artwork used by the installer UI. + +## Not included + +This repository contains source code and build assets only. It does not +contain, and must not be used to redistribute: + +- Microsoft installation images (`*.wim`, `*.esd`, `*.iso`), including the + `Data.pf` archive; +- Microsoft font derivatives (`tahoma*.pf2`, `segoeui18.pf2`); +- Total Commander or any other third-party binaries; +- signing keys, certificates or signed binaries; +- build output (`*.exe`, object files, logs). diff --git a/app.manifest b/app.manifest new file mode 100644 index 0000000..68227d5 --- /dev/null +++ b/app.manifest @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app.rc b/app.rc new file mode 100644 index 0000000..09e5901 --- /dev/null +++ b/app.rc @@ -0,0 +1,9 @@ +#include + +1 ICON "sec.ico" +1 24 "app.manifest" + +101 RCDATA "assets\\Montserrat-Regular.ttf" +102 RCDATA "assets\\Montserrat-Bold.ttf" + +201 RCDATA "assets\\wallpaper-install.png" diff --git a/assets/Montserrat-Bold.ttf b/assets/Montserrat-Bold.ttf new file mode 100644 index 0000000..011e811 Binary files /dev/null and b/assets/Montserrat-Bold.ttf differ diff --git a/assets/Montserrat-Regular.ttf b/assets/Montserrat-Regular.ttf new file mode 100644 index 0000000..a4a800c Binary files /dev/null and b/assets/Montserrat-Regular.ttf differ diff --git a/assets/OFL.txt b/assets/OFL.txt new file mode 100644 index 0000000..4515774 --- /dev/null +++ b/assets/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2024 The Montserrat.Git Project Authors (https://github.com/JulietaUla/Montserrat.git) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/background-install.bmp b/assets/background-install.bmp new file mode 100644 index 0000000..7599e02 Binary files /dev/null and b/assets/background-install.bmp differ diff --git a/assets/background-install.jpg b/assets/background-install.jpg new file mode 100644 index 0000000..0253e35 Binary files /dev/null and b/assets/background-install.jpg differ diff --git a/assets/background-install.png b/assets/background-install.png new file mode 100644 index 0000000..fb12130 Binary files /dev/null and b/assets/background-install.png differ diff --git a/assets/background-src.png b/assets/background-src.png new file mode 100644 index 0000000..e076673 Binary files /dev/null and b/assets/background-src.png differ diff --git a/assets/background-window.bmp b/assets/background-window.bmp new file mode 100644 index 0000000..856de3a Binary files /dev/null and b/assets/background-window.bmp differ diff --git a/assets/background-window.jpg b/assets/background-window.jpg new file mode 100644 index 0000000..b165f32 Binary files /dev/null and b/assets/background-window.jpg differ diff --git a/assets/background-window.png b/assets/background-window.png new file mode 100644 index 0000000..ea0b210 Binary files /dev/null and b/assets/background-window.png differ diff --git a/assets/sel_bar.png b/assets/sel_bar.png new file mode 100644 index 0000000..9654edc Binary files /dev/null and b/assets/sel_bar.png differ diff --git a/assets/sel_c.png b/assets/sel_c.png new file mode 100644 index 0000000..5a6daae Binary files /dev/null and b/assets/sel_c.png differ diff --git a/assets/sel_e.png b/assets/sel_e.png new file mode 100644 index 0000000..26448a3 Binary files /dev/null and b/assets/sel_e.png differ diff --git a/assets/sel_w.png b/assets/sel_w.png new file mode 100644 index 0000000..25553ed Binary files /dev/null and b/assets/sel_w.png differ diff --git a/assets/wallpaper-install.bmp b/assets/wallpaper-install.bmp new file mode 100644 index 0000000..f47d85d Binary files /dev/null and b/assets/wallpaper-install.bmp differ diff --git a/assets/wallpaper-install.jpg b/assets/wallpaper-install.jpg new file mode 100644 index 0000000..2c29031 Binary files /dev/null and b/assets/wallpaper-install.jpg differ diff --git a/assets/wallpaper-install.png b/assets/wallpaper-install.png new file mode 100644 index 0000000..56a6c46 Binary files /dev/null and b/assets/wallpaper-install.png differ diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..e003d2e --- /dev/null +++ b/build.cmd @@ -0,0 +1,18 @@ +@echo off +setlocal +cd /d "%~dp0" + +cmake -S . -B build_x64 -G "Visual Studio 17 2022" -A x64 +if errorlevel 1 exit /b 1 +cmake --build build_x64 --config Release +if errorlevel 1 exit /b 1 + +cmake -S . -B build_x86 -G "Visual Studio 17 2022" -A Win32 +if errorlevel 1 exit /b 1 +cmake --build build_x86 --config Release +if errorlevel 1 exit /b 1 + +echo. +echo Built: +echo %~dp0build_x64\Release\setup_x64.exe +echo %~dp0build_x86\Release\setup_x86.exe diff --git a/legacy-au3/README-legacy.md b/legacy-au3/README-legacy.md new file mode 100644 index 0000000..0ceaa6f --- /dev/null +++ b/legacy-au3/README-legacy.md @@ -0,0 +1,35 @@ +# Windows installer (legacy AutoIt) + +Исходный текст оригинальной версии на AutoIt (`setup.au3`, +`setup-preview.au3`). Поддерживаемая версия — C++-порт в корне репозитория +(`src/`, `assets/`, `CMakeLists.txt`). + +## Сборка C++ версии + +Из корня репозитория: + +``` +build.cmd +``` + +Получаются `build_x64\Release\setup_x64.exe` и +`build_x86\Release\setup_x86.exe`. `setup_preview.exe` — x64-сборка в режиме +предпросмотра (режим определяется по имени файла или ключу `/preview`). + +## Размещение на флешке PARAVIS + +- `\efi\Paravis\Boot\ws64.wim` — образ 1, `\Windows\Setup.exe` (x64) +- `\efi\Paravis\Boot\ws86.wim` — образ 1, `\Windows\Setup.exe` (x86) + +`Winpeshl.ini` в этих WIM указывает на `%SYSTEMROOT%\setup.exe`. + +Образы установки могут размещаться на любом диске, но с сохранением путей. +Если путей больше одного, будет ошибка. + +> Первый путь — `../efi/Paravis/Windows` +> Второй путь — `../WindowsESD` + +Также нужно создать файл `NOT.DEL` (можно пустой) в двух местах: + +1. по пути, где лежат образы ESD; +2. по пути `../efi/Paravis/Boot/`. diff --git a/legacy-au3/setup-preview.au3 b/legacy-au3/setup-preview.au3 new file mode 100644 index 0000000..e9caf16 --- /dev/null +++ b/legacy-au3/setup-preview.au3 @@ -0,0 +1,240 @@ +#NoTrayIcon +#include +#include +#include +#include +#include +#include + +Global $aArray = DriveGetDrive($DT_ALL), $sDisk, $iBtn11, $iBtn10, $iBtn7, $PathEr, $sDiskS, $UN11, $UN10x64, $UN10x86, $UN7x64, $UN7x86 +Global $bFontOk = False, $font = "Segoe UI" + +_LoadFonts() +$font = _Font() + +For $i = 1 To $aArray[0] + $aFileList = _FileListToArrayRec($aArray[$i] & "\WindowsESD", "NOT.DEL", $FLTAR_FILES, $FLTAR_RECUR, $FLTAR_NOSORT, $FLTAR_FULLPATH) + If Not @error Then + $sDisk = $aArray[$i] & "\WindowsESD\" + $PathEr = 1 + ExitLoop + EndIf +Next +For $i = 1 To $aArray[0] + $aFileList = _FileListToArrayRec($aArray[$i] & "\efi\Paravis\Windows", "NOT.DEL", $FLTAR_FILES, $FLTAR_RECUR, $FLTAR_NOSORT, $FLTAR_FULLPATH) + If Not @error Then + $sDisk = $aArray[$i] & "\efi\Paravis\Windows\" + $PathEr += 1 + ExitLoop + EndIf +Next +If $PathEr > 1 Then + MsgBox(48, 'Предупреждение!', 'В системе больше одной папки с образами. Проверьте и удалите лишние (читаем ReadMe.txt). Система будет перезагружена.') + Exit +EndIf +For $i = 1 To $aArray[0] + $aFileList = _FileListToArrayRec($aArray[$i] & "\efi\Paravis\Boot", "NOT.DEL", $FLTAR_FILES, $FLTAR_RECUR, $FLTAR_NOSORT, $FLTAR_FULLPATH) + If Not @error Then + $sDiskS = $aArray[$i] & "\efi\Paravis\Boot" + ExitLoop + EndIf +Next + +$ESD11 = $sDisk & "install_11.esd" +$ESD10x64 = $sDisk & "install_10_x64.esd" +$ESD10x86 = $sDisk & "install_10_x86.esd" +$ESD7x64 = $sDisk & "install_7_x64.esd" +$ESD7x86 = $sDisk & "install_7_x86.esd" +If FileExists($sDisk & "autounattend\unattend11.xml") Then + $UN11 = " /unattend:" & $sDisk & "autounattend\unattend11.xml" +Else + $UN11 = '' +EndIf +If FileExists($sDisk & "autounattend\unattend10x64.xml") Then + $UN10x64 = " /unattend:" & $sDisk & "autounattend\unattend10x64.xml" +Else + $UN10x64 = '' +EndIf +If FileExists($sDisk & "autounattend\unattend10x86.xml") Then + $UN10x86 = " /unattend:" & $sDisk & "autounattend\unattend10x86.xml" +Else + $UN10x86 = '' +EndIf +If FileExists($sDisk & "autounattend\unattend7x64.xml") Then + $UN7x64 = " /unattend:" & $sDisk & "autounattend\unattend7x64.xml" +Else + $UN7x64 = '' +EndIf +If FileExists($sDisk & "autounattend\unattend7x86.xml") Then + $UN7x86 = " /unattend:" & $sDisk & "autounattend\unattend7x86.xml" +Else + $UN7x86 = '' +EndIf +$OEM11 = " /m:" & $sDisk & "$OEM11$" +$OEM10x64 = " /m:" & $sDisk & "$OEM10x64$" +$OEM10x86 = " /m:" & $sDisk & "$OEM10x86$" +$OEM7x64 = " /m:" & $sDisk & "$OEM7x64$" +$OEM7x86 = " /m:" & $sDisk & "$OEM7x86$" +$setup = "X:\sources\setup.exe /installfrom:" +If FileExists(@WindowsDir & "\SYSWOW64") Then + ; preview skip + _x64() +Else + ; preview skip + _x86() +EndIf + +Func _LoadFonts() + Local $reg = @TempDir & "\Montserrat-Regular.ttf" + Local $bold = @TempDir & "\Montserrat-Bold.ttf" + FileInstall("assets\Montserrat-Regular.ttf", $reg, 1) + FileInstall("assets\Montserrat-Bold.ttf", $bold, 1) + Local $r1 = DllCall("gdi32.dll", "int", "AddFontResourceExW", "wstr", $reg, "dword", 0x10, "ptr", 0) + Local $r2 = DllCall("gdi32.dll", "int", "AddFontResourceExW", "wstr", $bold, "dword", 0x10, "ptr", 0) + If IsArray($r1) And IsArray($r2) Then + If $r1[0] > 0 Or $r2[0] > 0 Then $bFontOk = True + EndIf +EndFunc + +Func _Font() + If $bFontOk Then Return "Montserrat" + Return "Segoe UI" +EndFunc + +Func _x86() + $detect_efi = EnvGet("firmware_type") + If $detect_efi = "Legacy" Then + $hGUI = GUICreate("Windows Setup by Paravis", 410, 250, 307, 259, $WS_POPUP, $WS_EX_LAYERED) + GUISetBkColor(0xABCDEF) + GUICtrlSetState(-1, $GUI_DISABLE) + GUISetState() + _WinAPI_SetLayeredWindowAttributes($hGUI, 0xABCDEF, 255) + $iBtn10 = GUICtrlCreateButton("Установить Windows 10", 5, 5, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtn7 = GUICtrlCreateButton("Установить Windows 7", 5, 45, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtnTC = GUICtrlCreateButton("Total Commander", 5, 105, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtnR = GUICtrlCreateButton("Перезагрузка", 5, 145, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + GUISetState() + _checkesd86() + While 1 + Switch GUIGetMsg() + Case $GUI_EVENT_CLOSE + Case $iBtn10 + GUISetState(@SW_HIDE) + RunWait($setup & $ESD10x86 & $OEM10x86 & $UN10x86) + GUISetState(@SW_SHOW) + Case $iBtn7 + GUISetState(@SW_HIDE) + RunWait($setup & $ESD7x86 & $OEM7x86 & $UN7x86) + GUISetState(@SW_SHOW) + Case $iBtnTC + GUISetState(@SW_HIDE) + RunWait($sDiskS & '\TotalCommander\TOTALCMD.EXE /I="%%COMMANDER_PATH%%\tcmdmain.ini"') + GUISetState(@SW_SHOW) + Case $iBtnR + Exit + EndSwitch + WEnd + Else + If Not FileExists($sDisk & "install_10_x86.esd") Then + MsgBox(48, 'Предупреждение!', 'Не найден образ Windows 10 x86. Система будет перезагружена.') + Exit + EndIf + $hGUI = GUICreate("Windows Setup by Paravis", 360, 190, -1, -1, $WS_POPUP, $WS_EX_LAYERED) + GUISetBkColor(0xABCDEF) + GUICtrlSetState(-1, $GUI_DISABLE) + GUISetState() + _WinAPI_SetLayeredWindowAttributes($hGUI, 0xABCDEF, 255) + $iBtn10 = GUICtrlCreateButton("Установить Windows 10", 5, 5, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtnTC = GUICtrlCreateButton("Total Commander", 5, 65, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtnR = GUICtrlCreateButton("Перезагрузка", 5, 105, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + GUISetState() + While 1 + Switch GUIGetMsg() + Case $GUI_EVENT_CLOSE + Case $iBtn10 + GUISetState(@SW_HIDE) + RunWait($setup & $ESD10x86 & $OEM10x86 & $UN10x86) + GUISetState(@SW_SHOW) + Case $iBtnTC + GUISetState(@SW_HIDE) + RunWait($sDiskS & '\TotalCommander\TOTALCMD.EXE /I="%%COMMANDER_PATH%%\tcmdmain.ini"') + GUISetState(@SW_SHOW) + Case $iBtnR + Exit + EndSwitch + WEnd + EndIf +EndFunc + +Func _x64() + $hGUI = GUICreate("Windows Setup by Paravis", 410, 250, 307, 259, $WS_POPUP, $WS_EX_LAYERED) + GUISetBkColor(0xABCDEF) + GUICtrlSetState(-1, $GUI_DISABLE) + GUISetState() + _WinAPI_SetLayeredWindowAttributes($hGUI, 0xABCDEF, 255) + $iBtn11 = GUICtrlCreateButton("Установить Windows 11", 5, 5, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtn10 = GUICtrlCreateButton("Установить Windows 10", 5, 45, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtn7 = GUICtrlCreateButton("Установить Windows 7", 5, 85, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtnTC = GUICtrlCreateButton("Total Commander", 5, 145, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtnR = GUICtrlCreateButton("Перезагрузка", 5, 185, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + GUISetState() + _checkesd64() + While 1 + Switch GUIGetMsg() + Case $GUI_EVENT_CLOSE + Case $iBtn11 + GUISetState(@SW_HIDE) + RunWait($setup & $ESD11 & $OEM11 & $UN11) + GUISetState(@SW_SHOW) + Case $iBtn10 + GUISetState(@SW_HIDE) + RunWait($setup & $ESD10x64 & $OEM10x64 & $UN10x64) + GUISetState(@SW_SHOW) + Case $iBtn7 + GUISetState(@SW_HIDE) + RunWait($setup & $ESD7x64 & $OEM7x64 & $UN7x64) + GUISetState(@SW_SHOW) + Case $iBtnTC + GUISetState(@SW_HIDE) + RunWait($sDiskS & '\TotalCommander\TOTALCMD64.EXE /I="%%COMMANDER_PATH%%\tcmdmain.ini"') + GUISetState(@SW_SHOW) + Case $iBtnR + Exit + EndSwitch + WEnd +EndFunc + +Func _checkesd64() + Return + If Not FileExists($sDisk & "install_11.esd") Then + GUICtrlSetState($iBtn11, $GUI_DISABLE) + EndIf + If Not FileExists($sDisk & "install_10_x64.esd") Then + GUICtrlSetState($iBtn10, $GUI_DISABLE) + EndIf + If Not FileExists($sDisk & "install_7_x64.esd") Then + GUICtrlSetState($iBtn7, $GUI_DISABLE) + EndIf +EndFunc + +Func _checkesd86() + Return + If Not FileExists($sDisk & "install_10_x86.esd") Then + GUICtrlSetState($iBtn10, $GUI_DISABLE) + EndIf + If Not FileExists($sDisk & "install_7_x86.esd") Then + GUICtrlSetState($iBtn7, $GUI_DISABLE) + EndIf +EndFunc diff --git a/legacy-au3/setup.au3 b/legacy-au3/setup.au3 new file mode 100644 index 0000000..db0c041 --- /dev/null +++ b/legacy-au3/setup.au3 @@ -0,0 +1,249 @@ +#NoTrayIcon +#include +#include +#include +#include +#include +#include + +Global $aArray = DriveGetDrive($DT_ALL), $sDisk, $iBtn11, $iBtn10, $iBtn7, $PathEr, $sDiskS, $UN11, $UN10x64, $UN10x86, $UN7x64, $UN7x86 +Global $bFontOk = False, $font = "Segoe UI", $hBg + +_LoadFonts() +$font = _Font() +_ShowWallpaper() + +For $i = 1 To $aArray[0] + $aFileList = _FileListToArrayRec($aArray[$i] & "\WindowsESD", "NOT.DEL", $FLTAR_FILES, $FLTAR_RECUR, $FLTAR_NOSORT, $FLTAR_FULLPATH) + If Not @error Then + $sDisk = $aArray[$i] & "\WindowsESD\" + $PathEr = 1 + ExitLoop + EndIf +Next +For $i = 1 To $aArray[0] + $aFileList = _FileListToArrayRec($aArray[$i] & "\efi\Paravis\Windows", "NOT.DEL", $FLTAR_FILES, $FLTAR_RECUR, $FLTAR_NOSORT, $FLTAR_FULLPATH) + If Not @error Then + $sDisk = $aArray[$i] & "\efi\Paravis\Windows\" + $PathEr += 1 + ExitLoop + EndIf +Next +If $PathEr > 1 Then + MsgBox(48, 'Предупреждение!', 'В системе больше одной папки с образами. Проверьте и удалите лишние (читаем ReadMe.txt). Система будет перезагружена.') + Exit +EndIf +For $i = 1 To $aArray[0] + $aFileList = _FileListToArrayRec($aArray[$i] & "\efi\Paravis\Boot", "NOT.DEL", $FLTAR_FILES, $FLTAR_RECUR, $FLTAR_NOSORT, $FLTAR_FULLPATH) + If Not @error Then + $sDiskS = $aArray[$i] & "\efi\Paravis\Boot" + ExitLoop + EndIf +Next + +$ESD11 = $sDisk & "install_11.esd" +$ESD10x64 = $sDisk & "install_10_x64.esd" +$ESD10x86 = $sDisk & "install_10_x86.esd" +$ESD7x64 = $sDisk & "install_7_x64.esd" +$ESD7x86 = $sDisk & "install_7_x86.esd" +If FileExists($sDisk & "autounattend\unattend11.xml") Then + $UN11 = " /unattend:" & $sDisk & "autounattend\unattend11.xml" +Else + $UN11 = '' +EndIf +If FileExists($sDisk & "autounattend\unattend10x64.xml") Then + $UN10x64 = " /unattend:" & $sDisk & "autounattend\unattend10x64.xml" +Else + $UN10x64 = '' +EndIf +If FileExists($sDisk & "autounattend\unattend10x86.xml") Then + $UN10x86 = " /unattend:" & $sDisk & "autounattend\unattend10x86.xml" +Else + $UN10x86 = '' +EndIf +If FileExists($sDisk & "autounattend\unattend7x64.xml") Then + $UN7x64 = " /unattend:" & $sDisk & "autounattend\unattend7x64.xml" +Else + $UN7x64 = '' +EndIf +If FileExists($sDisk & "autounattend\unattend7x86.xml") Then + $UN7x86 = " /unattend:" & $sDisk & "autounattend\unattend7x86.xml" +Else + $UN7x86 = '' +EndIf +$OEM11 = " /m:" & $sDisk & "$OEM11$" +$OEM10x64 = " /m:" & $sDisk & "$OEM10x64$" +$OEM10x86 = " /m:" & $sDisk & "$OEM10x86$" +$OEM7x64 = " /m:" & $sDisk & "$OEM7x64$" +$OEM7x86 = " /m:" & $sDisk & "$OEM7x86$" +$setup = "X:\sources\setup.exe /installfrom:" +If FileExists(@WindowsDir & "\SYSWOW64") Then + RunWait($sDiskS & "\src_x64.exe auto2") + _x64() +Else + RunWait($sDiskS & "\src_x86.exe auto2") + _x86() +EndIf + +Func _LoadFonts() + Local $reg = @TempDir & "\Montserrat-Regular.ttf" + Local $bold = @TempDir & "\Montserrat-Bold.ttf" + FileInstall("assets\Montserrat-Regular.ttf", $reg, 1) + FileInstall("assets\Montserrat-Bold.ttf", $bold, 1) + Local $r1 = DllCall("gdi32.dll", "int", "AddFontResourceExW", "wstr", $reg, "dword", 0x10, "ptr", 0) + Local $r2 = DllCall("gdi32.dll", "int", "AddFontResourceExW", "wstr", $bold, "dword", 0x10, "ptr", 0) + If IsArray($r1) And IsArray($r2) Then + If $r1[0] > 0 Or $r2[0] > 0 Then $bFontOk = True + EndIf +EndFunc + +Func _Font() + If $bFontOk Then Return "Montserrat" + Return "Segoe UI" +EndFunc + +Func _ShowWallpaper() + Local $bg = @TempDir & "\paravis_wallpaper.bmp" + FileInstall("assets\wallpaper-install.bmp", $bg, 1) + $hBg = GUICreate("", @DesktopWidth, @DesktopHeight, 0, 0, $WS_POPUP, $WS_EX_TOOLWINDOW) + GUICtrlCreatePic($bg, 0, 0, @DesktopWidth, @DesktopHeight) + GUISetState(@SW_SHOW, $hBg) +EndFunc +Func _x86() + $detect_efi = EnvGet("firmware_type") + If $detect_efi = "Legacy" Then + $hGUI = GUICreate("Windows Setup by Paravis", 410, 250, -1, -1, $WS_POPUP, $WS_EX_LAYERED) + GUISetBkColor(0xABCDEF) + GUICtrlSetState(-1, $GUI_DISABLE) + GUISetState() + _WinAPI_SetLayeredWindowAttributes($hGUI, 0xABCDEF, 255) + WinSetOnTop($hGUI, "", 1) + $iBtn10 = GUICtrlCreateButton("Установить Windows 10", 5, 5, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtn7 = GUICtrlCreateButton("Установить Windows 7", 5, 45, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtnTC = GUICtrlCreateButton("Total Commander", 5, 105, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtnR = GUICtrlCreateButton("Перезагрузка", 5, 145, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + GUISetState() + _checkesd86() + While 1 + Switch GUIGetMsg() + Case $GUI_EVENT_CLOSE + Case $iBtn10 + GUISetState(@SW_HIDE) + RunWait($setup & $ESD10x86 & $OEM10x86 & $UN10x86) + GUISetState(@SW_SHOW) + Case $iBtn7 + GUISetState(@SW_HIDE) + RunWait($setup & $ESD7x86 & $OEM7x86 & $UN7x86) + GUISetState(@SW_SHOW) + Case $iBtnTC + GUISetState(@SW_HIDE) + RunWait($sDiskS & '\TotalCommander\TOTALCMD.EXE /I="%%COMMANDER_PATH%%\tcmdmain.ini"') + GUISetState(@SW_SHOW) + Case $iBtnR + Exit + EndSwitch + WEnd + Else + If Not FileExists($sDisk & "install_10_x86.esd") Then + MsgBox(48, 'Предупреждение!', 'Не найден образ Windows 10 x86. Система будет перезагружена.') + Exit + EndIf + $hGUI = GUICreate("Windows Setup by Paravis", 360, 190, -1, -1, $WS_POPUP, $WS_EX_LAYERED) + GUISetBkColor(0xABCDEF) + GUICtrlSetState(-1, $GUI_DISABLE) + GUISetState() + _WinAPI_SetLayeredWindowAttributes($hGUI, 0xABCDEF, 255) + WinSetOnTop($hGUI, "", 1) + $iBtn10 = GUICtrlCreateButton("Установить Windows 10", 5, 5, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtnTC = GUICtrlCreateButton("Total Commander", 5, 65, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtnR = GUICtrlCreateButton("Перезагрузка", 5, 105, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + GUISetState() + While 1 + Switch GUIGetMsg() + Case $GUI_EVENT_CLOSE + Case $iBtn10 + GUISetState(@SW_HIDE) + RunWait($setup & $ESD10x86 & $OEM10x86 & $UN10x86) + GUISetState(@SW_SHOW) + Case $iBtnTC + GUISetState(@SW_HIDE) + RunWait($sDiskS & '\TotalCommander\TOTALCMD.EXE /I="%%COMMANDER_PATH%%\tcmdmain.ini"') + GUISetState(@SW_SHOW) + Case $iBtnR + Exit + EndSwitch + WEnd + EndIf +EndFunc + +Func _x64() + $hGUI = GUICreate("Windows Setup by Paravis", 410, 250, -1, -1, $WS_POPUP, $WS_EX_LAYERED) + GUISetBkColor(0xABCDEF) + GUICtrlSetState(-1, $GUI_DISABLE) + GUISetState() + _WinAPI_SetLayeredWindowAttributes($hGUI, 0xABCDEF, 255) + WinSetOnTop($hGUI, "", 1) + $iBtn11 = GUICtrlCreateButton("Установить Windows 11", 5, 5, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtn10 = GUICtrlCreateButton("Установить Windows 10", 5, 45, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtn7 = GUICtrlCreateButton("Установить Windows 7", 5, 85, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtnTC = GUICtrlCreateButton("Total Commander", 5, 145, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + $iBtnR = GUICtrlCreateButton("Перезагрузка", 5, 185, 350, 40) + GUICtrlSetFont(-1, 13, 500, 0, $font) + GUISetState() + _checkesd64() + While 1 + Switch GUIGetMsg() + Case $GUI_EVENT_CLOSE + Case $iBtn11 + GUISetState(@SW_HIDE) + RunWait($setup & $ESD11 & $OEM11 & $UN11) + GUISetState(@SW_SHOW) + Case $iBtn10 + GUISetState(@SW_HIDE) + RunWait($setup & $ESD10x64 & $OEM10x64 & $UN10x64) + GUISetState(@SW_SHOW) + Case $iBtn7 + GUISetState(@SW_HIDE) + RunWait($setup & $ESD7x64 & $OEM7x64 & $UN7x64) + GUISetState(@SW_SHOW) + Case $iBtnTC + GUISetState(@SW_HIDE) + RunWait($sDiskS & '\TotalCommander\TOTALCMD64.EXE /I="%%COMMANDER_PATH%%\tcmdmain.ini"') + GUISetState(@SW_SHOW) + Case $iBtnR + Exit + EndSwitch + WEnd +EndFunc + +Func _checkesd64() + If Not FileExists($sDisk & "install_11.esd") Then + GUICtrlSetState($iBtn11, $GUI_DISABLE) + EndIf + If Not FileExists($sDisk & "install_10_x64.esd") Then + GUICtrlSetState($iBtn10, $GUI_DISABLE) + EndIf + If Not FileExists($sDisk & "install_7_x64.esd") Then + GUICtrlSetState($iBtn7, $GUI_DISABLE) + EndIf +EndFunc + +Func _checkesd86() + If Not FileExists($sDisk & "install_10_x86.esd") Then + GUICtrlSetState($iBtn10, $GUI_DISABLE) + EndIf + If Not FileExists($sDisk & "install_7_x86.esd") Then + GUICtrlSetState($iBtn7, $GUI_DISABLE) + EndIf +EndFunc diff --git a/sec.ico b/sec.ico new file mode 100644 index 0000000..401ca08 Binary files /dev/null and b/sec.ico differ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..8a03b35 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,627 @@ +#include "util.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +const wchar_t* kMainClass = L"ParavisWindowsSetupMain"; +const wchar_t* kWallClass = L"ParavisWindowsSetupWallpaper"; + +const COLORREF CLR_KEY = RGB(0xAB, 0xCD, 0xEF); +const int IDI_APP = 1; +const int IDR_FONT_REGULAR = 101; +const int IDR_FONT_BOLD = 102; +const int IDR_WALLPAPER = 201; + +HINSTANCE g_hInst = nullptr; +HWND g_wallHwnd = nullptr; +HWND g_mainHwnd = nullptr; +HBITMAP g_wallBitmap = nullptr; +int g_wallWidth = 0; +int g_wallHeight = 0; +HBRUSH g_keyBrush = nullptr; +HFONT g_buttonFont = nullptr; +std::wstring g_fontFace = L"Segoe UI"; +std::wstring g_imageRoot; +std::wstring g_bootRoot; +bool g_preview = false; +bool g_is64 = true; +bool g_x86Legacy = false; +bool g_busy = false; +bool g_gdiplusStarted = false; +ULONG_PTR g_gdiplusToken = 0; + +enum class Action { + Win11, + Win10x64, + Win7x64, + Win10x86, + Win7x86, + TotalCmd64, + TotalCmd86, + Reboot +}; + +struct ButtonDef { + int id = 0; + std::wstring text; + int y = 0; + Action action = Action::Reboot; + bool enabled = true; +}; + +std::vector g_buttons; + +void FillRectColor(HDC dc, const RECT& rect, COLORREF color) +{ + HBRUSH brush = CreateSolidBrush(color); + FillRect(dc, &rect, brush); + DeleteObject(brush); +} + +std::wstring Lower(std::wstring value) +{ + for (wchar_t& ch : value) { + ch = static_cast(towlower(ch)); + } + return value; +} + +bool StartsWith(const std::wstring& value, const std::wstring& prefix) +{ + return value.size() >= prefix.size() && + _wcsnicmp(value.c_str(), prefix.c_str(), prefix.size()) == 0; +} + +bool HasPreviewArg() +{ + std::wstring name = Lower(GetExeFileName()); + if (StartsWith(name, L"setup_preview")) { + return true; + } + std::wstring commandLine = Lower(GetCommandLineW()); + return commandLine.find(L"/preview") != std::wstring::npos; +} + +std::wstring FirmwareType() +{ + wchar_t buffer[64] = {}; + DWORD length = GetEnvironmentVariableW(L"firmware_type", buffer, + static_cast(std::size(buffer))); + if (length == 0 || length >= std::size(buffer)) { + return std::wstring(); + } + return std::wstring(buffer, length); +} + +std::wstring LoadFonts() +{ + std::wstring temp = GetTempDir(); + std::wstring regular = temp + L"Montserrat-Regular.ttf"; + std::wstring bold = temp + L"Montserrat-Bold.ttf"; + + bool loaded = false; + if (ExtractResourceToFile(g_hInst, IDR_FONT_REGULAR, RT_RCDATA, regular)) { + if (AddFontResourceExW(regular.c_str(), FR_PRIVATE, nullptr) > 0) { + loaded = true; + } + } + if (ExtractResourceToFile(g_hInst, IDR_FONT_BOLD, RT_RCDATA, bold)) { + if (AddFontResourceExW(bold.c_str(), FR_PRIVATE, nullptr) > 0) { + loaded = true; + } + } + + DebugLog(L"fonts loaded=%d regular=%s bold=%s", loaded ? 1 : 0, + regular.c_str(), bold.c_str()); + return loaded ? L"Montserrat" : L"Segoe UI"; +} + +bool LoadWallpaper() +{ + HRSRC resource = FindResourceW(g_hInst, MAKEINTRESOURCEW(IDR_WALLPAPER), + RT_RCDATA); + if (!resource) { + DebugLog(L"wallpaper PNG resource not found"); + return false; + } + + DWORD size = SizeofResource(g_hInst, resource); + HGLOBAL loaded = LoadResource(g_hInst, resource); + const void* data = loaded ? LockResource(loaded) : nullptr; + if (!data || size == 0) { + DebugLog(L"wallpaper PNG resource load failed"); + return false; + } + + HGLOBAL memory = GlobalAlloc(GMEM_MOVEABLE, size); + if (!memory) { + return false; + } + void* destination = GlobalLock(memory); + if (!destination) { + GlobalFree(memory); + return false; + } + memcpy(destination, data, size); + GlobalUnlock(memory); + + IStream* stream = nullptr; + if (FAILED(CreateStreamOnHGlobal(memory, TRUE, &stream))) { + GlobalFree(memory); + return false; + } + + Gdiplus::Bitmap bitmap(stream, FALSE); + if (bitmap.GetLastStatus() == Gdiplus::Ok) { + HBITMAP handle = nullptr; + if (bitmap.GetHBITMAP(Gdiplus::Color(255, 255, 255), &handle) == + Gdiplus::Ok && handle) { + g_wallBitmap = handle; + g_wallWidth = static_cast(bitmap.GetWidth()); + g_wallHeight = static_cast(bitmap.GetHeight()); + } + } + stream->Release(); + + DebugLog(L"wallpaper PNG %dx%d loaded=%d", g_wallWidth, g_wallHeight, + g_wallBitmap ? 1 : 0); + return g_wallBitmap != nullptr; +} + +void PaintWallpaper(HWND hwnd) +{ + PAINTSTRUCT paint{}; + HDC dc = BeginPaint(hwnd, &paint); + RECT client{}; + GetClientRect(hwnd, &client); + + if (g_wallBitmap) { + HDC memory = CreateCompatibleDC(dc); + HGDIOBJ old = SelectObject(memory, g_wallBitmap); + SetStretchBltMode(dc, COLORONCOLOR); + StretchBlt(dc, 0, 0, client.right, client.bottom, + memory, 0, 0, g_wallWidth, g_wallHeight, SRCCOPY); + SelectObject(memory, old); + DeleteDC(memory); + } else { + FillRectColor(dc, client, RGB(0xF0, 0xF0, 0xF0)); + } + EndPaint(hwnd, &paint); +} + +LRESULT CALLBACK WallpaperProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + PaintWallpaper(hwnd); + return 0; + case WM_CLOSE: + return 0; + } + return DefWindowProcW(hwnd, message, wParam, lParam); +} + +bool ContainsMarkerRecursive(const std::wstring& folder, const wchar_t* marker, + int depth) +{ + if (depth > 32 || !DirectoryExists(folder)) { + return false; + } + + WIN32_FIND_DATAW data{}; + HANDLE find = FindFirstFileW((folder + L"\\*").c_str(), &data); + if (find == INVALID_HANDLE_VALUE) { + return false; + } + + bool found = false; + do { + if (wcscmp(data.cFileName, L".") == 0 || + wcscmp(data.cFileName, L"..") == 0) { + continue; + } + + if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if ((data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) { + std::wstring child = folder + L"\\" + data.cFileName; + if (ContainsMarkerRecursive(child, marker, depth + 1)) { + found = true; + break; + } + } + } else if (_wcsicmp(data.cFileName, marker) == 0) { + found = true; + break; + } + } while (FindNextFileW(find, &data)); + + FindClose(find); + return found; +} + +void ShowWarning(const std::wstring& text) +{ + MessageBoxW(g_wallHwnd, text.c_str(), L"Предупреждение!", + MB_ICONWARNING | MB_OK); +} + +bool SearchPaths() +{ + int pathCount = 0; + + for (wchar_t letter = L'A'; letter <= L'Z'; ++letter) { + std::wstring root{ letter, L':', L'\\' }; + if (GetDriveTypeW(root.c_str()) == DRIVE_NO_ROOT_DIR) { + continue; + } + if (ContainsMarkerRecursive(root + L"WindowsESD", L"NOT.DEL", 0)) { + g_imageRoot = root + L"WindowsESD\\"; + ++pathCount; + break; + } + } + + for (wchar_t letter = L'A'; letter <= L'Z'; ++letter) { + std::wstring root{ letter, L':', L'\\' }; + if (GetDriveTypeW(root.c_str()) == DRIVE_NO_ROOT_DIR) { + continue; + } + if (ContainsMarkerRecursive(root + L"efi\\Paravis\\Windows", L"NOT.DEL", 0)) { + g_imageRoot = root + L"efi\\Paravis\\Windows\\"; + ++pathCount; + break; + } + } + + if (pathCount > 1) { + ShowWarning(L"В системе больше одной папки с образами. " + L"Проверьте и удалите лишние (читаем ReadMe.txt). " + L"Система будет перезагружена."); + return false; + } + + for (wchar_t letter = L'A'; letter <= L'Z'; ++letter) { + std::wstring root{ letter, L':', L'\\' }; + if (GetDriveTypeW(root.c_str()) == DRIVE_NO_ROOT_DIR) { + continue; + } + if (ContainsMarkerRecursive(root + L"efi\\Paravis\\Boot", L"NOT.DEL", 0)) { + g_bootRoot = root + L"efi\\Paravis\\Boot"; + break; + } + } + + DebugLog(L"imageRoot=%s bootRoot=%s paths=%d", + g_imageRoot.c_str(), g_bootRoot.c_str(), pathCount); + return true; +} + +bool HasEsd(const wchar_t* name) +{ + return FileExists(g_imageRoot + name); +} + +std::wstring UnattendArgument(const wchar_t* file) +{ + std::wstring path = g_imageRoot + L"autounattend\\" + file; + if (FileExists(path)) { + return L" /unattend:" + path; + } + return std::wstring(); +} + +std::wstring SetupCommand(const wchar_t* esd, const wchar_t* oem, + const wchar_t* unattend) +{ + std::wstring command = L"X:\\sources\\setup.exe /installfrom:" + + g_imageRoot + esd + L" /m:" + g_imageRoot + oem; + command += UnattendArgument(unattend); + return command; +} + +std::wstring TotalCmdCommand() +{ + const wchar_t* exe = g_is64 ? L"TOTALCMD64.EXE" : L"TOTALCMD.EXE"; + return g_bootRoot + L"\\TotalCommander\\" + exe + + L" /I=\"%%COMMANDER_PATH%%\\tcmdmain.ini\""; +} + +void RunInstallerCommand(const std::wstring& command) +{ + DebugLog(L"run: %s", command.c_str()); + g_busy = true; + ShowWindow(g_mainHwnd, SW_HIDE); + RunWait(command, false); + ShowWindow(g_mainHwnd, SW_SHOW); + SetWindowPos(g_mainHwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW); + g_busy = false; +} + +void HandleButton(int id) +{ + if (g_busy) { + return; + } + + const ButtonDef* selected = nullptr; + for (const ButtonDef& button : g_buttons) { + if (button.id == id) { + selected = &button; + break; + } + } + if (!selected) { + return; + } + + switch (selected->action) { + case Action::Win11: + RunInstallerCommand(SetupCommand(L"install_11.esd", L"$OEM11$", + L"unattend11.xml")); + break; + case Action::Win10x64: + RunInstallerCommand(SetupCommand(L"install_10_x64.esd", L"$OEM10x64$", + L"unattend10x64.xml")); + break; + case Action::Win7x64: + RunInstallerCommand(SetupCommand(L"install_7_x64.esd", L"$OEM7x64$", + L"unattend7x64.xml")); + break; + case Action::Win10x86: + RunInstallerCommand(SetupCommand(L"install_10_x86.esd", L"$OEM10x86$", + L"unattend10x86.xml")); + break; + case Action::Win7x86: + RunInstallerCommand(SetupCommand(L"install_7_x86.esd", L"$OEM7x86$", + L"unattend7x86.xml")); + break; + case Action::TotalCmd64: + case Action::TotalCmd86: + RunInstallerCommand(TotalCmdCommand()); + break; + case Action::Reboot: + ExitProcess(0); + break; + } +} + +void BuildButtonDefs() +{ + g_buttons.clear(); + int id = 1001; + + if (g_is64) { + g_buttons.push_back({ id++, L"Установить Windows 11", 5, Action::Win11, + g_preview || HasEsd(L"install_11.esd") }); + g_buttons.push_back({ id++, L"Установить Windows 10", 45, Action::Win10x64, + g_preview || HasEsd(L"install_10_x64.esd") }); + g_buttons.push_back({ id++, L"Установить Windows 7", 85, Action::Win7x64, + g_preview || HasEsd(L"install_7_x64.esd") }); + g_buttons.push_back({ id++, L"Total Commander", 145, Action::TotalCmd64, true }); + g_buttons.push_back({ id++, L"Перезагрузка", 185, Action::Reboot, true }); + } else if (g_x86Legacy) { + g_buttons.push_back({ id++, L"Установить Windows 10", 5, Action::Win10x86, + g_preview || HasEsd(L"install_10_x86.esd") }); + g_buttons.push_back({ id++, L"Установить Windows 7", 45, Action::Win7x86, + g_preview || HasEsd(L"install_7_x86.esd") }); + g_buttons.push_back({ id++, L"Total Commander", 105, Action::TotalCmd86, true }); + g_buttons.push_back({ id++, L"Перезагрузка", 145, Action::Reboot, true }); + } else { + g_buttons.push_back({ id++, L"Установить Windows 10", 5, Action::Win10x86, true }); + g_buttons.push_back({ id++, L"Total Commander", 65, Action::TotalCmd86, true }); + g_buttons.push_back({ id++, L"Перезагрузка", 105, Action::Reboot, true }); + } +} + +void CreateMainWindow() +{ + int width = (g_is64 || g_x86Legacy) ? 410 : 360; + int height = (g_is64 || g_x86Legacy) ? 250 : 190; + int screenWidth = GetSystemMetrics(SM_CXSCREEN); + int screenHeight = GetSystemMetrics(SM_CYSCREEN); + int x = (screenWidth - width) / 2; + int y = (screenHeight - height) / 2; + + if (g_preview && width == 410) { + x = 307; + y = 259; + } + + HDC screen = GetDC(nullptr); + int dpi = GetDeviceCaps(screen, LOGPIXELSY); + ReleaseDC(nullptr, screen); + if (dpi <= 0) { + dpi = 96; + } + + int fontHeight = -(13 * dpi / 72); + g_buttonFont = CreateFontW(fontHeight, 0, 0, 0, 500, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, + DEFAULT_PITCH | FF_DONTCARE, g_fontFace.c_str()); + + g_mainHwnd = CreateWindowExW( + WS_EX_LAYERED, kMainClass, L"Windows Setup by Paravis", + WS_POPUP | WS_CLIPCHILDREN, x, y, width, height, + nullptr, nullptr, g_hInst, nullptr); + if (!g_mainHwnd) { + DebugLog(L"main window creation failed error=%lu", GetLastError()); + return; + } + + SetLayeredWindowAttributes(g_mainHwnd, CLR_KEY, 0, LWA_COLORKEY); + + for (const ButtonDef& button : g_buttons) { + HWND control = CreateWindowExW( + 0, L"BUTTON", button.text.c_str(), + WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, + 5, button.y, 350, 40, + g_mainHwnd, reinterpret_cast(static_cast(button.id)), + g_hInst, nullptr); + if (!control) { + continue; + } + if (g_buttonFont) { + SendMessageW(control, WM_SETFONT, + reinterpret_cast(g_buttonFont), TRUE); + } + if (!button.enabled) { + EnableWindow(control, FALSE); + } + } + + SetWindowPos(g_mainHwnd, HWND_TOPMOST, x, y, width, height, SWP_SHOWWINDOW); + UpdateWindow(g_mainHwnd); + SetForegroundWindow(g_mainHwnd); + DebugLog(L"main window %dx%d at %d,%d buttons=%zu", width, height, x, y, + g_buttons.size()); +} + +void CreateWallpaperWindow() +{ + int width = GetSystemMetrics(SM_CXSCREEN); + int height = GetSystemMetrics(SM_CYSCREEN); + DWORD exStyle = WS_EX_TOOLWINDOW; + + if (g_preview) { + width = 1024; + height = 768; + exStyle |= WS_EX_TOPMOST; + } + + g_wallHwnd = CreateWindowExW( + exStyle, kWallClass, L"", WS_POPUP, + 0, 0, width, height, nullptr, nullptr, g_hInst, nullptr); + if (!g_wallHwnd) { + DebugLog(L"wallpaper window creation failed error=%lu", GetLastError()); + return; + } + + ShowWindow(g_wallHwnd, SW_SHOWNOACTIVATE); + UpdateWindow(g_wallHwnd); + DebugLog(L"wallpaper window %dx%d", width, height); +} + +LRESULT CALLBACK MainProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_ERASEBKGND: { + HDC dc = reinterpret_cast(wParam); + RECT rect{}; + GetClientRect(hwnd, &rect); + FillRectColor(dc, rect, CLR_KEY); + return 1; + } + case WM_COMMAND: + if (HIWORD(wParam) == BN_CLICKED) { + HandleButton(LOWORD(wParam)); + } + return 0; + case WM_CLOSE: + return 0; + case WM_DESTROY: + PostQuitMessage(0); + return 0; + } + return DefWindowProcW(hwnd, message, wParam, lParam); +} + +} // namespace + +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int) +{ + g_hInst = hInstance; + g_preview = HasPreviewArg(); + + Gdiplus::GdiplusStartupInput gdiplusInput; + g_gdiplusStarted = + Gdiplus::GdiplusStartup(&g_gdiplusToken, &gdiplusInput, nullptr) == + Gdiplus::Ok; + + g_keyBrush = CreateSolidBrush(CLR_KEY); + + WNDCLASSEXW wallpaperClass{}; + wallpaperClass.cbSize = sizeof(wallpaperClass); + wallpaperClass.lpfnWndProc = WallpaperProc; + wallpaperClass.hInstance = hInstance; + wallpaperClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); + wallpaperClass.lpszClassName = kWallClass; + wallpaperClass.hbrBackground = static_cast(GetStockObject(BLACK_BRUSH)); + if (!RegisterClassExW(&wallpaperClass)) { + return 1; + } + + WNDCLASSEXW mainClass{}; + mainClass.cbSize = sizeof(mainClass); + mainClass.lpfnWndProc = MainProc; + mainClass.hInstance = hInstance; + mainClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); + mainClass.hIcon = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_APP)); + mainClass.hIconSm = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_APP)); + mainClass.lpszClassName = kMainClass; + mainClass.hbrBackground = g_keyBrush; + if (!RegisterClassExW(&mainClass)) { + return 1; + } + + g_fontFace = LoadFonts(); + LoadWallpaper(); + CreateWallpaperWindow(); + + if (!SearchPaths()) { + return 0; + } + + g_is64 = DirectoryExists(GetWindowsDir() + L"\\SYSWOW64"); + std::wstring firmware = FirmwareType(); + g_x86Legacy = !g_is64 && _wcsicmp(firmware.c_str(), L"Legacy") == 0; + DebugLog(L"is64=%d firmware=%s legacy86=%d", g_is64 ? 1 : 0, + firmware.c_str(), g_x86Legacy ? 1 : 0); + + if (!g_preview && !g_bootRoot.empty()) { + std::wstring source = g_bootRoot + + (g_is64 ? L"\\src_x64.exe" : L"\\src_x86.exe"); + if (FileExists(source)) { + DebugLog(L"running source helper: %s auto2", source.c_str()); + RunWait(source + L" auto2", false); + } else { + DebugLog(L"source helper not found: %s", source.c_str()); + } + } + + if (!g_is64 && !g_x86Legacy) { + if (!FileExists(g_imageRoot + L"install_10_x86.esd")) { + ShowWarning(L"Не найден образ Windows 10 x86. " + L"Система будет перезагружена."); + return 0; + } + } + + BuildButtonDefs(); + CreateMainWindow(); + if (!g_mainHwnd) { + return 1; + } + + MSG message{}; + while (GetMessageW(&message, nullptr, 0, 0) > 0) { + TranslateMessage(&message); + DispatchMessageW(&message); + } + if (g_gdiplusStarted) { + Gdiplus::GdiplusShutdown(g_gdiplusToken); + } + return static_cast(message.wParam); +} diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..b0b697b --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,228 @@ +#include "util.h" + +#include +#include +#include +#include + +std::wstring Format(const wchar_t* fmt, ...) +{ + va_list args; + va_start(args, fmt); + va_list copy; + va_copy(copy, args); + int needed = _vscwprintf(fmt, copy); + va_end(copy); + + if (needed < 0) { + va_end(args); + return std::wstring(); + } + + std::wstring result(static_cast(needed), L'\0'); + if (needed > 0) { + _vsnwprintf_s(result.data(), result.size() + 1, _TRUNCATE, fmt, args); + } + va_end(args); + return result; +} + +std::wstring GetExeDir() +{ + wchar_t buffer[MAX_PATH * 4] = {}; + DWORD length = GetModuleFileNameW(nullptr, buffer, static_cast(std::size(buffer))); + if (length == 0) { + return std::wstring(); + } + std::wstring path(buffer, length); + size_t slash = path.find_last_of(L"\\/"); + if (slash != std::wstring::npos) { + path.resize(slash); + } + return path; +} + +std::wstring GetExeFileName() +{ + wchar_t buffer[MAX_PATH * 4] = {}; + DWORD length = GetModuleFileNameW(nullptr, buffer, static_cast(std::size(buffer))); + if (length == 0) { + return std::wstring(); + } + std::wstring path(buffer, length); + size_t slash = path.find_last_of(L"\\/"); + if (slash != std::wstring::npos) { + path = path.substr(slash + 1); + } + return path; +} + +std::wstring GetWindowsDir() +{ + wchar_t buffer[MAX_PATH] = {}; + UINT length = GetWindowsDirectoryW(buffer, MAX_PATH); + if (length == 0) { + return L"C:\\Windows"; + } + return std::wstring(buffer, length); +} + +std::wstring GetTempDir() +{ + wchar_t buffer[MAX_PATH + 1] = {}; + DWORD length = GetTempPathW(MAX_PATH, buffer); + if (length == 0 || length >= MAX_PATH) { + wchar_t windows[MAX_PATH] = {}; + GetWindowsDirectoryW(windows, MAX_PATH); + return std::wstring(windows) + L"\\Temp\\"; + } + std::wstring path(buffer, length); + if (path.empty() || path.back() != L'\\') { + path.push_back(L'\\'); + } + return path; +} + +bool FileExists(const std::wstring& path) +{ + DWORD attributes = GetFileAttributesW(path.c_str()); + return attributes != INVALID_FILE_ATTRIBUTES && + (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +bool DirectoryExists(const std::wstring& path) +{ + DWORD attributes = GetFileAttributesW(path.c_str()); + return attributes != INVALID_FILE_ATTRIBUTES && + (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +bool ExtractResourceToFile(HINSTANCE instance, int resourceId, const wchar_t* type, + const std::wstring& path) +{ + HRSRC resource = FindResourceW(instance, MAKEINTRESOURCEW(resourceId), type); + if (!resource) { + return false; + } + DWORD size = SizeofResource(instance, resource); + HGLOBAL loaded = LoadResource(instance, resource); + if (!loaded) { + return false; + } + const void* data = LockResource(loaded); + if (!data || size == 0) { + return false; + } + + HANDLE file = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) { + return false; + } + DWORD written = 0; + bool ok = WriteFile(file, data, size, &written, nullptr) != FALSE && written == size; + CloseHandle(file); + return ok; +} + +void PumpMessages() +{ + MSG message{}; + while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) { + if (message.message == WM_QUIT) { + PostQuitMessage(static_cast(message.wParam)); + continue; + } + TranslateMessage(&message); + DispatchMessageW(&message); + } +} + +int RunWait(const std::wstring& commandLine, bool hidden) +{ + STARTUPINFOW startup{}; + startup.cb = sizeof(startup); + DWORD creationFlags = 0; + if (hidden) { + startup.dwFlags = STARTF_USESHOWWINDOW; + startup.wShowWindow = SW_HIDE; + creationFlags = CREATE_NO_WINDOW; + } + + PROCESS_INFORMATION process{}; + std::vector buffer(commandLine.begin(), commandLine.end()); + buffer.push_back(L'\0'); + + if (!CreateProcessW(nullptr, buffer.data(), nullptr, nullptr, FALSE, + creationFlags, nullptr, nullptr, &startup, &process)) { + return -1; + } + + CloseHandle(process.hThread); + for (;;) { + DWORD result = MsgWaitForMultipleObjects(1, &process.hProcess, FALSE, + 30, QS_ALLINPUT); + if (result == WAIT_OBJECT_0) { + break; + } + PumpMessages(); + } + + DWORD exitCode = 1; + GetExitCodeProcess(process.hProcess, &exitCode); + CloseHandle(process.hProcess); + return static_cast(exitCode); +} + +COLORREF ColorFromHex(DWORD rgb) +{ + return RGB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF); +} + +void DebugLog(const wchar_t* fmt, ...) +{ + wchar_t enabled[8] = {}; + if (GetEnvironmentVariableW(L"PARAVIS_DEBUG_LOG", enabled, + static_cast(std::size(enabled))) == 0) { + return; + } + + va_list args; + va_start(args, fmt); + wchar_t message[4096] = {}; + _vsnwprintf_s(message, std::size(message), _TRUNCATE, fmt, args); + va_end(args); + + SYSTEMTIME st{}; + GetLocalTime(&st); + std::wstring line = Format(L"%02d:%02d:%02d.%03d [%lu] %s\r\n", + st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, + GetCurrentThreadId(), message); + + wchar_t temp[MAX_PATH] = {}; + DWORD length = GetTempPathW(MAX_PATH, temp); + std::wstring path; + if (length > 0) { + path.assign(temp, length); + } + path += L"winsetup_cpp_debug.log"; + + int needed = WideCharToMultiByte(CP_UTF8, 0, line.c_str(), + static_cast(line.size()), nullptr, 0, + nullptr, nullptr); + if (needed > 0) { + std::string bytes(static_cast(needed), '\0'); + WideCharToMultiByte(CP_UTF8, 0, line.c_str(), + static_cast(line.size()), bytes.data(), needed, + nullptr, nullptr); + HANDLE file = CreateFileW(path.c_str(), FILE_APPEND_DATA, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file != INVALID_HANDLE_VALUE) { + DWORD written = 0; + WriteFile(file, bytes.data(), static_cast(bytes.size()), + &written, nullptr); + CloseHandle(file); + } + } +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..50e400a --- /dev/null +++ b/src/util.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +std::wstring Format(const wchar_t* fmt, ...); +std::wstring GetExeDir(); +std::wstring GetExeFileName(); +std::wstring GetWindowsDir(); +std::wstring GetTempDir(); +bool FileExists(const std::wstring& path); +bool DirectoryExists(const std::wstring& path); +bool ExtractResourceToFile(HINSTANCE instance, int resourceId, const wchar_t* type, + const std::wstring& path); +int RunWait(const std::wstring& commandLine, bool hidden = false); +void PumpMessages(); +COLORREF ColorFromHex(DWORD rgb); +void DebugLog(const wchar_t* fmt, ...);