mirror of
https://gitea.tendokyu.moe/moegrid/sega-mount.git
synced 2026-10-08 14:37:58 +03:00
首次提交
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
#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 failed: {} 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) failed: {}", 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 failed: {}", 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("Failed to parse physical disk path: {}", 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("no volume found on disk {}", 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 failed: {}", 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 failed: {} 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
|
||||
Reference in New Issue
Block a user