Compare commits

...

16 Commits

Author SHA1 Message Date
PJB3005
6018acb85c Version: 255.1.4 2025-12-01 16:07:19 +01:00
PJB3005
4767e2ba7f Backport BitArray .NET 10 serializer fix
83ad6042a7 & b267cd6fb4

Does not include test code to avoid risking merge conflicts.

(cherry picked from commit 415585a30d74fcae61f581808220a7aaeca3eaf5)
(cherry picked from commit e36628a6d436ea08d6d31441c101a88a5504c515)
2025-12-01 16:07:19 +01:00
PJB3005
4fab77b7e4 Version: 255.1.3 2025-09-26 13:40:46 +02:00
PJB3005
b16fe44815 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:45 +02:00
PJB3005
ec234b620c Version: 255.1.2 2025-09-19 09:17:30 +02:00
Skye
cb12772f28 Fix resource loading on non-Windows platforms (#6201)
(cherry picked from commit 51bbc5dc45)
2025-09-19 09:17:30 +02:00
PJB3005
fdd593cdd1 Version: 255.1.1 2025-09-14 14:55:54 +02:00
PJB3005
133301ea57 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:54 +02:00
metalgearsloth
62b4714f1f Version: 255.1.0 2025-04-30 23:38:21 +10:00
Leon Friedrich
1d0404953f Add GridUidChangedEvent and MapUidChangedEvent (#5893)
* Add GridUidChangedEvent and MapUidChangedEvent

* cleanup

* Fix assert

* more fixes

* docs

* record struct

* Use implicit tuple constructor

* stinky review

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2025-04-30 18:36:53 +10:00
Leon Friedrich
d0da13f895 Fix CompileRobustXamlTask for benchmarks (#5902)
* Fix benchmarks

* I love it when path separators are also escape chars
2025-04-30 13:16:28 +10:00
metalgearsloth
ff23f98b26 Document container events (#5904)
It's hard to discern what it's raised directed on and this makes it much easier.
2025-04-30 13:10:03 +10:00
Leon Friedrich
ccfef2a786 Fix PVS NRE (#5900) 2025-04-28 20:11:18 +10:00
B_Kirill
62ce9724fc Clean up (#5890) 2025-04-26 22:59:02 +10:00
Milon
3bbe0e7f44 ftl hot reloading (#5874)
* sloth is so going to kill me

* the voices in my head told me to do this

* Register ILocalizationManagerInternal on client

* Avoid breaking change

* Cleanup

* Release notes
2025-04-26 22:38:56 +10:00
chromiumboy
addd8b5bdd Initial commit (#5880) 2025-04-25 22:19:11 +10:00
49 changed files with 498 additions and 135 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

@@ -71,6 +71,6 @@
</PropertyGroup>
<Exec
Condition="'$(_RobustUseExternalMSBuild)' == 'true'"
Command="&quot;$(DOTNET_HOST_PATH)&quot; msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileRobustXaml /p:_RobustForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
Command="&quot;$(DOTNET_HOST_PATH)&quot; msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileRobustXaml /p:_RobustForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false /p:IntermediateOutputPath=&quot;$(IntermediateOutputPath.TrimEnd('\'))/&quot;"/>
</Target>
</Project>

View File

@@ -54,6 +54,37 @@ END TEMPLATE-->
*None yet*
## 255.1.4
## 255.1.3
## 255.1.2
## 255.1.1
## 255.1.0
### New features
* The client localisation manager now supports hot-reloading ftl files.
* TransformSystem can now raise `GridUidChangedEvent` and `MapUidChangedEvent` when a entity's grid or map changes. This event is only raised if the `ExtraTransformEvents` metadata flag is enabled.
### Bugfixes
* Fixed a server crash due to a `NullReferenceException` in PVS system when a player's local entity is also one of their view subscriptions.
* Fix CompileRobustXamlTask for benchmarks.
* .ftl files will now hot reload.
* Fix placementmanager sometimes not clearing.
### Other
* Container events are now documented.
## 255.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

@@ -10,6 +10,7 @@ using Robust.Client.Graphics;
using Robust.Client.Graphics.Clyde;
using Robust.Client.HWId;
using Robust.Client.Input;
using Robust.Client.Localization;
using Robust.Client.Map;
using Robust.Client.Placement;
using Robust.Client.Player;
@@ -36,6 +37,7 @@ using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
@@ -104,6 +106,8 @@ namespace Robust.Client
deps.Register<IGamePrototypeLoadManager, GamePrototypeLoadManager>();
deps.Register<NetworkResourceManager>();
deps.Register<IReloadManager, ReloadManager>();
deps.Register<ILocalizationManager, ClientLocalizationManager>();
deps.Register<ILocalizationManagerInternal, ClientLocalizationManager>();
switch (mode)
{

View File

@@ -31,6 +31,7 @@ using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
@@ -94,6 +95,7 @@ namespace Robust.Client
[Dependency] private readonly IReplayRecordingManagerInternal _replayRecording = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IReloadManager _reload = default!;
[Dependency] private readonly ILocalizationManager _loc = default!;
private IWebViewManagerHook? _webViewHook;
@@ -180,6 +182,7 @@ namespace Robust.Client
_serializer.Initialize();
_inputManager.Initialize();
_console.Initialize();
_loc.Initialize();
// Make sure this is done before we try to load prototypes,
// avoid any possibility of race conditions causing the check to not finish
@@ -384,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

@@ -0,0 +1,33 @@
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Utility;
namespace Robust.Client.Localization;
internal sealed class ClientLocalizationManager : LocalizationManager, ILocalizationManagerInternal
{
[Dependency] private readonly IReloadManager _reload = default!;
void ILocalizationManager.Initialize() => Initialize();
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
_reload.Register(LocaleDirPath, "*.ftl");
_reload.OnChanged += OnReload;
}
/// <summary>
/// Handles Fluent hot reloading via LocalizationManager.ReloadLocalizations()
/// </summary>
private void OnReload(ResPath args)
{
if (args.Extension != "ftl")
return;
ReloadLocalizations();
}
}

View File

@@ -356,6 +356,12 @@ namespace Robust.Client.Placement
public event EventHandler? PlacementChanged;
public void Clear()
{
ClearWithoutDeactivation();
IsActive = false;
}
private void ClearWithoutDeactivation()
{
PlacementChanged?.Invoke(this, EventArgs.Empty);
Hijack = null;
@@ -365,7 +371,6 @@ namespace Robust.Client.Placement
CurrentMode = null;
DeactivateSpecialPlacement();
_placenextframe = false;
IsActive = false;
Eraser = false;
EraserRect = null;
PlacementOffset = Vector2i.Zero;
@@ -480,7 +485,7 @@ namespace Robust.Client.Placement
public void BeginHijackedPlacing(PlacementInformation info, PlacementHijack? hijack = null)
{
Clear();
ClearWithoutDeactivation();
CurrentPermission = info;

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Robust.Client.Graphics;
using Robust.Shared;
@@ -26,7 +25,6 @@ internal sealed class ReloadManager : IReloadManager
private readonly TimeSpan _reloadDelay = TimeSpan.FromMilliseconds(10);
private CancellationTokenSource _reloadToken = new();
private readonly HashSet<ResPath> _reloadQueue = new();
private List<FileSystemWatcher> _watchers = new();
public event Action<ResPath>? OnChanged;
@@ -69,6 +67,11 @@ internal sealed class ReloadManager : IReloadManager
_reloadQueue.Clear();
}
public void Register(ResPath directory, string filter)
{
Register(directory.ToString(), filter);
}
public void Register(string directory, string filter)
{
if (!_cfg.GetCVar(CVars.ResPrototypeReloadWatch))
@@ -90,7 +93,6 @@ internal sealed class ReloadManager : IReloadManager
NotifyFilter = NotifyFilters.LastWrite
};
_watchers.Add(watcher);
watcher.Changed += OnWatch;
@@ -100,7 +102,7 @@ internal sealed class ReloadManager : IReloadManager
}
catch (IOException ex)
{
Logger.Error($"Watching resources in path {path} threw an exception:\n{ex}");
_sawmill.Error($"Watching resources in path {path} threw an exception:\n{ex}");
}
}

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;
@@ -331,6 +331,7 @@ namespace Robust.Server
// TODO: solve this properly.
_serializer.Initialize();
_loc.Initialize();
_loc.AddLoadedToStringSerializer(_stringSerializer);
//IoCManager.Resolve<IMapLoader>().LoadedMapData +=

View File

@@ -14,6 +14,19 @@ public sealed class PointLightSystem : SharedPointLightSystem
base.Initialize();
SubscribeLocalEvent<PointLightComponent, ComponentGetState>(OnLightGetState);
SubscribeLocalEvent<PointLightComponent, ComponentStartup>(OnLightStartup);
SubscribeLocalEvent<PointLightComponent, ComponentShutdown>(OnLightShutdown);
SubscribeLocalEvent<PointLightComponent, MetaFlagRemoveAttemptEvent>(OnFlagRemoveAttempt);
}
private void OnLightShutdown(Entity<PointLightComponent> ent, ref ComponentShutdown args)
{
UpdatePriority(ent.Owner, ent.Comp, MetaData(ent.Owner));
}
private void OnFlagRemoveAttempt(Entity<PointLightComponent> ent, ref MetaFlagRemoveAttemptEvent args)
{
if (IsHighPriority(ent.Comp))
args.ToRemove &= ~MetaDataFlags.PvsPriority;
}
private void OnLightStartup(EntityUid uid, PointLightComponent component, ComponentStartup args)
@@ -21,10 +34,14 @@ public sealed class PointLightSystem : SharedPointLightSystem
UpdatePriority(uid, component, MetaData(uid));
}
private bool IsHighPriority(SharedPointLightComponent comp)
{
return comp is {Enabled: true, CastShadows: true, Radius: > 7, LifeStage: <= ComponentLifeStage.Running};
}
protected override void UpdatePriority(EntityUid uid, SharedPointLightComponent comp, MetaDataComponent meta)
{
var isHighPriority = comp.Enabled && comp.CastShadows && (comp.Radius > 7);
_metadata.SetFlag((uid, meta), MetaDataFlags.PvsPriority, isHighPriority);
_metadata.SetFlag((uid, meta), MetaDataFlags.PvsPriority, IsHighPriority(comp));
}
private void OnLightGetState(EntityUid uid, PointLightComponent component, ref ComponentGetState args)

View File

@@ -9,12 +9,30 @@ public sealed class ServerOccluderSystem : OccluderSystem
{
[Dependency] private readonly MetaDataSystem _metadata = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<OccluderComponent, MetaFlagRemoveAttemptEvent>(OnFlagRemoveAttempt);
}
private void OnFlagRemoveAttempt(Entity<OccluderComponent> ent, ref MetaFlagRemoveAttemptEvent args)
{
if (ent.Comp is {Enabled: true, LifeStage: <= ComponentLifeStage.Running})
args.ToRemove &= ~MetaDataFlags.PvsPriority;
}
protected override void OnCompStartup(EntityUid uid, OccluderComponent component, ComponentStartup args)
{
base.OnCompStartup(uid, component, args);
_metadata.SetFlag(uid, MetaDataFlags.PvsPriority, component.Enabled);
}
protected override void OnCompRemoved(EntityUid uid, OccluderComponent component, ComponentRemove args)
{
base.OnCompRemoved(uid, component, args);
_metadata.SetFlag(uid, MetaDataFlags.PvsPriority, false);
}
public override void SetEnabled(EntityUid uid, bool enabled, OccluderComponent? comp = null, MetaDataComponent? meta = null)
{
if (!Resolve(uid, ref comp, false))

View File

@@ -166,11 +166,11 @@ internal sealed partial class PvsSystem
var session = pvsSession.Session;
if (session.Status != SessionStatus.InGame)
{
pvsSession.Viewers = Array.Empty<Entity<TransformComponent, EyeComponent?>>();
pvsSession.Viewers = Array.Empty<Entity<TransformComponent, EyeComponent?>>();
return;
}
// Fast path
// The majority of players will have no view subscriptions
if (session.ViewSubscriptions.Count == 0)
{
if (session.AttachedEntity is not {} attached)
@@ -184,15 +184,21 @@ internal sealed partial class PvsSystem
return;
}
var count = session.ViewSubscriptions.Count;
var i = 0;
if (session.AttachedEntity is { } local)
{
Array.Resize(ref pvsSession.Viewers, session.ViewSubscriptions.Count + 1);
if (!session.ViewSubscriptions.Contains(local))
count += 1;
Array.Resize(ref pvsSession.Viewers, count);
// Attached entity is always the first viewer, to prioritize it and help reduce pop-in for the "main" eye.
pvsSession.Viewers[i++] = (local, Transform(local), _eyeQuery.CompOrNull(local));
}
else
{
Array.Resize(ref pvsSession.Viewers, session.ViewSubscriptions.Count);
Array.Resize(ref pvsSession.Viewers, count);
}
foreach (var ent in session.ViewSubscriptions)
@@ -200,6 +206,8 @@ internal sealed partial class PvsSystem
if (ent != session.AttachedEntity)
pvsSession.Viewers[i++] = (ent, Transform(ent), _eyeQuery.CompOrNull(ent));
}
DebugTools.AssertEqual(i, pvsSession.Viewers.Length);
}
private void ProcessVisibleChunks()

View File

@@ -0,0 +1,8 @@
using Robust.Shared.Localization;
namespace Robust.Server.Localization;
internal sealed class ServerLocalizationManager : LocalizationManager, ILocalizationManager
{
void ILocalizationManager.Initialize() => Initialize();
}

View File

@@ -4,6 +4,7 @@ using Robust.Server.Console;
using Robust.Server.DataMetrics;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Server.Localization;
using Robust.Server.Placement;
using Robust.Server.Player;
using Robust.Server.Prototypes;
@@ -21,6 +22,7 @@ using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
@@ -97,7 +99,9 @@ namespace Robust.Server
deps.Register<NetworkResourceManager>();
deps.Register<IHttpClientHolder, HttpClientHolder>();
deps.Register<UploadedContentManager>();
deps.Register<IHWId, DummyHWId>();
deps.Register<IHWId, DummyHWId>();
deps.Register<ILocalizationManager, ServerLocalizationManager>();
deps.Register<ILocalizationManagerInternal, ServerLocalizationManager>();
}
}
}

View File

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

View File

@@ -16,6 +16,9 @@ public abstract class ContainerAttemptEventBase : CancellableEntityEventArgs
}
}
/// <summary>
/// Raised directed on the container when attempting to insert an entity.
/// </summary>
public sealed class ContainerIsInsertingAttemptEvent : ContainerAttemptEventBase
{
/// <summary>
@@ -23,7 +26,7 @@ public sealed class ContainerIsInsertingAttemptEvent : ContainerAttemptEventBase
/// I.e., could the entity be inserted if the container doesn't contain anything else?
/// </summary>
public bool AssumeEmpty { get; set; }
public ContainerIsInsertingAttemptEvent(BaseContainer container, EntityUid entityUid, bool assumeEmpty)
: base(container, entityUid)
{
@@ -31,6 +34,9 @@ public sealed class ContainerIsInsertingAttemptEvent : ContainerAttemptEventBase
}
}
/// <summary>
/// Raised directed on the entity being inserted into the container.
/// </summary>
public sealed class ContainerGettingInsertedAttemptEvent : ContainerAttemptEventBase
{
/// <summary>
@@ -46,6 +52,9 @@ public sealed class ContainerGettingInsertedAttemptEvent : ContainerAttemptEvent
}
}
/// <summary>
/// Raised directed on the container when attempting to remove an entity.
/// </summary>
public sealed class ContainerIsRemovingAttemptEvent : ContainerAttemptEventBase
{
public ContainerIsRemovingAttemptEvent(BaseContainer container, EntityUid entityUid) : base(container, entityUid)
@@ -53,6 +62,9 @@ public sealed class ContainerIsRemovingAttemptEvent : ContainerAttemptEventBase
}
}
/// <summary>
/// Raised directed on the entity being removed from the container.
/// </summary>
public sealed class ContainerGettingRemovedAttemptEvent : ContainerAttemptEventBase
{
public ContainerGettingRemovedAttemptEvent(BaseContainer container, EntityUid entityUid) : base(container, entityUid)

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.

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

@@ -238,6 +238,11 @@ namespace Robust.Shared.GameObjects
/// a grid or map.
/// </summary>
PvsPriority = 1 << 4,
/// <summary>
/// If set, transform system will raise events directed at this entity whenever the GridUid or MapUid are modified.
/// </summary>
ExtraTransformEvents = 1 << 5,
}
/// <summary>

View File

@@ -402,32 +402,6 @@ namespace Robust.Shared.GameObjects
_entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().AttachToGridOrMap(Owner, this);
}
internal void UpdateChildMapIdsRecursive(
MapId newMapId,
EntityUid? newUid,
bool mapPaused,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<MetaDataComponent> metaQuery,
MetaDataSystem system)
{
foreach (var child in _children)
{
//Set Paused state
var metaData = metaQuery.GetComponent(child);
system.SetEntityPaused(child, mapPaused, metaData);
var concrete = xformQuery.GetComponent(child);
concrete.MapUid = newUid;
concrete.MapID = newMapId;
if (concrete.ChildCount != 0)
{
concrete.UpdateChildMapIdsRecursive(newMapId, newUid, mapPaused, xformQuery, metaQuery, system);
}
}
}
[Obsolete("Use TransformSystem.SetParent() instead")]
public void AttachParent(EntityUid parent)
{

View File

@@ -0,0 +1,21 @@
namespace Robust.Shared.GameObjects;
/// <summary>
/// Raised directed at an entity when <see cref="TransformComponent.GridUid"/> is modified.
/// This event is only raised if the entity has the <see cref="MetaDataFlags.ExtraTransformEvents"/> flag set.
/// </summary>
/// <remarks>
/// Event handlers should not modify positions or delete the entity, because the move event that triggered this event
/// is still being processed. This may also mean that the entity's current position/parent has not yet been updated,
/// and that positional entity queries are not reliable.
/// </remarks>
[ByRefEvent]
public readonly record struct GridUidChangedEvent(
Entity<TransformComponent, MetaDataComponent> Entity,
EntityUid? OldGrid)
{
public EntityUid? NewGrid => Entity.Comp1.GridUid;
public EntityUid Uid => Entity.Owner;
public TransformComponent Transform => Entity.Comp1;
public MetaDataComponent Meta => Entity.Comp2;
}

View File

@@ -0,0 +1,25 @@
using Robust.Shared.Map;
namespace Robust.Shared.GameObjects;
/// <summary>
/// Raised directed at an entity when <see cref="TransformComponent.MapUid"/> is modified.
/// This event is only raised if the entity has the <see cref="MetaDataFlags.ExtraTransformEvents"/> flag set.
/// </summary>
/// <remarks>
/// Event handlers should not modify positions or delete the entity, because the move event that triggered this event
/// is still being processed. This may also mean that the entity's current position/parent has not yet been updated,
/// and that positional entity queries are not reliable.
/// </remarks>
[ByRefEvent]
public readonly record struct MapUidChangedEvent(
Entity<TransformComponent, MetaDataComponent> Entity,
EntityUid? OldMap,
MapId OldMapId)
{
public EntityUid? NewMap => Entity.Comp1.MapUid;
public MapId? NewMapId => Entity.Comp1.MapID;
public EntityUid Uid => Entity.Owner;
public TransformComponent Transform => Entity.Comp1;
public MetaDataComponent Meta => Entity.Comp2;
}

View File

@@ -159,6 +159,8 @@ public abstract class MetaDataSystem : EntitySystem
if (toRemove == 0x0)
return;
// TODO PERF
// does this need to be a broadcast event?
var ev = new MetaFlagRemoveAttemptEvent(toRemove);
RaiseLocalEvent(uid, ref ev, true);

View File

@@ -47,8 +47,8 @@ public abstract partial class SharedTransformSystem
xform._localPosition = tilePos + newGrid.TileSizeHalfVector;
xform._localRotation += rotation;
SetGridId(uid, xform, newGridUid, XformQuery);
var meta = MetaData(uid);
SetGridId((uid, xform, meta), newGridUid);
RaiseMoveEvent((uid, xform, meta), oldGridUid, oldPos, oldRot, oldMap);
DebugTools.Assert(XformQuery.GetComponent(oldGridUid).MapID == XformQuery.GetComponent(newGridUid).MapID);
@@ -354,32 +354,49 @@ public abstract partial class SharedTransformSystem
#region GridId
/// <summary>
/// Sets the <see cref="GridId"/> for the transformcomponent without updating its children. Does not Dirty it.
/// </summary>
internal void SetGridIdNoRecursive(EntityUid uid, TransformComponent xform, EntityUid? gridUid)
/// <inheritdoc cref="SetGridId(Entity{TransformComponent,MetaDataComponent},EntityUid?)"/>
public void SetGridId(EntityUid uid, TransformComponent xform, EntityUid? gridId, EntityQuery<TransformComponent>? xformQuery = null)
{
DebugTools.Assert(gridUid == uid || !HasComp<MapGridComponent>(uid));
if (xform._gridUid == gridUid)
return;
DebugTools.Assert(gridUid == null || HasComp<MapGridComponent>(gridUid));
xform._gridUid = gridUid;
SetGridId((uid, xform, MetaData(uid)), gridId);
}
/// <summary>
/// Sets the <see cref="GridId"/> for the transformcomponent. Does not Dirty it.
/// Sets <see cref="TransformComponent.GridUid"/> for the entity and any children. Note that this does not dirty
/// the component, as this is implicitly networked via the transform hierarchy.
/// </summary>
public void SetGridId(EntityUid uid, TransformComponent xform, EntityUid? gridId, EntityQuery<TransformComponent>? xformQuery = null)
public void SetGridId(Entity<TransformComponent, MetaDataComponent?> ent, EntityUid? gridId)
{
if (!xform._gridInitialized || xform._gridUid == gridId || xform.GridUid == uid)
if (!ent.Comp1._gridInitialized || ent.Comp1._gridUid == gridId || ent.Comp1.GridUid == ent.Owner)
return;
DebugTools.Assert(!HasComp<MapGridComponent>(uid) || gridId == uid);
xform._gridUid = gridId;
foreach (var child in xform._children)
DebugTools.Assert(!HasComp<MapGridComponent>(ent.Owner) || gridId == ent.Owner);
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
_metaQuery.ResolveInternal(ent.Owner, ref ent.Comp2);
if ((ent.Comp2!.Flags & MetaDataFlags.ExtraTransformEvents) != 0)
{
SetGridId(child, XformQuery.GetComponent(child), gridId);
#if DEBUG
var childCount = ent.Comp1.ChildCount;
var oldParent = ent.Comp1.ParentUid;
#endif
var ev = new GridUidChangedEvent((ent.Owner, ent.Comp1, ent.Comp2), ent.Comp1._gridUid);
ent.Comp1._gridUid = gridId;
RaiseLocalEvent(ent, ref ev);
#if DEBUG
// Lets check content didn't do anything silly with the event.
DebugTools.AssertEqual(ent.Comp1._gridUid, gridId);
DebugTools.AssertEqual(ent.Comp1.ChildCount, childCount);
DebugTools.AssertEqual(ent.Comp1.ParentUid, oldParent);
#endif
}
ent.Comp1._gridUid = gridId;
foreach (var child in ent.Comp1._children)
{
SetGridId((child, XformQuery.GetComponent(child), null), gridId);
}
}
@@ -570,7 +587,10 @@ public abstract partial class SharedTransformSystem
if (newParent != null)
{
ChangeMapId(uid, xform, meta, newParent.MapID);
// TODO PERF
// if both map & grid id change, we should simultaneously update both.
ChangeMapId(entity, newParent.MapID);
if (!xform._gridInitialized)
InitializeGridUid(uid, xform);
@@ -578,16 +598,19 @@ public abstract partial class SharedTransformSystem
{
if (!newParent._gridInitialized)
InitializeGridUid(value.EntityId, newParent);
SetGridId(uid, xform, newParent.GridUid);
SetGridId(entity!, newParent.GridUid);
}
}
else
{
ChangeMapId(uid, xform, meta, MapId.Nullspace);
// TODO PERF
// if both map & grid id change, we should simultaneously update both.
ChangeMapId(entity, MapId.Nullspace);
if (!xform._gridInitialized)
InitializeGridUid(uid, xform);
else
SetGridId(uid, xform, null, XformQuery);
SetGridId(entity!, null);
}
if (xform.Initialized)
@@ -623,20 +646,54 @@ public abstract partial class SharedTransformSystem
SetCoordinates((uid, xform, _metaQuery.GetComponent(uid)), value, rotation, unanchor, newParent, oldParent);
}
private void ChangeMapId(EntityUid uid, TransformComponent xform, MetaDataComponent meta, MapId newMapId)
private void ChangeMapId(Entity<TransformComponent, MetaDataComponent> ent, MapId newMapId)
{
if (newMapId == xform.MapID)
if (newMapId == ent.Comp1.MapID)
return;
EntityUid? newUid = newMapId == MapId.Nullspace ? null : _map.GetMap(newMapId);
//Set Paused state
EntityUid? newMap = newMapId == MapId.Nullspace ? null : _map.GetMap(newMapId);
var mapPaused = _map.IsPaused(newMapId);
_metaData.SetEntityPaused(uid, mapPaused, meta);
xform.MapUid = newUid;
xform.MapID = newMapId;
xform.UpdateChildMapIdsRecursive(newMapId, newUid, mapPaused, XformQuery, _metaQuery, _metaData);
ChangeMapIdRecursive(ent, newMap, newMapId, mapPaused);
}
private void ChangeMapIdRecursive(
Entity<TransformComponent, MetaDataComponent> ent,
EntityUid? newMap,
MapId newMapId,
bool paused)
{
_metaData.SetEntityPaused(ent.Owner, paused, ent.Comp2);
if ((ent.Comp2.Flags & MetaDataFlags.ExtraTransformEvents) != 0)
{
#if DEBUG
var childCount = ent.Comp1.ChildCount;
var oldParent = ent.Comp1.ParentUid;
#endif
var ev = new MapUidChangedEvent(ent, ent.Comp1.MapUid, ent.Comp1.MapID);
ent.Comp1.MapUid = newMap;
ent.Comp1.MapID = newMapId;
RaiseLocalEvent(ent.Owner, ref ev);
#if DEBUG
// Lets check content didn't do anything silly with the event.
DebugTools.AssertEqual(ent.Comp1.MapUid, newMap);
DebugTools.AssertEqual(ent.Comp1.MapID, newMapId);
DebugTools.AssertEqual(ent.Comp1.ChildCount, childCount);
DebugTools.AssertEqual(ent.Comp1.ParentUid, oldParent);
#endif
}
ent.Comp1.MapUid = newMap;
ent.Comp1.MapID = newMapId;
foreach (var uid in ent.Comp1._children)
{
var child = new Entity<TransformComponent, MetaDataComponent>(uid, Transform(uid), MetaData(uid));
ChangeMapIdRecursive(child, newMap, newMapId, paused);
}
}
#endregion
@@ -1501,9 +1558,10 @@ public abstract partial class SharedTransformSystem
private void OnGridAdd(EntityUid uid, TransformComponent component, GridAddEvent args)
{
// Added to existing map so need to update all children too.
if (LifeStage(uid) > EntityLifeStage.Initialized)
var meta = MetaData(uid);
if (meta.EntityLifeStage > EntityLifeStage.Initialized)
{
SetGridId(uid, component, uid, XformQuery);
SetGridId((uid, component, meta), uid);
return;
}

View File

@@ -146,6 +146,13 @@ namespace Robust.Shared.Localization
/// Gets localization data for an entity prototype.
/// </summary>
EntityLocData GetEntityData(string prototypeId);
/// <summary>
/// Initializes the <see cref="LocalizationManager"/>.
/// </summary>
void Initialize()
{
}
}
internal interface ILocalizationManagerInternal : ILocalizationManager

View File

@@ -12,7 +12,7 @@ using Robust.Shared.Prototypes;
namespace Robust.Shared.Localization
{
internal sealed partial class LocalizationManager
internal abstract partial class LocalizationManager
{
// Concurrent dict so that multiple threads "reading" .Name won't cause a concurrent write issue
// when the cache gets populated.
@@ -134,7 +134,7 @@ namespace Robust.Shared.Localization
.Select(x => GetString(x.Suffix!));
suffix = string.Join(", ", suffixes);
}
return new EntityLocData(
name ?? "",
desc ?? "",

View File

@@ -12,7 +12,7 @@ using Robust.Shared.Maths;
namespace Robust.Shared.Localization
{
internal sealed partial class LocalizationManager
internal abstract partial class LocalizationManager
{
private static readonly Regex RegexWordMatch = new Regex(@"\w+");

View File

@@ -24,9 +24,9 @@ using Robust.Shared.Utility;
namespace Robust.Shared.Localization
{
internal sealed partial class LocalizationManager : ILocalizationManagerInternal, IPostInjectInit
internal abstract partial class LocalizationManager : ILocalizationManagerInternal
{
private static readonly ResPath LocaleDirPath = new("/Locale");
protected static readonly ResPath LocaleDirPath = new("/Locale");
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] private readonly IResourceManager _res = default!;
@@ -40,7 +40,9 @@ namespace Robust.Shared.Localization
private (CultureInfo, FluentBundle)? _defaultCulture;
private (CultureInfo, FluentBundle)[] _fallbackCultures = Array.Empty<(CultureInfo, FluentBundle)>();
void IPostInjectInit.PostInject()
void ILocalizationManager.Initialize() => Initialize();
public virtual void Initialize()
{
_logSawmill = _log.GetSawmill("loc");
_prototype.PrototypesReloaded += OnPrototypesReloaded;

View File

@@ -9,15 +9,13 @@ namespace Robust.Shared
public static void Setup(IConfigurationManager cfg)
{
// Disabled on non-release since I don't want this to start creating files in Steam's bin directory.
#if RELEASE
return;
#endif
#if !RELEASE
if (!cfg.GetCVar(CVars.SysProfileOpt))
return;
ProfileOptimization.SetProfileRoot(PathHelpers.GetExecutableDirectory());
ProfileOptimization.StartProfile("profile_opt");
#endif
}
}
}

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

@@ -0,0 +1,78 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using JetBrains.Annotations;
using NetSerializer;
namespace Robust.Shared.Serialization;
/// <summary>
/// Custom serializer implementation for <see cref="BitArray"/>.
/// </summary>
/// <remarks>
/// <para>
/// This type is necessary as, since .NET 10, the internal layout of <see cref="BitArray"/> was changed.
/// The type now (internally) implements <see cref="ISerializable"/> for backwards compatibility with existing
/// <c>BinaryFormatter</c> code, but NetSerializer does not support <see cref="ISerializable"/>.
/// </para>
/// <para>
/// This code is designed to be backportable &amp; network compatible with the previous behavior on .NET 9.
/// </para>
/// </remarks>
internal sealed class NetBitArraySerializer : IStaticTypeSerializer
{
// For reference, the layout of BitArray before .NET 10 was:
// private int[] m_array;
// private int m_length;
// private int _version;
// NetSerializer serialized these in the following order (sorted by name):
// _version, m_array, m_length
public bool Handles(Type type)
{
return type == typeof(BitArray);
}
public IEnumerable<Type> GetSubtypes(Type type)
{
return [typeof(int[]), typeof(int)];
}
public MethodInfo GetStaticWriter(Type type)
{
return typeof(NetBitArraySerializer).GetMethod("Write", BindingFlags.Static | BindingFlags.NonPublic)!;
}
public MethodInfo GetStaticReader(Type type)
{
return typeof(NetBitArraySerializer).GetMethod("Read", BindingFlags.Static | BindingFlags.NonPublic)!;
}
[UsedImplicitly]
private static void Write(Serializer serializer, Stream stream, BitArray value)
{
var intCount = (31 + value.Length) >> 5;
var ints = new int[intCount];
value.CopyTo(ints, 0);
serializer.SerializeDirect(stream, 0); // _version
serializer.SerializeDirect(stream, ints); // m_array
serializer.SerializeDirect(stream, value.Length); // m_length
}
[UsedImplicitly]
private static void Read(Serializer serializer, Stream stream, out BitArray value)
{
serializer.DeserializeDirect<int>(stream, out _); // _version
serializer.DeserializeDirect<int[]>(stream, out var array); // m_array
serializer.DeserializeDirect<int>(stream, out var length); // m_length
value = new BitArray(array)
{
Length = length
};
}
}

View File

@@ -91,6 +91,7 @@ namespace Robust.Shared.Serialization
MappedStringSerializer.TypeSerializer,
new Vector2Serializer(),
new Matrix3x2Serializer(),
new NetBitArraySerializer()
}
};
_serializer = new Serializer(types, settings);

View File

@@ -30,8 +30,6 @@ namespace Robust.Shared
deps.Register<IDynamicTypeFactory, DynamicTypeFactory>();
deps.Register<IDynamicTypeFactoryInternal, DynamicTypeFactory>();
deps.Register<IEntitySystemManager, EntitySystemManager>();
deps.Register<ILocalizationManager, LocalizationManager>();
deps.Register<ILocalizationManagerInternal, LocalizationManager>();
deps.Register<ILogManager, LogManager>();
deps.Register<IModLoader, ModLoader>();
deps.Register<IModLoaderInternal, ModLoader>();

View File

@@ -17,6 +17,11 @@ internal interface IReloadManager
/// </summary>
internal void Register(string directory, string filter);
/// <summary>
/// Registers the specified directory as a <see cref="ResPath"/> and specified file extension to subscribe to.
/// </summary>
internal void Register(ResPath directory, string filter);
void Initialize();
}

View File

@@ -11,6 +11,7 @@ using Robust.Server.Containers;
using Robust.Server.Debugging;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Server.Localization;
using Robust.Server.Physics;
using Robust.Server.Player;
using Robust.Server.Prototypes;
@@ -192,7 +193,7 @@ namespace Robust.UnitTesting.Server
container.Register<INetConfigurationManagerInternal, ServerNetConfigurationManager>();
container.Register<IDynamicTypeFactory, DynamicTypeFactory>();
container.Register<IDynamicTypeFactoryInternal, DynamicTypeFactory>();
container.Register<ILocalizationManager, LocalizationManager>();
container.Register<ILocalizationManager, ServerLocalizationManager>();
container.Register<IModLoader, TestingModLoader>();
container.Register<IModLoaderInternal, TestingModLoader>();
container.Register<ProfManager, ProfManager>();

View File

@@ -31,6 +31,7 @@ term1 = 2
res.MountString("/Locale/en-US/a.ftl", DuplicateTerm);
var loc = IoCManager.Resolve<ILocalizationManager>();
loc.Initialize();
var spyLog = (SpyLogManager) IoCManager.Resolve<ILogManager>();
var culture = new CultureInfo("en-US", false);

View File

@@ -34,6 +34,7 @@ namespace Robust.UnitTesting.Shared.Localization
var loc = IoCManager.Resolve<ILocalizationManager>();
var culture = new CultureInfo("en-US", false);
loc.Initialize();
loc.LoadCulture(culture);
}

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]