Skip to content

[BUG] SanitizeUserData 补齐 charaSlot 时未同步 charaLockSlot,返回标题时 UserDetail.Initialize 抛 IndexOutOfRangeException #150

Description

@Mofusigil

环境

  • AquaMai: v1.9.8-ge80a59d
  • 游戏: maimai DX 1.70(BUDDiES PLUS)
  • MelonLoader: 0.6.4
  • 连的是线上服,GetUserDataApi 下发的 userData 里 charaSlot / charaLockSlot 长度不一致

现象

2026-09-19 14:01:41,玩家打完一局,GameOver 结束返回标题(AdvertiseProcess.OnStart)时崩溃,弹出 AquaMai Crash Handler:

System.IndexOutOfRangeException: Index was outside the bounds of the array.
  at Manager.UserDatas.UserDetail.Initialize () [0x00131] in <24f6a64f2eeb42d4b6f9ae17d637d569>:0
  at Manager.UserData.Initialize () [0x0005e] in <24f6a64f2eeb42d4b6f9ae17d637d569>:0
  at (wrapper dynamic-method) Process.AdvertiseProcess.DMD<Process.AdvertiseProcess::OnStart>(Process.AdvertiseProcess)
  at Manager.ProcessManager.Update () [0x00032] in <24f6a64f2eeb42d4b6f9ae17d637d569>:0

同一档案 13:58:49 登录时有大量被 SanitizeUserData 修正的记录,说明该账号在服务器侧的数据本身就不完整:

[13:58:49] [SanitizeUserData] Set value of invalid int32 field iconId from 0 to 1
[13:58:49] [SanitizeUserData] Set value of invalid int32 field plateId from 0 to 1
[13:58:49] [SanitizeUserData] Set value of invalid int32 field titleId from 0 to 1
[13:58:49] [SanitizeUserData] Set value of invalid int32 field partnerId from 0 to 1
[13:58:49] [SanitizeUserData] Set value of invalid int32 field frameId from 0 to 1
[13:58:49] [SanitizeUserData] Set value of invalid int32 field selectMapId from 0 to 1
[13:58:51] [SanitizeUserData] Set value of invalid int32 field selectMusicId from null to 0

根因(反编译 Assembly-CSharp 确认)

Manager.UserDatas.UserDetail.Initialize()(无参重载,重置来宾状态)里:

for (int i = 0; i < this.CharaSlot.Length; i++)
{
    this.CharaSlot[i] = 0;
    this.CharaLockSlot[i] = 0;   // IL_0133:按 CharaSlot 的长度写 CharaLockSlot
}

循环边界只取 CharaSlot.Length,循环体却同时写 CharaLockSlot[i]。而 Net.VO.Mai2.VOExtensions.Export() 把服务器返回的 charaSlot / charaLockSlot 原样拷入 UserDetail,没有任何长度校验。

SanitizeUserData.OnUserDataResponse 目前会把 charaSlot 无条件补齐/截断到 5 个,但没有同步处理 charaLockSlot。当服务器下发的 charaLockSlot 少于 5 个(或为空数组)时,两边就出现 5 vs N 的长度差:游戏内流程不会立刻出错,但一返回标题重置用户数据就越界崩溃。

建议修复

在 OnUserDataResponse 里处理 charaSlot 的同时,把 charaLockSlot 补齐/截断到相同长度,例如:

var charaLockSlot = GetArrayOrSetDefault(userData, "charaLockSlot");
while (charaLockSlot.Count < charaSlot.Count)
{
    charaLockSlot.Add(new ProxyNumber(0));
}
userData["charaLockSlot"] = ToProxyArray(charaLockSlot.Take(charaSlot.Count));

临时规避

我先用一个 MelonLoader 小 mod 兜底(Harmony postfix UserDetail.set_CharaLockSlot + prefix UserDetail.Initialize,保证 CharaLockSlot.Length >= max(5, CharaSlot.Length)),加载后不再复现,退出与数据上传均正常。

完整崩溃日志(Errorlog/20260919140141.log)
Index was outside the bounds of the array.

ProcessManager Dump
Num	Pri	Name					State
0 	10	Process.CommonProcess	Update
1 	20	Process.GameOverProcess	Released
2 	20	Process.AdvertiseProcess	Start
3 	50	FadeProcess	Update
4 	51	Process.GenericProcess	Update

System.IndexOutOfRangeException: Index was outside the bounds of the array.
  at Manager.UserDatas.UserDetail.Initialize () [0x00131] in <24f6a64f2eeb42d4b6f9ae17d637d569>:0 
  at Manager.UserData.Initialize () [0x0005e] in <24f6a64f2eeb42d4b6f9ae17d637d569>:0 
  at (wrapper dynamic-method) Process.AdvertiseProcess.DMD<Process.AdvertiseProcess::OnStart>(Process.AdvertiseProcess)
  at Manager.ProcessManager.Update () [0x00032] in <24f6a64f2eeb42d4b6f9ae17d637d569>:0 
Monitor List
AdvertiseProcess(Clone)
AdvertiseProcess(Clone)
GameOverProcess(Clone)
GameOverProcess(Clone)
CommonProcess(Clone)
CommonProcess(Clone)
GenericProcess(Clone)
GenericProcess(Clone)
AdvertiseProcess
0总结算界面 prefab
Process

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions