SDGB 1.56 调试实录:从 AquaMai 崩溃到独立实现下屏位置与缩放
本文记录一次围绕 SDGB 1.56、Sinmai-Assist、AquaMai 与 MelonLoader 的完整调试过程。目标不是修改整个游戏窗口,而是在不加载完整 AquaMai 的前提下,只调整游戏内部下侧圆形屏幕的位置和大小,使其与实体控制器的中心准确对齐。
一、问题背景
我的 SDGB 界面由上下两部分组成:上侧是矩形信息屏,下侧是圆形游戏主屏。在实体控制器上使用时,下侧圆形画面的中心、直径和实际控制器存在偏差。我希望能够在游戏运行过程中调整下屏的位置和缩放比例,同时不影响上屏。
AquaMai 中原本提供了“屏幕位置调整”功能,但当前完整的 AquaMai.dll 与我的 SDGB 1.56 环境存在兼容性问题。只要加载完整 AquaMai,游戏就可能在启动或进入选歌界面后崩溃。因此,整个调试过程最终转向了一个更明确的目标:
提取 AquaMai 屏幕位置调整功能所需的最小逻辑,制作一个独立的 MelonLoader Mod,而不是继续修补完整 AquaMai。
二、第一次故障:AquaMai 在选歌界面崩溃
最初的主要异常是:
System.MissingMethodException:
No matching PacketUpsertUserAll constructor found调用栈指向 AquaMai 的:
AquaMai.Core.Helpers.Shim日志还出现了类似以下提示:
Failed to patch AquaMai.Core.Helpers.Shim
No matching PacketUpsertUserAll constructor found这说明问题不在触摸串口、外部控制器或启动脚本,而在 AquaMai 对当前游戏程序集的兼容性上。
AquaMai 会通过反射或补丁逻辑查找 PacketUpsertUserAll 的特定构造函数,但当前 SDGB 1.56 的 Assembly-CSharp.dll 中,相关类或构造函数结构与 AquaMai 预期不一致。最终形成了下面的故障链:
AquaMai 初始化 Shim
→ 找不到预期构造函数
→ 后续模块仍调用 Shim
→ 进入选歌或网络处理阶段时崩溃关闭 AquaMai 虽然可以避开异常,但也会同时失去由 AquaMai 提供的部分输入功能,因此不能把“关闭 AquaMai”当作最终解决方案。
三、覆盖更新包带来的版本混用风险
在升级 SDGB 时,我曾使用两个压缩包覆盖旧目录:
1.56覆盖包.zipamfs覆盖.zip
检查后发现,1.56覆盖包.zip 只包含部分文件,例如:
amdaemon.exe
AMDaemon.NET.dll
Assembly-CSharp-firstpass.dll
ChimeLib.NET.dll
amdaemon_api.dll
chimelib_dll.dll
resources.assets但其中没有:
Assembly-CSharp.dll
Sinmai.exe
UnityPlayer.dll
AquaMai.dll
MelonLoader这意味着它更接近“局部更新包”,而不是一个完整、可独立使用的 1.56 程序包。如果在旧目录上直接覆盖,就可能形成:
部分 1.56 文件
+ 旧版或其他来源的 Assembly-CSharp.dll
+ 不匹配的 AquaMai.dll这种混合环境很容易产生反射失败、方法签名不一致和补丁目标找不到等问题。
哈希核对
我对覆盖包中的关键文件进行了 MD5 核对:
amdaemon.exe
915a494f9aa45f85d928f00a6cdf46cb
AMDaemon.NET.dll
8b55e6800fc7b871b402fee2cbfb0642
Assembly-CSharp-firstpass.dll
c36d036aff69fa839a436f2fc90b19e8
ChimeLib.NET.dll
40e48691919fcb841509d22f3c93d04a
amdaemon_api.dll
285f12a50d4e5e682b7858cbf34f2611
chimelib_dll.dll
c4f916c4f46dc17a2ae428a0782215d3当前目录中的这些文件与覆盖包一致,但 Assembly-CSharp.dll 并不在覆盖包中,无法由该覆盖包完成版本闭环。
经验
更新这类环境时,不能只确认“文件存在”,还要确认:
- 文件来源一致;
- 文件版本属于同一套程序;
- 关键 DLL 的哈希能够对应;
- 不在旧目录上长期叠加不明来源的局部覆盖包。
四、重新构建后,Sinmai-Assist 缺少依赖
为了避开完整 AquaMai,我重新构建了一套使用 Sinmai-Assist 的 SDGB 环境。
MelonLoader 能识别 Sinmai-Assist.dll,但日志显示:
'Sinmai-Assist' is missing the following dependencies:
- 'netstandard' v2.0.0.0
- 'Swan.Lite' v3.1.0.0随后还出现:
ReflectionTypeLoadException
Could not load file or assembly 'netstandard'
BadImageFormatException这说明日志中的:
1 Mod loaded并不等于 Sinmai-Assist 已经完整工作。Mod 虽然被识别,但类型加载、Harmony Patch 和 GUI 绘制都处于异常状态。
处理方法
优先检查:
Get-ChildItem -Recurse -Filter netstandard.dll
Get-ChildItem -Recurse -Filter Swan.Lite.dll依赖文件应放在 MelonLoader 能提前加载的位置,例如:
Package\UserLibs\netstandard.dll
Package\UserLibs\Swan.Lite.dll只有以下错误消失后,才适合继续排查输入和游戏功能:
missing dependencies
ReflectionTypeLoadException
Could not load file or assembly 'netstandard'
BadImageFormatException日志还出现过:
DisableEnvironmentCheck, Method Not Found!这说明 Sinmai-Assist 中的部分补丁也可能没有完全适配当前 SDGB 版本。不过它的优先级低于依赖缺失,应先确保基础加载正常,再处理方法匹配问题。
五、二维码摄像头不工作:DummyLogin 与真实摄像头互斥
在配置二维码读取摄像头时,日志显示:
DummyLogin enabled, CustomCameraId has been automatically disabled.当时配置中同时启用了:
dummyLogin:
enable: true
customCameraId:
enable: true这两个功能在当前 Sinmai-Assist 逻辑中存在冲突。启用虚拟登录后,真实摄像头配置会被自动禁用。
需要真实二维码摄像头时,应先关闭 DummyLogin:
common:
dummyLogin:
enable: false
customCameraId:
enable: true
chimeCameraId: 0
leftQrCameraId: 0
rightQrCameraId: 0
photoCameraId: 0
changeGameSettings:
enable: true
codeRead: true如果日志仍提示找不到摄像头,则依次测试:
Camera ID: 0 → 1 → 2 → 3这一步带来的经验是:排查配置问题时,不能只看目标功能是否启用,还必须检查是否存在会自动关闭它的上游功能。
六、同一个 BAT 在不同机器上表现不同
后来出现了另一个问题:同一个启动 BAT 在两台电脑上运行,其中一台的 inject 窗口没有正常出现。
最初很难判断是:
- BAT 没有执行到 inject;
- inject 启动后立即退出;
- 杀毒软件拦截;
- 运行库缺失;
- 路径或工作目录不正确;
- 目标文件缺失。
为此,我制作了一个调试版启动脚本,主要增加了:
- 可见的 inject 控制台;
- 启动步骤日志;
- 必要文件检查;
- 必要目录检查;
- 进程状态检查;
- 退出码记录;
- 固定工作目录。
例如:
cd /d "%~dp0"
start "inject-debug" cmd /k ^
".\inject.exe -d -k .\mai2hook.dll .\amdaemon.exe -f -c .\config_common.json .\config_server.json .\config_client.json"最终原因:缺少 option 目录
调试窗口成功暴露了真正原因:其中一套 SDGB 缺少必要的 option 目录,导致 inject 初始化失败。补充目录后,程序恢复正常。
为避免空目录在复制、压缩或同步时再次丢失,我把目录检查写入启动脚本:
if not exist "option" (
echo [WARN] option directory not found, creating...
mkdir "option"
)
if not exist "option" (
echo [ERROR] Failed to create option directory.
pause
exit /b 1
)也可以在目录中保留一个占位文件:
option\.keep运行环境的完整性不仅包括文件,也包括必要的空目录。调试脚本最大的价值不是“自动修复一切”,而是让原本一闪而过的异常变得可见、可记录。
七、第一次屏幕调整尝试:从外部移动整个窗口
为了绕开 AquaMai,我首先尝试使用 PowerShell 调整游戏窗口:
F10 进入或退出调整模式
方向键 移动窗口
+ / - 缩放窗口
F9 输出当前位置
Esc 退出脚本外部脚本通过 Win32 API 调用:
MoveWindow
GetWindowRect
EnumWindows并尝试按以下条件识别游戏窗口:
ProcessName = sinmai
WindowTitle contains Sinmai
WindowClass = UnityWndClass
Exclude ConsoleWindowClass为什么失败
实际运行时,MelonLoader 控制台窗口会与 Sinmai 游戏进程同时出现。仅凭进程名、窗口标题或窗口类,很难稳定区分:
- Unity 游戏主窗口;
- MelonLoader 控制台;
- amdaemon 控制台;
- 其他辅助窗口。
即使偶尔选中了正确窗口,这个方案调整的仍然是整个 Windows 窗口,而不是游戏内部的下屏区域。
这里必须区分两个概念:
整个游戏窗口的位置和分辨率
≠
游戏内部上下屏区域的位置和缩放我的真实目标是只调整下侧圆形区域,因此外部窗口脚本从一开始就不是最合适的技术路线。
八、重新理解 AquaMai 的“屏幕位置调整”
AquaMai 中存在两类容易混淆的功能。
1. 整体窗口与分辨率设置
它负责:
窗口化
无边框
整体分辨率
Windows 窗口样式这一类通常基于:
Screen.SetResolution(...)
SetWindowLongPtr(...)
SetWindowPos(...)2. 游戏内部屏幕位置调整
我真正需要的是 ScreenPositionAdjust。它不是简单移动某个 UI 对象,也不是调整整个 Windows 窗口,而是重新组织游戏的渲染输出。
AquaMai 的核心思路是:
原游戏画面
→ Camera 捕获
→ RenderTexture
→ 新建显示 Canvas
→ 将上下屏拆分成 RawImage
→ 分别调整位置和缩放因此,正确方向不是继续完善 PowerShell 脚本,而是制作一个最小 MelonLoader Mod,只复刻这部分内部显示逻辑。
九、SinmaiScreenAdjustLite 的开发过程
最终我将独立 Mod 命名为:
SinmaiScreenAdjustLite它不引用完整 AquaMai,也不包含:
Shim
PacketUpsertUserAll
NetPacketHook
FixLevelDisplay
解锁功能
网络包处理
输入映射只保留下屏显示调整所需的逻辑。
十、v0.1.0:直接扫描并缩放 Unity 对象
第一版采用最直观的方法:
- 扫描场景中的
GameObject、Transform、RectTransform和Camera; - 输出疑似下屏对象;
- 自动选择类似路径:
LeftMonitor/.../Canvas/Main- 修改目标对象的:
localPosition
localScale默认配置为:
[LowerScreen]
Enable=true
OffsetX=0
OffsetY=0
Scale=1.0
TargetPath=为了适配不同流程中不断变化的对象名称,后续加入了通配路径:
TargetPath=LeftMonitor/*/Canvas/Main这样可以匹配:
PowerOnProcess(Clone)
GenericProcess(Clone)对象能够被找到,但它并不等于完整的圆形下屏渲染结果。
十一、v0.2.0:加入 F10 动态调整
第一版只支持固定配置,因此按 F10 没有反应。v0.2.0 才真正加入交互功能:
F10 开启/关闭调整模式
方向键 移动下屏
+ / - 调整缩放
F9 输出当前参数
Enter 保存配置调整模式中会显示:
OffsetX
OffsetY
Scale这一步解决了交互问题,但没有解决“缩放对象本身是否正确”的问题。
十二、v0.3.0:从 Canvas/Main 改到 Background
实际测试发现:
LeftMonitor/.../Canvas/Main是一个较大的容器,其中还包含:
MainMessage
SubMessage因此缩放 Canvas/Main 时,游戏中的信息提示栏也会一起变化。
v0.3.0 将目标收窄为:
TargetPath=LeftMonitor/*/Canvas/Main/Background同时,F9 增加了目标父级和子级对象的诊断输出。
但 Background 仍然只是流程中的一个背景 UI,而不是完整的下屏最终画面。直接修改单个 Unity UI 对象的路线到这里基本确认不可行。
十三、v0.4.0:改用 Camera 与 RenderTexture 重渲染
v0.4.0 开始采用更接近 AquaMai 的实现:
Capture Camera 捕获原始画面
→ 输出到 RenderTexture
→ 新建显示层
→ 上屏保持原位
→ 下屏作为独立 RawImage
→ F10 只调整下屏 RawImage这一步解决了“提示栏被当成缩放对象”的问题,因为调整对象不再是原始场景中的 Canvas/Main 或 Background。
日志中可以看到类似:
SinmaiScreenAdjustLite v0.4.0
RenderTexture mode initialized
Applied RenderTexture LowerScreen adjustment新问题
切换到重渲染后,一些原本不应该显示的 Unity 元素轮廓也被带入了画面。
十四、v0.4.1:显示层与捕获层隔离
为了避免 Capture Camera 拍到 Lite 自己的新显示层,v0.4.1 增加了专用 Unity Layer:
Lite Display Canvas → 专用 layer 31
Display Camera → 只渲染 layer 31
Capture Camera → 排除 layer 31同时:
- Capture Camera 使用黑色背景清屏;
- F9 输出 Camera 的 layer 与
cullingMask; - 避免显示层形成回授画面。
日志示例:
DisplayLayer=31
CaptureCullingMask=0x7FFFFEFF
DisplayCullingMask=0x80000000这一版减少了部分额外元素,但仍没有完全消除轮廓。
十五、建立可验证的诊断方法
为了避免继续盲目调整,我在后续版本中加入了几个诊断开关:
SolidLowerColorTest=false
DisableOriginalCamerasAfterSetup=false
DumpRenderPipeline=true
CircularLowerMask=falseSolidLowerColorTest
用纯色替换下屏纹理。它可以判断:
纯色模式仍有轮廓
→ 轮廓来自 RenderTexture 背后的原始输出
纯色模式轮廓消失
→ 轮廓来自 Capture Camera、UV 采样或 RenderTexture 内容DisableOriginalCamerasAfterSetup
在 Lite 的 Capture Camera 与 Display Camera 初始化完成后,临时禁用其他 Camera,用于判断是否仍有原始相机直接输出到窗口。
DumpRenderPipeline
输出所有 Camera 与 Canvas 的:
名称
enabled
depth
clearFlags
backgroundColor
cullingMask
targetTexture
rect / pixelRect
Canvas renderMode
sortingOrder
worldCamera
targetDisplayCircularLowerMask
给下屏增加圆形遮罩,用于判断边缘轮廓是否来自矩形 RawImage 暴露区域。
这些开关把“画面不对”转化成了能够逐项验证的问题。
十六、v0.5.0:最终定位到错误的 uvRect
最终检查发现,旧版真正的核心问题是:
RenderTexture 的下屏采样区域并不是正方形。
在 1920×1080 环境中,旧版实际采样了:
1920 × 607.5这不是一个适合圆形下屏的正方形区域。即使后续使用圆形遮罩,也会把不属于下屏的画面内容一起采样进来,导致边缘轮廓、额外 UI 和比例异常。
参考 AquaMai 修正后的参数
v0.5.0 将参数修正为:
RenderTexture: 1215 × 1080
1P 下屏 uvRect: (0, 0, 0.5, 0.5625)实际采样尺寸为:
宽度 = 1215 × 0.5 = 607.5
高度 = 1080 × 0.5625 = 607.5因此得到:
607.5 × 607.5日志确认:
sampledRegionIsSquare=True这一修正确保了下屏采样区域本身就是正方形,圆形画面才可能完整、准确地恢复。
相机位置修正
同时还完成了:
Capture Camera 使用双屏中心 X=0
Display Camera 保持 1P 中心最终完整圆形下屏恢复,额外 Unity 元素轮廓消失。
十七、v0.5.0 最终功能
当前版本已实现:
F10 开启/关闭下屏调整模式
方向键 移动下屏
+ / - 放大或缩小下屏
F9 输出当前参数与渲染管线信息
Enter 保存配置同时支持:
RenderTexture 下屏重渲染
上屏保持不动
下屏独立调整
显示层与捕获层隔离
动态 Camera 检查
圆形遮罩诊断
纯色替换诊断
完整渲染管线输出当前最终 DLL:
SinmaiScreenAdjustLite.dll
Version: v0.5.0
SHA256:
01F27CF255E2C872A5DF04A664B99F032CEDAD17BE1093CFBCEDE1A01EB8809B当前安全基线配置为:
SolidLowerColorTest=false
DisableOriginalCamerasAfterSetup=false
DumpRenderPipeline=true
CircularLowerMask=false也就是说,保留完整诊断日志,但关闭所有会改变正常画面的测试选项。
十八、最终结果
这次调试最终完成了最初目标:
- 不加载完整 AquaMai;
- 避开
Shim与PacketUpsertUserAll的兼容性崩溃; - 保留 Sinmai-Assist 与 inject 启动链路;
- 只调整游戏内部的下侧圆形屏幕;
- 不影响上侧矩形屏幕;
- 支持运行时实时移动和缩放;
- 支持保存调整结果;
- 修复额外 Unity 元素轮廓显现;
- 完整恢复圆形下屏。
完整的问题演进可以概括为:
AquaMai 完整版崩溃
→ 尝试外部窗口缩放
→ 发现目标应是游戏内部下屏
→ 直接缩放 Canvas/Main,误伤信息栏
→ 改为 Background,仍不是完整画面
→ 使用 Camera + RenderTexture 重渲染
→ 出现额外 Unity 元素轮廓
→ 增加 Layer 隔离和诊断开关
→ 发现 uvRect 采样区域错误
→ 修正 RenderTexture、uvRect 与相机中心
→ 完整圆形下屏恢复十九、调试心得
1. “能看到变化”不等于“找到了正确对象”
Canvas/Main、Background 和最终显示画面看起来相关,但它们只是渲染链路中的一部分。直接修改它们虽然会产生变化,却无法保证调整的是完整下屏。
2. 不要把窗口调整和内部画面调整混为一谈
外部 PowerShell 可以调整整个 Windows 窗口,但不能单独调整游戏内部的下屏。真正的内部屏幕调整需要进入 Unity 渲染管线。
3. Mod 显示为已加载,不代表它运行正常
只要日志中仍存在:
ReflectionTypeLoadException
Missing dependencies
BadImageFormatException就不能认为 Mod 已经处于可靠状态。
4. 局部覆盖包很容易制造混合版本环境
关键 DLL、主程序、资源文件和 Mod 必须来自匹配的版本。更新后应保留文件清单与哈希,而不是依赖文件时间和文件名判断。
5. 空目录也是程序依赖
option 目录缺失导致 inject 无法启动,是这次最容易被忽略的问题之一。部署和备份时应检查必要目录,而不只是文件。
6. 诊断开关比继续猜测更有效
纯色替换、禁用原始相机、渲染管线输出和圆形遮罩测试,将模糊的视觉异常拆分成了可以验证的路径。
7. RenderTexture 的尺寸与 UV 采样必须成套设计
本次最终问题并不是简单的 Layer 或 Mask,而是采样区域本身不正确。只有确保下屏采样区域是正方形,后续缩放、遮罩和位置调整才有正确基础。
8. 最小化实现比修复完整 Mod 更可控
完整 AquaMai 包含许多与屏幕调整无关的模块。将需要的功能提取为独立 Lite Mod,可以减少兼容性风险,也更容易定位问题和维护。
二十、后续维护建议
当前 v0.5.0 可以作为稳定基线。后续改动应尽量围绕现有显示管线小步进行,不应再次大幅重构。
建议的后续功能包括:
- 增加一键重置
OffsetX、OffsetY和Scale; - 保存配置前自动生成备份;
- 增加 1P / 2P 显示选择;
- 支持上屏独立调整;
- 在启动日志中输出最终生效配置;
- 改善 F10 调整模式中的参数显示;
- 增加配置合法性检查和范围限制;
- 为不同分辨率建立独立预设;
- 保存 DLL、源码、配置和哈希清单;
- 将调试版与日常使用版配置分开。
结语
这次问题表面上是“下屏圆形画面无法对齐”,实际涉及:
游戏程序集兼容性
MelonLoader Mod 依赖
启动脚本
必要目录
摄像头配置
Unity 对象层级
Camera
Canvas
RenderTexture
Layer
Mask
uvRect最终最重要的认识是:
屏幕位置调整不是简单移动窗口,也不是随便缩放一个 UI 对象,而是对游戏内部显示管线的重新组织。
通过把 AquaMai 中真正需要的显示逻辑提取成独立 Mod,我最终在保留现有启动和输入环境的同时,实现了下侧圆形屏幕的独立位置与缩放调整,并避开了完整 AquaMai 带来的兼容性问题。