diff --git a/ArchiSteamFarm/ASF.cs b/ArchiSteamFarm/ASF.cs
index 7b31b6fe0..1aecf3e27 100644
--- a/ArchiSteamFarm/ASF.cs
+++ b/ArchiSteamFarm/ASF.cs
@@ -204,7 +204,11 @@ namespace ArchiSteamFarm {
return false;
}
- return !botName.Equals(SharedInfo.ASF, StringComparison.OrdinalIgnoreCase);
+ if (botName.Equals(SharedInfo.ASF, StringComparison.OrdinalIgnoreCase)) {
+ return false;
+ }
+
+ return RuntimeCompatibility.Path.GetRelativePath(".", botName) == botName;
}
internal static async Task RestartOrExit() {
diff --git a/ArchiSteamFarm/ArchiSteamFarm.csproj b/ArchiSteamFarm/ArchiSteamFarm.csproj
index 528207ef9..7bf44aa82 100644
--- a/ArchiSteamFarm/ArchiSteamFarm.csproj
+++ b/ArchiSteamFarm/ArchiSteamFarm.csproj
@@ -6,6 +6,10 @@
Exe
+
+ true
+
+
diff --git a/ArchiSteamFarm/RuntimeCompatibility/Path.cs b/ArchiSteamFarm/RuntimeCompatibility/Path.cs
index 55bb862e9..825d5fc19 100644
--- a/ArchiSteamFarm/RuntimeCompatibility/Path.cs
+++ b/ArchiSteamFarm/RuntimeCompatibility/Path.cs
@@ -21,6 +21,7 @@
#if NETFRAMEWORK
using System;
+using System.Text;
#endif
using JetBrains.Annotations;
@@ -29,21 +30,97 @@ namespace ArchiSteamFarm.RuntimeCompatibility {
public static class Path {
public static string GetRelativePath(string relativeTo, string path) {
#if NETFRAMEWORK
- if (relativeTo == null) {
+ if (string.IsNullOrEmpty(relativeTo)) {
throw new ArgumentNullException(nameof(relativeTo));
}
- if (path == null) {
+ if (string.IsNullOrEmpty(path)) {
throw new ArgumentNullException(nameof(path));
}
- if (!path.StartsWith(relativeTo, StringComparison.Ordinal)) {
- throw new NotImplementedException();
+ StringComparison comparisonType = PathInternalNetCore.StringComparison;
+
+ relativeTo = System.IO.Path.GetFullPath(relativeTo);
+ path = System.IO.Path.GetFullPath(path);
+
+ // Need to check if the roots are different- if they are we need to return the "to" path.
+ if (!PathInternalNetCore.AreRootsEqual(relativeTo, path, comparisonType)) {
+ return path;
}
- string result = path[relativeTo.Length..];
+ int commonLength = PathInternalNetCore.GetCommonPathLength(
+ relativeTo, path,
+ comparisonType == StringComparison.OrdinalIgnoreCase
+ );
- return (result[0] == System.IO.Path.DirectorySeparatorChar) || (result[0] == System.IO.Path.AltDirectorySeparatorChar) ? result[1..] : result;
+ // If there is nothing in common they can't share the same root, return the "to" path as is.
+ if (commonLength == 0) {
+ return path;
+ }
+
+ // Trailing separators aren't significant for comparison
+ int relativeToLength = relativeTo.Length;
+
+ if (PathInternalNetCore.EndsInDirectorySeparator(relativeTo)) {
+ relativeToLength--;
+ }
+
+ bool pathEndsInSeparator = PathInternalNetCore.EndsInDirectorySeparator(path);
+ int pathLength = path.Length;
+
+ if (pathEndsInSeparator) {
+ pathLength--;
+ }
+
+ // If we have effectively the same path, return "."
+ if ((relativeToLength == pathLength) && (commonLength >= relativeToLength)) {
+ return ".";
+ }
+
+ // We have the same root, we need to calculate the difference now using the
+ // common Length and Segment count past the length.
+ //
+ // Some examples:
+ //
+ // C:\Foo C:\Bar L3, S1 -> ..\Bar
+ // C:\Foo C:\Foo\Bar L6, S0 -> Bar
+ // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar
+ // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar
+
+ StringBuilder sb = new(); //StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length));
+
+ // Add parent segments for segments past the common on the "from" path
+ if (commonLength < relativeToLength) {
+ sb.Append("..");
+
+ for (int i = commonLength + 1; i < relativeToLength; i++) {
+ if (PathInternalNetCore.IsDirectorySeparator(relativeTo[i])) {
+ sb.Append(System.IO.Path.DirectorySeparatorChar);
+ sb.Append("..");
+ }
+ }
+ } else if (PathInternalNetCore.IsDirectorySeparator(path[commonLength])) {
+ // No parent segments and we need to eat the initial separator
+ // (C:\Foo C:\Foo\Bar case)
+ commonLength++;
+ }
+
+ // Now add the rest of the "to" path, adding back the trailing separator
+ int differenceLength = pathLength - commonLength;
+
+ if (pathEndsInSeparator) {
+ differenceLength++;
+ }
+
+ if (differenceLength > 0) {
+ if (sb.Length > 0) {
+ sb.Append(System.IO.Path.DirectorySeparatorChar);
+ }
+
+ sb.Append(path, commonLength, differenceLength);
+ }
+
+ return sb.ToString(); //StringBuilderCache.GetStringAndRelease(sb);
#else
return System.IO.Path.GetRelativePath(relativeTo, path);
#endif
diff --git a/ArchiSteamFarm/RuntimeCompatibility/PathInternalNetCore.cs b/ArchiSteamFarm/RuntimeCompatibility/PathInternalNetCore.cs
new file mode 100644
index 000000000..f5ae1d872
--- /dev/null
+++ b/ArchiSteamFarm/RuntimeCompatibility/PathInternalNetCore.cs
@@ -0,0 +1,173 @@
+// _ _ _ ____ _ _____
+// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
+// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
+// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
+// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
+// |
+// Copyright 2015-2021 Ćukasz "JustArchi" Domeradzki
+// Contact: JustArchi@JustArchi.net
+// |
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// |
+// http://www.apache.org/licenses/LICENSE-2.0
+// |
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#if NETFRAMEWORK
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace ArchiSteamFarm.RuntimeCompatibility {
+ internal static class PathInternalNetCore {
+ private const string ExtendedDevicePathPrefix = @"\\?\";
+ private const string UncExtendedPathPrefix = @"\\?\UNC\";
+
+ internal static StringComparison StringComparison => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
+
+ ///
+ /// Returns true if the two paths have the same root
+ ///
+ internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType) {
+ int firstRootLength = GetRootLength(first);
+ int secondRootLength = GetRootLength(second);
+
+ return (firstRootLength == secondRootLength)
+ && (string.Compare(
+ first,
+ 0,
+ second,
+ 0,
+ firstRootLength,
+ comparisonType
+ ) == 0);
+ }
+
+ ///
+ /// Returns true if the path ends in a directory separator.
+ ///
+ internal static bool EndsInDirectorySeparator(string path) => (path.Length > 0) && IsDirectorySeparator(path[^1]);
+
+ ///
+ /// Get the common path length from the start of the string.
+ ///
+ internal static int GetCommonPathLength(string first, string second, bool ignoreCase) {
+ int commonChars = EqualStartingCharacterCount(first, second, ignoreCase);
+
+ // If nothing matches
+ if (commonChars == 0) {
+ return commonChars;
+ }
+
+ // Or we're a full string and equal length or match to a separator
+ if ((commonChars == first.Length)
+ && ((commonChars == second.Length) || IsDirectorySeparator(second[commonChars]))) {
+ return commonChars;
+ }
+
+ if ((commonChars == second.Length) && IsDirectorySeparator(first[commonChars])) {
+ return commonChars;
+ }
+
+ // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar.
+ while ((commonChars > 0) && !IsDirectorySeparator(first[commonChars - 1])) {
+ commonChars--;
+ }
+
+ return commonChars;
+ }
+
+ ///
+ /// True if the given character is a directory separator.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static bool IsDirectorySeparator(char c) => (c == System.IO.Path.DirectorySeparatorChar) || (c == System.IO.Path.AltDirectorySeparatorChar);
+
+ ///
+ /// Gets the count of common characters from the left optionally ignoring case
+ ///
+ private static unsafe int EqualStartingCharacterCount(string first, string second, bool ignoreCase) {
+ if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) {
+ return 0;
+ }
+
+ int commonChars = 0;
+
+ fixed (char* f = first)
+ fixed (char* s = second) {
+ char* l = f;
+ char* r = s;
+ char* leftEnd = l + first.Length;
+ char* rightEnd = r + second.Length;
+
+ while ((l != leftEnd) && (r != rightEnd)
+ && ((*l == *r) || (ignoreCase &&
+ (char.ToUpperInvariant(*l) == char.ToUpperInvariant(*r))))) {
+ commonChars++;
+ l++;
+ r++;
+ }
+ }
+
+ return commonChars;
+ }
+
+ ///
+ /// Gets the length of the root of the path (drive, share, etc.).
+ ///
+ private static int GetRootLength(string path) {
+ int i = 0;
+ int volumeSeparatorLength = 2; // Length to the colon "C:"
+ int uncRootLength = 2; // Length to the start of the server name "\\"
+
+ bool extendedSyntax = path.StartsWith(ExtendedDevicePathPrefix, StringComparison.Ordinal);
+ bool extendedUncSyntax = path.StartsWith(UncExtendedPathPrefix, StringComparison.Ordinal);
+
+ if (extendedSyntax) {
+ // Shift the position we look for the root from to account for the extended prefix
+ if (extendedUncSyntax) {
+ // "\\" -> "\\?\UNC\"
+ uncRootLength = UncExtendedPathPrefix.Length;
+ } else {
+ // "C:" -> "\\?\C:"
+ volumeSeparatorLength += ExtendedDevicePathPrefix.Length;
+ }
+ }
+
+ if ((!extendedSyntax || extendedUncSyntax) && (path.Length > 0) && IsDirectorySeparator(path[0])) {
+ // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo")
+
+ i = 1; // Drive rooted (\foo) is one character
+
+ if (extendedUncSyntax || ((path.Length > 1) && IsDirectorySeparator(path[1]))) {
+ // UNC (\\?\UNC\ or \\), scan past the next two directory separators at most
+ // (e.g. to \\?\UNC\Server\Share or \\Server\Share\)
+ i = uncRootLength;
+ int n = 2; // Maximum separators to skip
+
+ while ((i < path.Length) && (!IsDirectorySeparator(path[i]) || (--n > 0))) {
+ i++;
+ }
+ }
+ } else if ((path.Length >= volumeSeparatorLength) &&
+ (path[volumeSeparatorLength - 1] == System.IO.Path.VolumeSeparatorChar)) {
+ // Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:)
+ // If the colon is followed by a directory separator, move past it
+ i = volumeSeparatorLength;
+
+ if ((path.Length >= volumeSeparatorLength + 1) && IsDirectorySeparator(path[volumeSeparatorLength])) {
+ i++;
+ }
+ }
+
+ return i;
+ }
+ }
+}
+#endif