From c538f115f1964adc0b28923c910eb854c48609aa Mon Sep 17 00:00:00 2001 From: Paravis Date: Sat, 7 Nov 2026 20:41:09 +0300 Subject: [PATCH] Bootloader installer 2.0 (C++ port) --- .gitignore | 33 +++ CMakeLists.txt | 44 ++++ LICENSE | 21 ++ README.md | 64 +++++ build.cmd | 9 + legacy-au3/Change_bootloader.au3 | 393 +++++++++++++++++++++++++++++ legacy-au3/README-legacy.md | 7 + src/app.h | 63 +++++ src/app.manifest | 22 ++ src/app.rc | 33 +++ src/boot.cpp | 203 +++++++++++++++ src/main.cpp | 415 +++++++++++++++++++++++++++++++ src/sec.ico | Bin 0 -> 31826 bytes src/util.cpp | 245 ++++++++++++++++++ src/util.h | 17 ++ 15 files changed, 1569 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.cmd create mode 100644 legacy-au3/Change_bootloader.au3 create mode 100644 legacy-au3/README-legacy.md create mode 100644 src/app.h create mode 100644 src/app.manifest create mode 100644 src/app.rc create mode 100644 src/boot.cpp create mode 100644 src/main.cpp create mode 100644 src/sec.ico create mode 100644 src/util.cpp create mode 100644 src/util.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f522017 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Build output +build/ +build_x64/ +build_x86/ +out/ +bin/ +obj/ +*.exe +*.dll +*.lib +*.exp +*.obj +*.pdb +*.ilk +*.res +*.tlog +*.idb + +# 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..c2171a3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.20) + +project(ChangeBootloaderCpp LANGUAGES CXX) + +enable_language(RC) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +add_executable(ChangeBootLoader WIN32 + src/main.cpp + src/boot.cpp + src/util.cpp + src/app.rc +) + +set_property(TARGET ChangeBootLoader PROPERTY MSVC_RUNTIME_LIBRARY + "MultiThreaded$<$:Debug>") + +target_compile_definitions(ChangeBootLoader PRIVATE + UNICODE + _UNICODE + WIN32_LEAN_AND_MEAN + NOMINMAX +) + +target_compile_options(ChangeBootLoader PRIVATE + /utf-8 + /W4 + /permissive- +) + +target_link_libraries(ChangeBootLoader PRIVATE + user32 + gdi32 +) + +# The manifest is embedded through app.rc; do not let the linker add a second one. +target_link_options(ChangeBootLoader PRIVATE /MANIFEST:NO) + +set_target_properties(ChangeBootLoader PROPERTIES + OUTPUT_NAME "Change_BootLoader" +) 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..8c427e3 --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# Change BootLoader — C++ port + +Native Win32 port of `Change_bootloader\Change_bootloader.au3` with the same +window, buttons, actions and messages. + +## Build + +Requirements: Visual Studio 2022 (Desktop C++) and CMake 3.20+. + +```bat +build.cmd +``` + +Output: + +``` +build\Release\Change_BootLoader.exe +``` + +The executable is 32-bit, uses a static CRT (no VC++ redistributable), and +requests administrator rights through the embedded manifest. + +## How it works + +- The program must be started from the root of a PARAVIS flash drive, for + example `F:\Change_BootLoader.exe`. +- It derives the drive from its own path and checks for + `\efi\Paravis\Boot\bootice.exe` and `\efi\Paravis\Boot\NOT.DEL`. +- If those files are missing, it shows the original error and exits. +- Seven buttons perform the same operations as the AutoIt version: + - Legacy BIOS: XorBoot, Bootmgr, Grub4Dos, Grub2 + - UEFI: Bootmgr, Grub2, Xorboot +- Bootice, xcopy and the GRUB2 MBR helper script are invoked exactly like + the AutoIt version. The helper script is + `\efi\Paravis\Boot\grub2\bios\make-paravis-mbr.ps1`. + +## Notes + +- The UI matches the AutoIt version (376 × 208, same fonts, positions, + hover/pressed colors). +- Message boxes are native `MessageBoxW` dialogs with the same titles and + texts. +- The debug log is disabled by default. Set `PARAVIS_DEBUG_LOG=1` to write + `%TEMP%\cbl_cpp_debug.log`. +- The GRUB2 Legacy action uses the same bootice `/sectors` commands as the + AutoIt version. On some virtual disks bootice displays its own sector + confirmation/error dialogs; on a real PARAVIS USB flash it behaves the + same as the original. + +## Repository layout + +``` +src/ C++ sources and resources +legacy-au3/ original AutoIt script +build.cmd release build +``` + +## License + +MIT. See [`LICENSE`](LICENSE). + +This repository contains source code only. Third-party binaries used at +runtime (`bootice.exe`, `xcopy`, `powershell.exe`) are not distributed here +and must be supplied by the system or by the PARAVIS flash image. diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..2e54727 --- /dev/null +++ b/build.cmd @@ -0,0 +1,9 @@ +@echo off +setlocal +cd /d "%~dp0" +cmake -S . -B build -G "Visual Studio 17 2022" -A Win32 +if errorlevel 1 exit /b 1 +cmake --build build --config Release +if errorlevel 1 exit /b 1 +echo. +echo Built: %~dp0build\Release\Change_BootLoader.exe diff --git a/legacy-au3/Change_bootloader.au3 b/legacy-au3/Change_bootloader.au3 new file mode 100644 index 0000000..ec76f51 --- /dev/null +++ b/legacy-au3/Change_bootloader.au3 @@ -0,0 +1,393 @@ +#include +#include +#include +#include +#include +#NoTrayIcon +#RequireAdmin +#pragma compile(Stripper, True) +#pragma compile(ProductVersion, 3.2.0) +#pragma compile(ProductName, "Change bootLoader") +#pragma compile(FileDescription, CBL) +#pragma compile(LegalCopyright, paravis.ru@2024) +#pragma compile(CompanyName, paravis.ru@2024) + +Opt("ExpandEnvStrings", 1) +Opt("GUIOnEventMode", 0) +Opt("MustDeclareVars", 1) + +; --- оформление (как у установщика) +Global Const $CLR_BG = 0xF1F4F9 +Global Const $CLR_HEAD = 0x1E2A44 +Global Const $CLR_DIM = 0x6B7280 +Global Const $CLR_BORDER = 0xBFC9D6 +Global Const $CLR_BTN = 0xFFFFFF +Global Const $CLR_HOVER = 0xE4EEFF +Global Const $CLR_PRESS = 0xCFE0FF +Global Const $CLR_TEXT = 0x14202E + +Global $sDisk = StringLeft(@ScriptDir, 2) +Global $boot = ($sDisk & "\efi\Paravis\Boot") +Global $bootice = ($boot & "\bootice.exe") + +; защита: не даём случайно записать MBR в диск, где нет сборки Paravis +If Not FileExists($bootice) Or Not FileExists($boot & "\NOT.DEL") Then + MsgBox(BitOR($MB_ICONERROR, $MB_OK), "Change BootLoader", "Этот диск не является сборкой Paravis (нет bootice.exe или NOT.DEL):" & @CRLF & $sDisk) + Exit +EndIf + +Global $hMain, $hMin, $hClose, $hTitle, $hSub +Global $aFrame[7], $aBtn[7], $aBg[7], $aName[7] +Global $gDown = -1, $gBusy = False, $gDrag = False, $gDragX = 0, $gDragY = 0 +Global $gMinBg = -1, $gCloseBg = -1 + +; --- окно +$hMain = GUICreate("Change BootLoader", 376, 208, -1, -1, $WS_POPUP, $WS_EX_APPWINDOW) +GUISetBkColor($CLR_BG, $hMain) +GUISetFont(9, 400, 0, "Tahoma", $hMain) + +; шапка +Global $hHead = GUICtrlCreateLabel("", 0, 0, 376, 48) +GUICtrlSetBkColor($hHead, $CLR_HEAD) + +$hTitle = GUICtrlCreateLabel("Paravis Flash", 16, 2, 300, 24) +GUICtrlSetFont($hTitle, 15, 700, 0, "Tahoma") +GUICtrlSetColor($hTitle, 0xFFFFFF) +GUICtrlSetBkColor($hTitle, $CLR_HEAD) + +$hSub = GUICtrlCreateLabel("Смена загрузчика", 18, 27, 300, 14) +GUICtrlSetColor($hSub, 0xC7D0E0) +GUICtrlSetBkColor($hSub, $CLR_HEAD) + +$hMin = GUICtrlCreateLabel("—", 306, 2, 30, 18, BitOR($SS_CENTER, $SS_CENTERIMAGE, $SS_NOTIFY)) +GUICtrlSetFont($hMin, 10, 400, 0, "Tahoma") +GUICtrlSetColor($hMin, 0xC7D0E0) +GUICtrlSetBkColor($hMin, $CLR_HEAD) + +$hClose = GUICtrlCreateLabel("✕", 338, 2, 30, 18, BitOR($SS_CENTER, $SS_CENTERIMAGE, $SS_NOTIFY)) +GUICtrlSetFont($hClose, 9, 400, 0, "Tahoma") +GUICtrlSetColor($hClose, 0xC7D0E0) +GUICtrlSetBkColor($hClose, $CLR_HEAD) + +; --- подписи секций +Local $hSecL = GUICtrlCreateLabel("LEGACY BIOS", 15, 56, 200, 14) +GUICtrlSetFont($hSecL, 8, 700, 0, "Tahoma") +GUICtrlSetColor($hSecL, $CLR_DIM) +GUICtrlSetBkColor($hSecL, $CLR_BG) + +Local $hSecU = GUICtrlCreateLabel("UEFI", 15, 152, 200, 14) +GUICtrlSetFont($hSecU, 8, 700, 0, "Tahoma") +GUICtrlSetColor($hSecU, $CLR_DIM) +GUICtrlSetBkColor($hSecU, $CLR_BG) + +; --- кнопки: рамка 1 px + надпись +$aName[0] = "XorBoot" +$aName[1] = "Bootmgr" +$aName[2] = "Grub4Dos" +$aName[3] = "Grub2" +$aName[4] = "Bootmgr" +$aName[5] = "Grub2" +$aName[6] = "Xorboot" + +Local $aPos[7][4] = [ _ + [14, 72, 170, 32], _ + [192, 72, 170, 32], _ + [14, 110, 170, 32], _ + [192, 110, 170, 32], _ + [14, 168, 110, 32], _ + [132, 168, 110, 32], _ + [250, 168, 110, 32] ] + +Local $i +For $i = 0 To 6 + $aFrame[$i] = GUICtrlCreateLabel("", $aPos[$i][0] - 1, $aPos[$i][1] - 1, $aPos[$i][2] + 2, $aPos[$i][3] + 2) + GUICtrlSetBkColor($aFrame[$i], $CLR_BORDER) + $aBtn[$i] = GUICtrlCreateLabel($aName[$i], $aPos[$i][0], $aPos[$i][1], $aPos[$i][2], $aPos[$i][3], BitOR($SS_CENTER, $SS_CENTERIMAGE, $SS_NOTIFY)) + GUICtrlSetFont($aBtn[$i], 10, 400, 0, "Segoe UI Semibold") + GUICtrlSetColor($aBtn[$i], $CLR_TEXT) + GUICtrlSetBkColor($aBtn[$i], $CLR_BTN) + $aBg[$i] = $CLR_BTN +Next + +GUISetState(@SW_SHOW, $hMain) +AdlibRegister("_Poll", 40) + +While 1 + If GUIGetMsg() = $GUI_EVENT_CLOSE Then Exit + Sleep(30) +WEnd + +; ================= действия ================= + +Func _Run($idx) + If $gBusy Then Return + + ; перед любым действием подтверждаем целевой диск + If Not FileExists($boot & "\NOT.DEL") Or _DiskNumber($sDisk) < 0 Then + _Msg("Целевой диск не подтверждён:" & @CRLF & $sDisk & @CRLF & _ + "нет \efi\Paravis\Boot\NOT.DEL или не найден PhysicalDrive.") + Return + EndIf + + $gBusy = True + Switch $idx + Case 0 + _LegacyXor() + Case 1 + _LegacyMgr() + Case 2 + _LegacyG4d() + Case 3 + _LegacyGrub2() + Case 4 + _UefiMgr() + Case 5 + _UefiGrub2() + Case 6 + _UefiXor() + EndSwitch + $gBusy = False +EndFunc + +Func _LegacyXor() + FileDelete($sDisk & "\bootmgr") + FileDelete($sDisk & "\grldr") + RunWait($bootice & " /DEVICE=" & $sDisk & " /mbr /restore /file=" & $boot & "\xor\xmbr.bin /keep_dpt /quiet") + RunWait($bootice & " /DEVICE=" & $sDisk & " /pbr /restore /file=" & $boot & "\xor\xpbr.bin /keep_bpb /quiet") + _Done("XorBoot") +EndFunc + +Func _LegacyMgr() + FileDelete($sDisk & "\bootmgr") + FileDelete($sDisk & "\grldr") + FileCopy($boot & "\mgr\bootmgr", $sDisk & "\bootmgr") + RunWait($bootice & " /DEVICE=" & $sDisk & " /mbr /install /type=nt60 /quiet") + RunWait($bootice & " /DEVICE=" & $sDisk & " /pbr /install /type=bootmgr /quiet") + _Done("Bootmgr") +EndFunc + +Func _LegacyG4d() + FileDelete($sDisk & "\bootmgr") + FileDelete($sDisk & "\grldr") + FileCopy($boot & "\g4d\grldr", $sDisk & "\grldr") + RunWait($bootice & " /DEVICE=" & $sDisk & " /mbr /install /type=GRUB4DOS /quiet") + RunWait($bootice & " /DEVICE=" & $sDisk & " /pbr /install /type=GRUB4DOS /quiet") + _Done("Grub4Dos") +EndFunc + +Func _LegacyGrub2() + Local $src = $boot & "\loaders\grub2-bios" + Local $dst = $boot & "\grub2\bios" + If Not FileExists($src & "\boot.img") Or Not FileExists($src & "\core.img") Then + _Msg("Файлы GRUB2 для Legacy BIOS не найдены:" & @CRLF & $src) + Return + EndIf + If Not FileExists($dst & "\grub.cfg") Then + _Msg("Не найден основной grub.cfg:" & @CRLF & $dst & "\grub.cfg") + Return + EndIf + + Local $disk = _DiskNumber($sDisk) + If $disk < 0 Then + _Msg("Не удалось определить номер физического диска для " & $sDisk) + Return + EndIf + + ; убрать файлы других Legacy-загрузчиков из корня + FileDelete($sDisk & "\bootmgr") + FileDelete($sDisk & "\grldr") + + ; дерево для core.img: модули и меню должны лежать по префиксу /efi/Paravis/Boot/grub2/bios + DirCreate($dst) + DirCreate($dst & "\i386-pc") + FileCopy($src & "\boot.img", $dst & "\boot.img", 9) + FileCopy($src & "\core.img", $dst & "\core.img", 9) + FileCopy($src & "\make-paravis-mbr.ps1", $dst & "\make-paravis-mbr.ps1", 9) + ; grub.cfg НЕ копируем: в loaders лежит тестовое меню, рабочее уже в $dst + RunWait(@ComSpec & ' /c xcopy /E /I /Y /Q "' & $src & '\i386-pc" "' & $dst & '\i386-pc" > nul 2>&1', "", @SW_HIDE) + + ; MBR-образ: наш boot.img (0..439) + текущая таблица разделов (440..511) + Local $rc1 = RunWait('powershell.exe -NoProfile -ExecutionPolicy Bypass -File "' & $dst & '\make-paravis-mbr.ps1" -Disk ' & $disk & ' -Boot "' & $dst & '\boot.img" -Out "' & $dst & '\paravis-mbr-table.img"', "", @SW_HIDE) + If $rc1 <> 0 Then + _Msg("Не удалось сформировать MBR-образ (код " & $rc1 & ").") + Return + EndIf + + ; Bootice /sectors: MBR (сектор 0) и core.img (с сектора 1); таблица разделов сохраняется + Local $rc2 = RunWait('"' & $bootice & '" /DEVICE=' & $sDisk & ' /sectors /restore /file="' & $dst & '\paravis-mbr-table.img" /lba=0 /sectors=1 /quiet', "", @SW_HIDE) + If $rc2 <> 0 Then + _Msg("Ошибка записи MBR (код " & $rc2 & ").") + Return + EndIf + Local $rc3 = RunWait('"' & $bootice & '" /DEVICE=' & $sDisk & ' /sectors /restore /file="' & $dst & '\core.img" /lba=1 /sectors=591 /quiet', "", @SW_HIDE) + If $rc3 <> 0 Then + _Msg("Ошибка записи core.img (код " & $rc3 & ").") + Return + EndIf + _Done("Grub2 (Legacy)") +EndFunc + +Func _UefiMgr() + _UefiClean() + FileCopy($boot & "\mgr\UEFI\bootx64.efi", $sDisk & "\efi\boot\bootx64.efi") + FileCopy($boot & "\mgr\UEFI\bootia32.efi", $sDisk & "\efi\boot\bootia32.efi") + _Done("UEFI Bootmgr") +EndFunc + +Func _UefiGrub2() + ; bootia32 не трогаем: на ia32 остаётся XorBoot + FileDelete($sDisk & "\efi\boot\bootx64.efi") + FileDelete($sDisk & "\efi\boot\fbx64.efi") + FileDelete($sDisk & "\efi\boot\mmx64.efi") + FileCopy($boot & "\loaders\grub2\bootx64.efi", $sDisk & "\efi\boot\bootx64.efi") + FileCopy($boot & "\loaders\grub2\fbx64.efi", $sDisk & "\efi\boot\fbx64.efi") + FileCopy($boot & "\loaders\grub2\mmx64.efi", $sDisk & "\efi\boot\mmx64.efi") + _Done("UEFI GRUB2") +EndFunc + +Func _UefiXor() + _UefiClean() + FileCopy($boot & "\xor\UEFI\bootx64.efi", $sDisk & "\efi\boot\bootx64.efi") + FileCopy($boot & "\xor\UEFI\bootia32.efi", $sDisk & "\efi\boot\bootia32.efi") + _Done("UEFI Xorboot") +EndFunc + +Func _UefiClean() + FileDelete($sDisk & "\efi\boot\bootx64.efi") + FileDelete($sDisk & "\efi\boot\bootia32.efi") + FileDelete($sDisk & "\efi\boot\fbx64.efi") + FileDelete($sDisk & "\efi\boot\mmx64.efi") +EndFunc + +; ================= служба ================= + +Func _Done($name) + _Msg("Загрузчик изменён: " & $name) +EndFunc + +Func _Msg($text) + Local $prev = $gBusy + $gBusy = True + MsgBox(BitOR($MB_ICONINFORMATION, $MB_OK), "Change BootLoader", $text) + $gBusy = $prev +EndFunc + +Func _DiskNumber($letter) + Local $oWMI = ObjGet("winmgmts:\\.\root\cimv2") + If Not IsObj($oWMI) Then Return -1 + Local $col = $oWMI.ExecQuery("SELECT * FROM Win32_DiskDrive") + If Not IsObj($col) Then Return -1 + For $d In $col + Local $parts = $oWMI.ExecQuery("ASSOCIATORS OF {Win32_DiskDrive.DeviceID='" & $d.DeviceID & "'} WHERE AssocClass=Win32_DiskDriveToDiskPartition") + If IsObj($parts) Then + For $p In $parts + Local $lds = $oWMI.ExecQuery("ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" & $p.DeviceID & "'} WHERE AssocClass=Win32_LogicalDiskToPartition") + If IsObj($lds) Then + For $l In $lds + If StringUpper(String($l.DeviceID)) = StringUpper($letter) Then + Return Number(StringRegExpReplace($d.DeviceID, ".*PHYSICALDRIVE(\d+).*", "$1")) + EndIf + Next + EndIf + Next + EndIf + Next + Return -1 +EndFunc + +Func _Poll() + Local $ci = GUIGetCursorInfo($hMain) + If Not IsArray($ci) Then Return + + ; кнопки шапки + Local $minBg = $CLR_HEAD, $closeBg = $CLR_HEAD + If $ci[4] = $hMin Then + If $ci[2] = 1 Then + $minBg = 0x3A4A6B + Else + $minBg = 0x2C3A57 + EndIf + EndIf + If $ci[4] = $hClose Then + If $ci[2] = 1 Then + $closeBg = 0xC0392B + Else + $closeBg = 0xE04B3F + EndIf + EndIf + If $minBg <> $gMinBg Then + GUICtrlSetBkColor($hMin, $minBg) + $gMinBg = $minBg + EndIf + If $closeBg <> $gCloseBg Then + GUICtrlSetBkColor($hClose, $closeBg) + $gCloseBg = $closeBg + EndIf + + ; нажатия в шапке + Static $minDown = False, $closeDown = False + If $ci[4] = $hMin And $ci[2] = 1 Then + $minDown = True + ElseIf $minDown Then + $minDown = False + If $ci[4] = $hMin And $ci[2] = 0 Then GUISetState(@SW_MINIMIZE, $hMain) + EndIf + If $ci[4] = $hClose And $ci[2] = 1 Then + $closeDown = True + ElseIf $closeDown Then + $closeDown = False + If $ci[4] = $hClose And $ci[2] = 0 Then Exit + EndIf + + ; перетаскивание за шапку + If ($ci[4] = $hHead Or $ci[4] = $hTitle Or $ci[4] = $hSub) And $ci[2] = 1 And Not $gDrag Then + Local $m = MouseGetPos() + Local $w = WinGetPos($hMain) + If IsArray($m) And IsArray($w) Then + $gDrag = True + $gDragX = $m[0] - $w[0] + $gDragY = $m[1] - $w[1] + EndIf + EndIf + If $gDrag Then + If $ci[2] = 0 Then + $gDrag = False + Else + Local $m2 = MouseGetPos() + If IsArray($m2) Then WinMove($hMain, "", $m2[0] - $gDragX, $m2[1] - $gDragY) + EndIf + EndIf + + ; подсветка кнопок + Local $i + For $i = 0 To 6 + Local $bg = $CLR_BTN + If $ci[4] = $aBtn[$i] Or $ci[4] = $aFrame[$i] Then + If $ci[2] = 1 Then + $bg = $CLR_PRESS + Else + $bg = $CLR_HOVER + EndIf + EndIf + If $aBg[$i] <> $bg Then + GUICtrlSetBkColor($aBtn[$i], $bg) + $aBg[$i] = $bg + EndIf + Next + + ; клик по кнопке + If $gBusy Then Return + If $ci[2] = 1 Then + For $i = 0 To 6 + If $ci[4] = $aBtn[$i] Or $ci[4] = $aFrame[$i] Then + $gDown = $i + Return + EndIf + Next + $gDown = -1 + ElseIf $gDown >= 0 Then + Local $idx = $gDown + $gDown = -1 + If $ci[4] = $aBtn[$idx] Or $ci[4] = $aFrame[$idx] Then _Run($idx) + EndIf +EndFunc diff --git a/legacy-au3/README-legacy.md b/legacy-au3/README-legacy.md new file mode 100644 index 0000000..e29b3bf --- /dev/null +++ b/legacy-au3/README-legacy.md @@ -0,0 +1,7 @@ +# Change_bootloader + +Исходник Change bootloader'а (программа установки сборки на флешку). + +Все необходимые файлы для смены загрузчика, находятся уже на установленной флешке. Сама программа может лежать в любой папке на диске с установленной сборкой. + +НЕ ЗАПУСКАЙТЕ ИСХОДНИК И СКОМПИЛИРОВАННЫЙ EXE НА ДИСКАХ ГДЕ НЕТ СБОРКИ. В лучшем случае получите кучу ошибок. В худшем, угробите загрузочную запись диска. \ No newline at end of file diff --git a/src/app.h b/src/app.h new file mode 100644 index 0000000..6cef48f --- /dev/null +++ b/src/app.h @@ -0,0 +1,63 @@ +#pragma once + +#include "util.h" + +#include +#include + +class App { +public: + HINSTANCE hInst = nullptr; + HWND hwnd = nullptr; + + HFONT fontTitle = nullptr; + HFONT fontSub = nullptr; + HFONT fontMin = nullptr; + HFONT fontClose = nullptr; + HFONT fontSection = nullptr; + HFONT fontButton = nullptr; + + int width = 376; + int height = 208; + + std::wstring disk; + std::wstring boot; + std::wstring bootice; + + bool busy = false; + bool modal = false; + + bool minDown = false; + bool closeDown = false; + bool dragging = false; + int dragX = 0; + int dragY = 0; + int downButton = -1; + + COLORREF minBg = ColorFromHex(0x1E2A44); + COLORREF closeBg = ColorFromHex(0x1E2A44); + COLORREF buttonBg[7] = { + RGB(255, 255, 255), RGB(255, 255, 255), RGB(255, 255, 255), + RGB(255, 255, 255), RGB(255, 255, 255), RGB(255, 255, 255), + RGB(255, 255, 255) + }; + + bool Init(HWND window); + void CreateFonts(); + void DestroyFonts(); + void Paint(HDC dc); + void OnPoll(); + void RunAction(int index); + void ShowMessage(const std::wstring& text, + UINT flags = MB_ICONINFORMATION | MB_OK); + + void LegacyXor(); + void LegacyMgr(); + void LegacyG4d(); + void LegacyGrub2(); + void UefiMgr(); + void UefiGrub2(); + void UefiXor(); + void UefiClean(); + void Done(const std::wstring& name); +}; diff --git a/src/app.manifest b/src/app.manifest new file mode 100644 index 0000000..a1bcf1e --- /dev/null +++ b/src/app.manifest @@ -0,0 +1,22 @@ + + + + Change bootLoader + + + + + + + + + + + + + + + true + + + diff --git a/src/app.rc b/src/app.rc new file mode 100644 index 0000000..0d72edb --- /dev/null +++ b/src/app.rc @@ -0,0 +1,33 @@ +#include + +1 ICON "sec.ico" +1 24 "app.manifest" + +VS_VERSION_INFO VERSIONINFO +FILEVERSION 3,2,0,0 +PRODUCTVERSION 3,2,0,0 +FILEFLAGSMASK 0x3fL +FILEFLAGS 0x0L +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "paravis.ru@2024" + VALUE "FileDescription", "CBL" + VALUE "FileVersion", "3.2.0.0" + VALUE "InternalName", "ChangeBootLoader" + VALUE "OriginalFilename", "Change_BootLoader.exe" + VALUE "ProductName", "Change bootLoader" + VALUE "ProductVersion", "3.2.0.0" + VALUE "LegalCopyright", "paravis.ru@2024" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/boot.cpp b/src/boot.cpp new file mode 100644 index 0000000..0621c3d --- /dev/null +++ b/src/boot.cpp @@ -0,0 +1,203 @@ +#include "app.h" +#include "util.h" + +#include + +void App::ShowMessage(const std::wstring& text, UINT flags) +{ + bool previous = modal; + modal = true; + MessageBoxW(hwnd, text.c_str(), L"Change BootLoader", flags); + modal = previous; + InvalidateRect(hwnd, nullptr, FALSE); +} + +void App::RunAction(int index) +{ + if (busy) { + return; + } + + int diskNumber = GetDiskNumberForDrive(disk[0]); + DebugLog(L"RunAction index=%d disk=%s physical=%d", index, disk.c_str(), diskNumber); + + if (!FileExists(boot + L"\\NOT.DEL") || diskNumber < 0) { + ShowMessage(L"Целевой диск не подтверждён:\r\n" + disk + + L"\r\nнет \\efi\\Paravis\\Boot\\NOT.DEL или не найден PhysicalDrive."); + return; + } + + busy = true; + switch (index) { + case 0: + LegacyXor(); + break; + case 1: + LegacyMgr(); + break; + case 2: + LegacyG4d(); + break; + case 3: + LegacyGrub2(); + break; + case 4: + UefiMgr(); + break; + case 5: + UefiGrub2(); + break; + case 6: + UefiXor(); + break; + default: + break; + } + busy = false; + InvalidateRect(hwnd, nullptr, FALSE); +} + +void App::Done(const std::wstring& name) +{ + ShowMessage(L"Загрузчик изменён: " + name); +} + +void App::LegacyXor() +{ + DeleteFileIfExists(disk + L"\\bootmgr"); + DeleteFileIfExists(disk + L"\\grldr"); + RunWait(bootice + L" /DEVICE=" + disk + + L" /mbr /restore /file=" + boot + L"\\xor\\xmbr.bin /keep_dpt /quiet"); + RunWait(bootice + L" /DEVICE=" + disk + + L" /pbr /restore /file=" + boot + L"\\xor\\xpbr.bin /keep_bpb /quiet"); + Done(L"XorBoot"); +} + +void App::LegacyMgr() +{ + DeleteFileIfExists(disk + L"\\bootmgr"); + DeleteFileIfExists(disk + L"\\grldr"); + CopyFileOverwrite(boot + L"\\mgr\\bootmgr", disk + L"\\bootmgr"); + RunWait(bootice + L" /DEVICE=" + disk + L" /mbr /install /type=nt60 /quiet"); + RunWait(bootice + L" /DEVICE=" + disk + L" /pbr /install /type=bootmgr /quiet"); + Done(L"Bootmgr"); +} + +void App::LegacyG4d() +{ + DeleteFileIfExists(disk + L"\\bootmgr"); + DeleteFileIfExists(disk + L"\\grldr"); + CopyFileOverwrite(boot + L"\\g4d\\grldr", disk + L"\\grldr"); + RunWait(bootice + L" /DEVICE=" + disk + L" /mbr /install /type=GRUB4DOS /quiet"); + RunWait(bootice + L" /DEVICE=" + disk + L" /pbr /install /type=GRUB4DOS /quiet"); + Done(L"Grub4Dos"); +} + +void App::LegacyGrub2() +{ + std::wstring source = boot + L"\\loaders\\grub2-bios"; + std::wstring destination = boot + L"\\grub2\\bios"; + + if (!FileExists(source + L"\\boot.img") || !FileExists(source + L"\\core.img")) { + ShowMessage(L"Файлы GRUB2 для Legacy BIOS не найдены:\r\n" + source); + return; + } + if (!FileExists(destination + L"\\grub.cfg")) { + ShowMessage(L"Не найден основной grub.cfg:\r\n" + destination + L"\\grub.cfg"); + return; + } + + int diskNumber = GetDiskNumberForDrive(disk[0]); + if (diskNumber < 0) { + ShowMessage(L"Не удалось определить номер физического диска для " + disk); + return; + } + + DeleteFileIfExists(disk + L"\\bootmgr"); + DeleteFileIfExists(disk + L"\\grldr"); + + EnsureDirectory(destination); + EnsureDirectory(destination + L"\\i386-pc"); + CopyFileOverwrite(source + L"\\boot.img", destination + L"\\boot.img"); + CopyFileOverwrite(source + L"\\core.img", destination + L"\\core.img"); + CopyFileOverwrite(source + L"\\make-paravis-mbr.ps1", + destination + L"\\make-paravis-mbr.ps1"); + + std::wstring xcopy = L"\"" + GetComSpec() + + L"\" /c xcopy /E /I /Y /Q \"" + source + L"\\i386-pc\" \"" + + destination + L"\\i386-pc\" > nul 2>&1"; + RunWait(xcopy); + + std::wstring powershell = L"powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"" + + destination + L"\\make-paravis-mbr.ps1\" -Disk " + + std::to_wstring(diskNumber) + L" -Boot \"" + destination + + L"\\boot.img\" -Out \"" + destination + L"\\paravis-mbr-table.img\""; + int rc1 = RunWait(powershell); + if (rc1 != 0) { + ShowMessage(L"Не удалось сформировать MBR-образ (код " + + std::to_wstring(rc1) + L")."); + return; + } + + std::wstring mbr = L"\"" + bootice + L"\" /DEVICE=" + disk + + L" /sectors /restore /file=\"" + destination + + L"\\paravis-mbr-table.img\" /lba=0 /sectors=1 /quiet"; + int rc2 = RunWait(mbr); + if (rc2 != 0) { + ShowMessage(L"Ошибка записи MBR (код " + std::to_wstring(rc2) + L")."); + return; + } + + std::wstring core = L"\"" + bootice + L"\" /DEVICE=" + disk + + L" /sectors /restore /file=\"" + destination + + L"\\core.img\" /lba=1 /sectors=591 /quiet"; + int rc3 = RunWait(core); + if (rc3 != 0) { + ShowMessage(L"Ошибка записи core.img (код " + std::to_wstring(rc3) + L")."); + return; + } + + Done(L"Grub2 (Legacy)"); +} + +void App::UefiMgr() +{ + UefiClean(); + CopyFileOverwrite(boot + L"\\mgr\\UEFI\\bootx64.efi", + disk + L"\\efi\\boot\\bootx64.efi"); + CopyFileOverwrite(boot + L"\\mgr\\UEFI\\bootia32.efi", + disk + L"\\efi\\boot\\bootia32.efi"); + Done(L"UEFI Bootmgr"); +} + +void App::UefiGrub2() +{ + DeleteFileIfExists(disk + L"\\efi\\boot\\bootx64.efi"); + DeleteFileIfExists(disk + L"\\efi\\boot\\fbx64.efi"); + DeleteFileIfExists(disk + L"\\efi\\boot\\mmx64.efi"); + CopyFileOverwrite(boot + L"\\loaders\\grub2\\bootx64.efi", + disk + L"\\efi\\boot\\bootx64.efi"); + CopyFileOverwrite(boot + L"\\loaders\\grub2\\fbx64.efi", + disk + L"\\efi\\boot\\fbx64.efi"); + CopyFileOverwrite(boot + L"\\loaders\\grub2\\mmx64.efi", + disk + L"\\efi\\boot\\mmx64.efi"); + Done(L"UEFI GRUB2"); +} + +void App::UefiXor() +{ + UefiClean(); + CopyFileOverwrite(boot + L"\\xor\\UEFI\\bootx64.efi", + disk + L"\\efi\\boot\\bootx64.efi"); + CopyFileOverwrite(boot + L"\\xor\\UEFI\\bootia32.efi", + disk + L"\\efi\\boot\\bootia32.efi"); + Done(L"UEFI Xorboot"); +} + +void App::UefiClean() +{ + DeleteFileIfExists(disk + L"\\efi\\boot\\bootx64.efi"); + DeleteFileIfExists(disk + L"\\efi\\boot\\bootia32.efi"); + DeleteFileIfExists(disk + L"\\efi\\boot\\fbx64.efi"); + DeleteFileIfExists(disk + L"\\efi\\boot\\mmx64.efi"); +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..771f290 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,415 @@ +#include "app.h" +#include "util.h" + +#include + +#include +#include + +namespace { + +const wchar_t* kMainClass = L"ChangeBootLoaderCpp"; +const wchar_t* kWindowTitle = L"Change BootLoader"; + +const DWORD CLR_BG = 0xF1F4F9; +const DWORD CLR_HEAD = 0x1E2A44; +const DWORD CLR_DIM = 0x6B7280; +const DWORD CLR_BORDER = 0xBFC9D6; +const DWORD CLR_HOVER = 0xE4EEFF; +const DWORD CLR_PRESS = 0xCFE0FF; +const DWORD CLR_TEXT = 0x14202E; + +const wchar_t* kButtonNames[7] = { + L"XorBoot", L"Bootmgr", L"Grub4Dos", L"Grub2", + L"Bootmgr", L"Grub2", L"Xorboot" +}; + +const int kButtonPos[7][4] = { + { 14, 72, 170, 32 }, + { 192, 72, 170, 32 }, + { 14, 110, 170, 32 }, + { 192, 110, 170, 32 }, + { 14, 168, 110, 32 }, + { 132, 168, 110, 32 }, + { 250, 168, 110, 32 } +}; + +enum class HitKind { + None, + Header, + Title, + Sub, + Min, + Close, + Button +}; + +struct HitResult { + HitKind kind = HitKind::None; + int index = -1; +}; + +void FillRectColor(HDC dc, int x, int y, int w, int h, COLORREF color) +{ + RECT rect{ x, y, x + w, y + h }; + HBRUSH brush = CreateSolidBrush(color); + FillRect(dc, &rect, brush); + DeleteObject(brush); +} + +void DrawTextRect(HDC dc, const std::wstring& text, HFONT font, + int x, int y, int w, int h, COLORREF color, UINT flags) +{ + HGDIOBJ oldFont = SelectObject(dc, font); + int oldMode = SetBkMode(dc, TRANSPARENT); + COLORREF oldColor = SetTextColor(dc, color); + RECT rect{ x, y, x + w, y + h }; + DrawTextW(dc, text.c_str(), -1, &rect, flags | DT_NOPREFIX | DT_SINGLELINE); + SetTextColor(dc, oldColor); + SetBkMode(dc, oldMode); + SelectObject(dc, oldFont); +} + +HitResult HitTest(POINT point) +{ + auto inside = [&point](int x, int y, int w, int h) { + return point.x >= x && point.y >= y && + point.x < x + w && point.y < y + h; + }; + + if (inside(306, 2, 30, 18)) { + return { HitKind::Min, -1 }; + } + if (inside(338, 2, 30, 18)) { + return { HitKind::Close, -1 }; + } + if (inside(16, 2, 300, 24)) { + return { HitKind::Title, -1 }; + } + if (inside(18, 27, 300, 14)) { + return { HitKind::Sub, -1 }; + } + if (inside(0, 0, 376, 48)) { + return { HitKind::Header, -1 }; + } + + for (int i = 0; i < 7; ++i) { + int x = kButtonPos[i][0] - 1; + int y = kButtonPos[i][1] - 1; + int w = kButtonPos[i][2] + 2; + int h = kButtonPos[i][3] + 2; + if (inside(x, y, w, h)) { + return { HitKind::Button, i }; + } + } + return {}; +} + +LRESULT CALLBACK MainProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + App* app = reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); + + switch (message) { + case WM_CREATE: { + auto* create = reinterpret_cast(lParam); + app = reinterpret_cast(create->lpCreateParams); + SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(app)); + if (app) { + app->Init(hwnd); + } + return 0; + } + case WM_ERASEBKGND: + return 1; + case WM_PAINT: { + PAINTSTRUCT paint{}; + HDC dc = BeginPaint(hwnd, &paint); + if (app) { + app->Paint(dc); + } + EndPaint(hwnd, &paint); + return 0; + } + case WM_TIMER: + if (app && wParam == 1) { + app->OnPoll(); + } + return 0; + case WM_CLOSE: + DebugLog(L"WM_CLOSE busy=%d", (app && app->busy) ? 1 : 0); + if (app && app->busy) { + return 0; + } + DestroyWindow(hwnd); + return 0; + case WM_DESTROY: + if (app) { + app->DestroyFonts(); + } + PostQuitMessage(0); + return 0; + } + return DefWindowProcW(hwnd, message, wParam, lParam); +} + +} // namespace + +bool App::Init(HWND window) +{ + hwnd = window; + HDC dc = GetDC(hwnd); + int dpi = GetDeviceCaps(dc, LOGPIXELSY); + ReleaseDC(hwnd, dc); + if (dpi <= 0) { + dpi = 96; + } + + auto create = [dpi](int points, int weight, const wchar_t* face) -> HFONT { + // AutoIt/Windows GUI font sizing truncates fractional pixel heights; + // this matters for 8 pt fonts (10 px, not 11 px). + int height = -(points * dpi / 72); + return CreateFontW(height, 0, 0, 0, weight, + FALSE, FALSE, FALSE, DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, face); + }; + fontTitle = create(15, FW_BOLD, L"Tahoma"); + fontSub = create(9, FW_NORMAL, L"Tahoma"); + fontMin = create(10, FW_NORMAL, L"Tahoma"); + fontClose = create(9, FW_NORMAL, L"Tahoma"); + fontSection = create(8, FW_BOLD, L"Tahoma"); + fontButton = create(10, FW_NORMAL, L"Segoe UI Semibold"); + + HICON icon = LoadIconW(hInst, MAKEINTRESOURCEW(1)); + if (icon) { + SendMessageW(hwnd, WM_SETICON, ICON_BIG, reinterpret_cast(icon)); + SendMessageW(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast(icon)); + } + return true; +} + +void App::DestroyFonts() +{ + for (HFONT* font : { &fontTitle, &fontSub, &fontMin, &fontClose, + &fontSection, &fontButton }) { + if (*font) { + DeleteObject(*font); + *font = nullptr; + } + } +} + +void App::Paint(HDC dc) +{ + RECT client{}; + GetClientRect(hwnd, &client); + int w = client.right; + int h = client.bottom; + if (w <= 0 || h <= 0) { + return; + } + + HDC memory = CreateCompatibleDC(dc); + HBITMAP bitmap = CreateCompatibleBitmap(dc, w, h); + HGDIOBJ oldBitmap = SelectObject(memory, bitmap); + + FillRectColor(memory, 0, 0, 376, 208, ColorFromHex(CLR_BG)); + FillRectColor(memory, 0, 0, 376, 48, ColorFromHex(CLR_HEAD)); + + DrawTextRect(memory, L"Paravis Flash", fontTitle, 16, 2, 300, 24, + RGB(255, 255, 255), DT_LEFT | DT_TOP); + DrawTextRect(memory, L"Смена загрузчика", fontSub, 18, 27, 300, 14, + ColorFromHex(0xC7D0E0), DT_LEFT | DT_TOP); + + FillRectColor(memory, 306, 2, 30, 18, minBg); + DrawTextRect(memory, L"—", fontMin, 306, 2, 30, 18, + ColorFromHex(0xC7D0E0), DT_CENTER | DT_VCENTER); + FillRectColor(memory, 338, 2, 30, 18, closeBg); + DrawTextRect(memory, L"✕", fontClose, 338, 2, 30, 18, + ColorFromHex(0xC7D0E0), DT_CENTER | DT_VCENTER); + + DrawTextRect(memory, L"LEGACY BIOS", fontSection, 15, 56, 200, 14, + ColorFromHex(CLR_DIM), DT_LEFT | DT_TOP); + DrawTextRect(memory, L"UEFI", fontSection, 15, 152, 200, 14, + ColorFromHex(CLR_DIM), DT_LEFT | DT_TOP); + + for (int i = 0; i < 7; ++i) { + int x = kButtonPos[i][0]; + int y = kButtonPos[i][1]; + int bw = kButtonPos[i][2]; + int bh = kButtonPos[i][3]; + + FillRectColor(memory, x - 1, y - 1, bw + 2, bh + 2, + ColorFromHex(CLR_BORDER)); + FillRectColor(memory, x, y, bw, bh, buttonBg[i]); + DrawTextRect(memory, kButtonNames[i], fontButton, x, y, bw, bh, + ColorFromHex(CLR_TEXT), DT_CENTER | DT_VCENTER); + } + + BitBlt(dc, 0, 0, w, h, memory, 0, 0, SRCCOPY); + SelectObject(memory, oldBitmap); + DeleteObject(bitmap); + DeleteDC(memory); +} + +void App::OnPoll() +{ + if (modal) { + return; + } + + POINT screen{}; + GetCursorPos(&screen); + bool leftDown = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0; + POINT client = screen; + ScreenToClient(hwnd, &client); + HitResult hit = HitTest(client); + + COLORREF minColor = ColorFromHex(CLR_HEAD); + COLORREF closeColor = ColorFromHex(CLR_HEAD); + if (hit.kind == HitKind::Min) { + minColor = leftDown ? ColorFromHex(0x3A4A6B) : ColorFromHex(0x2C3A57); + } + if (hit.kind == HitKind::Close) { + closeColor = leftDown ? ColorFromHex(0xC0392B) : ColorFromHex(0xE04B3F); + } + minBg = minColor; + closeBg = closeColor; + + if (hit.kind == HitKind::Min && leftDown) { + minDown = true; + } else if (minDown) { + minDown = false; + if (hit.kind == HitKind::Min && !leftDown) { + ShowWindow(hwnd, SW_MINIMIZE); + } + } + if (hit.kind == HitKind::Close && leftDown) { + closeDown = true; + } else if (closeDown) { + closeDown = false; + if (hit.kind == HitKind::Close && !leftDown && !busy) { + DestroyWindow(hwnd); + return; + } + } + + if ((hit.kind == HitKind::Header || hit.kind == HitKind::Title || + hit.kind == HitKind::Sub) && leftDown && !dragging) { + POINT mouse{}; + RECT windowRect{}; + GetCursorPos(&mouse); + GetWindowRect(hwnd, &windowRect); + dragging = true; + dragX = mouse.x - windowRect.left; + dragY = mouse.y - windowRect.top; + } + if (dragging) { + if (!leftDown) { + dragging = false; + } else { + POINT mouse{}; + GetCursorPos(&mouse); + SetWindowPos(hwnd, nullptr, mouse.x - dragX, mouse.y - dragY, + 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + } + } + + for (int i = 0; i < 7; ++i) { + COLORREF background = RGB(255, 255, 255); + if (hit.kind == HitKind::Button && hit.index == i) { + background = leftDown ? ColorFromHex(CLR_PRESS) + : ColorFromHex(CLR_HOVER); + } + buttonBg[i] = background; + } + + if (busy) { + downButton = -1; + InvalidateRect(hwnd, nullptr, FALSE); + return; + } + + if (leftDown) { + if (hit.kind == HitKind::Button) { + downButton = hit.index; + } else { + downButton = -1; + } + } else if (downButton >= 0) { + int index = downButton; + downButton = -1; + if (hit.kind == HitKind::Button && hit.index == index) { + RunAction(index); + } + } + + InvalidateRect(hwnd, nullptr, FALSE); +} + +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int) +{ + App app; + app.hInst = hInstance; + + std::wstring exeDir = GetExeDir(); + if (exeDir.size() < 2 || exeDir[1] != L':') { + MessageBoxW(nullptr, L"Не удалось определить диск установщика.", + kWindowTitle, MB_ICONERROR | MB_OK); + return 1; + } + + wchar_t letter = exeDir[0]; + if (letter >= L'a' && letter <= L'z') { + letter = static_cast(letter - L'a' + L'A'); + } + app.disk = std::wstring(1, letter) + L":"; + app.boot = app.disk + L"\\efi\\Paravis\\Boot"; + app.bootice = app.boot + L"\\bootice.exe"; + + if (!FileExists(app.bootice) || !FileExists(app.boot + L"\\NOT.DEL")) { + std::wstring text = L"Этот диск не является сборкой Paravis " + L"(нет bootice.exe или NOT.DEL):\r\n" + app.disk; + MessageBoxW(nullptr, text.c_str(), kWindowTitle, MB_ICONERROR | MB_OK); + return 0; + } + + WNDCLASSEXW windowClass{}; + windowClass.cbSize = sizeof(windowClass); + windowClass.lpfnWndProc = MainProc; + windowClass.hInstance = hInstance; + windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); + windowClass.hIcon = LoadIconW(hInstance, MAKEINTRESOURCEW(1)); + windowClass.hIconSm = LoadIconW(hInstance, MAKEINTRESOURCEW(1)); + windowClass.lpszClassName = kMainClass; + if (!RegisterClassExW(&windowClass)) { + return 1; + } + + RECT workArea{}; + SystemParametersInfoW(SPI_GETWORKAREA, 0, &workArea, 0); + int workWidth = workArea.right - workArea.left; + int workHeight = workArea.bottom - workArea.top; + int x = workArea.left + (workWidth - app.width) / 2; + int y = workArea.top + (workHeight - app.height) / 2; + + app.hwnd = CreateWindowExW(WS_EX_APPWINDOW, kMainClass, kWindowTitle, + WS_POPUP | WS_CLIPCHILDREN, + x, y, app.width, app.height, + nullptr, nullptr, hInstance, &app); + if (!app.hwnd) { + return 1; + } + + ShowWindow(app.hwnd, SW_SHOW); + UpdateWindow(app.hwnd); + SetForegroundWindow(app.hwnd); + SetTimer(app.hwnd, 1, 40, nullptr); + + MSG message{}; + while (GetMessageW(&message, nullptr, 0, 0) > 0) { + TranslateMessage(&message); + DispatchMessageW(&message); + } + return static_cast(message.wParam); +} diff --git a/src/sec.ico b/src/sec.ico new file mode 100644 index 0000000000000000000000000000000000000000..401ca087058da559fb472a528033eebcb95271ec GIT binary patch literal 31826 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 ULB3xH4+X*#zUrO1YQTX10g;9lj{pDw literal 0 HcmV?d00001 diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..e8a3fb7 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,245 @@ +#include "util.h" + +#include + +#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 GetComSpec() +{ + wchar_t buffer[MAX_PATH * 2] = {}; + DWORD length = GetEnvironmentVariableW(L"ComSpec", buffer, + static_cast(std::size(buffer))); + if (length > 0 && length < std::size(buffer)) { + return std::wstring(buffer, length); + } + return L"C:\\Windows\\System32\\cmd.exe"; +} + +bool FileExists(const std::wstring& path) +{ + DWORD attributes = GetFileAttributesW(path.c_str()); + return attributes != INVALID_FILE_ATTRIBUTES && + (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +void DeleteFileIfExists(const std::wstring& path) +{ + DeleteFileW(path.c_str()); +} + +bool CopyFileOverwrite(const std::wstring& source, const std::wstring& destination) +{ + return CopyFileW(source.c_str(), destination.c_str(), FALSE) != FALSE; +} + +bool EnsureDirectory(const std::wstring& path) +{ + if (path.empty()) { + return false; + } + + std::wstring current; + size_t start = 0; + if (path.size() >= 3 && path[1] == L':' && (path[2] == L'\\' || path[2] == L'/')) { + current = path.substr(0, 3); + start = 3; + } else if (path.size() >= 2 && path[0] == L'\\' && path[1] == L'\\') { + current = path.substr(0, 2); + start = 2; + } + + while (start <= path.size()) { + size_t slash = path.find_first_of(L"\\/", start); + if (slash == std::wstring::npos) { + current += path.substr(start); + break; + } + current += path.substr(start, slash - start); + if (!current.empty()) { + CreateDirectoryW(current.c_str(), nullptr); + } + current.push_back(L'\\'); + start = slash + 1; + } + + if (!current.empty()) { + CreateDirectoryW(current.c_str(), nullptr); + } + DWORD attributes = GetFileAttributesW(path.c_str()); + return attributes != INVALID_FILE_ATTRIBUTES && + (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +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) +{ + STARTUPINFOW startup{}; + startup.cb = sizeof(startup); + startup.dwFlags = STARTF_USESHOWWINDOW; + startup.wShowWindow = SW_HIDE; + + PROCESS_INFORMATION process{}; + std::vector buffer(commandLine.begin(), commandLine.end()); + buffer.push_back(L'\0'); + + if (!CreateProcessW(nullptr, buffer.data(), nullptr, nullptr, FALSE, + CREATE_NO_WINDOW, 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); +} + +int GetDiskNumberForDrive(wchar_t letter) +{ + std::wstring path = L"\\\\.\\"; + path.push_back(letter); + path.push_back(L':'); + + HANDLE handle = CreateFileW(path.c_str(), 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, 0, nullptr); + if (handle == INVALID_HANDLE_VALUE) { + handle = CreateFileW(path.c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, 0, nullptr); + } + if (handle == INVALID_HANDLE_VALUE) { + return -1; + } + + std::vector buffer(sizeof(VOLUME_DISK_EXTENTS) + 16 * sizeof(DISK_EXTENT)); + DWORD returned = 0; + bool ok = DeviceIoControl(handle, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, + nullptr, 0, buffer.data(), + static_cast(buffer.size()), &returned, + nullptr) != FALSE; + CloseHandle(handle); + if (!ok || returned < sizeof(VOLUME_DISK_EXTENTS)) { + return -1; + } + + auto* extents = reinterpret_cast(buffer.data()); + if (extents->NumberOfDiskExtents == 0) { + return -1; + } + return static_cast(extents->Extents[0].DiskNumber); +} + +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"cbl_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..b887958 --- /dev/null +++ b/src/util.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +std::wstring Format(const wchar_t* fmt, ...); +std::wstring GetExeDir(); +std::wstring GetComSpec(); +bool FileExists(const std::wstring& path); +void DeleteFileIfExists(const std::wstring& path); +bool CopyFileOverwrite(const std::wstring& source, const std::wstring& destination); +bool EnsureDirectory(const std::wstring& path); +int RunWait(const std::wstring& commandLine); +int GetDiskNumberForDrive(wchar_t letter); +void PumpMessages(); +COLORREF ColorFromHex(DWORD rgb); +void DebugLog(const wchar_t* fmt, ...);