Repository Wiki
ChanIok/SpinningMomo

项目概览:旋转吧大喵是什么

旋转吧大喵(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.0README.md
后端语言原生 Win32 C++(C++23)xmake.lua
前端技术Vue 3 + TypeScript + WebView2AGENTS.md
用户文档https://spin.infinitymomo.comREADME.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

Loading diagram...

图中的每个节点都对应真实存在的模块: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.luaC++ 构建脚本
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、state
  • features::* —— 业务逻辑:adb_mode、gallery、letterbox、notifications、overlay、preview、recording、screenshot、settings、update、window_control
  • ui::* —— 原生 Win32 UI:floating_window、tray_icon、context_menu、webview_window
  • utils::* —— 共享工具:logger、file、graphics、image、media、path、string、system、throttle、timer、dialog、crash_dump、crypto
  • extensions::* —— 游戏专属集成(如 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

Loading diagram...

对应真实入口代码:

cpp
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

单实例与提权的顺序处理是启动流程中最精巧的部分——注释明确指出"提权前先释放单实例锁,避免提权后的新进程误判为'已有实例'":

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:

cpp
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:FULLRelease 也保留调试符号,便于分析生产崩溃 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 等),这与"窗口控制 / 信箱化 / 截图 / 录制 / 预览"等图形密集特性直接对应:

lua
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):

bash
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:android

Source: 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 并返回 -1main.cpp
检测到已有实例激活已有实例(activate_existing_instance())并正常退出(0)main.cpp
设置要求提权但 UAC 被取消/失败降级为普通权限继续运行:先重新抢锁,若仍失败则激活已有实例退出main.cpp
Application::Initialize 失败critical 日志 + MessageBoxW + exit_code = -1main.cpp
wWinMain 内未捕获 std::exceptioncritical 日志 + MessageBoxA + exit_code = -1main.cpp
进程崩溃崩溃转储处理器在 wWinMain 最早处安装(utils::crash_dump::install()),配合 Release 保留调试符号分析生产 dumpmain.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 给出了固定的六步清单,体现了本项目的分层思想:

text
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

子系统深读(兄弟页面主题)

以下子系统各有独立 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

外部资源

Sources

(4 files)