Paravis USB installer 2.0 (C++ port)
This commit is contained in:
commit
ce8b49d0ec
18 files changed
+4119
No files matched your search
+40
@@ -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
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
|
||||||
|
project(ParavisInstallerCpp LANGUAGES CXX)
|
||||||
|
|
||||||
|
enable_language(RC)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
add_executable(ParavisInstaller WIN32
|
||||||
|
src/main.cpp
|
||||||
|
src/install.cpp
|
||||||
|
src/disks.cpp
|
||||||
|
src/util.cpp
|
||||||
|
src/app.rc
|
||||||
|
)
|
||||||
|
|
||||||
|
# Keep the installer self-contained: no VC++ redistributable required.
|
||||||
|
set_property(TARGET ParavisInstaller PROPERTY MSVC_RUNTIME_LIBRARY
|
||||||
|
"MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||||
|
|
||||||
|
target_compile_definitions(ParavisInstaller PRIVATE
|
||||||
|
UNICODE
|
||||||
|
_UNICODE
|
||||||
|
WIN32_LEAN_AND_MEAN
|
||||||
|
NOMINMAX
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_options(ParavisInstaller PRIVATE
|
||||||
|
/utf-8
|
||||||
|
/W4
|
||||||
|
/permissive-
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(ParavisInstaller PRIVATE
|
||||||
|
comctl32
|
||||||
|
shell32
|
||||||
|
user32
|
||||||
|
gdi32
|
||||||
|
advapi32
|
||||||
|
)
|
||||||
|
|
||||||
|
# The manifest is embedded through app.rc; do not let the linker add a second one.
|
||||||
|
target_link_options(ParavisInstaller PRIVATE /MANIFEST:NO)
|
||||||
|
|
||||||
|
set_target_properties(ParavisInstaller PROPERTIES
|
||||||
|
OUTPUT_NAME "ParavisInstaller"
|
||||||
|
)
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Paravis installer — C++ port
|
||||||
|
|
||||||
|
Native Win32 port of `USBInstaller\usb-install.au3` with the same layout,
|
||||||
|
behaviour and data flow.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- Visual Studio 2022 with the Desktop C++ workload
|
||||||
|
- CMake 3.20 or newer
|
||||||
|
|
||||||
|
From this directory run:
|
||||||
|
|
||||||
|
```bat
|
||||||
|
build.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
The executable is produced at:
|
||||||
|
|
||||||
|
```
|
||||||
|
build\Release\ParavisInstaller.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
The target is 32-bit (`Win32`) like the original AutoIt build, and the
|
||||||
|
application manifest requests administrator rights.
|
||||||
|
|
||||||
|
## What is implemented
|
||||||
|
|
||||||
|
- 376 × 183 custom-drawn window, header, custom disk dropdown, status line,
|
||||||
|
progress bar, install/cancel button and hover/pressed states.
|
||||||
|
- USB disk enumeration by storage bus type, nominal size formatting and
|
||||||
|
Windows-system-disk protection.
|
||||||
|
- Diskpart layout: FAT32 PARAVIS 16 GB, FAT VTOYEFI 32 MB, exFAT DATA
|
||||||
|
(remaining space minus 3 MB).
|
||||||
|
- Offline disk is brought online before partitioning.
|
||||||
|
- Layout verification from the partition table and volume labels.
|
||||||
|
- DISM apply of images 1 → PARAVIS, 2 → VTOYEFI, 3 → DATA with progress
|
||||||
|
parsing and cancellation of the DISM process tree.
|
||||||
|
- Pressing the cancel button during installation asks for confirmation first.
|
||||||
|
- VTOYEFI drive letter removal after a successful install.
|
||||||
|
- Success warning, launch of `Change_BootLoader.exe` from the PARAVIS
|
||||||
|
partition, then immediate exit.
|
||||||
|
- If a `Change_BootLoader.exe` is present next to the installer, it is copied
|
||||||
|
to the PARAVIS partition after unpacking and used instead of the copy from
|
||||||
|
`Data.pf`.
|
||||||
|
- Service modes: `/selftest`, `/verify=N`, `/testdisk=N`, `/wim=path`.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `Data.pf` is searched next to the executable, exactly like the AutoIt
|
||||||
|
version. `/wim=` remains a service/testing override.
|
||||||
|
- DISM command lines are built without the outer `cmd /c "..."` quote pair,
|
||||||
|
so paths containing spaces work correctly.
|
||||||
|
- The debug log is disabled by default. Set the environment variable
|
||||||
|
`PARAVIS_DEBUG_LOG=1` to write `%TEMP%\paravis_cpp_debug.log`.
|
||||||
|
- Message boxes use the native `MessageBoxW` dialog; text, buttons and
|
||||||
|
behavior are the same as the AutoIt version.
|
||||||
|
|
||||||
|
## 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. Installation archives
|
||||||
|
(`Data.pf`), Windows images (`*.wim`, `*.esd`) and third-party binaries are
|
||||||
|
not distributed here.
|
||||||
@@ -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\ParavisInstaller.exe
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# PARAVIS FLASH — утилита разметки USB-диска (legacy AutoIt)
|
||||||
|
|
||||||
|
Исходный текст оригинальной версии на AutoIt (`usb-install.au3`).
|
||||||
|
Поддерживаемая версия — C++-порт в корне репозитория (`src/`, `CMakeLists.txt`).
|
||||||
|
|
||||||
|
`Paravis installer.exe` — 32-битная сборка (Windows 8 и новее, x86/x64).
|
||||||
|
|
||||||
|
## Что делает
|
||||||
|
|
||||||
|
Размечает выбранный USB-диск под мультизагрузочную флешку:
|
||||||
|
|
||||||
|
| # | Размер | ФС | Метка | Назначение |
|
||||||
|
|---|--------|-----|-------|------------|
|
||||||
|
| 1 | 16 384 МБ (16 ГБ) | FAT32 | `PARAVIS` | основной раздел, активный (bootmgr / g4d) |
|
||||||
|
| 2 | 32 МБ | FAT | `VTOYEFI` | раздел Ventoy |
|
||||||
|
| 3 | остальное − 1 − 16384 − 32 − 3 МБ | exFAT | `DATA` | данные |
|
||||||
|
| — | 3 МБ в конце | — | — | нераспределённые (нужны Ventoy; в интерфейсе не показываются) |
|
||||||
|
|
||||||
|
Разметка — MBR (`convert mbr`). Все данные на диске уничтожаются
|
||||||
|
(есть подтверждение, по умолчанию «Нет»).
|
||||||
|
|
||||||
|
## Распаковка файлов из архива
|
||||||
|
|
||||||
|
После успешной разметки и проверки раскладки утилита распаковывает архив
|
||||||
|
**`Data.pf`** (лежит рядом с exe) на созданные разделы:
|
||||||
|
|
||||||
|
| образ | раздел | содержимое |
|
||||||
|
|---|---|---|
|
||||||
|
| `1-PARAVIS` | 1-й (FAT32, PARAVIS) | программа, g4d, Xorboot, KRD, Strelec, MSDaRT, UBCD |
|
||||||
|
| `2-VTOYEFI` | 2-й (FAT, VTOYEFI) | Ventoy |
|
||||||
|
| `3-DATA` | 3-й (exFAT, DATA) | пусто (структура `ISO\Ventoy`) |
|
||||||
|
|
||||||
|
Где ищется архив:
|
||||||
|
|
||||||
|
* **только рядом с exe**, файл с именем **`Data.pf`**: `<папка exe>\Data.pf`;
|
||||||
|
* никакого поиска по другим папкам и никаких диалогов выбора — если файла
|
||||||
|
нет, утилита сообщает, что архив не найден, и файлы не копирует
|
||||||
|
(разметка при этом уже выполнена);
|
||||||
|
* служебный параметр `/wim=<путь>` оставлен только для автотестов.
|
||||||
|
|
||||||
|
Раздел определяется по номеру раздела на выбранном диске
|
||||||
|
(`Win32_DiskPartition.DiskIndex/Index` + ассоциация
|
||||||
|
`Win32_LogicalDiskToPartition`), а не по букве.
|
||||||
|
|
||||||
|
Архив `Data.pf` — это обычный WIM без сжатия (`/Compress:none`), только под
|
||||||
|
другим именем. DISM применяет его так же, как `.wim`.
|
||||||
|
|
||||||
|
## Служебные ключи (не для обычного запуска)
|
||||||
|
|
||||||
|
| Ключ | Назначение |
|
||||||
|
|------|------------|
|
||||||
|
| `/selftest` | пишет `%TEMP%\paravis_selftest.txt` (Windows-dir, номера системных дисков, admin) и выходит |
|
||||||
|
| `/verify=N` | пишет результат проверки раскладки диска N в `%TEMP%\paravis_verify.txt` (только чтение) |
|
||||||
|
| `/testdisk=N` | работает с диском N без выбора в списке — только для автотестов на VHD |
|
||||||
|
| `/wim=<путь>` | путь к архиву (по умолчанию — `Data.pf` рядом с exe) |
|
||||||
|
|
||||||
|
## Сборка
|
||||||
|
|
||||||
|
```
|
||||||
|
"C:\Program Files (x86)\AutoIt3\Aut2Exe\Aut2exe.exe" /in usb-install.au3 /out usb-install.exe /icon ..\..\windows-installer\sec.ico /x86
|
||||||
|
```
|
||||||
|
|
||||||
|
Автотесты (`driver*.au3`, `watch.au3`, скрипты PowerShell) в этот
|
||||||
|
репозиторий не входят: это были локальные стенды для VHD и реальной флешки.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="PARAVIS.FLASH" type="win32"/>
|
||||||
|
<description>PARAVIS FLASH installer</description>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
</assembly>
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,151 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "disks.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
enum class Hit {
|
||||||
|
None,
|
||||||
|
Header,
|
||||||
|
Title,
|
||||||
|
Sub,
|
||||||
|
Min,
|
||||||
|
Close,
|
||||||
|
Card,
|
||||||
|
Label,
|
||||||
|
Combo,
|
||||||
|
Drop,
|
||||||
|
DropName,
|
||||||
|
DropSize,
|
||||||
|
Arrow,
|
||||||
|
Status,
|
||||||
|
Progress,
|
||||||
|
Work,
|
||||||
|
Go,
|
||||||
|
Hint
|
||||||
|
};
|
||||||
|
|
||||||
|
class App {
|
||||||
|
public:
|
||||||
|
HINSTANCE hInst = nullptr;
|
||||||
|
HWND hwnd = nullptr;
|
||||||
|
HWND hProgress = nullptr;
|
||||||
|
HWND hPopup = nullptr;
|
||||||
|
UINT dpi = 96;
|
||||||
|
|
||||||
|
HFONT fontTitle = nullptr;
|
||||||
|
HFONT fontSub = nullptr;
|
||||||
|
HFONT fontMin = nullptr;
|
||||||
|
HFONT fontClose = nullptr;
|
||||||
|
HFONT fontText = nullptr;
|
||||||
|
HFONT fontBold = nullptr;
|
||||||
|
HFONT fontGo = nullptr;
|
||||||
|
HFONT fontArrow = nullptr;
|
||||||
|
|
||||||
|
int width = 376;
|
||||||
|
int height = 183;
|
||||||
|
|
||||||
|
std::vector<DiskItem> disks;
|
||||||
|
int selIdx = 0;
|
||||||
|
bool busy = false;
|
||||||
|
bool modal = false;
|
||||||
|
bool canInstall = false;
|
||||||
|
bool installed = false;
|
||||||
|
bool cancel = false;
|
||||||
|
bool needScan = false;
|
||||||
|
bool dimmed = false;
|
||||||
|
int phase = 0;
|
||||||
|
int applied = 0;
|
||||||
|
|
||||||
|
ULONGLONG devTick = 0;
|
||||||
|
ULONGLONG clickGuard = 0;
|
||||||
|
ULONGLONG dropGuard = 0;
|
||||||
|
bool goPollDown = false;
|
||||||
|
bool dropDownGui = false;
|
||||||
|
bool popupMouseDown = false;
|
||||||
|
bool minDown = false;
|
||||||
|
bool closeDown = false;
|
||||||
|
bool dragging = false;
|
||||||
|
int dragX = 0;
|
||||||
|
int dragY = 0;
|
||||||
|
|
||||||
|
std::wstring sig = L"-";
|
||||||
|
std::wstring runStamp;
|
||||||
|
std::wstring wimPath;
|
||||||
|
int testDisk = -1;
|
||||||
|
std::wstring workText;
|
||||||
|
std::wstring labelText = L"Выбран накопитель:";
|
||||||
|
std::wstring statusText;
|
||||||
|
std::wstring goText = L"УСТАНОВИТЬ";
|
||||||
|
|
||||||
|
COLORREF goBg = ColorFromHex(0x2563EB);
|
||||||
|
COLORREF goFg = RGB(255, 255, 255);
|
||||||
|
COLORREF comboFrame = ColorFromHex(0xD5DAE3);
|
||||||
|
COLORREF minBg = ColorFromHex(0x1E2A44);
|
||||||
|
COLORREF closeBg = ColorFromHex(0x1E2A44);
|
||||||
|
|
||||||
|
COLORREF dropBg = RGB(255, 255, 255);
|
||||||
|
COLORREF nameBg = RGB(255, 255, 255);
|
||||||
|
COLORREF nameFg = ColorFromHex(0x1F2430);
|
||||||
|
COLORREF sizeBg = RGB(255, 255, 255);
|
||||||
|
COLORREF sizeFg = ColorFromHex(0x1F2430);
|
||||||
|
COLORREF arrowBg = RGB(255, 255, 255);
|
||||||
|
COLORREF arrowFg = ColorFromHex(0x6B7280);
|
||||||
|
|
||||||
|
int popupHover = -1;
|
||||||
|
|
||||||
|
HANDLE runJob = nullptr;
|
||||||
|
HANDLE runProcess = nullptr;
|
||||||
|
DWORD runPid = 0;
|
||||||
|
|
||||||
|
bool Init(HWND window);
|
||||||
|
void CreateFonts();
|
||||||
|
void DestroyFonts();
|
||||||
|
|
||||||
|
void ScanDisks();
|
||||||
|
void ShowSelected();
|
||||||
|
void RefreshGo();
|
||||||
|
void GoCaption();
|
||||||
|
void GoState(bool on);
|
||||||
|
void ArrowState();
|
||||||
|
void DimControls(bool on);
|
||||||
|
void BusySet(bool value);
|
||||||
|
void CancelLook(Hit hit, bool leftDown);
|
||||||
|
void CheckInstalled();
|
||||||
|
void Apply();
|
||||||
|
int ApplyImages(int disk, const std::wstring& wim);
|
||||||
|
bool DismApply(const std::wstring& wim, int index, const std::wstring& dir,
|
||||||
|
const std::wstring& caption);
|
||||||
|
int RunCmd(const std::wstring& commandLine, const std::wstring& caption);
|
||||||
|
void HideVentoy(int disk);
|
||||||
|
std::wstring FindWim();
|
||||||
|
int ShowMessage(UINT flags, const wchar_t* title, const std::wstring& text);
|
||||||
|
|
||||||
|
void SetWork(const std::wstring& text);
|
||||||
|
void ShowProgress(int value);
|
||||||
|
void HideProgress();
|
||||||
|
void InvalidateAll();
|
||||||
|
void InvalidatePopup();
|
||||||
|
|
||||||
|
void Paint(HDC dc);
|
||||||
|
void PaintPopup(HDC dc);
|
||||||
|
void OpenPopup();
|
||||||
|
void ClosePopup();
|
||||||
|
void TogglePopup();
|
||||||
|
void HandlePopup(POINT screen, bool leftDown);
|
||||||
|
void OnPoll();
|
||||||
|
void OnAutoRefresh();
|
||||||
|
void OnDeviceChange(WPARAM wParam);
|
||||||
|
void PumpMessages();
|
||||||
|
void SleepPump(int milliseconds);
|
||||||
|
bool WaitForProcess(HANDLE process);
|
||||||
|
bool GoPoll(Hit hit, bool leftDown);
|
||||||
|
void ConfirmCancel();
|
||||||
|
void RequestCancel();
|
||||||
|
|
||||||
|
Hit HitTest(POINT pt) const;
|
||||||
|
RECT PopupScreenRect() const;
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="PARAVIS.FLASH" type="win32"/>
|
||||||
|
<description>PARAVIS FLASH installer</description>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
</assembly>
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
1 ICON "sec.ico"
|
||||||
|
1 24 "app.manifest"
|
||||||
|
|
||||||
|
VS_VERSION_INFO VERSIONINFO
|
||||||
|
FILEVERSION 1,0,0,0
|
||||||
|
PRODUCTVERSION 1,0,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"
|
||||||
|
VALUE "FileDescription", "PARAVIS FLASH installer"
|
||||||
|
VALUE "FileVersion", "1.0.0.0"
|
||||||
|
VALUE "InternalName", "ParavisInstaller"
|
||||||
|
VALUE "OriginalFilename", "Paravis installer.exe"
|
||||||
|
VALUE "ProductName", "PARAVIS FLASH"
|
||||||
|
VALUE "ProductVersion", "1.0.0.0"
|
||||||
|
END
|
||||||
|
END
|
||||||
|
BLOCK "VarFileInfo"
|
||||||
|
BEGIN
|
||||||
|
VALUE "Translation", 0x409, 1200
|
||||||
|
END
|
||||||
|
END
|
||||||
+496
@@ -0,0 +1,496 @@
|
|||||||
|
#include "disks.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <winioctl.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cwctype>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
HANDLE OpenPhysicalDrive(int disk)
|
||||||
|
{
|
||||||
|
std::wstring path = L"\\\\.\\PhysicalDrive" + std::to_wstring(disk);
|
||||||
|
HANDLE handle = CreateFileW(path.c_str(), GENERIC_READ,
|
||||||
|
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
|
||||||
|
OPEN_EXISTING, 0, nullptr);
|
||||||
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
|
handle = CreateFileW(path.c_str(), 0,
|
||||||
|
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
|
||||||
|
OPEN_EXISTING, 0, nullptr);
|
||||||
|
}
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE OpenVolume(const 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);
|
||||||
|
}
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring TrimAscii(std::string value)
|
||||||
|
{
|
||||||
|
size_t begin = 0;
|
||||||
|
while (begin < value.size() && (value[begin] == ' ' || value[begin] == '\t' ||
|
||||||
|
value[begin] == '\r' || value[begin] == '\n')) {
|
||||||
|
++begin;
|
||||||
|
}
|
||||||
|
size_t end = value.size();
|
||||||
|
while (end > begin && (value[end - 1] == ' ' || value[end - 1] == '\t' ||
|
||||||
|
value[end - 1] == '\r' || value[end - 1] == '\n')) {
|
||||||
|
--end;
|
||||||
|
}
|
||||||
|
return AnsiToWide(value.substr(begin, end - begin), CP_ACP);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring TrimWide(std::wstring value)
|
||||||
|
{
|
||||||
|
size_t begin = 0;
|
||||||
|
while (begin < value.size() && iswspace(value[begin])) {
|
||||||
|
++begin;
|
||||||
|
}
|
||||||
|
size_t end = value.size();
|
||||||
|
while (end > begin && iswspace(value[end - 1])) {
|
||||||
|
--end;
|
||||||
|
}
|
||||||
|
return value.substr(begin, end - begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring DescriptorString(const std::vector<BYTE>& buffer,
|
||||||
|
const STORAGE_DEVICE_DESCRIPTOR& descriptor,
|
||||||
|
DWORD offset)
|
||||||
|
{
|
||||||
|
if (offset == 0 || offset >= descriptor.Size || offset >= buffer.size()) {
|
||||||
|
return std::wstring();
|
||||||
|
}
|
||||||
|
size_t available = std::min<size_t>(descriptor.Size, buffer.size()) - offset;
|
||||||
|
const char* start = reinterpret_cast<const char*>(buffer.data() + offset);
|
||||||
|
size_t length = 0;
|
||||||
|
while (length < available && start[length] != '\0') {
|
||||||
|
++length;
|
||||||
|
}
|
||||||
|
return TrimAscii(std::string(start, length));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StorageDescriptor {
|
||||||
|
bool valid = false;
|
||||||
|
int busType = -1;
|
||||||
|
bool removable = false;
|
||||||
|
std::wstring vendor;
|
||||||
|
std::wstring product;
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageDescriptor QueryStorageDescriptor(HANDLE handle)
|
||||||
|
{
|
||||||
|
StorageDescriptor result;
|
||||||
|
STORAGE_PROPERTY_QUERY query{};
|
||||||
|
query.PropertyId = StorageDeviceProperty;
|
||||||
|
query.QueryType = PropertyStandardQuery;
|
||||||
|
|
||||||
|
std::vector<BYTE> buffer(4096);
|
||||||
|
DWORD returned = 0;
|
||||||
|
if (!DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query),
|
||||||
|
buffer.data(), static_cast<DWORD>(buffer.size()), &returned, nullptr)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (returned < sizeof(STORAGE_DEVICE_DESCRIPTOR)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* descriptor = reinterpret_cast<STORAGE_DEVICE_DESCRIPTOR*>(buffer.data());
|
||||||
|
if (descriptor->Size < sizeof(STORAGE_DEVICE_DESCRIPTOR) || descriptor->Size > buffer.size()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.valid = true;
|
||||||
|
result.busType = static_cast<int>(descriptor->BusType);
|
||||||
|
result.removable = descriptor->RemovableMedia != 0;
|
||||||
|
result.vendor = DescriptorString(buffer, *descriptor, descriptor->VendorIdOffset);
|
||||||
|
result.product = DescriptorString(buffer, *descriptor, descriptor->ProductIdOffset);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QueryDiskSize(HANDLE handle, unsigned long long& size)
|
||||||
|
{
|
||||||
|
GET_LENGTH_INFORMATION length{};
|
||||||
|
DWORD returned = 0;
|
||||||
|
if (DeviceIoControl(handle, IOCTL_DISK_GET_LENGTH_INFO, nullptr, 0,
|
||||||
|
&length, sizeof(length), &returned, nullptr)) {
|
||||||
|
size = static_cast<unsigned long long>(length.Length.QuadPart);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> buffer(sizeof(DISK_GEOMETRY_EX) + 256);
|
||||||
|
if (DeviceIoControl(handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nullptr, 0,
|
||||||
|
buffer.data(), static_cast<DWORD>(buffer.size()), &returned, nullptr)) {
|
||||||
|
auto* geometry = reinterpret_cast<DISK_GEOMETRY_EX*>(buffer.data());
|
||||||
|
size = static_cast<unsigned long long>(geometry->DiskSize.QuadPart);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring MakeDiskName(const StorageDescriptor& descriptor)
|
||||||
|
{
|
||||||
|
std::wstring name;
|
||||||
|
if (!descriptor.vendor.empty()) {
|
||||||
|
name = descriptor.vendor;
|
||||||
|
}
|
||||||
|
if (!descriptor.product.empty()) {
|
||||||
|
if (!name.empty()) {
|
||||||
|
name += L" ";
|
||||||
|
}
|
||||||
|
name += descriptor.product;
|
||||||
|
}
|
||||||
|
name = TrimWide(name);
|
||||||
|
if (name.empty()) {
|
||||||
|
name = L"(без названия)";
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddDisk(std::vector<DiskItem>& disks, int number,
|
||||||
|
const StorageDescriptor& descriptor, unsigned long long size)
|
||||||
|
{
|
||||||
|
DiskItem item;
|
||||||
|
item.number = number;
|
||||||
|
item.size = size;
|
||||||
|
item.name = MakeDiskName(descriptor);
|
||||||
|
item.cap = NominalSize(size);
|
||||||
|
disks.push_back(std::move(item));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetVolumeExtents(wchar_t letter,
|
||||||
|
std::vector<std::pair<int, unsigned long long>>& extents)
|
||||||
|
{
|
||||||
|
HANDLE handle = OpenVolume(letter);
|
||||||
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> buffer(sizeof(VOLUME_DISK_EXTENTS) + 32 * sizeof(DISK_EXTENT));
|
||||||
|
DWORD returned = 0;
|
||||||
|
bool ok = DeviceIoControl(handle, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, nullptr, 0,
|
||||||
|
buffer.data(), static_cast<DWORD>(buffer.size()),
|
||||||
|
&returned, nullptr) != FALSE;
|
||||||
|
CloseHandle(handle);
|
||||||
|
if (!ok || returned < sizeof(VOLUME_DISK_EXTENTS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* info = reinterpret_cast<VOLUME_DISK_EXTENTS*>(buffer.data());
|
||||||
|
for (DWORD i = 0; i < info->NumberOfDiskExtents; ++i) {
|
||||||
|
extents.emplace_back(static_cast<int>(info->Extents[i].DiskNumber),
|
||||||
|
static_cast<unsigned long long>(info->Extents[i].StartingOffset.QuadPart));
|
||||||
|
}
|
||||||
|
return !extents.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FindVolumeForPartition(int disk, unsigned long long offset,
|
||||||
|
wchar_t& letter, std::wstring& label, std::wstring& fileSystem)
|
||||||
|
{
|
||||||
|
for (wchar_t ch = L'A'; ch <= L'Z'; ++ch) {
|
||||||
|
std::wstring root;
|
||||||
|
root.push_back(ch);
|
||||||
|
root += L":\\";
|
||||||
|
if (GetDriveTypeW(root.c_str()) == DRIVE_NO_ROOT_DIR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<int, unsigned long long>> extents;
|
||||||
|
if (!GetVolumeExtents(ch, extents)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matched = false;
|
||||||
|
for (const auto& extent : extents) {
|
||||||
|
if (extent.first == disk && extent.second == offset) {
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!matched) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
letter = ch;
|
||||||
|
wchar_t volumeName[256] = {};
|
||||||
|
wchar_t fsName[64] = {};
|
||||||
|
DWORD serial = 0;
|
||||||
|
DWORD maxComponent = 0;
|
||||||
|
DWORD flags = 0;
|
||||||
|
if (GetVolumeInformationW(root.c_str(), volumeName, 256, &serial,
|
||||||
|
&maxComponent, &flags, fsName, 64)) {
|
||||||
|
label = TrimWide(volumeName);
|
||||||
|
fileSystem = fsName;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::vector<DiskItem> ScanUsbDisks()
|
||||||
|
{
|
||||||
|
std::vector<DiskItem> disks;
|
||||||
|
struct Candidate {
|
||||||
|
int number;
|
||||||
|
bool removable;
|
||||||
|
unsigned long long size;
|
||||||
|
StorageDescriptor descriptor;
|
||||||
|
};
|
||||||
|
std::vector<Candidate> candidates;
|
||||||
|
|
||||||
|
for (int disk = 0; disk < 128; ++disk) {
|
||||||
|
HANDLE handle = OpenPhysicalDrive(disk);
|
||||||
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
StorageDescriptor descriptor = QueryStorageDescriptor(handle);
|
||||||
|
unsigned long long size = 0;
|
||||||
|
QueryDiskSize(handle, size);
|
||||||
|
CloseHandle(handle);
|
||||||
|
|
||||||
|
if (descriptor.valid && descriptor.busType == BusTypeUsb) {
|
||||||
|
AddDisk(disks, disk, descriptor, size);
|
||||||
|
} else {
|
||||||
|
candidates.push_back({disk, descriptor.valid && descriptor.removable, size, descriptor});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disks.empty()) {
|
||||||
|
for (const auto& candidate : candidates) {
|
||||||
|
if (candidate.descriptor.valid &&
|
||||||
|
(candidate.descriptor.busType == BusTypeUsb || candidate.removable)) {
|
||||||
|
AddDisk(disks, candidate.number, candidate.descriptor, candidate.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return disks;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetDiskSizeBytes(int disk, unsigned long long& size)
|
||||||
|
{
|
||||||
|
HANDLE handle = OpenPhysicalDrive(disk);
|
||||||
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ok = QueryDiskSize(handle, size);
|
||||||
|
CloseHandle(handle);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> GetSystemDiskIds()
|
||||||
|
{
|
||||||
|
std::vector<int> result;
|
||||||
|
wchar_t windowsDir[MAX_PATH] = {};
|
||||||
|
if (GetWindowsDirectoryW(windowsDir, MAX_PATH) == 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (windowsDir[1] != L':') {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t letter = static_cast<wchar_t>(towupper(windowsDir[0]));
|
||||||
|
std::vector<std::pair<int, unsigned long long>> extents;
|
||||||
|
if (!GetVolumeExtents(letter, extents)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& extent : extents) {
|
||||||
|
if (std::find(result.begin(), result.end(), extent.first) == result.end()) {
|
||||||
|
result.push_back(extent.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
double NominalGb(unsigned long long bytes)
|
||||||
|
{
|
||||||
|
double gb = static_cast<double>(bytes) / 1000000000.0;
|
||||||
|
static const int list[] = {
|
||||||
|
1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1000, 1500, 2000, 3000,
|
||||||
|
4000, 6000, 8000, 10000, 12000, 14000, 16000, 18000, 20000
|
||||||
|
};
|
||||||
|
for (int value : list) {
|
||||||
|
if (gb <= static_cast<double>(value) * 1.02) {
|
||||||
|
return static_cast<double>(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gb;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring NominalSize(unsigned long long bytes)
|
||||||
|
{
|
||||||
|
double value = NominalGb(bytes);
|
||||||
|
if (value >= 1000.0) {
|
||||||
|
return Format(L"%g ТБ", value / 1000.0);
|
||||||
|
}
|
||||||
|
return Format(L"%d ГБ", static_cast<int>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PartitionInfo> GetPartitions(int disk, bool& ok)
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
std::vector<PartitionInfo> result;
|
||||||
|
|
||||||
|
HANDLE handle = OpenPhysicalDrive(disk);
|
||||||
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> buffer(sizeof(DRIVE_LAYOUT_INFORMATION_EX) +
|
||||||
|
256 * sizeof(PARTITION_INFORMATION_EX));
|
||||||
|
DWORD returned = 0;
|
||||||
|
bool success = DeviceIoControl(handle, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, nullptr, 0,
|
||||||
|
buffer.data(), static_cast<DWORD>(buffer.size()),
|
||||||
|
&returned, nullptr) != FALSE;
|
||||||
|
if (!success && GetLastError() == ERROR_INSUFFICIENT_BUFFER && returned > buffer.size()) {
|
||||||
|
buffer.resize(returned);
|
||||||
|
success = DeviceIoControl(handle, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, nullptr, 0,
|
||||||
|
buffer.data(), static_cast<DWORD>(buffer.size()),
|
||||||
|
&returned, nullptr) != FALSE;
|
||||||
|
}
|
||||||
|
CloseHandle(handle);
|
||||||
|
|
||||||
|
if (!success || returned < sizeof(DRIVE_LAYOUT_INFORMATION_EX)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* layout = reinterpret_cast<DRIVE_LAYOUT_INFORMATION_EX*>(buffer.data());
|
||||||
|
for (DWORD i = 0; i < layout->PartitionCount; ++i) {
|
||||||
|
const PARTITION_INFORMATION_EX& entry = layout->PartitionEntry[i];
|
||||||
|
if (entry.PartitionLength.QuadPart <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
PartitionInfo info;
|
||||||
|
info.offset = static_cast<unsigned long long>(entry.StartingOffset.QuadPart);
|
||||||
|
info.size = static_cast<unsigned long long>(entry.PartitionLength.QuadPart);
|
||||||
|
info.number = static_cast<int>(entry.PartitionNumber);
|
||||||
|
result.push_back(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(result.begin(), result.end(), [](const PartitionInfo& a, const PartitionInfo& b) {
|
||||||
|
return a.offset < b.offset;
|
||||||
|
});
|
||||||
|
ok = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring GetPartitionLetter(int disk, int idx)
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
std::vector<PartitionInfo> partitions = GetPartitions(disk, ok);
|
||||||
|
if (!ok || idx < 0 || idx >= static_cast<int>(partitions.size())) {
|
||||||
|
return std::wstring();
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t letter = 0;
|
||||||
|
std::wstring label;
|
||||||
|
std::wstring fileSystem;
|
||||||
|
if (!FindVolumeForPartition(disk, partitions[idx].offset, letter, label, fileSystem)) {
|
||||||
|
return std::wstring();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring result;
|
||||||
|
result.push_back(letter);
|
||||||
|
result += L":\\";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskIsInstalled(int disk)
|
||||||
|
{
|
||||||
|
std::wstring first = GetPartitionLetter(disk, 0);
|
||||||
|
if (first.empty() || !FileExists(first + L"efi\\Paravis\\Boot\\NOT.DEL")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::wstring third = GetPartitionLetter(disk, 2);
|
||||||
|
if (third.empty() || !FileExists(third + L"ISO\\Ventoy\\NOT.DEL")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring VerifyLayout(int disk, bool& ok)
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
bool layoutOk = false;
|
||||||
|
std::vector<PartitionInfo> partitions = GetPartitions(disk, layoutOk);
|
||||||
|
if (!layoutOk) {
|
||||||
|
return L"разделов: ? (нет доступа к WMI)";
|
||||||
|
}
|
||||||
|
|
||||||
|
const wchar_t* want[] = { L"PARAVIS", L"VTOYEFI", L"DATA" };
|
||||||
|
std::wstring report;
|
||||||
|
std::wstring labels = L"|";
|
||||||
|
unsigned long long sizes[3] = { 0, 0, 0 };
|
||||||
|
int count = static_cast<int>(partitions.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
unsigned long long megabytes = partitions[i].size / 1048576ULL;
|
||||||
|
if (i < 3) {
|
||||||
|
sizes[i] = megabytes;
|
||||||
|
}
|
||||||
|
std::wstring line = Format(L" раздел %d: %llu МБ", i + 1, megabytes);
|
||||||
|
|
||||||
|
wchar_t letter = 0;
|
||||||
|
std::wstring label;
|
||||||
|
std::wstring fileSystem;
|
||||||
|
if (FindVolumeForPartition(disk, partitions[i].offset, letter, label, fileSystem)) {
|
||||||
|
std::wstring device;
|
||||||
|
device.push_back(letter);
|
||||||
|
device += L":";
|
||||||
|
line += L" " + device + L" " + label + L" " + fileSystem;
|
||||||
|
if (!label.empty()) {
|
||||||
|
labels += label + L"|";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
report += line + L"\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring missing;
|
||||||
|
for (const wchar_t* name : want) {
|
||||||
|
std::wstring token = std::wstring(L"|") + name + L"|";
|
||||||
|
if (labels.find(token) == std::wstring::npos) {
|
||||||
|
missing += L" ";
|
||||||
|
missing += name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto absDiff = [](unsigned long long value, unsigned long long expected) -> long long {
|
||||||
|
return value >= expected
|
||||||
|
? static_cast<long long>(value - expected)
|
||||||
|
: static_cast<long long>(expected - value);
|
||||||
|
};
|
||||||
|
bool sizeOk = absDiff(sizes[0], 16384) <= 8 && absDiff(sizes[1], 32) <= 8;
|
||||||
|
|
||||||
|
std::wstring head = Format(L"разделов: %d", count);
|
||||||
|
if (count != 3) {
|
||||||
|
head += L" (ожидалось 3)";
|
||||||
|
}
|
||||||
|
if (!missing.empty()) {
|
||||||
|
report += L"нет меток:" + missing + L"\r\n";
|
||||||
|
}
|
||||||
|
if (!sizeOk && count == 3) {
|
||||||
|
report += L"не совпали размеры 1-го и 2-го разделов\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = count == 3 && missing.empty() && sizeOk;
|
||||||
|
return head + L"\r\n" + report;
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct DiskItem {
|
||||||
|
int number = 0;
|
||||||
|
unsigned long long size = 0;
|
||||||
|
std::wstring name;
|
||||||
|
std::wstring cap;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PartitionInfo {
|
||||||
|
unsigned long long offset = 0;
|
||||||
|
unsigned long long size = 0;
|
||||||
|
int number = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<DiskItem> ScanUsbDisks();
|
||||||
|
bool GetDiskSizeBytes(int disk, unsigned long long& size);
|
||||||
|
std::vector<int> GetSystemDiskIds();
|
||||||
|
double NominalGb(unsigned long long bytes);
|
||||||
|
std::wstring NominalSize(unsigned long long bytes);
|
||||||
|
std::vector<PartitionInfo> GetPartitions(int disk, bool& ok);
|
||||||
|
std::wstring GetPartitionLetter(int disk, int idx);
|
||||||
|
bool DiskIsInstalled(int disk);
|
||||||
|
std::wstring VerifyLayout(int disk, bool& ok);
|
||||||
+694
@@ -0,0 +1,694 @@
|
|||||||
|
#include "app.h"
|
||||||
|
#include "disks.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cwchar>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const unsigned long long MB = 1048576ULL;
|
||||||
|
const int P1_MB = 16384;
|
||||||
|
const int P2_MB = 32;
|
||||||
|
const int TAIL_MB = 3;
|
||||||
|
|
||||||
|
std::wstring TempName(const std::wstring& run, const std::wstring& suffix)
|
||||||
|
{
|
||||||
|
return GetTempDir() + L"paravis_" + run + L"_" + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RunHidden(App* app, const std::wstring& commandLine)
|
||||||
|
{
|
||||||
|
DebugLog(L"RunHidden: %s", commandLine.c_str());
|
||||||
|
STARTUPINFOW startup{};
|
||||||
|
startup.cb = sizeof(startup);
|
||||||
|
startup.dwFlags = STARTF_USESHOWWINDOW;
|
||||||
|
startup.wShowWindow = SW_HIDE;
|
||||||
|
|
||||||
|
PROCESS_INFORMATION process{};
|
||||||
|
std::vector<wchar_t> 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 false;
|
||||||
|
}
|
||||||
|
|
||||||
|
app->WaitForProcess(process.hProcess);
|
||||||
|
DWORD exitCode = 1;
|
||||||
|
GetExitCodeProcess(process.hProcess, &exitCode);
|
||||||
|
CloseHandle(process.hThread);
|
||||||
|
CloseHandle(process.hProcess);
|
||||||
|
DebugLog(L"RunHidden done code=%lu", exitCode);
|
||||||
|
return exitCode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KillProcessTree(DWORD pid)
|
||||||
|
{
|
||||||
|
if (pid == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t systemDir[MAX_PATH] = {};
|
||||||
|
GetSystemDirectoryW(systemDir, MAX_PATH);
|
||||||
|
std::wstring command = L"\"" + std::wstring(systemDir) +
|
||||||
|
L"\\taskkill.exe\" /T /F /PID " + std::to_wstring(pid);
|
||||||
|
|
||||||
|
STARTUPINFOW startup{};
|
||||||
|
startup.cb = sizeof(startup);
|
||||||
|
startup.dwFlags = STARTF_USESHOWWINDOW;
|
||||||
|
startup.wShowWindow = SW_HIDE;
|
||||||
|
|
||||||
|
PROCESS_INFORMATION process{};
|
||||||
|
std::vector<wchar_t> buffer(command.begin(), command.end());
|
||||||
|
buffer.push_back(L'\0');
|
||||||
|
if (CreateProcessW(nullptr, buffer.data(), nullptr, nullptr, FALSE,
|
||||||
|
CREATE_NO_WINDOW, nullptr, nullptr, &startup, &process)) {
|
||||||
|
CloseHandle(process.hThread);
|
||||||
|
CloseHandle(process.hProcess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LaunchLoader(const std::wstring& path)
|
||||||
|
{
|
||||||
|
DebugLog(L"LaunchLoader: %s", path.c_str());
|
||||||
|
std::wstring commandLine = L"\"" + path + L"\"";
|
||||||
|
std::vector<wchar_t> buffer(commandLine.begin(), commandLine.end());
|
||||||
|
buffer.push_back(L'\0');
|
||||||
|
|
||||||
|
STARTUPINFOW startup{};
|
||||||
|
startup.cb = sizeof(startup);
|
||||||
|
PROCESS_INFORMATION process{};
|
||||||
|
if (!CreateProcessW(nullptr, buffer.data(), nullptr, nullptr, FALSE, 0,
|
||||||
|
nullptr, nullptr, &startup, &process)) {
|
||||||
|
DebugLog(L"LaunchLoader failed error=%lu", GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DebugLog(L"LaunchLoader started pid=%lu", process.dwProcessId);
|
||||||
|
CloseHandle(process.hThread);
|
||||||
|
CloseHandle(process.hProcess);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateOutputProgress(App* app, const std::string& output,
|
||||||
|
const std::wstring& caption, double& shown)
|
||||||
|
{
|
||||||
|
size_t percent = output.rfind('%');
|
||||||
|
if (percent == std::string::npos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = percent;
|
||||||
|
while (index > 0 && (output[index - 1] == ' ' || output[index - 1] == '\t' ||
|
||||||
|
output[index - 1] == '\r' || output[index - 1] == '\n')) {
|
||||||
|
--index;
|
||||||
|
}
|
||||||
|
size_t numberEnd = index;
|
||||||
|
while (index > 0 && (std::isdigit(static_cast<unsigned char>(output[index - 1])) ||
|
||||||
|
output[index - 1] == '.' || output[index - 1] == ',')) {
|
||||||
|
--index;
|
||||||
|
}
|
||||||
|
if (index == numberEnd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string number = output.substr(index, numberEnd - index);
|
||||||
|
std::replace(number.begin(), number.end(), ',', '.');
|
||||||
|
char* parseEnd = nullptr;
|
||||||
|
double value = std::strtod(number.c_str(), &parseEnd);
|
||||||
|
if (parseEnd == number.c_str()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != shown) {
|
||||||
|
shown = value;
|
||||||
|
app->SetWork(caption);
|
||||||
|
app->ShowProgress(static_cast<int>(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::wstring App::FindWim()
|
||||||
|
{
|
||||||
|
if (!wimPath.empty()) {
|
||||||
|
if (FileExists(wimPath)) {
|
||||||
|
return wimPath;
|
||||||
|
}
|
||||||
|
ShowMessage(MB_ICONWARNING | MB_OK, L"Архив не найден",
|
||||||
|
L"Файл не найден:\r\n" + wimPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring path = JoinPath(GetExeDir(), L"Data.pf");
|
||||||
|
if (FileExists(path)) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return std::wstring();
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::Apply()
|
||||||
|
{
|
||||||
|
DebugLog(L"Apply enter busy=%d canInstall=%d testDisk=%d selIdx=%d disks=%zu",
|
||||||
|
busy ? 1 : 0, canInstall ? 1 : 0, testDisk, selIdx, disks.size());
|
||||||
|
if (busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!canInstall && testDisk < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int disk = testDisk;
|
||||||
|
unsigned long long expectedSize = 0;
|
||||||
|
if (disk < 0) {
|
||||||
|
int index = (selIdx >= 0 && selIdx < static_cast<int>(disks.size())) ? selIdx : -1;
|
||||||
|
if (index < 0) {
|
||||||
|
ShowMessage(MB_ICONWARNING | MB_OK, L"Устройство не выбрано",
|
||||||
|
L"Выберите флешку в списке.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
disk = disks[index].number;
|
||||||
|
expectedSize = disks[index].size;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> systemDisks = GetSystemDiskIds();
|
||||||
|
if (std::find(systemDisks.begin(), systemDisks.end(), disk) != systemDisks.end()) {
|
||||||
|
ShowMessage(MB_ICONERROR | MB_OK, L"Это системный диск",
|
||||||
|
Format(L"Диск %d содержит Windows. Разметка отменена - выберите другой диск.",
|
||||||
|
disk));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long sizeBytes = 0;
|
||||||
|
if (!GetDiskSizeBytes(disk, sizeBytes) || sizeBytes == 0) {
|
||||||
|
ShowMessage(MB_ICONERROR | MB_OK, L"Ошибка",
|
||||||
|
Format(L"Не удалось определить размер диска %d.", disk));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expectedSize > 0) {
|
||||||
|
unsigned long long difference = sizeBytes > expectedSize
|
||||||
|
? sizeBytes - expectedSize
|
||||||
|
: expectedSize - sizeBytes;
|
||||||
|
if (difference > expectedSize / 100) {
|
||||||
|
ShowMessage(MB_ICONERROR | MB_OK, L"Диск не совпадает",
|
||||||
|
Format(L"Размер выбранного диска (%.1f ГБ) не совпадает с тем, "
|
||||||
|
L"что был в списке (%.1f ГБ).\r\n\r\n"
|
||||||
|
L"Нажмите «Обновить» и выберите устройство заново.",
|
||||||
|
static_cast<double>(sizeBytes) / 1073741824.0,
|
||||||
|
static_cast<double>(expectedSize) / 1073741824.0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long totalMB = sizeBytes / MB;
|
||||||
|
long long p3 = static_cast<long long>(totalMB) - 1 - P1_MB - P2_MB - TAIL_MB;
|
||||||
|
if (p3 < 512) {
|
||||||
|
ShowMessage(MB_ICONERROR | MB_OK, L"Слишком маленький диск",
|
||||||
|
Format(L"Нужен диск не меньше %d МБ.\r\nСейчас: %llu МБ.",
|
||||||
|
P1_MB + P2_MB + TAIL_MB + 512, totalMB));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring message =
|
||||||
|
Format(L"Накопитель (%.1f ГБ)\r\n\r\n",
|
||||||
|
static_cast<double>(sizeBytes) / 1073741824.0);
|
||||||
|
message += L"Будет создано:\r\n";
|
||||||
|
message += L" 1) FAT32 16 ГБ метка PARAVIS\r\n";
|
||||||
|
message += L" 2) FAT 32 МБ метка VTOYEFI\r\n";
|
||||||
|
message += Format(L" 3) exFAT %.1f ГБ метка DATA\r\n\r\n",
|
||||||
|
static_cast<double>(p3) / 1024.0);
|
||||||
|
message += L"Затем на разделы будут распакованы файлы из архива Data.pf\r\n";
|
||||||
|
message += L"(1 -> PARAVIS, 2 -> VTOYEFI, 3 -> DATA).\r\n\r\n";
|
||||||
|
message += L"ВНИМАНИЕ! ВСЕ ДАННЫЕ НА ЭТОМ ДИСКЕ БУДУТ УНИЧТОЖЕНЫ.\r\n";
|
||||||
|
message += L"Продолжить?";
|
||||||
|
|
||||||
|
goText = L"Ожидание подтверждения...";
|
||||||
|
goBg = ColorFromHex(0xAEB6C4);
|
||||||
|
goFg = ColorFromHex(0xEDEFF3);
|
||||||
|
InvalidateAll();
|
||||||
|
|
||||||
|
if (ShowMessage(MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2,
|
||||||
|
L"Подтверждение", message) != IDYES) {
|
||||||
|
GoCaption();
|
||||||
|
RefreshGo();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugLog(L"confirmation accepted, disk=%d size=%llu totalMB=%llu p3=%lld",
|
||||||
|
disk, sizeBytes, totalMB, p3);
|
||||||
|
BusySet(true);
|
||||||
|
phase = 1;
|
||||||
|
DebugLog(L"busy set, phase=1");
|
||||||
|
|
||||||
|
std::wstring onlineFile = TempName(runStamp, L"online.txt");
|
||||||
|
std::wstring onlineScript = Format(L"select disk %d\r\nonline disk\r\nexit\r\n", disk);
|
||||||
|
if (WriteAnsiFile(onlineFile, onlineScript)) {
|
||||||
|
std::wstring command = L"\"" + GetComSpec() + L"\" /c diskpart.exe /s \"" +
|
||||||
|
onlineFile + L"\" > nul 2>&1";
|
||||||
|
RunHidden(this, command);
|
||||||
|
DeleteFileW(onlineFile.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring script = Format(
|
||||||
|
L"select disk %d\r\n"
|
||||||
|
L"clean\r\n"
|
||||||
|
L"convert mbr\r\n"
|
||||||
|
L"create partition primary size=%d\r\n"
|
||||||
|
L"format fs=fat32 quick label=\"PARAVIS\"\r\n"
|
||||||
|
L"active\r\n"
|
||||||
|
L"assign\r\n"
|
||||||
|
L"create partition primary size=%d\r\n"
|
||||||
|
L"format fs=fat quick label=\"VTOYEFI\"\r\n"
|
||||||
|
L"assign\r\n"
|
||||||
|
L"create partition primary size=%lld\r\n"
|
||||||
|
L"format fs=exfat quick label=\"DATA\"\r\n"
|
||||||
|
L"assign\r\n"
|
||||||
|
L"exit\r\n",
|
||||||
|
disk, P1_MB, P2_MB, p3);
|
||||||
|
|
||||||
|
std::wstring scriptFile = TempName(runStamp, L"diskpart.txt");
|
||||||
|
std::wstring logFile = TempName(runStamp, L"diskpart_log.txt");
|
||||||
|
if (!WriteAnsiFile(scriptFile, script)) {
|
||||||
|
ShowMessage(MB_ICONERROR | MB_OK, L"Ошибка",
|
||||||
|
L"Не удалось создать файл скрипта.");
|
||||||
|
BusySet(false);
|
||||||
|
HideProgress();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetWork(L"Разметка диска…");
|
||||||
|
|
||||||
|
std::wstring command = L"\"" + GetComSpec() + L"\" /c diskpart.exe /s \"" +
|
||||||
|
scriptFile + L"\" > \"" + logFile + L"\" 2>&1";
|
||||||
|
DebugLog(L"diskpart start");
|
||||||
|
RunHidden(this, command);
|
||||||
|
DebugLog(L"diskpart done");
|
||||||
|
|
||||||
|
std::string logBytes = ReadFileBytes(logFile);
|
||||||
|
std::wstring logText = AnsiToWide(logBytes, CP_ACP);
|
||||||
|
|
||||||
|
SleepPump(1500);
|
||||||
|
|
||||||
|
bool layoutOk = false;
|
||||||
|
std::wstring check = VerifyLayout(disk, layoutOk);
|
||||||
|
DebugLog(L"layoutOk=%d check=%s", layoutOk ? 1 : 0, check.c_str());
|
||||||
|
|
||||||
|
if (!layoutOk) {
|
||||||
|
SetWork(L"Раскладка не совпала с ожидаемой");
|
||||||
|
HideProgress();
|
||||||
|
ShowMessage(MB_ICONERROR | MB_OK, L"Ошибка разметки",
|
||||||
|
L"РАСКЛАДКА НЕ СОВПАЛА С ОЖИДАЕМОЙ\r\n\r\n" + check +
|
||||||
|
L"\r\n\r\nЛог: " + logFile + L"\r\n\r\n" + logText.substr(0, 600));
|
||||||
|
BusySet(false);
|
||||||
|
HideProgress();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
BusySet(false);
|
||||||
|
HideProgress();
|
||||||
|
ShowMessage(MB_ICONINFORMATION | MB_OK, L"Отменено",
|
||||||
|
L"Операция отменена пользователем.\r\n\r\n"
|
||||||
|
L"Диск размечен, файлы не скопированы.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring wim = FindWim();
|
||||||
|
DebugLog(L"wim=%s", wim.c_str());
|
||||||
|
if (wim.empty()) {
|
||||||
|
SetWork(L"Размечено, архив Data.pf не найден");
|
||||||
|
ShowMessage(MB_ICONWARNING | MB_OK, L"Разметка выполнена",
|
||||||
|
L"Диск размечен, раскладка верна.\r\n\r\n" + check +
|
||||||
|
L"\r\n\r\nНо файл Data.pf не найден рядом с программой - "
|
||||||
|
L"файлы не скопированы.");
|
||||||
|
BusySet(false);
|
||||||
|
HideProgress();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
phase = 2;
|
||||||
|
int result = ApplyImages(disk, wim);
|
||||||
|
phase = 0;
|
||||||
|
DebugLog(L"ApplyImages result=%d applied=%d cancel=%d", result, applied, cancel ? 1 : 0);
|
||||||
|
|
||||||
|
if (result == 1) {
|
||||||
|
std::wstring first = GetPartitionLetter(disk, 0);
|
||||||
|
DebugLog(L"partition0=%s", first.c_str());
|
||||||
|
|
||||||
|
// If the C++ bootloader changer sits next to the installer, put it on
|
||||||
|
// the PARAVIS partition and use it instead of the copy from Data.pf.
|
||||||
|
if (!first.empty()) {
|
||||||
|
std::wstring source = JoinPath(GetExeDir(), L"Change_BootLoader.exe");
|
||||||
|
if (FileExists(source)) {
|
||||||
|
std::wstring destination = first + L"Change_BootLoader.exe";
|
||||||
|
if (_wcsicmp(source.c_str(), destination.c_str()) == 0) {
|
||||||
|
DebugLog(L"bootloader source is already the target: %s", source.c_str());
|
||||||
|
} else if (CopyFileW(source.c_str(), destination.c_str(), FALSE)) {
|
||||||
|
DebugLog(L"copied bootloader: %s -> %s", source.c_str(), destination.c_str());
|
||||||
|
} else {
|
||||||
|
DebugLog(L"bootloader copy failed error=%lu", GetLastError());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DebugLog(L"no C++ bootloader next to installer: %s", source.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HideVentoy(disk);
|
||||||
|
SetWork(L"Готово");
|
||||||
|
HideProgress();
|
||||||
|
DebugLog(L"showing success message");
|
||||||
|
|
||||||
|
ShowMessage(MB_ICONINFORMATION | MB_OK, L"Установка завершена",
|
||||||
|
L"Установка завершена успешно. Сейчас будет запущен установщик загрузчиков. "
|
||||||
|
L"Выберите Legacy и UEFI загрузчики. Если этого не сделать, то будет "
|
||||||
|
L"работать только UEFI.");
|
||||||
|
|
||||||
|
DebugLog(L"success message closed, partition0=%s", first.c_str());
|
||||||
|
if (!first.empty()) {
|
||||||
|
std::wstring loader = first + L"Change_BootLoader.exe";
|
||||||
|
DebugLog(L"loader path=%s exists=%d", loader.c_str(), FileExists(loader) ? 1 : 0);
|
||||||
|
if (FileExists(loader)) {
|
||||||
|
if (LaunchLoader(loader)) {
|
||||||
|
DebugLog(L"loader launched, ExitProcess");
|
||||||
|
ExitProcess(0);
|
||||||
|
}
|
||||||
|
ShowMessage(MB_ICONWARNING | MB_OK, L"Установщик загрузчиков",
|
||||||
|
L"Не удалось запустить Change_BootLoader.exe.\r\n\r\n"
|
||||||
|
L"Путь: " + loader);
|
||||||
|
} else {
|
||||||
|
ShowMessage(MB_ICONWARNING | MB_OK, L"Установщик загрузчиков",
|
||||||
|
L"Файл Change_BootLoader.exe не найден на разделе PARAVIS.\r\n\r\n"
|
||||||
|
L"Путь: " + loader);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ShowMessage(MB_ICONWARNING | MB_OK, L"Установщик загрузчиков",
|
||||||
|
L"Не удалось определить букву раздела PARAVIS.");
|
||||||
|
}
|
||||||
|
} else if (result == 2) {
|
||||||
|
SetWork(L"Ошибка записи");
|
||||||
|
HideProgress();
|
||||||
|
ShowMessage(MB_ICONERROR | MB_OK, L"Ошибка копирования файлов",
|
||||||
|
L"Диск размечен, но скопировать файлы не удалось.\r\n\r\n"
|
||||||
|
L"Запустите установку ещё раз.");
|
||||||
|
} else if (result == 3) {
|
||||||
|
SetWork(L"Отменено");
|
||||||
|
HideProgress();
|
||||||
|
BusySet(false);
|
||||||
|
HideProgress();
|
||||||
|
ShowMessage(MB_ICONINFORMATION | MB_OK, L"Отменено",
|
||||||
|
Format(L"Операция отменена пользователем.\r\n\r\n"
|
||||||
|
L"Диск размечен, скопировано образов: %d из 3.", applied));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BusySet(false);
|
||||||
|
HideProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
int App::ApplyImages(int disk, const std::wstring& wim)
|
||||||
|
{
|
||||||
|
const wchar_t* names[3] = { L"PARAVIS", L"VTOYEFI", L"DATA" };
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
if (cancel) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring dir;
|
||||||
|
for (int attempt = 0; attempt < 15; ++attempt) {
|
||||||
|
dir = GetPartitionLetter(disk, i);
|
||||||
|
if (!dir.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SleepPump(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dir.empty()) {
|
||||||
|
ShowMessage(MB_ICONERROR | MB_OK, L"Ошибка",
|
||||||
|
Format(L"Не удалось определить букву раздела %d (%s).\r\n"
|
||||||
|
L"Проверьте, что раздел подключён (diskpart: assign).",
|
||||||
|
i + 1, names[i]));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring caption = std::wstring(names[i]) + L" → " + dir;
|
||||||
|
DebugLog(L"ApplyImages image=%d part=%d dir=%s", i + 1, i, dir.c_str());
|
||||||
|
ShowProgress(0);
|
||||||
|
SetWork(caption);
|
||||||
|
|
||||||
|
if (!DismApply(wim, i + 1, dir, caption)) {
|
||||||
|
if (cancel) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
++applied;
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool App::DismApply(const std::wstring& wim, int index, const std::wstring& dir,
|
||||||
|
const std::wstring& caption)
|
||||||
|
{
|
||||||
|
std::wstring arguments = L"/Apply-Image /ImageFile:\"" + wim +
|
||||||
|
L"\" /Index:" + std::to_wstring(index) +
|
||||||
|
L" /ApplyDir:" + dir;
|
||||||
|
int code = RunCmd(arguments, caption);
|
||||||
|
if (code == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetWork(caption + Format(L": ОШИБКА (код %d)", code));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int App::RunCmd(const std::wstring& arguments, const std::wstring& caption)
|
||||||
|
{
|
||||||
|
DebugLog(L"RunCmd start: dism %s", arguments.c_str());
|
||||||
|
|
||||||
|
wchar_t systemDir[MAX_PATH] = {};
|
||||||
|
GetSystemDirectoryW(systemDir, MAX_PATH);
|
||||||
|
std::wstring dism = std::wstring(systemDir) + L"\\dism.exe";
|
||||||
|
std::wstring full = L"\"" + dism + L"\" " + arguments;
|
||||||
|
|
||||||
|
SECURITY_ATTRIBUTES security{};
|
||||||
|
security.nLength = sizeof(security);
|
||||||
|
security.bInheritHandle = TRUE;
|
||||||
|
|
||||||
|
HANDLE readPipe = nullptr;
|
||||||
|
HANDLE writePipe = nullptr;
|
||||||
|
if (!CreatePipe(&readPipe, &writePipe, &security, 0)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
SetHandleInformation(readPipe, HANDLE_FLAG_INHERIT, 0);
|
||||||
|
|
||||||
|
HANDLE nullInput = CreateFileW(L"NUL", GENERIC_READ,
|
||||||
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||||
|
&security, OPEN_EXISTING, 0, nullptr);
|
||||||
|
|
||||||
|
STARTUPINFOW startup{};
|
||||||
|
startup.cb = sizeof(startup);
|
||||||
|
startup.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
||||||
|
startup.wShowWindow = SW_HIDE;
|
||||||
|
startup.hStdOutput = writePipe;
|
||||||
|
startup.hStdError = writePipe;
|
||||||
|
startup.hStdInput = nullInput;
|
||||||
|
|
||||||
|
PROCESS_INFORMATION process{};
|
||||||
|
std::vector<wchar_t> buffer(full.begin(), full.end());
|
||||||
|
buffer.push_back(L'\0');
|
||||||
|
|
||||||
|
BOOL created = CreateProcessW(dism.c_str(), buffer.data(), nullptr, nullptr, TRUE,
|
||||||
|
CREATE_NO_WINDOW | CREATE_SUSPENDED,
|
||||||
|
nullptr, nullptr, &startup, &process);
|
||||||
|
CloseHandle(writePipe);
|
||||||
|
if (nullInput != INVALID_HANDLE_VALUE) {
|
||||||
|
CloseHandle(nullInput);
|
||||||
|
}
|
||||||
|
if (!created) {
|
||||||
|
CloseHandle(readPipe);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE job = CreateJobObjectW(nullptr, nullptr);
|
||||||
|
if (job) {
|
||||||
|
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit{};
|
||||||
|
limit.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||||
|
SetInformationJobObject(job, JobObjectExtendedLimitInformation, &limit, sizeof(limit));
|
||||||
|
if (!AssignProcessToJobObject(job, process.hProcess)) {
|
||||||
|
DebugLog(L"AssignProcessToJobObject failed error=%lu", GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runJob = job;
|
||||||
|
runProcess = process.hProcess;
|
||||||
|
runPid = process.dwProcessId;
|
||||||
|
ResumeThread(process.hThread);
|
||||||
|
CloseHandle(process.hThread);
|
||||||
|
|
||||||
|
std::string output;
|
||||||
|
double shown = -1.0;
|
||||||
|
bool killed = false;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
for (;;) {
|
||||||
|
DWORD available = 0;
|
||||||
|
if (!PeekNamedPipe(readPipe, nullptr, 0, nullptr, &available, nullptr) ||
|
||||||
|
available == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
char chunk[4096];
|
||||||
|
DWORD toRead = std::min<DWORD>(available, sizeof(chunk));
|
||||||
|
DWORD read = 0;
|
||||||
|
if (!ReadFile(readPipe, chunk, toRead, &read, nullptr) || read == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
output.append(chunk, read);
|
||||||
|
}
|
||||||
|
UpdateOutputProgress(this, output, caption, shown);
|
||||||
|
|
||||||
|
DWORD wait = MsgWaitForMultipleObjects(1, &process.hProcess, FALSE, 30, QS_ALLINPUT);
|
||||||
|
if (wait == WAIT_OBJECT_0) {
|
||||||
|
for (;;) {
|
||||||
|
DWORD available = 0;
|
||||||
|
if (!PeekNamedPipe(readPipe, nullptr, 0, nullptr, &available, nullptr) ||
|
||||||
|
available == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
char chunk[4096];
|
||||||
|
DWORD toRead = std::min<DWORD>(available, sizeof(chunk));
|
||||||
|
DWORD read = 0;
|
||||||
|
if (!ReadFile(readPipe, chunk, toRead, &read, nullptr) || read == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
output.append(chunk, read);
|
||||||
|
}
|
||||||
|
UpdateOutputProgress(this, output, caption, shown);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (wait != WAIT_OBJECT_0) {
|
||||||
|
// Pump even on timeout so due WM_TIMER messages run OnPoll and
|
||||||
|
// the cancel button stays responsive during DISM.
|
||||||
|
PumpMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancel && !killed) {
|
||||||
|
killed = true;
|
||||||
|
if (job) {
|
||||||
|
TerminateJobObject(job, 1);
|
||||||
|
}
|
||||||
|
TerminateProcess(process.hProcess, 1);
|
||||||
|
KillProcessTree(process.dwProcessId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitForSingleObject(process.hProcess, INFINITE);
|
||||||
|
|
||||||
|
DWORD exitCode = 1;
|
||||||
|
GetExitCodeProcess(process.hProcess, &exitCode);
|
||||||
|
int code = static_cast<int>(exitCode);
|
||||||
|
|
||||||
|
runJob = nullptr;
|
||||||
|
runProcess = nullptr;
|
||||||
|
runPid = 0;
|
||||||
|
if (job) {
|
||||||
|
CloseHandle(job);
|
||||||
|
}
|
||||||
|
CloseHandle(process.hProcess);
|
||||||
|
CloseHandle(readPipe);
|
||||||
|
DebugLog(L"RunCmd done code=%d killed=%d cancel=%d", code, killed ? 1 : 0, cancel ? 1 : 0);
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::HideVentoy(int disk)
|
||||||
|
{
|
||||||
|
std::wstring scriptFile = TempName(runStamp, L"hide.txt");
|
||||||
|
std::wstring script = Format(
|
||||||
|
L"select disk %d\r\n"
|
||||||
|
L"select partition 2\r\n"
|
||||||
|
L"remove\r\n"
|
||||||
|
L"set id=EF override\r\n"
|
||||||
|
L"exit\r\n", disk);
|
||||||
|
|
||||||
|
if (WriteAnsiFile(scriptFile, script)) {
|
||||||
|
std::wstring command = L"\"" + GetComSpec() + L"\" /c diskpart.exe /s \"" +
|
||||||
|
scriptFile + L"\" > nul 2>&1";
|
||||||
|
RunHidden(this, command);
|
||||||
|
DeleteFileW(scriptFile.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int App::ShowMessage(UINT flags, const wchar_t* title, const std::wstring& text)
|
||||||
|
{
|
||||||
|
DebugLog(L"MsgBox enter title=%s busy=%d", title, busy ? 1 : 0);
|
||||||
|
bool previous = modal;
|
||||||
|
modal = true;
|
||||||
|
GoState(false);
|
||||||
|
DimControls(true);
|
||||||
|
|
||||||
|
int result = MessageBoxW(hwnd, text.c_str(), title, flags);
|
||||||
|
DebugLog(L"MsgBox exit title=%s result=%d", title, result);
|
||||||
|
|
||||||
|
modal = previous;
|
||||||
|
clickGuard = GetTickCount64();
|
||||||
|
if (!busy) {
|
||||||
|
DimControls(false);
|
||||||
|
RefreshGo();
|
||||||
|
} else {
|
||||||
|
CancelLook(Hit::None, false);
|
||||||
|
}
|
||||||
|
InvalidateAll();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::ConfirmCancel()
|
||||||
|
{
|
||||||
|
if (cancel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DebugLog(L"ConfirmCancel phase=%d", phase);
|
||||||
|
int result = ShowMessage(MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2,
|
||||||
|
L"Отмена установки",
|
||||||
|
L"Вы уверены, что хотите отменить установку?");
|
||||||
|
if (result == IDYES) {
|
||||||
|
RequestCancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::RequestCancel()
|
||||||
|
{
|
||||||
|
DebugLog(L"RequestCancel phase=%d busy=%d", phase, busy ? 1 : 0);
|
||||||
|
if (cancel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cancel = true;
|
||||||
|
goText = L"Отменяю. Ждите...";
|
||||||
|
goBg = ColorFromHex(0xAEB6C4);
|
||||||
|
goFg = ColorFromHex(0xEDEFF3);
|
||||||
|
if (phase == 2) {
|
||||||
|
if (runJob) {
|
||||||
|
TerminateJobObject(runJob, 1);
|
||||||
|
}
|
||||||
|
if (runProcess) {
|
||||||
|
TerminateProcess(runProcess, 1);
|
||||||
|
}
|
||||||
|
// Fallback for environments where the process could not be assigned
|
||||||
|
// to a job: kill the whole DISM tree.
|
||||||
|
KillProcessTree(runPid);
|
||||||
|
}
|
||||||
|
InvalidateAll();
|
||||||
|
}
|
||||||
+987
@@ -0,0 +1,987 @@
|
|||||||
|
#include "app.h"
|
||||||
|
#include "disks.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <commctrl.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
#include <windowsx.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cwctype>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const wchar_t* kMainClass = L"ParavisInstallerCpp";
|
||||||
|
const wchar_t* kPopupClass = L"ParavisInstallerCppPopup";
|
||||||
|
|
||||||
|
const DWORD CLR_BG = 0xF7F8FA;
|
||||||
|
const DWORD CLR_HEAD = 0x1E2A44;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring TempFile(const std::wstring& name)
|
||||||
|
{
|
||||||
|
return GetTempDir() + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteSelftest()
|
||||||
|
{
|
||||||
|
std::wstring text;
|
||||||
|
wchar_t windowsDir[MAX_PATH] = {};
|
||||||
|
GetWindowsDirectoryW(windowsDir, MAX_PATH);
|
||||||
|
text += std::wstring(L"windows=") + windowsDir + L"\r\n";
|
||||||
|
|
||||||
|
std::wstring ids;
|
||||||
|
for (int disk : GetSystemDiskIds()) {
|
||||||
|
ids += L"|" + std::to_wstring(disk) + L"|";
|
||||||
|
}
|
||||||
|
text += L"system_disks=" + ids + L"\r\n";
|
||||||
|
text += std::wstring(L"admin=") + (IsAdmin() ? L"1" : L"0") + L"\r\n";
|
||||||
|
|
||||||
|
std::wstring wim = JoinPath(GetExeDir(), L"Data.pf");
|
||||||
|
text += L"wim=" + wim + L"\r\n";
|
||||||
|
text += std::wstring(L"wim_exists=") + (FileExists(wim) ? L"1" : L"0") + L"\r\n";
|
||||||
|
|
||||||
|
WriteAnsiFile(TempFile(L"paravis_selftest.txt"), text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteVerify(int disk)
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
std::wstring report = VerifyLayout(disk, ok);
|
||||||
|
std::wstring text = Format(L"disk=%d ok=%s\r\n", disk, ok ? L"True" : L"False");
|
||||||
|
text += report;
|
||||||
|
text += L"\r\n";
|
||||||
|
WriteAnsiFile(TempFile(L"paravis_verify.txt"), text);
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK PopupProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
App* app = reinterpret_cast<App*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||||
|
|
||||||
|
switch (message) {
|
||||||
|
case WM_CREATE: {
|
||||||
|
auto* create = reinterpret_cast<CREATESTRUCTW*>(lParam);
|
||||||
|
SetWindowLongPtrW(hwnd, GWLP_USERDATA,
|
||||||
|
reinterpret_cast<LONG_PTR>(create->lpCreateParams));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case WM_ERASEBKGND:
|
||||||
|
return 1;
|
||||||
|
case WM_PAINT: {
|
||||||
|
PAINTSTRUCT paint{};
|
||||||
|
HDC dc = BeginPaint(hwnd, &paint);
|
||||||
|
if (app) {
|
||||||
|
app->PaintPopup(dc);
|
||||||
|
}
|
||||||
|
EndPaint(hwnd, &paint);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case WM_MOUSEACTIVATE:
|
||||||
|
return MA_NOACTIVATE;
|
||||||
|
case WM_DESTROY:
|
||||||
|
if (app && app->hPopup == hwnd) {
|
||||||
|
app->hPopup = nullptr;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return DefWindowProcW(hwnd, message, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK MainProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
App* app = reinterpret_cast<App*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||||
|
|
||||||
|
switch (message) {
|
||||||
|
case WM_CREATE: {
|
||||||
|
auto* create = reinterpret_cast<CREATESTRUCTW*>(lParam);
|
||||||
|
app = reinterpret_cast<App*>(create->lpCreateParams);
|
||||||
|
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(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) {
|
||||||
|
if (wParam == 1) {
|
||||||
|
app->OnPoll();
|
||||||
|
} else if (wParam == 2) {
|
||||||
|
app->OnAutoRefresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
case WM_DEVICECHANGE:
|
||||||
|
if (app) {
|
||||||
|
app->OnDeviceChange(wParam);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
case WM_CLOSE:
|
||||||
|
DebugLog(L"WM_CLOSE busy=%d", (app && app->busy) ? 1 : 0);
|
||||||
|
if (app && app->busy) {
|
||||||
|
// Closing during installation must not leave diskpart/DISM running
|
||||||
|
// in the background. Treat it like the cancel button.
|
||||||
|
app->ConfirmCancel();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
DestroyWindow(hwnd);
|
||||||
|
return 0;
|
||||||
|
case WM_DESTROY:
|
||||||
|
DebugLog(L"WM_DESTROY");
|
||||||
|
if (app) {
|
||||||
|
if (app->hPopup) {
|
||||||
|
DestroyWindow(app->hPopup);
|
||||||
|
app->hPopup = nullptr;
|
||||||
|
}
|
||||||
|
app->DestroyFonts();
|
||||||
|
}
|
||||||
|
PostQuitMessage(0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return DefWindowProcW(hwnd, message, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool App::Init(HWND window)
|
||||||
|
{
|
||||||
|
hwnd = window;
|
||||||
|
HDC dc = GetDC(hwnd);
|
||||||
|
dpi = static_cast<UINT>(GetDeviceCaps(dc, LOGPIXELSY));
|
||||||
|
ReleaseDC(hwnd, dc);
|
||||||
|
if (dpi == 0) {
|
||||||
|
dpi = 96;
|
||||||
|
}
|
||||||
|
CreateFonts();
|
||||||
|
|
||||||
|
hProgress = CreateWindowExW(0, PROGRESS_CLASSW, nullptr,
|
||||||
|
WS_CHILD | PBS_SMOOTH,
|
||||||
|
14, 139, 348, 5,
|
||||||
|
hwnd, reinterpret_cast<HMENU>(static_cast<INT_PTR>(100)),
|
||||||
|
hInst, nullptr);
|
||||||
|
if (hProgress) {
|
||||||
|
SendMessageW(hProgress, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
|
||||||
|
SendMessageW(hProgress, PBM_SETPOS, 0, 0);
|
||||||
|
ShowWindow(hProgress, SW_HIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
HICON icon = LoadIconW(hInst, MAKEINTRESOURCEW(1));
|
||||||
|
if (icon) {
|
||||||
|
SendMessageW(hwnd, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(icon));
|
||||||
|
SendMessageW(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(icon));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::CreateFonts()
|
||||||
|
{
|
||||||
|
auto create = [this](int points, int weight) -> HFONT {
|
||||||
|
return CreateFontW(-MulDiv(points, static_cast<int>(dpi), 72), 0, 0, 0, weight,
|
||||||
|
FALSE, FALSE, FALSE, DEFAULT_CHARSET,
|
||||||
|
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
|
||||||
|
DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Tahoma");
|
||||||
|
};
|
||||||
|
fontTitle = create(15, FW_BOLD);
|
||||||
|
fontSub = create(9, FW_NORMAL);
|
||||||
|
fontMin = create(10, FW_NORMAL);
|
||||||
|
fontClose = create(9, FW_NORMAL);
|
||||||
|
fontText = create(9, FW_NORMAL);
|
||||||
|
fontBold = create(9, FW_BOLD);
|
||||||
|
fontGo = create(10, FW_BOLD);
|
||||||
|
fontArrow = create(8, FW_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::DestroyFonts()
|
||||||
|
{
|
||||||
|
for (HFONT* font : { &fontTitle, &fontSub, &fontMin, &fontClose,
|
||||||
|
&fontText, &fontBold, &fontGo, &fontArrow }) {
|
||||||
|
if (*font) {
|
||||||
|
DeleteObject(*font);
|
||||||
|
*font = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::ScanDisks()
|
||||||
|
{
|
||||||
|
std::vector<DiskItem> found = ScanUsbDisks();
|
||||||
|
std::wstring newSig;
|
||||||
|
for (const auto& item : found) {
|
||||||
|
newSig += std::to_wstring(item.number) + L":" + std::to_wstring(item.size) + L"|";
|
||||||
|
}
|
||||||
|
if (newSig == sig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sig = newSig;
|
||||||
|
|
||||||
|
int currentNumber = -1;
|
||||||
|
if (!disks.empty() && selIdx >= 0 && selIdx < static_cast<int>(disks.size())) {
|
||||||
|
currentNumber = disks[selIdx].number;
|
||||||
|
}
|
||||||
|
|
||||||
|
disks = std::move(found);
|
||||||
|
|
||||||
|
if (disks.empty()) {
|
||||||
|
labelText = L"Подходящие накопители отсутствуют!";
|
||||||
|
statusText = L"Найдено: 0";
|
||||||
|
selIdx = 0;
|
||||||
|
installed = false;
|
||||||
|
ArrowState();
|
||||||
|
RefreshGo();
|
||||||
|
InvalidateAll();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int foundIndex = 0;
|
||||||
|
for (int i = 0; i < static_cast<int>(disks.size()); ++i) {
|
||||||
|
if (disks[i].number == currentNumber) {
|
||||||
|
foundIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selIdx = foundIndex;
|
||||||
|
ShowSelected();
|
||||||
|
RefreshGo();
|
||||||
|
labelText = L"Выбран накопитель:";
|
||||||
|
statusText = Format(L"Найдено: %d", static_cast<int>(disks.size()));
|
||||||
|
ArrowState();
|
||||||
|
InvalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::ShowSelected()
|
||||||
|
{
|
||||||
|
if (disks.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selIdx < 0 || selIdx >= static_cast<int>(disks.size())) {
|
||||||
|
selIdx = 0;
|
||||||
|
}
|
||||||
|
CheckInstalled();
|
||||||
|
InvalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::CheckInstalled()
|
||||||
|
{
|
||||||
|
installed = false;
|
||||||
|
if (!disks.empty() && selIdx >= 0 && selIdx < static_cast<int>(disks.size())) {
|
||||||
|
installed = DiskIsInstalled(disks[selIdx].number);
|
||||||
|
}
|
||||||
|
GoCaption();
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::GoCaption()
|
||||||
|
{
|
||||||
|
if (busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
goText = installed ? L"Переустановить" : L"УСТАНОВИТЬ";
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::GoState(bool on)
|
||||||
|
{
|
||||||
|
goBg = on ? ColorFromHex(0x2563EB) : ColorFromHex(0xAEB6C4);
|
||||||
|
goFg = on ? RGB(255, 255, 255) : ColorFromHex(0xEDEFF3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::ArrowState()
|
||||||
|
{
|
||||||
|
arrowFg = disks.empty() ? ColorFromHex(0xC7CBD3) : ColorFromHex(0x6B7280);
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::RefreshGo()
|
||||||
|
{
|
||||||
|
canInstall = false;
|
||||||
|
if (!disks.empty() && selIdx >= 0 && selIdx < static_cast<int>(disks.size())) {
|
||||||
|
canInstall = NominalGb(disks[selIdx].size) >= 32.0;
|
||||||
|
}
|
||||||
|
GoCaption();
|
||||||
|
GoState(canInstall);
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::DimControls(bool on)
|
||||||
|
{
|
||||||
|
dimmed = on;
|
||||||
|
if (on) {
|
||||||
|
dropBg = nameBg = sizeBg = arrowBg = ColorFromHex(0xF2F3F5);
|
||||||
|
nameFg = ColorFromHex(0xA0A6B1);
|
||||||
|
sizeFg = ColorFromHex(0xB4B9C2);
|
||||||
|
arrowFg = ColorFromHex(0xC7CBD3);
|
||||||
|
comboFrame = ColorFromHex(0xE3E6EB);
|
||||||
|
ClosePopup();
|
||||||
|
} else {
|
||||||
|
dropBg = nameBg = sizeBg = arrowBg = RGB(255, 255, 255);
|
||||||
|
nameFg = ColorFromHex(0x1F2430);
|
||||||
|
sizeFg = ColorFromHex(0x6B7280);
|
||||||
|
ArrowState();
|
||||||
|
comboFrame = ColorFromHex(0xD5DAE3);
|
||||||
|
}
|
||||||
|
InvalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::BusySet(bool value)
|
||||||
|
{
|
||||||
|
DebugLog(L"BusySet %d", value ? 1 : 0);
|
||||||
|
busy = value;
|
||||||
|
if (value) {
|
||||||
|
cancel = false;
|
||||||
|
phase = 0;
|
||||||
|
applied = 0;
|
||||||
|
DimControls(true);
|
||||||
|
goText = L"ОТМЕНИТЬ";
|
||||||
|
goBg = ColorFromHex(0xC0392B);
|
||||||
|
goFg = RGB(255, 255, 255);
|
||||||
|
} else {
|
||||||
|
cancel = false;
|
||||||
|
phase = 0;
|
||||||
|
runJob = nullptr;
|
||||||
|
runProcess = nullptr;
|
||||||
|
runPid = 0;
|
||||||
|
CheckInstalled();
|
||||||
|
DimControls(false);
|
||||||
|
RefreshGo();
|
||||||
|
}
|
||||||
|
InvalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::CancelLook(Hit hit, bool leftDown)
|
||||||
|
{
|
||||||
|
COLORREF background;
|
||||||
|
COLORREF foreground;
|
||||||
|
if (cancel) {
|
||||||
|
background = ColorFromHex(0xAEB6C4);
|
||||||
|
foreground = ColorFromHex(0xEDEFF3);
|
||||||
|
} else {
|
||||||
|
background = ColorFromHex(0xC0392B);
|
||||||
|
foreground = RGB(255, 255, 255);
|
||||||
|
if (hit == Hit::Go) {
|
||||||
|
background = leftDown ? ColorFromHex(0xA93226) : ColorFromHex(0xD6453C);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goBg = background;
|
||||||
|
goFg = foreground;
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::SetWork(const std::wstring& text)
|
||||||
|
{
|
||||||
|
workText = text;
|
||||||
|
InvalidateRect(hwnd, nullptr, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::ShowProgress(int value)
|
||||||
|
{
|
||||||
|
if (!hProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
value = std::clamp(value, 0, 100);
|
||||||
|
if (!IsWindowVisible(hProgress)) {
|
||||||
|
ShowWindow(hProgress, SW_SHOW);
|
||||||
|
InvalidateRect(hwnd, nullptr, FALSE);
|
||||||
|
}
|
||||||
|
SendMessageW(hProgress, PBM_SETPOS, static_cast<WPARAM>(value), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::HideProgress()
|
||||||
|
{
|
||||||
|
if (hProgress && IsWindowVisible(hProgress)) {
|
||||||
|
ShowWindow(hProgress, SW_HIDE);
|
||||||
|
InvalidateRect(hwnd, nullptr, FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::InvalidateAll()
|
||||||
|
{
|
||||||
|
InvalidateRect(hwnd, nullptr, FALSE);
|
||||||
|
if (hPopup) {
|
||||||
|
InvalidateRect(hPopup, nullptr, FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::InvalidatePopup()
|
||||||
|
{
|
||||||
|
if (hPopup) {
|
||||||
|
InvalidateRect(hPopup, nullptr, FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, w, h, ColorFromHex(CLR_BG));
|
||||||
|
FillRectColor(memory, 0, 0, 376, 42, 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, 26, 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);
|
||||||
|
|
||||||
|
FillRectColor(memory, 8, 45, 360, 46, RGB(255, 255, 255));
|
||||||
|
DrawTextRect(memory, labelText, fontText, 16, 45, 340, 15,
|
||||||
|
ColorFromHex(0x6B7280), DT_LEFT | DT_TOP);
|
||||||
|
|
||||||
|
FillRectColor(memory, 15, 61, 342, 28, comboFrame);
|
||||||
|
FillRectColor(memory, 16, 62, 340, 26, dropBg);
|
||||||
|
FillRectColor(memory, 21, 62, 240, 26, nameBg);
|
||||||
|
FillRectColor(memory, 262, 62, 74, 26, sizeBg);
|
||||||
|
FillRectColor(memory, 336, 62, 16, 26, arrowBg);
|
||||||
|
|
||||||
|
std::wstring diskName;
|
||||||
|
std::wstring diskCap;
|
||||||
|
if (!disks.empty() && selIdx >= 0 && selIdx < static_cast<int>(disks.size())) {
|
||||||
|
diskName = disks[selIdx].name;
|
||||||
|
diskCap = disks[selIdx].cap;
|
||||||
|
}
|
||||||
|
DrawTextRect(memory, diskName, fontText, 21, 62, 240, 26, nameFg,
|
||||||
|
DT_LEFT | DT_VCENTER);
|
||||||
|
DrawTextRect(memory, diskCap, fontText, 262, 62, 74, 26, sizeFg,
|
||||||
|
DT_RIGHT | DT_VCENTER);
|
||||||
|
DrawTextRect(memory, L"▼", fontArrow, 336, 62, 16, 26, arrowFg,
|
||||||
|
DT_CENTER | DT_VCENTER);
|
||||||
|
|
||||||
|
DrawTextRect(memory, statusText, fontText, 14, 95, 348, 15,
|
||||||
|
ColorFromHex(0x6B7280), DT_LEFT | DT_TOP);
|
||||||
|
|
||||||
|
if (busy) {
|
||||||
|
DrawTextRect(memory, workText, fontBold, 14, 115, 348, 18,
|
||||||
|
ColorFromHex(0x1F2430), DT_CENTER | DT_TOP);
|
||||||
|
} else {
|
||||||
|
DrawTextRect(memory, L"Все данные на выбранном диске будут уничтожены",
|
||||||
|
fontBold, 14, 115, 348, 18, ColorFromHex(0xC0392B),
|
||||||
|
DT_CENTER | DT_TOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
FillRectColor(memory, 14, 147, 348, 30, goBg);
|
||||||
|
DrawTextRect(memory, goText, fontGo, 14, 147, 348, 30, goFg,
|
||||||
|
DT_CENTER | DT_VCENTER);
|
||||||
|
|
||||||
|
BitBlt(dc, 0, 0, w, h, memory, 0, 0, SRCCOPY);
|
||||||
|
SelectObject(memory, oldBitmap);
|
||||||
|
DeleteObject(bitmap);
|
||||||
|
DeleteDC(memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::PaintPopup(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, w, h, ColorFromHex(0xD5DAE3));
|
||||||
|
|
||||||
|
int count = static_cast<int>(disks.size());
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
int y = 1 + i * 24;
|
||||||
|
COLORREF background = (i == popupHover) ? ColorFromHex(0xEEF3FF) : RGB(255, 255, 255);
|
||||||
|
FillRectColor(memory, 1, y, 340, 24, background);
|
||||||
|
DrawTextRect(memory, L" " + disks[i].name, fontText, 1, y, 248, 24,
|
||||||
|
ColorFromHex(0x1F2430), DT_LEFT | DT_VCENTER);
|
||||||
|
DrawTextRect(memory, disks[i].cap + L" ", fontText, 249, y, 92, 24,
|
||||||
|
ColorFromHex(0x6B7280), DT_RIGHT | DT_VCENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
BitBlt(dc, 0, 0, w, h, memory, 0, 0, SRCCOPY);
|
||||||
|
SelectObject(memory, oldBitmap);
|
||||||
|
DeleteObject(bitmap);
|
||||||
|
DeleteDC(memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT App::PopupScreenRect() const
|
||||||
|
{
|
||||||
|
RECT rect{};
|
||||||
|
if (hPopup) {
|
||||||
|
GetWindowRect(hPopup, &rect);
|
||||||
|
}
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::OpenPopup()
|
||||||
|
{
|
||||||
|
if (hPopup || disks.empty() || busy || modal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int count = static_cast<int>(disks.size());
|
||||||
|
RECT mainRect{};
|
||||||
|
GetWindowRect(hwnd, &mainRect);
|
||||||
|
int popupWidth = 342;
|
||||||
|
int popupHeight = count * 24 + 2;
|
||||||
|
hPopup = CreateWindowExW(WS_EX_TOOLWINDOW, kPopupClass, L"", WS_POPUP,
|
||||||
|
mainRect.left + 15, mainRect.top + 89,
|
||||||
|
popupWidth, popupHeight,
|
||||||
|
hwnd, nullptr, hInst, this);
|
||||||
|
if (!hPopup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
popupHover = -1;
|
||||||
|
popupMouseDown = false;
|
||||||
|
dropGuard = GetTickCount64();
|
||||||
|
ShowWindow(hPopup, SW_SHOWNOACTIVATE);
|
||||||
|
UpdateWindow(hPopup);
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::ClosePopup()
|
||||||
|
{
|
||||||
|
if (hPopup) {
|
||||||
|
HWND popup = hPopup;
|
||||||
|
hPopup = nullptr;
|
||||||
|
DestroyWindow(popup);
|
||||||
|
}
|
||||||
|
popupHover = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::TogglePopup()
|
||||||
|
{
|
||||||
|
if (hPopup) {
|
||||||
|
ClosePopup();
|
||||||
|
} else {
|
||||||
|
OpenPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::HandlePopup(POINT screen, bool leftDown)
|
||||||
|
{
|
||||||
|
if (!hPopup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
POINT point = screen;
|
||||||
|
ScreenToClient(hPopup, &point);
|
||||||
|
RECT client{};
|
||||||
|
GetClientRect(hPopup, &client);
|
||||||
|
bool inside = PtInRect(&client, point) != FALSE;
|
||||||
|
|
||||||
|
int hover = -1;
|
||||||
|
int count = static_cast<int>(disks.size());
|
||||||
|
if (inside && point.x >= 1 && point.x < 341 && point.y >= 1) {
|
||||||
|
int index = (point.y - 1) / 24;
|
||||||
|
if (index >= 0 && index < count && point.y < 1 + count * 24) {
|
||||||
|
hover = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hover != popupHover) {
|
||||||
|
popupHover = hover;
|
||||||
|
InvalidatePopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftDown && GetTickCount64() - dropGuard > 400) {
|
||||||
|
if (hover >= 0) {
|
||||||
|
selIdx = hover;
|
||||||
|
ShowSelected();
|
||||||
|
RefreshGo();
|
||||||
|
ClosePopup();
|
||||||
|
} else if (!inside) {
|
||||||
|
ClosePopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool App::GoPoll(Hit hit, bool leftDown)
|
||||||
|
{
|
||||||
|
if (hit == Hit::Go && leftDown) {
|
||||||
|
goPollDown = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (goPollDown) {
|
||||||
|
goPollDown = false;
|
||||||
|
if (hit == Hit::Go && !leftDown) {
|
||||||
|
if (GetTickCount64() - clickGuard < 400) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::OnPoll()
|
||||||
|
{
|
||||||
|
if (modal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
POINT screen{};
|
||||||
|
GetCursorPos(&screen);
|
||||||
|
bool leftDown = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
|
||||||
|
|
||||||
|
bool overPopup = false;
|
||||||
|
if (hPopup) {
|
||||||
|
RECT popupRect = PopupScreenRect();
|
||||||
|
overPopup = PtInRect(&popupRect, screen) != FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
POINT client = screen;
|
||||||
|
ScreenToClient(hwnd, &client);
|
||||||
|
Hit hit = overPopup ? Hit::None : HitTest(client);
|
||||||
|
|
||||||
|
if (busy) {
|
||||||
|
if (!cancel) {
|
||||||
|
CancelLook(hit, leftDown);
|
||||||
|
if (GoPoll(hit, leftDown)) {
|
||||||
|
ConfirmCancel();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CancelLook(hit, leftDown);
|
||||||
|
}
|
||||||
|
if (hPopup) {
|
||||||
|
ClosePopup();
|
||||||
|
}
|
||||||
|
InvalidateAll();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GoPoll(hit, leftDown)) {
|
||||||
|
DebugLog(L"GO click -> Apply");
|
||||||
|
Apply();
|
||||||
|
InvalidateAll();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
COLORREF minColor = ColorFromHex(CLR_HEAD);
|
||||||
|
COLORREF closeColor = ColorFromHex(CLR_HEAD);
|
||||||
|
if (hit == Hit::Min) {
|
||||||
|
minColor = leftDown ? ColorFromHex(0x3A4A6B) : ColorFromHex(0x2C3A57);
|
||||||
|
}
|
||||||
|
if (hit == Hit::Close) {
|
||||||
|
closeColor = leftDown ? ColorFromHex(0xC0392B) : ColorFromHex(0xE04B3F);
|
||||||
|
}
|
||||||
|
minBg = minColor;
|
||||||
|
closeBg = closeColor;
|
||||||
|
|
||||||
|
if (hit == Hit::Min && leftDown) {
|
||||||
|
minDown = true;
|
||||||
|
} else if (minDown) {
|
||||||
|
minDown = false;
|
||||||
|
if (hit == Hit::Min && !leftDown) {
|
||||||
|
ShowWindow(hwnd, SW_MINIMIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hit == Hit::Close && leftDown) {
|
||||||
|
closeDown = true;
|
||||||
|
} else if (closeDown) {
|
||||||
|
closeDown = false;
|
||||||
|
if (hit == Hit::Close && !leftDown) {
|
||||||
|
DebugLog(L"close button click");
|
||||||
|
DestroyWindow(hwnd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((hit == Hit::Header || hit == Hit::Title || hit == Hit::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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
COLORREF background;
|
||||||
|
if (!canInstall) {
|
||||||
|
background = ColorFromHex(0xAEB6C4);
|
||||||
|
} else if (hit == Hit::Go && leftDown) {
|
||||||
|
background = ColorFromHex(0x1B4FD8);
|
||||||
|
} else if (hit == Hit::Go) {
|
||||||
|
background = ColorFromHex(0x3B82F6);
|
||||||
|
} else {
|
||||||
|
background = ColorFromHex(0x2563EB);
|
||||||
|
}
|
||||||
|
goBg = background;
|
||||||
|
goFg = canInstall ? RGB(255, 255, 255) : ColorFromHex(0xEDEFF3);
|
||||||
|
|
||||||
|
bool comboHover = !disks.empty() &&
|
||||||
|
(hit == Hit::Combo || hit == Hit::Drop || hit == Hit::DropName ||
|
||||||
|
hit == Hit::DropSize || hit == Hit::Arrow);
|
||||||
|
comboFrame = comboHover ? ColorFromHex(0x7FA6F0) : ColorFromHex(0xD5DAE3);
|
||||||
|
|
||||||
|
if (!disks.empty() &&
|
||||||
|
(hit == Hit::Drop || hit == Hit::Arrow || hit == Hit::DropName ||
|
||||||
|
hit == Hit::DropSize) && leftDown && !dropDownGui) {
|
||||||
|
TogglePopup();
|
||||||
|
dropDownGui = true;
|
||||||
|
}
|
||||||
|
if (!leftDown) {
|
||||||
|
dropDownGui = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hPopup) {
|
||||||
|
HandlePopup(screen, leftDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
InvalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::OnAutoRefresh()
|
||||||
|
{
|
||||||
|
if (!needScan) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (busy || modal || hPopup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (GetTickCount64() - devTick < 500) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
needScan = false;
|
||||||
|
ScanDisks();
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::OnDeviceChange(WPARAM wParam)
|
||||||
|
{
|
||||||
|
if (wParam == 0x8000 || wParam == 0x8004 || wParam == 0x0007) {
|
||||||
|
if (!needScan) {
|
||||||
|
needScan = true;
|
||||||
|
devTick = GetTickCount64();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::PumpMessages()
|
||||||
|
{
|
||||||
|
MSG message{};
|
||||||
|
while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) {
|
||||||
|
if (message.message == WM_QUIT) {
|
||||||
|
PostQuitMessage(static_cast<int>(message.wParam));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TranslateMessage(&message);
|
||||||
|
DispatchMessageW(&message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::SleepPump(int milliseconds)
|
||||||
|
{
|
||||||
|
ULONGLONG end = GetTickCount64() + static_cast<ULONGLONG>(milliseconds);
|
||||||
|
while (GetTickCount64() < end) {
|
||||||
|
ULONGLONG remaining = end - GetTickCount64();
|
||||||
|
DWORD wait = static_cast<DWORD>(std::min<ULONGLONG>(remaining, 50));
|
||||||
|
MsgWaitForMultipleObjects(0, nullptr, FALSE, wait, QS_ALLINPUT);
|
||||||
|
PumpMessages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool App::WaitForProcess(HANDLE process)
|
||||||
|
{
|
||||||
|
for (;;) {
|
||||||
|
DWORD result = MsgWaitForMultipleObjects(1, &process, FALSE, 30, QS_ALLINPUT);
|
||||||
|
if (result == WAIT_OBJECT_0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Always pump: PeekMessage is what materializes due WM_TIMER messages,
|
||||||
|
// so this keeps the cancel button responsive during diskpart.
|
||||||
|
PumpMessages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Hit App::HitTest(POINT pt) const
|
||||||
|
{
|
||||||
|
auto inside = [&pt](int x, int y, int w, int h) {
|
||||||
|
return pt.x >= x && pt.y >= y && pt.x < x + w && pt.y < y + h;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (inside(306, 2, 30, 18)) {
|
||||||
|
return Hit::Min;
|
||||||
|
}
|
||||||
|
if (inside(338, 2, 30, 18)) {
|
||||||
|
return Hit::Close;
|
||||||
|
}
|
||||||
|
if (inside(336, 62, 16, 26)) {
|
||||||
|
return Hit::Arrow;
|
||||||
|
}
|
||||||
|
if (inside(21, 62, 240, 26)) {
|
||||||
|
return Hit::DropName;
|
||||||
|
}
|
||||||
|
if (inside(262, 62, 74, 26)) {
|
||||||
|
return Hit::DropSize;
|
||||||
|
}
|
||||||
|
if (inside(16, 62, 340, 26)) {
|
||||||
|
return Hit::Drop;
|
||||||
|
}
|
||||||
|
if (inside(15, 61, 342, 28)) {
|
||||||
|
return Hit::Combo;
|
||||||
|
}
|
||||||
|
if (inside(14, 147, 348, 30)) {
|
||||||
|
return Hit::Go;
|
||||||
|
}
|
||||||
|
if (inside(16, 2, 300, 24)) {
|
||||||
|
return Hit::Title;
|
||||||
|
}
|
||||||
|
if (inside(18, 26, 300, 14)) {
|
||||||
|
return Hit::Sub;
|
||||||
|
}
|
||||||
|
if (inside(0, 0, 376, 42)) {
|
||||||
|
return Hit::Header;
|
||||||
|
}
|
||||||
|
if (inside(8, 45, 360, 46)) {
|
||||||
|
return Hit::Card;
|
||||||
|
}
|
||||||
|
return Hit::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int)
|
||||||
|
{
|
||||||
|
int argc = 0;
|
||||||
|
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||||
|
std::vector<std::wstring> arguments;
|
||||||
|
if (argv) {
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
arguments.emplace_back(argv[i]);
|
||||||
|
}
|
||||||
|
LocalFree(argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugLog(L"=== start pid=%lu args=%zu", GetCurrentProcessId(), arguments.size());
|
||||||
|
App app;
|
||||||
|
app.hInst = hInstance;
|
||||||
|
app.runStamp = GetRunStamp();
|
||||||
|
|
||||||
|
for (const std::wstring& argument : arguments) {
|
||||||
|
std::wstring lower = argument;
|
||||||
|
for (wchar_t& ch : lower) {
|
||||||
|
ch = static_cast<wchar_t>(towlower(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower == L"/selftest") {
|
||||||
|
WriteSelftest();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (lower.rfind(L"/verify=", 0) == 0) {
|
||||||
|
WriteVerify(_wtoi(argument.c_str() + 8));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (lower.rfind(L"/testdisk=", 0) == 0) {
|
||||||
|
app.testDisk = _wtoi(argument.c_str() + 10);
|
||||||
|
}
|
||||||
|
if (lower.rfind(L"/wim=", 0) == 0) {
|
||||||
|
app.wimPath = argument.substr(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
INITCOMMONCONTROLSEX commonControls{};
|
||||||
|
commonControls.dwSize = sizeof(commonControls);
|
||||||
|
commonControls.dwICC = ICC_PROGRESS_CLASS | ICC_STANDARD_CLASSES;
|
||||||
|
InitCommonControlsEx(&commonControls);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
WNDCLASSEXW popupClass = windowClass;
|
||||||
|
popupClass.lpfnWndProc = PopupProc;
|
||||||
|
popupClass.lpszClassName = kPopupClass;
|
||||||
|
popupClass.hIcon = nullptr;
|
||||||
|
popupClass.hIconSm = nullptr;
|
||||||
|
if (!RegisterClassExW(&popupClass)) {
|
||||||
|
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, L"Paravis installer",
|
||||||
|
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);
|
||||||
|
SetTimer(app.hwnd, 2, 250, nullptr);
|
||||||
|
|
||||||
|
app.ScanDisks();
|
||||||
|
|
||||||
|
MSG message{};
|
||||||
|
while (GetMessageW(&message, nullptr, 0, 0) > 0) {
|
||||||
|
TranslateMessage(&message);
|
||||||
|
DispatchMessageW(&message);
|
||||||
|
}
|
||||||
|
DebugLog(L"message loop ended wParam=%llu", static_cast<unsigned long long>(message.wParam));
|
||||||
|
return static_cast<int>(message.wParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
+226
@@ -0,0 +1,226 @@
|
|||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
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<size_t>(needed), L'\0');
|
||||||
|
if (needed > 0) {
|
||||||
|
_vsnwprintf_s(result.data(), result.size() + 1, _TRUNCATE, fmt, args);
|
||||||
|
}
|
||||||
|
va_end(args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 JoinPath(windows, L"Temp") + L"\\";
|
||||||
|
}
|
||||||
|
std::wstring path(buffer, length);
|
||||||
|
if (path.empty() || path.back() != L'\\') {
|
||||||
|
path.push_back(L'\\');
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring GetExeDir()
|
||||||
|
{
|
||||||
|
wchar_t buffer[MAX_PATH * 4] = {};
|
||||||
|
DWORD length = GetModuleFileNameW(nullptr, buffer, static_cast<DWORD>(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 GetRunStamp()
|
||||||
|
{
|
||||||
|
SYSTEMTIME st{};
|
||||||
|
GetLocalTime(&st);
|
||||||
|
return Format(L"%04d%02d%02d_%02d%02d%02d",
|
||||||
|
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring JoinPath(const std::wstring& dir, const std::wstring& name)
|
||||||
|
{
|
||||||
|
if (dir.empty()) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
if (dir.back() == L'\\' || dir.back() == L'/') {
|
||||||
|
return dir + name;
|
||||||
|
}
|
||||||
|
return dir + L"\\" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileExists(const std::wstring& path)
|
||||||
|
{
|
||||||
|
DWORD attributes = GetFileAttributesW(path.c_str());
|
||||||
|
return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsAdmin()
|
||||||
|
{
|
||||||
|
HANDLE token = nullptr;
|
||||||
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TOKEN_ELEVATION elevation{};
|
||||||
|
DWORD returned = 0;
|
||||||
|
bool elevated = false;
|
||||||
|
if (GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &returned)) {
|
||||||
|
elevated = elevation.TokenIsElevated != 0;
|
||||||
|
}
|
||||||
|
CloseHandle(token);
|
||||||
|
return elevated;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string WideToAnsi(const std::wstring& text, UINT codepage)
|
||||||
|
{
|
||||||
|
if (text.empty()) {
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
int needed = WideCharToMultiByte(codepage, 0, text.c_str(), static_cast<int>(text.size()),
|
||||||
|
nullptr, 0, nullptr, nullptr);
|
||||||
|
if (needed <= 0) {
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
std::string result(static_cast<size_t>(needed), '\0');
|
||||||
|
WideCharToMultiByte(codepage, 0, text.c_str(), static_cast<int>(text.size()),
|
||||||
|
result.data(), needed, nullptr, nullptr);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring AnsiToWide(const std::string& text, UINT codepage)
|
||||||
|
{
|
||||||
|
if (text.empty()) {
|
||||||
|
return std::wstring();
|
||||||
|
}
|
||||||
|
int needed = MultiByteToWideChar(codepage, 0, text.data(), static_cast<int>(text.size()),
|
||||||
|
nullptr, 0);
|
||||||
|
if (needed <= 0) {
|
||||||
|
return std::wstring();
|
||||||
|
}
|
||||||
|
std::wstring result(static_cast<size_t>(needed), L'\0');
|
||||||
|
MultiByteToWideChar(codepage, 0, text.data(), static_cast<int>(text.size()),
|
||||||
|
result.data(), needed);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteAnsiFile(const std::wstring& path, const std::wstring& text)
|
||||||
|
{
|
||||||
|
std::string bytes = WideToAnsi(text, CP_ACP);
|
||||||
|
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 = true;
|
||||||
|
if (!bytes.empty()) {
|
||||||
|
ok = WriteFile(file, bytes.data(), static_cast<DWORD>(bytes.size()), &written, nullptr) != FALSE
|
||||||
|
&& written == bytes.size();
|
||||||
|
}
|
||||||
|
CloseHandle(file);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ReadFileBytes(const std::wstring& path)
|
||||||
|
{
|
||||||
|
HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||||
|
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
|
if (file == INVALID_HANDLE_VALUE) {
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
LARGE_INTEGER size{};
|
||||||
|
if (!GetFileSizeEx(file, &size) || size.QuadPart <= 0) {
|
||||||
|
CloseHandle(file);
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
if (size.QuadPart > 32 * 1024 * 1024) {
|
||||||
|
size.QuadPart = 32 * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string data(static_cast<size_t>(size.QuadPart), '\0');
|
||||||
|
DWORD read = 0;
|
||||||
|
bool ok = ReadFile(file, data.data(), static_cast<DWORD>(data.size()), &read, nullptr) != FALSE;
|
||||||
|
CloseHandle(file);
|
||||||
|
if (!ok) {
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
data.resize(read);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring GetComSpec()
|
||||||
|
{
|
||||||
|
wchar_t buffer[MAX_PATH * 2] = {};
|
||||||
|
DWORD length = GetEnvironmentVariableW(L"ComSpec", buffer, static_cast<DWORD>(std::size(buffer)));
|
||||||
|
if (length > 0 && length < std::size(buffer)) {
|
||||||
|
return std::wstring(buffer, length);
|
||||||
|
}
|
||||||
|
return L"C:\\Windows\\System32\\cmd.exe";
|
||||||
|
}
|
||||||
|
|
||||||
|
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<DWORD>(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);
|
||||||
|
|
||||||
|
std::string utf8 = WideToAnsi(line, CP_UTF8);
|
||||||
|
std::wstring path = GetTempDir() + L"paravis_cpp_debug.log";
|
||||||
|
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, utf8.data(), static_cast<DWORD>(utf8.size()), &written, nullptr);
|
||||||
|
CloseHandle(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
std::wstring Format(const wchar_t* fmt, ...);
|
||||||
|
std::wstring GetTempDir();
|
||||||
|
std::wstring GetExeDir();
|
||||||
|
std::wstring GetRunStamp();
|
||||||
|
std::wstring JoinPath(const std::wstring& dir, const std::wstring& name);
|
||||||
|
bool FileExists(const std::wstring& path);
|
||||||
|
bool IsAdmin();
|
||||||
|
std::string WideToAnsi(const std::wstring& text, UINT codepage = CP_ACP);
|
||||||
|
std::wstring AnsiToWide(const std::string& text, UINT codepage = CP_ACP);
|
||||||
|
bool WriteAnsiFile(const std::wstring& path, const std::wstring& text);
|
||||||
|
std::string ReadFileBytes(const std::wstring& path);
|
||||||
|
std::wstring GetComSpec();
|
||||||
|
COLORREF ColorFromHex(DWORD rgb);
|
||||||
|
void DebugLog(const wchar_t* fmt, ...);
|
||||||
Reference in new issue
Block a user