mirror of
https://gitea.tendokyu.moe/moegrid/sega-mount.git
synced 2026-09-29 02:07:53 +03:00
218 lines
9.2 KiB
C++
218 lines
9.2 KiB
C++
#include "vhd_mount.h"
|
||
#include "utils/strings.h"
|
||
#include <initguid.h>
|
||
#include <virtdisk.h>
|
||
#include <string>
|
||
#include <vector>
|
||
#include <format>
|
||
|
||
namespace vhd_mount {
|
||
// ===== 内部 helper =====
|
||
|
||
// 打开VHD句柄,失败返回INVALID_HANDLE_VALUE并填充error
|
||
static HANDLE open_vhd(const std::wstring &vhd_path, VIRTUAL_DISK_ACCESS_MASK access_mask, DWORD rw_depth,
|
||
std::string *error) {
|
||
VIRTUAL_STORAGE_TYPE vst = {};
|
||
vst.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_VHD;
|
||
vst.VendorId = VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT;
|
||
|
||
OPEN_VIRTUAL_DISK_PARAMETERS open_params = {};
|
||
open_params.Version = OPEN_VIRTUAL_DISK_VERSION_1;
|
||
open_params.Version1.RWDepth = rw_depth;
|
||
|
||
HANDLE hVhd = nullptr;
|
||
DWORD err = OpenVirtualDisk(&vst, vhd_path.c_str(),
|
||
access_mask,
|
||
OPEN_VIRTUAL_DISK_FLAG_NONE,
|
||
&open_params, &hVhd);
|
||
if (err != ERROR_SUCCESS) {
|
||
if (error)
|
||
*error = std::format("OpenVirtualDisk 失败:{} path={}", err, utils::wstr_to_str(vhd_path));
|
||
return INVALID_HANDLE_VALUE;
|
||
}
|
||
return hVhd;
|
||
}
|
||
|
||
// 通过任意卷设备路径(\\.\X: 或 \\?\Volume{GUID})获取卷所在磁盘号
|
||
static bool device_get_disk_number(const std::wstring &device_path, DWORD &disk_number) {
|
||
HANDLE hVol = CreateFileW(device_path.c_str(),
|
||
GENERIC_READ,
|
||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||
nullptr,
|
||
OPEN_EXISTING,
|
||
FILE_ATTRIBUTE_NORMAL,
|
||
nullptr);
|
||
if (hVol == INVALID_HANDLE_VALUE) return false;
|
||
|
||
struct VolExtentsBuf {
|
||
DWORD NumberOfDiskExtents;
|
||
DISK_EXTENT Extents[256];
|
||
} buf{};
|
||
std::memset(&buf, 0, sizeof(buf));
|
||
DWORD bytes_returned = 0;
|
||
BOOL ok = DeviceIoControl(hVol, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
|
||
nullptr, 0,
|
||
&buf, sizeof(buf),
|
||
&bytes_returned, nullptr);
|
||
CloseHandle(hVol);
|
||
if (!ok || buf.NumberOfDiskExtents == 0) return false;
|
||
disk_number = buf.Extents[0].DiskNumber;
|
||
return true;
|
||
}
|
||
|
||
// 获取指定物理磁盘号上的所有卷GUID路径(封装FindFirstVolumeW,不重复扫描)
|
||
// 返回的路径形如 \\?\Volume{GUID}\(带尾部反斜杠)
|
||
static bool get_volumes_on_disk(DWORD disk_number,
|
||
std::vector<std::wstring> &out_volumes) {
|
||
out_volumes.clear();
|
||
wchar_t vol_name[MAX_PATH];
|
||
HANDLE hFind = FindFirstVolumeW(vol_name, _countof(vol_name));
|
||
if (hFind == INVALID_HANDLE_VALUE) {
|
||
return false;
|
||
}
|
||
do {
|
||
std::wstring guid_path(vol_name);
|
||
// FindFirstVolumeW返回\\?\前缀,CreateFile需要\\.\前缀才能打开卷设备
|
||
std::wstring dev_path = guid_path;
|
||
if (dev_path.size() >= 4 && dev_path.substr(0, 4) == L"\\\\?\\") {
|
||
dev_path = std::format(L"\\\\.\\{}", dev_path.substr(4));
|
||
}
|
||
if (!dev_path.empty() && dev_path.back() == L'\\') dev_path.pop_back();
|
||
|
||
DWORD disk_num = 0;
|
||
if (!device_get_disk_number(dev_path, disk_num)) continue;
|
||
if (disk_num != disk_number) continue;
|
||
out_volumes.push_back(guid_path);
|
||
} while (FindNextVolumeW(hFind, vol_name, _countof(vol_name)));
|
||
FindVolumeClose(hFind);
|
||
return true;
|
||
}
|
||
|
||
// ===== 公开接口 =====
|
||
|
||
// 可写挂载VHD,返回磁盘号、物理路径、卷GUID(不分配盘符)
|
||
// 流程:Open(ATTACH_RW|GET_INFO) → Attach(NO_DRIVE_LETTER) → GetVirtualDiskPhysicalPath → 查找卷GUID
|
||
bool attach_vhd(const std::wstring &vhd_path, MountedVhdInfo &info, std::string *error) {
|
||
// 可写方式打开overlay层(父盘仍是只读的,overlay本身本地可写)
|
||
HANDLE hVhd = open_vhd(vhd_path,
|
||
VIRTUAL_DISK_ACCESS_ATTACH_RW | VIRTUAL_DISK_ACCESS_GET_INFO,
|
||
1, // 顶层1层可写
|
||
error);
|
||
if (hVhd == INVALID_HANDLE_VALUE) return false;
|
||
|
||
// 挂载VHD(可写,不自动分配盘符)
|
||
ATTACH_VIRTUAL_DISK_PARAMETERS attach_params = {};
|
||
attach_params.Version = ATTACH_VIRTUAL_DISK_VERSION_1;
|
||
attach_params.Version1.Reserved = 0;
|
||
DWORD err = AttachVirtualDisk(hVhd, nullptr,
|
||
ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME |
|
||
ATTACH_VIRTUAL_DISK_FLAG_NO_DRIVE_LETTER,
|
||
0, &attach_params, nullptr);
|
||
if (err != ERROR_SUCCESS) {
|
||
if (error) *error = std::format("AttachVirtualDisk(RW)失败:{}", err);
|
||
CloseHandle(hVhd);
|
||
return false;
|
||
}
|
||
|
||
// 通过VHD句柄直接获取物理磁盘路径(\\.\PhysicalDriveX),重试等待系统定位
|
||
// 等待:20次 × 100ms = 最长2秒
|
||
wchar_t phys_path[MAX_PATH] = {};
|
||
ULONG path_size = sizeof(phys_path);
|
||
err = ERROR_NOT_READY;
|
||
for (int retry = 0; retry < 20; retry++) {
|
||
path_size = sizeof(phys_path);
|
||
err = GetVirtualDiskPhysicalPath(hVhd, &path_size, phys_path);
|
||
if (err == ERROR_SUCCESS) break;
|
||
Sleep(100);
|
||
}
|
||
CloseHandle(hVhd);
|
||
|
||
if (err != ERROR_SUCCESS) {
|
||
if (error) *error = std::format("GetVirtualDiskPhysicalPath 失败:{}", err);
|
||
return false;
|
||
}
|
||
|
||
info.physical_path = phys_path;
|
||
|
||
// 解析 "\\.\PhysicalDriveX" 中的磁盘号
|
||
DWORD disk_num = 0;
|
||
if (swscanf_s(phys_path, L"\\\\.\\PhysicalDrive%u", &disk_num) != 1) {
|
||
if (error)
|
||
*error = std::format("解析物理磁盘路径失败:{}", utils::wstr_to_str(phys_path));
|
||
return false;
|
||
}
|
||
info.disk_number = disk_num;
|
||
|
||
// 在该磁盘上查找卷GUID路径(重试等待卷枚举就绪)
|
||
// 等待:20次 × 100ms = 最长2秒
|
||
for (int retry = 0; retry < 20; retry++) {
|
||
std::vector<std::wstring> vols;
|
||
get_volumes_on_disk(disk_num, vols);
|
||
if (!vols.empty()) {
|
||
info.volume_guid = vols.front();
|
||
return true;
|
||
}
|
||
Sleep(100);
|
||
}
|
||
|
||
if (error) *error = std::format("磁盘 {} 上未找到卷", disk_num);
|
||
return false;
|
||
}
|
||
|
||
bool detach_vhd(const std::wstring &vhd_path, std::string *error) {
|
||
HANDLE hVhd = open_vhd(vhd_path, VIRTUAL_DISK_ACCESS_DETACH | VIRTUAL_DISK_ACCESS_GET_INFO, 0, error);
|
||
if (hVhd == INVALID_HANDLE_VALUE) return false;
|
||
|
||
DWORD err = DetachVirtualDisk(hVhd, DETACH_VIRTUAL_DISK_FLAG_NONE, 0);
|
||
CloseHandle(hVhd);
|
||
|
||
if (err != ERROR_SUCCESS) {
|
||
if (error) *error = std::format("DetachVirtualDisk 失败:{}", err);
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool create_diff_vhd(const std::wstring &vhd_path, const std::wstring &parent_vhd_path, std::string *error) {
|
||
// 如果上次运行遗留了已挂载的overlay.vhd,先尝试卸载再删除
|
||
std::string detach_err;
|
||
(void) detach_vhd(vhd_path, &detach_err);
|
||
DeleteFileW(vhd_path.c_str());
|
||
|
||
VIRTUAL_STORAGE_TYPE vst = {};
|
||
vst.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_VHD;
|
||
vst.VendorId = VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT;
|
||
|
||
CREATE_VIRTUAL_DISK_PARAMETERS params = {};
|
||
params.Version = CREATE_VIRTUAL_DISK_VERSION_2;
|
||
params.Version2.UniqueId = GUID_NULL;
|
||
params.Version2.MaximumSize = 0; // 差分盘跟随父盘大小
|
||
params.Version2.BlockSizeInBytes = 0;
|
||
params.Version2.SectorSizeInBytes = 512; // VHD1必须为512
|
||
params.Version2.ParentPath = parent_vhd_path.c_str();
|
||
params.Version2.SourcePath = nullptr;
|
||
params.Version2.OpenFlags = OPEN_VIRTUAL_DISK_FLAG_NONE;
|
||
params.Version2.ResiliencyGuid = GUID_NULL;
|
||
|
||
// Version2强制要求VirtualDiskAccessMask必须为VIRTUAL_DISK_ACCESS_NONE
|
||
HANDLE hVhd = nullptr;
|
||
DWORD err = CreateVirtualDisk(&vst,
|
||
vhd_path.c_str(),
|
||
VIRTUAL_DISK_ACCESS_NONE,
|
||
nullptr,
|
||
CREATE_VIRTUAL_DISK_FLAG_NONE,
|
||
0,
|
||
¶ms,
|
||
nullptr,
|
||
&hVhd);
|
||
if (err != ERROR_SUCCESS) {
|
||
if (error)
|
||
*error = std::format("CreateVirtualDisk 失败:{} path={} parent={}", err,
|
||
utils::wstr_to_str(vhd_path), utils::wstr_to_str(parent_vhd_path));
|
||
return false;
|
||
}
|
||
CloseHandle(hVhd);
|
||
return true;
|
||
}
|
||
} // namespace vhd_mount
|