From 70fcba39de34fc211d09da12783898161724b0dc Mon Sep 17 00:00:00 2001
From: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
Date: Mon, 29 Jan 2024 19:58:18 +0100
Subject: Make config filename changable for releases & Log to Ryujinx
 directory if application directory is not writable (#4707)

* Remove GetBaseApplicationDirectory() & Move logs directory to user base path

We should assume the application directory might be write-protected.

* Use Ryujinx.sh in Ryujinx.desktop

This desktop file isn't really used right now,
so this changes effectively nothing.

* Use properties in ReleaseInformation.cs and add ConfigName property

* Configure config filename in Github workflows

* Add a separate config step for macOS

Because they use BSD sed instead of GNU sed

* Keep log directory at the old location for dev environments

* Add FileSystemUtils since Directory.Move() doesn't work across filesystems

Steal CopyDirectory code from https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories

* Fix "Open Logs folder" button pointing to the wrong directory

* Add execute permissions to Ryujinx.sh

* Fix missing newlines

* AppDataManager: Use FileSystemUtils.MoveDirectory()

* Make dotnet format happy

* Add a fallback for the logging directory
---
 .github/workflows/build.yml                        | 17 +++++-
 .github/workflows/release.yml                      |  2 +
 distribution/linux/Ryujinx.desktop                 |  2 +-
 distribution/linux/Ryujinx.sh                      |  2 +-
 src/Ryujinx.Ava/Modules/Updater/Updater.cs         |  4 +-
 src/Ryujinx.Ava/Program.cs                         |  6 +--
 .../UI/ViewModels/MainWindowViewModel.cs           |  9 +++-
 src/Ryujinx.Common/Configuration/AppDataManager.cs | 38 ++------------
 .../Logging/Targets/FileLogTarget.cs               | 60 ++++++++++++++++++----
 src/Ryujinx.Common/ReleaseInformation.cs           | 55 +++++---------------
 src/Ryujinx.Common/Utilities/FileSystemUtils.cs    | 48 +++++++++++++++++
 src/Ryujinx.Headless.SDL2/Program.cs               | 27 +++++++---
 src/Ryujinx.SDL2.Common/SDL2Driver.cs              |  5 +-
 src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs    |  3 +-
 .../Configuration/LoggerModule.cs                  | 19 ++++++-
 .../Helper/FileAssociationHelper.cs                |  4 +-
 src/Ryujinx/Modules/Updater/UpdateDialog.cs        |  3 +-
 src/Ryujinx/Modules/Updater/Updater.cs             |  4 +-
 src/Ryujinx/Program.cs                             |  6 +--
 src/Ryujinx/Ui/Helper/ThemeHelper.cs               |  3 +-
 src/Ryujinx/Ui/MainWindow.cs                       |  7 ++-
 src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs     |  2 +-
 22 files changed, 209 insertions(+), 117 deletions(-)
 mode change 100644 => 100755 distribution/linux/Ryujinx.sh
 create mode 100644 src/Ryujinx.Common/Utilities/FileSystemUtils.cs

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index cf4fdf05..6124ae51 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -40,7 +40,7 @@ jobs:
       - uses: actions/setup-dotnet@v4
         with:
           global-json-file: global.json
-          
+
       - name: Overwrite csc problem matcher
         run: echo "::add-matcher::.github/csc.json"
 
@@ -49,6 +49,16 @@ jobs:
         run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
         shell: bash
 
+      - name: Change config filename
+        run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
+        shell: bash
+        if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
+
+      - name: Change config filename for macOS
+        run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
+        shell: bash
+        if: github.event_name == 'pull_request' && matrix.os == 'macOS-latest'
+
       - name: Build
         run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
 
@@ -135,6 +145,11 @@ jobs:
         id: git_short_hash
         run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
 
+      - name: Change config filename
+        run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
+        shell: bash
+        if: github.event_name == 'pull_request'
+
       - name: Publish macOS Ryujinx.Ava
         run: |
           ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1fb0acdc..d6bcd3fa 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -85,6 +85,7 @@ jobs:
           sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
           sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
           sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
+          sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
         shell: bash
 
       - name: Create output dir
@@ -186,6 +187,7 @@ jobs:
           sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
           sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
           sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
+          sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
         shell: bash
 
       - name: Publish macOS Ryujinx.Ava
diff --git a/distribution/linux/Ryujinx.desktop b/distribution/linux/Ryujinx.desktop
index a4550d10..44f05bf3 100644
--- a/distribution/linux/Ryujinx.desktop
+++ b/distribution/linux/Ryujinx.desktop
@@ -4,7 +4,7 @@ Name=Ryujinx
 Type=Application
 Icon=Ryujinx
 Exec=Ryujinx.sh %f
-Comment=Plays Nintendo Switch applications
+Comment=A Nintendo Switch Emulator
 GenericName=Nintendo Switch Emulator
 Terminal=false
 Categories=Game;Emulator;
diff --git a/distribution/linux/Ryujinx.sh b/distribution/linux/Ryujinx.sh
old mode 100644
new mode 100755
index f356cad0..a80cdcae
--- a/distribution/linux/Ryujinx.sh
+++ b/distribution/linux/Ryujinx.sh
@@ -17,4 +17,4 @@ if command -v gamemoderun > /dev/null 2>&1; then
     COMMAND="$COMMAND gamemoderun"
 fi
 
-$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
\ No newline at end of file
+$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
diff --git a/src/Ryujinx.Ava/Modules/Updater/Updater.cs b/src/Ryujinx.Ava/Modules/Updater/Updater.cs
index af7608d3..ad33b101 100644
--- a/src/Ryujinx.Ava/Modules/Updater/Updater.cs
+++ b/src/Ryujinx.Ava/Modules/Updater/Updater.cs
@@ -665,7 +665,7 @@ namespace Ryujinx.Modules
                 return false;
             }
 
-            if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid())
+            if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid)
             {
                 if (showWarnings)
                 {
@@ -683,7 +683,7 @@ namespace Ryujinx.Modules
 #else
             if (showWarnings)
             {
-                if (ReleaseInformation.IsFlatHubBuild())
+                if (ReleaseInformation.IsFlatHubBuild)
                 {
                     Dispatcher.UIThread.InvokeAsync(() =>
                         ContentDialogHelper.CreateWarningDialog(
diff --git a/src/Ryujinx.Ava/Program.cs b/src/Ryujinx.Ava/Program.cs
index cc062a25..d85749ef 100644
--- a/src/Ryujinx.Ava/Program.cs
+++ b/src/Ryujinx.Ava/Program.cs
@@ -35,7 +35,7 @@ namespace Ryujinx.Ava
 
         public static void Main(string[] args)
         {
-            Version = ReleaseInformation.GetVersion();
+            Version = ReleaseInformation.Version;
 
             if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
             {
@@ -125,8 +125,8 @@ namespace Ryujinx.Ava
 
         public static void ReloadConfig()
         {
-            string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
-            string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
+            string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
+            string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
 
             // Now load the configuration as the other subsystems are now registered
             if (File.Exists(localConfigurationPath))
diff --git a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
index 7146dfd7..dff5b59b 100644
--- a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
@@ -357,7 +357,7 @@ namespace Ryujinx.Ava.UI.ViewModels
 
         public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
 
-        public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild();
+        public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild;
 
         public string LoadHeading
         {
@@ -1350,7 +1350,12 @@ namespace Ryujinx.Ava.UI.ViewModels
 
         public void OpenLogsFolder()
         {
-            string logPath = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "Logs");
+            string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
+
+            if (ReleaseInformation.IsValid)
+            {
+                logPath = Path.Combine(AppDataManager.BaseDirPath, "Logs");
+            }
 
             new DirectoryInfo(logPath).Create();
 
diff --git a/src/Ryujinx.Common/Configuration/AppDataManager.cs b/src/Ryujinx.Common/Configuration/AppDataManager.cs
index 8a226d9a..35aea3c2 100644
--- a/src/Ryujinx.Common/Configuration/AppDataManager.cs
+++ b/src/Ryujinx.Common/Configuration/AppDataManager.cs
@@ -1,4 +1,5 @@
 using Ryujinx.Common.Logging;
+using Ryujinx.Common.Utilities;
 using System;
 using System.IO;
 
@@ -6,8 +7,8 @@ namespace Ryujinx.Common.Configuration
 {
     public static class AppDataManager
     {
-        public const string DefaultBaseDir = "Ryujinx";
-        public const string DefaultPortableDir = "portable";
+        private const string DefaultBaseDir = "Ryujinx";
+        private const string DefaultPortableDir = "portable";
 
         // The following 3 are always part of Base Directory
         private const string GamesDir = "games";
@@ -109,8 +110,7 @@ namespace Ryujinx.Common.Configuration
                 string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
                 if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath))
                 {
-                    CopyDirectory(oldConfigPath, BaseDirPath);
-                    Directory.Delete(oldConfigPath, true);
+                    FileSystemUtils.MoveDirectory(oldConfigPath, BaseDirPath);
                     Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
                 }
             }
@@ -127,41 +127,13 @@ namespace Ryujinx.Common.Configuration
         }
 
         // Check if existing old baseDirPath is a symlink, to prevent possible errors.
-        // Should be removed, when the existance of the old directory isn't checked anymore.
+        // Should be removed, when the existence of the old directory isn't checked anymore.
         private static bool IsPathSymlink(string path)
         {
             FileAttributes attributes = File.GetAttributes(path);
             return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
         }
 
-        private static void CopyDirectory(string sourceDir, string destinationDir)
-        {
-            var dir = new DirectoryInfo(sourceDir);
-
-            if (!dir.Exists)
-            {
-                throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
-            }
-
-            DirectoryInfo[] subDirs = dir.GetDirectories();
-            Directory.CreateDirectory(destinationDir);
-
-            foreach (FileInfo file in dir.GetFiles())
-            {
-                if (file.Name == ".DS_Store")
-                {
-                    continue;
-                }
-
-                file.CopyTo(Path.Combine(destinationDir, file.Name));
-            }
-
-            foreach (DirectoryInfo subDir in subDirs)
-            {
-                CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name));
-            }
-        }
-
         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.Common/Logging/Targets/FileLogTarget.cs b/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs
index 8aa2a26b..c40c3abe 100644
--- a/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs
+++ b/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs
@@ -13,31 +13,71 @@ namespace Ryujinx.Common.Logging.Targets
 
         string ILogTarget.Name { get => _name; }
 
-        public FileLogTarget(string path, string name)
-            : this(path, name, FileShare.Read, FileMode.Append)
-        { }
+        public FileLogTarget(string name, FileStream fileStream)
+        {
+            _name = name;
+            _logWriter = new StreamWriter(fileStream);
+            _formatter = new DefaultLogFormatter();
+        }
 
-        public FileLogTarget(string path, string name, FileShare fileShare, FileMode fileMode)
+        public static FileStream PrepareLogFile(string path)
         {
             // Ensure directory is present
             DirectoryInfo logDir = new(Path.Combine(path, "Logs"));
-            logDir.Create();
+            try
+            {
+                logDir.Create();
+            }
+            catch (IOException exception)
+            {
+                Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}': {exception}");
+
+                return null;
+            }
 
             // Clean up old logs, should only keep 3
             FileInfo[] files = logDir.GetFiles("*.log").OrderBy((info => info.CreationTime)).ToArray();
             for (int i = 0; i < files.Length - 2; i++)
             {
-                files[i].Delete();
+                try
+                {
+                    files[i].Delete();
+                }
+                catch (UnauthorizedAccessException exception)
+                {
+                    Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}");
+
+                    return null;
+                }
+                catch (IOException exception)
+                {
+                    Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}");
+
+                    return null;
+                }
             }
 
-            string version = ReleaseInformation.GetVersion();
+            string version = ReleaseInformation.Version;
 
             // Get path for the current time
             path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
 
-            _name = name;
-            _logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare));
-            _formatter = new DefaultLogFormatter();
+            try
+            {
+                return File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
+            }
+            catch (UnauthorizedAccessException exception)
+            {
+                Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}");
+
+                return null;
+            }
+            catch (IOException exception)
+            {
+                Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}");
+
+                return null;
+            }
         }
 
         public void Log(object sender, LogEventArgs args)
diff --git a/src/Ryujinx.Common/ReleaseInformation.cs b/src/Ryujinx.Common/ReleaseInformation.cs
index ab65a98f..774ae012 100644
--- a/src/Ryujinx.Common/ReleaseInformation.cs
+++ b/src/Ryujinx.Common/ReleaseInformation.cs
@@ -1,5 +1,3 @@
-using Ryujinx.Common.Configuration;
-using System;
 using System.Reflection;
 
 namespace Ryujinx.Common
@@ -9,50 +7,25 @@ namespace Ryujinx.Common
     {
         private const string FlatHubChannelOwner = "flathub";
 
-        public const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
-        public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
-        public const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
+        private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
+        private const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
+        private const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
+        private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";
+
         public const string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%";
         public const string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%";
 
-        public static bool IsValid()
-        {
-            return !BuildGitHash.StartsWith("%%") &&
-                   !ReleaseChannelName.StartsWith("%%") &&
-                   !ReleaseChannelOwner.StartsWith("%%") &&
-                   !ReleaseChannelRepo.StartsWith("%%");
-        }
-
-        public static bool IsFlatHubBuild()
-        {
-            return IsValid() && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
-        }
-
-        public static string GetVersion()
-        {
-            if (IsValid())
-            {
-                return BuildVersion;
-            }
+        public static string ConfigName => !ConfigFileName.StartsWith("%%") ? ConfigFileName : "Config.json";
 
-            return Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
-        }
+        public static bool IsValid =>
+            !BuildGitHash.StartsWith("%%") &&
+            !ReleaseChannelName.StartsWith("%%") &&
+            !ReleaseChannelOwner.StartsWith("%%") &&
+            !ReleaseChannelRepo.StartsWith("%%") &&
+            !ConfigFileName.StartsWith("%%");
 
-#if FORCE_EXTERNAL_BASE_DIR
-        public static string GetBaseApplicationDirectory()
-        {
-            return AppDataManager.BaseDirPath;
-        }
-#else
-        public static string GetBaseApplicationDirectory()
-        {
-            if (IsFlatHubBuild() || OperatingSystem.IsMacOS())
-            {
-                return AppDataManager.BaseDirPath;
-            }
+        public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
 
-            return AppDomain.CurrentDomain.BaseDirectory;
-        }
-#endif
+        public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
     }
 }
diff --git a/src/Ryujinx.Common/Utilities/FileSystemUtils.cs b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs
new file mode 100644
index 00000000..e76c2b60
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs
@@ -0,0 +1,48 @@
+using System.IO;
+
+namespace Ryujinx.Common.Utilities
+{
+    public static class FileSystemUtils
+    {
+        public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive)
+        {
+            // Get information about the source directory
+            var dir = new DirectoryInfo(sourceDir);
+
+            // Check if the source directory exists
+            if (!dir.Exists)
+            {
+                throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
+            }
+
+            // Cache directories before we start copying
+            DirectoryInfo[] dirs = dir.GetDirectories();
+
+            // Create the destination directory
+            Directory.CreateDirectory(destinationDir);
+
+            // Get the files in the source directory and copy to the destination directory
+            foreach (FileInfo file in dir.GetFiles())
+            {
+                string targetFilePath = Path.Combine(destinationDir, file.Name);
+                file.CopyTo(targetFilePath);
+            }
+
+            // If recursive and copying subdirectories, recursively call this method
+            if (recursive)
+            {
+                foreach (DirectoryInfo subDir in dirs)
+                {
+                    string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
+                    CopyDirectory(subDir.FullName, newDestinationDir, true);
+                }
+            }
+        }
+
+        public static void MoveDirectory(string sourceDir, string destinationDir)
+        {
+            CopyDirectory(sourceDir, destinationDir, true);
+            Directory.Delete(sourceDir, true);
+        }
+    }
+}
diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs
index e545079b..6eaa1b86 100644
--- a/src/Ryujinx.Headless.SDL2/Program.cs
+++ b/src/Ryujinx.Headless.SDL2/Program.cs
@@ -61,7 +61,7 @@ namespace Ryujinx.Headless.SDL2
 
         static void Main(string[] args)
         {
-            Version = ReleaseInformation.GetVersion();
+            Version = ReleaseInformation.Version;
 
             // Make process DPI aware for proper window sizing on high-res screens.
             ForceDpiAware.Windows();
@@ -427,11 +427,26 @@ namespace Ryujinx.Headless.SDL2
 
             if (!option.DisableFileLog)
             {
-                Logger.AddTarget(new AsyncLogTargetWrapper(
-                    new FileLogTarget(ReleaseInformation.GetBaseApplicationDirectory(), "file"),
-                    1000,
-                    AsyncLogTargetOverflowAction.Block
-                ));
+                FileStream logFile = FileLogTarget.PrepareLogFile(AppDomain.CurrentDomain.BaseDirectory);
+
+                if (logFile == null)
+                {
+                    logFile = FileLogTarget.PrepareLogFile(AppDataManager.BaseDirPath);
+
+                    if (logFile == null)
+                    {
+                        Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the application directory or the Ryujinx directory is writable.");
+                    }
+                }
+
+                if (logFile != null)
+                {
+                    Logger.AddTarget(new AsyncLogTargetWrapper(
+                        new FileLogTarget("file", logFile),
+                        1000,
+                        AsyncLogTargetOverflowAction.Block
+                    ));
+                }
             }
 
             // Setup graphics configuration
diff --git a/src/Ryujinx.SDL2.Common/SDL2Driver.cs b/src/Ryujinx.SDL2.Common/SDL2Driver.cs
index db1a85e3..552deafd 100644
--- a/src/Ryujinx.SDL2.Common/SDL2Driver.cs
+++ b/src/Ryujinx.SDL2.Common/SDL2Driver.cs
@@ -1,4 +1,3 @@
-using Ryujinx.Common;
 using Ryujinx.Common.Logging;
 using System;
 using System.Collections.Concurrent;
@@ -13,8 +12,6 @@ namespace Ryujinx.SDL2.Common
     {
         private static SDL2Driver _instance;
 
-        public static bool IsInitialized => _instance != null;
-
         public static SDL2Driver Instance
         {
             get
@@ -96,7 +93,7 @@ namespace Ryujinx.SDL2.Common
 
                 SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE);
 
-                string gamepadDbPath = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "SDL_GameControllerDB.txt");
+                string gamepadDbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SDL_GameControllerDB.txt");
 
                 if (File.Exists(gamepadDbPath))
                 {
diff --git a/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs b/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
index 950eb55b..3b35ff27 100644
--- a/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
+++ b/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
@@ -8,6 +8,7 @@ using LibHac.Ns;
 using LibHac.Tools.Fs;
 using LibHac.Tools.FsSystem;
 using LibHac.Tools.FsSystem.NcaUtils;
+using Ryujinx.Common;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
 using Ryujinx.Common.Utilities;
@@ -105,7 +106,7 @@ namespace Ryujinx.Ui.App.Common
 
                     if (!Directory.Exists(appDir))
                     {
-                        Logger.Warning?.Print(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{appDir}\"");
+                        Logger.Warning?.Print(LogClass.Application, $"The \"game_dirs\" section in \"{ReleaseInformation.ConfigName}\" contains an invalid directory: \"{appDir}\"");
 
                         continue;
                     }
diff --git a/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs b/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs
index 54ad20dd..6cd63272 100644
--- a/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs
+++ b/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs
@@ -1,7 +1,9 @@
 using Ryujinx.Common;
+using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
 using Ryujinx.Common.Logging.Targets;
 using System;
+using System.IO;
 
 namespace Ryujinx.Ui.Common.Configuration
 {
@@ -80,8 +82,23 @@ namespace Ryujinx.Ui.Common.Configuration
         {
             if (e.NewValue)
             {
+                FileStream logFile = FileLogTarget.PrepareLogFile(AppDomain.CurrentDomain.BaseDirectory);
+
+                if (logFile == null)
+                {
+                    logFile = FileLogTarget.PrepareLogFile(AppDataManager.BaseDirPath);
+
+                    if (logFile == null)
+                    {
+                        Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the application directory or the Ryujinx directory is writable.");
+                        Logger.RemoveTarget("file");
+
+                        return;
+                    }
+                }
+
                 Logger.AddTarget(new AsyncLogTargetWrapper(
-                    new FileLogTarget(ReleaseInformation.GetBaseApplicationDirectory(), "file"),
+                    new FileLogTarget("file", logFile),
                     1000,
                     AsyncLogTargetOverflowAction.Block
                 ));
diff --git a/src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs b/src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs
index 570fb91e..daa59d25 100644
--- a/src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs
+++ b/src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs
@@ -22,7 +22,7 @@ namespace Ryujinx.Ui.Common.Helper
         [LibraryImport("shell32.dll", SetLastError = true)]
         public static partial void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
 
-        public static bool IsTypeAssociationSupported => (OperatingSystem.IsLinux() || OperatingSystem.IsWindows()) && !ReleaseInformation.IsFlatHubBuild();
+        public static bool IsTypeAssociationSupported => (OperatingSystem.IsLinux() || OperatingSystem.IsWindows()) && !ReleaseInformation.IsFlatHubBuild;
 
         [SupportedOSPlatform("linux")]
         private static bool AreMimeTypesRegisteredLinux() => File.Exists(Path.Combine(_mimeDbPath, "packages", "Ryujinx.xml"));
@@ -34,7 +34,7 @@ namespace Ryujinx.Ui.Common.Helper
 
             if ((uninstall && AreMimeTypesRegisteredLinux()) || (!uninstall && !AreMimeTypesRegisteredLinux()))
             {
-                string mimeTypesFile = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "mime", "Ryujinx.xml");
+                string mimeTypesFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "mime", "Ryujinx.xml");
                 string additionalArgs = !uninstall ? "--novendor" : "";
 
                 using Process mimeProcess = new();
diff --git a/src/Ryujinx/Modules/Updater/UpdateDialog.cs b/src/Ryujinx/Modules/Updater/UpdateDialog.cs
index 69563437..0057761b 100644
--- a/src/Ryujinx/Modules/Updater/UpdateDialog.cs
+++ b/src/Ryujinx/Modules/Updater/UpdateDialog.cs
@@ -1,6 +1,7 @@
 using Gdk;
 using Gtk;
 using Ryujinx.Common;
+using Ryujinx.Common.Configuration;
 using Ryujinx.Ui;
 using Ryujinx.Ui.Common.Configuration;
 using Ryujinx.Ui.Common.Helper;
@@ -52,7 +53,7 @@ namespace Ryujinx.Modules
                 ProcessStartInfo processStart = new(ryuName)
                 {
                     UseShellExecute = true,
-                    WorkingDirectory = ReleaseInformation.GetBaseApplicationDirectory(),
+                    WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory
                 };
 
                 foreach (string argument in CommandLineState.Arguments)
diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs
index f8ce4c0b..2fed4362 100644
--- a/src/Ryujinx/Modules/Updater/Updater.cs
+++ b/src/Ryujinx/Modules/Updater/Updater.cs
@@ -532,7 +532,7 @@ namespace Ryujinx.Modules
                 return false;
             }
 
-            if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid())
+            if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid)
             {
                 if (showWarnings)
                 {
@@ -546,7 +546,7 @@ namespace Ryujinx.Modules
 #else
             if (showWarnings)
             {
-                if (ReleaseInformation.IsFlatHubBuild())
+                if (ReleaseInformation.IsFlatHubBuild)
                 {
                     GtkDialog.CreateWarningDialog("Updater Disabled!", "Please update Ryujinx via FlatHub.");
                 }
diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs
index 597d00f3..3b5cd770 100644
--- a/src/Ryujinx/Program.cs
+++ b/src/Ryujinx/Program.cs
@@ -71,7 +71,7 @@ namespace Ryujinx
 
         static void Main(string[] args)
         {
-            Version = ReleaseInformation.GetVersion();
+            Version = ReleaseInformation.Version;
 
             if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
             {
@@ -167,8 +167,8 @@ namespace Ryujinx
                 Quality = 100,
             });
 
-            string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
-            string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
+            string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
+            string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
 
             // Now load the configuration as the other subsystems are now registered
             ConfigurationPath = File.Exists(localConfigurationPath)
diff --git a/src/Ryujinx/Ui/Helper/ThemeHelper.cs b/src/Ryujinx/Ui/Helper/ThemeHelper.cs
index 67962cb6..5cd9ad52 100644
--- a/src/Ryujinx/Ui/Helper/ThemeHelper.cs
+++ b/src/Ryujinx/Ui/Helper/ThemeHelper.cs
@@ -1,4 +1,5 @@
 using Gtk;
+using Ryujinx.Common;
 using Ryujinx.Common.Logging;
 using Ryujinx.Ui.Common.Configuration;
 using System.IO;
@@ -24,7 +25,7 @@ namespace Ryujinx.Ui.Helper
             }
             else
             {
-                Logger.Warning?.Print(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{ConfigurationState.Instance.Ui.CustomThemePath}\".");
+                Logger.Warning?.Print(LogClass.Application, $"The \"custom_theme_path\" section in \"{ReleaseInformation.ConfigName}\" contains an invalid path: \"{ConfigurationState.Instance.Ui.CustomThemePath}\".");
 
                 ConfigurationState.Instance.Ui.CustomThemePath.Value = "";
                 ConfigurationState.Instance.Ui.EnableCustomTheme.Value = false;
diff --git a/src/Ryujinx/Ui/MainWindow.cs b/src/Ryujinx/Ui/MainWindow.cs
index 6cce034b..3cd2b0eb 100644
--- a/src/Ryujinx/Ui/MainWindow.cs
+++ b/src/Ryujinx/Ui/MainWindow.cs
@@ -1376,7 +1376,12 @@ namespace Ryujinx.Ui
 
         private void OpenLogsFolder_Pressed(object sender, EventArgs args)
         {
-            string logPath = System.IO.Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "Logs");
+            string logPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
+
+            if (ReleaseInformation.IsValid)
+            {
+                logPath = System.IO.Path.Combine(AppDataManager.BaseDirPath, "Logs");
+            }
 
             new DirectoryInfo(logPath).Create();
 
diff --git a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs b/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
index 6bf43842..eb9f52d7 100644
--- a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
+++ b/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
@@ -78,7 +78,7 @@ namespace Ryujinx.Ui.Widgets
             _extractExeFsMenuItem.Sensitive = hasNca;
             _extractLogoMenuItem.Sensitive = hasNca;
 
-            _createShortcutMenuItem.Sensitive = !ReleaseInformation.IsFlatHubBuild();
+            _createShortcutMenuItem.Sensitive = !ReleaseInformation.IsFlatHubBuild;
 
             PopupAtPointer(null);
         }
-- 
cgit v1.2.3-70-g09d2