mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
323 lines
9.5 KiB
C#
323 lines
9.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using Robust.Shared.Interfaces.Configuration;
|
|
using Robust.Shared.Interfaces.Resources;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Log;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Shared.ContentPack
|
|
{
|
|
/// <summary>
|
|
/// Virtual file system for all disk resources.
|
|
/// </summary>
|
|
internal partial class ResourceManager : IResourceManagerInternal
|
|
{
|
|
[Dependency] private readonly IConfigurationManager _config = default!;
|
|
|
|
private readonly ReaderWriterLockSlim _contentRootsLock = new(LockRecursionPolicy.SupportsRecursion);
|
|
|
|
private readonly List<(ResourcePath prefix, IContentRoot root)> _contentRoots =
|
|
new();
|
|
|
|
// Special file names on Windows like serial ports.
|
|
private static readonly Regex BadPathSegmentRegex =
|
|
new("^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$", RegexOptions.IgnoreCase);
|
|
|
|
// Literally not characters that can't go into filenames on Windows.
|
|
private static readonly Regex BadPathCharacterRegex =
|
|
new("[<>:\"|?*\0\\x01-\\x1f]", RegexOptions.IgnoreCase);
|
|
|
|
/// <inheritdoc />
|
|
public IWritableDirProvider UserData { get; private set; } = default!;
|
|
|
|
/// <inheritdoc />
|
|
public void Initialize(string? userData)
|
|
{
|
|
if (userData != null)
|
|
{
|
|
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
|
|
}
|
|
else
|
|
{
|
|
UserData = new VirtualWritableDirProvider();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void MountDefaultContentPack()
|
|
{
|
|
//Assert server only
|
|
|
|
var zipPath = _config.GetCVar<string>("resource.pack");
|
|
|
|
// no pack in config
|
|
if (string.IsNullOrWhiteSpace(zipPath))
|
|
{
|
|
Logger.WarningS("res", "No default ContentPack to load in configuration.");
|
|
return;
|
|
}
|
|
|
|
MountContentPack(zipPath);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void MountContentPack(string pack, ResourcePath? prefix = null)
|
|
{
|
|
prefix = SanitizePrefix(prefix);
|
|
|
|
if (!Path.IsPathRooted(pack))
|
|
pack = PathHelpers.ExecutableRelativeFile(pack);
|
|
|
|
var packInfo = new FileInfo(pack);
|
|
|
|
if (!packInfo.Exists)
|
|
{
|
|
throw new FileNotFoundException("Specified ContentPack does not exist: " + packInfo.FullName);
|
|
}
|
|
|
|
//create new PackLoader
|
|
|
|
var loader = new PackLoader(packInfo);
|
|
AddRoot(prefix, loader);
|
|
}
|
|
|
|
public void MountContentPack(Stream zipStream, ResourcePath? prefix = null)
|
|
{
|
|
prefix = SanitizePrefix(prefix);
|
|
|
|
var loader = new PackLoader(zipStream);
|
|
AddRoot(prefix, loader);
|
|
}
|
|
|
|
protected void AddRoot(ResourcePath prefix, IContentRoot loader)
|
|
{
|
|
loader.Mount();
|
|
_contentRootsLock.EnterWriteLock();
|
|
try
|
|
{
|
|
_contentRoots.Add((prefix, loader));
|
|
}
|
|
finally
|
|
{
|
|
_contentRootsLock.ExitWriteLock();
|
|
}
|
|
}
|
|
|
|
private static ResourcePath SanitizePrefix(ResourcePath? prefix)
|
|
{
|
|
if (prefix == null)
|
|
{
|
|
prefix = ResourcePath.Root;
|
|
}
|
|
else if (!prefix.IsRooted)
|
|
{
|
|
throw new ArgumentException("Prefix must be rooted.", nameof(prefix));
|
|
}
|
|
|
|
return prefix;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void MountContentDirectory(string path, ResourcePath? prefix = null)
|
|
{
|
|
prefix = SanitizePrefix(prefix);
|
|
|
|
if (!Path.IsPathRooted(path))
|
|
path = PathHelpers.ExecutableRelativeFile(path);
|
|
|
|
var pathInfo = new DirectoryInfo(path);
|
|
if (!pathInfo.Exists)
|
|
{
|
|
throw new DirectoryNotFoundException("Specified directory does not exist: " + pathInfo.FullName);
|
|
}
|
|
|
|
var loader = new DirLoader(pathInfo, Logger.GetSawmill("res"));
|
|
AddRoot(prefix, loader);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Stream ContentFileRead(string path)
|
|
{
|
|
return ContentFileRead(new ResourcePath(path));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Stream ContentFileRead(ResourcePath path)
|
|
{
|
|
if (TryContentFileRead(path, out var fileStream))
|
|
{
|
|
return fileStream;
|
|
}
|
|
|
|
throw new FileNotFoundException($"Path does not exist in the VFS: '{path}'");
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool TryContentFileRead(string path, [NotNullWhen(true)] out Stream? fileStream)
|
|
{
|
|
return TryContentFileRead(new ResourcePath(path), out fileStream);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool TryContentFileRead(ResourcePath path, [NotNullWhen(true)] out Stream? fileStream)
|
|
{
|
|
if (path == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(path));
|
|
}
|
|
|
|
if (!path.IsRooted)
|
|
{
|
|
throw new ArgumentException("Path must be rooted", nameof(path));
|
|
}
|
|
#if DEBUG
|
|
if (!IsPathValid(path))
|
|
{
|
|
throw new FileNotFoundException($"Path '{path}' contains invalid characters/filenames.");
|
|
}
|
|
#endif
|
|
_contentRootsLock.EnterReadLock();
|
|
|
|
try
|
|
{
|
|
foreach (var (prefix, root) in _contentRoots)
|
|
{
|
|
if (!path.TryRelativeTo(prefix, out var relative))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (root.TryGetFile(relative, out fileStream))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
fileStream = null;
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
_contentRootsLock.ExitReadLock();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool ContentFileExists(string path)
|
|
{
|
|
return ContentFileExists(new ResourcePath(path));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool ContentFileExists(ResourcePath path)
|
|
{
|
|
return TryContentFileRead(path, out var _);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IEnumerable<ResourcePath> ContentFindFiles(string path)
|
|
{
|
|
return ContentFindFiles(new ResourcePath(path));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IEnumerable<ResourcePath> ContentFindFiles(ResourcePath path)
|
|
{
|
|
if (path == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(path));
|
|
}
|
|
|
|
if (!path.IsRooted)
|
|
{
|
|
throw new ArgumentException("Path is not rooted", nameof(path));
|
|
}
|
|
|
|
var alreadyReturnedFiles = new HashSet<ResourcePath>();
|
|
|
|
_contentRootsLock.EnterReadLock();
|
|
try
|
|
{
|
|
foreach (var (prefix, root) in _contentRoots)
|
|
{
|
|
if (!path.TryRelativeTo(prefix, out var relative))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var filename in root.FindFiles(relative))
|
|
{
|
|
var newPath = prefix / filename;
|
|
if (!alreadyReturnedFiles.Contains(newPath))
|
|
{
|
|
alreadyReturnedFiles.Add(newPath);
|
|
yield return newPath;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_contentRootsLock.ExitReadLock();
|
|
}
|
|
}
|
|
|
|
public bool TryGetDiskFilePath(ResourcePath path, [NotNullWhen(true)] out string? diskPath)
|
|
{
|
|
// loop over each root trying to get the file
|
|
_contentRootsLock.EnterReadLock();
|
|
try
|
|
{
|
|
foreach (var (prefix, root) in _contentRoots)
|
|
{
|
|
if (root is not DirLoader dirLoader || !path.TryRelativeTo(prefix, out var tempPath))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
diskPath = dirLoader.GetPath(tempPath);
|
|
if (File.Exists(diskPath))
|
|
return true;
|
|
}
|
|
|
|
diskPath = null;
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
_contentRootsLock.ExitReadLock();
|
|
}
|
|
}
|
|
|
|
public void MountStreamAt(MemoryStream stream, ResourcePath path)
|
|
{
|
|
var loader = new SingleStreamLoader(stream, path.ToRelativePath());
|
|
AddRoot(ResourcePath.Root, loader);
|
|
}
|
|
|
|
internal static bool IsPathValid(ResourcePath path)
|
|
{
|
|
var asString = path.ToString();
|
|
if (BadPathCharacterRegex.IsMatch(asString))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var segment in path.EnumerateSegments())
|
|
{
|
|
if (BadPathSegmentRegex.IsMatch(segment))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|