#include "app.h" #include "disks.h" #include "util.h" #include #include #include #include #include #include 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 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 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 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(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(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(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 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(sizeBytes) / 1073741824.0, static_cast(expectedSize) / 1073741824.0)); return; } } unsigned long long totalMB = sizeBytes / MB; long long p3 = static_cast(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(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(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 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(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(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(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(); }