feat: Update room version clamping logic; adjust maximum persisted room version for March-2023 client compatibility

This commit is contained in:
Alexa
2026-07-17 00:16:54 +03:00
parent e93bbb14cb
commit 674a3fc257
3 changed files with 40 additions and 15 deletions
@@ -49,7 +49,7 @@ public sealed class RoomBlobVersionClampTests
Assert.True(changed);
var msg = PersistedRoomData.Parser.ParseFrom(bytes);
Assert.Equal(PersistedRoomVersion.LatestVersion, msg.Version);
Assert.Equal(RoomDataBlobService.Client2023MaxPersistedRoomVersion, (int)msg.Version);
Assert.Equal(DEPRECATED_RoomPersistenceVersion.LatestRoomPersistenceVersion, msg.DEPRECATEDVersion);
}
@@ -60,17 +60,21 @@ public sealed class RoomBlobVersionClampTests
var (bytes, _) = RoomDataBlobService.ClampVersionsFor2023(input);
// version=131 is a two-byte varint, 19 is one byte — the output
// version=131 is a two-byte varint, 16 is one byte — the output
// shrinks by exactly that and matches the input everywhere else.
Assert.Equal(input.Length - 1, bytes.Length);
var expected = BuildModernBlob(deprecatedVersion: 38, version: 19);
var expected = BuildModernBlob(
deprecatedVersion: 38,
version: RoomDataBlobService.Client2023MaxPersistedRoomVersion);
Assert.Equal(expected, bytes);
}
[Fact]
public void Blob_already_at_2023_versions_passes_through_unchanged()
{
var input = BuildModernBlob(deprecatedVersion: 38, version: 19);
var input = BuildModernBlob(
deprecatedVersion: 38,
version: RoomDataBlobService.Client2023MaxPersistedRoomVersion);
var (bytes, changed) = RoomDataBlobService.ClampVersionsFor2023(input);
+20 -8
View File
@@ -126,16 +126,28 @@ public class RoomDataBlobService
if (pos > input.Length) throw new EndOfStreamException("Unexpected end of protobuf field.");
}
/// <summary>Highest <c>PersistedRoomVersion</c> (field 30) the running
/// March-2023 client (build 2023-03-21) actually accepts. NOT the same
/// as our proto's <see cref="PersistedRoomVersion.LatestVersion"/> (19):
/// that enum came from a runtime dump of a NEWER build, and this client
/// rejects 19 — its real ceiling is 16 (observed on the .room blobs of
/// rooms this client loads without complaint). Clamp to this, not the
/// proto's Latest.</summary>
public const int Client2023MaxPersistedRoomVersion = 16;
/// <summary>Clamp the two top-level version varints of a
/// <c>PersistedRoomData</c> blob to the newest values the March-2023
/// client knows: field 1 (<c>DEPRECATED_RoomPersistenceVersion</c>,
/// max 38) and field 30 (<c>PersistedRoomVersion</c>, max 19 =
/// V19February23BetaRelease). Modern RecNet exports stamp much higher
/// values (a Sep-2025 save carries version=131) and the client refuses
/// the whole room with its "update Rec Room to visit this room" gate.
/// Pure wire-format rewrite — every other byte is preserved verbatim,
/// so blobs already at or below the 2023 versions round-trip
/// unchanged. On malformed input the original bytes are returned.</summary>
/// max 38) and field 30 (<c>PersistedRoomVersion</c>, max
/// <see cref="Client2023MaxPersistedRoomVersion"/> = 16). Modern RecNet
/// exports stamp much higher values (a Sep-2025 save carries
/// version=131) and the client refuses the whole room with its
/// "update Rec Room to visit this room" gate. Pure wire-format rewrite —
/// every other byte is preserved verbatim, so blobs already at or below
/// the 2023 versions round-trip unchanged. On malformed input the
/// original bytes are returned. NOTE: this only addresses the room-HEADER
/// version gate; a room whose CircuitsV2 graph (field 18) is too new is
/// rejected regardless and this clamp cannot help it.</summary>
public static (byte[] Bytes, bool Changed) ClampVersionsFor2023(byte[] input)
{
try
@@ -154,7 +166,7 @@ public class RoomDataBlobService
var value = ReadVarint(input, ref pos);
var max = fieldNumber == 1
? (ulong)LatestDeprecatedPersistenceVersion
: (ulong)LatestPersistenceVersion;
: (ulong)Client2023MaxPersistedRoomVersion;
if (value > max)
{
value = max;
+12 -3
View File
@@ -117,9 +117,18 @@ immediately.
RecNet zip exports carry room-data blobs whose top-level version varints
(field 1 `DEPRECATED_RoomPersistenceVersion`, field 30
`PersistedRoomVersion`) are far past what the March-2023 client knows —
a Sep-2025 save stamps `version=131` against the client's maximum of 19
(`V19February23BetaRelease`) — and the client rejects the whole room with
its "update Rec Room to visit this room" gate before spawning anything.
a Sep-2025 save stamps `version=131` against the client's maximum of 16
(`RoomDataBlobService.Client2023MaxPersistedRoomVersion`; the proto's
`LatestVersion=19` came from a newer build's dump and this client rejects
it) — and the client rejects the whole room with its "update Rec Room to
visit this room" gate before spawning anything.
This clamp only addresses the room-**header** version gate. A room whose
CircuitsV2 chip graph (`circuit_data`, field 18) was saved in a newer Rec
Room is rejected by a separate CircuitsV2 version gate regardless — check
the client's `Player.log`, where the stack frame under "Booting player to
dorm" names the failing subsystem (`CircuitsV2Manager` = the circuit
graph, not the header). That case is not fixable by this clamp.
While the clamp is active (the default), the CDN serve path rewrites the
two varints down to the 2023 maxima on the way out for `.room` / `.meta` /