Skip to content

Conversation

@Starrah
Copy link
Contributor

@Starrah Starrah commented Feb 1, 2026

  1. 根据 [Feature Request] Dont ruin my account功能不足 #82 的建议,增加了 DontRuinMyAccount 发动时重置的成绩范围。 Close [Feature Request] Dont ruin my account功能不足 #82.
    • 判定表会被清空显示为全0(我认为这是最合理的,而不是转为全miss。不然就和开局直接trackskip的情况无法区分了。或者说“判定表全0”可以作为DontRuinMyAccount的标志)
    • 达成率、DX分、连击、同步、是否clear都是0/None
    • 自然的,isAchieveNewRecord和isDeluxscoreNewRecord就不可能为true,beforeRating/afterRating等肯定也保持不变。
  2. 新增了选项:forPracticeMode,默认为false。当它开启时,对PracticeMode和DebugFeature的相关功能,如暂停、变速、快进、快退等,也开启防毁号。

现在实现:
1. 判定表会被清空显示为全0(我认为这是最合理的,而不是转为全miss。不然就和开局直接trackskip的情况无法区分了。或者说“判定表全0”可以作为DontRuinMyAccount的标志)
2. 达成率、DX分、连击、同步、是否clear都是0/None
3. 自然的,isAchieveNewRecord和isDeluxscoreNewRecord就不可能为true,beforeRating/afterRating等肯定也保持不变。
@gemini-code-assist
Copy link

Summary of Changes

Hello @Starrah, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在通过扩展“DontRuinMyAccount”功能的适用范围并改进其成绩重置机制,来增强用户体验。现在,除了自动播放外,练习模式和调试功能的使用也将触发防毁号,确保在非标准游戏过程中不会意外保存成绩。同时,成绩重置逻辑也变得更加彻底,能够将更多成绩相关数据清零。

Highlights

  • 增强成绩重置机制: 根据 [Feature Request] Dont ruin my account功能不足 #82 的建议,增强了 DontRuinMyAccount 功能,使其在触发时能更全面地重置成绩数据。现在,判定表会被清空显示为全0,达成率、DX分、连击、同步、是否clear等都将设为0/None,确保不会意外保存不准确的成绩。
  • 新增练习模式防毁号选项: 新增了 forPracticeMode 配置选项(默认为 false),允许用户选择在练习模式或使用 DebugFeature 相关功能(如暂停、变速、快进、快退)时也触发防毁号机制,防止这些操作影响真实成绩记录。
  • 本地化信息更新: 更新了本地化字符串,将“Autoplay was used”提示信息扩展为包含“Autoplay/PracticeMode was used”,以更准确地反映防毁号功能现在也适用于练习模式。
  • 防毁号功能集成: 在 DebugFeaturePracticeMode 模块中集成了 DontRuinMyAccount.triggerForPracticeMode() 调用,确保在相关操作(如暂停、快进、变速)发生时能正确触发防毁号。
  • 代码重构与优化: 重构了 DontRuinMyAccount 类的内部逻辑,引入了 trigger()triggerForPracticeMode() 方法,以提高代码的可维护性和清晰度,并使用 Traverse.Create 简化了私有属性的设置。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

这个 PR 很好地增强了 DontRuinMyAccount 功能。它将分数保护扩展到了练习模式,并增加了新的配置选项,这很有用。当分数保护被触发时,重置分数的逻辑现在更加全面,能够清除更多的成绩相关字段。代码重构,特别是 trigger 方法的提取,使得逻辑更清晰、更易于维护。我发现了一些可以轻微改进的地方,并已在代码中提出了具体建议。总的来说,这是一次高质量的更新。

@clansty
Copy link
Member

clansty commented Feb 2, 2026

哇 好耶!

[ConfigEntry(zh: "AutoPlay 激活后显示提示", en: "Show notice when AutoPlay is activated")]
public static readonly bool showNotice = true;
[ConfigEntry(zh: "使用练习模式/DebugFeature相关功能也不保存成绩", en: "Also not save scores when using PracticeMode/DebugFeature")]
public static readonly bool forPracticeMode = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我觉得其实这个默认开启好了

Copy link
Contributor Author

@Starrah Starrah Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好 我一会改一下推上来,刚好发现了一个bug(功能在调速的情况下不生效)我修一下

@Starrah Starrah marked this pull request as draft February 2, 2026 10:59
@Starrah
Copy link
Contributor Author

Starrah commented Feb 2, 2026

发现了一些练习模式原有的bug,导致本Mod的部分功能(调速时trigger不保存成绩)不工作。
简单来说就是Practice.SetSpeed()运行到一半,还没等运行到我新加的triggerForPracticeMode就抛异常了。

正在尝试调查和解决。

@clansty
Copy link
Member

clansty commented Feb 3, 2026

我昨天也发现我用外部 dll 调用 PractiseMode 的 SetSpeed 会 null reference exception,我没看出来为什么,但是从练习模式 ui 里面调速似乎就没问题
不排除是我尝试把速度设置成>2 的问题

@Starrah
Copy link
Contributor Author

Starrah commented Feb 3, 2026

我昨天也发现我用外部 dll 调用 PractiseMode 的 SetSpeed 会 null reference exception,我没看出来为什么,但是从练习模式 ui 里面调速似乎就没问题 不排除是我尝试把速度设置成>2 的问题

@clansty 已经查明了,是SetSpeed 有一个PV调速的功能,在1.60下一定会 null reference exception 。练习模式 ui 里面调速“似乎就没问题”只是因为那个异常最后是被MonoBehaviour的update函数捕获的,所以log没有出现在MelonLogger里罢了。

稍后会发PR上来,里面会附具体的分析。

@Starrah Starrah marked this pull request as ready for review February 3, 2026 08:09
@Starrah
Copy link
Contributor Author

Starrah commented Feb 3, 2026

发现了一些练习模式原有的bug,导致本Mod的部分功能(调速时trigger不保存成绩)不工作。 简单来说就是Practice.SetSpeed()运行到一半,还没等运行到我新加的triggerForPracticeMode就抛异常了。

正在尝试调查和解决。

这部分问题已经在#114 中解决。然后已将forPracticeMode改为默认true。可以合并了。

@clansty
Copy link
Member

clansty commented Feb 3, 2026

诶,可是,PractiseModeUI.Update 也并没有套 try catch 呀
如果说 monobehavior 的 update 抛出的 exception 不会导致应用程序崩溃的话,那那些 process 的 OnUpdate 之类的函数其实也是 GameMainObject 的 Update 在调用它呀,为什么就会导致崩溃呢
好神奇
然后 Melonloader 的那个日志,其实也是 aquamai 的一个 mod 捕获了全局的游戏崩溃事件去打印或者弹出错误报告程序的呀
我觉得 PractiseModeUI 里面调用 SetSpeed 的 exception 被静默了这件事情好神奇

@Starrah
Copy link
Contributor Author

Starrah commented Feb 3, 2026

诶,可是,PractiseModeUI.Update 也并没有套 try catch 呀 如果说 monobehavior 的 update 抛出的 exception 不会导致应用程序崩溃的话,那那些 process 的 OnUpdate 之类的函数其实也是 GameMainObject 的 Update 在调用它呀,为什么就会导致崩溃呢 好神奇 然后 Melonloader 的那个日志,其实也是 aquamai 的一个 mod 捕获了全局的游戏崩溃事件去打印或者弹出错误报告程序的呀 我觉得 PractiseModeUI 里面调用 SetSpeed 的 exception 被静默了这件事情好神奇

这是因为,AquaMai.ErrorReport之所以能捕获到游戏内异常的原因是,利用了游戏内置的异常处理机制GameMain.ExceptionHandler,给它打了钩子

[HarmonyPostfix]
[HarmonyPatch(typeof(GameMain), "ExceptionHandler")]
private static void ExceptionHandler(GameMain __instance, Exception e)
{
if (_errorUi == null)
{
_errorUi = new GameObject("ErrorUI").AddComponent<Ui>();
_errorUi.gameObject.SetActive(true);
}
string logFile = $"{MAI2System.Path.ErrorLogPath}{DateTime.Now:yyyyMMddHHmmss}.log";
MelonLogger.Msg("Error Log:");

分析

下面的分析可以很直观的说明原因:我在SetSpeed()函数内,和某个[HarmonyPatch(typeof(GameProcess), "OnUpdate")]钩子内,分别使用了这样的代码MelonLogger.Msg($"xxxx {new StackTrace(true)}");,打印其调用栈,结果如下:

[20:19:49.064] SetSpeed   at AquaMai.Mods.UX.PracticeMode.PracticeMode.SetSpeed () [0x00000] in <1d0abc4c703447b8b813153c77654973>:0 
  at AquaMai.Mods.UX.PracticeMode.PracticeMode.SpeedDown () [0x00000] in <1d0abc4c703447b8b813153c77654973>:0 
  at AquaMai.Mods.UX.PracticeMode.Libs.PracticeModeUI.Update () [0x00000] in <1d0abc4c703447b8b813153c77654973>:0 
[20:19:49.081] GameProcess:OnUpdate   at AquaMai.Mods.UX.PracticeMode.PracticeMode.GameProcessPostUpdate (Process.GameProcess __instance, Monitor.GameMonitor[] ____monitors) [0x00000] in <1d0abc4c703447b8b813153c77654973>:0 
  at Process.GameProcess.DMD<Process.GameProcess::OnUpdate> (Process.GameProcess ) [0x00000] in <b926778010814c959998ed2627867f07>:0 
  at Manager.ProcessManager.Update () [0x00000] in <b926778010814c959998ed2627867f07>:0 
  at Main.GameMain.DMD<Main.GameMain::Update> (Main.GameMain ) [0x00000] in <b926778010814c959998ed2627867f07>:0 
  at Main.GameMainObject.DMD<Main.GameMainObject::Update> (Main.GameMainObject ) [0x00000] in <b926778010814c959998ed2627867f07>:0 

为什么游戏内Process的异常可以被捕获

先说为什么下面的GameProcess:OnUpdate,以及一切Process的OnUpdate等内置函数,可以引发游戏崩溃并引发AquaMai的错误报告窗口。以下代码节选自1.60的Manager.ProcessManager.Update ()的反编译:

  public void Update()
  {
    if (this._isException) return; // 只要曾发生过异常,就不再对任何Process进行Update了。这算是“游戏会崩溃”的一部分原因(详见下方备注)
    try
    {
      for (; linkedListNode != null; linkedListNode = linkedListNode.Next) // 注:linkedListNode是由所有Process构成的链表
      {
        // ......
            linkedListNode.Value.Process.OnUpdate();
        // ......
      }
    }
    catch (Exception ex)
    {
      // ......
      this._errorHandler(ex); // 调用游戏内置的错误处理函数
    }
  }

PS:上面提到“这算是‘游戏会崩溃’的一部分原因”,这是因为这里的逻辑是对所有Process都不再给予Update,如果只是这样的话行为应该是界面完全卡住、但窗口不会消失。所以似乎还有另一部分代码实现了关闭窗口这件事,这个我暂时还没找到,不过也不重要)

其中,this._errorHandler是一个Action,其函数实现来自GameMain.ExceptionHandler,就是被挂钩子的那个函数。
所以为什么“游戏自带的Process内的异常可以被捕获”的原因,其实是因为所有Process都是被ProcessManager调度的,而ProcessManager里面嵌入了内置的异常处理、发生异常时调用GameMain.ExceptionHandler的逻辑,而AquaMai.ErrorReport是通过钩住这个GameMain.ExceptionHandler实现的,所以只有会利用游戏内生的机制传递到GameMain.ExceptionHandler的异常才能被AquaMai看见。

PPS:看GameMain.ExceptionHandler的时候看到了一些熟悉的东西,我刚知道原来AquaMai的ErrorReport,包括错误发生时的截屏,原来就是这里来的啊。

  public void ExceptionHandler(Exception e)
  {
    // ......
    stringBuilder.AppendLine("Monitor List");
    foreach (UnityEngine.Object @object in Resources.FindObjectsOfTypeAll(typeof (MonitorBase)))
    {
      if (@object.hideFlags != HideFlags.NotEditable || @object.hideFlags != HideFlags.HideAndDontSave)
        stringBuilder.AppendLine(@object.name);
    }
    string str2 = this._processManager.Dump();
    // ......
    string errorLogPath = MAI2System.Path.ErrorLogPath;
    using (StreamWriter streamWriter = new StreamWriter($"{errorLogPath}{str3}.log", false))
    {
      streamWriter.WriteLine(e.Message);
      streamWriter.WriteLine(str1);
      streamWriter.Write(str2);
      // ......
    }
    // ......
    this._container.monoBehaviour.StartCoroutine(MAI2.Log.takeCrashScreenShot($"{errorLogPath}{str3}.png"));
    this._createError = true;
  }

为什么SetSpeed()等地方的异常无法被捕获

注意到它的调用栈只有三行:

  at AquaMai.Mods.UX.PracticeMode.PracticeMode.SetSpeed () [0x00000] in <1d0abc4c703447b8b813153c77654973>:0
  at AquaMai.Mods.UX.PracticeMode.PracticeMode.SpeedDown () [0x00000] in <1d0abc4c703447b8b813153c77654973>:0 
  at AquaMai.Mods.UX.PracticeMode.Libs.PracticeModeUI.Update () [0x00000] in <1d0abc4c703447b8b813153c77654973>:0 

其中最下面一行就直接是MonoBehaviourUpdate()钩子了。它是被Unity引擎直接利用引擎的代码调用的,更往下的调用栈没打出来、我大胆猜测因为是C++之类的根本不是C#代码的东西调用的。

所以包括SetSpeed(),以及还有一些其他地方在内,凡是我们自己创建了MonoBehaviour然后挂到GameObject的情况,
其中抛出的异常是都是被MonoBehaviour往下、直接传递到Unity引擎里面去了的。(以我浅薄的Unity开发知识,这是会导致Unity内置的那个LogConsole里打出一条error来。)所以我们从任何地方,无论是AquaMai的错误报告还是MelonLoader的log都看不到它,但我大胆猜测如果(用调试模式运行?)unity的话,是能在unity的console里看到它的。

以上!

@clansty
Copy link
Member

clansty commented Feb 3, 2026

哇 原来是这样
我其实并没有仔细分析过 有点想当然了
辛苦了!

@clansty clansty merged commit ae7c93b into MuNET-OSS:main Feb 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Dont ruin my account功能不足

2 participants