添加了图标,内置了驱动,修改了界面

This commit is contained in:
MoeGrid
2026-08-03 23:25:17 +08:00
parent 606857a318
commit 93d19f272b
33 changed files with 14958 additions and 491 deletions
+14 -20
View File
@@ -38,7 +38,7 @@ if (WIN32)
target_link_libraries(imgui PUBLIC d3d11 d3dcompiler dxgi)
endif ()
# ===== 公共源文件cmd 和 gui 共享) =====
# ===== 公共源文件 =====
set(SEGA_CORE_SOURCES
src/config.cpp
src/driver_service.cpp
@@ -52,37 +52,30 @@ set(SEGA_CORE_SOURCES
src/utils/files.cpp
src/utils/log.cpp
src/utils/strings.cpp
src/utils/resource.cpp
)
set(SEGA_WIN_LIBS bcrypt virtdisk)
# ===== 目标1: sega_mount_cmd (命令行版) =====
add_executable(sega_mount_cmd
src/cmd/main.cpp
${SEGA_CORE_SOURCES}
)
target_include_directories(sega_mount_cmd PRIVATE src)
if (WIN32)
target_link_libraries(sega_mount_cmd PRIVATE ${SEGA_WIN_LIBS})
endif ()
set_target_properties(sega_mount_cmd PROPERTIES OUTPUT_NAME sega_mount_cmd)
# ===== 目标2: sega_mount_gui (GUI版, ImGui + Win32 + DirectX11) =====
add_executable(sega_mount_gui
src/gui/main.cpp
# ===== 统一目标: sega_mount (GUI + CLI 合一) =====
# 无参数启动 GUI;带参数(-m/-u)走 CLI 模式
add_executable(sega_mount
src/main.cpp
src/gui/theme.cpp
src/gui/imgui_spectrum.cpp
src/gui/dx11_backend.cpp
src/gui/gui_utils.cpp
src/gui/ui.cpp
res/resource.rc
${SEGA_CORE_SOURCES}
)
target_include_directories(sega_mount_gui PRIVATE src)
target_include_directories(sega_mount PRIVATE src res)
if (WIN32)
target_link_libraries(sega_mount_gui PRIVATE ${SEGA_WIN_LIBS} imgui comdlg32)
# WinMain 入口需要 Windows 子系统(不弹控制台窗口)
set_target_properties(sega_mount_gui PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS")
target_link_libraries(sega_mount PRIVATE ${SEGA_WIN_LIBS} imgui comdlg32)
# WinMain 入口需要 Windows 子系统(无参数时不弹控制台窗口)
set_target_properties(sega_mount PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS")
endif ()
set_target_properties(sega_mount_gui PROPERTIES OUTPUT_NAME sega_mount_gui)
set_target_properties(sega_mount PROPERTIES OUTPUT_NAME sega_mount)
# ===== 单元测试: 挂载/卸载 OPT 容器 =====
add_executable(test_mount_opt
@@ -94,6 +87,7 @@ add_executable(test_mount_opt
src/utils/files.cpp
src/utils/log.cpp
src/utils/strings.cpp
src/utils/resource.cpp
)
target_include_directories(test_mount_opt PRIVATE src)
if (WIN32)
-13
View File
@@ -1,13 +0,0 @@
[CONFIG]
ICF_PATH = D:\SDGB_1.55.01\amfs\ICF1
IMAGE_DIR = D:\SDGB_1.55.01
OVERLAY_DIR = D:\SDGB_1.55.01\overlay
MOUNT_LETTER = X
APP_LINK = C:\Mount\App
OPT_LINK = C:\Mount\Option
[KEYS]
APP_KEY = 7CA4E6B6F3D6E8B26472973887D7FA3A
APP_IV = 53FE7135762DE3F97E7FE76B0FEF3F27
OPT_KEY = 5C84A9E726EAA5DD351F2B0750C23697
OPT_IV = C063BF6F562D084D7963C987F5281761
+86
View File
@@ -0,0 +1,86 @@
import os
import re
import json
# 获取当前脚本文件所在的文件夹路径
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
# 拼接出绝对路径,确保一定能找到同目录下的 icons.json
JSON_FILE = os.path.join(SCRIPT_DIR, 'info.json')
HEADER_FILE = os.path.join(SCRIPT_DIR, 'lucide.h')
def hex_to_utf8_escape(hex_str):
"""
将 JSON 中的 '\\e585''e585' 转化为 C++ 可用的 UTF-8 转义序列,如 "\\xEE\\x96\\x85"
"""
# 提取纯十六进制字符串
clean_hex = hex_str.replace('\\', '').replace('e', 'E', 1) if hex_str.startswith('\\') else hex_str
code_point = int(clean_hex, 16)
# 编码为 UTF-8 字节
utf8_bytes = chr(code_point).encode('utf-8')
# 转为 C++ 转义字符串格式 "\\xEE\\x96\\x85"
return ''.join([f'\\x{b:02X}' for b in utf8_bytes])
def sanitize_macro_name(key):
"""
将图标 key (如 'a-arrow-down') 转换为合法的 C++ 宏名称 (如 'ICON_A_ARROW_DOWN')
"""
# 将连字符/空格等替换为下划线,转大写
clean_key = re.sub(r'[^a-zA-Z0-9_]', '_', key)
return f"ICON_{clean_key.upper()}"
def generate_header():
with open(JSON_FILE, 'r', encoding='utf-8') as f:
data = json.load(f)
macros = []
min_code = 0xFFFF
max_code = 0x0000
for key, info in data.items():
encoded_code = info.get('encodedCode', '')
if not encoded_code:
continue
# 解析 Unicode 码位,用于计算范围
clean_hex = encoded_code.replace('\\', '')
code_point = int(clean_hex, 16)
if code_point < min_code:
min_code = code_point
if code_point > max_code:
max_code = code_point
macro_name = sanitize_macro_name(key)
utf8_str = hex_to_utf8_escape(encoded_code)
macros.append((macro_name, utf8_str, key, encoded_code))
# 生成 C++ 头文件内容
header_content = []
header_content.append("// Auto-generated by generate_header.py")
header_content.append("#pragma once\n")
# 记录码位范围(ImGui 加载字体时需要用到)
header_content.append(f"#define ICON_MIN 0x{min_code:04X}")
header_content.append(f"#define ICON_MAX 0x{max_code:04X}\n")
# 生成宏定义
for macro_name, utf8_str, original_key, raw_hex in macros:
# 对齐排版
header_content.append(f'#define {macro_name:<30} "{utf8_str}" // {raw_hex} ({original_key})')
with open(HEADER_FILE, 'w', encoding='utf-8') as f:
f.write('\n'.join(header_content))
print(f"✅ 成功生成头文件 {HEADER_FILE}!共处理了 {len(macros)} 个图标。")
print(f"📊 码位范围: 0x{min_code:04X} - 0x{max_code:04X}")
if __name__ == '__main__':
generate_header()
+12044
View File
File diff suppressed because it is too large Load Diff
+2013
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+6 -9
View File
@@ -7,11 +7,13 @@ KEY/IV 直接生成为字节数组,C++ 端无需 hex_to_bytes 转换
"""
import json
import sys
from pathlib import Path
import os
JSON_PATH = Path("./res/keys.json")
OUTPUT_PATH = Path("./src/utils/game_keys.h")
# 获取当前脚本文件所在的文件夹路径
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
JSON_PATH = os.path.join(SCRIPT_DIR, "./keys.json")
OUTPUT_PATH = os.path.join(SCRIPT_DIR, "./game_keys.h")
def hex_to_c_array(hex_str):
@@ -22,10 +24,6 @@ def hex_to_c_array(hex_str):
def main():
if not JSON_PATH.exists():
print("Error: " + str(JSON_PATH) + " not found", file=sys.stderr)
sys.exit(1)
with open(JSON_PATH, "r", encoding="utf-8") as f:
entries = json.load(f)
@@ -61,7 +59,6 @@ def main():
out.append("")
content = "\n".join(out)
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
with open(OUTPUT_PATH, "w", encoding="utf-8") as f:
f.write(content)
+6
View File
@@ -0,0 +1,6 @@
// Windows 资源文件 — 将字体和驱动文件嵌入 exe
#include "resource.h"
IDR_LUCIDE_FONT RCDATA "..\\res\\icons\\lucide.ttf"
IDR_DRIVER_SYS RCDATA "..\\res\\driver\\sgfscrypt.sys"
IDR_DRIVER_INF RCDATA "..\\res\\driver\\sgfscrypt.inf"
-100
View File
@@ -1,100 +0,0 @@
#define LOG_MODULE "MAIN"
#include "config.h"
#include "driver_service.h"
#include "icf_mount.h"
#include "utils/strings.h"
#include "utils/files.h"
#include "utils/log.h"
#include <windows.h>
#include <string>
static void print_usage() {
std::fputs(
"SEGA MOUNT\n"
"Usage:\n"
" sega_mount -m [config.ini] Mount\n"
" sega_mount -u [config.ini] Unmount\n",
stdout
);
std::fflush(stdout);
}
int main(int argc, char *argv[]) {
// 设置控制台为UTF-8代码页,配合/utf-8编译选项避免中文乱码
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
if (argc < 2) {
print_usage();
return 1;
}
// 解析命令:-m 挂载,-u 卸载
std::string cmd = argv[1];
bool is_mount = cmd == "-m";
bool is_unmount = cmd == "-u";
if (!is_mount && !is_unmount) {
LOG_ERROR("Unknown command: {}", cmd);
print_usage();
return 1;
}
// 确定config.ini路径:优先命令行参数,否则使用当前运行目录
if (argc >= 3) {
g_config_path_w = utils::to_wstring(argv[2]);
} else {
g_config_path_w = std::format(L"{}\\config.ini", utils::get_cwd());
}
// 解析config.ini 到全局 g_cfg
std::string cfg_err;
if (!parse_config(g_config_path_w, g_cfg, &cfg_err)) {
LOG_ERROR("{}", cfg_err);
return 1;
}
g_config_path = utils::wstr_to_str(g_config_path_w);
if (g_cfg.icf_path.empty()) {
LOG_ERROR("ICF_PATH is empty in config");
return 1;
}
LOG_INFO("Config loaded from {}", g_config_path);
LOG_INFO(" ICF_PATH: {}", utils::wstr_to_str(g_cfg.icf_path));
LOG_INFO(" IMAGE_DIR: {}", utils::wstr_to_str(g_cfg.image_dir));
LOG_INFO(" OVERLAY_DIR: {}", utils::wstr_to_str(g_cfg.overlay_dir));
LOG_INFO(" MOUNT_LETTER: {}", static_cast<char>(g_cfg.mount_letter));
LOG_INFO(" APP_LINK: {}", utils::wstr_to_str(g_cfg.app_link));
LOG_INFO(" OPT_LINK: {}", utils::wstr_to_str(g_cfg.opt_link));
LOG_INFO(" APP_KEY: {}", g_cfg.app_key.empty() ? "(use built-in table)" : "(override)");
LOG_INFO(" OPT_KEY: {}", g_cfg.opt_key.empty() ? "(use built-in table)" : "(override)");
// 确保sgfscrypt服务已安装并运行
std::string svc_err;
if (!driver::ensure_running(&svc_err)) {
LOG_ERROR("Failed to start driver service: {}", svc_err);
LOG_ERROR("Hint: run this program as Administrator");
return 1;
}
if (is_mount) {
auto result = icf_mount::mount_icf_apps(g_cfg);
if (!result.success) {
LOG_ERROR("Mount failed: {}", result.error);
return 1;
}
LOG_INFO("Mount Success: {} APPs, {} OPTs mounted",
result.apps.size(), result.opts.size());
return 0;
}
// is_unmount
auto result = icf_mount::unmount_icf_apps(g_cfg);
if (!result.success) {
LOG_ERROR("Unmount failed: {}", result.error);
return 1;
}
LOG_INFO("Unmount Success: {} APPs, {} OPTs unmounted",
result.apps.size(), result.opts.size());
return 0;
}
+1 -1
View File
@@ -9,7 +9,7 @@ std::wstring g_config_path_w;
bool parse_config(const std::wstring &path, Config &cfg, std::string *error) {
if (GetFileAttributesW(path.c_str()) == INVALID_FILE_ATTRIBUTES) {
if (error) *error = "Config file not found: " + utils::wstr_to_str(path);
if (error) *error = "配置文件未找到:" + utils::wstr_to_str(path);
return false;
}
+17 -17
View File
@@ -1,17 +1,20 @@
#define LOG_MODULE "DRIVER"
#include "driver_service.h"
#include "resource.h"
#include "utils/files.h"
#include "utils/strings.h"
#include "utils/resource.h"
#include "utils/log.h"
#include <windows.h>
#include <string>
namespace driver {
bool ensure_running(std::string *error) {
SC_HANDLE hScm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
if (!hScm) {
if (error) *error = "OpenSCManager failed: " + utils::last_error_str();
if (error) *error = "OpenSCManager 失败:" + utils::last_error_str();
return false;
}
@@ -21,31 +24,28 @@ namespace driver {
if (!hSvc) {
DWORD last = GetLastError();
if (last != ERROR_SERVICE_DOES_NOT_EXIST) {
if (error) *error = "OpenService failed: " + utils::last_error_str(last);
if (error) *error = "OpenService 失败:" + utils::last_error_str(last);
CloseServiceHandle(hScm);
return false;
}
// Service does not exist - copy driver to system directory and create service
std::wstring src_path = utils::get_exe_dir() + L"\\" + DRIVER_FILENAME;
// Service does not exist - extract embedded driver to system directory and create service
// Get system drivers directory (typically C:\Windows\System32\drivers)
wchar_t sys_dir[MAX_PATH] = {};
UINT sys_len = GetSystemDirectoryW(sys_dir, MAX_PATH);
if (sys_len == 0 || sys_len > MAX_PATH) {
if (error) *error = "GetSystemDirectory failed: " + utils::last_error_str();
if (error) *error = "GetSystemDirectory 失败:" + utils::last_error_str();
CloseServiceHandle(hScm);
return false;
}
std::wstring dst_path = std::wstring(sys_dir) + L"\\drivers\\" + DRIVER_FILENAME;
// Copy driver file to system directory (overwrite if exists)
if (!CopyFileW(src_path.c_str(), dst_path.c_str(), FALSE)) {
if (error) *error = "Copy driver to system directory failed: " + utils::last_error_str();
// 从嵌入资源提取驱动文件到系统目录(覆盖已有文件)
if (!utils::extract_resource_to_file(IDR_DRIVER_SYS, dst_path, error)) {
CloseServiceHandle(hScm);
return false;
}
LOG_INFO("Driver copied to {}", utils::wstr_to_str(dst_path));
LOG_INFO("驱动已提取到 {}", utils::wstr_to_str(dst_path));
hSvc = CreateServiceW(
hScm, SERVICE_NAME, SERVICE_NAME,
@@ -58,17 +58,17 @@ namespace driver {
);
if (!hSvc) {
if (error) *error = "CreateService failed: " + utils::last_error_str();
if (error) *error = "CreateService 失败:" + utils::last_error_str();
CloseServiceHandle(hScm);
return false;
}
LOG_INFO("Service installed: {}", utils::wstr_to_str(dst_path));
LOG_INFO("服务已安装:{}", utils::wstr_to_str(dst_path));
}
// Query current state
SERVICE_STATUS status{};
if (!QueryServiceStatus(hSvc, &status)) {
if (error) *error = "QueryServiceStatus failed: " + utils::last_error_str();
if (error) *error = "QueryServiceStatus 失败:" + utils::last_error_str();
CloseServiceHandle(hSvc);
CloseServiceHandle(hScm);
return false;
@@ -84,7 +84,7 @@ namespace driver {
if (!StartServiceW(hSvc, 0, nullptr)) {
DWORD last = GetLastError();
if (last != ERROR_SERVICE_ALREADY_RUNNING) {
if (error) *error = "StartService failed: " + utils::last_error_str(last);
if (error) *error = "StartService 失败:" + utils::last_error_str(last);
CloseServiceHandle(hSvc);
CloseServiceHandle(hScm);
return false;
@@ -96,7 +96,7 @@ namespace driver {
while (tries-- > 0) {
if (!QueryServiceStatus(hSvc, &status)) break;
if (status.dwCurrentState == SERVICE_RUNNING) {
LOG_INFO("Service started");
LOG_INFO("服务已启动");
CloseServiceHandle(hSvc);
CloseServiceHandle(hScm);
return true;
@@ -106,13 +106,13 @@ namespace driver {
}
if (status.dwCurrentState != SERVICE_RUNNING) {
if (error) *error = "Service failed to reach RUNNING state";
if (error) *error = "服务未能进入运行状态";
CloseServiceHandle(hSvc);
CloseServiceHandle(hScm);
return false;
}
LOG_INFO("Service started");
LOG_INFO("服务已启动");
CloseServiceHandle(hSvc);
CloseServiceHandle(hScm);
return true;
+27 -27
View File
@@ -2,8 +2,8 @@
#include "fscrypt.h"
#include "utils/strings.h"
#include "utils/aes.h"
#include "utils/crypto_constants.h"
#include "utils/game_keys.h"
#include "crypto_constants.h"
#include "keys/game_keys.h"
#include "utils/log.h"
#include <cstdio>
#include <format>
@@ -29,7 +29,7 @@ namespace fscrypt {
// 解析解密后的BootId,提取容器类型、数据偏移、扇区大小等信息
static bool parse_boot_id(const uint8_t *decrypted_bootid, size_t size, BootIdInfo *info, std::string *error) {
if (size < 0x38) {
if (error) *error = "Decrypted BootId is too small";
if (error) *error = "解密后的 BootId 太小";
return false;
}
info->type = static_cast<ContainerType>(decrypted_bootid[13]);
@@ -65,7 +65,7 @@ namespace fscrypt {
*image_type = "OPTION";
return true;
default:
if (error) *error = std::format("Unsupported BootId container type: {}", static_cast<int>(type));
if (error) *error = std::format("不支持的 BootId 容器类型:{}", static_cast<int>(type));
return false;
}
}
@@ -81,7 +81,7 @@ namespace fscrypt {
// GameID = 文件名前4个字符
if (filename.size() < 4) {
if (error) *error = "Filename too short for GameID";
if (error) *error = "文件名太短,无法提取 GameID";
return false;
}
std::string game_id;
@@ -94,7 +94,7 @@ namespace fscrypt {
bool is_app = filename.size() >= 4 && _wcsicmp(filename.substr(filename.size() - 4).c_str(), L".app") == 0;
bool is_opt = filename.size() >= 4 && _wcsicmp(filename.substr(filename.size() - 4).c_str(), L".opt") == 0;
if (!is_app && !is_opt) {
if (error) *error = "Filename must end with .app or .opt";
if (error) *error = "文件名必须以 .app .opt 结尾";
return false;
}
@@ -104,7 +104,7 @@ namespace fscrypt {
if (!key_hex.empty() && !iv_hex.empty()) {
if (!utils::hex_to_bytes(key_hex, out_key, 16) ||
!utils::hex_to_bytes(iv_hex, out_iv, 16)) {
if (error) *error = std::format("Invalid hex in set_key: {}/{}", key_hex, iv_hex);
if (error) *error = std::format("set_key 中十六进制无效:{}/{}", key_hex, iv_hex);
return false;
}
return true;
@@ -121,7 +121,7 @@ namespace fscrypt {
return true;
}
if (error) *error = std::format("GameID {} not found in keys table", game_id);
if (error) *error = std::format("GameID {} 在密钥表中未找到", game_id);
return false;
}
@@ -142,7 +142,7 @@ namespace fscrypt {
nullptr
);
if (hDriver == INVALID_HANDLE_VALUE) {
result.error = std::format("Open Fscrypt Driver Failed: {}", utils::last_error_str());
result.error = std::format("打开 Fscrypt 驱动失败:{}", utils::last_error_str());
return result;
}
@@ -151,7 +151,7 @@ namespace fscrypt {
uint8_t app_iv[16];
std::string err;
if (!lookup_key_iv(container_path, key, app_iv, &err)) {
result.error = std::format("Lookup key/IV failed: {}", err);
result.error = std::format("查找密钥/IV 失败:{}", err);
CloseHandle(hDriver);
return result;
}
@@ -159,14 +159,14 @@ namespace fscrypt {
// Read container BootId (0x60 bytes)
FILE *container_file = nullptr;
if (_wfopen_s(&container_file, container_path.c_str(), L"rb") != 0 || !container_file) {
result.error = "Open container file failed";
result.error = "打开容器文件失败";
CloseHandle(hDriver);
return result;
}
uint8_t bootid_encrypted[BOOTID_SIZE] = {};
size_t bootid_read = fread(bootid_encrypted, 1, BOOTID_SIZE, container_file);
if (bootid_read != BOOTID_SIZE) {
result.error = "Read BootId failed";
result.error = "读取 BootId 失败";
fclose(container_file);
CloseHandle(hDriver);
return result;
@@ -177,7 +177,7 @@ namespace fscrypt {
auto bootid_decrypted = aes::cbc_decrypt(crypto::BOOT_KEY, crypto::BOOT_IV,
bootid_encrypted, BOOTID_SIZE, &err);
if (bootid_decrypted.empty()) {
result.error = std::format("Decrypt BootId failed: {}", err);
result.error = std::format("解密 BootId 失败:{}", err);
fclose(container_file);
CloseHandle(hDriver);
return result;
@@ -186,7 +186,7 @@ namespace fscrypt {
// Parse BootId
BootIdInfo info{};
if (!parse_boot_id(bootid_decrypted.data(), bootid_decrypted.size(), &info, &err)) {
result.error = std::format("Parse BootId failed: {}", err);
result.error = std::format("解析 BootId 失败:{}", err);
fclose(container_file);
CloseHandle(hDriver);
return result;
@@ -196,7 +196,7 @@ namespace fscrypt {
const char *container_name = nullptr;
const char *image_type = nullptr;
if (!recognize_container(info.type, &container_name, &image_type, &err)) {
result.error = std::format("Recognize BootId container failed: {}", err);
result.error = std::format("识别 BootId 容器失败:{}", err);
fclose(container_file);
CloseHandle(hDriver);
return result;
@@ -261,7 +261,7 @@ namespace fscrypt {
&in_buf, sizeof(in_buf),
out_buf, sizeof(out_buf),
&bytes_returned, nullptr)) {
result.error = std::format("DeviceIoControl failed: {}", utils::last_error_str());
result.error = std::format("DeviceIoControl 失败:{}", utils::last_error_str());
CloseHandle(hDriver);
return result;
}
@@ -270,9 +270,9 @@ namespace fscrypt {
if (bytes_returned >= sizeof(wchar_t) && out_buf[0] != 0) {
result.nt_target = out_buf; // 含 \??\FscryptDisk_<tag>\ .
result.success = true;
LOG_INFO("Mounted: {}", utils::wstr_to_str(result.nt_target));
LOG_INFO("已挂载:{}", utils::wstr_to_str(result.nt_target));
} else {
result.error = "Mount IOCTL succeeded but output buffer is empty";
result.error = "Mount IOCTL 成功但输出缓冲区为空";
result.success = false;
}
@@ -300,7 +300,7 @@ namespace fscrypt {
nullptr
);
if (hDriver == INVALID_HANDLE_VALUE) {
LOG_ERROR("Open Fscrypt Driver Failed: {}", utils::last_error_str());
LOG_ERROR("打开 Fscrypt 驱动失败:{}", utils::last_error_str());
return false;
}
@@ -327,7 +327,7 @@ namespace fscrypt {
return true;
}
LOG_ERROR("UNMOUNT failed: {}", utils::last_error_str());
LOG_ERROR("UNMOUNT 失败:{}", utils::last_error_str());
CloseHandle(hDriver);
return false;
}
@@ -346,7 +346,7 @@ namespace fscrypt {
nullptr
);
if (hCheck == INVALID_HANDLE_VALUE) {
LOG_ERROR("LINK failed: Non-existent device");
LOG_ERROR("LINK 失败:设备不存在");
return false;
}
CloseHandle(hCheck);
@@ -406,7 +406,7 @@ namespace fscrypt {
std::error_code mk_ec;
fs::create_directories(link_path, mk_ec);
if (mk_ec && !fs::exists(link_path)) {
LOG_ERROR("CreateDirectory failed for {}: {} {}", utils::wstr_to_str(link_path), mk_ec.value(),
LOG_ERROR("创建目录失败 {}{} {}", utils::wstr_to_str(link_path), mk_ec.value(),
mk_ec.message());
return false;
}
@@ -422,7 +422,7 @@ namespace fscrypt {
nullptr
);
if (hDir == INVALID_HANDLE_VALUE) {
LOG_ERROR("CreateFileW failed for {}: {}", utils::wstr_to_str(link_path), utils::last_error_str());
LOG_ERROR("CreateFileW 失败 {}{}", utils::wstr_to_str(link_path), utils::last_error_str());
return false;
}
@@ -431,12 +431,12 @@ namespace fscrypt {
reparse_buf.data(), static_cast<DWORD>(buf_size),
nullptr, 0, &bytes_returned, nullptr);
if (!ok) {
LOG_ERROR("FSCTL_SET_REPARSE_POINT failed: {}", utils::last_error_str());
LOG_ERROR("FSCTL_SET_REPARSE_POINT 失败:{}", utils::last_error_str());
}
CloseHandle(hDir);
if (ok) {
LOG_INFO("Linked {} -> {}", utils::wstr_to_str(target), utils::wstr_to_str(source));
LOG_INFO("已链接 {} -> {}", utils::wstr_to_str(target), utils::wstr_to_str(source));
}
return ok;
}
@@ -455,10 +455,10 @@ namespace fscrypt {
// mount point重解析点可直接用RemoveDirectoryW移除(会删除junction而非目标内容)
if (!RemoveDirectoryW(path.c_str())) {
LOG_ERROR("RemoveDirectory failed for {}: {}", utils::wstr_to_str(path), utils::last_error_str());
LOG_ERROR("RemoveDirectory 失败 {}{}", utils::wstr_to_str(path), utils::last_error_str());
return false;
}
LOG_INFO("Removed link {}", utils::wstr_to_str(path));
LOG_INFO("已移除链接 {}", utils::wstr_to_str(path));
return true;
}
}
+15 -11
View File
@@ -23,16 +23,20 @@ bool CreateDeviceD3D(HWND hWnd) {
UINT createDeviceFlags = 0;
D3D_FEATURE_LEVEL featureLevel;
const D3D_FEATURE_LEVEL featureLevelArray[2] = {D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0};
HRESULT res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
createDeviceFlags, featureLevelArray, 2,
D3D11_SDK_VERSION, &sd, &g_pSwapChain,
&g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
constexpr D3D_FEATURE_LEVEL featureLevelArray[2] = {D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0};
HRESULT res = D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
createDeviceFlags, featureLevelArray, 2,
D3D11_SDK_VERSION, &sd, &g_pSwapChain,
&g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext
);
if (res == DXGI_ERROR_UNSUPPORTED)
res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_WARP, nullptr,
createDeviceFlags, featureLevelArray, 2,
D3D11_SDK_VERSION, &sd, &g_pSwapChain,
&g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
res = D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_WARP, nullptr,
createDeviceFlags, featureLevelArray, 2,
D3D11_SDK_VERSION, &sd, &g_pSwapChain,
&g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext
);
if (res != S_OK)
return false;
@@ -78,8 +82,8 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
case WM_SIZE:
if (wParam == SIZE_MINIMIZED)
return 0;
g_ResizeWidth = (UINT) LOWORD(lParam);
g_ResizeHeight = (UINT) HIWORD(lParam);
g_ResizeWidth = static_cast<UINT>(LOWORD(lParam));
g_ResizeHeight = static_cast<UINT>(HIWORD(lParam));
return 0;
case WM_SYSCOMMAND:
if ((wParam & 0xfff0) == SC_KEYMENU)
+3
View File
@@ -24,3 +24,6 @@ inline ID3D11RenderTargetView *g_mainRenderTargetView = nullptr;
// ===== 驱动状态 =====
inline bool g_driver_ready = false;
// ===== 主题状态 =====
inline bool g_dark_theme = true; // true=暗色, false=亮色
+1 -1
View File
@@ -12,7 +12,7 @@ void load_config_file(const std::wstring &path) {
g_config_path = utils::wstr_to_str(path);
std::string err;
if (parse_config(path, g_cfg, &err)) {
g_log_lines.push_back(std::format("[INFO] Config loaded: {}", g_config_path));
g_log_lines.push_back(std::format("[INFO] 配置已加载:{}", g_config_path));
} else {
g_log_lines.push_back(std::format("[ERROR] {}", err));
}
+112
View File
@@ -0,0 +1,112 @@
// Adobe Spectrum 主题 for ImGui(精简版,不含内嵌字体数据)
// 来源:https://github.com/adizorde/imgui-spectrum
// 字体加载与压缩字体数据已移除,项目使用自带的中文字体(msyh.ttc)
#include "imgui_spectrum.h"
#include "imgui.h"
namespace ImGui::Spectrum {
// 浅色调色板
static const SpectrumPalette LightPalette = {
/* GRAY50..900 */ Color(0xFFFFFF), Color(0xFAFAFA), Color(0xF5F5F5), Color(0xEAEAEA), Color(0xE1E1E1),
Color(0xCACACA), Color(0xB3B3B3), Color(0x8E8E8E), Color(0x707070), Color(0x4B4B4B), Color(0x2C2C2C),
/* BLUE */ Color(0x2680EB), Color(0x1473E6), Color(0x0D66D0), Color(0x095ABA),
/* RED */ Color(0xE34850), Color(0xD7373F), Color(0xC9252D), Color(0xBB121A),
/* ORANGE */ Color(0xE68619), Color(0xDA7B11), Color(0xCB6F10), Color(0xBD640D),
/* GREEN */ Color(0x2D9D78), Color(0x268E6C), Color(0x12805C), Color(0x107154),
/* INDIGO */ Color(0x6767EC), Color(0x5C5CE0), Color(0x5151D3), Color(0x4646C6),
/* CELERY */ Color(0x44B556), Color(0x3DA74E), Color(0x379947), Color(0x318B40),
/* MAGENTA */ Color(0xD83790), Color(0xCE2783), Color(0xBC1C74), Color(0xAE0E66),
/* YELLOW */ Color(0xDFBF00), Color(0xD2B200), Color(0xC4A600), Color(0xB79900),
/* FUCHSIA */ Color(0xC038CC), Color(0xB130BD), Color(0xA228AD), Color(0x93219E),
/* SEAFOAM */ Color(0x1B959A), Color(0x16878C), Color(0x0F797D), Color(0x096C6F),
/* CHARTREUSE */ Color(0x85D044), Color(0x7CC33F), Color(0x73B53A), Color(0x6AA834),
/* PURPLE */ Color(0x9256D9), Color(0x864CCC), Color(0x7A42BF), Color(0x6F38B1),
};
// 暗色调色板
static const SpectrumPalette DarkPalette = {
/* GRAY50..900 */ Color(0x252525), Color(0x2F2F2F), Color(0x323232), Color(0x393939), Color(0x3E3E3E),
Color(0x4D4D4D), Color(0x5C5C5C), Color(0x7B7B7B), Color(0x999999), Color(0xCDCDCD), Color(0xFFFFFF),
/* BLUE */ Color(0x2680EB), Color(0x378EF0), Color(0x4B9CF5), Color(0x5AA9FA),
/* RED */ Color(0xE34850), Color(0xEC5B62), Color(0xF76D74), Color(0xFF7B82),
/* ORANGE */ Color(0xE68619), Color(0xF29423), Color(0xF9A43F), Color(0xFFB55B),
/* GREEN */ Color(0x2D9D78), Color(0x33AB84), Color(0x39B990), Color(0x3FC89C),
/* INDIGO */ Color(0x6767EC), Color(0x7575F1), Color(0x8282F6), Color(0x9090FA),
/* CELERY */ Color(0x44B556), Color(0x4BC35F), Color(0x51D267), Color(0x58E06F),
/* MAGENTA */ Color(0xD83790), Color(0xE2499D), Color(0xEC5AAA), Color(0xF56BB7),
/* YELLOW */ Color(0xDFBF00), Color(0xEDCC00), Color(0xFAD900), Color(0xFFE22E),
/* FUCHSIA */ Color(0xC038CC), Color(0xCF3EDC), Color(0xD951E5), Color(0xE366EF),
/* SEAFOAM */ Color(0x1B959A), Color(0x20A3A8), Color(0x23B2B8), Color(0x26C0C7),
/* CHARTREUSE */ Color(0x85D044), Color(0x8EDE49), Color(0x9BEC54), Color(0xA3F858),
/* PURPLE */ Color(0x9256D9), Color(0x9D64E1), Color(0xA873E9), Color(0xB483F0),
};
const SpectrumPalette *Colors = &LightPalette;
// 应用 Spectrum 主题样式。dark=true 使用暗色主题。
void StyleColorsSpectrum(bool dark) {
Colors = dark ? &DarkPalette : &LightPalette;
ImGuiStyle *style = &ImGui::GetStyle();
style->WindowRounding = 4.0f;
style->FrameRounding = 4.0f;
style->FrameBorderSize = 1.0f;
style->GrabRounding = 4.0f;
ImVec4 *col = style->Colors;
col[ImGuiCol_Text] = ColorConvertU32ToFloat4(Colors->GRAY800); // text on hovered controls is gray900
col[ImGuiCol_TextDisabled] = ColorConvertU32ToFloat4(Colors->GRAY500);
col[ImGuiCol_WindowBg] = ColorConvertU32ToFloat4(Colors->GRAY100);
col[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
col[ImGuiCol_PopupBg] = ColorConvertU32ToFloat4(Colors->GRAY50);
// not sure about this. Note: applies to tooltips too.
col[ImGuiCol_Border] = ColorConvertU32ToFloat4(Colors->GRAY300);
col[ImGuiCol_BorderShadow] = ColorConvertU32ToFloat4(Spectrum::Static::NONE); // We don't want shadows. Ever.
col[ImGuiCol_FrameBg] = ColorConvertU32ToFloat4(Colors->GRAY75);
// this isnt right, spectrum does not do this, but it's a good fallback
col[ImGuiCol_FrameBgHovered] = ColorConvertU32ToFloat4(Colors->GRAY50);
col[ImGuiCol_FrameBgActive] = ColorConvertU32ToFloat4(Colors->GRAY200);
col[ImGuiCol_TitleBg] = ColorConvertU32ToFloat4(Colors->GRAY300);
// those titlebar values are totally made up, spectrum does not have this.
col[ImGuiCol_TitleBgActive] = ColorConvertU32ToFloat4(Colors->GRAY200);
col[ImGuiCol_TitleBgCollapsed] = ColorConvertU32ToFloat4(Colors->GRAY400);
col[ImGuiCol_MenuBarBg] = ColorConvertU32ToFloat4(Colors->GRAY100);
col[ImGuiCol_ScrollbarBg] = ColorConvertU32ToFloat4(Colors->GRAY100); // same as regular background
col[ImGuiCol_ScrollbarGrab] = ColorConvertU32ToFloat4(Colors->GRAY400);
col[ImGuiCol_ScrollbarGrabHovered] = ColorConvertU32ToFloat4(Colors->GRAY600);
col[ImGuiCol_ScrollbarGrabActive] = ColorConvertU32ToFloat4(Colors->GRAY700);
col[ImGuiCol_CheckMark] = ColorConvertU32ToFloat4(Colors->BLUE500);
col[ImGuiCol_SliderGrab] = ColorConvertU32ToFloat4(Colors->GRAY700);
col[ImGuiCol_SliderGrabActive] = ColorConvertU32ToFloat4(Colors->GRAY800);
col[ImGuiCol_Button] = ColorConvertU32ToFloat4(Colors->GRAY75);
// match default button to Spectrum's 'Action Button'.
col[ImGuiCol_ButtonHovered] = ColorConvertU32ToFloat4(Colors->GRAY50);
col[ImGuiCol_ButtonActive] = ColorConvertU32ToFloat4(Colors->GRAY200);
col[ImGuiCol_Header] = ColorConvertU32ToFloat4(Colors->BLUE400);
col[ImGuiCol_HeaderHovered] = ColorConvertU32ToFloat4(Colors->BLUE500);
col[ImGuiCol_HeaderActive] = ColorConvertU32ToFloat4(Colors->BLUE600);
col[ImGuiCol_Separator] = ColorConvertU32ToFloat4(Colors->GRAY400);
col[ImGuiCol_SeparatorHovered] = ColorConvertU32ToFloat4(Colors->GRAY600);
col[ImGuiCol_SeparatorActive] = ColorConvertU32ToFloat4(Colors->GRAY700);
col[ImGuiCol_ResizeGrip] = ColorConvertU32ToFloat4(Colors->GRAY400);
col[ImGuiCol_ResizeGripHovered] = ColorConvertU32ToFloat4(Colors->GRAY600);
col[ImGuiCol_ResizeGripActive] = ColorConvertU32ToFloat4(Colors->GRAY700);
col[ImGuiCol_PlotLines] = ColorConvertU32ToFloat4(Colors->BLUE400);
col[ImGuiCol_PlotLinesHovered] = ColorConvertU32ToFloat4(Colors->BLUE600);
col[ImGuiCol_PlotHistogram] = ColorConvertU32ToFloat4(Colors->BLUE400);
col[ImGuiCol_PlotHistogramHovered] = ColorConvertU32ToFloat4(Colors->BLUE600);
col[ImGuiCol_TextSelectedBg] = ColorConvertU32ToFloat4((Colors->BLUE400 & 0x00FFFFFF) | 0x33000000);
col[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
col[ImGuiCol_NavCursor] = ColorConvertU32ToFloat4((Colors->GRAY900 & 0x00FFFFFF) | 0x0A000000);
col[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
col[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
col[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);
col[ImGuiCol_CheckMark] = ColorConvertU32ToFloat4(Colors->GRAY50);
col[ImGuiCol_Tab] = ColorConvertU32ToFloat4(Colors->GRAY300);
col[ImGuiCol_TabSelected] = ColorConvertU32ToFloat4(Colors->BLUE500);
col[ImGuiCol_TabHovered] = ColorConvertU32ToFloat4(Colors->BLUE700);
col[ImGuiCol_TabDimmed] = ColorConvertU32ToFloat4(Colors->GRAY400);
col[ImGuiCol_TabDimmedSelected] = ColorConvertU32ToFloat4(Colors->BLUE700);
}
}
+93
View File
@@ -0,0 +1,93 @@
#pragma once
/*
Color definitions in ImGui are a good starting point,
but do not cover all the intricacies of Spectrum's possible colors
in controls and widgets.
One big difference is that ImGui communicates widget activity
(hover, pressed) with their background, while spectrum uses a mix
of background and border, with border being the most common choice.
Because of this, we reference extra colors in spectrum from
imgui_widgets.cpp directly. Theme-dependent colors are accessed at
runtime via Spectrum::Colors (a pointer swapped by SetTheme()).
*/
namespace ImGui::Spectrum {
// Widget metric constants
constexpr float CHECKBOX_BORDER_SIZE = 2.0f;
constexpr float CHECKBOX_ROUNDING = 2.0f;
// Sets the ImGui style to Spectrum. Pass dark=true for dark theme.
// To switch themes at runtime, call again with the new value.
void StyleColorsSpectrum(bool dark = false);
namespace {
// Unnamed namespace, since we only use this here.
unsigned int Color(unsigned int c) {
// ImGui ImU32 format is 0xAABBGGRR; standard RGB literals are 0xRRGGBB.
// Swap R and B channels and add full alpha.
const short a = 0xFF;
const short r = (c >> 16) & 0xFF;
const short g = (c >> 8) & 0xFF;
const short b = (c >> 0) & 0xFF;
return (a << 24) | (r << 0) | (g << 8) | (b << 16);
}
}
inline unsigned int color_alpha(unsigned int alpha, unsigned int c) {
return ((alpha & 0xFF) << 24) | (c & 0x00FFFFFF);
}
namespace Static {
// static colors (same in both light and dark themes)
const unsigned int NONE = 0x00000000; // transparent
const unsigned int WHITE = Color(0xFFFFFF);
const unsigned int BLACK = Color(0x000000);
const unsigned int GRAY200 = Color(0xF4F4F4);
const unsigned int GRAY300 = Color(0xEAEAEA);
const unsigned int GRAY400 = Color(0xD3D3D3);
const unsigned int GRAY500 = Color(0xBCBCBC);
const unsigned int GRAY600 = Color(0x959595);
const unsigned int GRAY700 = Color(0x767676);
const unsigned int GRAY800 = Color(0x505050);
const unsigned int GRAY900 = Color(0x323232);
const unsigned int BLUE400 = Color(0x378EF0);
const unsigned int BLUE500 = Color(0x2680EB);
const unsigned int BLUE600 = Color(0x1473E6);
const unsigned int BLUE700 = Color(0x0D66D0);
const unsigned int RED400 = Color(0xEC5B62);
const unsigned int RED500 = Color(0xE34850);
const unsigned int RED600 = Color(0xD7373F);
const unsigned int RED700 = Color(0xC9252D);
const unsigned int ORANGE400 = Color(0xF29423);
const unsigned int ORANGE500 = Color(0xE68619);
const unsigned int ORANGE600 = Color(0xDA7B11);
const unsigned int ORANGE700 = Color(0xCB6F10);
const unsigned int GREEN400 = Color(0x33AB84);
const unsigned int GREEN500 = Color(0x2D9D78);
const unsigned int GREEN600 = Color(0x268E6C);
const unsigned int GREEN700 = Color(0x12805C);
}
// Theme-dependent color palette. Access via Spectrum::Colors->.
struct SpectrumPalette {
unsigned int GRAY50, GRAY75, GRAY100, GRAY200, GRAY300, GRAY400, GRAY500, GRAY600, GRAY700, GRAY800,
GRAY900;
unsigned int BLUE400, BLUE500, BLUE600, BLUE700;
unsigned int RED400, RED500, RED600, RED700;
unsigned int ORANGE400, ORANGE500, ORANGE600, ORANGE700;
unsigned int GREEN400, GREEN500, GREEN600, GREEN700;
unsigned int INDIGO400, INDIGO500, INDIGO600, INDIGO700;
unsigned int CELERY400, CELERY500, CELERY600, CELERY700;
unsigned int MAGENTA400, MAGENTA500, MAGENTA600, MAGENTA700;
unsigned int YELLOW400, YELLOW500, YELLOW600, YELLOW700;
unsigned int FUCHSIA400, FUCHSIA500, FUCHSIA600, FUCHSIA700;
unsigned int SEAFOAM400, SEAFOAM500, SEAFOAM600, SEAFOAM700;
unsigned int CHARTREUSE400, CHARTREUSE500, CHARTREUSE600, CHARTREUSE700;
unsigned int PURPLE400, PURPLE500, PURPLE600, PURPLE700;
};
extern const SpectrumPalette *Colors;
}
-148
View File
@@ -1,148 +0,0 @@
// SEGA Mount GUI — ImGui + Win32 + DirectX11
#define LOG_MODULE "GUI"
#include "gui_common.h"
#include "dx11_backend.h"
#include "gui_utils.h"
#include "ui.h"
#include "theme.h"
#include "imgui.h"
#include "imgui_impl_win32.h"
#include "imgui_impl_dx11.h"
#include "driver_service.h"
#include "utils/files.h"
#include <tchar.h>
#include <windows.h>
#include <format>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) {
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
// 默认加载当前目录的 config.ini
std::wstring default_config = std::format(L"{}\\config.ini", utils::get_cwd());
load_config_file(default_config);
// 启动时自动检测驱动
{
std::string svc_err;
g_driver_ready = driver::ensure_running(&svc_err);
if (g_driver_ready)
g_log_lines.emplace_back("[INFO] Driver service ready");
else
g_log_lines.push_back(std::format("[ERROR] Driver: {}", svc_err));
}
// 初始化 ImGui DPI 感知
ImGui_ImplWin32_EnableDpiAwareness();
float main_scale = ImGui_ImplWin32_GetDpiScaleForMonitor(
::MonitorFromPoint(POINT{0, 0}, MONITOR_DEFAULTTOPRIMARY));
// 创建 Win32 窗口
WNDCLASSEXW wc = {
sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L,
hInstance, nullptr, nullptr, nullptr, nullptr,
L"SEGA Mount", nullptr
};
::RegisterClassExW(&wc);
HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"SEGA Mount GUI",
WS_OVERLAPPEDWINDOW, 100, 100,
(int)(900 * main_scale), (int)(600 * main_scale),
nullptr, nullptr, wc.hInstance, nullptr);
// 初始化 D3D11
if (!CreateDeviceD3D(hwnd)) {
CleanupDeviceD3D();
::UnregisterClassW(wc.lpszClassName, wc.hInstance);
return 1;
}
::ShowWindow(hwnd, SW_SHOWDEFAULT);
::UpdateWindow(hwnd);
// 初始化 ImGui
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
(void) io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
// 应用暗色主题(CinderImGui 风格)
apply_dark_theme();
// DPI 缩放
ImGuiStyle &style = ImGui::GetStyle();
style.ScaleAllSizes(main_scale);
style.FontScaleDpi = main_scale;
// 加载中文字体
io.Fonts->AddFontFromFileTTF(R"(c:\Windows\Fonts\msyh.ttc)", 0, nullptr,
io.Fonts->GetGlyphRangesChineseFull());
ImGui_ImplWin32_Init(hwnd);
ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
// 启用日志捕获
g_log_capture_enabled = true;
// 主循环
bool done = false;
while (!done) {
MSG msg;
while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
if (msg.message == WM_QUIT)
done = true;
}
if (done)
break;
if (g_SwapChainOccluded && g_pSwapChain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED) {
::Sleep(10);
continue;
}
g_SwapChainOccluded = false;
if (g_ResizeWidth != 0 && g_ResizeHeight != 0) {
CleanupRenderTarget();
g_pSwapChain->ResizeBuffers(0, g_ResizeWidth, g_ResizeHeight, DXGI_FORMAT_UNKNOWN, 0);
g_ResizeWidth = g_ResizeHeight = 0;
CreateRenderTarget();
}
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
// 窗口最小化时 DisplaySize 为 0×0,跳过渲染避免 ClipRect 断言失败
ImGuiIO &frame_io = ImGui::GetIO();
if (frame_io.DisplaySize.x <= 0.0f || frame_io.DisplaySize.y <= 0.0f) {
::Sleep(10);
continue;
}
ImGui::NewFrame();
DrawUI();
ImGui::Render();
constexpr float clear_color[4] = {0.1f, 0.1f, 0.1f, 1.0f};
g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color);
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
HRESULT hr = g_pSwapChain->Present(1, 0);
g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED);
}
// 清理
g_log_capture_enabled = false;
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
CleanupDeviceD3D();
::DestroyWindow(hwnd);
::UnregisterClassW(wc.lpszClassName, wc.hInstance);
return 0;
}
+15 -56
View File
@@ -1,62 +1,21 @@
#include "theme.h"
#include "imgui.h"
#include "imgui_spectrum.h"
// 从 CinderImGui::Options::darkTheme() 提取,适配原生 ImGui
// ModalWindowDarkening → ModalWindowDimBg (ImGui 1.73+ 重命名)
void apply_dark_theme() {
ImGuiStyle &style = ImGui::GetStyle();
static bool s_dark = true; // 当前主题状态
style.WindowMinSize = ImVec2(160, 20);
style.FramePadding = ImVec2(4, 2);
style.ItemSpacing = ImVec2(6, 2);
style.ItemInnerSpacing = ImVec2(2, 4);
style.Alpha = 0.95f;
style.WindowRounding = 4.0f;
style.FrameRounding = 2.0f;
style.IndentSpacing = 6.0f;
style.ColumnsMinSpacing = 50.0f;
style.GrabMinSize = 14.0f;
style.GrabRounding = 16.0f;
style.ScrollbarSize = 12.0f;
style.ScrollbarRounding = 16.0f;
// 应用 Adobe Spectrum 主题。dark=true 暗色, dark=false 亮色
void apply_theme(bool dark) {
s_dark = dark;
ImGui::Spectrum::StyleColorsSpectrum(dark);
}
ImVec4 *c = style.Colors;
c[ImGuiCol_Text] = ImVec4(0.86f, 0.93f, 0.89f, 0.78f);
c[ImGuiCol_TextDisabled] = ImVec4(0.86f, 0.93f, 0.89f, 0.28f);
c[ImGuiCol_WindowBg] = ImVec4(0.13f, 0.14f, 0.17f, 1.00f);
c[ImGuiCol_Border] = ImVec4(0.31f, 0.31f, 1.00f, 0.00f);
c[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
c[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.22f, 0.27f, 1.00f);
c[ImGuiCol_FrameBgHovered] = ImVec4(0.92f, 0.18f, 0.29f, 0.78f);
c[ImGuiCol_FrameBgActive] = ImVec4(0.92f, 0.18f, 0.29f, 1.00f);
c[ImGuiCol_TitleBg] = ImVec4(0.20f, 0.22f, 0.27f, 1.00f);
c[ImGuiCol_TitleBgCollapsed] = ImVec4(0.20f, 0.22f, 0.27f, 0.75f);
c[ImGuiCol_TitleBgActive] = ImVec4(0.92f, 0.18f, 0.29f, 1.00f);
c[ImGuiCol_MenuBarBg] = ImVec4(0.20f, 0.22f, 0.27f, 0.47f);
c[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.22f, 0.27f, 1.00f);
c[ImGuiCol_ScrollbarGrab] = ImVec4(0.09f, 0.15f, 0.16f, 1.00f);
c[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.92f, 0.18f, 0.29f, 0.78f);
c[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.92f, 0.18f, 0.29f, 1.00f);
c[ImGuiCol_CheckMark] = ImVec4(0.71f, 0.22f, 0.27f, 1.00f);
c[ImGuiCol_SliderGrab] = ImVec4(0.47f, 0.77f, 0.83f, 0.14f);
c[ImGuiCol_SliderGrabActive] = ImVec4(0.92f, 0.18f, 0.29f, 1.00f);
c[ImGuiCol_Button] = ImVec4(0.47f, 0.77f, 0.83f, 0.14f);
c[ImGuiCol_ButtonHovered] = ImVec4(0.92f, 0.18f, 0.29f, 0.86f);
c[ImGuiCol_ButtonActive] = ImVec4(0.92f, 0.18f, 0.29f, 1.00f);
c[ImGuiCol_Header] = ImVec4(0.92f, 0.18f, 0.29f, 0.76f);
c[ImGuiCol_HeaderHovered] = ImVec4(0.92f, 0.18f, 0.29f, 0.86f);
c[ImGuiCol_HeaderActive] = ImVec4(0.92f, 0.18f, 0.29f, 1.00f);
c[ImGuiCol_Separator] = ImVec4(0.14f, 0.16f, 0.19f, 1.00f);
c[ImGuiCol_SeparatorHovered] = ImVec4(0.92f, 0.18f, 0.29f, 0.78f);
c[ImGuiCol_SeparatorActive] = ImVec4(0.92f, 0.18f, 0.29f, 1.00f);
c[ImGuiCol_ResizeGrip] = ImVec4(0.47f, 0.77f, 0.83f, 0.04f);
c[ImGuiCol_ResizeGripHovered] = ImVec4(0.92f, 0.18f, 0.29f, 0.78f);
c[ImGuiCol_ResizeGripActive] = ImVec4(0.92f, 0.18f, 0.29f, 1.00f);
c[ImGuiCol_PlotLines] = ImVec4(0.86f, 0.93f, 0.89f, 0.63f);
c[ImGuiCol_PlotLinesHovered] = ImVec4(0.92f, 0.18f, 0.29f, 1.00f);
c[ImGuiCol_PlotHistogram] = ImVec4(0.86f, 0.93f, 0.89f, 0.63f);
c[ImGuiCol_PlotHistogramHovered] = ImVec4(0.92f, 0.18f, 0.29f, 1.00f);
c[ImGuiCol_TextSelectedBg] = ImVec4(0.92f, 0.18f, 0.29f, 0.43f);
c[ImGuiCol_PopupBg] = ImVec4(0.20f, 0.22f, 0.27f, 0.9f);
c[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.22f, 0.27f, 0.73f);
// 切换主题(Dark ↔ Light
void toggle_theme() {
apply_theme(!s_dark);
}
// 获取当前是否为暗色主题
bool is_dark_theme() {
return s_dark;
}
+8 -2
View File
@@ -1,4 +1,10 @@
#pragma once
// 应用 CinderImGui 风格的暗色主题(红色强调)
void apply_dark_theme();
// 应用 Adobe Spectrum 主题。dark=true 暗色, dark=false 亮色
void apply_theme(bool dark);
// 切换主题(Dark ↔ Light
void toggle_theme();
// 获取当前是否为暗色主题
bool is_dark_theme();
+78 -32
View File
@@ -2,9 +2,48 @@
#include "gui_common.h"
#include "gui_utils.h"
#include "icf_mount.h"
#include "theme.h"
#include "resource.h"
#include "utils/resource.h"
#include "utils/strings.h"
#include "imgui.h"
#include "icons/lucide.h"
#include <format>
#include <windows.h>
// 初始化 ImGui:主题、DPI 缩放、字体加载
void InitUI(float main_scale) {
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
// 应用 Spectrum 主题(默认暗色)
apply_theme(g_dark_theme);
// DPI 缩放
ImGuiStyle &style = ImGui::GetStyle();
style.ScaleAllSizes(main_scale);
style.FontScaleDpi = main_scale;
// 加载中文字体(主字体)
constexpr float font_size = 18.0f;
io.Fonts->AddFontFromFileTTF(R"(c:\Windows\Fonts\msyh.ttc)", font_size, nullptr,
io.Fonts->GetGlyphRangesChineseFull());
// 加载 Lucide 图标字体(合并模式回退),从 exe 资源中加载
{
const void *font_data = nullptr;
DWORD font_size_res = 0;
if (utils::load_resource(IDR_LUCIDE_FONT, font_data, font_size_res)) {
static constexpr ImWchar icon_ranges[] = {ICON_MIN, ICON_MAX, 0};
ImFontConfig merge_cfg;
merge_cfg.MergeMode = true;
merge_cfg.FontDataOwnedByAtlas = false;
merge_cfg.GlyphOffset.y = font_size * 0.18f;
io.Fonts->AddFontFromMemoryTTF(const_cast<void *>(font_data), static_cast<int>(font_size_res),
font_size, &merge_cfg, icon_ranges);
}
}
}
void DrawUI() {
ImGuiViewport *viewport = ImGui::GetMainViewport();
@@ -18,7 +57,7 @@ void DrawUI() {
ImGui::Begin("Main", nullptr, flags);
// ===== 顶栏:配置文件路径 =====
ImGui::Text("Config:");
ImGui::Text(ICON_SETTINGS " 配置文件:");
ImGui::SameLine();
ImGui::InputText("##config_path", g_config_path.data(), g_config_path.capacity() + 1,
ImGuiInputTextFlags_CallbackResize,
@@ -34,25 +73,32 @@ void DrawUI() {
},
&g_config_path);
ImGui::SameLine();
if (ImGui::Button("Load")) {
if (ImGui::Button(ICON_FOLDER_OPEN " 加载")) {
g_config_path_w = utils::to_wstring(g_config_path);
load_config_file(g_config_path_w);
}
ImGui::SameLine();
if (ImGui::Button("Save")) {
if (ImGui::Button(ICON_SAVE " 保存")) {
g_config_path_w = utils::to_wstring(g_config_path);
std::string err;
save_config(g_config_path_w, g_cfg, &err);
g_log_lines.emplace_back("[INFO] Config saved");
g_log_lines.emplace_back("[INFO] 配置已保存");
}
// 主题切换按钮
ImGui::SameLine();
if (ImGui::Button(is_dark_theme() ? (ICON_SUN " 亮色模式") : (ICON_MOON " 暗色模式"))) {
toggle_theme();
g_dark_theme = is_dark_theme();
}
ImGui::Separator();
// ===== 配置编辑区 =====
if (ImGui::CollapsingHeader("Configuration", ImGuiTreeNodeFlags_DefaultOpen)) {
// 3列布局: label(110) | input(自适应) | button(30)
if (ImGui::CollapsingHeader(ICON_SETTINGS " 配置", ImGuiTreeNodeFlags_DefaultOpen)) {
// 3列布局: label(130) | input(自适应) | button(30)
ImGui::Columns(3, nullptr, false);
float col_label = 110.0f;
float col_label = 130.0f;
float col_button = 30.0f;
float col_input = ImGui::GetWindowWidth() - col_label - col_button - 40.0f;
ImGui::SetColumnWidth(0, col_label);
@@ -62,15 +108,15 @@ void DrawUI() {
// ICF_PATH (file)
static char icf_path_buf[512];
strncpy_s(icf_path_buf, utils::wstr_to_str(g_cfg.icf_path).c_str(), sizeof(icf_path_buf) - 1);
ImGui::TextUnformatted("ICF Path");
ImGui::TextUnformatted(ICON_FILE " ICF 路径");
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
if (ImGui::InputText("##icf_path", icf_path_buf, sizeof(icf_path_buf)))
g_cfg.icf_path = utils::to_wstring(icf_path_buf);
ImGui::PopItemWidth();
ImGui::NextColumn();
if (ImGui::Button("...##icf_browse")) {
auto sel = open_file_dialog("ICF Files", "*.icf");
if (ImGui::Button(ICON_FOLDER_OPEN "##icf_browse")) {
auto sel = open_file_dialog("ICF 文件", "*.icf");
if (!sel.empty()) {
strncpy_s(icf_path_buf, sel.c_str(), sizeof(icf_path_buf) - 1);
g_cfg.icf_path = utils::to_wstring(sel);
@@ -81,14 +127,14 @@ void DrawUI() {
// IMAGE_DIR (folder)
static char image_dir_buf[512];
strncpy_s(image_dir_buf, utils::wstr_to_str(g_cfg.image_dir).c_str(), sizeof(image_dir_buf) - 1);
ImGui::TextUnformatted("Image Dir");
ImGui::TextUnformatted(ICON_FOLDER_OPEN " 镜像目录");
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
if (ImGui::InputText("##image_dir", image_dir_buf, sizeof(image_dir_buf)))
g_cfg.image_dir = utils::to_wstring(image_dir_buf);
ImGui::PopItemWidth();
ImGui::NextColumn();
if (ImGui::Button("...##img_browse")) {
if (ImGui::Button(ICON_FOLDER_OPEN "##img_browse")) {
auto sel = open_folder_dialog();
if (!sel.empty()) {
strncpy_s(image_dir_buf, sel.c_str(), sizeof(image_dir_buf) - 1);
@@ -100,14 +146,14 @@ void DrawUI() {
// OVERLAY_DIR (folder)
static char overlay_dir_buf[512];
strncpy_s(overlay_dir_buf, utils::wstr_to_str(g_cfg.overlay_dir).c_str(), sizeof(overlay_dir_buf) - 1);
ImGui::TextUnformatted("Overlay Dir");
ImGui::TextUnformatted(ICON_LAYERS " Overlay 目录");
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
if (ImGui::InputText("##overlay_dir", overlay_dir_buf, sizeof(overlay_dir_buf)))
g_cfg.overlay_dir = utils::to_wstring(overlay_dir_buf);
ImGui::PopItemWidth();
ImGui::NextColumn();
if (ImGui::Button("...##ovr_browse")) {
if (ImGui::Button(ICON_FOLDER_OPEN "##ovr_browse")) {
auto sel = open_folder_dialog();
if (!sel.empty()) {
strncpy_s(overlay_dir_buf, sel.c_str(), sizeof(overlay_dir_buf) - 1);
@@ -117,7 +163,7 @@ void DrawUI() {
ImGui::NextColumn();
// MOUNT_LETTER
ImGui::TextUnformatted("Mount Letter");
ImGui::TextUnformatted(ICON_HARD_DRIVE " 挂载盘符");
ImGui::NextColumn();
char letter_str[2] = {static_cast<char>(g_cfg.mount_letter), 0};
ImGui::PushItemWidth(40);
@@ -130,14 +176,14 @@ void DrawUI() {
// APP_LINK (folder)
static char app_link_buf[512];
strncpy_s(app_link_buf, utils::wstr_to_str(g_cfg.app_link).c_str(), sizeof(app_link_buf) - 1);
ImGui::TextUnformatted("App Link");
ImGui::TextUnformatted(ICON_LINK " App 链接");
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
if (ImGui::InputText("##app_link", app_link_buf, sizeof(app_link_buf)))
g_cfg.app_link = utils::to_wstring(app_link_buf);
ImGui::PopItemWidth();
ImGui::NextColumn();
if (ImGui::Button("...##app_browse")) {
if (ImGui::Button(ICON_FOLDER_OPEN "##app_browse")) {
auto sel = open_folder_dialog();
if (!sel.empty()) {
strncpy_s(app_link_buf, sel.c_str(), sizeof(app_link_buf) - 1);
@@ -149,14 +195,14 @@ void DrawUI() {
// OPT_LINK (folder)
static char opt_link_buf[512];
strncpy_s(opt_link_buf, utils::wstr_to_str(g_cfg.opt_link).c_str(), sizeof(opt_link_buf) - 1);
ImGui::TextUnformatted("Opt Link");
ImGui::TextUnformatted(ICON_LINK " Opt 链接");
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
if (ImGui::InputText("##opt_link", opt_link_buf, sizeof(opt_link_buf)))
g_cfg.opt_link = utils::to_wstring(opt_link_buf);
ImGui::PopItemWidth();
ImGui::NextColumn();
if (ImGui::Button("...##opt_browse")) {
if (ImGui::Button(ICON_FOLDER_OPEN "##opt_browse")) {
auto sel = open_folder_dialog();
if (!sel.empty()) {
strncpy_s(opt_link_buf, sel.c_str(), sizeof(opt_link_buf) - 1);
@@ -171,45 +217,45 @@ void DrawUI() {
ImGui::Separator();
// ===== 操作区 =====
ImGui::Text("Driver: ");
ImGui::Text(ICON_HARD_DRIVE " 驱动:");
ImGui::SameLine();
if (g_driver_ready) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
ImGui::Text("Ready");
ImGui::Text(ICON_CIRCLE_CHECK " 就绪");
ImGui::PopStyleColor();
} else {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
ImGui::Text("Not Running");
ImGui::Text(ICON_CIRCLE_X " 未运行");
ImGui::PopStyleColor();
}
ImGui::SameLine();
if (ImGui::Button(g_driver_ready ? "Mount" : "Mount (need driver)")) {
if (ImGui::Button(g_driver_ready ? (ICON_PLAY " 挂载") : (ICON_CIRCLE_ALERT " 挂载(需要驱动)"))) {
if (g_driver_ready && !g_mounting) {
g_mounting = true;
g_log_lines.emplace_back("[INFO] Starting mount...");
g_log_lines.emplace_back("[INFO] 开始挂载...");
auto result = icf_mount::mount_icf_apps(g_cfg);
if (result.success) {
g_log_lines.push_back(std::format("[INFO] Mount OK: {} APPs, {} OPTs",
g_log_lines.push_back(std::format("[INFO] 挂载成功:{} APP{} OPT",
result.apps.size(), result.opts.size()));
} else {
g_log_lines.push_back(std::format("[ERROR] Mount failed: {}", result.error));
g_log_lines.push_back(std::format("[ERROR] 挂载失败:{}", result.error));
}
g_mounting = false;
}
}
ImGui::SameLine();
if (ImGui::Button("Unmount")) {
if (ImGui::Button(ICON_SQUARE " 卸载")) {
if (g_driver_ready && !g_unmounting) {
g_unmounting = true;
g_log_lines.emplace_back("[INFO] Starting unmount...");
g_log_lines.emplace_back("[INFO] 开始卸载...");
auto result = icf_mount::unmount_icf_apps(g_cfg);
if (result.success) {
g_log_lines.push_back(std::format("[INFO] Unmount OK: {} APPs, {} OPTs",
g_log_lines.push_back(std::format("[INFO] 卸载成功:{} APP{} OPT",
result.apps.size(), result.opts.size()));
} else {
g_log_lines.push_back(std::format("[ERROR] Unmount failed: {}", result.error));
g_log_lines.push_back(std::format("[ERROR] 卸载失败:{}", result.error));
}
g_unmounting = false;
}
@@ -218,9 +264,9 @@ void DrawUI() {
ImGui::Separator();
// ===== 日志面板 =====
ImGui::Checkbox("Auto-scroll", &g_auto_scroll);
ImGui::Checkbox(" 自动滚动", &g_auto_scroll);
ImGui::SameLine();
if (ImGui::Button("Clear Log"))
if (ImGui::Button(ICON_TRASH " 清空日志"))
g_log_lines.clear();
ImGui::BeginChild("LogPanel", ImVec2(-1, -1), ImGuiChildFlags_Borders);
+4
View File
@@ -1,4 +1,8 @@
#pragma once
// 初始化 ImGui:主题、DPI 缩放、字体加载
// main_scale 为当前显示器 DPI 缩放比例
void InitUI(float main_scale);
// 主界面绘制
void DrawUI();
+36 -36
View File
@@ -26,7 +26,7 @@ namespace icf_mount {
// 读取ICF文件
auto icf_data = utils::read_file(cfg.icf_path);
if (icf_data.empty()) {
result.error = "Failed to read ICF file";
result.error = "读取 ICF 文件失败";
return result;
}
@@ -34,14 +34,14 @@ namespace icf_mount {
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);
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 = "No active APP entries found in ICF";
result.error = "ICF 中未找到活跃的 APP 条目";
return result;
}
@@ -52,11 +52,11 @@ namespace icf_mount {
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);
result.error = std::format("分区序号不连续:{} -> {}", 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,
result.error = std::format("分区 {} 基础版本不匹配:{} != {}", cur->part_index,
cur->base_version, prev->version);
return result;
}
@@ -81,14 +81,14 @@ namespace icf_mount {
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);
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("Mount part {} failed: {}", entry->part_index, mount_res.error);
LOG_ERROR("挂载分区 {} 失败:{}", entry->part_index, mount_res.error);
all_ok = false;
}
@@ -109,14 +109,14 @@ namespace icf_mount {
if (!vhd_exists) {
// 创建overlay.vhd差分盘
LOG_INFO("Creating overlay.vhd (parent: internal_{}.vhd)", last->part_index);
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 create failed: {}", ov_err);
LOG_ERROR("创建 overlay 失败:{}", ov_err);
all_ok = false;
}
} else {
LOG_INFO("Reusing existing overlay.vhd");
LOG_INFO("复用已有 overlay.vhd");
// 先尝试detach,处理上次未正常卸载的情况
vhd_mount::detach_vhd(overlay_vhd, nullptr);
}
@@ -126,7 +126,7 @@ namespace icf_mount {
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);
LOG_ERROR("附加 overlay 失败:{}", attach_err);
all_ok = false;
} else {
result.apps.back().vhd_success = true;
@@ -136,10 +136,10 @@ namespace icf_mount {
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: {}", drive, utils::last_error_str());
LOG_ERROR("分配盘符 {} 失败:{}", drive, utils::last_error_str());
all_ok = false;
} else {
LOG_INFO("Overlay mounted to {}:", drive);
LOG_INFO("overlay 已挂载到 {}", drive);
// 仅新建overlay.vhd时才拷贝overlay目录文件
// OVERLAY_DIR为空或不存在时跳过拷贝
@@ -153,16 +153,16 @@ namespace icf_mount {
size_t copied = 0;
if (!utils::copy_dir_overwrite(cfg.overlay_dir, mount_point,
&cp_err, &copied)) {
LOG_ERROR("Overlay copy failed: {}", cp_err);
LOG_ERROR("overlay 文件拷贝失败:{}", cp_err);
all_ok = false;
} else {
LOG_INFO("Copied {} files", copied);
LOG_INFO("已拷贝 {} 个文件", copied);
}
} else {
LOG_INFO("OVERLAY_DIR not found, skip copy");
LOG_INFO("OVERLAY_DIR 目录不存在,跳过拷贝");
}
} else {
LOG_INFO("OVERLAY_DIR empty, skip copy");
LOG_INFO("OVERLAY_DIR 为空,跳过拷贝");
}
}
}
@@ -183,14 +183,14 @@ namespace icf_mount {
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);
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("Mount OPT {} failed: {}", entry->version, mount_res.error);
LOG_ERROR("挂载 OPT {} 失败:{}", entry->version, mount_res.error);
}
result.opts.push_back(mount_entry);
@@ -203,7 +203,7 @@ namespace icf_mount {
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);
LOG_ERROR("链接 APP {} 失败", app.part_index);
}
}
}
@@ -215,14 +215,14 @@ namespace icf_mount {
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);
LOG_ERROR("链接 OPT {} 失败", opt.version);
}
}
}
result.success = all_ok;
if (!all_ok) {
result.error = "Some APPs failed to mount";
result.error = "部分 APP 挂载失败";
}
return result;
}
@@ -279,14 +279,14 @@ namespace icf_mount {
// 读取并解析ICF
auto icf_data = utils::read_file(cfg.icf_path);
if (icf_data.empty()) {
result.error = "Failed to read ICF file";
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("Parse ICF failed: {}", parse_err);
result.error = std::format("解析 ICF 失败:{}", parse_err);
return result;
}
@@ -318,21 +318,21 @@ namespace icf_mount {
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);
LOG_INFO("已移除盘符 {}", drive);
}
WIN32_FILE_ATTRIBUTE_DATA fa;
if (GetFileAttributesExW(overlay_vhd.c_str(), GetFileExInfoStandard, &fa)) {
LOG_INFO("Detaching overlay.vhd...");
LOG_INFO("正在分离 overlay.vhd...");
std::string vhd_err;
if (!vhd_mount::detach_vhd(overlay_vhd, &vhd_err)) {
LOG_ERROR("Overlay detach failed: {}", vhd_err);
LOG_ERROR("分离 overlay 失败:{}", vhd_err);
all_ok = false;
} else {
LOG_INFO("Detached overlay.vhd");
LOG_INFO("已分离 overlay.vhd");
}
} else {
LOG_INFO("overlay.vhd not found, skip detach");
LOG_INFO("overlay.vhd 不存在,跳过分离");
}
}
@@ -347,13 +347,13 @@ namespace icf_mount {
std::wstring tag = std::format(L"APP_{}", entry->part_index);
LOG_INFO("Unmount part {}: {}", entry->part_index, entry->filename);
LOG_INFO("卸载分区 {}{}", 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);
mount_entry.error = "卸载失败";
LOG_ERROR("卸载分区 {} 失败", entry->part_index);
all_ok = false;
}
@@ -371,13 +371,13 @@ namespace icf_mount {
std::wstring opt_tag = std::format(L"OPT_{}", utils::to_wstring(entry->version));
LOG_INFO("Unmount OPT {}: {}", entry->version, entry->filename);
LOG_INFO("卸载 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);
mount_entry.error = "卸载失败";
LOG_ERROR("卸载 OPT {} 失败", entry->version);
all_ok = false;
}
@@ -386,7 +386,7 @@ namespace icf_mount {
result.success = all_ok;
if (!all_ok) {
result.error = "Some containers failed to unmount";
result.error = "部分容器卸载失败";
}
return result;
}
+8 -8
View File
@@ -1,7 +1,7 @@
#include "icf_parser.h"
#include "utils/aes.h"
#include "utils/crc32.h"
#include "utils/crypto_constants.h"
#include "crypto_constants.h"
#include <algorithm>
#include <format>
@@ -69,13 +69,13 @@ namespace icf {
std::optional<std::vector<uint8_t> > decrypt(const std::vector<uint8_t> &encrypted,
std::string *error) {
if (encrypted.size() & 0xF) {
if (error) *error = "Data length not aligned to 16 bytes";
if (error) *error = "数据长度未按 16 字节对齐";
return std::nullopt;
}
std::string err;
auto result = aes::cbc_decrypt(crypto::BOOT_KEY, crypto::BOOT_IV, encrypted.data(), encrypted.size(), &err);
if (result.empty()) {
if (error) *error = std::format("Decryption failed: {}", err);
if (error) *error = std::format("解密失败:{}", err);
return std::nullopt;
}
return result;
@@ -135,7 +135,7 @@ namespace icf {
dataname = decode_version(sec->version, true);
entry.version = dataname;
if (sysver != 0) {
entry.error = "Redundant SYSTEM image";
entry.error = "重复的 SYSTEM 镜像";
}
sysver = *reinterpret_cast<const uint32_t *>(sec->platform_version);
break;
@@ -155,14 +155,14 @@ namespace icf {
for (size_t k = 0; k < app_datanames.size(); k++) {
if (app_datanames[k] == basename) {
if (app_timestamps[k] != entry.base_timestamp) {
entry.error = "Base APP timestamp mismatch";
entry.error = "基础 APP 时间戳不匹配";
}
found_base = true;
break;
}
}
if (!found_base) {
entry.error = std::format("Unable to locate base APP {}", basename);
entry.error = std::format("无法找到基础 APP {}", basename);
}
sfx = std::format("{}_{}.app", sec->part_index, basename);
} else {
@@ -170,7 +170,7 @@ namespace icf {
}
if (*reinterpret_cast<const uint32_t *>(sec->platform_version) != sysver) {
if (entry.error.empty()) entry.error = "SYSTEM version mismatch";
if (entry.error.empty()) entry.error = "SYSTEM 版本不匹配";
}
pfx = std::string(file.header.game_id, 4);
@@ -184,7 +184,7 @@ namespace icf {
break;
}
default: {
entry.error = std::format("Unknown Type: {}", sec->type);
entry.error = std::format("未知类型:{}", sec->type);
file.entries.push_back(entry);
continue;
}
+280
View File
@@ -0,0 +1,280 @@
// SEGA Mount — 统一入口(GUI + CLI
// 无参数启动 GUI;带参数(-m/-u)走 CLI 模式
#define LOG_MODULE "MAIN"
#include "gui/gui_common.h"
#include "gui/dx11_backend.h"
#include "gui/gui_utils.h"
#include "gui/ui.h"
#include "gui/theme.h"
#include "config.h"
#include "driver_service.h"
#include "icf_mount.h"
#include "utils/strings.h"
#include "utils/files.h"
#include "utils/log.h"
#include "imgui.h"
#include "imgui_impl_win32.h"
#include "imgui_impl_dx11.h"
#include <tchar.h>
#include <windows.h>
#include <format>
#include <cstdio>
#include <ios>
// ===== CLI 模式 =====
// 附着到父进程的控制台,使 stdout/stderr/stdin 可用
static void attach_to_parent_console() {
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
FILE *fp = nullptr;
freopen_s(&fp, "CONOUT$", "w", stdout);
freopen_s(&fp, "CONOUT$", "w", stderr);
freopen_s(&fp, "CONIN$", "r", stdin);
std::ios::sync_with_stdio();
}
}
static void print_usage() {
std::fputs(
"SEGA MOUNT\n"
"用法:\n"
" sega_mount -h 显示帮助\n"
" sega_mount -m [config.ini] 挂载\n"
" sega_mount -u [config.ini] 卸载\n"
" sega_mount 启动 GUI(无参数)\n",
stdout
);
std::fflush(stdout);
}
// CLI 主逻辑:解析参数并执行挂载/卸载
static int run_cli(int argc, wchar_t *argv[]) {
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
if (argc < 2) {
print_usage();
return 1;
}
// 解析命令:-h 帮助,-m 挂载,-u 卸载
std::string cmd = utils::wstr_to_str(argv[1]);
bool is_mount = cmd == "-m";
bool is_unmount = cmd == "-u";
if (cmd == "-h" || cmd == "--help") {
print_usage();
return 0;
}
if (!is_mount && !is_unmount) {
LOG_ERROR("未知命令:{}", cmd);
print_usage();
return 1;
}
// 确定config.ini路径:优先命令行参数,否则使用当前运行目录
if (argc >= 3) {
g_config_path_w = argv[2];
} else {
g_config_path_w = std::format(L"{}\\config.ini", utils::get_cwd());
}
// 解析config.ini 到全局 g_cfg
std::string cfg_err;
if (!parse_config(g_config_path_w, g_cfg, &cfg_err)) {
LOG_ERROR("{}", cfg_err);
return 1;
}
g_config_path = utils::wstr_to_str(g_config_path_w);
if (g_cfg.icf_path.empty()) {
LOG_ERROR("配置中 ICF_PATH 为空");
return 1;
}
LOG_INFO("配置已加载:{}", g_config_path);
LOG_INFO(" ICF_PATH {}", utils::wstr_to_str(g_cfg.icf_path));
LOG_INFO(" IMAGE_DIR {}", utils::wstr_to_str(g_cfg.image_dir));
LOG_INFO(" OVERLAY_DIR {}", utils::wstr_to_str(g_cfg.overlay_dir));
LOG_INFO(" MOUNT_LETTER{}", static_cast<char>(g_cfg.mount_letter));
LOG_INFO(" APP_LINK {}", utils::wstr_to_str(g_cfg.app_link));
LOG_INFO(" OPT_LINK {}", utils::wstr_to_str(g_cfg.opt_link));
LOG_INFO(" APP_KEY {}", g_cfg.app_key.empty() ? "(使用内置密钥表)" : "(自定义覆盖)");
LOG_INFO(" OPT_KEY {}", g_cfg.opt_key.empty() ? "(使用内置密钥表)" : "(自定义覆盖)");
// 确保sgfscrypt服务已安装并运行
std::string svc_err;
if (!driver::ensure_running(&svc_err)) {
LOG_ERROR("启动驱动服务失败:{}", svc_err);
LOG_ERROR("提示:请以管理员身份运行此程序");
return 1;
}
if (is_mount) {
auto result = icf_mount::mount_icf_apps(g_cfg);
if (!result.success) {
LOG_ERROR("挂载失败:{}", result.error);
return 1;
}
LOG_INFO("挂载成功:{} 个 APP{} 个 OPT",
result.apps.size(), result.opts.size());
return 0;
}
// is_unmount
auto result = icf_mount::unmount_icf_apps(g_cfg);
if (!result.success) {
LOG_ERROR("卸载失败:{}", result.error);
return 1;
}
LOG_INFO("卸载成功:{} 个 APP{} 个 OPT",
result.apps.size(), result.opts.size());
return 0;
}
// ===== GUI 模式 =====
static int run_gui(HINSTANCE hInstance) {
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
// 默认加载当前目录的 config.ini
std::wstring default_config = std::format(L"{}\\config.ini", utils::get_cwd());
load_config_file(default_config);
// 启动时自动检测驱动
{
std::string svc_err;
g_driver_ready = driver::ensure_running(&svc_err);
if (g_driver_ready)
g_log_lines.emplace_back("[INFO] 驱动服务已就绪");
else
g_log_lines.push_back(std::format("[ERROR] 驱动:{}", svc_err));
}
// 初始化 ImGui DPI 感知
ImGui_ImplWin32_EnableDpiAwareness();
float main_scale = ImGui_ImplWin32_GetDpiScaleForMonitor(
::MonitorFromPoint(POINT{0, 0}, MONITOR_DEFAULTTOPRIMARY));
// 创建 Win32 窗口
WNDCLASSEXW wc = {
sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L,
hInstance, nullptr, nullptr, nullptr, nullptr,
L"SEGA Mount", nullptr
};
::RegisterClassExW(&wc);
HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"SEGA Mount GUI",
WS_OVERLAPPEDWINDOW, 100, 100,
(int)(900 * main_scale), (int)(600 * main_scale),
nullptr, nullptr, wc.hInstance, nullptr);
// 初始化 D3D11
if (!CreateDeviceD3D(hwnd)) {
CleanupDeviceD3D();
::UnregisterClassW(wc.lpszClassName, wc.hInstance);
return 1;
}
::ShowWindow(hwnd, SW_SHOWDEFAULT);
::UpdateWindow(hwnd);
// 初始化 ImGui
IMGUI_CHECKVERSION();
ImGui::CreateContext();
InitUI(main_scale);
ImGui_ImplWin32_Init(hwnd);
ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
// 启用日志捕获
g_log_capture_enabled = true;
// 主循环
bool done = false;
while (!done) {
MSG msg;
while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
if (msg.message == WM_QUIT)
done = true;
}
if (done)
break;
if (g_SwapChainOccluded && g_pSwapChain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED) {
::Sleep(10);
continue;
}
g_SwapChainOccluded = false;
if (g_ResizeWidth != 0 && g_ResizeHeight != 0) {
CleanupRenderTarget();
g_pSwapChain->ResizeBuffers(0, g_ResizeWidth, g_ResizeHeight, DXGI_FORMAT_UNKNOWN, 0);
g_ResizeWidth = g_ResizeHeight = 0;
CreateRenderTarget();
}
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
// 窗口最小化时 DisplaySize 为 0×0,跳过渲染避免 ClipRect 断言失败
ImGuiIO &frame_io = ImGui::GetIO();
if (frame_io.DisplaySize.x <= 0.0f || frame_io.DisplaySize.y <= 0.0f) {
::Sleep(10);
continue;
}
ImGui::NewFrame();
DrawUI();
ImGui::Render();
// 根据当前主题设置背景色:Dark GRAY100=#323232, Light GRAY100=#F5F5F5
float clear_color[4];
if (is_dark_theme()) {
clear_color[0] = clear_color[1] = clear_color[2] = 0.196f; // #323232
} else {
clear_color[0] = clear_color[1] = clear_color[2] = 0.961f; // #F5F5F5
}
clear_color[3] = 1.0f;
g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color);
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
HRESULT hr = g_pSwapChain->Present(1, 0);
g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED);
}
// 清理
g_log_capture_enabled = false;
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
CleanupDeviceD3D();
::DestroyWindow(hwnd);
::UnregisterClassW(wc.lpszClassName, wc.hInstance);
return 0;
}
// ===== 统一入口 =====
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) {
// 解析命令行参数
int argc;
LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc);
// 有命令行参数时进入 CLI 模式
if (argc > 1) {
attach_to_parent_console();
int ret = run_cli(argc, argv);
LocalFree(argv);
return ret;
}
LocalFree(argv);
// 无参数时启动 GUI
return run_gui(hInstance);
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
// 资源头文件 — 定义嵌入资源的 ID
#define IDR_LUCIDE_FONT 101
#define IDR_DRIVER_SYS 102
#define IDR_DRIVER_INF 103
+54
View File
@@ -0,0 +1,54 @@
#include "resource.h"
#include "strings.h"
#include <windows.h>
namespace utils {
// 加载嵌入资源(RT_RCDATA),返回数据指针和大小
bool load_resource(int resource_id, const void *&data, DWORD &size,
std::string *error) {
HRSRC hRes = FindResourceW(nullptr, MAKEINTRESOURCEW(resource_id), MAKEINTRESOURCEW(10));
if (!hRes) {
if (error) *error = "查找资源失败:" + last_error_str();
return false;
}
HGLOBAL hMem = LoadResource(nullptr, hRes);
if (!hMem) {
if (error) *error = "加载资源失败:" + last_error_str();
return false;
}
void *ptr = LockResource(hMem);
DWORD sz = SizeofResource(nullptr, hRes);
if (!ptr || sz == 0) {
if (error) *error = "资源数据为空";
return false;
}
data = ptr;
size = sz;
return true;
}
// 将嵌入资源提取到文件(覆盖已有文件)
bool extract_resource_to_file(int resource_id, const std::wstring &dst_path,
std::string *error) {
const void *data = nullptr;
DWORD size = 0;
if (!load_resource(resource_id, data, size, error))
return false;
HANDLE hFile = CreateFileW(dst_path.c_str(), GENERIC_WRITE, 0, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE) {
if (error) *error = "创建目标文件失败:" + last_error_str();
return false;
}
DWORD written = 0;
if (!WriteFile(hFile, data, size, &written, nullptr) || written != size) {
if (error) *error = "写入文件失败:" + last_error_str();
CloseHandle(hFile);
return false;
}
CloseHandle(hFile);
return true;
}
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include <string>
#include <windows.h>
namespace utils {
// 加载嵌入资源(RT_RCDATA),返回数据指针和大小
// 资源数据由 Windows 管理,无需手动释放
// 成功返回 true,失败时填充 error
bool load_resource(int resource_id, const void *&data, DWORD &size,
std::string *error = nullptr);
// 将嵌入资源提取到文件(覆盖已有文件)
// 成功返回 true,失败时填充 error
bool extract_resource_to_file(int resource_id, const std::wstring &dst_path,
std::string *error = nullptr);
}
+7 -3
View File
@@ -68,12 +68,16 @@ namespace utils {
// 将 Windows 错误码转为可读字符串: "5: Access is denied."
std::string last_error_str(DWORD code) {
// 用 FormatMessage 取系统英文描述文本
// 用 FormatMessage 取系统描述文本(使用系统默认语言)
LPWSTR msg_buf = nullptr;
DWORD msg_len = FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, code, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
reinterpret_cast<LPWSTR>(&msg_buf), 0, nullptr);
nullptr, code,
0,
reinterpret_cast<LPWSTR>(&msg_buf),
0,
nullptr
);
std::string desc;
if (msg_len > 0 && msg_buf) {
+7 -7
View File
@@ -27,7 +27,7 @@ namespace vhd_mount {
&open_params, &hVhd);
if (err != ERROR_SUCCESS) {
if (error)
*error = std::format("OpenVirtualDisk failed: {} path={}", err, utils::wstr_to_str(vhd_path));
*error = std::format("OpenVirtualDisk 失败:{} path={}", err, utils::wstr_to_str(vhd_path));
return INVALID_HANDLE_VALUE;
}
return hVhd;
@@ -109,7 +109,7 @@ namespace vhd_mount {
ATTACH_VIRTUAL_DISK_FLAG_NO_DRIVE_LETTER,
0, &attach_params, nullptr);
if (err != ERROR_SUCCESS) {
if (error) *error = std::format("AttachVirtualDisk (RW) failed: {}", err);
if (error) *error = std::format("AttachVirtualDiskRW)失败:{}", err);
CloseHandle(hVhd);
return false;
}
@@ -128,7 +128,7 @@ namespace vhd_mount {
CloseHandle(hVhd);
if (err != ERROR_SUCCESS) {
if (error) *error = std::format("GetVirtualDiskPhysicalPath failed: {}", err);
if (error) *error = std::format("GetVirtualDiskPhysicalPath 失败:{}", err);
return false;
}
@@ -138,7 +138,7 @@ namespace vhd_mount {
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));
*error = std::format("解析物理磁盘路径失败:{}", utils::wstr_to_str(phys_path));
return false;
}
info.disk_number = disk_num;
@@ -155,7 +155,7 @@ namespace vhd_mount {
Sleep(100);
}
if (error) *error = std::format("no volume found on disk {}", disk_num);
if (error) *error = std::format("磁盘 {} 上未找到卷", disk_num);
return false;
}
@@ -167,7 +167,7 @@ namespace vhd_mount {
CloseHandle(hVhd);
if (err != ERROR_SUCCESS) {
if (error) *error = std::format("DetachVirtualDisk failed: {}", err);
if (error) *error = std::format("DetachVirtualDisk 失败:{}", err);
return false;
}
return true;
@@ -207,7 +207,7 @@ namespace vhd_mount {
&hVhd);
if (err != ERROR_SUCCESS) {
if (error)
*error = std::format("CreateVirtualDisk failed: {} path={} parent={}", err,
*error = std::format("CreateVirtualDisk 失败:{} path={} parent={}", err,
utils::wstr_to_str(vhd_path), utils::wstr_to_str(parent_vhd_path));
return false;
}