mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
fix: a cloned template carries its content instead of starting empty
Cloning a first-party template (RecCenter and friends) deliberately left the
new room with no blob, on the reasoning that its content is baked client
geometry and any blob is just a MakerPen overlay. That left the copy with
nothing to load.
The copy flow is what exposes it. IBEOONPEELF.GetRoomData / GetRoomMetadata
fetch "/room/{blob}" from the CDN as part of copying and throw a
BCPHBJHOIEM-family exception when that does not give them a usable room —
surfacing as the bare "Failed to copy room: Exception of type 'CEMNLBKJABA'"
with no message. For a name nothing is stored under, the CDN synthesises a
~1.8 KB stub: room_1583_dorknet_v8.dat served 1872 bytes where the source's
room_100_v1.dat is 10,930. The clone that DID work carried real content.
Since GiveCloneItsOwnBlobsAsync copies the bytes under the clone's own name,
the original worry — inheriting an overlay on a SHARED template — no longer
applies: nothing is shared either way. IsFirstPartyTemplate had no other
callers and is gone.
The existing assertion that a template clone must have NO blob encoded the
belief this replaces, so it now asserts the clone carries content. The
"doesn't share the source's object" half is covered by
RoomCloneShapeTests.Clone_does_not_reference_the_source_rooms_blob, which runs
with a real object store; this suite has none and deliberately falls back to
keeping the reference.
This commit is contained in:
@@ -198,8 +198,22 @@ public sealed class RoomSave2023Tests : IClassFixture<DorkNetServerFactory>
|
|||||||
var firstCloneId = await CloneAndAssertAsync(
|
var firstCloneId = await CloneAndAssertAsync(
|
||||||
roomsClient, roomId, "FirstClone", pathPrefix: "/roomserver");
|
roomsClient, roomId, "FirstClone", pathPrefix: "/roomserver");
|
||||||
Assert.NotEqual(roomId, firstCloneId);
|
Assert.NotEqual(roomId, firstCloneId);
|
||||||
await AssertBlobsAsync(firstCloneId, expectEmpty: true,
|
// A clone of a first-party template used to be left with NO blob, on
|
||||||
"first-party template blob must not be copied onto the clone");
|
// the reasoning that its content is baked client geometry. That left
|
||||||
|
// the copy with nothing to load: the CDN synthesises a ~1.8 KB stub for
|
||||||
|
// a name it has nothing stored under, and the client fetches exactly
|
||||||
|
// that through GetRoomData ("/room/{blob}") while copying, then rejects
|
||||||
|
// it. Clones that carried real content joined fine.
|
||||||
|
//
|
||||||
|
// The content now comes across. Sharing the SOURCE's object is still
|
||||||
|
// wrong, but that is prevented by copying it under the clone's own name
|
||||||
|
// rather than by having no blob at all — see
|
||||||
|
// RoomCloneShapeTests.Clone_does_not_reference_the_source_rooms_blob,
|
||||||
|
// which runs with a real object store. This suite has none, so the
|
||||||
|
// copy deliberately falls back to keeping the reference and only the
|
||||||
|
// "has content" half is checkable here.
|
||||||
|
await AssertBlobsAsync(firstCloneId, expectEmpty: false,
|
||||||
|
"a clone of a first-party template must carry its content");
|
||||||
|
|
||||||
// Simulate the player saving MakerPen edits into their clone.
|
// Simulate the player saving MakerPen edits into their clone.
|
||||||
var editedBlob = $"room_{firstCloneId}_edit_{Guid.NewGuid():N}.dat";
|
var editedBlob = $"room_{firstCloneId}_edit_{Guid.NewGuid():N}.dat";
|
||||||
|
|||||||
@@ -1460,15 +1460,20 @@ public class RoomsModerationController(
|
|||||||
// badge, and quest-instance logic can boot the player). Match
|
// badge, and quest-instance logic can boot the player). Match
|
||||||
// RoomService.CloneAsync and tag it "community".
|
// RoomService.CloneAsync and tag it "community".
|
||||||
TagsCsv = "community",
|
TagsCsv = "community",
|
||||||
// First-party RRO templates (AG rooms owned by the system
|
// Carry the source's blob NAME here; GiveCloneItsOwnBlobsAsync
|
||||||
// account — RecCenter and friends) keep their content in baked
|
// below replaces it with a copy under the clone's own name, so
|
||||||
// client geometry; any blob on the row is a MakerPen OVERLAY
|
// nothing ends up shared.
|
||||||
// save made against the shared template. A clone must start
|
//
|
||||||
// from the pristine baked scene with a fresh (empty) blob, not
|
// First-party RRO templates (RecCenter and friends) used to be
|
||||||
// inherit that overlay. Scoped to system-owned templates so
|
// emptied here on the reasoning that their content is baked client
|
||||||
// copying a USER's RRO-derived room (IsAGRoom inherited) still
|
// geometry and any blob is just a MakerPen overlay. That left the
|
||||||
// carries their MakerPen edits along.
|
// clone with no data at all: the CDN synthesises a ~1.8 KB stub for
|
||||||
CurrentDataBlobName = IsFirstPartyTemplate(source) ? string.Empty : source.CurrentDataBlobName,
|
// a name it has nothing stored under, the client fetches it through
|
||||||
|
// GetRoomData ("/room/{blob}") as part of the copy, and rejects it.
|
||||||
|
// A clone that kept real content joined fine. Since the blob is now
|
||||||
|
// copied rather than referenced, the original worry — inheriting an
|
||||||
|
// overlay on a SHARED template — no longer applies.
|
||||||
|
CurrentDataBlobName = source.CurrentDataBlobName,
|
||||||
};
|
};
|
||||||
db.Rooms.Add(clone);
|
db.Rooms.Add(clone);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
@@ -1493,9 +1498,9 @@ public class RoomsModerationController(
|
|||||||
OrderIndex = s.OrderIndex,
|
OrderIndex = s.OrderIndex,
|
||||||
Name = s.Name,
|
Name = s.Name,
|
||||||
RoomSceneLocationId = s.RoomSceneLocationId,
|
RoomSceneLocationId = s.RoomSceneLocationId,
|
||||||
// Fresh blob when cloning a first-party template — see
|
// See CurrentDataBlobName above: copied under the clone's own
|
||||||
// CurrentDataBlobName above.
|
// name by GiveCloneItsOwnBlobsAsync, never shared.
|
||||||
DataBlobName = IsFirstPartyTemplate(source) ? string.Empty : s.DataBlobName,
|
DataBlobName = s.DataBlobName,
|
||||||
StudioSubRoomDataSaveId = s.StudioSubRoomDataSaveId,
|
StudioSubRoomDataSaveId = s.StudioSubRoomDataSaveId,
|
||||||
StudioUnityAssetId = s.StudioUnityAssetId,
|
StudioUnityAssetId = s.StudioUnityAssetId,
|
||||||
StudioAssetBundleNamesCsv = s.StudioAssetBundleNamesCsv,
|
StudioAssetBundleNamesCsv = s.StudioAssetBundleNamesCsv,
|
||||||
@@ -1661,11 +1666,6 @@ public class RoomsModerationController(
|
|||||||
? RoomService.SyntheticDefaultRoomDataBlobName(roomId)
|
? RoomService.SyntheticDefaultRoomDataBlobName(roomId)
|
||||||
: $"room_{roomId}_dorknet_v8_sub{orderIndex}.dat";
|
: $"room_{roomId}_dorknet_v8_sub{orderIndex}.dat";
|
||||||
|
|
||||||
/// <summary>A seeded first-party RRO template (RecCenter, the games):
|
|
||||||
/// AG-flagged AND owned by the system account. User clones inherit the
|
|
||||||
/// AG flag but are owned by the player, so they don't match.</summary>
|
|
||||||
private static bool IsFirstPartyTemplate(RoomEntity room)
|
|
||||||
=> room.IsAGRoom && room.CreatorPlayerId == PlayerService.SystemAccountId;
|
|
||||||
|
|
||||||
private async Task<string> ReadCloneNameAsync()
|
private async Task<string> ReadCloneNameAsync()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user