commit c538f115f1964adc0b28923c910eb854c48609aa Author: Paravis Date: Sat Nov 7 20:41:09 2026 +0300 Bootloader installer 2.0 (C++ port) 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 0000000..401ca08 Binary files /dev/null and b/src/sec.ico differ 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, ...);