mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Prevent invalid prototypes from being spawned (#4279)
This commit is contained in:
@@ -81,30 +81,35 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
private protected override EntityUid CreateEntity(string? prototypeName, EntityUid uid = default, IEntityLoadContext? context = null)
|
||||
{
|
||||
var entity = base.CreateEntity(prototypeName, uid, context);
|
||||
if (prototypeName == null)
|
||||
return base.CreateEntity(prototypeName, uid, context);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(prototypeName))
|
||||
{
|
||||
var prototype = PrototypeManager.Index<EntityPrototype>(prototypeName);
|
||||
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
|
||||
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
|
||||
|
||||
// At this point in time, all data configure on the entity *should* be purely from the prototype.
|
||||
// As such, we can reset the modified ticks to Zero,
|
||||
// which indicates "not different from client's own deserialization".
|
||||
// So the initial data for the component or even the creation doesn't have to be sent over the wire.
|
||||
foreach (var (netId, component) in GetNetComponents(entity))
|
||||
{
|
||||
// Make sure to ONLY get components that are defined in the prototype.
|
||||
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
|
||||
// And those aren't guaranteed to exist on the client, so don't clear them.
|
||||
var compName = ComponentFactory.GetComponentName(component.GetType());
|
||||
if (prototype.Components.ContainsKey(compName))
|
||||
component.ClearTicks();
|
||||
}
|
||||
}
|
||||
var entity = base.CreateEntity(prototype, uid, context);
|
||||
|
||||
// At this point in time, all data configure on the entity *should* be purely from the prototype.
|
||||
// As such, we can reset the modified ticks to Zero,
|
||||
// which indicates "not different from client's own deserialization".
|
||||
// So the initial data for the component or even the creation doesn't have to be sent over the wire.
|
||||
ClearTicks(entity, prototype);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void ClearTicks(EntityUid entity, EntityPrototype prototype)
|
||||
{
|
||||
foreach (var (netId, component) in GetNetComponents(entity))
|
||||
{
|
||||
// Make sure to ONLY get components that are defined in the prototype.
|
||||
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
|
||||
// And those aren't guaranteed to exist on the client, so don't clear them.
|
||||
var compName = ComponentFactory.GetComponentName(netId);
|
||||
if (prototype.Components.ContainsKey(compName))
|
||||
component.ClearTicks();
|
||||
}
|
||||
}
|
||||
|
||||
public override EntityStringRepresentation ToPrettyString(EntityUid uid)
|
||||
{
|
||||
TryGetComponent(uid, out ActorComponent? actor);
|
||||
|
||||
@@ -1034,26 +1034,20 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
while (query.MoveNext(out var uid, out var md))
|
||||
{
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
|
||||
if (md.EntityLastModifiedTick <= fromTick)
|
||||
continue;
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
|
||||
// Temporary debugging code.
|
||||
// TODO REMOVE TEMPORARY CODE
|
||||
if (state.Empty)
|
||||
{
|
||||
var msg = $"{nameof(GetEntityState)} returned an empty state while enumerating all. Entity {ToPrettyString(uid)}. Net component Data:";
|
||||
foreach (var (_, cmp) in EntityManager.GetNetComponents(uid))
|
||||
{
|
||||
msg += $"\nName: {_factory.GetComponentName(cmp.GetType())}" +
|
||||
$"Enabled: {cmp.NetSyncEnabled}, " +
|
||||
$"Lifestage: {cmp.LifeStage}, " +
|
||||
$"OwnerOnly: {cmp.SendOnlyToOwner}, " +
|
||||
$"SessionSpecific: {cmp.SessionSpecific}, " +
|
||||
$"LastModified: {cmp.LastModifiedTick}";
|
||||
}
|
||||
Log.Error(msg);
|
||||
Log.Error($@"{nameof(GetEntityState)} returned an empty state while enumerating entities.
|
||||
Tick: {fromTick}--{_gameTiming.CurTick}
|
||||
Entity: {ToPrettyString(uid)}
|
||||
Last modified: {md.EntityLastModifiedTick}
|
||||
Metadata last modified: {md.LastModifiedTick}
|
||||
Transform last modified: {Transform(uid).LastModifiedTick}");
|
||||
}
|
||||
|
||||
stateEntities.Add(state);
|
||||
@@ -1077,26 +1071,21 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick || md.EntityLastModifiedTick == GameTick.Zero);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick > fromTick || md.EntityLastModifiedTick == GameTick.Zero);
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
|
||||
// Temporary debugging code.
|
||||
// TODO REMOVE TEMPORARY CODE
|
||||
if (state.Empty)
|
||||
{
|
||||
var msg = $"{nameof(GetEntityState)} returned an empty state for new entity {ToPrettyString(uid)}. Net component Data:";
|
||||
foreach (var (_, cmp) in EntityManager.GetNetComponents(uid))
|
||||
{
|
||||
msg += $"\nName: {_factory.GetComponentName(cmp.GetType())}" +
|
||||
$"Enabled: {cmp.NetSyncEnabled}, " +
|
||||
$"Lifestage: {cmp.LifeStage}, " +
|
||||
$"OwnerOnly: {cmp.SendOnlyToOwner}, " +
|
||||
$"SessionSpecific: {cmp.SessionSpecific}, " +
|
||||
$"LastModified: {cmp.LastModifiedTick}";
|
||||
}
|
||||
Log.Error(msg);
|
||||
Log.Error($@"{nameof(GetEntityState)} returned an empty state for a new entity.
|
||||
Tick: {fromTick}--{_gameTiming.CurTick}
|
||||
Entity: {ToPrettyString(uid)}
|
||||
Last modified: {md.EntityLastModifiedTick}
|
||||
Metadata last modified: {md.LastModifiedTick}
|
||||
Transform last modified: {Transform(uid).LastModifiedTick}");
|
||||
continue;
|
||||
}
|
||||
|
||||
stateEntities.Add(state);
|
||||
@@ -1109,6 +1098,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick || md.EntityLastModifiedTick == GameTick.Zero);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick > fromTick || md.EntityLastModifiedTick == GameTick.Zero);
|
||||
|
||||
|
||||
@@ -328,6 +328,12 @@ namespace Robust.Shared.GameObjects
|
||||
return GetRegistration(componentType).Name;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public string GetComponentName(ushort netID)
|
||||
{
|
||||
return GetRegistration(netID).Name;
|
||||
}
|
||||
|
||||
public ComponentRegistration GetRegistration(ushort netID)
|
||||
{
|
||||
if (_networkedComponents is null)
|
||||
|
||||
@@ -751,8 +751,17 @@ namespace Robust.Shared.GameObjects
|
||||
if (prototypeName == null)
|
||||
return AllocEntity(out _, uid);
|
||||
|
||||
PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype);
|
||||
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
|
||||
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
|
||||
|
||||
return CreateEntity(prototype, uid, context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates an entity and loads components but does not do initialization.
|
||||
/// </summary>
|
||||
private protected EntityUid CreateEntity(EntityPrototype prototype, EntityUid uid = default, IEntityLoadContext? context = null)
|
||||
{
|
||||
var entity = AllocEntity(prototype, out var metadata, uid);
|
||||
try
|
||||
{
|
||||
@@ -764,7 +773,7 @@ namespace Robust.Shared.GameObjects
|
||||
// Exception during entity loading.
|
||||
// Need to delete the entity to avoid corrupt state causing crashes later.
|
||||
DeleteEntity(entity);
|
||||
throw new EntityCreationException($"Exception inside CreateEntity with prototype {prototypeName}", e);
|
||||
throw new EntityCreationException($"Exception inside CreateEntity with prototype {prototype.ID}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,6 +156,17 @@ namespace Robust.Shared.GameObjects
|
||||
/// </exception>
|
||||
[Pure]
|
||||
string GetComponentName(Type componentType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of a component, throwing an exception if it does not exist.
|
||||
/// </summary>
|
||||
/// <param name="netID">The network ID corresponding to the component.</param>
|
||||
/// <returns>The registered name of the component</returns>
|
||||
/// <exception cref="UnknownComponentException">
|
||||
/// Thrown if no component with id <see cref="netID"/> exists.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
string GetComponentName(ushort netID);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the registration belonging to a component, throwing an exception if it does not exist.
|
||||
|
||||
@@ -65,12 +65,12 @@ public sealed class PvsSystemTests : RobustIntegrationTest
|
||||
var mapCoords = new EntityCoordinates(map, new Vector2(2, 2));
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
player = sEntMan.SpawnEntity("", gridCoords);
|
||||
other = sEntMan.SpawnEntity("", gridCoords);
|
||||
player = sEntMan.SpawnEntity(null, gridCoords);
|
||||
other = sEntMan.SpawnEntity(null, gridCoords);
|
||||
otherXform = sEntMan.GetComponent<TransformComponent>(other);
|
||||
|
||||
// Ensure map PVS chunk is not empty
|
||||
sEntMan.SpawnEntity("", mapCoords);
|
||||
sEntMan.SpawnEntity(null, mapCoords);
|
||||
|
||||
// Attach player.
|
||||
var session = (IPlayerSession) sPlayerMan.Sessions.First();
|
||||
|
||||
@@ -84,7 +84,7 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var coords = new EntityCoordinates(grid1, new Vector2(0.5f, 0.5f));
|
||||
player = sEntMan.SpawnEntity("", coords);
|
||||
player = sEntMan.SpawnEntity(null, coords);
|
||||
var session = (IPlayerSession) sPlayerMan.Sessions.First();
|
||||
session.AttachToEntity(player);
|
||||
session.JoinGame();
|
||||
@@ -107,10 +107,10 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest
|
||||
var coords = new EntityCoordinates(grid2, new Vector2(0.5f, 0.5f));
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
entA = sEntMan.SpawnEntity("", coords);
|
||||
entB = sEntMan.SpawnEntity("", coords);
|
||||
childA = sEntMan.SpawnEntity("", new EntityCoordinates(entA, default));
|
||||
childB = sEntMan.SpawnEntity("", new EntityCoordinates(entB, default));
|
||||
entA = sEntMan.SpawnEntity(null, coords);
|
||||
entB = sEntMan.SpawnEntity(null, coords);
|
||||
childA = sEntMan.SpawnEntity(null, new EntityCoordinates(entA, default));
|
||||
childB = sEntMan.SpawnEntity(null, new EntityCoordinates(entB, default));
|
||||
});
|
||||
|
||||
await RunTicks();
|
||||
@@ -122,10 +122,10 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest
|
||||
EntityUid clientChildB = default;
|
||||
await client.WaitPost(() =>
|
||||
{
|
||||
entC = cEntMan.SpawnEntity("", coords);
|
||||
childC = cEntMan.SpawnEntity("", new EntityCoordinates(entC, default));
|
||||
clientChildA = cEntMan.SpawnEntity("", new EntityCoordinates(entA, default));
|
||||
clientChildB = cEntMan.SpawnEntity("", new EntityCoordinates(entB, default));
|
||||
entC = cEntMan.SpawnEntity(null, coords);
|
||||
childC = cEntMan.SpawnEntity(null, new EntityCoordinates(entC, default));
|
||||
clientChildA = cEntMan.SpawnEntity(null, new EntityCoordinates(entA, default));
|
||||
clientChildB = cEntMan.SpawnEntity(null, new EntityCoordinates(entB, default));
|
||||
});
|
||||
|
||||
await RunTicks();
|
||||
|
||||
@@ -74,7 +74,7 @@ public sealed class BroadphaseNetworkingTest : RobustIntegrationTest
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var coords = new EntityCoordinates(grid1, new Vector2(0.5f, 0.5f));
|
||||
player = sEntMan.SpawnEntity("", coords);
|
||||
player = sEntMan.SpawnEntity(null, coords);
|
||||
|
||||
// Enable physics
|
||||
var physics = sEntMan.AddComponent<PhysicsComponent>(player);
|
||||
|
||||
Reference in New Issue
Block a user