Compare commits

...

18 Commits

Author SHA1 Message Date
PJB3005
cd90cc6e26 Version: 260.2.3 2025-09-26 13:40:44 +02:00
PJB3005
3923dd39ae Validate that content assemblies have a limited list of names.
Also, only read assemblies once from disk

(cherry picked from commit 443a8dfca65be7d60c4bd46181b4c749b4756114)
2025-09-26 13:40:44 +02:00
PJB3005
3caffa04da Version: 260.2.2 2025-09-19 09:17:28 +02:00
Skye
06b377d1d5 Fix resource loading on non-Windows platforms (#6201)
(cherry picked from commit 51bbc5dc45)
2025-09-19 09:17:28 +02:00
PJB3005
41fb191dda Version: 260.2.1 2025-09-14 14:55:52 +02:00
PJB3005
d4bcc1dc05 Squashed commit of the following:
commit d4f265c314
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sun Sep 14 14:32:44 2025 +0200

    Fix incorrect path combine in DirLoader and WritableDirProvider

    This (and the other couple past commits) reported by Elelzedel.

commit 7654d38612
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 22:50:51 2025 +0200

    Move CEF cache out of data directory

    Don't want content messing with this...

commit cdcc255123
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:11:16 2025 +0200

    Make Robust.Client.WebView.Cef.Program internal.

commit 2f56a6a110
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:10:46 2025 +0200

    Update SpaceWizards.NFluidSynth to 0.2.2

commit 16fc48cef2
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:09:43 2025 +0200

    Hide IWritableDirProvider.RootDir on client

    This shouldn't be exposed.

(cherry picked from commit 2f07159336bc640e41fbbccfdec4133a68c13bdb)
(cherry picked from commit d6c3212c74373ed2420cc4be2cf10fcd899c2106)
(cherry picked from commit bfa70d7e2ca6758901b680547fcfa9b24e0610b7)
(cherry picked from commit 06e52f5d58efc1491915822c2650f922673c82c6)
2025-09-14 14:55:51 +02:00
metalgearsloth
84dcd658aa Version: 260.2.0 2025-05-21 23:30:58 +10:00
metalgearsloth
a634d6bd04 Add WorldNormal to StartCollideEvent (#5954)
We already have the value just a matter of adding it to the event.
2025-05-21 20:41:57 +10:00
DrSmugleaf
36f9df3079 Add System.Text.StringBuilder Insert(int, string) to sandbox.yml (#5955) 2025-05-21 11:20:10 +02:00
keronshb
824c018a69 Version: 260.1.0 2025-05-19 13:11:32 -04:00
Tayrtahn
4b6b688c72 Cleanup warnings in PlacementManager (#5939) 2025-05-18 19:14:16 +10:00
Tayrtahn
71df25b251 Cleanup warning in Clyde.Sprite (#5940) 2025-05-18 18:51:27 +10:00
metalgearsloth
be14a3c249 Expose CompFactory to systems (#5941) 2025-05-18 00:56:09 -04:00
metalgearsloth
3c2a4d5c79 Version: 260.0.0 2025-05-18 03:07:24 +10:00
metalgearsloth
44180b3ee0 Fix / remove startcollidevent worldpoint (#5936)
Now it's worldpoints because it may not necessarily be 1 pointr and internally we fix the actual points themselves.
2025-05-18 03:03:12 +10:00
metalgearsloth
bb0e77e937 Add some EntProtoId overloads (#5938)
Need it for some content stuff didn't feel like doing the rest yet.
2025-05-17 18:28:12 +10:00
ArtisticRoomba
684b9bc852 Add new Vertical property to progress bars (#5932) 2025-05-17 18:27:44 +10:00
Tayrtahn
9f3db6693e Add SpriteSystem dependency to VisualizerSystem (#5935)
* Add protected SpriteSystem reference to VisualizerSystem

* Capital S
2025-05-17 13:26:40 +10:00
32 changed files with 248 additions and 83 deletions

View File

@@ -57,7 +57,7 @@
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />

View File

@@ -1,4 +1,4 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<!-- This file automatically reset by Tools/version.py -->

View File

@@ -54,6 +54,47 @@ END TEMPLATE-->
*None yet*
## 260.2.3
## 260.2.2
## 260.2.1
## 260.2.0
### New features
* Add `StringBuilder.Insert(int, string)` to sandbox.
* Add the WorldNormal to the StartCollideEvent.
## 260.1.0
### New features
* `ComponentFactory` is now exposed to `EntitySystem` as `Factory`
### Other
* Cleanup warnings in PLacementManager
* Cleanup warnings in Clide.Sprite
## 260.0.0
### Breaking changes
* Fix / change `StartCollideEvent.WorldPoint` to return all points for the collision which may be up to 2 instead of 1.
### New features
* Add SpriteSystem dependency to VisualizerSystem.
* Add Vertical property to progress bars
* Add some `EntProtoId` overloads for group entity spawn methods.
## 259.0.0
### Breaking changes

View File

@@ -6,7 +6,7 @@ using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
{
public static class Program
internal static class Program
{
// This was supposed to be the main entry for the subprocess program... It doesn't work.
public static int Main(string[] args)

View File

@@ -5,6 +5,7 @@ using System.Net;
using System.Reflection;
using System.Text;
using Robust.Client.Console;
using Robust.Client.Utility;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameControllerInternal _gameController = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -61,7 +63,10 @@ namespace Robust.Client.WebView.Cef
var cachePath = "";
if (_resourceManager.UserData is WritableDirProvider userData)
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
{
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
cachePath = Path.Combine(rootDir, "cef_cache", "0");
}
var settings = new CefSettings()
{

View File

@@ -387,7 +387,7 @@ namespace Robust.Client
_prof.Initialize();
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)

View File

@@ -11,6 +11,7 @@ public abstract class VisualizerSystem<T> : EntitySystem
{
[Dependency] protected readonly AppearanceSystem AppearanceSystem = default!;
[Dependency] protected readonly AnimationPlayerSystem AnimationSystem = default!;
[Dependency] protected readonly SpriteSystem SpriteSystem = default!;
public override void Initialize()
{

View File

@@ -153,7 +153,7 @@ internal partial class Clyde
// special casing angle = n*pi/2 to avoid box rotation & bounding calculations doesn't seem to give significant speedups.
data.SpriteScreenBB = TransformCenteredBox(
data.Sprite.Bounds,
_spriteSystem.GetLocalBounds((data.Uid, data.Sprite)),
finalRotation,
pos + batch.PreScaleViewOffset,
batch.ViewScale);

View File

@@ -10,6 +10,7 @@ internal sealed partial class Clyde
private MapSystem _mapSystem = default!;
private LightTreeSystem _lightTreeSystem = default!;
private TransformSystem _transformSystem = default!;
private SpriteSystem _spriteSystem = default!;
private SpriteTreeSystem _spriteTreeSystem = default!;
private ClientOccluderSystem _occluderSystem = default!;
@@ -24,6 +25,7 @@ internal sealed partial class Clyde
_mapSystem = _entitySystemManager.GetEntitySystem<MapSystem>();
_lightTreeSystem = _entitySystemManager.GetEntitySystem<LightTreeSystem>();
_transformSystem = _entitySystemManager.GetEntitySystem<TransformSystem>();
_spriteSystem = _entitySystemManager.GetEntitySystem<SpriteSystem>();
_spriteTreeSystem = _entitySystemManager.GetEntitySystem<SpriteTreeSystem>();
_occluderSystem = _entitySystemManager.GetEntitySystem<ClientOccluderSystem>();
}
@@ -33,6 +35,7 @@ internal sealed partial class Clyde
_mapSystem = null!;
_lightTreeSystem = null!;
_transformSystem = null!;
_spriteSystem = null!;
_spriteTreeSystem = null!;
_occluderSystem = null!;
}

View File

@@ -47,6 +47,7 @@ namespace Robust.Client.Placement
private SharedMapSystem Maps => EntityManager.System<SharedMapSystem>();
private SharedTransformSystem XformSystem => EntityManager.System<SharedTransformSystem>();
private SpriteSystem Sprite => EntityManager.System<SpriteSystem>();
/// <summary>
/// How long before a pending tile change is dropped.
@@ -711,11 +712,11 @@ namespace Robust.Client.Placement
CurrentPlacementOverlayEntity = null;
}
private SpriteComponent SetupPlacementOverlayEntity()
private Entity<SpriteComponent> SetupPlacementOverlayEntity()
{
EnsureNoPlacementOverlayEntity();
CurrentPlacementOverlayEntity = EntityManager.SpawnEntity(null, MapCoordinates.Nullspace);
return EntityManager.EnsureComponent<SpriteComponent>(CurrentPlacementOverlayEntity.Value);
return (CurrentPlacementOverlayEntity.Value, EntityManager.EnsureComponent<SpriteComponent>(CurrentPlacementOverlayEntity.Value));
}
private void PreparePlacement(string templateName)
@@ -732,10 +733,16 @@ namespace Robust.Client.Placement
EntityManager.GetComponent<MetaDataComponent>(CurrentPlacementOverlayEntity.Value));
}
public void PreparePlacementSprite(SpriteComponent sprite)
public void PreparePlacementSprite(Entity<SpriteComponent> sprite)
{
var sc = SetupPlacementOverlayEntity();
sc.CopyFrom(sprite);
Sprite.CopySprite(sprite.AsNullable(), sc.AsNullable());
}
[Obsolete("Use the Entity<SpriteComponent> overload.")]
public void PreparePlacementSprite(SpriteComponent sprite)
{
PreparePlacementSprite((sprite.Owner, sprite));
}
public void PreparePlacementTexList(List<IDirectionalTextureProvider>? texs, bool noRot, EntityPrototype? prototype)
@@ -746,27 +753,27 @@ namespace Robust.Client.Placement
// This one covers most cases (including Construction)
foreach (var v in texs)
{
if (v is RSI.State)
if (v is RSI.State st)
{
var st = (RSI.State) v;
sc.AddLayer(st.StateId, st.RSI);
Sprite.AddRsiLayer(sc.AsNullable(), st.StateId, st.RSI);
}
else
{
// Fallback
sc.AddLayer(v.Default);
Sprite.AddTextureLayer(sc.AsNullable(), v.Default);
}
}
}
else
{
sc.AddLayer(new ResPath("/Textures/Interface/tilebuildoverlay.png"));
Sprite.AddTextureLayer(sc.AsNullable(), new ResPath("/Textures/Interface/tilebuildoverlay.png"));
}
sc.NoRotation = noRot;
sc.Comp.NoRotation = noRot;
if (prototype != null && prototype.TryGetComponent<SpriteComponent>("Sprite", out var spriteComp))
{
sc.Scale = spriteComp.Scale;
Sprite.SetScale(sc.AsNullable(), spriteComp.Scale);
}
}
@@ -774,7 +781,7 @@ namespace Robust.Client.Placement
private void PreparePlacementTile()
{
var sc = SetupPlacementOverlayEntity();
sc.AddLayer(new ResPath("/Textures/Interface/tilebuildoverlay.png"));
Sprite.AddTextureLayer(sc.AsNullable(), new ResPath("/Textures/Interface/tilebuildoverlay.png"));
IsActive = true;
}

View File

@@ -166,7 +166,7 @@ namespace Robust.Client.Player
{
if (_client.RunLevel != ClientRunLevel.SinglePlayerGame)
Sawmill.Warning($"Attaching local player to an entity {EntManager.ToPrettyString(uid)} without an eye. This eye will not be netsynced and may cause issues.");
var eye = (EyeComponent) Factory.GetComponent(typeof(EyeComponent));
var eye = Factory.GetComponent<EyeComponent>();
eye.NetSyncEnabled = false;
EntManager.AddComponent(uid.Value, eye);
}

View File

@@ -14,6 +14,27 @@ namespace Robust.Client.UserInterface.Controls
private StyleBox? _backgroundStyleBoxOverride;
private StyleBox? _foregroundStyleBoxOverride;
private bool _vertical;
/// <summary>
/// Whether the progress bar is oriented vertically.
/// </summary>
/// <remarks>
/// A vertical progress bar fills from bottom to top.
/// </remarks>
public bool Vertical
{
get => _vertical;
set
{
if (_vertical != value)
{
_vertical = value;
InvalidateMeasure();
}
}
}
public StyleBox? BackgroundStyleBoxOverride
{
get => _backgroundStyleBoxOverride;
@@ -70,11 +91,23 @@ namespace Robust.Client.UserInterface.Controls
{
return;
}
var minSize = fg.MinimumSize;
var size = PixelWidth * GetAsRatio() - minSize.X;
if (size > 0)
if (_vertical)
{
fg.Draw(handle, UIBox2.FromDimensions(0, 0, minSize.X + size, PixelHeight), UIScale);
var size = PixelHeight * GetAsRatio();
if (size > 0)
{
fg.Draw(handle, UIBox2.FromDimensions(0, PixelHeight - size, PixelWidth, size), UIScale);
}
}
else
{
var minSize = fg.MinimumSize;
var size = PixelWidth * GetAsRatio() - minSize.X;
if (size > 0)
{
fg.Draw(handle, UIBox2.FromDimensions(0, 0, minSize.X + size, PixelHeight), UIScale);
}
}
}

View File

@@ -297,7 +297,7 @@ namespace Robust.Server
: null;
// Set up the VFS
_resources.Initialize(dataDir);
_resources.Initialize(dataDir, hideUserDataDir: false);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;

View File

@@ -6,4 +6,3 @@
[assembly: InternalsVisibleTo("Robust.Client")]
[assembly: InternalsVisibleTo("Robust.UnitTesting")]
[assembly: InternalsVisibleTo("Content.Benchmarks")]

View File

@@ -88,6 +88,7 @@ namespace Robust.Shared.ContentPack
public string SystemAssemblyName = default!;
public HashSet<VerifierError> AllowedVerifierErrors = default!;
public List<string> WhitelistedNamespaces = default!;
public List<string> AllowedAssemblyPrefixes = default!;
public Dictionary<string, Dictionary<string, TypeConfig>> Types = default!;
}

View File

@@ -131,6 +131,16 @@ namespace Robust.Shared.ContentPack
return false;
}
#pragma warning disable RA0004
var loadedConfig = _config.Result;
#pragma warning restore RA0004
if (!loadedConfig.AllowedAssemblyPrefixes.Any(allowedNamePrefix => asmName.StartsWith(allowedNamePrefix)))
{
_sawmill.Error($"Assembly name '{asmName}' is not allowed for a content assembly");
return false;
}
if (VerifyIL)
{
if (!DoVerifyIL(asmName, resolver, peReader, reader))
@@ -179,10 +189,6 @@ namespace Robust.Shared.ContentPack
return true;
}
#pragma warning disable RA0004
var loadedConfig = _config.Result;
#pragma warning restore RA0004
var badRefs = new ConcurrentBag<EntityHandle>();
// We still do explicit type reference scanning, even though the actual whitelists work with raw members.

View File

@@ -60,9 +60,7 @@ namespace Robust.Shared.ContentPack
internal string GetPath(ResPath relPath)
{
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()))
// Sanitise platform-specific path and standardize it for engine use.
.Replace(Path.DirectorySeparatorChar, '/');
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
}
/// <inheritdoc />

View File

@@ -14,7 +14,11 @@ namespace Robust.Shared.ContentPack
/// The directory to use for user data.
/// If null, a virtual temporary file system is used instead.
/// </param>
void Initialize(string? userData);
/// <param name="hideUserDataDir">
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
/// <see cref="IResourceManager.UserData"/>.
/// </param>
void Initialize(string? userData, bool hideUserDataDir);
/// <summary>
/// Mounts a single stream as a content file. Useful for unit testing.

View File

@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
{
/// <summary>
/// The root path of this provider.
/// Can be null if it's a virtual provider.
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
/// </summary>
string? RootDir { get; }

View File

@@ -93,19 +93,23 @@ namespace Robust.Shared.ContentPack
{
var sw = Stopwatch.StartNew();
Sawmill.Debug("LOADING modules");
var files = new Dictionary<string, (ResPath Path, string[] references)>();
var files = new Dictionary<string, (ResPath Path, MemoryStream data, string[] references)>();
// Find all modules we want to load.
foreach (var fullPath in paths)
{
using var asmFile = _res.ContentFileRead(fullPath);
var refData = GetAssemblyReferenceData(asmFile);
var ms = new MemoryStream();
asmFile.CopyTo(ms);
ms.Position = 0;
var refData = GetAssemblyReferenceData(ms);
if (refData == null)
continue;
var (asmRefs, asmName) = refData.Value;
if (!files.TryAdd(asmName, (fullPath, asmRefs)))
if (!files.TryAdd(asmName, (fullPath, ms, asmRefs)))
{
Sawmill.Error("Found multiple modules with the same assembly name " +
$"'{asmName}', A: {files[asmName].Path}, B: {fullPath}.");
@@ -122,10 +126,10 @@ namespace Robust.Shared.ContentPack
Parallel.ForEach(files, pair =>
{
var (name, (path, _)) = pair;
var (name, (_, data, _)) = pair;
using var stream = _res.ContentFileRead(path);
if (!typeChecker.CheckAssembly(stream, resolver))
data.Position = 0;
if (!typeChecker.CheckAssembly(data, resolver))
{
throw new TypeCheckFailedException($"Assembly {name} failed type checks.");
}
@@ -137,14 +141,15 @@ namespace Robust.Shared.ContentPack
var nodes = TopologicalSort.FromBeforeAfter(
files,
kv => kv.Key,
kv => kv.Value.Path,
kv => kv.Value,
_ => Array.Empty<string>(),
kv => kv.Value.references,
allowMissing: true); // missing refs would be non-content assemblies so allow that.
// Actually load them in the order they depend on each other.
foreach (var path in TopologicalSort.Sort(nodes))
foreach (var item in TopologicalSort.Sort(nodes))
{
var (path, memory, _) = item;
Sawmill.Debug($"Loading module: '{path}'");
try
{
@@ -156,9 +161,9 @@ namespace Robust.Shared.ContentPack
}
else
{
using var assemblyStream = _res.ContentFileRead(path);
memory.Position = 0;
using var symbolsStream = _res.ContentFileReadOrNull(path.WithExtension("pdb"));
LoadGameAssembly(assemblyStream, symbolsStream, skipVerify: true);
LoadGameAssembly(memory, symbolsStream, skipVerify: true);
}
}
catch (Exception e)
@@ -174,7 +179,7 @@ namespace Robust.Shared.ContentPack
private (string[] refs, string name)? GetAssemblyReferenceData(Stream stream)
{
using var reader = ModLoader.MakePEReader(stream);
using var reader = ModLoader.MakePEReader(stream, leaveOpen: true);
var metaReader = reader.GetMetadataReader();
var name = metaReader.GetString(metaReader.GetAssemblyDefinition().Name);

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack
{
@@ -63,5 +64,27 @@ namespace Robust.Shared.ContentPack
!OperatingSystem.IsWindows()
&& !OperatingSystem.IsMacOS();
internal static string SafeGetResourcePath(string baseDir, ResPath path)
{
var relSysPath = path.ToRelativeSystemPath();
if (relSysPath.Contains("\\..") || relSysPath.Contains("/.."))
{
// Hard cap on any exploit smuggling a .. in there.
// Since that could allow leaving sandbox.
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
var retPath = Path.GetFullPath(Path.Join(baseDir, relSysPath));
// better safe than sorry check
if (!retPath.StartsWith(baseDir))
{
// Allow path to match if it's just missing the directory separator at the end.
if (retPath != baseDir.TrimEnd(Path.DirectorySeparatorChar))
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
return retPath;
}
}
}

View File

@@ -41,13 +41,13 @@ namespace Robust.Shared.ContentPack
public IWritableDirProvider UserData { get; private set; } = default!;
/// <inheritdoc />
public virtual void Initialize(string? userData)
public virtual void Initialize(string? userData, bool hideRootDir)
{
Sawmill = _logManager.GetSawmill("res");
if (userData != null)
{
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
UserData = new WritableDirProvider(Directory.CreateDirectory(userData), hideRootDir);
}
else
{
@@ -379,6 +379,10 @@ namespace Robust.Shared.ContentPack
{
var rootDir = loader.GetPath(new ResPath(@"/"));
// TODO: GET RID OF THIS.
// This code shouldn't be passing OS disk paths through ResPath.
rootDir = rootDir.Replace(Path.DirectorySeparatorChar, '/');
yield return new ResPath(rootDir);
}
}

View File

@@ -17,6 +17,10 @@ WhitelistedNamespaces:
- Content
- OpenDreamShared
AllowedAssemblyPrefixes:
- OpenDream
- Content
# The type whitelist does NOT care about which assembly types come from.
# This is because types switch assembly all the time.
# Just look up stuff like StreamReader on https://apisof.net.
@@ -875,6 +879,7 @@ Types:
- "System.Text.StringBuilder Insert(int, object)"
- "System.Text.StringBuilder Insert(int, sbyte)"
- "System.Text.StringBuilder Insert(int, short)"
- "System.Text.StringBuilder Insert(int, string)"
- "System.Text.StringBuilder Insert(int, string, int)"
- "System.Text.StringBuilder Insert(int, System.Decimal)"
- "System.Text.StringBuilder Insert(int, System.ReadOnlySpan`1<char>)"

View File

@@ -10,17 +10,22 @@ namespace Robust.Shared.ContentPack
/// <inheritdoc />
internal sealed class WritableDirProvider : IWritableDirProvider
{
/// <inheritdoc />
private readonly bool _hideRootDir;
public string RootDir { get; }
string? IWritableDirProvider.RootDir => _hideRootDir ? null : RootDir;
/// <summary>
/// Constructs an instance of <see cref="WritableDirProvider"/>.
/// </summary>
/// <param name="rootDir">Root file system directory to allow writing.</param>
public WritableDirProvider(DirectoryInfo rootDir)
/// <param name="hideRootDir">If true, <see cref="IWritableDirProvider.RootDir"/> is reported as null.</param>
public WritableDirProvider(DirectoryInfo rootDir, bool hideRootDir)
{
// FullName does not have a trailing separator, and we MUST have a separator.
RootDir = rootDir.FullName + Path.DirectorySeparatorChar.ToString();
_hideRootDir = hideRootDir;
}
#region File Access
@@ -119,7 +124,7 @@ namespace Robust.Shared.ContentPack
throw new FileNotFoundException();
var dirInfo = new DirectoryInfo(GetFullPath(path));
return new WritableDirProvider(dirInfo);
return new WritableDirProvider(dirInfo, _hideRootDir);
}
/// <inheritdoc />
@@ -180,20 +185,7 @@ namespace Robust.Shared.ContentPack
path = path.Clean();
return GetFullPath(RootDir, path);
}
private static string GetFullPath(string root, ResPath path)
{
var relPath = path.ToRelativeSystemPath();
if (relPath.Contains("\\..") || relPath.Contains("/.."))
{
// Hard cap on any exploit smuggling a .. in there.
// Since that could allow leaving sandbox.
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
return Path.GetFullPath(Path.Combine(root, relPath));
return PathHelpers.SafeGetResourcePath(RootDir, path);
}
}
}

View File

@@ -273,11 +273,11 @@ namespace Robust.Shared.GameObjects
public IComponent GetComponent(Type componentType)
{
if (!_types.ContainsKey(componentType))
if (!_types.TryGetValue(componentType, out var value))
{
throw new InvalidOperationException($"{componentType} is not a registered component.");
}
return _typeFactory.CreateInstanceUnchecked<IComponent>(_types[componentType].Type);
return _typeFactory.CreateInstanceUnchecked<IComponent>(value.Type);
}
public IComponent GetComponent(CompIdx componentType)
@@ -287,11 +287,11 @@ namespace Robust.Shared.GameObjects
public T GetComponent<T>() where T : IComponent, new()
{
if (!_types.ContainsKey(typeof(T)))
if (!_types.TryGetValue(typeof(T), out var reg))
{
throw new InvalidOperationException($"{typeof(T)} is not a registered component.");
}
return _typeFactory.CreateInstanceUnchecked<T>(_types[typeof(T)].Type);
return _typeFactory.CreateInstanceUnchecked<T>(reg.Type);
}
public IComponent GetComponent(ComponentRegistration reg)

View File

@@ -52,6 +52,16 @@ public partial class EntityManager
return ents;
}
public EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params EntProtoId[] protoNames)
{
var ents = new EntityUid[protoNames.Length];
for (var i = 0; i < protoNames.Length; i++)
{
ents[i] = SpawnAttachedTo(protoNames[i], coordinates);
}
return ents;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, List<string?> protoNames)
{
@@ -74,6 +84,14 @@ public partial class EntityManager
return ents;
}
public void SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames)
{
foreach (var protoName in protoNames)
{
SpawnAttachedTo(protoName, coordinates);
}
}
public virtual EntityUid SpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
{
if (!coordinates.IsValid(this))

View File

@@ -29,6 +29,8 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IReplayRecordingManager _replayMan = default!;
[Dependency] protected readonly ILocalizationManager Loc = default!;
protected IComponentFactory Factory => EntityManager.ComponentFactory;
public ISawmill Log { get; private set; } = default!;
protected virtual string SawmillName

View File

@@ -22,6 +22,8 @@ public partial interface IEntityManager
EntityUid[] SpawnEntities(MapCoordinates coordinates, params string?[] protoNames);
EntityUid[] SpawnEntities(MapCoordinates coordinates, string? prototype, int count);
EntityUid[] SpawnEntities(MapCoordinates coordinates, List<string?> protoNames);
void SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params EntProtoId[] protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, List<string?> protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params string?[] protoNames);

View File

@@ -1,8 +1,8 @@
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Utility;
namespace Robust.Shared.Physics.Events;
@@ -20,9 +20,16 @@ public readonly struct StartCollideEvent
public readonly Fixture OurFixture;
public readonly Fixture OtherFixture;
public readonly Vector2 WorldPoint;
public StartCollideEvent(
internal readonly FixedArray2<Vector2> _worldPoints;
public readonly int PointCount;
public readonly Vector2 WorldNormal;
public Vector2[] WorldPoints => _worldPoints.AsSpan[..PointCount].ToArray();
internal StartCollideEvent(
EntityUid ourEntity,
EntityUid otherEntity,
string ourFixtureId,
@@ -31,7 +38,9 @@ public readonly struct StartCollideEvent
Fixture otherFixture,
PhysicsComponent ourBody,
PhysicsComponent otherBody,
Vector2 worldPoint)
FixedArray2<Vector2> worldPoints,
int pointCount,
Vector2 worldNormal)
{
OurEntity = ourEntity;
OtherEntity = otherEntity;
@@ -39,8 +48,10 @@ public readonly struct StartCollideEvent
OtherFixtureId = otherFixtureId;
OurFixture = ourFixture;
OtherFixture = otherFixture;
WorldPoint = worldPoint;
OtherBody = otherBody;
OurBody = ourBody;
_worldPoints = worldPoints;
PointCount = pointCount;
WorldNormal = worldNormal;
}
}

View File

@@ -551,7 +551,7 @@ public abstract partial class SharedPhysicsSystem
}
var status = ArrayPool<ContactStatus>.Shared.Rent(index);
var worldPoints = ArrayPool<Vector2>.Shared.Rent(index);
var worldPoints = ArrayPool<FixedArray4<Vector2>>.Shared.Rent(index);
// Update contacts all at once.
BuildManifolds(contacts, index, status, worldPoints);
@@ -586,9 +586,11 @@ public abstract partial class SharedPhysicsSystem
var uidA = contact.EntityA;
var uidB = contact.EntityB;
var worldPoint = worldPoints[i];
var points = new FixedArray2<Vector2>(worldPoint._00, worldPoint._01);
var worldNormal = worldPoint._02;
var ev1 = new StartCollideEvent(uidA, uidB, contact.FixtureAId, contact.FixtureBId, fixtureA, fixtureB, bodyA, bodyB, worldPoint);
var ev2 = new StartCollideEvent(uidB, uidA, contact.FixtureBId, contact.FixtureAId, fixtureB, fixtureA, bodyB, bodyA, worldPoint);
var ev1 = new StartCollideEvent(uidA, uidB, contact.FixtureAId, contact.FixtureBId, fixtureA, fixtureB, bodyA, bodyB, points, contact.Manifold.PointCount, worldNormal);
var ev2 = new StartCollideEvent(uidB, uidA, contact.FixtureBId, contact.FixtureAId, fixtureB, fixtureA, bodyB, bodyA, points, contact.Manifold.PointCount, worldNormal);
RaiseLocalEvent(uidA, ref ev1, true);
RaiseLocalEvent(uidB, ref ev2, true);
@@ -626,10 +628,10 @@ public abstract partial class SharedPhysicsSystem
ArrayPool<Contact>.Shared.Return(contacts);
ArrayPool<ContactStatus>.Shared.Return(status);
ArrayPool<Vector2>.Shared.Return(worldPoints);
ArrayPool<FixedArray4<Vector2>>.Shared.Return(worldPoints);
}
private void BuildManifolds(Contact[] contacts, int count, ContactStatus[] status, Vector2[] worldPoints)
private void BuildManifolds(Contact[] contacts, int count, ContactStatus[] status, FixedArray4<Vector2>[] worldPoints)
{
if (count == 0)
return;
@@ -672,7 +674,7 @@ public abstract partial class SharedPhysicsSystem
public Contact[] Contacts;
public ContactStatus[] Status;
public Vector2[] WorldPoints;
public FixedArray4<Vector2>[] WorldPoints;
public bool[] Wake;
public void Execute(int index)
@@ -681,7 +683,7 @@ public abstract partial class SharedPhysicsSystem
}
}
private void UpdateContact(Contact[] contacts, int index, ContactStatus[] status, bool[] wake, Vector2[] worldPoints)
private void UpdateContact(Contact[] contacts, int index, ContactStatus[] status, bool[] wake, FixedArray4<Vector2>[] worldPoints)
{
var contact = contacts[index];
@@ -706,7 +708,11 @@ public abstract partial class SharedPhysicsSystem
if (contactStatus == ContactStatus.StartTouching)
{
worldPoints[index] = Physics.Transform.Mul(bodyATransform, contacts[index].Manifold.LocalPoint);
var points = new FixedArray4<Vector2>();
contact.GetWorldManifold(bodyATransform, bodyBTransform, out var worldNormal, points.AsSpan);
// Use the 3rd Vector2 as the world normal, 4th is blank.
points._02 = worldNormal;
worldPoints[index] = points;
}
}

View File

@@ -9,7 +9,6 @@
[assembly: InternalsVisibleTo("Robust.UnitTesting")]
[assembly: InternalsVisibleTo("OpenToolkit.GraphicsLibraryFramework")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // Gives access to Castle(Moq)
[assembly: InternalsVisibleTo("Content.Benchmarks")]
[assembly: InternalsVisibleTo("Robust.Benchmarks")]
[assembly: InternalsVisibleTo("Robust.Client.WebView")]
[assembly: InternalsVisibleTo("Robust.Packaging")]

View File

@@ -24,7 +24,7 @@ namespace Robust.UnitTesting.Shared.Resources
_testDir = Directory.CreateDirectory(_testDirPath);
var subDir = Path.Combine(_testDirPath, "writable");
_dirProvider = new WritableDirProvider(Directory.CreateDirectory(subDir));
_dirProvider = new WritableDirProvider(Directory.CreateDirectory(subDir), hideRootDir: false);
}
[OneTimeTearDown]