回退混淆

This commit is contained in:
wuwenbo 2026-05-29 20:43:46 +08:00
parent 2791d75490
commit 30d3d0999b
8 changed files with 239 additions and 86 deletions

View File

@ -0,0 +1,67 @@
# 001 MemberCiv MemoryPack formatter 被混淆
- 代表 Issue`b76a639f1ed68b9682abfadd03a1c69e`
- 类型:`InvalidOperationException`
- 当前版本次数5119同族合计5200
- 代表设备:`02-50-e2-57-ef-bc`
- 代表样本:发生 `2026-05-29 19:44:27`,上报 `19:49:09`
## 现象
CrashSight 报:
```text
Type implements IMemoryPackFormatterRegister but can not found RegisterFormatter. Type: fxl
```
解混淆结果:
```text
fxl = RuntimeData.MemberCiv
RuntimeData.MemberCiv::RegisterFormatter() -> moz
en.cpk = TH1_Logic.MatchConfig.MatchConfigManager.LoadMatchLevelData()
RuntimeData.MapConfig.gvo = RuntimeData.MapConfig.CheckMapConfigChanged()
```
## 前置链路
同设备最近上报显示,同一启动会话中先出现 `MemberCiv` formatter 注册失败,随后多个 Issue 被拆分上报:
- `8dc15e2856f92a990e26f50b07e1bd1f``EventManager Publish<UpdateUIOutsideMultiplayRoomSetting>` 包装的 `MemoryPackSerializationException`
- `93317bed3bc828790e1be710e9fc7831` / `01b3ddf1405bd4bd47c2d3e5bc85747f`:同一个 `InvalidOperationException`
- `b76a639f1ed68b9682abfadd03a1c69e`:最终高频刷出的 `InvalidOperationException`
最早业务入口在启动阶段:
```text
Main.Start
MatchConfigManager.Init
MatchConfigManager.LoadMatchLevelData
MemoryPackSerializer.Deserialize<MatchLevelData>
MapConfig.Deserialize
List<MemberCiv>.Deserialize
MemberCiv RegisterFormatter missing
```
之后在多人房间刷新阶段继续触发:
```text
MapConfig.CheckMapConfigChanged
MemoryPackSerializer.Serialize(MapConfig)
List<MemberCiv>.Serialize
MemberCiv RegisterFormatter missing
```
## 源码位置
- `Unity/Assets/Scripts/TH1_Data/MapData.cs:905``MemberCiv`
- `Unity/Assets/Scripts/TH1_Data/MapData.cs:921``RegisterFormatter`
- `Unity/Assets/Scripts/TH1_Logic/MatchConfig/MatchConfigManager.cs:50``LoadMatchLevelData`
- `Unity/Assets/Scripts/TH1_Data/MapData.cs:838``CheckMapConfigChanged`
## 根因
`MemberCiv` 使用 `[MemoryPackable(GenerateType.NoGenerate)]` 和手写 formatter。MemoryPack 运行时通过固定方法名 `RegisterFormatter` 注册 formatter但 0.7.2 的 OPS 映射显示该方法被重命名为 `moz`。因此运行时找不到注册入口。
建议给 `MemberCiv` 或至少 `RegisterFormatter` 加混淆排除,并确认构建后 OPS 映射不再改名。

View File

@ -0,0 +1,52 @@
# 002 多人房间 UI 空引用链
- 代表 Issue`d8a063f8bcbc06d36e8d722839a88bf1`
- 同族 Issue`17500e7928b92cb03088006333cb2436`, `4e0504e171679341e10b8508ed43e470`, `85a8a206d18e54098a20b278e8a97a32`, `35f02a6e9fbd5bdb156210cadd9f613b`, `632adc9d542a5b6ca862164dd8392df4`, `be87c0557f8597c99f803003fbd192c7`
- 当前版本次数72
- 代表设备:`7c-10-c9-3e-f4-7a`
## 现象
空引用集中在多人房间:
```text
UIOutsideMultiplayView.ChangeRoomSeatCount
UIOutsideMultiplayView.ReconcileRoomMembers
UIOutsideMultiplayView.SetMapConfig
UIOutsideMultiplayView.SetRoomInfoMember
UIOutsideMultiplayView.RefreshRoomInfo
```
解混淆结果:
```text
lxl = ChangeRoomSeatCount(int)
lxt = ReconcileRoomMembers()
lzd = SetMapConfig(bool)
lze = OnGameModeOptionClickedInternal(...)
bhw = SetRoomInfoMember()
bhv = RefreshRoomInfo()
```
## 前置链路
同设备最近上报显示,最近样本启动于 `2026-05-29 19:57:47`
- `19:57:48`:先报 `地图数据反序列化失败,可能是版本不兼容: fxl is failed in provider at creating formatter.`
- `19:58:37`:再报 `UIOutsideMultiplayView.ReconcileRoomMembers()` 空引用
- `19:58:39`:用户点击席位变化后报 `ChangeRoomSeatCount(int)` 空引用
这说明 UI 空引用是 `MemberCiv` formatter 问题打坏初始 `MapConfig`/房间席位状态后的级联症状,不是首因。
## 源码位置
- `Unity/Assets/Scripts/TH1_Logic/Core/Main.cs:160``MapConfig = MapData.GetMatchConfig(); if null GetMatchConfig(2)`
- `Unity/Assets/Scripts/TH1_Data/MapData.cs:2259`:本地 `match_config.dat` 反序列化失败后返回 null
- `Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideMultiplayView.cs:807``ReconcileRoomMembers`
- `Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideMultiplayView.cs:711``ChangeRoomSeatCount`
- `Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideMultiplayView.cs:1548``SetMapConfig`
## 建议
先修 `MemberCiv.RegisterFormatter` 混淆。随后给多人房间入口增加防御:`Main.Instance.MapConfig == null` 时不要进入房间设置刷新/席位变更,回退默认配置或显示初始化失败提示,避免根因残留时继续刷空引用。

View File

@ -0,0 +1,49 @@
# 003 Steamworks 未初始化仍刷新大厅
- 代表 Issue`341f705a3c788ba90fbf37396a4c9470`
- 同族 Issue`4e05127a1634bef7000f599ad35388bf`, `93c9ce84e1edba72583b2e7607f7bf7b`
- 当前版本次数3
- 当前版本影响设备1
- 代表设备:`84-a9-38-89-71-cc`
## 现象
0.7.2 只有 1 次直接 `InvalidOperationException`,另外 2 次是 UnityLogError 包装的同一异常:
```text
Steamworks is not initialized.
Steamworks.InteropHelp.TestIfAvailableClient
SteamMatchmaking.AddRequestLobbyListDistanceFilter
SteamLobbyManager.SearchPublicLobbies
UIOutsideMultiplayView.OnRefreshLobbyClicked
```
解混淆结果:
```text
bag.jdo = SteamLobbyManager.SearchPublicLobbies(...)
dio = UIOutsideMultiplayView.OnRefreshLobbyClicked()
bei = UIOutsideMultiplayView.SetNoRoom()
geh = UIOutsideMultiplayView.SetContent(ShowUIOutsideMultiplay)
```
## 前置链路
同设备最近上报显示,同一启动会话:
- `19:26:44``ShowUIOutsideMultiplay` 打开联机界面时触发刷新大厅
- `19:26:45`:离开房间/无房间状态又触发刷新大厅
- `19:26:50`:直接记录 `Steamworks is not initialized`
这些都发生在运行 9 秒内,说明 Steam 初始化尚未完成或不可用时UI 已经调用大厅搜索。
## 源码位置
- `Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideMultiplayView.cs:1936``OnRefreshLobbyClicked` 直接 `_lobby.SearchPublicLobbies()`
- `Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideMultiplayView.cs:352`:其他路径已有 `_lobby.IsInitialized()` 防护
- `Unity/Assets/Scripts/TH1_Logic/Steam/SteamLobbyManager.cs:1151``SearchPublicLobbies` 直接调用 Steam Matchmaking API
## 建议
`OnRefreshLobbyClicked``SearchPublicLobbies` 两层都加初始化保护:未初始化时不要调用 Steam Matchmaking并通过现有联网提示/大厅提示反馈给 UI。这个问题和 `MemberCiv` 混淆无关,优先级低于 5200 次的主因。

View File

@ -0,0 +1,28 @@
# CrashSight 0.7.2 阻断报错分析
- 捕获时间2026-05-29 20:20~20:30 UTC+8
- 筛选范围:`0.7.2`错误分析ERROR未处理/处理中,`start=0/10/20`
- 列表总数30 个 Issue当前版本累计 5275 次
- 分类30 个 blocking0 个纯 logerror
- 原始抓取:`Temp/CrashSight/Daily_2026-05-29/errors_0.7.2_status0_2_ERROR.json`
## 结论
当前新版本的大量阻断并不是 30 个独立问题,主要是 3 个阻断家族。
| 家族 | Issue 数 | 当前版本次数 | 源头 |
|---|---:|---:|---|
| MemberCiv MemoryPack formatter 被混淆 | 20 | 5200 | `RuntimeData.MemberCiv::RegisterFormatter()` 被 OPS 混淆成 `moz`MemoryPack 反射找不到固定方法名 |
| 多人房间 UI 空引用 | 7 | 72 | 同设备前置先发生 `MapConfig`/`MemberCiv` 反序列化失败,导致 `Main.Instance.MapConfig`/房间席位状态异常后继续刷新 UI |
| Steam 未初始化仍刷新大厅 | 3 | 3 | `UIOutsideMultiplayView.OnRefreshLobbyClicked()` 未判断 `_lobby.IsInitialized()`,直接调用 Steam Matchmaking |
## 最高优先级
先修 `MemberCiv` 的混淆保留。这个单点导致 `MatchLevelData` 和本地 `match_config.dat``MapConfig.MultiCivs` 反序列化失败,并级联出 20 个 MemoryPack Issue 与 7 个多人房间 UI 空引用。
## 报告
- [001 MemberCiv MemoryPack formatter](blocking/001_memberciv_memorypack_formatter.md)
- [002 UIOutsideMultiplayView null chain](blocking/002_multiplay_ui_null_chain.md)
- [003 Steamworks not initialized](blocking/003_steam_not_initialized.md)

View File

@ -0,0 +1,6 @@
# LogError Summary
当前筛选下 30 个 Issue 全部包含真实异常或被 `LogSystem.LogError` 包装的异常栈,因此本次没有纯诊断型 `logerror`
同设备最近上报中看到的 `CompleteExecute Player 不一致``Wrong With FragmentCityConnectExpUp` 等属于另一个问题集合,不在当前 `0.7.2` ERROR 列表 30 条主集内,本次未展开归因。

View File

@ -0,0 +1,33 @@
{
"date": "2026-05-29",
"filter": {
"version": "0.7.2",
"status": "0,2",
"exceptionCategoryList": "ERROR",
"starts": [0, 10, 20]
},
"totalIssues": 30,
"blockingIssues": 30,
"logerrorIssues": 0,
"currentVersionOccurrences": 5275,
"families": [
{
"id": "memberciv-memorypack-formatter",
"issueCount": 20,
"occurrences": 5200,
"rootCause": "RuntimeData.MemberCiv::RegisterFormatter was obfuscated to moz, so MemoryPack could not find the required RegisterFormatter method by reflection."
},
{
"id": "multiplay-ui-null-chain",
"issueCount": 7,
"occurrences": 72,
"rootCause": "UIOutsideMultiplayView null references are preceded by MapConfig/MemberCiv MemoryPack failures in the same device sessions."
},
{
"id": "steam-not-initialized",
"issueCount": 3,
"occurrences": 3,
"rootCause": "Multiplayer UI refresh calls SearchPublicLobbies before Steamworks is initialized."
}
]
}

View File

@ -902,12 +902,9 @@ namespace RuntimeData
}
[MemoryPackable(GenerateType.NoGenerate)]
public partial class MemberCiv : IMemoryPackable<MemberCiv>
[MemoryPackable]
public partial class MemberCiv
{
private const byte CurrentSerializedMemberCount = 9;
private const byte LegacySerializedMemberCountWithoutTeam = 8;
public ulong MemberId;
public uint PlayerId;
public uint CivId;
@ -917,87 +914,6 @@ namespace RuntimeData
public int TeamId;
public bool IsAI;
public bool IsCivFixed;
public static void RegisterFormatter()
{
MemoryPackFormatterProvider.Register(new MemberCivFormatter());
}
public static void Serialize(ref MemoryPackWriter writer, ref MemberCiv value)
{
if (value == null)
{
writer.WriteNullObjectHeader();
return;
}
writer.WriteObjectHeader(CurrentSerializedMemberCount);
writer.WriteUnmanaged(value.MemberId);
writer.WriteUnmanaged(value.PlayerId);
writer.WriteUnmanaged(value.CivId);
writer.WriteUnmanaged(value.ForceId);
writer.WriteUnmanaged(value.IsReady);
writer.WriteUnmanaged(value.Index);
writer.WriteUnmanaged(value.TeamId);
writer.WriteUnmanaged(value.IsAI);
writer.WriteUnmanaged(value.IsCivFixed);
}
public static void Deserialize(ref MemoryPackReader reader, ref MemberCiv value)
{
if (!reader.TryReadObjectHeader(out var memberCount))
{
value = null;
return;
}
if (memberCount != CurrentSerializedMemberCount
&& memberCount != LegacySerializedMemberCountWithoutTeam)
{
MemoryPackSerializationException.ThrowInvalidPropertyCount(
typeof(MemberCiv),
CurrentSerializedMemberCount,
memberCount);
}
value ??= new MemberCiv();
value.MemberId = reader.ReadUnmanaged<ulong>();
value.PlayerId = reader.ReadUnmanaged<uint>();
value.CivId = reader.ReadUnmanaged<uint>();
value.ForceId = reader.ReadUnmanaged<uint>();
value.IsReady = reader.ReadUnmanaged<bool>();
value.Index = reader.ReadUnmanaged<int>();
if (memberCount == LegacySerializedMemberCountWithoutTeam)
{
value.TeamId = GetLegacyDefaultTeamId(value.Index);
value.IsAI = reader.ReadUnmanaged<bool>();
value.IsCivFixed = reader.ReadUnmanaged<bool>();
}
else
{
value.TeamId = Math.Max(MapConfig.NoTeamId, reader.ReadUnmanaged<int>());
value.IsAI = reader.ReadUnmanaged<bool>();
value.IsCivFixed = reader.ReadUnmanaged<bool>();
}
}
private static int GetLegacyDefaultTeamId(int index)
{
return Math.Max(MapConfig.NoTeamId + 1, index + 1);
}
private sealed class MemberCivFormatter : MemoryPackFormatter<MemberCiv>
{
public override void Serialize(ref MemoryPackWriter writer, ref MemberCiv value)
{
MemberCiv.Serialize(ref writer, ref value);
}
public override void Deserialize(ref MemoryPackReader reader, ref MemberCiv value)
{
MemberCiv.Deserialize(ref reader, ref value);
}
}
}

View File

@ -159,6 +159,8 @@ namespace TH1_Logic.Core
// 构造初始化 MapConfig
MapConfig = MapData.GetMatchConfig();
if (MapConfig == null) MapConfig = MatchConfigManager.Instance.GetMatchConfig(2);
if (MapConfig == null) MapConfig = new MapConfig();
ConfirmMap = new PlayerConfirmMap();
// 模型暂时不需要