Paravis USB installer 2.0 (C++ port)
This commit is contained in:
commit
ce8b49d0ec
18 files changed
+4119
No files matched your search
+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;
|
||||
}
|
||||
Reference in new issue
Block a user