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