首次提交

This commit is contained in:
MoeGrid
2026-07-31 15:39:45 +08:00
commit feef2db6ac
32 changed files with 3447 additions and 0 deletions
+400
View File
@@ -0,0 +1,400 @@
#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 = "Failed to read ICF file";
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("Parse ICF failed: {}", parse_err);
return result;
}
// 筛选Active状态的APP条目,按part_index排序
auto app_entries = get_active_apps(*icf_file);
if (app_entries.empty()) {
result.error = "No active APP entries found in ICF";
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("Part index not consecutive: {} -> {}", prev->part_index, cur->part_index);
return result;
}
if (cur->base_version != prev->version) {
result.error = std::format("Base version mismatch for part {}: {} != {}", cur->part_index,
cur->base_version, prev->version);
return result;
}
}
// APP/OPT文件所在目录:优先使用image_dir,否则使用exe所在目录
std::wstring dir = cfg.image_dir.empty() ? utils::get_exe_dir() : cfg.image_dir;
// 构造密钥override(config.ini [KEYS] section,空则用内置密钥表)
fscrypt::KeyOverride key_override;
key_override.app_key_hex = cfg.app_key;
key_override.app_iv_hex = cfg.app_iv;
key_override.opt_key_hex = cfg.opt_key;
key_override.opt_iv_hex = 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("Mount part {}: {}", entry->part_index, entry->filename);
auto mount_res = fscrypt::mount_container(app_path, tag, &key_override);
mount_entry.success = mount_res.success;
mount_entry.error = mount_res.error;
if (!mount_res.success) {
LOG_ERROR("Mount part {} failed: {}", 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 exe_dir = utils::get_exe_dir();
std::wstring overlay_vhd = std::format(L"{}\\overlay.vhd", exe_dir);
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("Creating overlay.vhd (parent: 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 create failed: {}", ov_err);
all_ok = false;
}
} else {
LOG_INFO("Reusing existing 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 failed: {}", 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("Assign drive {} failed: Error = {}", drive, GetLastError());
all_ok = false;
} else {
LOG_INFO("Overlay mounted to {}:", 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 copy failed: {}", cp_err);
all_ok = false;
} else {
LOG_INFO("Copied {} files", copied);
}
} else {
LOG_INFO("OVERLAY_DIR not found, skip copy");
}
} else {
LOG_INFO("OVERLAY_DIR empty, skip copy");
}
}
}
}
}
}
// 挂载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("Mount OPT {}: {}", entry->version, entry->filename);
auto mount_res = fscrypt::mount_container(opt_path, opt_tag, &key_override);
mount_entry.success = mount_res.success;
mount_entry.error = mount_res.error;
if (!mount_res.success) {
LOG_ERROR("Mount OPT {} failed: {}", entry->version, mount_res.error);
}
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("Link APP {} failed", 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("Link OPT {} failed", opt.version);
}
}
}
result.success = all_ok;
if (!all_ok) {
result.error = "Some APPs failed to mount";
}
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 = "Failed to read ICF file";
return result;
}
std::string parse_err;
auto icf_file = icf::parse_file(icf_data, &parse_err);
if (!icf_file) {
result.error = std::format("Parse ICF failed: {}", 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 exe_dir = utils::get_exe_dir();
std::wstring overlay_vhd = std::format(L"{}\\overlay.vhd", exe_dir);
{
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("Removed drive letter {}", drive);
}
WIN32_FILE_ATTRIBUTE_DATA fa;
if (GetFileAttributesExW(overlay_vhd.c_str(), GetFileExInfoStandard, &fa)) {
LOG_INFO("Detaching overlay.vhd...");
std::string vhd_err;
if (!vhd_mount::detach_vhd(overlay_vhd, &vhd_err)) {
LOG_ERROR("Overlay detach failed: {}", vhd_err);
all_ok = false;
} else {
LOG_INFO("Detached overlay.vhd");
}
} else {
LOG_INFO("overlay.vhd not found, skip detach");
}
}
// 4) 倒序卸载APP容器
for (auto it = app_entries.rbegin(); it != app_entries.rend(); ++it) {
auto *entry = *it;
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("Unmount part {}: {}", entry->part_index, entry->filename);
bool ok = fscrypt::unmount_container(tag);
mount_entry.success = ok;
if (!ok) {
mount_entry.error = "Unmount failed";
LOG_ERROR("Unmount part {} failed", 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("Unmount OPT {}: {}", entry->version, entry->filename);
bool ok = fscrypt::unmount_container(opt_tag);
mount_entry.success = ok;
if (!ok) {
mount_entry.error = "Unmount failed";
LOG_ERROR("Unmount OPT {} failed", entry->version);
all_ok = false;
}
result.opts.push_back(mount_entry);
}
result.success = all_ok;
if (!all_ok) {
result.error = "Some containers failed to unmount";
}
return result;
}
}