项目概览:旋转吧大喵是什么
旋转吧大喵(SpinningMomo)是一个面向《无限暖暖》的 Windows 桌面游戏摄影工具:以原生 Win32 C++ 后端 + 内嵌 WebView2 前端的双进程形态,提供游戏窗口比例一键切换、8K–12K 超清截图与录制、以及内置游戏摄影图库等能力。
目的与范围(Purpose and Scope)
本页面是整个 Wiki 的顶层入口页,回答"旋转吧大喵是什么"这个问题:
- ✅ 本页覆盖:项目定位与核心能力、总体架构(双进程模型)、仓库目录构成、技术栈与构建体系、启动流程、设计哲学概览。
- ❌ 本页不覆盖:各子系统的深度实现(图库扫描与资产生命周期、AppState 内部布局、无限暖暖硬链接镜像、Android 采集守护进程、RPC 协议细节等)。这些内容分别属于兄弟页面,本页仅给出 "For X, see Y" 式的导航。
边界依据:仓库的 AGENTS.md 明确将关键子系统的设计文档下放到各自的 README.md(src/features/gallery/README.md、src/core/state/README.md、src/extensions/infinity_nikki/README.md、android/capture/README.md),本页尊重这一边界,只做总览级引用。
概述(Overview)
旋转吧大喵是一个 Windows 专用的桌面工具,聚焦《无限暖暖》(Infinity Nikki)的游戏摄影、截图、录像及相关工作流。项目对外宣称的核心能力有四项(摘自 README 简介):
- ▸ 一键切换游戏窗口比例/尺寸,完美适配竖构图拍摄、相册浏览等场景
- ▸ 突破原生限制,支持生成 8K-12K 超高清游戏截图和录制
- ▸ 内置游戏摄影图库,自动索引照片与视频,支持标签、评分、时间线、颜色筛选和批量整理
- ▸ 专为《无限暖暖》优化,同时兼容多数窗口化运行的其他游戏
Source: README.md
几个关键的项目属性:
| 属性 | 值 | 依据 |
|---|---|---|
| 目标平台 | 仅 Windows(x64) | xmake.lua set_plat("windows") / set_arch("x64") |
| 开源协议 | GPL 3.0 | README.md |
| 后端语言 | 原生 Win32 C++(C++23) | xmake.lua |
| 前端技术 | Vue 3 + TypeScript + WebView2 | AGENTS.md |
| 用户文档 | https://spin.infinitymomo.com | README.md |
| 代码签名 | SignPath.io 免费签名服务 | README.md |
目标用户与使用场景:游戏摄影师(需要在游戏内进行竖构图拍摄、批量整理照片)、内容创作者(需要超出游戏原生分辨率限制的超高清素材)。工具围绕"游戏窗口"这一核心对象展开:窗口控制(features::window_control)、信箱化(features::letterbox)、截图(features::screenshot)、录制(features::recording)、预览(features::preview)与图库(features::gallery)共同构成一条完整的摄影工作流。
总体架构(Architecture)
双进程模型
应用是一个原生 Win32 C++ 后端,内嵌 WebView2 前端。两端通过 JSON-RPC 2.0 通信,具体有两套传输层:
- WebView bridge —— 生产环境下,Vue 应用运行在 WebView2 内部时使用;
- HTTP + SSE —— 开发环境下,Vue 应用在普通浏览器中运行时使用(后端由 uWebSockets 监听 51206 端口);SSE 提供服务端到客户端的推送通知。
前端通过检测 window.chrome.webview 的存在自动选择传输层。
Source: AGENTS.md
图中的每个节点都对应真实存在的模块:core::rpc、core::AppState、features::*、ui::* 是后端的命名空间划分(见下文"后端分层"),web/src/core/rpc/、web/src/core/env/、web/src/features/ 是前端的目录划分。
仓库整体构成
仓库不只是"一个 exe",而是包含多个交付面:
| 目录/文件 | 角色 |
|---|---|
src/ | C++ 后端主实现(core / features / ui / utils / extensions / vendor / migrations / locales) |
web/ | Vite + Vue 3 主前端;dev server 将 /rpc 与 /static 代理到 localhost:51206 |
android/ | Android 采集守护进程 momo-capture 源码与构建配置(构建产物 build/android/momo-capture.jar) |
docs/ | 独立的 VitePress 文档站(用户与开发者文档),不进入运行时包 |
playground/ | 独立的 Node/TypeScript 脚本,用于后端 HTTP/RPC 调试与实验 |
installer/ | WiX 源文件,用于生成 MSI 与 bundle 安装器 |
tasks/ | 自定义 xmake 任务(release、vs) |
xmake.lua | C++ 构建脚本 |
version.json / cliff.toml | 版本号与 git-cliff 变更日志配置 |
Source: AGENTS.md
设计意图:将"运行时产物"(src/ + web/ + android/)、"开发者工具"(playground/、docs/、tasks/)与"分发物"(installer/)严格分离,避免调试工具混入用户安装包。
后端分层与设计哲学
C++ 头文件架构(.hpp + .cpp)
后端使用 C++23 的头文件 + 实现文件组织,并带有预编译头(src/pch.hpp)加速构建。命名空间划分为:
core::*—— 框架基础设施:async 运行时、数据库、事件、HTTP client/server、RPC、WebView、i18n、commands、migration、worker pool、tasks、runtime info、shutdown、statefeatures::*—— 业务逻辑:adb_mode、gallery、letterbox、notifications、overlay、preview、recording、screenshot、settings、update、window_controlui::*—— 原生 Win32 UI:floating_window、tray_icon、context_menu、webview_windowutils::*—— 共享工具:logger、file、graphics、image、media、path、string、system、throttle、timer、dialog、crash_dump、cryptoextensions::*—— 游戏专属集成(如infinity_nikki)vendor/**/*.hpp—— 面向标准库、Win32 与第三方头的项目自有包含门面
Source: AGENTS.md
一个重要的工程约束:每个项目头文件必须在不依赖 PCH 的情况下自包含——显式包含 vendor/std.hpp 与所需的 vendor 门面;src/pch.hpp 只加速这些依赖。尖括号外部包含只允许出现在 src/vendor/ 内部。这个规则让依赖关系在每个文件的头部可见,避免"隐式依赖通过 PCH 泄漏"。
设计哲学:为什么不用 OOP 类层次
C++ 后端刻意不使用 OOP 类继承体系,而是遵循以下模式(摘自 AGENTS.md "Design Philosophy"):
- POD 结构体 + 自由函数:纯数据结构配合操作它们的自由函数;
- 集中式状态:所有状态存放在
AppState中,按引用传递; - Usecase 编排:
usecase.hpp/.cpp是特性的顶层编排层,可跨模块/跨特性协调调用(含core::*、UI 与 extensions); - 工作流所有权:其他模块可以按需依赖并复用公共能力,但跨模块的工作流编排必须放在 usecase 层。
Source: AGENTS.md
core::AppState 是唯一的根状态对象,以 std::unique_ptr 成员持有全部子系统状态;所有函数是接受 AppState& 的自由函数。这种"数据与逻辑分离 + 单一状态根"的设计,避免了深层继承树与跨模块生命周期纠缠,代价是必须依赖约定(而非访问修饰符)来划清模块边界——因此仓库用 src/core/state/README.md 单独固化了 AppState 的布局与 API 依赖约定。
关键横切模式
| 模式 | 实现方式 | 设计意图 |
|---|---|---|
| 错误处理 | 全仓 std::expected<T, std::string>,无异常控制流 | 失败路径显式化,编译器强制处理错误 |
| 异步 | 基于 Asio 的协程运行时(core::async),RPC handler 返回 asio::awaitable<RpcResult<T>> | 用协程表达异步,避免回调地狱 |
| 事件 | 类型擦除的事件总线(core::events):同步 send() 与异步 post()(经 PostMessageW 唤醒 Win32 消息循环) | 让 IO 线程与 UI 线程安全汇合 |
| RPC 注册 | core::rpc::register_method<Req, Res>() + reflect-cpp 自动(反)序列化;字段名在 snake_case(C++)与 camelCase(JSON)间自动转换 | 单点定义请求/响应结构,前端无需手写协议代码 |
| 命令 | core::commands 注册表绑定动作、开关态、i18n key 与可选热键;右键菜单与托盘图标由该注册表驱动 | 菜单/热键/动作三者同源,避免漂移 |
| 数据库 | SQLite(SQLiteCpp),线程本地连接 + DataMapper(ORM 式行映射)+ 自动生成迁移系统(scripts/generate-migrations.js) | 每线程独立连接消除 SQLite 并发瓶颈 |
| 字符串编码 | 内部处理 UTF-8(std::string),Win32 API 调用 UTF-16(std::wstring),经 utils::string 转换 | 兼顾跨平台数据格式与 Win32 原生要求 |
Source: AGENTS.md
RPC 端点组织
端点位于 src/core/rpc/endpoints/<domain>/,每个域暴露一个 register_all(state),由 registry.cpp 统一调用。游戏专属适配器位于 src/extensions/,经由 rpc/endpoints/extensions/ 暴露。这保证了"通用能力"与"游戏专属能力"在代码与端点两个层面都保持分离。
启动流程(Core Flow)
入口是 src/main.cpp 的 wWinMain。初始化顺序遵循:main.cpp → Application::Initialize() → core::initializer::initialize_application();大致顺序是先核心基础设施,再原生 UI,再特性服务,最后扩展与启动任务。
Source: AGENTS.md
对应真实入口代码:
1// Win32 入口
2auto __stdcall wWinMain(HINSTANCE hInstance, [[maybe_unused]] HINSTANCE hPrevInstance,
3 LPWSTR lpCmdLine, [[maybe_unused]] int nCmdShow) -> int {
4 auto ui_com_init = wil::CoInitializeEx(COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
5
6 // 尽早安装崩溃转储处理器
7 utils::crash_dump::install();
8
9 const auto startup_settings = features::settings::load_startup_settings();
10
11 // 尽早初始化日志,覆盖单实例、提权与启动早期故障
12 if (auto result = utils::logging::initialize(startup_settings.logger_level); !result) {
13 const auto error_message = "Logger Failed: " + result.error();
14 MessageBoxA(nullptr, error_message.c_str(), "Fatal Error", MB_ICONERROR);
15 return -1;
16 }Source: main.cpp
单实例与提权的顺序处理是启动流程中最精巧的部分——注释明确指出"提权前先释放单实例锁,避免提权后的新进程误判为'已有实例'":
1 // 需要管理员权限时尝试提权重启
2 if (startup_settings.always_run_as_admin && !utils::system::is_process_elevated()) {
3 Logger().info("Elevation required by settings, attempting restart as elevated");
4 // 提权前先释放单实例锁,避免提权后的新进程误判为“已有实例”
5 utils::system::release_single_instance_lock();
6
7 if (utils::system::restart_as_elevated(lpCmdLine)) {
8 Logger().info("Elevated process started successfully, current process exits");
9 utils::logging::shutdown();
10 // 提权进程已启动,当前进程退出
11 return 0;
12 }
13
14 Logger().warn("Elevation was cancelled or failed, continuing without admin privileges");
15
16 // 取消 UAC 或启动失败:重新获取单实例锁后继续普通权限
17 if (!utils::system::acquire_single_instance_lock()) {
18 Logger().info("Existing instance detected after elevation fallback, activating it");
19 utils::system::activate_existing_instance();
20 utils::logging::shutdown();
21 return 0;
22 }
23 }Source: main.cpp
主流程随后进入 Application:
1 int exit_code = 0;
2 // 主流程
3 try {
4 Application app;
5
6 if (!app.Initialize(hInstance)) {
7 Logger().critical("Failed to initialize application");
8 MessageBoxW(nullptr, L"Failed to initialize application", L"Error", MB_ICONERROR);
9 exit_code = -1;
10 } else {
11 exit_code = app.Run();
12 }
13
14 } catch (const std::exception& e) {
15 Logger().critical("Unhandled exception: {}", e.what());
16 MessageBoxA(nullptr, e.what(), "Fatal Error", MB_ICONERROR);
17 exit_code = -1;
18 }
19
20 // 退出前关闭日志
21 utils::logging::shutdown();
22 return exit_code;Source: main.cpp
构建体系与技术栈
构建由 xmake 驱动,配置在根 xmake.lua。工具链、依赖与关键编译选项如下:
| 配置项 | 值 | 说明 |
|---|---|---|
| C++ 标准 | c++23(set_languages("c++23")) | 使用 std::expected 等新特性 |
| 默认工具链 | clang-cl[llvm],可用 --toolchain 覆盖 | 统一编译器行为 |
| 编译选项 | /utf-8 /bigobj | 统一源码编码;容纳大目标文件 |
| 运行时库 | Debug 用 MD,Release 用 MT | 静态链接 CRT 便于分发 |
| 包管理 | vcpkg(package.requires_lock + baseline 锁定快照) | 可复现依赖解析 |
| 目标 | SpinningMomo(binary,windows/x64),PCH 为 src/pch.hpp | 单一可执行文件 |
| Release 符号 | set_symbols("debug") + /DEBUG:FULL | Release 也保留调试符号,便于分析生产崩溃 dump |
Source: xmake.lua
第三方依赖(全部经 vcpkg 引入):
- uwebsockets —— 开发模式下的 HTTP + SSE 服务
- spdlog —— 日志(
SPDLOG_COMPILED_LIB) - asio —— 协程异步运行时
- reflectcpp —— RPC 请求/响应的反射(反)序列化
- webview2 —— 内嵌前端容器
- wil —— Win32 C++ 工具库(如
wil::CoInitializeEx) - xxhash —— 高速哈希(如资产内容哈希)
- sqlitecpp —— SQLite 封装
- libwebp / zlib —— 图片编码与压缩
Source: xmake.lua
同时链接大量 Windows 系统库(dwmapi、dcomp、d3d11、dxgi、d2d1、dwrite、mf*/mfreadwrite 等),这与"窗口控制 / 信箱化 / 截图 / 录制 / 预览"等图形密集特性直接对应:
1 -- Windows系统库
2 add_links("dwmapi", "dcomp", "windowsapp", "RuntimeObject", "d3d11", "dxgi", "d3dcompiler",
3 "d2d1", "dwrite", "shell32", "Shlwapi", "gdi32", "user32", "Ws2_32", "Secur32",
4 "Advapi32", "Bcrypt", "Iphlpapi", "Dbghelp", "Userenv", "mf", "mfplat", "mfreadwrite", "mfuuid", "strmiids")Source: xmake.lua
常用命令(来自 AGENTS.md,前端与安卓侧用 pnpm):
1# C++ backend — debug
2xmake build
3
4# C++ backend — release
5xmake release
6
7# Web frontend
8pnpm run build:web
9
10# Android capture service
11pnpm run build:androidSource: AGENTS.md
构建产物与分发:Release 输出到 build\windows\x64\release\,Debug 到 build\windows\x64\debug\,Android 守护进程为 build/android/momo-capture.jar,最终分发物(exe + web 资源)落在 dist/。安装器由 node scripts/build-installer.js(或 pnpm run build:installer)构建,默认产出 MSI 包 + WiX bundle 的 setup .exe,--msi-only 跳过 bundle,--version X.Y.Z 覆盖 version.json。
Source: AGENTS.md
代码生成脚本
以下脚本必须在其源文件变化后重跑,属于"手动同步"的生成物约定:
node scripts/generate-migrations.js—— 修改src/migrations/*.sql之后node scripts/generate-embedded-locales.js—— 修改src/locales/*.json(zh-CN / en-US)之后node scripts/generate-map-injection-cpp.js—— 修改web/src/features/map/injection/source/*.js之后(重新生成压缩 JS 及其 C++ 头)
Source: AGENTS.md
Web 前端结构
主前端位于 web/,技术栈为 Vue 3 + TypeScript + Pinia + Tailwind CSS v4 + shadcn-vue/reka-ui,使用 Vite 兼容工具链构建。关键目录:
| 目录 | 职责 |
|---|---|
web/src/core/rpc/ | JSON-RPC 客户端(WebView 与 HTTP 双传输) |
web/src/core/i18n/ | 客户端国际化 |
web/src/core/env/ | 运行环境检测(决定用哪套传输层) |
web/src/core/tasks/ | 前端任务编排 |
web/src/features/ | 特性模块:gallery、settings、home、about、map、onboarding、common、playground |
web/src/composables/ | 共享 composables:useRpc、useI18n、useToast |
web/src/extensions/ | 游戏专属集成(infinity_nikki) |
web/src/router/ | 路由 |
web/src/types/ | 共享 TS 类型 |
web/src/lib/ / web/src/assets/ | 共享 UI/辅助与静态资源 |
Source: AGENTS.md
开发期 web/ 使用 Vite dev server,并把 /rpc 与 /static 代理到后端 localhost:51206——这就是"浏览器开发模式"能复用同一套 RPC 客户端的原因。
失败模式与边界情况(Failure Modes & Edge Cases)
从已读源码可以确认的启动期失败处理:
| 场景 | 处理 | 依据 |
|---|---|---|
| 日志初始化失败 | MessageBoxA 弹出 Fatal Error 并返回 -1 | main.cpp |
| 检测到已有实例 | 激活已有实例(activate_existing_instance())并正常退出(0) | main.cpp |
| 设置要求提权但 UAC 被取消/失败 | 降级为普通权限继续运行:先重新抢锁,若仍失败则激活已有实例退出 | main.cpp |
Application::Initialize 失败 | critical 日志 + MessageBoxW + exit_code = -1 | main.cpp |
wWinMain 内未捕获 std::exception | critical 日志 + MessageBoxA + exit_code = -1 | main.cpp |
| 进程崩溃 | 崩溃转储处理器在 wWinMain 最早处安装(utils::crash_dump::install()),配合 Release 保留调试符号分析生产 dump | main.cpp、xmake.lua |
并发与一致性相关的横切约定:异步事件总线通过 PostMessageW 唤醒 Win32 消息循环,保证 UI 线程亲和;SQLite 采用线程本地连接规避跨线程共享连接的问题;错误一律走 std::expected<T, std::string>,不存在以异常做控制流的路径。
Source: AGENTS.md
测试与质量保障
测试策略(AGENTS.md 明确:不要自动运行测试,由用户确认或手动执行):
- 场景测试(TypeScript):位于
tests/scenarios/,通过pnpm run test:scenarios运行。它们在隔离的便携沙箱中,经 JSON-RPC 测试编译好的SpinningMomo.exe;默认门控 Release 构建(build/windows/x64/release/),因为内容哈希语义只在 Release 下成立,测试前需先xmake release;可用--exe=<path>或SPINNING_MOMO_EXE指向其他构建。运行前需关闭已运行的应用实例。 - 单元测试(C++):遗留 doctest 单测位于
tests/,经xmake test运行。 - 交互式调试:交互式 RPC 测试工具位于
web/src/features/playground/与根目录playground/。
Source: AGENTS.md
工程协作约定(同样来自 AGENTS.md):注释应描述意图与逻辑(why / what)而非复述代码(how);改代码时同步更新相关注释保持一致。
Source: AGENTS.md
扩展点:如何添加一个新特性
AGENTS.md 给出了固定的六步清单,体现了本项目的分层思想:
11. 在 src/features/<name>/ 下创建目录,含 .hpp 接口与 .cpp 实现
22. 在 <name>/state.hpp 中添加状态结构体(features::<name> 命名空间),
3 并注册到 core::AppState
43. 在 src/core/rpc/endpoints/<name>/ 下新增 RPC 端点文件,
5 实现 register_all(state) 并在 registry.cpp 中接线
64. 若需要热键/菜单项,在 core/commands/builtin.cpp 注册命令
75. 若需要初始化,加入 core::initializer::initialize_application
86. Web 侧在 web/src/features/<name>/ 添加 api.ts、store/index.ts、
9 types.ts、组件与页面Source: AGENTS.md
命名约定:C++ 命名空间用下蛇命名(features::gallery),类型 PascalCase(GalleryState),文件/函数下蛇(gallery.hpp、initialize());前端组件 PascalCase(GalleryPage.vue),模块 camelCase(galleryApi.ts);C++ 包含顺序为 .cpp 先包含对应头,再 vendor/std.hpp,其余 vendor 头,最后项目头。
Source: AGENTS.md
相关链接(Related Links)
子系统深读(兄弟页面主题)
以下子系统各有独立 README,本页仅做导航,不展开:
- 图库资产身份 / 元数据继承 / 30 天缺失生命周期 / 扫描器与监视器不变量 →
src/features/gallery/README.md AppState布局与 API 依赖约定 →src/core/state/README.md- 无限暖暖媒体硬链接镜像与任务编排 →
src/extensions/infinity_nikki/README.md - Android 采集守护进程(momo-capture)架构 / VirtualDisplay 流水线 / ADB 调试 →
android/capture/README.md
Source: AGENTS.md
外部资源
- 用户文档:https://spin.infinitymomo.com (见 README.md)
- 构建指南:https://spin.infinitymomo.com/developer/architecture (见 README.md)
- 最新版本下载:GitHub Release
- 第三方开源许可:CREDITS.md
- 许可证:LICENSE(GPL 3.0)