diff --git a/src/BizHawk.Common/Extensions/PathExtensions.cs b/src/BizHawk.Common/Extensions/PathExtensions.cs
index fe9dac7bec..c013fdc592 100644
--- a/src/BizHawk.Common/Extensions/PathExtensions.cs
+++ b/src/BizHawk.Common/Extensions/PathExtensions.cs
@@ -4,7 +4,7 @@ using BizHawk.Common.StringExtensions;
namespace BizHawk.Common.PathExtensions
{
- public static partial class PathExtensions
+ public static class PathExtensions
{
/// iff indicates a child of , with being returned if either path is
/// algorithm for Windows taken from https://stackoverflow.com/a/7710620/7467292
@@ -48,7 +48,19 @@ namespace BizHawk.Common.PathExtensions
///
public static bool IsAbsolute(this string path)
{
- return PathInternal.IsPathFullyQualified(path);
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
+ return Path.IsPathFullyQualified(path);
+#else
+ if (OSTailoredCode.IsUnixHost)
+ {
+ return path.StartsWith(Path.DirectorySeparatorChar);
+ }
+ else
+ {
+ var root = Path.GetPathRoot(path);
+ return root.StartsWithOrdinal(@"\\") || root.EndsWith('\\') && root is not @"\";
+ }
+#endif
}
/// iff absolute (OS-dependent)
diff --git a/src/BizHawk.Common/Extensions/Pillaged/PathInternal.Unix.cs b/src/BizHawk.Common/Extensions/Pillaged/PathInternal.Unix.cs
deleted file mode 100644
index 6dfeb0a299..0000000000
--- a/src/BizHawk.Common/Extensions/Pillaged/PathInternal.Unix.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#pragma warning disable
-
-using System;
-using System.Diagnostics;
-using System.IO;
-
-namespace BizHawk.Common.PathExtensions
-{
- /// Contains internal path helpers that are shared between many projects.
- internal static partial class PathInternal
- {
- public static bool IsPathFullyQualified(string path)
- {
- return !PathInternal.IsPartiallyQualified(path);
- }
-
- internal static int GetRootLength(ReadOnlySpan path)
- {
- return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0;
- }
-
- internal static bool EndsInDirectorySeparator(ReadOnlySpan path)
- => path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]);
-
- internal static ReadOnlySpan TrimEndingDirectorySeparator(ReadOnlySpan path) =>
- EndsInDirectorySeparator(path) && !IsRoot(path) ?
- path.Slice(0, path.Length - 1) :
- path;
-
- internal static bool IsRoot(ReadOnlySpan path)
- => path.Length == GetRootLength(path);
-
- internal static bool IsDirectorySeparator(char c)
- {
- // The alternate directory separator char is the same as the directory separator,
- // so we only need to check one.
- if (OSTailoredCode.IsUnixHost)
- {
- Debug.Assert(Path.DirectorySeparatorChar == Path.AltDirectorySeparatorChar);
- return c == Path.DirectorySeparatorChar;
- }
- else
- {
- return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
- }
- }
-
- internal static bool IsPartiallyQualified(string path)
- {
- if (OSTailoredCode.IsUnixHost)
- {
- // This is much simpler than Windows where paths can be rooted, but not fully qualified (such as Drive Relative)
- // As long as the path is rooted in Unix it doesn't use the current directory and therefore is fully qualified.
- return string.IsNullOrEmpty(path) || path[0] != Path.DirectorySeparatorChar;
- }
- else
- {
- if (path.Length < 2)
- {
- // It isn't fixed, it must be relative. There is no way to specify a fixed
- // path with one character (or less).
- return true;
- }
-
- if (IsDirectorySeparator(path[0]))
- {
- // There is no valid way to specify a relative path with two initial slashes or
- // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
- return !(path[1] == '?' || IsDirectorySeparator(path[1]));
- }
-
- // The only way to specify a fixed path that doesn't begin with two slashes
- // is the drive, colon, slash format- i.e. C:\
- return !((path.Length >= 3)
- && (path[1] == Path.VolumeSeparatorChar)
- && IsDirectorySeparator(path[2])
- // To match old behavior we'll check the drive character for validity as the path is technically
- // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream.
- && IsValidDriveChar(path[0]));
- }
- }
- }
-}
diff --git a/src/BizHawk.Common/Extensions/Pillaged/PathInternal.Windows.cs b/src/BizHawk.Common/Extensions/Pillaged/PathInternal.Windows.cs
deleted file mode 100644
index c05b908ae6..0000000000
--- a/src/BizHawk.Common/Extensions/Pillaged/PathInternal.Windows.cs
+++ /dev/null
@@ -1,151 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#pragma warning disable
-
-using System;
-using System.Diagnostics.CodeAnalysis;
-
-namespace BizHawk.Common.PathExtensions
-{
- /// Contains internal path helpers that are shared between many projects.
- internal static partial class PathInternal
- {
- // All paths in Win32 ultimately end up becoming a path to a File object in the Windows object manager. Passed in paths get mapped through
- // DosDevice symbolic links in the object tree to actual File objects under \Devices. To illustrate, this is what happens with a typical
- // path "Foo" passed as a filename to any Win32 API:
- //
- // 1. "Foo" is recognized as a relative path and is appended to the current directory (say, "C:\" in our example)
- // 2. "C:\Foo" is prepended with the DosDevice namespace "\??\"
- // 3. CreateFile tries to create an object handle to the requested file "\??\C:\Foo"
- // 4. The Object Manager recognizes the DosDevices prefix and looks
- // a. First in the current session DosDevices ("\Sessions\1\DosDevices\" for example, mapped network drives go here)
- // b. If not found in the session, it looks in the Global DosDevices ("\GLOBAL??\")
- // 5. "C:" is found in DosDevices (in our case "\GLOBAL??\C:", which is a symbolic link to "\Device\HarddiskVolume6")
- // 6. The full path is now "\Device\HarddiskVolume6\Foo", "\Device\HarddiskVolume6" is a File object and parsing is handed off
- // to the registered parsing method for Files
- // 7. The registered open method for File objects is invoked to create the file handle which is then returned
- //
- // There are multiple ways to directly specify a DosDevices path. The final format of "\??\" is one way. It can also be specified
- // as "\\.\" (the most commonly documented way) and "\\?\". If the question mark syntax is used the path will skip normalization
- // (essentially GetFullPathName()) and path length checks.
-
- // Windows Kernel-Mode Object Manager
- // https://msdn.microsoft.com/en-us/library/windows/hardware/ff565763.aspx
- // https://channel9.msdn.com/Shows/Going+Deep/Windows-NT-Object-Manager
- //
- // Introduction to MS-DOS Device Names
- // https://msdn.microsoft.com/en-us/library/windows/hardware/ff548088.aspx
- //
- // Local and Global MS-DOS Device Names
- // https://msdn.microsoft.com/en-us/library/windows/hardware/ff554302.aspx
-
- internal const string ExtendedDevicePathPrefix = @"\\?\";
- internal const string UncPathPrefix = @"\\";
- internal const string UncDevicePrefixToInsert = @"?\UNC\";
- internal const string UncExtendedPathPrefix = @"\\?\UNC\";
- internal const string DevicePathPrefix = @"\\.\";
-
- internal const int MaxShortPath = 260;
-
- // \\?\, \\.\, \??\
- internal const int DevicePrefixLength = 4;
-
- ///
- /// Returns true if the given character is a valid drive letter
- ///
- internal static bool IsValidDriveChar(char value)
- {
- return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z'));
- }
-
- private static bool EndsWithPeriodOrSpace(string path)
- {
- if (string.IsNullOrEmpty(path))
- return false;
-
- char c = path[path.Length - 1];
- return c == ' ' || c == '.';
- }
-
- ///
- /// Adds the extended path prefix (\\?\) if not already a device path, IF the path is not relative,
- /// AND the path is more than 259 characters. (> MAX_PATH + null). This will also insert the extended
- /// prefix if the path ends with a period or a space. Trailing periods and spaces are normally eaten
- /// away from paths during normalization, but if we see such a path at this point it should be
- /// normalized and has retained the final characters. (Typically from one of the *Info classes)
- ///
- [return: NotNullIfNotNull("path")]
- internal static string? EnsureExtendedPrefixIfNeeded(string? path)
- {
- if (path != null && (path.Length >= MaxShortPath || EndsWithPeriodOrSpace(path)))
- {
- return EnsureExtendedPrefix(path);
- }
- else
- {
- return path;
- }
- }
-
- ///
- /// Adds the extended path prefix (\\?\) if not relative or already a device path.
- ///
- internal static string EnsureExtendedPrefix(string path)
- {
- // Putting the extended prefix on the path changes the processing of the path. It won't get normalized, which
- // means adding to relative paths will prevent them from getting the appropriate current directory inserted.
-
- // If it already has some variant of a device path (\??\, \\?\, \\.\, //./, etc.) we don't need to change it
- // as it is either correct or we will be changing the behavior. When/if Windows supports long paths implicitly
- // in the future we wouldn't want normalization to come back and break existing code.
-
- // In any case, all internal usages should be hitting normalize path (Path.GetFullPath) before they hit this
- // shimming method. (Or making a change that doesn't impact normalization, such as adding a filename to a
- // normalized base path.)
- if (IsPartiallyQualified(path) || IsDevice(path))
- return path;
-
- // Given \\server\share in longpath becomes \\?\UNC\server\share
- if (path.StartsWith(UncPathPrefix, StringComparison.OrdinalIgnoreCase))
- return path.Insert(2, UncDevicePrefixToInsert);
-
- return ExtendedDevicePathPrefix + path;
- }
-
- ///
- /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
- ///
- internal static bool IsDevice(string path)
- {
- // If the path begins with any two separators is will be recognized and normalized and prepped with
- // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not.
- return IsExtended(path)
- ||
- (
- path.Length >= DevicePrefixLength
- && IsDirectorySeparator(path[0])
- && IsDirectorySeparator(path[1])
- && (path[2] == '.' || path[2] == '?')
- && IsDirectorySeparator(path[3])
- );
- }
-
- ///
- /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the
- /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization
- /// and path length checks.
- ///
- internal static bool IsExtended(string path)
- {
- // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
- // Skipping of normalization will *only* occur if back slashes ('\') are used.
- return path.Length >= DevicePrefixLength
- && path[0] == '\\'
- && (path[1] == '\\' || path[1] == '?')
- && path[2] == '?'
- && path[3] == '\\';
- }
-
- }
-}