1+ using System . Runtime . InteropServices ;
2+ using Environment = System . Environment ;
3+
14namespace Reloaded . Mod . Launcher . Lib . Utility ;
25
36/// <summary>
@@ -6,37 +9,137 @@ namespace Reloaded.Mod.Launcher.Lib.Utility;
69public static class PathUtility
710{
811 /// <summary>
9- /// Checks whether a given path is inside a OneDrive-managed folder.
10- /// Uses the <c>OneDrive</c> / <c>OneDriveCommercial</c> environment variables.
12+ /// Checks whether a given path is inside a cloud sync folder (OneDrive, Dropbox, Google Drive,
13+ /// iCloud, Box, MEGA, etc.). Reloaded and games inside such folders are avoided because many mods
14+ /// do not tolerate cloud offload/locking, and load times are poor.
15+ ///
16+ /// Detection is layered, checked in order:
17+ ///
18+ /// - OneDrive environment variables (<c>OneDrive</c> / <c>OneDriveCommercial</c>).
19+ ///
20+ /// - The Windows Cloud Files API (<c>CfGetSyncRootInfoByPath</c>, available on Windows 10 1709+),
21+ /// which detects any provider that registers a sync root (OneDrive, Dropbox, iCloud, Box, ...).
22+ ///
23+ /// - Known cloud folder names under the user profile, as a fallback for older systems or
24+ /// providers that do not register a sync root.
1125 /// </summary>
1226 /// <param name="path">The path to check.</param>
13- /// <returns>True if the path is inside a OneDrive root ; false otherwise.</returns>
14- public static bool IsPathInOneDrive ( string path )
27+ /// <returns>True if the path is inside a cloud-synced folder ; false otherwise.</returns>
28+ public static bool IsPathInCloudSyncFolder ( string path )
1529 {
1630 if ( string . IsNullOrEmpty ( path ) )
1731 return false ;
1832
33+ string fullPath ;
34+ try { fullPath = Path . GetFullPath ( path ) ; }
35+ catch { return false ; }
36+
37+ return IsInOneDrive ( fullPath )
38+ || IsInRegisteredCloudSyncRoot ( fullPath )
39+ || IsInKnownCloudFolder ( fullPath ) ;
40+ }
41+
42+ // --- Tier 1: OneDrive environment variables ---
43+ private static bool IsInOneDrive ( string fullPath )
44+ {
1945 foreach ( var envVar in s_oneDriveEnvVars )
2046 {
21- var root = System . Environment . GetEnvironmentVariable ( envVar ) ;
47+ var root = Environment . GetEnvironmentVariable ( envVar ) ;
2248 if ( string . IsNullOrEmpty ( root ) )
2349 continue ;
2450
2551 try
2652 {
2753 var fullRoot = Path . GetFullPath ( root )
2854 . TrimEnd ( Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar ) ;
29- var fullPath = Path . GetFullPath ( path )
30- . TrimEnd ( Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar ) ;
55+ if ( IsUnder ( fullPath , fullRoot ) )
56+ return true ;
57+ }
58+ catch { /* malformed path/env - skip */ }
59+ }
3160
32- if ( fullPath . StartsWith ( fullRoot + Path . DirectorySeparatorChar , StringComparison . OrdinalIgnoreCase ) )
61+ return false ;
62+ }
63+
64+ // --- Tier 2: Cloud Files API (cldapi.dll), Windows 10 1709+ ---
65+ // A single native call reports whether the path is inside any registered cloud sync root,
66+ // regardless of provider. No ancestor walk or hydration-state dependence.
67+ private static bool IsInRegisteredCloudSyncRoot ( string fullPath )
68+ {
69+ try
70+ {
71+ // CF_SYNC_ROOT_INFO_CLASS.CfSyncRootInfoBasic == 0.
72+ // A small buffer is plenty for the basic info struct; we only care about the HRESULT.
73+ var buffer = new byte [ 256 ] ;
74+ int hr = CfGetSyncRootInfoByPath ( fullPath , 0 , buffer , buffer . Length , out _ ) ;
75+ return hr >= 0 ; // S_OK (0) => the path resolves to a registered sync root.
76+ }
77+ catch
78+ {
79+ // cldapi.dll missing (older than Windows 10 1709) or call failed: rely on other tiers.
80+ return false ;
81+ }
82+ }
83+
84+ // --- Tier 3: known cloud folder names under the user profile ---
85+ private static bool IsInKnownCloudFolder ( string fullPath )
86+ {
87+ var userProfile = Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ;
88+ if ( string . IsNullOrEmpty ( userProfile ) )
89+ return false ;
90+
91+ string fullProfile ;
92+ try { fullProfile = Path . GetFullPath ( userProfile ) . TrimEnd ( Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar ) ; }
93+ catch { return false ; }
94+
95+ foreach ( var folder in s_knownCloudFolders )
96+ {
97+ try
98+ {
99+ if ( IsUnder ( fullPath , Path . Combine ( fullProfile , folder ) ) )
33100 return true ;
34101 }
35- catch { /* malformed path - skip */ }
102+ catch { /* malformed name - skip */ }
36103 }
37104
38105 return false ;
39106 }
40107
108+ /// <summary>
109+ /// True if <paramref name="fullPath"/> equals or is a descendant of <paramref name="root"/>.
110+ /// Case-insensitive. The separator is appended on the prefix check so a root named
111+ /// e.g. "OneDrive" does not match a sibling like "OneDriveBackup".
112+ /// </summary>
113+ private static bool IsUnder ( string fullPath , string root )
114+ {
115+ var trimmedRoot = root . TrimEnd ( Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar ) ;
116+ if ( trimmedRoot . Length == 0 )
117+ return false ;
118+
119+ return fullPath . Equals ( trimmedRoot , StringComparison . OrdinalIgnoreCase )
120+ || fullPath . StartsWith ( trimmedRoot + Path . DirectorySeparatorChar , StringComparison . OrdinalIgnoreCase ) ;
121+ }
122+
123+ [ DllImport ( "cldapi.dll" , CharSet = CharSet . Unicode ) ]
124+ [ return : MarshalAs ( UnmanagedType . I4 ) ]
125+ private static extern int CfGetSyncRootInfoByPath (
126+ string filePath ,
127+ int infoClass ,
128+ [ Out ] byte [ ] infoBuffer ,
129+ int infoBufferSize ,
130+ out int returnedLength ) ;
131+
41132 private static readonly string [ ] s_oneDriveEnvVars = { "OneDrive" , "OneDriveCommercial" } ;
133+
134+ private static readonly string [ ] s_knownCloudFolders =
135+ {
136+ "Dropbox" ,
137+ "Google Drive" ,
138+ "GoogleDrive" ,
139+ "iCloudDrive" ,
140+ "Box Sync" ,
141+ "Box" ,
142+ "MEGA" ,
143+ "pCloud"
144+ } ;
42145}
0 commit comments