From b99e808791f0d1d39946b5fce0026592bfa4646c Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Tue, 6 Feb 2018 20:28:32 -0300
Subject: [PATCH] Support loading NSO/NRO without a MOD0 header, stub some
 functions, support more ids on SvcGetInfo

---
 Ryujinx/Cpu/Translation/AILEmitterCtx.cs      |  2 +-
 Ryujinx/Loaders/Executable.cs                 | 27 ++++++----
 .../Executables/{IElf.cs => IExecutable.cs}   |  2 +-
 Ryujinx/Loaders/Executables/Nro.cs            |  2 +-
 Ryujinx/Loaders/Executables/Nso.cs            |  2 +-
 Ryujinx/OsHle/Ipc/IpcHandler.cs               | 37 +++++++++----
 .../OsHle/Objects/AmIApplicationFunctions.cs  |  7 +++
 Ryujinx/OsHle/Objects/AmISelfController.cs    |  7 +++
 .../Objects/ViIApplicationDisplayService.cs   |  7 +++
 Ryujinx/OsHle/Process.cs                      |  4 +-
 Ryujinx/OsHle/Services/ServiceFspSrv.cs       |  7 +++
 Ryujinx/OsHle/Services/ServiceNvDrv.cs        |  9 ++++
 Ryujinx/OsHle/Svc/SvcHandler.cs               |  3 +-
 Ryujinx/OsHle/Svc/SvcSystem.cs                | 53 +++++++++++++++++--
 Ryujinx/VirtualFs.cs                          | 25 +++++----
 15 files changed, 153 insertions(+), 41 deletions(-)
 rename Ryujinx/Loaders/Executables/{IElf.cs => IExecutable.cs} (93%)

diff --git a/Ryujinx/Cpu/Translation/AILEmitterCtx.cs b/Ryujinx/Cpu/Translation/AILEmitterCtx.cs
index bf56db21..410308ff 100644
--- a/Ryujinx/Cpu/Translation/AILEmitterCtx.cs
+++ b/Ryujinx/Cpu/Translation/AILEmitterCtx.cs
@@ -147,7 +147,7 @@ namespace ChocolArm64.Translation
 
             int IntCond = (int)Cond;            
 
-            if (LastFlagOp == LastCmpOp && BranchOps.ContainsKey(Cond))
+            if (LastCmpOp != null && LastFlagOp == LastCmpOp && BranchOps.ContainsKey(Cond))
             {
                 Ldloc(Tmp3Index, AIoType.Int, GetIntType(LastCmpOp));
                 Ldloc(Tmp4Index, AIoType.Int, GetIntType(LastCmpOp));
diff --git a/Ryujinx/Loaders/Executable.cs b/Ryujinx/Loaders/Executable.cs
index 31caf294..4a3d36f5 100644
--- a/Ryujinx/Loaders/Executable.cs
+++ b/Ryujinx/Loaders/Executable.cs
@@ -7,7 +7,7 @@ namespace Ryujinx.Loaders
 {
     class Executable
     {
-        private IElf    NsoData;
+        private IExecutable    NsoData;
         private AMemory Memory;
 
         private ElfDyn[] Dynamic;
@@ -15,23 +15,25 @@ namespace Ryujinx.Loaders
         public long ImageBase { get; private set; }
         public long ImageEnd  { get; private set; }
 
-        public Executable(IElf NsoData, AMemory Memory, long ImageBase)
+        public Executable(IExecutable Exe, AMemory Memory, long ImageBase)
         {
-            this.NsoData   = NsoData;
+            this.NsoData   = Exe;
             this.Memory    = Memory;
             this.ImageBase = ImageBase;
             this.ImageEnd  = ImageBase;
 
-            WriteData(ImageBase + NsoData.TextOffset, NsoData.Text, MemoryType.CodeStatic, AMemoryPerm.RX);
-            WriteData(ImageBase + NsoData.ROOffset,   NsoData.RO,   MemoryType.Normal,     AMemoryPerm.Read);
-            WriteData(ImageBase + NsoData.DataOffset, NsoData.Data, MemoryType.Normal,     AMemoryPerm.RW);
+            WriteData(ImageBase + Exe.TextOffset, Exe.Text, MemoryType.CodeStatic, AMemoryPerm.RX);
+            WriteData(ImageBase + Exe.ROOffset,   Exe.RO,   MemoryType.Normal,     AMemoryPerm.Read);
+            WriteData(ImageBase + Exe.DataOffset, Exe.Data, MemoryType.Normal,     AMemoryPerm.RW);
 
-            if (NsoData.Text.Count == 0)
+            if (Exe.Mod0Offset == 0)
             {
+                MapBss(ImageBase + Exe.DataOffset + Exe.Data.Count, Exe.BssSize);
+
                 return;
             }
 
-            long Mod0Offset = ImageBase + NsoData.Mod0Offset;
+            long Mod0Offset = ImageBase + Exe.Mod0Offset;
 
             int  Mod0Magic        = Memory.ReadInt32(Mod0Offset + 0x0);
             long DynamicOffset    = Memory.ReadInt32(Mod0Offset + 0x4)  + Mod0Offset;
@@ -41,9 +43,7 @@ namespace Ryujinx.Loaders
             long EhHdrEndOffset   = Memory.ReadInt32(Mod0Offset + 0x14) + Mod0Offset;
             long ModObjOffset     = Memory.ReadInt32(Mod0Offset + 0x18) + Mod0Offset;
 
-             long BssSize = BssEndOffset - BssStartOffset;
-
-            Memory.Manager.MapPhys(BssStartOffset, BssSize, (int)MemoryType.Normal, AMemoryPerm.RW);
+            MapBss(BssStartOffset, BssEndOffset - BssStartOffset);
 
             ImageEnd = BssEndOffset;
 
@@ -83,6 +83,11 @@ namespace Ryujinx.Loaders
             }
         }
 
+        private void MapBss(long Position, long Size)
+        {
+            Memory.Manager.MapPhys(Position, Size, (int)MemoryType.Normal, AMemoryPerm.RW);
+        }
+
         private ElfRel GetRelocation(long Position)
         {
             long Offset = Memory.ReadInt64(Position + 0);
diff --git a/Ryujinx/Loaders/Executables/IElf.cs b/Ryujinx/Loaders/Executables/IExecutable.cs
similarity index 93%
rename from Ryujinx/Loaders/Executables/IElf.cs
rename to Ryujinx/Loaders/Executables/IExecutable.cs
index bc8eb1bc..3e42a834 100644
--- a/Ryujinx/Loaders/Executables/IElf.cs
+++ b/Ryujinx/Loaders/Executables/IExecutable.cs
@@ -2,7 +2,7 @@ using System.Collections.ObjectModel;
 
 namespace Ryujinx.Loaders.Executables
 {
-    interface IElf
+    interface IExecutable
     {
         ReadOnlyCollection<byte> Text { get; }
         ReadOnlyCollection<byte> RO   { get; }
diff --git a/Ryujinx/Loaders/Executables/Nro.cs b/Ryujinx/Loaders/Executables/Nro.cs
index 5067ba12..2217f331 100644
--- a/Ryujinx/Loaders/Executables/Nro.cs
+++ b/Ryujinx/Loaders/Executables/Nro.cs
@@ -4,7 +4,7 @@ using System.IO;
 
 namespace Ryujinx.Loaders.Executables
 {
-    class Nro : IElf
+    class Nro : IExecutable
     {
         private byte[] m_Text;
         private byte[] m_RO;
diff --git a/Ryujinx/Loaders/Executables/Nso.cs b/Ryujinx/Loaders/Executables/Nso.cs
index ae9a9af6..ead1baf1 100644
--- a/Ryujinx/Loaders/Executables/Nso.cs
+++ b/Ryujinx/Loaders/Executables/Nso.cs
@@ -5,7 +5,7 @@ using System.IO;
 
 namespace Ryujinx.Loaders.Executables
 {
-    class Nso : IElf
+    class Nso : IExecutable
     {
         private byte[] m_Text;
         private byte[] m_RO;
diff --git a/Ryujinx/OsHle/Ipc/IpcHandler.cs b/Ryujinx/OsHle/Ipc/IpcHandler.cs
index 444b1022..50968014 100644
--- a/Ryujinx/OsHle/Ipc/IpcHandler.cs
+++ b/Ryujinx/OsHle/Ipc/IpcHandler.cs
@@ -20,6 +20,7 @@ namespace Ryujinx.OsHle.Ipc
             { ( "acc:u0",    100), Service.AccU0InitializeApplicationInfo           },
             { ( "acc:u0",    101), Service.AccU0GetBaasAccountManagerForApplication },
             { ( "apm",         0), Service.ApmOpenSession                           },
+            { ( "apm:p",       0), Service.ApmOpenSession                           },
             { ( "appletOE",    0), Service.AppletOpenApplicationProxy               },
             { ( "audout:u",    0), Service.AudOutListAudioOuts                      },
             { ( "audout:u",    1), Service.AudOutOpenAudioOut                       },
@@ -27,6 +28,7 @@ namespace Ryujinx.OsHle.Ipc
             { ( "audren:u",    1), Service.AudRenGetAudioRendererWorkBufferSize     },
             { ( "friend:a",    0), Service.FriendCreateFriendService                },
             { ( "fsp-srv",     1), Service.FspSrvInitialize                         },
+            { ( "fsp-srv",    18), Service.FspSrvMountSdCard                        },
             { ( "fsp-srv",    51), Service.FspSrvMountSaveData                      },
             { ( "fsp-srv",   200), Service.FspSrvOpenDataStorageByCurrentProcess    },
             { ( "fsp-srv",   203), Service.FspSrvOpenRomStorage                     },
@@ -43,11 +45,13 @@ namespace Ryujinx.OsHle.Ipc
             { ( "nvdrv",       2), Service.NvDrvClose                               },
             { ( "nvdrv",       3), Service.NvDrvInitialize                          },
             { ( "nvdrv",       4), Service.NvDrvQueryEvent                          },
+            { ( "nvdrv",       8), Service.NvDrvSetClientPid                        },
             { ( "nvdrv:a",     0), Service.NvDrvOpen                                },
             { ( "nvdrv:a",     1), Service.NvDrvIoctl                               },
             { ( "nvdrv:a",     2), Service.NvDrvClose                               },
             { ( "nvdrv:a",     3), Service.NvDrvInitialize                          },
             { ( "nvdrv:a",     4), Service.NvDrvQueryEvent                          },
+            { ( "nvdrv:a",     8), Service.NvDrvSetClientPid                        },
             { ( "pctl:a",      0), Service.PctlCreateService                        },
             { ( "pl:u",        1), Service.PlGetLoadState                           },
             { ( "pl:u",        2), Service.PlGetFontSize                            },
@@ -81,6 +85,7 @@ namespace Ryujinx.OsHle.Ipc
             { (typeof(AmIApplicationFunctions),  1), AmIApplicationFunctions.PopLaunchParameter },
             { (typeof(AmIApplicationFunctions), 20), AmIApplicationFunctions.EnsureSaveData     },
             { (typeof(AmIApplicationFunctions), 21), AmIApplicationFunctions.GetDesiredLanguage },
+            { (typeof(AmIApplicationFunctions), 40), AmIApplicationFunctions.NotifyRunning      },
 
             //IApplicationProxy
             { (typeof(AmIApplicationProxy),    0), AmIApplicationProxy.GetCommonStateGetter    },
@@ -103,6 +108,7 @@ namespace Ryujinx.OsHle.Ipc
             { (typeof(AmISelfController), 11), AmISelfController.SetOperationModeChangedNotification   },
             { (typeof(AmISelfController), 12), AmISelfController.SetPerformanceModeChangedNotification },
             { (typeof(AmISelfController), 13), AmISelfController.SetFocusHandlingMode                  },
+            { (typeof(AmISelfController), 16), AmISelfController.SetOutOfFocusSuspendingEnabled        },
 
             //IStorage
             { (typeof(AmIStorage), 0), AmIStorage.Open },
@@ -142,14 +148,15 @@ namespace Ryujinx.OsHle.Ipc
             { (typeof(TimeISystemClock), 0), TimeISystemClock.GetCurrentTime },
 
             //IApplicationDisplayService
-            { (typeof(ViIApplicationDisplayService),  100), ViIApplicationDisplayService.GetRelayService          },
-            { (typeof(ViIApplicationDisplayService),  101), ViIApplicationDisplayService.GetSystemDisplayService  },
-            { (typeof(ViIApplicationDisplayService),  102), ViIApplicationDisplayService.GetManagerDisplayService },
-            { (typeof(ViIApplicationDisplayService), 1010), ViIApplicationDisplayService.OpenDisplay              },
-            { (typeof(ViIApplicationDisplayService), 2020), ViIApplicationDisplayService.OpenLayer                },
-            { (typeof(ViIApplicationDisplayService), 2030), ViIApplicationDisplayService.CreateStrayLayer         },
-            { (typeof(ViIApplicationDisplayService), 2101), ViIApplicationDisplayService.SetLayerScalingMode      },
-            { (typeof(ViIApplicationDisplayService), 5202), ViIApplicationDisplayService.GetDisplayVSyncEvent     },
+            { (typeof(ViIApplicationDisplayService),  100), ViIApplicationDisplayService.GetRelayService                      },
+            { (typeof(ViIApplicationDisplayService),  101), ViIApplicationDisplayService.GetSystemDisplayService              },
+            { (typeof(ViIApplicationDisplayService),  102), ViIApplicationDisplayService.GetManagerDisplayService             },
+            { (typeof(ViIApplicationDisplayService),  103), ViIApplicationDisplayService.GetIndirectDisplayTransactionService },
+            { (typeof(ViIApplicationDisplayService), 1010), ViIApplicationDisplayService.OpenDisplay                          },
+            { (typeof(ViIApplicationDisplayService), 2020), ViIApplicationDisplayService.OpenLayer                            },
+            { (typeof(ViIApplicationDisplayService), 2030), ViIApplicationDisplayService.CreateStrayLayer                     },
+            { (typeof(ViIApplicationDisplayService), 2101), ViIApplicationDisplayService.SetLayerScalingMode                  },
+            { (typeof(ViIApplicationDisplayService), 5202), ViIApplicationDisplayService.GetDisplayVSyncEvent                 },
 
             //IHOSBinderDriver
             { (typeof(ViIHOSBinderDriver), 0), ViIHOSBinderDriver.TransactParcel  },
@@ -189,6 +196,8 @@ namespace Ryujinx.OsHle.Ipc
 
                     bool IgnoreNullPR = false;
 
+                    string DbgServiceName = string.Empty;
+
                     if (Session is HDomain Dom)
                     {
                         if (Request.DomCmd == IpcDomCmd.SendMsg)
@@ -200,10 +209,14 @@ namespace Ryujinx.OsHle.Ipc
 
                             if (Obj is HDomain)
                             {
+                                DbgServiceName = $"{ServiceName} {CmdId}";
+
                                 ServiceCmds.TryGetValue((ServiceName, CmdId), out ProcReq);
                             }
                             else if (Obj != null)
                             {
+                                DbgServiceName = $"{ServiceName} {Obj.GetType().Name} {CmdId}";
+
                                 ObjectCmds.TryGetValue((Obj.GetType(), CmdId), out ProcReq);
                             }
                         }
@@ -225,12 +238,16 @@ namespace Ryujinx.OsHle.Ipc
                         {
                             object Obj = ((HSessionObj)Session).Obj;
 
+                            DbgServiceName = $"{ServiceName} {Obj.GetType().Name} {CmdId}";
+
                             ObjectCmds.TryGetValue((Obj.GetType(), CmdId), out ProcReq);
                         }
                         else
                         {
+                            DbgServiceName = $"{ServiceName} {CmdId}";
+
                             ServiceCmds.TryGetValue((ServiceName, CmdId), out ProcReq);
-                        }                        
+                        }
                     }
 
                     if (ProcReq != null)
@@ -255,7 +272,7 @@ namespace Ryujinx.OsHle.Ipc
                     }
                     else if (!IgnoreNullPR)
                     {   
-                        throw new NotImplementedException(ServiceName);
+                        throw new NotImplementedException(DbgServiceName);
                     }
                 }
                 else if (Request.Type == IpcMessageType.Control)
diff --git a/Ryujinx/OsHle/Objects/AmIApplicationFunctions.cs b/Ryujinx/OsHle/Objects/AmIApplicationFunctions.cs
index e174f943..b5712484 100644
--- a/Ryujinx/OsHle/Objects/AmIApplicationFunctions.cs
+++ b/Ryujinx/OsHle/Objects/AmIApplicationFunctions.cs
@@ -35,6 +35,13 @@ namespace Ryujinx.OsHle.Objects
             return 0;
         }
 
+        public static long NotifyRunning(ServiceCtx Context)
+        {
+            Context.ResponseData.Write(1);
+
+            return 0;
+        }
+
         private static byte[] MakeLaunchParams()
         {
             //Size needs to be at least 0x88 bytes otherwise application errors.
diff --git a/Ryujinx/OsHle/Objects/AmISelfController.cs b/Ryujinx/OsHle/Objects/AmISelfController.cs
index 33cde095..8affb92b 100644
--- a/Ryujinx/OsHle/Objects/AmISelfController.cs
+++ b/Ryujinx/OsHle/Objects/AmISelfController.cs
@@ -24,5 +24,12 @@ namespace Ryujinx.OsHle.Objects
 
             return 0;
         }
+
+        public static long SetOutOfFocusSuspendingEnabled(ServiceCtx Context)
+        {
+            bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
+
+            return 0;
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx/OsHle/Objects/ViIApplicationDisplayService.cs b/Ryujinx/OsHle/Objects/ViIApplicationDisplayService.cs
index 81616ae1..174c97b4 100644
--- a/Ryujinx/OsHle/Objects/ViIApplicationDisplayService.cs
+++ b/Ryujinx/OsHle/Objects/ViIApplicationDisplayService.cs
@@ -31,6 +31,13 @@ namespace Ryujinx.OsHle.Objects
             return 0;
         }
 
+        public static long GetIndirectDisplayTransactionService(ServiceCtx Context)
+        {
+            MakeObject(Context, new ViIHOSBinderDriver());
+
+            return 0;
+        }
+
         public static long OpenDisplay(ServiceCtx Context)
         {
             string Name = GetDisplayName(Context);
diff --git a/Ryujinx/OsHle/Process.cs b/Ryujinx/OsHle/Process.cs
index 3f72527e..b723b165 100644
--- a/Ryujinx/OsHle/Process.cs
+++ b/Ryujinx/OsHle/Process.cs
@@ -54,7 +54,7 @@ namespace Ryujinx.OsHle
                 AMemoryPerm.RW);
         }
 
-        public void LoadProgram(IElf Program)
+        public void LoadProgram(IExecutable Program)
         {
             Executable Executable = new Executable(Program, Memory, ImageBase);
 
@@ -138,7 +138,7 @@ namespace Ryujinx.OsHle
             Thread.Registers.SvcCall  += SvcHandler.SvcCall;
             Thread.Registers.ProcessId = ProcessId;
             Thread.Registers.ThreadId  = Ns.Os.IdGen.GenerateId();
-            Thread.Registers.Tpidr   = TlsPageAddr + TlsSlot * TlsSize;
+            Thread.Registers.Tpidr     = TlsPageAddr + TlsSlot * TlsSize;
             Thread.Registers.X0        = (ulong)ArgsPtr;
             Thread.Registers.X1        = (ulong)Handle;
             Thread.Registers.X31       = (ulong)StackTop;
diff --git a/Ryujinx/OsHle/Services/ServiceFspSrv.cs b/Ryujinx/OsHle/Services/ServiceFspSrv.cs
index e2ceb0ae..7f431f26 100644
--- a/Ryujinx/OsHle/Services/ServiceFspSrv.cs
+++ b/Ryujinx/OsHle/Services/ServiceFspSrv.cs
@@ -11,6 +11,13 @@ namespace Ryujinx.OsHle.Services
             return 0;
         }
 
+        public static long FspSrvMountSdCard(ServiceCtx Context)
+        {
+            MakeObject(Context, new FspSrvIFileSystem(Context.Ns.VFs.GetSdCardPath()));
+
+            return 0;
+        }
+
         public static long FspSrvMountSaveData(ServiceCtx Context)
         {
             MakeObject(Context, new FspSrvIFileSystem(Context.Ns.VFs.GetGameSavesPath()));
diff --git a/Ryujinx/OsHle/Services/ServiceNvDrv.cs b/Ryujinx/OsHle/Services/ServiceNvDrv.cs
index f7c0d302..f410e1ab 100644
--- a/Ryujinx/OsHle/Services/ServiceNvDrv.cs
+++ b/Ryujinx/OsHle/Services/ServiceNvDrv.cs
@@ -110,6 +110,15 @@ namespace Ryujinx.OsHle.Services
             return 0;
         }
 
+        public static long NvDrvSetClientPid(ServiceCtx Context)
+        {
+            long Pid = Context.RequestData.ReadInt64();
+
+            Context.ResponseData.Write(0);
+
+            return 0;
+        }
+
         private static long NvGpuAsIoctlBindChannel(ServiceCtx Context)
         {
             long Position = Context.Request.PtrBuff[0].Position;
diff --git a/Ryujinx/OsHle/Svc/SvcHandler.cs b/Ryujinx/OsHle/Svc/SvcHandler.cs
index 245d2438..937c341e 100644
--- a/Ryujinx/OsHle/Svc/SvcHandler.cs
+++ b/Ryujinx/OsHle/Svc/SvcHandler.cs
@@ -43,7 +43,8 @@ namespace Ryujinx.OsHle.Svc
             Success      = 0,
             ErrBadHandle = 0xe401,
             ErrTimeout   = 0xea01,
-            ErrBadIpcReq = 0xf601,
+            ErrBadInfo   = 0xf001,
+            ErrBadIpcReq = 0xf601
         }
 
         private Switch  Ns;
diff --git a/Ryujinx/OsHle/Svc/SvcSystem.cs b/Ryujinx/OsHle/Svc/SvcSystem.cs
index 75d04830..fa39f518 100644
--- a/Ryujinx/OsHle/Svc/SvcSystem.cs
+++ b/Ryujinx/OsHle/Svc/SvcSystem.cs
@@ -133,11 +133,28 @@ namespace Ryujinx.OsHle.Svc
             long Handle   = (long)Registers.X2;
             int  InfoId   =  (int)Registers.X3;
 
+            //Fail for info not available on older Kernel versions.
+            if (InfoType == 18 ||
+                InfoType == 19)
+            {
+                Registers.X0 = (int)SvcResult.ErrBadInfo;
+
+                return;
+            }
+
             switch (InfoType)
             {
-                case 6:  Registers.X1 = GetTotalMem(Memory); break;
-                case 7:  Registers.X1 = GetUsedMem(Memory);  break;
-                case 11: Registers.X1 = GetRnd64();          break;
+                case 2:  Registers.X1 = GetMapRegionBaseAddr();  break;
+                case 3:  Registers.X1 = GetMapRegionSize();      break;
+                case 4:  Registers.X1 = GetHeapRegionBaseAddr(); break;
+                case 5:  Registers.X1 = GetHeapRegionSize();     break;
+                case 6:  Registers.X1 = GetTotalMem(Memory);     break;
+                case 7:  Registers.X1 = GetUsedMem(Memory);      break;
+                case 11: Registers.X1 = GetRnd64();              break;
+                case 12: Registers.X1 = GetAddrSpaceBaseAddr();  break;
+                case 13: Registers.X1 = GetAddrSpaceSize();      break;
+                case 14: Registers.X1 = GetMapRegionBaseAddr();  break;
+                case 15: Registers.X1 = GetMapRegionSize();      break;
 
                 default: throw new NotImplementedException($"SvcGetInfo: {InfoType} {Handle} {InfoId}");
             }
@@ -159,5 +176,35 @@ namespace Ryujinx.OsHle.Svc
         {
             return (ulong)Rng.Next() + ((ulong)Rng.Next() << 32);
         }
+
+        private static ulong GetAddrSpaceBaseAddr()
+        {
+            return 0x08000000;
+        }
+
+        private static ulong GetAddrSpaceSize()
+        {
+            return AMemoryMgr.AddrSize - GetAddrSpaceBaseAddr();
+        }
+
+        private static ulong GetMapRegionBaseAddr()
+        {
+            return 0x80000000;
+        }
+
+        private static ulong GetMapRegionSize()
+        {
+            return 0x40000000;
+        }
+
+        private static ulong GetHeapRegionBaseAddr()
+        {
+            return GetMapRegionBaseAddr() + GetMapRegionSize();
+        }
+
+        private static ulong GetHeapRegionSize()
+        {
+            return 0x40000000;
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx/VirtualFs.cs b/Ryujinx/VirtualFs.cs
index 03317f4a..e5579bcf 100644
--- a/Ryujinx/VirtualFs.cs
+++ b/Ryujinx/VirtualFs.cs
@@ -5,8 +5,9 @@ namespace Ryujinx
 {
     class VirtualFs : IDisposable
     {
-        private const string BasePath  = "Fs";
-        private const string SavesPath = "Saves";
+        private const string BasePath   = "Fs";
+        private const string SavesPath  = "Saves";
+        private const string SdCardPath = "SdCard";
 
         public Stream RomFs { get; private set; }
 
@@ -15,7 +16,7 @@ namespace Ryujinx
             RomFs = new FileStream(FileName, FileMode.Open, FileAccess.Read);
         }
 
-        internal string GetFullPath(string BasePath, string FileName)
+        public string GetFullPath(string BasePath, string FileName)
         {
             if (FileName.StartsWith('/'))
             {
@@ -32,19 +33,23 @@ namespace Ryujinx
             return FullPath;
         }
 
-        internal string GetGameSavesPath()
-        {
-            string SavesDir = Path.Combine(GetBasePath(), SavesPath);
+        public string GetSdCardPath() => MakeDirAndGetFullPath(SdCardPath);
 
-            if (!Directory.Exists(SavesDir))
+        public string GetGameSavesPath() => MakeDirAndGetFullPath(SavesPath);
+
+        private string MakeDirAndGetFullPath(string Dir)
+        {
+            string FullPath = Path.Combine(GetBasePath(), Dir);
+
+            if (!Directory.Exists(FullPath))
             {
-                Directory.CreateDirectory(SavesDir);
+                Directory.CreateDirectory(FullPath);
             }
 
-            return SavesDir;
+            return FullPath;
         }
 
-        internal string GetBasePath()
+        public string GetBasePath()
         {
             return Path.Combine(Directory.GetCurrentDirectory(), BasePath);
         }