mirror of
https://gitea.tendokyu.moe/moegrid/sega-mount.git
synced 2026-10-11 16:58:08 +03:00
优化fscrypt,添加GUI目标
This commit is contained in:
+145
@@ -0,0 +1,145 @@
|
||||
# Generated from CLion Inspection settings
|
||||
---
|
||||
Checks: '-*,
|
||||
bugprone-argument-comment,
|
||||
bugprone-assert-side-effect,
|
||||
bugprone-bad-signal-to-kill-thread,
|
||||
bugprone-branch-clone,
|
||||
bugprone-copy-constructor-init,
|
||||
bugprone-dangling-handle,
|
||||
bugprone-dynamic-static-initializers,
|
||||
bugprone-fold-init-type,
|
||||
bugprone-forward-declaration-namespace,
|
||||
bugprone-forwarding-reference-overload,
|
||||
bugprone-inaccurate-erase,
|
||||
bugprone-incorrect-roundings,
|
||||
bugprone-integer-division,
|
||||
bugprone-lambda-function-name,
|
||||
bugprone-macro-parentheses,
|
||||
bugprone-macro-repeated-side-effects,
|
||||
bugprone-misplaced-operator-in-strlen-in-alloc,
|
||||
bugprone-misplaced-pointer-arithmetic-in-alloc,
|
||||
bugprone-misplaced-widening-cast,
|
||||
bugprone-move-forwarding-reference,
|
||||
bugprone-multiple-statement-macro,
|
||||
bugprone-no-escape,
|
||||
bugprone-parent-virtual-call,
|
||||
bugprone-posix-return,
|
||||
bugprone-reserved-identifier,
|
||||
bugprone-sizeof-container,
|
||||
bugprone-sizeof-expression,
|
||||
bugprone-spuriously-wake-up-functions,
|
||||
bugprone-string-constructor,
|
||||
bugprone-string-integer-assignment,
|
||||
bugprone-string-literal-with-embedded-nul,
|
||||
bugprone-suspicious-enum-usage,
|
||||
bugprone-suspicious-include,
|
||||
bugprone-suspicious-memset-usage,
|
||||
bugprone-suspicious-missing-comma,
|
||||
bugprone-suspicious-semicolon,
|
||||
bugprone-suspicious-string-compare,
|
||||
bugprone-suspicious-memory-comparison,
|
||||
bugprone-suspicious-realloc-usage,
|
||||
bugprone-swapped-arguments,
|
||||
bugprone-terminating-continue,
|
||||
bugprone-throw-keyword-missing,
|
||||
bugprone-too-small-loop-variable,
|
||||
bugprone-undefined-memory-manipulation,
|
||||
bugprone-undelegated-constructor,
|
||||
bugprone-unhandled-self-assignment,
|
||||
bugprone-unused-raii,
|
||||
bugprone-unused-return-value,
|
||||
bugprone-use-after-move,
|
||||
bugprone-virtual-near-miss,
|
||||
cert-dcl21-cpp,
|
||||
cert-dcl58-cpp,
|
||||
cert-err34-c,
|
||||
cert-err52-cpp,
|
||||
cert-err60-cpp,
|
||||
cert-flp30-c,
|
||||
cert-msc50-cpp,
|
||||
cert-msc51-cpp,
|
||||
cert-str34-c,
|
||||
cppcoreguidelines-interfaces-global-init,
|
||||
cppcoreguidelines-narrowing-conversions,
|
||||
cppcoreguidelines-pro-type-member-init,
|
||||
cppcoreguidelines-pro-type-static-cast-downcast,
|
||||
cppcoreguidelines-slicing,
|
||||
google-default-arguments,
|
||||
google-runtime-operator,
|
||||
hicpp-exception-baseclass,
|
||||
hicpp-multiway-paths-covered,
|
||||
misc-misplaced-const,
|
||||
misc-new-delete-overloads,
|
||||
misc-non-copyable-objects,
|
||||
misc-throw-by-value-catch-by-reference,
|
||||
misc-unconventional-assign-operator,
|
||||
misc-uniqueptr-reset-release,
|
||||
modernize-avoid-bind,
|
||||
modernize-concat-nested-namespaces,
|
||||
modernize-deprecated-headers,
|
||||
modernize-deprecated-ios-base-aliases,
|
||||
modernize-loop-convert,
|
||||
modernize-make-shared,
|
||||
modernize-make-unique,
|
||||
modernize-pass-by-value,
|
||||
modernize-raw-string-literal,
|
||||
modernize-redundant-void-arg,
|
||||
modernize-replace-auto-ptr,
|
||||
modernize-replace-disallow-copy-and-assign-macro,
|
||||
modernize-replace-random-shuffle,
|
||||
modernize-return-braced-init-list,
|
||||
modernize-shrink-to-fit,
|
||||
modernize-unary-static-assert,
|
||||
modernize-use-auto,
|
||||
modernize-use-bool-literals,
|
||||
modernize-use-emplace,
|
||||
modernize-use-equals-default,
|
||||
modernize-use-equals-delete,
|
||||
modernize-use-nodiscard,
|
||||
modernize-use-noexcept,
|
||||
modernize-use-nullptr,
|
||||
modernize-use-override,
|
||||
modernize-use-transparent-functors,
|
||||
modernize-use-uncaught-exceptions,
|
||||
mpi-buffer-deref,
|
||||
mpi-type-mismatch,
|
||||
openmp-use-default-none,
|
||||
performance-faster-string-find,
|
||||
performance-for-range-copy,
|
||||
performance-implicit-conversion-in-loop,
|
||||
performance-inefficient-algorithm,
|
||||
performance-inefficient-string-concatenation,
|
||||
performance-inefficient-vector-operation,
|
||||
performance-move-const-arg,
|
||||
performance-move-constructor-init,
|
||||
performance-no-automatic-move,
|
||||
performance-noexcept-move-constructor,
|
||||
performance-trivially-destructible,
|
||||
performance-type-promotion-in-math-fn,
|
||||
performance-unnecessary-copy-initialization,
|
||||
performance-unnecessary-value-param,
|
||||
portability-simd-intrinsics,
|
||||
readability-avoid-const-params-in-decls,
|
||||
readability-const-return-type,
|
||||
readability-container-size-empty,
|
||||
readability-convert-member-functions-to-static,
|
||||
readability-delete-null-pointer,
|
||||
readability-deleted-default,
|
||||
readability-inconsistent-declaration-parameter-name,
|
||||
readability-make-member-function-const,
|
||||
readability-misleading-indentation,
|
||||
readability-misplaced-array-index,
|
||||
readability-non-const-parameter,
|
||||
readability-redundant-control-flow,
|
||||
readability-redundant-declaration,
|
||||
readability-redundant-function-ptr-dereference,
|
||||
readability-redundant-smartptr-get,
|
||||
readability-redundant-string-cstr,
|
||||
readability-redundant-string-init,
|
||||
readability-simplify-subscript-expr,
|
||||
readability-static-accessed-through-instance,
|
||||
readability-static-definition-in-anonymous-namespace,
|
||||
readability-string-compare,
|
||||
readability-uniqueptr-delete-release,
|
||||
readability-use-anyofallof'
|
||||
+5
-5
@@ -47,11 +47,11 @@ sgfscrypt.sys
|
||||
sgfscrypt.inf
|
||||
|
||||
# ===== 项目私有目录/文件 =====
|
||||
driver/
|
||||
refer/
|
||||
overlay/
|
||||
check/
|
||||
note.txt
|
||||
/driver/
|
||||
/refer/
|
||||
/overlay/
|
||||
/check/
|
||||
/note.txt
|
||||
|
||||
# ===== Windows 系统文件 =====
|
||||
Thumbs.db
|
||||
|
||||
+80
-12
@@ -3,8 +3,44 @@ project(sega_mount)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
add_executable(sega_mount
|
||||
src/main.cpp
|
||||
# ===== 公共编译设置 =====
|
||||
if (WIN32)
|
||||
add_compile_options(/utf-8)
|
||||
add_compile_definitions(WINVER=0x0A00 _WIN32_WINNT=0x0A00 NTDDI_VERSION=0x0A000000)
|
||||
endif ()
|
||||
|
||||
# ===== FetchContent: ImGui (Win32 + DirectX11 后端) =====
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
imgui
|
||||
GIT_REPOSITORY https://github.com/ocornut/imgui.git
|
||||
GIT_TAG v1.92.9b
|
||||
GIT_SHALLOW TRUE
|
||||
CONFIGURE_COMMAND "" # ImGui 无 CMakeLists.txt,跳过自动配置
|
||||
BUILD_COMMAND ""
|
||||
)
|
||||
FetchContent_MakeAvailable(imgui)
|
||||
|
||||
# 手动构建 ImGui 静态库(ImGui 仓库不含 CMakeLists.txt)
|
||||
add_library(imgui STATIC
|
||||
${imgui_SOURCE_DIR}/imgui.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_draw.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_tables.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_widgets.cpp
|
||||
${imgui_SOURCE_DIR}/backends/imgui_impl_win32.cpp
|
||||
${imgui_SOURCE_DIR}/backends/imgui_impl_dx11.cpp
|
||||
)
|
||||
target_include_directories(imgui PUBLIC
|
||||
${imgui_SOURCE_DIR}
|
||||
${imgui_SOURCE_DIR}/backends
|
||||
)
|
||||
if (WIN32)
|
||||
target_link_libraries(imgui PUBLIC d3d11 d3dcompiler dxgi)
|
||||
endif ()
|
||||
|
||||
# ===== 公共源文件(cmd 和 gui 共享) =====
|
||||
set(SEGA_CORE_SOURCES
|
||||
src/config.cpp
|
||||
src/driver_service.cpp
|
||||
src/fscrypt.cpp
|
||||
src/icf_parser.cpp
|
||||
@@ -18,16 +54,48 @@ add_executable(sega_mount
|
||||
src/utils/strings.cpp
|
||||
)
|
||||
|
||||
target_include_directories(sega_mount PRIVATE src)
|
||||
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 PRIVATE bcrypt virtdisk)
|
||||
# 源文件含中文注释,指定UTF-8编码避免MSVC解析错误
|
||||
target_compile_options(sega_mount PRIVATE /utf-8)
|
||||
# 指定Windows SDK最低版本为Win10,启用virtdisk.h等结构体定义
|
||||
target_compile_definitions(sega_mount PRIVATE
|
||||
WINVER=0x0A00
|
||||
_WIN32_WINNT=0x0A00
|
||||
NTDDI_VERSION=0x0A000000
|
||||
)
|
||||
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
|
||||
src/gui/theme.cpp
|
||||
src/gui/dx11_backend.cpp
|
||||
src/gui/gui_utils.cpp
|
||||
src/gui/ui.cpp
|
||||
${SEGA_CORE_SOURCES}
|
||||
)
|
||||
target_include_directories(sega_mount_gui PRIVATE src)
|
||||
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")
|
||||
endif ()
|
||||
set_target_properties(sega_mount_gui PROPERTIES OUTPUT_NAME sega_mount_gui)
|
||||
|
||||
# ===== 单元测试: 挂载/卸载 OPT 容器 =====
|
||||
add_executable(test_mount_opt
|
||||
tests/test_mount_opt.cpp
|
||||
src/driver_service.cpp
|
||||
src/fscrypt.cpp
|
||||
src/utils/aes.cpp
|
||||
src/utils/bcrypt_util.cpp
|
||||
src/utils/files.cpp
|
||||
src/utils/log.cpp
|
||||
src/utils/strings.cpp
|
||||
)
|
||||
target_include_directories(test_mount_opt PRIVATE src)
|
||||
if (WIN32)
|
||||
target_link_libraries(test_mount_opt PRIVATE bcrypt)
|
||||
endif ()
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "config.h"
|
||||
#include "utils/strings.h"
|
||||
#include <windows.h>
|
||||
|
||||
// 全局实例定义
|
||||
Config g_cfg;
|
||||
std::string g_config_path;
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
wchar_t buf[512];
|
||||
|
||||
auto read_w = [&](const wchar_t *key) -> std::wstring {
|
||||
DWORD len = GetPrivateProfileStringW(L"CONFIG", key, L"", buf, _countof(buf), path.c_str());
|
||||
return {buf, len};
|
||||
};
|
||||
|
||||
cfg.icf_path = read_w(L"ICF_PATH");
|
||||
cfg.image_dir = read_w(L"IMAGE_DIR");
|
||||
cfg.overlay_dir = read_w(L"OVERLAY_DIR");
|
||||
|
||||
std::wstring letter = read_w(L"MOUNT_LETTER");
|
||||
if (!letter.empty()) {
|
||||
wchar_t ch = letter[0];
|
||||
if (ch >= L'a' && ch <= L'z') ch -= L'a' - L'A';
|
||||
cfg.mount_letter = ch;
|
||||
}
|
||||
|
||||
cfg.app_link = read_w(L"APP_LINK");
|
||||
cfg.opt_link = read_w(L"OPT_LINK");
|
||||
|
||||
auto read_a = [&](const wchar_t *key) -> std::string {
|
||||
DWORD len = GetPrivateProfileStringW(L"KEYS", key, L"", buf, _countof(buf), path.c_str());
|
||||
return utils::wstr_to_str(std::wstring(buf, len));
|
||||
};
|
||||
|
||||
cfg.app_key = read_a(L"APP_KEY");
|
||||
cfg.app_iv = read_a(L"APP_IV");
|
||||
cfg.opt_key = read_a(L"OPT_KEY");
|
||||
cfg.opt_iv = read_a(L"OPT_IV");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool save_config(const std::wstring &path, const Config &cfg, std::string *error) {
|
||||
auto write_w = [&](const wchar_t *key, const std::wstring &val) {
|
||||
WritePrivateProfileStringW(L"CONFIG", key, val.c_str(), path.c_str());
|
||||
};
|
||||
auto write_a = [&](const wchar_t *key, const std::string &val) {
|
||||
WritePrivateProfileStringW(L"KEYS", key, utils::to_wstring(val).c_str(), path.c_str());
|
||||
};
|
||||
|
||||
write_w(L"ICF_PATH", cfg.icf_path);
|
||||
write_w(L"IMAGE_DIR", cfg.image_dir);
|
||||
write_w(L"OVERLAY_DIR", cfg.overlay_dir);
|
||||
write_w(L"MOUNT_LETTER", std::wstring(1, cfg.mount_letter));
|
||||
write_w(L"APP_LINK", cfg.app_link);
|
||||
write_w(L"OPT_LINK", cfg.opt_link);
|
||||
write_a(L"APP_KEY", cfg.app_key);
|
||||
write_a(L"APP_IV", cfg.app_iv);
|
||||
write_a(L"OPT_KEY", cfg.opt_key);
|
||||
write_a(L"OPT_IV", cfg.opt_iv);
|
||||
return true;
|
||||
}
|
||||
@@ -16,3 +16,16 @@ struct Config {
|
||||
std::string opt_key; // OPT密钥hex
|
||||
std::string opt_iv; // OPT IV hex
|
||||
};
|
||||
|
||||
// 全局配置实例,cmd 和 gui 共用
|
||||
extern Config g_cfg;
|
||||
|
||||
// 全局配置文件路径(UTF-8 用于显示,宽字符用于 API 调用)
|
||||
extern std::string g_config_path;
|
||||
extern std::wstring g_config_path_w;
|
||||
|
||||
// 解析 config.ini 到 cfg,失败返回 false 并设置 error
|
||||
bool parse_config(const std::wstring &path, Config &cfg, std::string *error);
|
||||
|
||||
// 保存 cfg 到 config.ini
|
||||
bool save_config(const std::wstring &path, const Config &cfg, std::string *error);
|
||||
|
||||
+31
-12
@@ -11,45 +11,64 @@ 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: " + std::to_string(GetLastError());
|
||||
if (error) *error = "OpenSCManager failed: " + utils::last_error_str();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to open existing service
|
||||
SC_HANDLE hSvc = OpenServiceW(hScm, SERVICE_NAME,
|
||||
SERVICE_ALL_ACCESS);
|
||||
SC_HANDLE hSvc = OpenServiceW(hScm, SERVICE_NAME,SERVICE_ALL_ACCESS);
|
||||
|
||||
if (!hSvc) {
|
||||
DWORD last = GetLastError();
|
||||
if (last != ERROR_SERVICE_DOES_NOT_EXIST) {
|
||||
if (error) *error = "OpenService failed: " + std::to_string(last);
|
||||
if (error) *error = "OpenService failed: " + utils::last_error_str(last);
|
||||
CloseServiceHandle(hScm);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Service does not exist - create it
|
||||
std::wstring driver_path = utils::get_exe_dir() + L"\\" + DRIVER_FILENAME;
|
||||
// Service does not exist - copy driver to system directory and create service
|
||||
std::wstring src_path = utils::get_exe_dir() + L"\\" + DRIVER_FILENAME;
|
||||
|
||||
// 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();
|
||||
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();
|
||||
CloseServiceHandle(hScm);
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("Driver copied to {}", utils::wstr_to_str(dst_path));
|
||||
|
||||
hSvc = CreateServiceW(
|
||||
hScm, SERVICE_NAME, SERVICE_NAME,
|
||||
SERVICE_ALL_ACCESS,
|
||||
SERVICE_KERNEL_DRIVER,
|
||||
SERVICE_DEMAND_START,
|
||||
SERVICE_ERROR_NORMAL,
|
||||
driver_path.c_str(),
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
dst_path.c_str(),
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr
|
||||
);
|
||||
|
||||
if (!hSvc) {
|
||||
if (error) *error = "CreateService failed: " + std::to_string(GetLastError());
|
||||
if (error) *error = "CreateService failed: " + utils::last_error_str();
|
||||
CloseServiceHandle(hScm);
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("Service installed: {}", utils::wstr_to_str(driver_path));
|
||||
LOG_INFO("Service installed: {}", utils::wstr_to_str(dst_path));
|
||||
}
|
||||
|
||||
// Query current state
|
||||
SERVICE_STATUS status{};
|
||||
if (!QueryServiceStatus(hSvc, &status)) {
|
||||
if (error) *error = "QueryServiceStatus failed: " + std::to_string(GetLastError());
|
||||
if (error) *error = "QueryServiceStatus failed: " + utils::last_error_str();
|
||||
CloseServiceHandle(hSvc);
|
||||
CloseServiceHandle(hScm);
|
||||
return false;
|
||||
@@ -65,7 +84,7 @@ namespace driver {
|
||||
if (!StartServiceW(hSvc, 0, nullptr)) {
|
||||
DWORD last = GetLastError();
|
||||
if (last != ERROR_SERVICE_ALREADY_RUNNING) {
|
||||
if (error) *error = "StartService failed: " + std::to_string(last);
|
||||
if (error) *error = "StartService failed: " + utils::last_error_str(last);
|
||||
CloseServiceHandle(hSvc);
|
||||
CloseServiceHandle(hScm);
|
||||
return false;
|
||||
|
||||
+171
-119
@@ -10,8 +10,24 @@
|
||||
#include <filesystem>
|
||||
|
||||
namespace fscrypt {
|
||||
// ===== 全局密钥覆盖 =====
|
||||
// 由 set_key() 设置,空字符串表示对应类型用内置密钥表
|
||||
static std::string g_app_key_hex;
|
||||
static std::string g_app_iv_hex;
|
||||
static std::string g_opt_key_hex;
|
||||
static std::string g_opt_iv_hex;
|
||||
|
||||
// 设置全局密钥覆盖(在挂载前调用一次)
|
||||
void set_key(const std::string &app_key_hex, const std::string &app_iv_hex,
|
||||
const std::string &opt_key_hex, const std::string &opt_iv_hex) {
|
||||
g_app_key_hex = app_key_hex;
|
||||
g_app_iv_hex = app_iv_hex;
|
||||
g_opt_key_hex = opt_key_hex;
|
||||
g_opt_iv_hex = opt_iv_hex;
|
||||
}
|
||||
|
||||
// 解析解密后的BootId,提取容器类型、数据偏移、扇区大小等信息
|
||||
bool parse_boot_id(const uint8_t *decrypted_bootid, size_t size, BootIdInfo *info, std::string *error) {
|
||||
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";
|
||||
return false;
|
||||
@@ -27,14 +43,14 @@ namespace fscrypt {
|
||||
info->data_offset = read_u64_le(decrypted_bootid + 32);
|
||||
info->data_size = read_u64_le(decrypted_bootid + 40);
|
||||
info->sector_size = read_u64_le(decrypted_bootid + 48);
|
||||
info->total_sectors = info->sector_size * info->data_size;
|
||||
info->total_sectors = info->data_size * info->sector_size;
|
||||
info->encrypted_offset = (info->data_size * (info->data_offset - info->sector_size)) << 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 根据BootId容器类型识别容器名称和镜像类型(OS/APP/OPTION)
|
||||
bool recognize_container(ContainerType type, const char **container_name, const char **image_type,
|
||||
std::string *error) {
|
||||
static bool recognize_container(ContainerType type, const char **container_name, const char **image_type,
|
||||
std::string *error) {
|
||||
switch (static_cast<uint8_t>(type)) {
|
||||
case 0:
|
||||
*container_name = "NTFS Container";
|
||||
@@ -54,10 +70,10 @@ namespace fscrypt {
|
||||
}
|
||||
}
|
||||
|
||||
// 查找AES密钥和IV:优先用config.ini override,为空则查内置密钥表
|
||||
// 查找AES密钥和IV:若已通过set_key设置则用之,否则查内置密钥表
|
||||
// 规则:取文件名前4字符作为GameID,.app用AppKey/AppIV,.opt用OptKey/OptIV
|
||||
bool lookup_key_iv(const std::wstring &container_path, uint8_t out_key[16], uint8_t out_iv[16],
|
||||
std::string *error, const KeyOverride *override_keys) {
|
||||
static bool lookup_key_iv(const std::wstring &container_path, uint8_t out_key[16], uint8_t out_iv[16],
|
||||
std::string *error) {
|
||||
// 提取文件名
|
||||
std::wstring filename = container_path;
|
||||
size_t slash = filename.find_last_of(L"\\/");
|
||||
@@ -75,27 +91,23 @@ namespace fscrypt {
|
||||
}
|
||||
|
||||
// 判断扩展名:.app 或 .opt
|
||||
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;
|
||||
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";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 优先使用config.ini指定的密钥(override)
|
||||
if (override_keys) {
|
||||
const std::string &key_hex = is_app ? override_keys->app_key_hex : override_keys->opt_key_hex;
|
||||
const std::string &iv_hex = is_app ? override_keys->app_iv_hex : override_keys->opt_iv_hex;
|
||||
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 config override: {}/{}", key_hex, iv_hex);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
// 优先使用set_key设置的全局密钥
|
||||
const std::string &key_hex = is_app ? g_app_key_hex : g_opt_key_hex;
|
||||
const std::string &iv_hex = is_app ? g_app_iv_hex : g_opt_iv_hex;
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 从编译期密钥表查找GameID,直接拷贝字节数组
|
||||
@@ -115,23 +127,30 @@ namespace fscrypt {
|
||||
|
||||
|
||||
// 挂载容器:读取BootId、解密、识别类型,向驱动发送MOUNT IOCTL
|
||||
MountResult mount_container(const std::wstring &container_path, const std::wstring &tag,
|
||||
const KeyOverride *override_keys) {
|
||||
// 密钥来源:若已通过set_key设置则用之,否则查内置密钥表
|
||||
MountResult mount_container(const std::wstring &container_path, const std::wstring &tag) {
|
||||
MountResult result = {};
|
||||
|
||||
// Open Fscrypt driver
|
||||
HANDLE hDriver = CreateFileW(L"\\\\.\\Fscrypt", GENERIC_READ | GENERIC_WRITE,
|
||||
0, nullptr, OPEN_EXISTING, 0x80, nullptr);
|
||||
HANDLE hDriver = CreateFileW(
|
||||
L"\\\\.\\Fscrypt",
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
0x80,
|
||||
nullptr
|
||||
);
|
||||
if (hDriver == INVALID_HANDLE_VALUE) {
|
||||
result.error = std::format("Open Fscrypt Driver Failed: {}", GetLastError());
|
||||
result.error = std::format("Open Fscrypt Driver Failed: {}", utils::last_error_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Look up AES key and IV from embedded keys.json based on filename
|
||||
// Look up AES key and IV: use set_key override if set, else built-in table
|
||||
uint8_t key[16];
|
||||
uint8_t app_iv[16];
|
||||
std::string err;
|
||||
if (!lookup_key_iv(container_path, key, app_iv, &err, override_keys)) {
|
||||
if (!lookup_key_iv(container_path, key, app_iv, &err)) {
|
||||
result.error = std::format("Lookup key/IV failed: {}", err);
|
||||
CloseHandle(hDriver);
|
||||
return result;
|
||||
@@ -186,70 +205,74 @@ namespace fscrypt {
|
||||
LOG_INFO("{} ({})", container_name, image_type);
|
||||
fclose(container_file);
|
||||
|
||||
// Prepare mount IOCTL input buffer (total 0x280 = 640 bytes)
|
||||
// Layout from fscrypt_link.exe sub_140005620 (Buffer is wchar_t[], offsets in bytes):
|
||||
// 0x000: int32 code = 640
|
||||
// 0x004: wchar_t path[258] (NT path: \??\...)
|
||||
// 0x208: uint64_t unk_208 = 0x200000
|
||||
// 0x210: uint64_t unk_210 = 0
|
||||
// 0x218: uint64_t unk_218 = 1
|
||||
// 0x220: uint8_t key[16] (AES key from key.bin / JSON)
|
||||
// 0x230: uint8_t iv[16] (AES IV, calculated or from JSON)
|
||||
// 0x240: wchar_t tag[32]
|
||||
// ===== MOUNT IOCTL 输入缓冲区 (IOCTL 0x22E008, 0x280 = 640 bytes) =====
|
||||
// 通过IDA逆向 sgfscrypt.sys 的 fscrypt_mount_with_tag / fscrypt_init_device_extension 得到:
|
||||
//
|
||||
// 0x000: uint32 buffer_size 声明的缓冲区大小。>=0x240 不读tag(自动编号),>=0x280 读tag
|
||||
// 0x004: wchar_t path[258] 容器文件 NT 路径,如 \??\C:\xxx\SDGB.app
|
||||
// 0x208: uint64 header_size 容器头部跳过字节数。data_offset=0时 disk_size = file_size - header_size
|
||||
// 0x210: uint64 data_offset 数据区起始偏移。非0时 disk_size = data_offset,为0则自动计算
|
||||
// 0x218: uint32 encrypted 加密标志,非0则用 key/IV 初始化 AES-128-CBC 读写加解密
|
||||
// 0x21C: uint32 reserved 驱动读取但未使用,填0
|
||||
// 0x220: uint8 aes_key[16] AES-128 密钥
|
||||
// 0x230: uint8 aes_iv[16] AES-128 IV
|
||||
// 0x240: wchar_t tag[32] 设备标签,生成 \\.\FscryptDisk_<tag>
|
||||
//
|
||||
// 输出缓冲区: wchar_t tag[] — 驱动格式化后的 tag 字符串(含null终止),长度 = wcslen(tag)*2 + 2
|
||||
#pragma pack(push, 1)
|
||||
struct MountInBuffer {
|
||||
int code;
|
||||
wchar_t path[258];
|
||||
uint64_t unk_208;
|
||||
uint64_t unk_210;
|
||||
uint64_t unk_218;
|
||||
uint8_t key[16];
|
||||
uint8_t iv[16];
|
||||
wchar_t tag[32];
|
||||
uint32_t buffer_size; // 0x000
|
||||
wchar_t path[258]; // 0x004
|
||||
uint64_t header_size; // 0x208
|
||||
uint64_t data_offset; // 0x210
|
||||
uint32_t encrypted; // 0x218
|
||||
uint32_t reserved; // 0x21C
|
||||
uint8_t aes_key[16]; // 0x220
|
||||
uint8_t aes_iv[16]; // 0x230
|
||||
wchar_t tag[32]; // 0x240
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(MountInBuffer) == 0x280, "MountInBuffer must be 0x280");
|
||||
|
||||
MountInBuffer in_buf{};
|
||||
memset(&in_buf, 0, sizeof(in_buf));
|
||||
in_buf.code = 640;
|
||||
in_buf.buffer_size = sizeof(in_buf); // 0x280,走完整模式(带tag)
|
||||
|
||||
// Build NT path: \??\<normalized_path>
|
||||
std::wstring nt_path = std::format(L"\\??\\{}", utils::normalize_path(container_path));
|
||||
wcsncpy_s(in_buf.path, nt_path.c_str(), 257);
|
||||
|
||||
in_buf.unk_208 = 0x200000;
|
||||
in_buf.unk_210 = 0;
|
||||
in_buf.unk_218 = 1;
|
||||
in_buf.header_size = 0x200000; // 跳过容器头部 2MB
|
||||
in_buf.data_offset = 0; // 0 = 自动计算: disk_size = file_size - header_size
|
||||
in_buf.encrypted = 1; // 启用 AES-128-CBC 读写加解密
|
||||
|
||||
// Copy AES key and IV from JSON lookup (key at 0x220, iv at 0x230)
|
||||
memcpy(in_buf.key, key, 16);
|
||||
memcpy(in_buf.iv, app_iv, 16);
|
||||
// Copy AES key and IV (key at 0x220, iv at 0x230)
|
||||
memcpy(in_buf.aes_key, key, 16);
|
||||
memcpy(in_buf.aes_iv, app_iv, 16);
|
||||
|
||||
// Copy tag
|
||||
wcsncpy_s(in_buf.tag, tag.c_str(), 31);
|
||||
|
||||
// Send mount IOCTL
|
||||
uint8_t out_buf[1024] = {};
|
||||
// Send mount IOCTL — 驱动在输出缓冲区返回格式化的 NT 路径: \??\FscryptDisk_<tag>
|
||||
// bytes_returned = wcslen(out) * 2 + 2 (含 null 终止符)
|
||||
wchar_t out_buf[512] = {};
|
||||
DWORD bytes_returned = 0;
|
||||
if (!DeviceIoControl(hDriver, IOCTL_FSCRYPT_MOUNT,
|
||||
&in_buf, sizeof(in_buf),
|
||||
out_buf, sizeof(out_buf),
|
||||
&bytes_returned, nullptr)) {
|
||||
result.error = std::format("DeviceIoControl failed, Error = {}", GetLastError());
|
||||
result.error = std::format("DeviceIoControl failed: {}", utils::last_error_str());
|
||||
CloseHandle(hDriver);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Query symlink to verify mount
|
||||
std::wstring device_path = std::format(L"\\\\.\\FscryptDisk_{}", tag);
|
||||
HANDLE hCheck = CreateFileW(device_path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (hCheck != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(hCheck);
|
||||
// 从输出缓冲区取驱动返回的 NT 路径,验证挂载成功
|
||||
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));
|
||||
} else {
|
||||
LOG_ERROR("Query Symbol Link Failed");
|
||||
result.error = "Mount IOCTL succeeded but output buffer is empty";
|
||||
result.success = false;
|
||||
}
|
||||
|
||||
@@ -258,38 +281,53 @@ namespace fscrypt {
|
||||
}
|
||||
|
||||
// 卸载容器:向驱动发送UNMOUNT IOCTL
|
||||
// ===== UNMOUNT IOCTL 输入缓冲区 (IOCTL 0x22E00C, 0x204 = 516 bytes) =====
|
||||
// 通过IDA逆向 sgfscrypt.sys 的 fscrypt_find_device_by_tag 得到:
|
||||
// 驱动用 RtlCompareUnicodeString 将 input.path(跳过前4字节code) 与设备tag做不区分大小写比较
|
||||
// 找到后从链表摘除,调用 fscrypt_detach_device 清理:
|
||||
// FSCTL_LOCK_VOLUME(0x90018) → FSCTL_DISMOUNT_VOLUME(0x90020) → IoDeleteSymbolicLink
|
||||
//
|
||||
// 0x000: uint32 buffer_size 声明的缓冲区大小,必须 >= 8
|
||||
// 0x004: wchar_t path[258] NT路径,如 \??\FscryptDisk_APP_1\ .
|
||||
bool unmount_container(const std::wstring &tag) {
|
||||
HANDLE hDriver = CreateFileW(L"\\\\.\\Fscrypt", GENERIC_READ | GENERIC_WRITE,
|
||||
0, nullptr, OPEN_EXISTING, 0x80, nullptr);
|
||||
HANDLE hDriver = CreateFileW(
|
||||
L"\\\\.\\Fscrypt",
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
0x80,
|
||||
nullptr
|
||||
);
|
||||
if (hDriver == INVALID_HANDLE_VALUE) {
|
||||
LOG_ERROR("Open Fscrypt Driver Failed: {}", GetLastError());
|
||||
LOG_ERROR("Open Fscrypt Driver Failed: {}", utils::last_error_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
struct UnmountInBuffer {
|
||||
int code;
|
||||
wchar_t path[258];
|
||||
uint32_t buffer_size; // 0x000
|
||||
wchar_t path[258]; // 0x004
|
||||
};
|
||||
|
||||
UnmountInBuffer in_buf{};
|
||||
memset(&in_buf, 0, sizeof(in_buf));
|
||||
in_buf.code = 516; // 0x204
|
||||
in_buf.buffer_size = sizeof(in_buf); // 0x204
|
||||
|
||||
// Build path: \??\FscryptDisk_<tag> (no trailing backslash in comment)
|
||||
// Build path: \??\FscryptDisk_<tag>\ (带尾部反斜杠,驱动用此字符串做Unicode比较匹配设备tag)
|
||||
std::wstring nt_path = std::format(L"\\??\\FscryptDisk_{}\\", tag);
|
||||
wcsncpy_s(in_buf.path, nt_path.c_str(), 257);
|
||||
|
||||
uint8_t out_buf[1024] = {};
|
||||
// UNMOUNT 无输出缓冲区,驱动不返回任何数据
|
||||
DWORD bytes_returned = 0;
|
||||
if (DeviceIoControl(hDriver, IOCTL_FSCRYPT_UNMOUNT,
|
||||
&in_buf, sizeof(in_buf),
|
||||
out_buf, sizeof(out_buf),
|
||||
nullptr, 0,
|
||||
&bytes_returned, nullptr)) {
|
||||
CloseHandle(hDriver);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_ERROR("UNMOUNT failed: Error = {}", GetLastError());
|
||||
LOG_ERROR("UNMOUNT failed: {}", utils::last_error_str());
|
||||
CloseHandle(hDriver);
|
||||
return false;
|
||||
}
|
||||
@@ -298,9 +336,15 @@ namespace fscrypt {
|
||||
bool link_container(const std::wstring &tag, const std::wstring &link_path) {
|
||||
// Verify device exists: \\.\FscryptDisk_<tag>
|
||||
std::wstring device_path = std::format(L"\\\\.\\FscryptDisk_{}", tag);
|
||||
HANDLE hCheck = CreateFileW(device_path.c_str(), 0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
HANDLE hCheck = CreateFileW(
|
||||
device_path.c_str(),
|
||||
0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
nullptr
|
||||
);
|
||||
if (hCheck == INVALID_HANDLE_VALUE) {
|
||||
LOG_ERROR("LINK failed: Non-existent device");
|
||||
return false;
|
||||
@@ -315,44 +359,47 @@ namespace fscrypt {
|
||||
for (auto &c: target) if (c == L'/') c = L'\\';
|
||||
while (target.size() > 3 && target.back() == L'\\') target.pop_back();
|
||||
|
||||
// 创建重解析点
|
||||
// REPARSE_DATA_BUFFER结构(共16字节头部):
|
||||
// 0x00: ReparseTag (4字节)
|
||||
// 0x04: ReparseDataLength (2字节)
|
||||
// 0x06: Reserved (2字节)
|
||||
// 0x08: SubstituteNameOffset (2字节)
|
||||
// 0x0A: SubstituteNameLength (2字节)
|
||||
// 0x0C: PrintNameOffset (2字节)
|
||||
// 0x0E: PrintNameLength (2字节)
|
||||
// 0x10: PathBuffer (变长)
|
||||
size_t sub_len = source.size();
|
||||
size_t print_len = target.size();
|
||||
size_t buf_size = 16 + (sub_len + 1) * 2 + (print_len + 1) * 2;
|
||||
// 创建重解析点(Junction),使用本地定义的 mount point reparse 结构体
|
||||
// PathBuffer 布局: [substitute_name\0 print_name\0]
|
||||
#pragma pack(push, 1)
|
||||
struct MountPointReparseBuffer {
|
||||
USHORT SubstituteNameOffset;
|
||||
USHORT SubstituteNameLength;
|
||||
USHORT PrintNameOffset;
|
||||
USHORT PrintNameLength;
|
||||
WCHAR PathBuffer[1]; // 变长,实际大小由 buf_size 决定
|
||||
};
|
||||
struct ReparseDataBuffer {
|
||||
ULONG ReparseTag;
|
||||
USHORT ReparseDataLength;
|
||||
USHORT Reserved;
|
||||
MountPointReparseBuffer MountPointReparseBuffer;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
size_t sub_chars = source.size();
|
||||
size_t print_chars = target.size();
|
||||
size_t header_size = offsetof(ReparseDataBuffer, MountPointReparseBuffer.PathBuffer);
|
||||
size_t buf_size = header_size + (sub_chars + 1 + print_chars + 1) * sizeof(WCHAR);
|
||||
|
||||
std::vector<uint8_t> reparse_buf(buf_size, 0);
|
||||
// ReparseTag
|
||||
*reinterpret_cast<DWORD *>(reparse_buf.data()) = IO_REPARSE_TAG_MOUNT_POINT;
|
||||
// ReparseDataLength
|
||||
*reinterpret_cast<WORD *>(reparse_buf.data() + 4) = static_cast<WORD>(buf_size - 8);
|
||||
// Reserved
|
||||
*reinterpret_cast<WORD *>(reparse_buf.data() + 6) = 0;
|
||||
// SubstituteNameOffset
|
||||
*reinterpret_cast<WORD *>(reparse_buf.data() + 8) = 0;
|
||||
// SubstituteNameLength
|
||||
*reinterpret_cast<WORD *>(reparse_buf.data() + 10) = static_cast<WORD>(sub_len * 2);
|
||||
// PrintNameOffset
|
||||
*reinterpret_cast<WORD *>(reparse_buf.data() + 12) = static_cast<WORD>((sub_len + 1) * 2);
|
||||
// PrintNameLength
|
||||
*reinterpret_cast<WORD *>(reparse_buf.data() + 14) = static_cast<WORD>(print_len * 2);
|
||||
auto *rdb = reinterpret_cast<ReparseDataBuffer *>(reparse_buf.data());
|
||||
|
||||
// Copy substitute name (source)
|
||||
memcpy(reparse_buf.data() + 16, source.c_str(), sub_len * 2);
|
||||
// null terminator
|
||||
*reinterpret_cast<WCHAR *>(reparse_buf.data() + 16 + sub_len * 2) = 0;
|
||||
// Copy print name (target)
|
||||
memcpy(reparse_buf.data() + 16 + (sub_len + 1) * 2, target.c_str(), print_len * 2);
|
||||
// null terminator
|
||||
*reinterpret_cast<WCHAR *>(reparse_buf.data() + 16 + (sub_len + 1) * 2 + print_len * 2) = 0;
|
||||
rdb->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
|
||||
rdb->ReparseDataLength = static_cast<USHORT>(buf_size - 8); // 减去 8 字节头部(Tag+DataLength+Reserved)
|
||||
rdb->Reserved = 0;
|
||||
|
||||
auto &mp = rdb->MountPointReparseBuffer;
|
||||
mp.SubstituteNameOffset = 0;
|
||||
mp.SubstituteNameLength = static_cast<USHORT>(sub_chars * sizeof(WCHAR));
|
||||
mp.PrintNameOffset = static_cast<USHORT>((sub_chars + 1) * sizeof(WCHAR));
|
||||
mp.PrintNameLength = static_cast<USHORT>(print_chars * sizeof(WCHAR));
|
||||
|
||||
// Copy substitute name (source) + null, print name (target) + null
|
||||
memcpy(mp.PathBuffer, source.c_str(), sub_chars * sizeof(WCHAR));
|
||||
mp.PathBuffer[sub_chars] = 0;
|
||||
memcpy(mp.PathBuffer + sub_chars + 1, target.c_str(), print_chars * sizeof(WCHAR));
|
||||
mp.PathBuffer[sub_chars + 1 + print_chars] = 0;
|
||||
|
||||
// 递归创建目录及其父目录(CreateDirectoryW无法递归创建父目录)
|
||||
namespace fs = std::filesystem;
|
||||
@@ -365,12 +412,17 @@ namespace fscrypt {
|
||||
}
|
||||
|
||||
// Open the directory
|
||||
HANDLE hDir = CreateFileW(link_path.c_str(), GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
HANDLE hDir = CreateFileW(
|
||||
link_path.c_str(),
|
||||
GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
|
||||
nullptr
|
||||
);
|
||||
if (hDir == INVALID_HANDLE_VALUE) {
|
||||
LOG_ERROR("CreateFileW failed for {}: {}", utils::wstr_to_str(link_path), GetLastError());
|
||||
LOG_ERROR("CreateFileW failed for {}: {}", utils::wstr_to_str(link_path), utils::last_error_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -379,7 +431,7 @@ namespace fscrypt {
|
||||
reparse_buf.data(), static_cast<DWORD>(buf_size),
|
||||
nullptr, 0, &bytes_returned, nullptr);
|
||||
if (!ok) {
|
||||
LOG_ERROR("FSCTL_SET_REPARSE_POINT failed: {}", GetLastError());
|
||||
LOG_ERROR("FSCTL_SET_REPARSE_POINT failed: {}", utils::last_error_str());
|
||||
}
|
||||
CloseHandle(hDir);
|
||||
|
||||
@@ -403,7 +455,7 @@ namespace fscrypt {
|
||||
|
||||
// mount point重解析点可直接用RemoveDirectoryW移除(会删除junction而非目标内容)
|
||||
if (!RemoveDirectoryW(path.c_str())) {
|
||||
LOG_ERROR("RemoveDirectory failed for {}: {}", utils::wstr_to_str(path), GetLastError());
|
||||
LOG_ERROR("RemoveDirectory failed for {}: {}", utils::wstr_to_str(path), utils::last_error_str());
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("Removed link {}", utils::wstr_to_str(path));
|
||||
|
||||
+24
-18
@@ -9,8 +9,24 @@ namespace fscrypt {
|
||||
|
||||
// AES密钥和IV已统一至 utils/crypto_constants.h 的 crypto::BOOT_KEY / BOOT_IV
|
||||
|
||||
// IOCTL码(FSCTL_SET_REPARSE_POINT和IO_REPARSE_TAG_MOUNT_POINT
|
||||
// 已在winioctl.h/winnt.h中定义)
|
||||
// ===== sgfscrypt.sys 驱动 IOCTL 码 (通过IDA逆向分析得到) =====
|
||||
//
|
||||
// 控制设备 \\.\Fscrypt — 容器挂载/卸载管理:
|
||||
// 0x226040 Ping/IsAlive 检测驱动是否运行,输出 0x103
|
||||
// 0x22E000 MOUNT(带盘符) 输入含盘符字符,创建带盘符的磁盘设备
|
||||
// 0x22E004 UNMOUNT(按盘符) 输入8字节,按盘符查找并卸载
|
||||
// 0x22E008 MOUNT(带Tag) 输入含路径/密钥/IV/Tag,创建无盘符磁盘设备 ★本项目使用
|
||||
// 0x22E00C UNMOUNT(按Tag) 输入含NT路径,按Tag字符串匹配卸载 ★本项目使用
|
||||
//
|
||||
// 磁盘设备 \\.\FscryptDisk_<tag> — 响应Windows标准磁盘IOCTL (驱动透明处理):
|
||||
// 0x70000 GET_DRIVE_GEOMETRY 0x7405C GET_LENGTH_INFO
|
||||
// 0x70024 IS_WRITABLE 0x74800 CHECK_VERIFY
|
||||
// 0x2D0800 STORAGE_CHECK_VERIFY2 读写数据时驱动自动做 AES-128-CBC 加解密
|
||||
//
|
||||
// (FSCTL_SET_REPARSE_POINT 和 IO_REPARSE_TAG_MOUNT_POINT 已在 winioctl.h/winnt.h 中定义)
|
||||
constexpr DWORD IOCTL_FSCRYPT_PING = 0x226040;
|
||||
constexpr DWORD IOCTL_FSCRYPT_MOUNT_DRIVE = 0x22E000;
|
||||
constexpr DWORD IOCTL_FSCRYPT_UNMOUNT_DRIVE = 0x22E004;
|
||||
constexpr DWORD IOCTL_FSCRYPT_MOUNT = 0x22E008;
|
||||
constexpr DWORD IOCTL_FSCRYPT_UNMOUNT = 0x22E00C;
|
||||
|
||||
@@ -51,25 +67,15 @@ namespace fscrypt {
|
||||
const char **image_type,
|
||||
std::string *error = nullptr);
|
||||
|
||||
// 密钥覆盖配置(从config.ini读取,hex字符串,空表示用内置密钥表)
|
||||
struct KeyOverride {
|
||||
std::string app_key_hex;
|
||||
std::string app_iv_hex;
|
||||
std::string opt_key_hex;
|
||||
std::string opt_iv_hex;
|
||||
};
|
||||
|
||||
// 查找AES密钥和IV:优先用override,为空则查内置密钥表
|
||||
// 规则:取文件名前4字符作为GameID,.app用AppKey/AppIV,.opt用OptKey/OptIV
|
||||
bool lookup_key_iv(const std::wstring &container_path,
|
||||
uint8_t out_key[16], uint8_t out_iv[16],
|
||||
std::string *error = nullptr,
|
||||
const KeyOverride *override_keys = nullptr);
|
||||
// 设置全局密钥覆盖(hex字符串,空则对应类型用内置密钥表)
|
||||
// 应在mount_container之前调用一次,之后所有挂载均使用此密钥
|
||||
void set_key(const std::string &app_key_hex, const std::string &app_iv_hex,
|
||||
const std::string &opt_key_hex, const std::string &opt_iv_hex);
|
||||
|
||||
// 挂载容器:读取BootId、解密、识别类型,向驱动发送MOUNT IOCTL
|
||||
// 密钥来源:若已通过set_key设置则用之,否则查内置密钥表
|
||||
MountResult mount_container(const std::wstring &container_path,
|
||||
const std::wstring &tag,
|
||||
const KeyOverride *override_keys = nullptr);
|
||||
const std::wstring &tag);
|
||||
|
||||
// 卸载容器:向驱动发送UNMOUNT IOCTL
|
||||
bool unmount_container(const std::wstring &tag);
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "dx11_backend.h"
|
||||
#include "gui_common.h"
|
||||
#include "imgui.h"
|
||||
|
||||
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
bool CreateDeviceD3D(HWND hWnd) {
|
||||
DXGI_SWAP_CHAIN_DESC sd;
|
||||
ZeroMemory(&sd, sizeof(sd));
|
||||
sd.BufferCount = 2;
|
||||
sd.BufferDesc.Width = 0;
|
||||
sd.BufferDesc.Height = 0;
|
||||
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
sd.BufferDesc.RefreshRate.Numerator = 60;
|
||||
sd.BufferDesc.RefreshRate.Denominator = 1;
|
||||
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
||||
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
sd.OutputWindow = hWnd;
|
||||
sd.SampleDesc.Count = 1;
|
||||
sd.SampleDesc.Quality = 0;
|
||||
sd.Windowed = TRUE;
|
||||
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
|
||||
|
||||
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);
|
||||
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);
|
||||
if (res != S_OK)
|
||||
return false;
|
||||
|
||||
CreateRenderTarget();
|
||||
return true;
|
||||
}
|
||||
|
||||
void CleanupDeviceD3D() {
|
||||
CleanupRenderTarget();
|
||||
if (g_pSwapChain) {
|
||||
g_pSwapChain->Release();
|
||||
g_pSwapChain = nullptr;
|
||||
}
|
||||
if (g_pd3dDeviceContext) {
|
||||
g_pd3dDeviceContext->Release();
|
||||
g_pd3dDeviceContext = nullptr;
|
||||
}
|
||||
if (g_pd3dDevice) {
|
||||
g_pd3dDevice->Release();
|
||||
g_pd3dDevice = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateRenderTarget() {
|
||||
ID3D11Texture2D *pBackBuffer;
|
||||
g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
|
||||
g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &g_mainRenderTargetView);
|
||||
pBackBuffer->Release();
|
||||
}
|
||||
|
||||
void CleanupRenderTarget() {
|
||||
if (g_mainRenderTargetView) {
|
||||
g_mainRenderTargetView->Release();
|
||||
g_mainRenderTargetView = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
|
||||
return true;
|
||||
|
||||
switch (msg) {
|
||||
case WM_SIZE:
|
||||
if (wParam == SIZE_MINIMIZED)
|
||||
return 0;
|
||||
g_ResizeWidth = (UINT) LOWORD(lParam);
|
||||
g_ResizeHeight = (UINT) HIWORD(lParam);
|
||||
return 0;
|
||||
case WM_SYSCOMMAND:
|
||||
if ((wParam & 0xfff0) == SC_KEYMENU)
|
||||
return 0;
|
||||
break;
|
||||
case WM_DESTROY:
|
||||
::PostQuitMessage(0);
|
||||
return 0;
|
||||
default: ;
|
||||
}
|
||||
return ::DefWindowProcW(hWnd, msg, wParam, lParam);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include <d3d11.h>
|
||||
#include <windows.h>
|
||||
|
||||
// DX11 + Win32 后端初始化与清理
|
||||
bool CreateDeviceD3D(HWND hWnd);
|
||||
|
||||
void CleanupDeviceD3D();
|
||||
|
||||
void CreateRenderTarget();
|
||||
|
||||
void CleanupRenderTarget();
|
||||
|
||||
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <d3d11.h>
|
||||
|
||||
// ===== 操作状态 =====
|
||||
inline bool g_mounting = false;
|
||||
inline bool g_unmounting = false;
|
||||
|
||||
// ===== 日志捕获 =====
|
||||
inline std::vector<std::string> g_log_lines;
|
||||
inline std::mutex g_log_mutex;
|
||||
inline bool g_auto_scroll = true;
|
||||
inline bool g_log_capture_enabled = false;
|
||||
|
||||
// ===== DX11 全局变量 =====
|
||||
inline ID3D11Device *g_pd3dDevice = nullptr;
|
||||
inline ID3D11DeviceContext *g_pd3dDeviceContext = nullptr;
|
||||
inline IDXGISwapChain *g_pSwapChain = nullptr;
|
||||
inline bool g_SwapChainOccluded = false;
|
||||
inline UINT g_ResizeWidth = 0, g_ResizeHeight = 0;
|
||||
inline ID3D11RenderTargetView *g_mainRenderTargetView = nullptr;
|
||||
|
||||
// ===== 驱动状态 =====
|
||||
inline bool g_driver_ready = false;
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "gui_utils.h"
|
||||
#include "gui_common.h"
|
||||
#include "config.h"
|
||||
#include "utils/strings.h"
|
||||
#include <windows.h>
|
||||
#include <commdlg.h>
|
||||
#include <shlobj.h>
|
||||
#include <format>
|
||||
|
||||
void load_config_file(const std::wstring &path) {
|
||||
g_config_path_w = 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));
|
||||
} else {
|
||||
g_log_lines.push_back(std::format("[ERROR] {}", err));
|
||||
}
|
||||
}
|
||||
|
||||
std::string open_file_dialog(const char *filter_desc, const char *filter_ext) {
|
||||
wchar_t buf[MAX_PATH] = {};
|
||||
OPENFILENAMEW ofn = {};
|
||||
ofn.lStructSize = sizeof(ofn);
|
||||
ofn.lpstrFile = buf;
|
||||
ofn.nMaxFile = MAX_PATH;
|
||||
ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
|
||||
|
||||
std::wstring wfilter = utils::to_wstring(std::string(filter_desc) + '\0' + filter_ext + '\0');
|
||||
ofn.lpstrFilter = wfilter.c_str();
|
||||
|
||||
if (GetOpenFileNameW(&ofn))
|
||||
return utils::wstr_to_str(buf);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string open_folder_dialog() {
|
||||
BROWSEINFOW bi = {};
|
||||
bi.hwndOwner = nullptr;
|
||||
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
|
||||
LPITEMIDLIST pidl = SHBrowseForFolderW(&bi);
|
||||
if (!pidl) return {};
|
||||
|
||||
wchar_t buf[MAX_PATH] = {};
|
||||
if (!SHGetPathFromIDListW(pidl, buf)) return {};
|
||||
CoTaskMemFree(pidl);
|
||||
return utils::wstr_to_str(buf);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
// 加载配置文件并更新全局 g_cfg / g_config_path
|
||||
void load_config_file(const std::wstring &path);
|
||||
|
||||
// 打开文件选择对话框,返回 UTF-8 路径,取消则返回空
|
||||
std::string open_file_dialog(const char *filter_desc, const char *filter_ext);
|
||||
|
||||
// 打开目录选择对话框,返回 UTF-8 路径,取消则返回空
|
||||
std::string open_folder_dialog();
|
||||
@@ -0,0 +1,148 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "theme.h"
|
||||
#include "imgui.h"
|
||||
|
||||
// 从 CinderImGui::Options::darkTheme() 提取,适配原生 ImGui
|
||||
// ModalWindowDarkening → ModalWindowDimBg (ImGui 1.73+ 重命名)
|
||||
void apply_dark_theme() {
|
||||
ImGuiStyle &style = ImGui::GetStyle();
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
// 应用 CinderImGui 风格的暗色主题(红色强调)
|
||||
void apply_dark_theme();
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
#include "ui.h"
|
||||
#include "gui_common.h"
|
||||
#include "gui_utils.h"
|
||||
#include "icf_mount.h"
|
||||
#include "utils/strings.h"
|
||||
#include "imgui.h"
|
||||
#include <format>
|
||||
|
||||
void DrawUI() {
|
||||
ImGuiViewport *viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(viewport->WorkPos);
|
||||
ImGui::SetNextWindowSize(viewport->WorkSize);
|
||||
|
||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus;
|
||||
|
||||
ImGui::Begin("Main", nullptr, flags);
|
||||
|
||||
// ===== 顶栏:配置文件路径 =====
|
||||
ImGui::Text("Config:");
|
||||
ImGui::SameLine();
|
||||
ImGui::InputText("##config_path", g_config_path.data(), g_config_path.capacity() + 1,
|
||||
ImGuiInputTextFlags_CallbackResize,
|
||||
[](ImGuiInputTextCallbackData *data) -> int {
|
||||
auto *s = static_cast<std::string *>(data->UserData);
|
||||
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) {
|
||||
s->resize(data->BufTextLen);
|
||||
data->Buf = s->data();
|
||||
data->BufSize = static_cast<int>(s->capacity() + 1);
|
||||
data->BufDirty = true;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
&g_config_path);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Load")) {
|
||||
g_config_path_w = utils::to_wstring(g_config_path);
|
||||
load_config_file(g_config_path_w);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("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");
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// ===== 配置编辑区 =====
|
||||
if (ImGui::CollapsingHeader("Configuration", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
// 3列布局: label(110) | input(自适应) | button(30)
|
||||
ImGui::Columns(3, nullptr, false);
|
||||
float col_label = 110.0f;
|
||||
float col_button = 30.0f;
|
||||
float col_input = ImGui::GetWindowWidth() - col_label - col_button - 40.0f;
|
||||
ImGui::SetColumnWidth(0, col_label);
|
||||
ImGui::SetColumnWidth(1, col_input);
|
||||
ImGui::SetColumnWidth(2, col_button);
|
||||
|
||||
// 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::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 (!sel.empty()) {
|
||||
strncpy_s(icf_path_buf, sel.c_str(), sizeof(icf_path_buf) - 1);
|
||||
g_cfg.icf_path = utils::to_wstring(sel);
|
||||
}
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
|
||||
// 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::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")) {
|
||||
auto sel = open_folder_dialog();
|
||||
if (!sel.empty()) {
|
||||
strncpy_s(image_dir_buf, sel.c_str(), sizeof(image_dir_buf) - 1);
|
||||
g_cfg.image_dir = utils::to_wstring(sel);
|
||||
}
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
|
||||
// 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::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")) {
|
||||
auto sel = open_folder_dialog();
|
||||
if (!sel.empty()) {
|
||||
strncpy_s(overlay_dir_buf, sel.c_str(), sizeof(overlay_dir_buf) - 1);
|
||||
g_cfg.overlay_dir = utils::to_wstring(sel);
|
||||
}
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
|
||||
// MOUNT_LETTER
|
||||
ImGui::TextUnformatted("Mount Letter");
|
||||
ImGui::NextColumn();
|
||||
char letter_str[2] = {static_cast<char>(g_cfg.mount_letter), 0};
|
||||
ImGui::PushItemWidth(40);
|
||||
if (ImGui::InputText("##mount_letter", letter_str, 2))
|
||||
if (letter_str[0]) g_cfg.mount_letter = static_cast<wchar_t>(letter_str[0]);
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::NextColumn();
|
||||
ImGui::NextColumn(); // no browse button for letter
|
||||
|
||||
// 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::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")) {
|
||||
auto sel = open_folder_dialog();
|
||||
if (!sel.empty()) {
|
||||
strncpy_s(app_link_buf, sel.c_str(), sizeof(app_link_buf) - 1);
|
||||
g_cfg.app_link = utils::to_wstring(sel);
|
||||
}
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
|
||||
// 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::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")) {
|
||||
auto sel = open_folder_dialog();
|
||||
if (!sel.empty()) {
|
||||
strncpy_s(opt_link_buf, sel.c_str(), sizeof(opt_link_buf) - 1);
|
||||
g_cfg.opt_link = utils::to_wstring(sel);
|
||||
}
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
|
||||
ImGui::Columns(1);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// ===== 操作区 =====
|
||||
ImGui::Text("Driver: ");
|
||||
ImGui::SameLine();
|
||||
if (g_driver_ready) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
|
||||
ImGui::Text("Ready");
|
||||
ImGui::PopStyleColor();
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
|
||||
ImGui::Text("Not Running");
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(g_driver_ready ? "Mount" : "Mount (need driver)")) {
|
||||
if (g_driver_ready && !g_mounting) {
|
||||
g_mounting = true;
|
||||
g_log_lines.emplace_back("[INFO] Starting mount...");
|
||||
auto result = icf_mount::mount_icf_apps(g_cfg);
|
||||
if (result.success) {
|
||||
g_log_lines.push_back(std::format("[INFO] Mount OK: {} APPs, {} OPTs",
|
||||
result.apps.size(), result.opts.size()));
|
||||
} else {
|
||||
g_log_lines.push_back(std::format("[ERROR] Mount failed: {}", result.error));
|
||||
}
|
||||
g_mounting = false;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Unmount")) {
|
||||
if (g_driver_ready && !g_unmounting) {
|
||||
g_unmounting = true;
|
||||
g_log_lines.emplace_back("[INFO] Starting unmount...");
|
||||
auto result = icf_mount::unmount_icf_apps(g_cfg);
|
||||
if (result.success) {
|
||||
g_log_lines.push_back(std::format("[INFO] Unmount OK: {} APPs, {} OPTs",
|
||||
result.apps.size(), result.opts.size()));
|
||||
} else {
|
||||
g_log_lines.push_back(std::format("[ERROR] Unmount failed: {}", result.error));
|
||||
}
|
||||
g_unmounting = false;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// ===== 日志面板 =====
|
||||
ImGui::Checkbox("Auto-scroll", &g_auto_scroll);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear Log"))
|
||||
g_log_lines.clear();
|
||||
|
||||
ImGui::BeginChild("LogPanel", ImVec2(-1, -1), ImGuiChildFlags_Borders);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_log_mutex);
|
||||
for (const auto &line: g_log_lines) {
|
||||
if (line.find("[ERROR]") != std::string::npos)
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
|
||||
else if (line.find("[WARN]") != std::string::npos)
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.2f, 1.0f));
|
||||
else
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
|
||||
ImGui::TextUnformatted(line.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
if (g_auto_scroll && !g_log_lines.empty())
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
// 主界面绘制
|
||||
void DrawUI();
|
||||
+5
-9
@@ -65,12 +65,8 @@ namespace icf_mount {
|
||||
// APP/OPT文件所在目录:优先使用image_dir,否则使用当前目录
|
||||
std::wstring dir = cfg.image_dir.empty() ? utils::get_cwd() : 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;
|
||||
// 设置全局密钥覆盖(config.ini [KEYS] section,空则用内置密钥表)
|
||||
fscrypt::set_key(cfg.app_key, cfg.app_iv, cfg.opt_key, cfg.opt_iv);
|
||||
|
||||
// 依次挂载每个APP容器
|
||||
bool all_ok = true;
|
||||
@@ -87,7 +83,7 @@ namespace icf_mount {
|
||||
|
||||
LOG_INFO("Mount part {}: {}", entry->part_index, entry->filename);
|
||||
|
||||
auto mount_res = fscrypt::mount_container(app_path, tag, &key_override);
|
||||
auto mount_res = fscrypt::mount_container(app_path, tag);
|
||||
mount_entry.success = mount_res.success;
|
||||
mount_entry.error = mount_res.error;
|
||||
|
||||
@@ -140,7 +136,7 @@ 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: Error = {}", drive, GetLastError());
|
||||
LOG_ERROR("Assign drive {} failed: {}", drive, utils::last_error_str());
|
||||
all_ok = false;
|
||||
} else {
|
||||
LOG_INFO("Overlay mounted to {}:", drive);
|
||||
@@ -189,7 +185,7 @@ namespace icf_mount {
|
||||
|
||||
LOG_INFO("Mount OPT {}: {}", entry->version, entry->filename);
|
||||
|
||||
auto mount_res = fscrypt::mount_container(opt_path, opt_tag, &key_override);
|
||||
auto mount_res = fscrypt::mount_container(opt_path, opt_tag);
|
||||
mount_entry.success = mount_res.success;
|
||||
mount_entry.error = mount_res.error;
|
||||
|
||||
|
||||
-144
@@ -1,144 +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>
|
||||
|
||||
// 解析config.ini,使用Windows系统API GetPrivateProfileStringW
|
||||
static bool parse_config(const std::wstring &path, Config &cfg, std::string *error) {
|
||||
if (GetFileAttributesW(path.c_str()) == INVALID_FILE_ATTRIBUTES) {
|
||||
*error = "Config file not found: " + utils::wstr_to_str(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
wchar_t buf[512];
|
||||
|
||||
// [CONFIG] section
|
||||
auto read_w = [&](const wchar_t *key) -> std::wstring {
|
||||
DWORD len = GetPrivateProfileStringW(L"CONFIG", key, L"", buf, _countof(buf), path.c_str());
|
||||
return {buf, len};
|
||||
};
|
||||
|
||||
cfg.icf_path = read_w(L"ICF_PATH");
|
||||
cfg.image_dir = read_w(L"IMAGE_DIR");
|
||||
cfg.overlay_dir = read_w(L"OVERLAY_DIR");
|
||||
|
||||
std::wstring letter = read_w(L"MOUNT_LETTER");
|
||||
if (!letter.empty()) {
|
||||
wchar_t ch = letter[0];
|
||||
if (ch >= L'a' && ch <= L'z') ch -= L'a' - L'A';
|
||||
cfg.mount_letter = ch;
|
||||
}
|
||||
|
||||
cfg.app_link = read_w(L"APP_LINK");
|
||||
cfg.opt_link = read_w(L"OPT_LINK");
|
||||
|
||||
// [KEYS] section
|
||||
auto read_a = [&](const wchar_t *key) -> std::string {
|
||||
DWORD len = GetPrivateProfileStringW(L"KEYS", key, L"", buf, _countof(buf), path.c_str());
|
||||
return utils::wstr_to_str(std::wstring(buf, len));
|
||||
};
|
||||
|
||||
cfg.app_key = read_a(L"APP_KEY");
|
||||
cfg.app_iv = read_a(L"APP_IV");
|
||||
cfg.opt_key = read_a(L"OPT_KEY");
|
||||
cfg.opt_iv = read_a(L"OPT_IV");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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路径:优先命令行参数,否则使用当前运行目录
|
||||
std::wstring config_path;
|
||||
if (argc >= 3) {
|
||||
config_path = utils::to_wstring(argv[2]);
|
||||
} else {
|
||||
config_path = std::format(L"{}\\config.ini", utils::get_cwd());
|
||||
}
|
||||
|
||||
// 解析config.ini
|
||||
Config cfg;
|
||||
std::string cfg_err;
|
||||
if (!parse_config(config_path, cfg, &cfg_err)) {
|
||||
LOG_ERROR("{}", cfg_err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cfg.icf_path.empty()) {
|
||||
LOG_ERROR("ICF_PATH is empty in config");
|
||||
return 1;
|
||||
}
|
||||
|
||||
LOG_INFO("Config loaded from {}", utils::wstr_to_str(config_path));
|
||||
LOG_INFO(" ICF_PATH: {}", utils::wstr_to_str(cfg.icf_path));
|
||||
LOG_INFO(" IMAGE_DIR: {}", utils::wstr_to_str(cfg.image_dir));
|
||||
LOG_INFO(" OVERLAY_DIR: {}", utils::wstr_to_str(cfg.overlay_dir));
|
||||
LOG_INFO(" MOUNT_LETTER: {}", static_cast<char>(cfg.mount_letter));
|
||||
LOG_INFO(" APP_LINK: {}", utils::wstr_to_str(cfg.app_link));
|
||||
LOG_INFO(" OPT_LINK: {}", utils::wstr_to_str(cfg.opt_link));
|
||||
LOG_INFO(" APP_KEY: {}", cfg.app_key.empty() ? "(use built-in table)" : "(override)");
|
||||
LOG_INFO(" OPT_KEY: {}", 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(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(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;
|
||||
}
|
||||
@@ -65,4 +65,51 @@ namespace utils {
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// 将 Windows 错误码转为可读字符串: "5: Access is denied."
|
||||
std::string last_error_str(DWORD code) {
|
||||
// 用 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);
|
||||
|
||||
std::string desc;
|
||||
if (msg_len > 0 && msg_buf) {
|
||||
// 直接用 WideCharToMultiByte 转 UTF-8
|
||||
int utf8_len = WideCharToMultiByte(
|
||||
CP_UTF8,
|
||||
0,
|
||||
msg_buf,
|
||||
static_cast<int>(msg_len),
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr
|
||||
);
|
||||
if (utf8_len > 0) {
|
||||
desc.resize(utf8_len);
|
||||
WideCharToMultiByte(
|
||||
CP_UTF8,
|
||||
0,
|
||||
msg_buf,
|
||||
static_cast<int>(msg_len),
|
||||
desc.data(),
|
||||
utf8_len,
|
||||
nullptr,
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
// 去掉末尾的换行
|
||||
while (!desc.empty() && (desc.back() == '\r' || desc.back() == '\n' || desc.back() == ' '))
|
||||
desc.pop_back();
|
||||
}
|
||||
LocalFree(msg_buf);
|
||||
|
||||
// 组合: "5: Access is denied."
|
||||
if (!desc.empty())
|
||||
return std::format("{}: {}", code, desc);
|
||||
return std::format("{}", code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,8 @@ namespace utils {
|
||||
|
||||
// 将字节数组格式化为十六进制小写字符串
|
||||
std::string bytes_to_hex(const uint8_t *data, size_t len);
|
||||
|
||||
// 将 Windows GetLastError() 的错误码转为可读字符串
|
||||
// 格式: "5 (ERROR_ACCESS_DENIED)" — 数字 + 符号名
|
||||
std::string last_error_str(DWORD code = GetLastError());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
// 单元测试:挂载/卸载/链接/取消链接 OPT 容器
|
||||
// 需要管理员权限运行(驱动安装+挂载都需要)
|
||||
//
|
||||
// 用法:
|
||||
// test_mount_opt mount <opt_path> <tag> 挂载容器,验证设备可访问
|
||||
// test_mount_opt unmount <tag> 卸载容器,验证设备已消失
|
||||
// test_mount_opt link <tag> <link_dir> 为已挂载的卷创建目录链接(Junction)
|
||||
// test_mount_opt unlink <link_dir> 移除目录链接(Junction)
|
||||
//
|
||||
// 示例:
|
||||
// test_mount_opt mount "C:\Users\aiyah\Desktop\SDGA_A011_20260716114938_0.opt" OPT_TEST
|
||||
// test_mount_opt link OPT_TEST "C:\mnt\opt_test"
|
||||
// test_mount_opt unlink "C:\mnt\opt_test"
|
||||
// test_mount_opt unmount OPT_TEST
|
||||
#define LOG_MODULE "TEST"
|
||||
#include "fscrypt.h"
|
||||
#include "driver_service.h"
|
||||
#include "utils/strings.h"
|
||||
#include "utils/log.h"
|
||||
#include <windows.h>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <format>
|
||||
|
||||
static void print_usage() {
|
||||
puts("Usage:");
|
||||
puts(" test_mount_opt mount <opt_path> <tag> Mount container");
|
||||
puts(" test_mount_opt unmount <tag> Unmount container");
|
||||
puts(" test_mount_opt link <tag> <link_dir> Link device to directory");
|
||||
puts(" test_mount_opt unlink <link_dir> Remove directory link");
|
||||
}
|
||||
|
||||
// 确保驱动服务运行
|
||||
static bool ensure_driver() {
|
||||
std::string svc_err;
|
||||
if (!driver::ensure_running(&svc_err)) {
|
||||
LOG_ERROR("driver::ensure_running failed: {}", svc_err);
|
||||
LOG_ERROR("Hint: run this program as Administrator");
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("Driver service ready");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 验证设备 \\.\FscryptDisk_<tag> 是否可访问
|
||||
static bool check_device(const std::wstring &tag, bool should_exist) {
|
||||
std::wstring device_path = std::format(L"\\\\.\\FscryptDisk_{}", tag);
|
||||
HANDLE hCheck = CreateFileW(device_path.c_str(), 0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
bool exists = (hCheck != INVALID_HANDLE_VALUE);
|
||||
if (exists) CloseHandle(hCheck);
|
||||
|
||||
if (should_exist && !exists) {
|
||||
LOG_ERROR("Device {} not accessible: {}", utils::wstr_to_str(device_path), GetLastError());
|
||||
return false;
|
||||
}
|
||||
if (!should_exist && exists) {
|
||||
LOG_ERROR("Device {} still exists after unmount!", utils::wstr_to_str(device_path));
|
||||
return false;
|
||||
}
|
||||
if (should_exist) {
|
||||
LOG_INFO("Device {} accessible", utils::wstr_to_str(device_path));
|
||||
} else {
|
||||
LOG_INFO("Device {} removed", utils::wstr_to_str(device_path));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 子命令实现 ----
|
||||
|
||||
static int cmd_mount(int argc, char *argv[]) {
|
||||
if (argc < 4) { print_usage(); return 1; }
|
||||
std::wstring opt_path = utils::to_wstring(argv[2]);
|
||||
std::wstring tag = utils::to_wstring(argv[3]);
|
||||
|
||||
if (!ensure_driver()) return 1;
|
||||
|
||||
LOG_INFO("Mounting '{}' as tag '{}' ...", utils::wstr_to_str(opt_path), utils::wstr_to_str(tag));
|
||||
auto result = fscrypt::mount_container(opt_path, tag);
|
||||
if (!result.success) {
|
||||
LOG_ERROR("Mount failed: {}", result.error);
|
||||
return 1;
|
||||
}
|
||||
LOG_INFO("Mount succeeded");
|
||||
|
||||
if (!check_device(tag, true)) return 1;
|
||||
|
||||
LOG_INFO("=== MOUNT OK ===");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_unmount(int argc, char *argv[]) {
|
||||
if (argc < 3) { print_usage(); return 1; }
|
||||
std::wstring tag = utils::to_wstring(argv[2]);
|
||||
|
||||
if (!ensure_driver()) return 1;
|
||||
|
||||
LOG_INFO("Unmounting tag '{}' ...", utils::wstr_to_str(tag));
|
||||
if (!fscrypt::unmount_container(tag)) {
|
||||
LOG_ERROR("Unmount failed");
|
||||
return 1;
|
||||
}
|
||||
LOG_INFO("Unmount succeeded");
|
||||
|
||||
if (!check_device(tag, false)) return 1;
|
||||
|
||||
LOG_INFO("=== UNMOUNT OK ===");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_link(int argc, char *argv[]) {
|
||||
if (argc < 4) { print_usage(); return 1; }
|
||||
std::wstring tag = utils::to_wstring(argv[2]);
|
||||
std::wstring link_dir = utils::to_wstring(argv[3]);
|
||||
|
||||
LOG_INFO("Linking tag '{}' -> '{}' ...", utils::wstr_to_str(tag), utils::wstr_to_str(link_dir));
|
||||
if (!fscrypt::link_container(tag, link_dir)) {
|
||||
LOG_ERROR("Link failed");
|
||||
return 1;
|
||||
}
|
||||
LOG_INFO("=== LINK OK ===");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_unlink(int argc, char *argv[]) {
|
||||
if (argc < 3) { print_usage(); return 1; }
|
||||
std::wstring link_dir = utils::to_wstring(argv[2]);
|
||||
|
||||
LOG_INFO("Unlinking '{}' ...", utils::wstr_to_str(link_dir));
|
||||
if (!fscrypt::unlink_container(link_dir)) {
|
||||
LOG_ERROR("Unlink failed");
|
||||
return 1;
|
||||
}
|
||||
LOG_INFO("=== UNLINK OK ===");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
SetConsoleCP(CP_UTF8);
|
||||
|
||||
if (argc < 2) {
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string cmd = argv[1];
|
||||
if (cmd == "mount") return cmd_mount(argc, argv);
|
||||
if (cmd == "unmount") return cmd_unmount(argc, argv);
|
||||
if (cmd == "link") return cmd_link(argc, argv);
|
||||
if (cmd == "unlink") return cmd_unlink(argc, argv);
|
||||
|
||||
LOG_ERROR("Unknown command: {}", cmd);
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user