fix: nested room-details objects carry only keys the client reads

Extends the parity check into the payload's nested objects — sub-room 0, the
first role, stats and the first tag each have their own generated reader, and a
key set that disagrees with one of those breaks the room just as hard as a
wrong top-level set.

It immediately found seven invented keys. Six on every sub-room (CurrentSave,
DefaultMatchmakingPolicy, LastModeratedSaveModerationState, ShouldAutoStageSaves,
StagedSubRoomDataSaveId, SubRoomUnityAssetId) and IsPrimaryGenre on every tag,
whose real shape is exactly {Tag, Type}. Six of the seven appear NOWHERE in the
decompile; SubRoomUnityAssetId exists but belongs to another DTO, not this one.

The extractor's minimum field count drops from 4 to 2, which is what let this
hide: a room tag has two fields, so a 4-field floor skipped its reader entirely
and the invented third key had nothing to be checked against. The alias rule in
fields_in() is what rejects false positives — the field count never was.

This does not explain the clone failure on its own: the captured response
carries all 41 top-level keys and is still rejected. But it removes the whole
class of "we send something the reader has no slot for" from the search.
This commit is contained in:
Alexa
2026-07-28 11:27:35 +03:00
parent 078d576383
commit 4a1ac47007
4 changed files with 1103 additions and 47 deletions
@@ -1795,7 +1795,6 @@ public class RoomsController(
{
Type = RoomTagTypeForWire(t),
Tag = t,
IsPrimaryGenre = false,
})
.ToArray();
@@ -2006,7 +2005,6 @@ public class RoomsController(
RoomId = room.Id,
Name = s.Name,
UnityAssetId = s.StudioUnityAssetId,
SubRoomUnityAssetId = s.StudioUnityAssetId,
UnityAsset = unityAsset?.Filename ?? string.Empty,
UnityAssetHash = string.Empty,
DataBlob = dataBlob,
@@ -2017,36 +2015,6 @@ public class RoomsController(
MaxPlayers = s.MaxPlayers,
Accessibility = room.Accessibility,
UnitySceneId = s.RoomSceneLocationId,
CurrentSave = new
{
SubRoomDataSaveId = s.StudioSubRoomDataSaveId ?? 0L,
SubRoomId = (long)s.OrderIndex,
UnityAssetId = s.StudioUnityAssetId,
SubRoomUnityAssetId = s.StudioUnityAssetId,
CreatedByAccountId = room.CreatorPlayerId,
ReferencedUnityAssetIds = Array.Empty<string>(),
DataBlob = dataBlob,
DataBlobHash = (string?)null,
PersistenceVersion = saveVersion,
OMVersion = 0,
SavedByAccountId = room.CreatorPlayerId,
SavedOnPlatform = 0,
SavedOnDeviceClass = 2,
Description = string.Empty,
ModerationState = 0,
CreatedAt = (s.DataModifiedAt == default ? DateTime.UtcNow : s.DataModifiedAt)
.ToString("yyyy-MM-ddTHH:mm:ssZ"),
UgcSubVersion = saveVersion,
UnityAssetHash = string.Empty,
UnityAsset = unityAssetParent,
UnityAssetFilename = unityAsset?.Filename ?? string.Empty,
BakedUnityAssets = bakedAssets,
UnitySubAssets = Array.Empty<object>(),
},
LastModeratedSaveModerationState = 0,
DefaultMatchmakingPolicy = 0,
ShouldAutoStageSaves = true,
StagedSubRoomDataSaveId = (long?)null,
};
}).ToArray()
: new object[]
@@ -2063,7 +2031,6 @@ public class RoomsController(
RoomId = room.Id,
Name = "Home",
UnityAssetId = string.Empty,
SubRoomUnityAssetId = string.Empty,
UnityAsset = string.Empty,
UnityAssetHash = string.Empty,
// Same rule as the populated branch: honour the override,
@@ -2076,15 +2043,6 @@ public class RoomsController(
MaxPlayers = 8,
Accessibility = room.Accessibility,
UnitySceneId = room.LocationReplicationId,
CurrentSave = new
{
SubRoomDataSaveId = 0L,
SubRoomId = 0L,
UnityAssetId = string.Empty,
SubRoomUnityAssetId = string.Empty,
ReferencedUnityAssetIds = Array.Empty<string>(),
DataBlob = dataBlobName,
},
},
};
@@ -2102,7 +2060,6 @@ public class RoomsController(
{
Type = RoomTagTypeForWire(t),
Tag = t,
IsPrimaryGenre = false,
})
.ToArray();
@@ -3384,7 +3341,6 @@ public class RoomsController(
{
Type = RoomTagTypeForWire(t),
Tag = t,
IsPrimaryGenre = false,
})
.ToList();