Files
moegrid_sega-mount/src/icf_mount.cpp
T

395 lines
17 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#define LOG_MODULE "ICF"
#include "icf_mount.h"
#include "icf_parser.h"
#include "fscrypt.h"
#include "vhd_mount.h"
#include "utils/files.h"
#include "utils/strings.h"
#include "utils/log.h"
#include <map>
#include <format>
#include <ranges>
#include <windows.h>
namespace icf_mount {
// 前向声明
static std::vector<const icf::DecodedEntry *> get_active_apps(const icf::IcfFile &icf_file);
static std::vector<const icf::DecodedEntry *> get_active_opts(const icf::IcfFile &icf_file);
// 解析ICF文件并挂载所有Active状态的APP和OPT容器
// 流程:挂载APP → 创建/挂载overlay → 挂载OPT → 创建LINK
IcfMountResult mount_icf_apps(const Config &cfg) {
IcfMountResult result{};
result.success = false;
// 读取ICF文件
auto icf_data = utils::read_file(cfg.icf_path);
if (icf_data.empty()) {
result.error = "读取 ICF 文件失败";
return result;
}
// 解密并解析ICF(parse_file内部会处理解密)
std::string parse_err;
auto icf_file = icf::parse_file(icf_data, &parse_err);
if (!icf_file) {
result.error = std::format("解析 ICF 失败:{}", parse_err);
return result;
}
// 筛选Active状态的APP条目,按part_index排序
auto app_entries = get_active_apps(*icf_file);
if (app_entries.empty()) {
result.error = "ICF 中未找到活跃的 APP 条目";
return result;
}
// 验证base version依赖关系
// part 0 没有base version
// part n (n>0) 的 base_version 应该等于 part n-1 的 version
for (size_t i = 1; i < app_entries.size(); i++) {
auto *cur = app_entries[i];
auto *prev = app_entries[i - 1];
if (cur->part_index != prev->part_index + 1) {
result.error = std::format("分区序号不连续:{} -> {}", prev->part_index, cur->part_index);
return result;
}
if (cur->base_version != prev->version) {
result.error = std::format("分区 {} 基础版本不匹配:{} != {}", cur->part_index,
cur->base_version, prev->version);
return result;
}
}
// APP/OPT文件所在目录:优先使用image_dir,否则使用当前目录
std::wstring dir = cfg.image_dir.empty() ? utils::get_cwd() : cfg.image_dir;
// 设置全局密钥覆盖(config.ini [KEYS] section,空则用内置密钥表)
fscrypt::set_key(cfg.app_key, cfg.app_iv, cfg.opt_key, cfg.opt_iv);
// 依次挂载每个APP容器
bool all_ok = true;
for (auto entry: app_entries) {
MountEntry mount_entry{};
mount_entry.filename = entry->filename;
mount_entry.version = entry->version;
mount_entry.part_index = entry->part_index;
mount_entry.tag = std::format("APP_{}", entry->part_index);
mount_entry.vhd_success = false;
std::wstring app_path = std::format(L"{}\\{}", dir, utils::to_wstring(entry->filename));
std::wstring tag = std::format(L"APP_{}", entry->part_index);
LOG_INFO("挂载分区 {}:{}", entry->part_index, entry->filename);
auto mount_res = fscrypt::mount_container(app_path, tag);
mount_entry.success = mount_res.success;
mount_entry.error = mount_res.error;
if (!mount_res.success) {
LOG_ERROR("挂载分区 {} 失败:{}", entry->part_index, mount_res.error);
all_ok = false;
}
result.apps.push_back(mount_entry);
}
// 所有APP容器挂载完成后,处理overlay写层:
// overlay.vhd不存在 → 创建差分盘 + 拷贝overlay目录文件
// overlay.vhd已存在 → 直接挂载(数据已在里面,无需重新拷贝)
if (all_ok && !app_entries.empty()) {
auto *last = app_entries.back();
std::wstring overlay_vhd = std::format(L"{}\\overlay.vhd", utils::get_cwd());
std::wstring last_tag = std::format(L"APP_{}", last->part_index);
std::wstring parent_for_create = std::format(L"\\\\?\\FscryptDisk_{}\\internal_{}.vhd", last_tag,
last->part_index);
bool vhd_exists = GetFileAttributesW(overlay_vhd.c_str()) != INVALID_FILE_ATTRIBUTES;
if (!vhd_exists) {
// 创建overlay.vhd差分盘
LOG_INFO("创建 overlay.vhd(父盘:internal_{}.vhd)", last->part_index);
std::string ov_err;
if (!vhd_mount::create_diff_vhd(overlay_vhd, parent_for_create, &ov_err)) {
LOG_ERROR("创建 overlay 失败:{}", ov_err);
all_ok = false;
}
} else {
LOG_INFO("复用已有 overlay.vhd");
// 先尝试detach,处理上次未正常卸载的情况
vhd_mount::detach_vhd(overlay_vhd, nullptr);
}
if (all_ok) {
// 挂载overlay.vhd
vhd_mount::MountedVhdInfo vhd_info{};
std::string attach_err;
if (!vhd_mount::attach_vhd(overlay_vhd, vhd_info, &attach_err)) {
LOG_ERROR("附加 overlay 失败:{}", attach_err);
all_ok = false;
} else {
result.apps.back().vhd_success = true;
// 分配盘符到overlay卷
char drive = static_cast<char>(cfg.mount_letter);
std::wstring mount_point = std::format(L"{}:\\", cfg.mount_letter);
DeleteVolumeMountPointW(mount_point.c_str()); // 清理可能残留的旧挂载点
if (!SetVolumeMountPointW(mount_point.c_str(), vhd_info.volume_guid.c_str())) {
LOG_ERROR("分配盘符 {} 失败:{}", drive, utils::last_error_str());
all_ok = false;
} else {
LOG_INFO("overlay 已挂载到 {}:", drive);
// 仅新建overlay.vhd时才拷贝overlay目录文件
// OVERLAY_DIR为空或不存在时跳过拷贝
if (!vhd_exists) {
if (!cfg.overlay_dir.empty()) {
DWORD attr = GetFileAttributesW(cfg.overlay_dir.c_str());
bool dir_exists = attr != INVALID_FILE_ATTRIBUTES &&
attr & FILE_ATTRIBUTE_DIRECTORY;
if (dir_exists) {
std::string cp_err;
size_t copied = 0;
if (!utils::copy_dir_overwrite(cfg.overlay_dir, mount_point,
&cp_err, &copied)) {
LOG_ERROR("overlay 文件拷贝失败:{}", cp_err);
all_ok = false;
} else {
LOG_INFO("已拷贝 {} 个文件", copied);
}
} else {
LOG_INFO("OVERLAY_DIR 目录不存在,跳过拷贝");
}
} else {
LOG_INFO("OVERLAY_DIR 为空,跳过拷贝");
}
}
}
}
}
}
// 挂载OPT容器(独立于APP/overlay,不受其结果影响)
auto opt_entries = get_active_opts(*icf_file);
for (auto entry: opt_entries) {
MountEntry mount_entry{};
mount_entry.filename = entry->filename;
mount_entry.version = entry->version;
mount_entry.part_index = entry->part_index;
mount_entry.tag = std::format("OPT_{}", entry->version);
mount_entry.vhd_success = false;
std::wstring opt_path = std::format(L"{}\\{}", dir, utils::to_wstring(entry->filename));
std::wstring opt_tag = std::format(L"OPT_{}", utils::to_wstring(entry->version));
LOG_INFO("挂载 OPT {}:{}", entry->version, entry->filename);
auto mount_res = fscrypt::mount_container(opt_path, opt_tag);
mount_entry.success = mount_res.success;
mount_entry.error = mount_res.error;
if (!mount_res.success) {
LOG_ERROR("挂载 OPT {} 失败:{}", entry->version, mount_res.error);
all_ok = false;
}
result.opts.push_back(mount_entry);
}
// 创建APP LINK(仅对挂载成功的APP)
if (!cfg.app_link.empty()) {
for (const auto &app: result.apps) {
if (!app.success) continue;
std::wstring tag = std::format(L"APP_{}", app.part_index);
std::wstring link_path = std::format(L"{}\\{}", cfg.app_link, app.part_index);
if (!fscrypt::link_container(tag, link_path)) {
LOG_ERROR("链接 APP {} 失败", app.part_index);
}
}
}
// 创建OPT LINK(仅对挂载成功的OPT)
if (!cfg.opt_link.empty()) {
for (const auto &opt: result.opts) {
if (!opt.success) continue;
std::wstring tag = std::format(L"OPT_{}", utils::to_wstring(opt.version));
std::wstring link_path = std::format(L"{}\\{}", cfg.opt_link, utils::to_wstring(opt.version));
if (!fscrypt::link_container(tag, link_path)) {
LOG_ERROR("链接 OPT {} 失败", opt.version);
}
}
}
result.success = all_ok;
if (!all_ok) {
result.error = "部分容器挂载失败,详见日志";
}
return result;
}
// 从ICF中获取所有Active状态的APP条目,同一part_index只保留timestamp最新的,按part_index升序
static std::vector<const icf::DecodedEntry *> get_active_apps(const icf::IcfFile &icf_file) {
// 按 part_index 分组,每组保留 timestamp 最大的条目(timestamp格式为 YYYYMMDDhhmmss,定长数字串可直接比较)
std::map<uint8_t, const icf::DecodedEntry *> latest_by_part;
for (const auto &entry: icf_file.entries) {
if (entry.type == icf::EntryType::App &&
static_cast<uint16_t>(entry.status) == 0x0102) {
auto it = latest_by_part.find(entry.part_index);
if (it == latest_by_part.end() || entry.timestamp > it->second->timestamp) {
latest_by_part[entry.part_index] = &entry;
}
}
}
// map 按 key 升序,直接收集 values 即可
std::vector<const icf::DecodedEntry *> app_entries;
app_entries.reserve(latest_by_part.size());
for (const auto *entry: latest_by_part | std::views::values) {
app_entries.push_back(entry);
}
return app_entries;
}
// 从ICF中获取所有Active状态的OPT条目,同一version只保留timestamp最新的
static std::vector<const icf::DecodedEntry *> get_active_opts(const icf::IcfFile &icf_file) {
// OPT的version字段为4字符名称(如A001),按此分组去重
std::map<std::string, const icf::DecodedEntry *> latest_by_name;
for (const auto &entry: icf_file.entries) {
if (entry.type == icf::EntryType::Opt &&
static_cast<uint16_t>(entry.status) == 0x0102) {
auto it = latest_by_name.find(entry.version);
if (it == latest_by_name.end() || entry.timestamp > it->second->timestamp) {
latest_by_name[entry.version] = &entry;
}
}
}
std::vector<const icf::DecodedEntry *> opt_entries;
opt_entries.reserve(latest_by_name.size());
for (const auto *entry: latest_by_name | std::views::values) {
opt_entries.push_back(entry);
}
return opt_entries;
}
// 解析ICF文件并卸载所有容器
// 流程:移除LINK → 卸载overlay → 倒序卸载APP → 卸载OPT
IcfMountResult unmount_icf_apps(const Config &cfg) {
IcfMountResult result{};
result.success = false;
// 读取并解析ICF
auto icf_data = utils::read_file(cfg.icf_path);
if (icf_data.empty()) {
result.error = "读取 ICF 文件失败";
return result;
}
std::string parse_err;
auto icf_file = icf::parse_file(icf_data, &parse_err);
if (!icf_file) {
result.error = std::format("解析 ICF 失败:{}", parse_err);
return result;
}
auto app_entries = get_active_apps(*icf_file);
auto opt_entries = get_active_opts(*icf_file);
bool all_ok = true;
// 1) 移除APP LINK
if (!cfg.app_link.empty()) {
for (auto *entry: app_entries) {
std::wstring link_path = std::format(L"{}\\{}", cfg.app_link, entry->part_index);
fscrypt::unlink_container(link_path);
}
}
// 2) 移除OPT LINK
if (!cfg.opt_link.empty()) {
for (auto *entry: opt_entries) {
std::wstring link_path = std::format(L"{}\\{}", cfg.opt_link,
utils::to_wstring(entry->version));
fscrypt::unlink_container(link_path);
}
}
// 3) overlay.vhd:移除盘符 + detach(保留文件供下次沿用)
std::wstring overlay_vhd = std::format(L"{}\\overlay.vhd", utils::get_cwd());
{
char drive = static_cast<char>(cfg.mount_letter);
std::wstring mount_point = std::format(L"{}:\\", cfg.mount_letter);
if (DeleteVolumeMountPointW(mount_point.c_str())) {
LOG_INFO("已移除盘符 {}", drive);
}
WIN32_FILE_ATTRIBUTE_DATA fa;
if (GetFileAttributesExW(overlay_vhd.c_str(), GetFileExInfoStandard, &fa)) {
LOG_INFO("正在分离 overlay.vhd...");
std::string vhd_err;
if (!vhd_mount::detach_vhd(overlay_vhd, &vhd_err)) {
LOG_ERROR("分离 overlay 失败:{}", vhd_err);
all_ok = false;
} else {
LOG_INFO("已分离 overlay.vhd");
}
} else {
LOG_INFO("overlay.vhd 不存在,跳过分离");
}
}
// 4) 倒序卸载APP容器
for (auto entry : std::views::reverse(app_entries)) {
MountEntry mount_entry{};
mount_entry.filename = entry->filename;
mount_entry.version = entry->version;
mount_entry.part_index = entry->part_index;
mount_entry.tag = std::format("APP_{}", entry->part_index);
mount_entry.vhd_success = false;
std::wstring tag = std::format(L"APP_{}", entry->part_index);
LOG_INFO("卸载分区 {}:{}", entry->part_index, entry->filename);
bool ok = fscrypt::unmount_container(tag);
mount_entry.success = ok;
if (!ok) {
mount_entry.error = "卸载失败";
LOG_ERROR("卸载分区 {} 失败", entry->part_index);
all_ok = false;
}
result.apps.push_back(mount_entry);
}
// 5) 卸载OPT容器
for (auto *entry: opt_entries) {
MountEntry mount_entry{};
mount_entry.filename = entry->filename;
mount_entry.version = entry->version;
mount_entry.part_index = entry->part_index;
mount_entry.tag = std::format("OPT_{}", entry->version);
mount_entry.vhd_success = false;
std::wstring opt_tag = std::format(L"OPT_{}", utils::to_wstring(entry->version));
LOG_INFO("卸载 OPT {}:{}", entry->version, entry->filename);
bool ok = fscrypt::unmount_container(opt_tag);
mount_entry.success = ok;
if (!ok) {
mount_entry.error = "卸载失败";
LOG_ERROR("卸载 OPT {} 失败", entry->version);
all_ok = false;
}
result.opts.push_back(mount_entry);
}
result.success = all_ok;
if (!all_ok) {
result.error = "部分容器卸载失败";
}
return result;
}
}