diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs
index 9bf9a7bf8..2c4708d76 100644
--- a/Ryujinx.HLE/HOS/ApplicationLoader.cs
+++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs
@@ -21,6 +21,7 @@ using System.Linq;
 using System.Reflection;
 
 using static LibHac.Fs.ApplicationSaveDataManagement;
+using static Ryujinx.HLE.HOS.ModLoader;
 using ApplicationId = LibHac.Ncm.ApplicationId;
 
 namespace Ryujinx.HLE.HOS
@@ -30,7 +31,22 @@ namespace Ryujinx.HLE.HOS
     public class ApplicationLoader
     {
         // Binaries from exefs are loaded into mem in this order. Do not change.
-        private static readonly string[] ExeFsPrefixes = { "rtld", "main", "subsdk*", "sdk" };
+        internal static readonly string[] ExeFsPrefixes =
+        {
+            "rtld",
+            "main",
+            "subsdk0",
+            "subsdk1",
+            "subsdk2",
+            "subsdk3",
+            "subsdk4",
+            "subsdk5",
+            "subsdk6",
+            "subsdk7",
+            "subsdk8",
+            "subsdk9",
+            "sdk"
+        };
 
         private readonly Switch            _device;
         private readonly ContentManager    _contentManager;
@@ -463,37 +479,48 @@ namespace Ryujinx.HLE.HOS
 
             metaData ??= ReadNpdm(codeFs);
 
-            List<NsoExecutable> nsos = new List<NsoExecutable>();
+            NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
 
-            foreach (string exePrefix in ExeFsPrefixes) // Load binaries with standard prefixes
+            for(int i = 0; i < nsos.Length; i++)
             {
-                foreach (DirectoryEntryEx file in codeFs.EnumerateEntries("/", exePrefix))
+                string name = ExeFsPrefixes[i];
+
+                if (!codeFs.FileExists(name))
                 {
-                    if (Path.GetExtension(file.Name) != string.Empty)
-                    {
-                        continue;
-                    }
-
-                    Logger.Info?.Print(LogClass.Loader, $"Loading {file.Name}...");
-
-                    codeFs.OpenFile(out IFile nsoFile, file.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
-                    NsoExecutable nso = new NsoExecutable(nsoFile.AsStorage(), file.Name);
-
-                    nsos.Add(nso);
+                    continue; // file doesn't exist, skip
                 }
+
+                Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
+
+                codeFs.OpenFile(out IFile nsoFile, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+                nsos[i] = new NsoExecutable(nsoFile.AsStorage(), name);
             }
 
             // ExeFs file replacements
-            bool modified = _fileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
+            ModLoadResult modLoadResult = _fileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
 
-            NsoExecutable[] programs = nsos.ToArray();
+            // collect the nsos, ignoring ones that aren't used
+            NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
 
-            modified |= _fileSystem.ModLoader.ApplyNsoPatches(TitleId, programs);
+            // take the npdm from mods if present
+            if (modLoadResult.Npdm != null)
+            {
+                metaData = modLoadResult.Npdm;
+            }
+
+            bool hasPatches = _fileSystem.ModLoader.ApplyNsoPatches(TitleId, programs);
 
             _contentManager.LoadEntries(_device);
 
-            if (_device.System.EnablePtc && modified)
+            bool usePtc = _device.System.EnablePtc;
+
+            // don't use PTC if exefs files have been replaced
+            usePtc &= !modLoadResult.Modified;
+            // don't use PTC if exefs files have been patched
+            usePtc &= !hasPatches;
+
+            if (_device.System.EnablePtc && !usePtc)
             {
                 Logger.Warning?.Print(LogClass.Ptc, $"Detected exefs modifications. PPTC disabled.");
             }
@@ -501,7 +528,7 @@ namespace Ryujinx.HLE.HOS
             Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
             _device.Gpu.HostInitalized.Set();
 
-            Ptc.Initialize(TitleIdText, DisplayVersion, _device.System.EnablePtc && !modified);
+            Ptc.Initialize(TitleIdText, DisplayVersion, usePtc);
 
             ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: programs);
         }
@@ -657,4 +684,4 @@ namespace Ryujinx.HLE.HOS
             return resultCode;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/Ryujinx.HLE/HOS/ModLoader.cs b/Ryujinx.HLE/HOS/ModLoader.cs
index 3d701525a..d2f80ebd8 100644
--- a/Ryujinx.HLE/HOS/ModLoader.cs
+++ b/Ryujinx.HLE/HOS/ModLoader.cs
@@ -12,6 +12,7 @@ using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Linq;
 using System.IO;
+using Ryujinx.HLE.Loaders.Npdm;
 
 namespace Ryujinx.HLE.HOS
 {
@@ -381,66 +382,87 @@ namespace Ryujinx.HLE.HOS
             return true;
         }
 
-        internal bool ApplyExefsMods(ulong titleId, List<NsoExecutable> nsos)
+        public struct ModLoadResult
         {
+            public BitVector32 Stubs;
+            public BitVector32 Replaces;
+            public Npdm Npdm;
+
+            public bool Modified => (Stubs.Data | Replaces.Data) != 0;
+        }
+
+        internal ModLoadResult ApplyExefsMods(ulong titleId, NsoExecutable[] nsos)
+        {
+            ModLoadResult modLoadResult = new ModLoadResult
+            {
+                Stubs = new BitVector32(),
+                Replaces = new BitVector32()
+            };
+
             if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsDirs.Count == 0)
             {
-                return false;
+                return modLoadResult;
             }
 
-            bool replaced = false;
 
-            if (nsos.Count > 32)
+            if (nsos.Length != ApplicationLoader.ExeFsPrefixes.Length)
             {
-                throw new ArgumentOutOfRangeException("NSO Count is more than 32");
+                throw new ArgumentOutOfRangeException("NSO Count is incorrect");
             }
 
             var exeMods = mods.ExefsDirs;
 
-            BitVector32 stubs = new BitVector32();
-            BitVector32 repls = new BitVector32();
-
             foreach (var mod in exeMods)
             {
-                for (int i = 0; i < nsos.Count; ++i)
+                for (int i = 0; i < ApplicationLoader.ExeFsPrefixes.Length; ++i)
                 {
-                    var nso = nsos[i];
-                    var nsoName = nso.Name;
+                    var nsoName = ApplicationLoader.ExeFsPrefixes[i];
 
                     FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName));
                     if (nsoFile.Exists)
                     {
-                        if (repls[1 << i])
+                        if (modLoadResult.Replaces[1 << i])
                         {
                             Logger.Warning?.Print(LogClass.ModLoader, $"Multiple replacements to '{nsoName}'");
+
                             continue;
                         }
 
-                        repls[1 << i] = true;
+                        modLoadResult.Replaces[1 << i] = true;
 
                         nsos[i] = new NsoExecutable(nsoFile.OpenRead().AsStorage(), nsoName);
                         Logger.Info?.Print(LogClass.ModLoader, $"NSO '{nsoName}' replaced");
+                    }
 
-                        replaced = true;
+                    modLoadResult.Stubs[1 << i] |= File.Exists(Path.Combine(mod.Path.FullName, nsoName + StubExtension));
+                }
+
+                FileInfo npdmFile = new FileInfo(Path.Combine(mod.Path.FullName, "main.npdm"));
+                if(npdmFile.Exists)
+                {
+                    if(modLoadResult.Npdm != null)
+                    {
+                        Logger.Warning?.Print(LogClass.ModLoader, "Multiple replacements to 'main.npdm'");
 
                         continue;
                     }
 
-                    stubs[1 << i] |= File.Exists(Path.Combine(mod.Path.FullName, nsoName + StubExtension));
+                    modLoadResult.Npdm = new Npdm(npdmFile.OpenRead());
+                    
+                    Logger.Info?.Print(LogClass.ModLoader, $"main.npdm replaced");
                 }
             }
 
-            for (int i = nsos.Count - 1; i >= 0; --i)
+            for (int i = ApplicationLoader.ExeFsPrefixes.Length - 1; i >= 0; --i)
             {
-                if (stubs[1 << i] && !repls[1 << i]) // Prioritizes replacements over stubs
+                if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs
                 {
                     Logger.Info?.Print(LogClass.ModLoader, $"    NSO '{nsos[i].Name}' stubbed");
-                    nsos.RemoveAt(i);
-                    replaced = true;
+                    nsos[i] = null;
                 }
             }
 
-            return replaced;
+            return modLoadResult;
         }
 
         internal void ApplyNroPatches(NroExecutable nro)
@@ -542,4 +564,4 @@ namespace Ryujinx.HLE.HOS
             return count > 0;
         }
     }
-}
\ No newline at end of file
+}