4 using System.Collections.Generic;
5 using System.Collections.Immutable;
14 static class Validation
16 private static readonly ImmutableArray<Identifier> unwritableDirs =
new[] {
"Content".ToIdentifier() }.ToImmutableArray();
17 private static readonly ImmutableArray<Identifier> unwritableExtensions =
new[]
19 ".exe",
".dll",
".json",
".pdb",
".com",
".scr",
".dylib",
".so",
".a",
".app",
21 }.ToIdentifiers().ToImmutableArray();
27 SkipValidationInDebugBuilds =
false;
34 public static Skipper SkipInDebugBuilds()
36 SkipValidationInDebugBuilds =
true;
43 public static bool SkipValidationInDebugBuilds;
45 public static bool CanWrite(
string path,
bool isDirectory)
47 string getFullPath(
string p)
48 => System.IO.Path.GetFullPath(p).CleanUpPath();
50 path = getFullPath(path);
54 string workshopStagingDir = getFullPath(SteamManager.Workshop.PublishStagingDir);
55 string tempDownloadDir = getFullPath(ModReceiver.DownloadFolder);
60 Identifier extension = System.IO.Path.GetExtension(path).Replace(
" ",
"").ToIdentifier();
62 bool pathStartsWith(
string prefix)
63 => path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
65 if (!pathStartsWith(workshopModsDir)
66 && !pathStartsWith(localModsDir)
68 && !pathStartsWith(tempDownloadDir)
69 && !pathStartsWith(workshopStagingDir)
71 && unwritableExtensions.Any(e => e == extension))
77 foreach (var unwritableDir
in unwritableDirs)
79 string dir = System.IO.Path.GetFullPath(unwritableDir.Value).CleanUpPath();
81 if (path.StartsWith(dir, StringComparison.InvariantCultureIgnoreCase))
84 return SkipValidationInDebugBuilds;
95 public static class SafeXML
97 public static void SaveSafe(
98 this System.Xml.Linq.XDocument doc,
100 System.Xml.Linq.SaveOptions saveOptions = System.Xml.Linq.SaveOptions.None,
101 bool throwExceptions =
false)
103 if (!Validation.CanWrite(path,
false))
105 string errorMsg = $
"Cannot save XML document to \"{path}\": modifying the files in this folder/with this extension is not allowed.";
108 throw new InvalidOperationException(errorMsg);
112 DebugConsole.ThrowError(errorMsg);
116 doc.Save(path, saveOptions);
119 public static void SaveSafe(
this System.Xml.Linq.XElement element,
string path,
bool throwExceptions =
false)
121 if (!Validation.CanWrite(path,
false))
123 string errorMsg = $
"Cannot save XML element to \"{path}\": modifying the files in this folder/with this extension is not allowed.";
126 throw new InvalidOperationException(errorMsg);
130 DebugConsole.ThrowError(errorMsg);
137 public static void SaveSafe(
this System.Xml.Linq.XDocument doc, XmlWriter writer)
142 public static void WriteTo(
this System.Xml.Linq.XDocument doc, XmlWriter writer)
152 public XmlWriter(
string path, System.Xml.XmlWriterSettings settings)
154 if (!Validation.CanWrite(path,
false))
156 DebugConsole.ThrowError($
"Cannot write XML document to \"{path}\": modifying the files in this folder/with this extension is not allowed.");
168 public void Write(System.Xml.Linq.XDocument doc)
172 DebugConsole.ThrowError(
"Cannot write to invalid XmlWriter");
182 DebugConsole.ThrowError(
"Cannot flush invalid XmlWriter");
192 DebugConsole.ThrowError(
"Cannot dispose invalid XmlWriter");
199 public static class XmlWriterExtensions
201 public static void Save(
this System.Xml.Linq.XDocument doc, XmlWriter writer)
203 doc.Save(writer.Writer ??
throw new NullReferenceException(
"Unable to save XML document: XML writer is null."));
207 public static class Path
209 public static readonly
char DirectorySeparatorChar = System.IO.Path.DirectorySeparatorChar;
210 public static readonly
char AltDirectorySeparatorChar = System.IO.Path.AltDirectorySeparatorChar;
212 public static string GetExtension(
string path) => System.IO.Path.GetExtension(path);
214 public static string GetFileNameWithoutExtension(
string path) => System.IO.Path.GetFileNameWithoutExtension(path);
216 public static string? GetPathRoot(
string? path) => System.IO.Path.GetPathRoot(path);
218 public static string GetRelativePath(
string relativeTo,
string path) => System.IO.Path.GetRelativePath(relativeTo, path);
220 public static string GetDirectoryName(ContentPath path) => GetDirectoryName(path.Value)!;
222 public static string? GetDirectoryName(
string path) => System.IO.Path.GetDirectoryName(path);
224 public static string GetFileName(
string path) => System.IO.Path.GetFileName(path);
226 public static string GetFullPath(
string path) => System.IO.Path.GetFullPath(path);
228 public static string Combine(params
string[] s) => System.IO.Path.Combine(s);
230 public static string GetTempFileName() => System.IO.Path.GetTempFileName();
232 public static bool IsPathRooted(
string path) => System.IO.Path.IsPathRooted(path);
234 private static readonly ImmutableHashSet<char> invalidFileNameChars = ImmutableHashSet.Create
236 '\"',
'<',
'>',
'|',
'\0',
237 (
char)1, (
char)2, (
char)3, (
char)4, (
char)5, (
char)6, (
char)7, (
char)8, (
char)9, (
char)10,
238 (
char)11, (
char)12, (
char)13, (
char)14, (
char)15, (
char)16, (
char)17, (
char)18, (
char)19, (
char)20,
239 (
char)21, (
char)22, (
char)23, (
char)24, (
char)25, (
char)26, (
char)27, (
char)28, (
char)29, (
char)30,
240 (
char)31,
':',
'*',
'?',
'\\',
'/'
246 public static ImmutableHashSet<char> GetInvalidFileNameCharsCrossPlatform() => invalidFileNameChars;
249 public static class Directory
251 public static string GetCurrentDirectory()
254 return System.IO.Directory.GetCurrentDirectory();
257 public static void SetCurrentDirectory(
string path)
260 System.IO.Directory.SetCurrentDirectory(path);
263 public static string[] GetFiles(
string path)
267 return System.IO.Directory.GetFiles(path);
269 catch (UnauthorizedAccessException e)
271 DebugConsole.ThrowError($
"Cannot get files at \"{path}\": unauthorized access. The folder/file(s) might be read-only!", e);
272 return Array.Empty<
string>();
276 public static string[] GetFiles(
string path,
string pattern, System.IO.SearchOption option = System.IO.SearchOption.AllDirectories)
280 return System.IO.Directory.GetFiles(path, pattern, option);
282 catch (UnauthorizedAccessException e)
284 DebugConsole.ThrowError($
"Cannot get files at \"{path}\": unauthorized access. The folder/file(s) might be read-only!", e);
285 return Array.Empty<
string>();
289 public static string[] GetDirectories(
string path,
string searchPattern =
"*", System.IO.SearchOption searchOption = System.IO.SearchOption.TopDirectoryOnly)
293 return System.IO.Directory.GetDirectories(path, searchPattern, searchOption);
295 catch (UnauthorizedAccessException e)
297 DebugConsole.ThrowError($
"Cannot get directories at \"{path}\": unauthorized access. The folder(s) might be read-only!", e);
298 return Array.Empty<
string>();
302 public static string[] GetFileSystemEntries(
string path)
306 return System.IO.Directory.GetFileSystemEntries(path);
308 catch (UnauthorizedAccessException e)
310 DebugConsole.ThrowError($
"Cannot get file system entries at \"{path}\": unauthorized access. The file/folder might be read-only!", e);
311 return Array.Empty<
string>();
315 public static IEnumerable<string> EnumerateDirectories(
string path,
string pattern)
319 return System.IO.Directory.EnumerateDirectories(path, pattern);
321 catch (UnauthorizedAccessException e)
323 DebugConsole.ThrowError($
"Cannot enumerate directories at \"{path}\": unauthorized access. The folder(s) might be read-only!", e);
324 return Array.Empty<
string>();
328 public static IEnumerable<string> EnumerateFiles(
string path,
string pattern)
332 return System.IO.Directory.EnumerateFiles(path, pattern);
334 catch (UnauthorizedAccessException e)
336 DebugConsole.ThrowError($
"Cannot enumerate files at \"{path}\": unauthorized access. The file(s)/folder(s) might be read-only!", e);
337 return Array.Empty<
string>();
341 public static bool Exists(
string path)
343 return System.IO.Directory.Exists(path);
346 public static System.IO.DirectoryInfo? CreateDirectory(
string path)
348 if (!Validation.CanWrite(path,
true))
350 DebugConsole.ThrowError($
"Cannot create directory \"{path}\": modifying the contents of this folder/using this extension is not allowed.");
351 Validation.CanWrite(path,
true);
356 return System.IO.Directory.CreateDirectory(path);
358 catch (UnauthorizedAccessException e)
360 DebugConsole.ThrowError($
"Cannot create directory at \"{path}\": unauthorized access. The file/folder might be read-only!", e);
365 public static void Delete(
string path,
bool recursive=
true)
367 if (!Validation.CanWrite(path,
true))
369 DebugConsole.ThrowError($
"Cannot delete directory \"{path}\": modifying the contents of this folder/using this extension is not allowed.");
375 System.IO.Directory.Delete(path, recursive);
377 catch (UnauthorizedAccessException e)
379 DebugConsole.ThrowError($
"Cannot delete \"{path}\": unauthorized access. The file/folder might be read-only!", e);
383 public static bool TryDelete(
string path,
bool recursive =
true)
387 Delete(path, recursive);
396 public static DateTime GetLastWriteTime(
string path)
400 return System.IO.Directory.GetLastWriteTime(path);
402 catch (UnauthorizedAccessException e)
404 DebugConsole.ThrowError($
"Cannot get last write time at \"{path}\": unauthorized access. The file/folder might be read-only!", e);
405 return new DateTime();
410 public static class File
412 public static bool Exists(ContentPath path) => Exists(path.Value);
414 public static bool Exists(
string path) => System.IO.File.Exists(path);
416 public static void Copy(
string src,
string dest,
bool overwrite =
false)
418 if (!Validation.CanWrite(dest,
false))
420 DebugConsole.ThrowError($
"Cannot copy \"{src}\" to \"{dest}\": modifying the contents of this folder/using this extension is not allowed.");
425 System.IO.File.Copy(src, dest, overwrite);
427 catch (UnauthorizedAccessException e)
429 DebugConsole.ThrowError($
"Cannot copy \"{src}\" to \"{dest}\": unauthorized access. The file/folder might be read-only!", e);
433 public static void Move(
string src,
string dest)
435 if (!Validation.CanWrite(src,
false))
437 DebugConsole.ThrowError($
"Cannot move \"{src}\" to \"{dest}\": modifying the contents of the source folder is not allowed.");
440 if (!Validation.CanWrite(dest,
false))
442 DebugConsole.ThrowError($
"Cannot move \"{src}\" to \"{dest}\": modifying the contents of the destination folder is not allowed");
447 System.IO.File.Move(src, dest);
449 catch (UnauthorizedAccessException e)
451 DebugConsole.ThrowError($
"Cannot move \"{src}\" to \"{dest}\": unauthorized access. The file/folder might be read-only!", e);
455 public static void Delete(ContentPath path) => Delete(path.Value);
457 public static void Delete(
string path)
459 if (!Validation.CanWrite(path,
false))
461 DebugConsole.ThrowError($
"Cannot delete file \"{path}\": modifying the contents of this folder/using this extension is not allowed.");
466 System.IO.File.Delete(path);
468 catch (UnauthorizedAccessException e)
470 DebugConsole.ThrowError($
"Cannot delete {path}: unauthorized access. The file/folder might be read-only!", e);
474 public static DateTime GetLastWriteTime(
string path)
476 return System.IO.File.GetLastWriteTime(path);
479 public static FileStream? Open(
481 System.IO.FileMode mode,
482 System.IO.FileAccess access = System.IO.FileAccess.ReadWrite,
483 System.IO.FileShare? share =
null)
487 case System.IO.FileMode.Create:
488 case System.IO.FileMode.CreateNew:
489 case System.IO.FileMode.OpenOrCreate:
490 case System.IO.FileMode.Append:
491 case System.IO.FileMode.Truncate:
492 if (!Validation.CanWrite(path,
false))
494 DebugConsole.ThrowError($
"Cannot open \"{path}\" in {mode} mode: modifying the contents of this folder/using this extension is not allowed.");
500 !Validation.CanWrite(path,
false) ?
501 System.IO.FileAccess.Read :
503 var shareVal = share ?? (access == System.IO.FileAccess.Read ? System.IO.FileShare.Read : System.IO.FileShare.None);
506 return new FileStream(path, System.IO.File.Open(path, mode, access, shareVal));
508 catch (UnauthorizedAccessException e)
510 DebugConsole.ThrowError($
"Cannot open {path} (stream): unauthorized access. The file/folder might be read-only!", e);
515 public static FileStream? OpenRead(
string path)
517 return Open(path, System.IO.FileMode.Open, System.IO.FileAccess.Read);
520 public static FileStream? OpenWrite(
string path)
522 return Open(path, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write);
525 public static FileStream? Create(
string path)
527 return Open(path, System.IO.FileMode.Create, System.IO.FileAccess.Write);
530 public static void WriteAllBytes(
string path,
byte[] contents)
532 if (!Validation.CanWrite(path,
false))
534 DebugConsole.ThrowError($
"Cannot write all bytes to \"{path}\": modifying the files in this folder/with this extension is not allowed.");
539 System.IO.File.WriteAllBytes(path, contents);
541 catch (UnauthorizedAccessException e)
543 DebugConsole.ThrowError($
"Cannot write at {path}: unauthorized access. The file/folder might be read-only!", e);
547 public static void WriteAllText(
string path,
string contents, System.Text.Encoding? encoding =
null)
549 if (!Validation.CanWrite(path,
false))
551 DebugConsole.ThrowError($
"Cannot write all text to \"{path}\": modifying the files in this folder/with this extension is not allowed.");
556 System.IO.File.WriteAllText(path, contents, encoding ?? System.Text.Encoding.UTF8);
558 catch (UnauthorizedAccessException e)
560 DebugConsole.ThrowError($
"Cannot write at {path}: unauthorized access. The file/folder might be read-only!", e);
564 public static void WriteAllLines(
string path, IEnumerable<string> contents, System.Text.Encoding? encoding =
null)
566 if (!Validation.CanWrite(path,
false))
568 DebugConsole.ThrowError($
"Cannot write all lines to \"{path}\": modifying the files in this folder/with this extension is not allowed.");
573 System.IO.File.WriteAllLines(path, contents, encoding ?? System.Text.Encoding.UTF8);
575 catch (UnauthorizedAccessException e)
577 DebugConsole.ThrowError($
"Cannot write at {path}: unauthorized access. The file/folder might be read-only!", e);
581 public static byte[] ReadAllBytes(
string path)
585 return System.IO.File.ReadAllBytes(path);
587 catch (UnauthorizedAccessException e)
589 DebugConsole.ThrowError($
"Cannot read {path}: unauthorized access. The file/folder might be read-only!", e);
590 return Array.Empty<
byte>();
594 public static string ReadAllText(
string path, System.Text.Encoding? encoding =
null)
598 return System.IO.File.ReadAllText(path, encoding ?? System.Text.Encoding.UTF8);
600 catch (UnauthorizedAccessException e)
602 DebugConsole.ThrowError($
"Cannot read {path}: unauthorized access. The file/folder might be read-only!", e);
607 public static string[] ReadAllLines(
string path, System.Text.Encoding? encoding =
null)
611 return System.IO.File.ReadAllLines(path, encoding ?? System.Text.Encoding.UTF8);
613 catch (UnauthorizedAccessException e)
615 DebugConsole.ThrowError($
"Cannot read {path}: unauthorized access. The file/folder might be read-only!", e);
616 return Array.Empty<
string>();
623 private readonly System.IO.
FileStream innerStream;
624 private readonly
string fileName;
628 innerStream = stream;
632 public override bool CanRead => innerStream.CanRead;
633 public override bool CanSeek => innerStream.CanSeek;
639 if (!Validation.CanWrite(fileName,
false)) {
return false; }
644 public override long Length => innerStream.Length;
658 public override int Read(
byte[] buffer,
int offset,
int count)
660 return innerStream.
Read(buffer, offset, count);
663 public override void Write(
byte[] buffer,
int offset,
int count)
665 if (Validation.CanWrite(fileName,
false))
667 innerStream.
Write(buffer, offset, count);
671 DebugConsole.ThrowError($
"Cannot write to file \"{fileName}\": modifying the files in this folder/with this extension is not allowed.");
675 public override long Seek(
long offset, System.IO.SeekOrigin origin)
677 return innerStream.
Seek(offset, origin);
690 protected override void Dispose(
bool notCalledByFinalizer)
692 if (notCalledByFinalizer) { innerStream.
Dispose(); }
711 public string Name => innerInfo.Name;
714 public System.IO.FileAttributes
Attributes => innerInfo.Attributes;
719 foreach (var dir
in dirs)
728 foreach (var file
in files)
736 if (!Validation.CanWrite(innerInfo.
FullName,
false))
738 DebugConsole.ThrowError($
"Cannot delete directory \"{Name}\": modifying the contents of this folder/using this extension is not allowed.");
747 private System.IO.
FileInfo innerInfo;
751 innerInfo =
new System.IO.
FileInfo(path);
760 public string Name => innerInfo.Name;
772 if (!Validation.CanWrite(innerInfo.
FullName,
false))
774 DebugConsole.ThrowError($
"Cannot set read-only to {value} for \"{Name}\": modifying the files in this folder/with this extension is not allowed.");
781 public void CopyTo(
string dest,
bool overwriteExisting =
false)
783 if (!Validation.CanWrite(dest,
false))
785 DebugConsole.ThrowError($
"Cannot copy \"{Name}\" to \"{dest}\": modifying the contents of the destination folder is not allowed.");
788 innerInfo.
CopyTo(dest, overwriteExisting);
793 if (!Validation.CanWrite(innerInfo.
FullName,
false))
795 DebugConsole.ThrowError($
"Cannot delete file \"{Name}\": modifying the files in this folder/with this extension is not allowed.");
static readonly string WorkshopModsDir
const string LocalModsDir
DirectoryInfo(string path)
System.IO.FileAttributes Attributes
IEnumerable< FileInfo > GetFiles()
IEnumerable< DirectoryInfo > GetDirectories()
FileInfo(System.IO.FileInfo info)
void CopyTo(string dest, bool overwriteExisting=false)
FileStream(string fn, System.IO.FileStream stream)
override long Seek(long offset, System.IO.SeekOrigin origin)
override void Write(byte[] buffer, int offset, int count)
override void SetLength(long value)
override void Dispose(bool notCalledByFinalizer)
override int Read(byte[] buffer, int offset, int count)
XmlWriter(string path, System.Xml.XmlWriterSettings settings)
static XmlWriter Create(string path, System.Xml.XmlWriterSettings settings)
readonly System.Xml.? XmlWriter Writer
void Write(System.Xml.Linq.XDocument doc)