2020-08-30 22:21:53 +05:30
using Ryujinx.Common.Logging ;
2024-01-29 19:58:18 +01:00
using Ryujinx.Common.Utilities ;
2020-08-30 22:21:53 +05:30
using System ;
using System.IO ;
2024-02-11 12:04:39 -06:00
using System.Runtime.Versioning ;
2020-08-30 22:21:53 +05:30
namespace Ryujinx.Common.Configuration
{
public static class AppDataManager
{
2024-01-29 19:58:18 +01:00
private const string DefaultBaseDir = "Ryujinx" ;
private const string DefaultPortableDir = "portable" ;
2020-08-30 22:21:53 +05:30
// The following 3 are always part of Base Directory
private const string GamesDir = "games" ;
private const string ProfilesDir = "profiles" ;
private const string KeysDir = "system" ;
2021-03-16 02:40:36 +05:30
public enum LaunchMode
{
UserProfile ,
Portable ,
2023-06-28 18:41:38 +02:00
Custom ,
2021-03-16 02:40:36 +05:30
}
public static LaunchMode Mode { get ; private set ; }
2020-08-30 22:21:53 +05:30
public static string BaseDirPath { get ; private set ; }
public static string GamesDirPath { get ; private set ; }
public static string ProfilesDirPath { get ; private set ; }
public static string KeysDirPath { get ; private set ; }
2021-03-16 02:40:36 +05:30
public static string KeysDirPathUser { get ; }
2020-08-30 22:21:53 +05:30
2024-02-10 19:17:19 -06:00
public static string LogsDirPath { get ; private set ; }
2020-08-30 22:21:53 +05:30
public const string DefaultNandDir = "bis" ;
public const string DefaultSdcardDir = "sdcard" ;
private const string DefaultModsDir = "mods" ;
public static string CustomModsPath { get ; set ; }
2023-06-28 18:41:38 +02:00
public static string CustomSdModsPath { get ; set ; }
2020-08-30 22:21:53 +05:30
public static string CustomNandPath { get ; set ; } // TODO: Actually implement this into VFS
public static string CustomSdCardPath { get ; set ; } // TODO: Actually implement this into VFS
static AppDataManager ( )
{
2021-03-16 02:40:36 +05:30
KeysDirPathUser = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) , ".switch" ) ;
2020-08-30 22:21:53 +05:30
}
public static void Initialize ( string baseDirPath )
{
2024-02-10 19:17:19 -06:00
string appDataPath = Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) ;
2022-12-06 22:00:25 +00:00
if ( appDataPath . Length = = 0 )
{
appDataPath = Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ;
}
string userProfilePath = Path . Combine ( appDataPath , DefaultBaseDir ) ;
2021-03-16 02:40:36 +05:30
string portablePath = Path . Combine ( AppDomain . CurrentDomain . BaseDirectory , DefaultPortableDir ) ;
2020-08-30 22:21:53 +05:30
2024-01-19 18:09:51 -08:00
// On macOS, check for a portable directory next to the app bundle as well.
if ( OperatingSystem . IsMacOS ( ) & & ! Directory . Exists ( portablePath ) )
{
string bundlePath = Path . GetFullPath ( Path . Combine ( AppDomain . CurrentDomain . BaseDirectory , ".." , ".." ) ) ;
// Make sure we're actually running within an app bundle.
if ( bundlePath . EndsWith ( ".app" ) )
{
portablePath = Path . GetFullPath ( Path . Combine ( bundlePath , ".." , DefaultPortableDir ) ) ;
}
}
2021-03-16 02:40:36 +05:30
if ( Directory . Exists ( portablePath ) )
{
BaseDirPath = portablePath ;
Mode = LaunchMode . Portable ;
}
else
{
BaseDirPath = userProfilePath ;
Mode = LaunchMode . UserProfile ;
}
if ( baseDirPath ! = null & & baseDirPath ! = userProfilePath )
2020-08-30 22:21:53 +05:30
{
if ( ! Directory . Exists ( baseDirPath ) )
{
2021-03-16 02:40:36 +05:30
Logger . Error ? . Print ( LogClass . Application , $"Custom Data Directory '{baseDirPath}' does not exist. Falling back to {Mode}..." ) ;
2020-08-30 22:21:53 +05:30
}
else
{
BaseDirPath = baseDirPath ;
2021-03-16 02:40:36 +05:30
Mode = LaunchMode . Custom ;
2020-08-30 22:21:53 +05:30
}
}
2021-03-16 02:40:36 +05:30
BaseDirPath = Path . GetFullPath ( BaseDirPath ) ; // convert relative paths
2024-02-11 12:04:39 -06:00
if ( IsPathSymlink ( BaseDirPath ) )
2023-02-21 06:14:31 -05:00
{
2024-02-11 12:04:39 -06:00
Logger . Warning ? . Print ( LogClass . Application , $"Application data directory is a symlink. This may be unintended." ) ;
2023-02-21 06:14:31 -05:00
}
2020-08-30 22:21:53 +05:30
SetupBasePaths ( ) ;
}
2024-02-10 19:17:19 -06:00
public static string GetOrCreateLogsDir ( )
{
if ( Directory . Exists ( LogsDirPath ) )
{
return LogsDirPath ;
}
Logger . Notice . Print ( LogClass . Application , "Logging directory not found; attempting to create new logging directory." ) ;
LogsDirPath = SetUpLogsDir ( ) ;
return LogsDirPath ;
}
private static string SetUpLogsDir ( )
{
string logDir = "" ;
if ( Mode = = LaunchMode . Portable )
{
logDir = Path . Combine ( BaseDirPath , "Logs" ) ;
try
{
Directory . CreateDirectory ( logDir ) ;
}
catch
{
Logger . Warning ? . Print ( LogClass . Application , $"Logging directory could not be created '{logDir}'" ) ;
return null ;
}
}
else
{
if ( OperatingSystem . IsMacOS ( ) )
{
// NOTE: Should evaluate to "~/Library/Logs/Ryujinx/".
logDir = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) , "Library" , "Logs" , DefaultBaseDir ) ;
try
{
Directory . CreateDirectory ( logDir ) ;
}
catch
{
Logger . Warning ? . Print ( LogClass . Application , $"Logging directory could not be created '{logDir}'" ) ;
logDir = "" ;
}
if ( string . IsNullOrEmpty ( logDir ) )
{
// NOTE: Should evaluate to "~/Library/Application Support/Ryujinx/Logs".
logDir = Path . Combine ( BaseDirPath , "Logs" ) ;
try
{
Directory . CreateDirectory ( logDir ) ;
}
catch
{
Logger . Warning ? . Print ( LogClass . Application , $"Logging directory could not be created '{logDir}'" ) ;
return null ;
}
}
}
else if ( OperatingSystem . IsWindows ( ) )
{
// NOTE: Should evaluate to a "Logs" directory in whatever directory Ryujinx was launched from.
logDir = Path . Combine ( AppDomain . CurrentDomain . BaseDirectory , "Logs" ) ;
try
{
Directory . CreateDirectory ( logDir ) ;
}
catch
{
Logger . Warning ? . Print ( LogClass . Application , $"Logging directory could not be created '{logDir}'" ) ;
logDir = "" ;
}
if ( string . IsNullOrEmpty ( logDir ) )
{
// NOTE: Should evaluate to "C:\Users\user\AppData\Roaming\Ryujinx\Logs".
logDir = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) , DefaultBaseDir , "Logs" ) ;
try
{
Directory . CreateDirectory ( logDir ) ;
}
catch
{
Logger . Warning ? . Print ( LogClass . Application , $"Logging directory could not be created '{logDir}'" ) ;
return null ;
}
}
}
else if ( OperatingSystem . IsLinux ( ) )
{
// NOTE: Should evaluate to "~/.config/Ryujinx/Logs".
logDir = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) , DefaultBaseDir , "Logs" ) ;
try
{
Directory . CreateDirectory ( logDir ) ;
}
catch
{
Logger . Warning ? . Print ( LogClass . Application , $"Logging directory could not be created '{logDir}'" ) ;
return null ;
}
}
}
return logDir ;
}
2020-08-30 22:21:53 +05:30
private static void SetupBasePaths ( )
{
Directory . CreateDirectory ( BaseDirPath ) ;
2024-02-10 19:17:19 -06:00
LogsDirPath = SetUpLogsDir ( ) ;
2020-08-30 22:21:53 +05:30
Directory . CreateDirectory ( GamesDirPath = Path . Combine ( BaseDirPath , GamesDir ) ) ;
Directory . CreateDirectory ( ProfilesDirPath = Path . Combine ( BaseDirPath , ProfilesDir ) ) ;
Directory . CreateDirectory ( KeysDirPath = Path . Combine ( BaseDirPath , KeysDir ) ) ;
}
2023-06-09 15:31:19 +02:00
// Check if existing old baseDirPath is a symlink, to prevent possible errors.
2024-01-29 19:58:18 +01:00
// Should be removed, when the existence of the old directory isn't checked anymore.
2023-06-09 15:31:19 +02:00
private static bool IsPathSymlink ( string path )
{
2024-02-11 17:10:21 -06:00
try
{
FileAttributes attributes = File . GetAttributes ( path ) ;
return ( attributes & FileAttributes . ReparsePoint ) = = FileAttributes . ReparsePoint ;
}
catch
{
return false ;
}
2023-06-09 15:31:19 +02:00
}
2024-02-11 12:04:39 -06:00
[SupportedOSPlatform("macos")]
public static void FixMacOSConfigurationFolders ( )
{
string oldConfigPath = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ,
".config" , DefaultBaseDir ) ;
if ( Path . Exists ( oldConfigPath ) & & ! IsPathSymlink ( oldConfigPath ) & & ! Path . Exists ( BaseDirPath ) )
{
FileSystemUtils . MoveDirectory ( oldConfigPath , BaseDirPath ) ;
Directory . CreateSymbolicLink ( oldConfigPath , BaseDirPath ) ;
}
string correctApplicationDataDirectoryPath =
Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) , DefaultBaseDir ) ;
if ( IsPathSymlink ( correctApplicationDataDirectoryPath ) )
{
//copy the files somewhere temporarily
string tempPath = Path . Combine ( Path . GetTempPath ( ) , DefaultBaseDir ) ;
try
{
FileSystemUtils . CopyDirectory ( correctApplicationDataDirectoryPath , tempPath , true ) ;
}
catch ( Exception exception )
{
Logger . Error ? . Print ( LogClass . Application ,
$"Critical error copying Ryujinx application data into the temp folder. {exception}" ) ;
try
{
FileSystemInfo resolvedDirectoryInfo =
Directory . ResolveLinkTarget ( correctApplicationDataDirectoryPath , true ) ;
string resolvedPath = resolvedDirectoryInfo . FullName ;
Logger . Error ? . Print ( LogClass . Application , $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink." ) ;
}
catch ( Exception symlinkException )
{
Logger . Error ? . Print ( LogClass . Application , $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder." ) ;
}
return ;
}
//delete the symlink
try
{
//This will fail if this is an actual directory, so there is no way we can actually delete user data here.
File . Delete ( correctApplicationDataDirectoryPath ) ;
}
catch ( Exception exception )
{
Logger . Error ? . Print ( LogClass . Application ,
$"Critical error deleting the Ryujinx application data folder symlink at {correctApplicationDataDirectoryPath}. {exception}" ) ;
try
{
FileSystemInfo resolvedDirectoryInfo =
Directory . ResolveLinkTarget ( correctApplicationDataDirectoryPath , true ) ;
string resolvedPath = resolvedDirectoryInfo . FullName ;
Logger . Error ? . Print ( LogClass . Application , $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink." ) ;
}
catch ( Exception symlinkException )
{
Logger . Error ? . Print ( LogClass . Application , $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder." ) ;
}
return ;
}
//put the files back
try
{
FileSystemUtils . CopyDirectory ( tempPath , correctApplicationDataDirectoryPath , true ) ;
}
catch ( Exception exception )
{
Logger . Error ? . Print ( LogClass . Application ,
$"Critical error copying Ryujinx application data into the correct location. {exception}. Please manually move your application data from {tempPath} to {correctApplicationDataDirectoryPath}." ) ;
}
}
}
2023-06-28 18:41:38 +02:00
public static string GetModsPath ( ) = > CustomModsPath ? ? Directory . CreateDirectory ( Path . Combine ( BaseDirPath , DefaultModsDir ) ) . FullName ;
2022-03-06 21:12:01 +00:00
public static string GetSdModsPath ( ) = > CustomSdModsPath ? ? Directory . CreateDirectory ( Path . Combine ( BaseDirPath , DefaultSdcardDir , "atmosphere" ) ) . FullName ;
2020-08-30 22:21:53 +05:30
}
2023-06-28 18:41:38 +02:00
}