777 lines
27 KiB
C++
777 lines
27 KiB
C++
#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;
|
|
}
|
|
|
|
if (esdPfAvailable && !IsWimFile(esdPfPath)) {
|
|
ShowMessage(MB_ICONERROR | MB_OK, L"WindowsESD.pf повреждён",
|
|
L"Файл WindowsESD.pf не является WIM-архивом:\r\n" + esdPfPath);
|
|
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";
|
|
if (esdPfAvailable) {
|
|
if (noEsd) {
|
|
message += L"WindowsESD.pf распаковываться не будет.\r\n\r\n";
|
|
} else {
|
|
message += L"После Data.pf будет распакован WindowsESD.pf.\r\n\r\n";
|
|
}
|
|
} else {
|
|
message += L"WindowsESD.pf не найден - Windows ESD не будет распакован.\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) {
|
|
if (!noEsd && esdPfAvailable) {
|
|
int esdResult = ApplyWindowsEsd(disk);
|
|
if (esdResult == 3) {
|
|
SetWork(L"Отменено");
|
|
HideProgress();
|
|
BusySet(false);
|
|
HideProgress();
|
|
ShowMessage(MB_ICONINFORMATION | MB_OK, L"Отменено",
|
|
L"Операция отменена пользователем.\r\n\r\n"
|
|
L"Диск размечен, Windows ESD не распакован.");
|
|
return;
|
|
}
|
|
if (esdResult == 2) {
|
|
SetWork(L"Ошибка Windows ESD");
|
|
HideProgress();
|
|
ShowMessage(MB_ICONERROR | MB_OK, L"Ошибка Windows ESD",
|
|
L"Не удалось распаковать WindowsESD.pf.\r\n\r\n"
|
|
L"Проверьте, что файл не повреждён.");
|
|
BusySet(false);
|
|
HideProgress();
|
|
return;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int App::ApplyWindowsEsd(int disk)
|
|
{
|
|
std::wstring dir;
|
|
for (int attempt = 0; attempt < 15; ++attempt) {
|
|
dir = GetPartitionLetter(disk, 2);
|
|
if (!dir.empty()) {
|
|
break;
|
|
}
|
|
SleepPump(1000);
|
|
}
|
|
|
|
if (dir.empty()) {
|
|
ShowMessage(MB_ICONERROR | MB_OK, L"Ошибка",
|
|
L"Не удалось определить букву раздела DATA.");
|
|
return 2;
|
|
}
|
|
if (cancel) {
|
|
return 3;
|
|
}
|
|
|
|
if (!IsWimFile(esdPfPath)) {
|
|
ShowMessage(MB_ICONERROR | MB_OK, L"WindowsESD.pf повреждён",
|
|
L"Файл WindowsESD.pf не является WIM-архивом:\r\n" + esdPfPath);
|
|
return 2;
|
|
}
|
|
|
|
std::wstring caption = L"Распаковка Windows ESD";
|
|
DebugLog(L"ApplyWindowsEsd %s -> %s", esdPfPath.c_str(), dir.c_str());
|
|
phase = 2;
|
|
ShowProgress(0);
|
|
SetWork(caption);
|
|
|
|
bool ok = DismApply(esdPfPath, 1, dir, caption);
|
|
phase = 0;
|
|
if (!ok) {
|
|
if (cancel) {
|
|
return 3;
|
|
}
|
|
return 2;
|
|
}
|
|
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();
|
|
}
|