From 946633276b557a8e522b5e09790c9827cf51f8d6 Mon Sep 17 00:00:00 2001
From: jcm <john.moody@coloradocollege.edu>
Date: Sun, 11 Feb 2024 12:04:39 -0600
Subject: [PATCH] macOS: Stop storing user data in Documents for some users;
 fix symlinks (#6241)

* macOS: Stop storing user data in Documents for some users; fix symlinks

* Use SupportedOSPlatform tag, catch exceptions, log warning instead of error

* Provide best path hints to user if symlink fixup fails

---------

Co-authored-by: jcm <butt@butts.com>
---
 .../Configuration/AppDataManager.cs           | 90 ++++++++++++++++---
 .../Configuration/ConfigurationFileFormat.cs  |  2 +-
 .../Configuration/ConfigurationState.cs       | 12 +++
 3 files changed, 92 insertions(+), 12 deletions(-)

diff --git a/src/Ryujinx.Common/Configuration/AppDataManager.cs b/src/Ryujinx.Common/Configuration/AppDataManager.cs
index f3df0fc9..26b587da 100644
--- a/src/Ryujinx.Common/Configuration/AppDataManager.cs
+++ b/src/Ryujinx.Common/Configuration/AppDataManager.cs
@@ -2,6 +2,7 @@ using Ryujinx.Common.Logging;
 using Ryujinx.Common.Utilities;
 using System;
 using System.IO;
+using System.Runtime.Versioning;
 
 namespace Ryujinx.Common.Configuration
 {
@@ -95,18 +96,9 @@ namespace Ryujinx.Common.Configuration
 
             BaseDirPath = Path.GetFullPath(BaseDirPath); // convert relative paths
 
-            // NOTE: Moves the Ryujinx folder in `~/.config` to `~/Library/Application Support` if one is found
-            // and a Ryujinx folder does not already exist in Application Support.
-            // Also creates a symlink from `~/.config/Ryujinx` to `~/Library/Application Support/Ryujinx` to preserve backwards compatibility.
-            // This should be removed in the future.
-            if (OperatingSystem.IsMacOS() && Mode == LaunchMode.UserProfile)
+            if (IsPathSymlink(BaseDirPath))
             {
-                string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
-                if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath))
-                {
-                    FileSystemUtils.MoveDirectory(oldConfigPath, BaseDirPath);
-                    Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
-                }
+                Logger.Warning?.Print(LogClass.Application, $"Application data directory is a symlink. This may be unintended.");
             }
 
             SetupBasePaths();
@@ -245,6 +237,82 @@ namespace Ryujinx.Common.Configuration
             return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
         }
 
+        [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}.");
+                }
+            }
+        }
+
         public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName;
         public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName;
     }
diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs
index 0ee51d83..0f6c21ef 100644
--- a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs
+++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs
@@ -15,7 +15,7 @@ namespace Ryujinx.UI.Common.Configuration
         /// <summary>
         /// The current version of the file format
         /// </summary>
-        public const int CurrentVersion = 48;
+        public const int CurrentVersion = 49;
 
         /// <summary>
         /// Version of the configuration file format
diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs
index 1d6934ce..b7f36087 100644
--- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs
+++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs
@@ -1430,6 +1430,18 @@ namespace Ryujinx.UI.Common.Configuration
                 configurationFileUpdated = true;
             }
 
+            if (configurationFileFormat.Version < 49)
+            {
+                Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 49.");
+
+                if (OperatingSystem.IsMacOS())
+                {
+                    AppDataManager.FixMacOSConfigurationFolders();
+                }
+
+                configurationFileUpdated = true;
+            }
+
             Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
             Graphics.ResScale.Value = configurationFileFormat.ResScale;
             Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;