aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Ryujinx.HLE/FileSystem/Content/ContentManager.cs300
-rw-r--r--Ryujinx.HLE/FileSystem/Content/ContentPath.cs19
-rw-r--r--Ryujinx.HLE/FileSystem/Content/LocationEntry.cs28
-rw-r--r--Ryujinx.HLE/FileSystem/Content/LocationHelper.cs91
-rw-r--r--Ryujinx.HLE/FileSystem/Content/StorageId.cs9
-rw-r--r--Ryujinx.HLE/FileSystem/Content/TitleType.cs15
-rw-r--r--Ryujinx.HLE/FileSystem/FileSystemProvider.cs281
-rw-r--r--Ryujinx.HLE/FileSystem/IFileSystemProvider.cs41
-rw-r--r--Ryujinx.HLE/FileSystem/PFsProvider.cs146
-rw-r--r--Ryujinx.HLE/FileSystem/RomFsProvider.cs163
-rw-r--r--Ryujinx.HLE/FileSystem/SaveSpaceId.cs2
-rw-r--r--Ryujinx.HLE/FileSystem/StorageId.cs12
-rw-r--r--Ryujinx.HLE/FileSystem/VirtualFileSystem.cs39
-rw-r--r--Ryujinx.HLE/HOS/Font/SharedFontManager.cs68
-rw-r--r--Ryujinx.HLE/HOS/Horizon.cs18
-rw-r--r--Ryujinx.HLE/HOS/Services/Es/IETicketService.cs23
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/DirectoryEntry.cs21
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/DirectoryEntryType.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/FileSystemType.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/FsErr.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/IDirectory.cs37
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/IFile.cs5
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs180
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs246
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/IStorage.cs1
-rw-r--r--Ryujinx.HLE/HOS/Services/Lr/ILocationResolver.cs265
-rw-r--r--Ryujinx.HLE/HOS/Services/Lr/ILocationResolverManager.cs32
-rw-r--r--Ryujinx.HLE/HOS/Services/Lr/LrErr.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Ncm/IContentStorage.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs23
-rw-r--r--Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/ServiceFactory.cs11
-rw-r--r--Ryujinx.HLE/HOS/Services/Set/ISystemSettingsServer.cs61
-rw-r--r--Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs2
-rw-r--r--Ryujinx.HLE/Utilities/FontUtils.cs37
-rw-r--r--Ryujinx.HLE/Utilities/StringUtils.cs27
38 files changed, 2142 insertions, 136 deletions
diff --git a/.gitignore b/.gitignore
index 123f4618..c0af2609 100644
--- a/.gitignore
+++ b/.gitignore
@@ -161,3 +161,6 @@ $RECYCLE.BIN/
# VS Launch Settings
launchSettings.json
+
+# NetCore Publishing Profiles
+PublishProfiles/
diff --git a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs
new file mode 100644
index 00000000..025eb261
--- /dev/null
+++ b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs
@@ -0,0 +1,300 @@
+using LibHac;
+using Ryujinx.HLE.Utilities;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Ryujinx.HLE.FileSystem.Content
+{
+ internal class ContentManager
+ {
+ private Dictionary<StorageId, LinkedList<LocationEntry>> LocationEntries;
+
+ private Dictionary<string, long> SharedFontTitleDictionary;
+
+ private SortedDictionary<(ulong, ContentType), string> ContentDictionary;
+
+ private Switch Device;
+
+ public ContentManager(Switch Device)
+ {
+ ContentDictionary = new SortedDictionary<(ulong, ContentType), string>();
+ LocationEntries = new Dictionary<StorageId, LinkedList<LocationEntry>>();
+
+ SharedFontTitleDictionary = new Dictionary<string, long>()
+ {
+ { "FontStandard", 0x0100000000000811 },
+ { "FontChineseSimplified", 0x0100000000000814 },
+ { "FontExtendedChineseSimplified", 0x0100000000000814 },
+ { "FontKorean", 0x0100000000000812 },
+ { "FontChineseTraditional", 0x0100000000000813 },
+ { "FontNintendoExtended" , 0x0100000000000810 },
+ };
+
+ this.Device = Device;
+ }
+
+ public void LoadEntries()
+ {
+ ContentDictionary = new SortedDictionary<(ulong, ContentType), string>();
+
+ foreach (StorageId StorageId in Enum.GetValues(typeof(StorageId)))
+ {
+ string ContentDirectory = null;
+ string ContentPathString = null;
+ string RegisteredDirectory = null;
+
+ try
+ {
+ ContentPathString = LocationHelper.GetContentRoot(StorageId);
+ ContentDirectory = LocationHelper.GetRealPath(Device.FileSystem, ContentPathString);
+ RegisteredDirectory = Path.Combine(ContentDirectory, "registered");
+ }
+ catch (NotSupportedException NEx)
+ {
+ continue;
+ }
+
+ Directory.CreateDirectory(RegisteredDirectory);
+
+ LinkedList<LocationEntry> LocationList = new LinkedList<LocationEntry>();
+
+ void AddEntry(LocationEntry Entry)
+ {
+ LocationList.AddLast(Entry);
+ }
+
+ foreach (string DirectoryPath in Directory.EnumerateDirectories(RegisteredDirectory))
+ {
+ if (Directory.GetFiles(DirectoryPath).Length > 0)
+ {
+ string NcaName = new DirectoryInfo(DirectoryPath).Name.Replace(".nca", string.Empty);
+
+ using (FileStream NcaFile = new FileStream(Directory.GetFiles(DirectoryPath)[0], FileMode.Open, FileAccess.Read))
+ {
+ Nca Nca = new Nca(Device.System.KeySet, NcaFile, false);
+
+ string SwitchPath = Path.Combine(ContentPathString + ":",
+ NcaFile.Name.Replace(ContentDirectory, string.Empty).TrimStart('\\'));
+
+ // Change path format to switch's
+ SwitchPath = SwitchPath.Replace('\\', '/');
+
+ LocationEntry Entry = new LocationEntry(SwitchPath,
+ 0,
+ (long)Nca.Header.TitleId,
+ Nca.Header.ContentType);
+
+ AddEntry(Entry);
+
+ ContentDictionary.Add((Nca.Header.TitleId, Nca.Header.ContentType), NcaName);
+
+ NcaFile.Close();
+ Nca.Dispose();
+ NcaFile.Dispose();
+ }
+ }
+ }
+
+ foreach (string FilePath in Directory.EnumerateFiles(ContentDirectory))
+ {
+ if (Path.GetExtension(FilePath) == ".nca")
+ {
+ string NcaName = Path.GetFileNameWithoutExtension(FilePath);
+
+ using (FileStream NcaFile = new FileStream(FilePath, FileMode.Open, FileAccess.Read))
+ {
+ Nca Nca = new Nca(Device.System.KeySet, NcaFile, false);
+
+ string SwitchPath = Path.Combine(ContentPathString + ":",
+ FilePath.Replace(ContentDirectory, string.Empty).TrimStart('\\'));
+
+ // Change path format to switch's
+ SwitchPath = SwitchPath.Replace('\\', '/');
+
+ LocationEntry Entry = new LocationEntry(SwitchPath,
+ 0,
+ (long)Nca.Header.TitleId,
+ Nca.Header.ContentType);
+
+ AddEntry(Entry);
+
+ ContentDictionary.Add((Nca.Header.TitleId, Nca.Header.ContentType), NcaName);
+
+ NcaFile.Close();
+ Nca.Dispose();
+ NcaFile.Dispose();
+ }
+ }
+ }
+
+ if(LocationEntries.ContainsKey(StorageId) && LocationEntries[StorageId]?.Count == 0)
+ {
+ LocationEntries.Remove(StorageId);
+ }
+
+ if (!LocationEntries.ContainsKey(StorageId))
+ {
+ LocationEntries.Add(StorageId, LocationList);
+ }
+ }
+ }
+
+ public void ClearEntry(long TitleId, ContentType ContentType,StorageId StorageId)
+ {
+ RemoveLocationEntry(TitleId, ContentType, StorageId);
+ }
+
+ public void RefreshEntries(StorageId StorageId, int Flag)
+ {
+ LinkedList<LocationEntry> LocationList = LocationEntries[StorageId];
+ LinkedListNode<LocationEntry> LocationEntry = LocationList.First;
+
+ while (LocationEntry != null)
+ {
+ LinkedListNode<LocationEntry> NextLocationEntry = LocationEntry.Next;
+
+ if (LocationEntry.Value.Flag == Flag)
+ {
+ LocationList.Remove(LocationEntry.Value);
+ }
+
+ LocationEntry = NextLocationEntry;
+ }
+ }
+
+ public bool HasNca(string NcaId, StorageId StorageId)
+ {
+ if (ContentDictionary.ContainsValue(NcaId))
+ {
+ var Content = ContentDictionary.FirstOrDefault(x => x.Value == NcaId);
+ long TitleId = (long)Content.Key.Item1;
+ ContentType ContentType = Content.Key.Item2;
+ StorageId Storage = GetInstalledStorage(TitleId, ContentType, StorageId);
+
+ return Storage == StorageId;
+ }
+
+ return false;
+ }
+
+ public UInt128 GetInstalledNcaId(long TitleId, ContentType ContentType)
+ {
+ if (ContentDictionary.ContainsKey(((ulong)TitleId,ContentType)))
+ {
+ return new UInt128(ContentDictionary[((ulong)TitleId,ContentType)]);
+ }
+
+ return new UInt128();
+ }
+
+ public StorageId GetInstalledStorage(long TitleId, ContentType ContentType, StorageId StorageId)
+ {
+ LocationEntry LocationEntry = GetLocation(TitleId, ContentType, StorageId);
+
+ return LocationEntry.ContentPath != null ?
+ LocationHelper.GetStorageId(LocationEntry.ContentPath) : StorageId.None;
+ }
+
+ public string GetInstalledContentPath(long TitleId, StorageId StorageId, ContentType ContentType)
+ {
+ LocationEntry LocationEntry = GetLocation(TitleId, ContentType, StorageId);
+
+ if (VerifyContentType(LocationEntry, ContentType))
+ {
+ return LocationEntry.ContentPath;
+ }
+
+ return string.Empty;
+ }
+
+ public void RedirectLocation(LocationEntry NewEntry, StorageId StorageId)
+ {
+ LocationEntry LocationEntry = GetLocation(NewEntry.TitleId, NewEntry.ContentType, StorageId);
+
+ if (LocationEntry.ContentPath != null)
+ {
+ RemoveLocationEntry(NewEntry.TitleId, NewEntry.ContentType, StorageId);
+ }
+
+ AddLocationEntry(NewEntry, StorageId);
+ }
+
+ private bool VerifyContentType(LocationEntry LocationEntry, ContentType ContentType)
+ {
+ StorageId StorageId = LocationHelper.GetStorageId(LocationEntry.ContentPath);
+ string InstalledPath = Device.FileSystem.SwitchPathToSystemPath(LocationEntry.ContentPath);
+
+ if (!string.IsNullOrWhiteSpace(InstalledPath))
+ {
+ if (File.Exists(InstalledPath))
+ {
+ FileStream File = new FileStream(InstalledPath, FileMode.Open, FileAccess.Read);
+ Nca Nca = new Nca(Device.System.KeySet, File, false);
+ bool ContentCheck = Nca.Header.ContentType == ContentType;
+
+ Nca.Dispose();
+ File.Dispose();
+
+ return ContentCheck;
+ }
+ }
+
+ return false;
+ }
+
+ private void AddLocationEntry(LocationEntry Entry, StorageId StorageId)
+ {
+ LinkedList<LocationEntry> LocationList = null;
+
+ if (LocationEntries.ContainsKey(StorageId))
+ {
+ LocationList = LocationEntries[StorageId];
+ }
+
+ if (LocationList != null)
+ {
+ if (LocationList.Contains(Entry))
+ {
+ LocationList.Remove(Entry);
+ }
+
+ LocationList.AddLast(Entry);
+ }
+ }
+
+ private void RemoveLocationEntry(long TitleId, ContentType ContentType, StorageId StorageId)
+ {
+ LinkedList<LocationEntry> LocationList = null;
+
+ if (LocationEntries.ContainsKey(StorageId))
+ {
+ LocationList = LocationEntries[StorageId];
+ }
+
+ if (LocationList != null)
+ {
+ LocationEntry Entry =
+ LocationList.ToList().Find(x => x.TitleId == TitleId && x.ContentType == ContentType);
+
+ if (Entry.ContentPath != null)
+ {
+ LocationList.Remove(Entry);
+ }
+ }
+ }
+
+ public bool TryGetFontTitle(string FontName, out long TitleId)
+ {
+ return SharedFontTitleDictionary.TryGetValue(FontName, out TitleId);
+ }
+
+ private LocationEntry GetLocation(long TitleId, ContentType ContentType,StorageId StorageId)
+ {
+ LinkedList<LocationEntry> LocationList = LocationEntries[StorageId];
+
+ return LocationList.ToList().Find(x => x.TitleId == TitleId && x.ContentType == ContentType);
+ }
+ }
+}
diff --git a/Ryujinx.HLE/FileSystem/Content/ContentPath.cs b/Ryujinx.HLE/FileSystem/Content/ContentPath.cs
new file mode 100644
index 00000000..1e2c8ab3
--- /dev/null
+++ b/Ryujinx.HLE/FileSystem/Content/ContentPath.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.FileSystem.Content
+{
+ static class ContentPath
+ {
+ public const string SystemContent = "@SystemContent";
+ public const string UserContent = "@UserContent";
+ public const string SdCardContent = "@SdCardContent";
+ public const string SdCard = "@SdCard";
+ public const string CalibFile = "@CalibFile";
+ public const string Safe = "@Safe";
+ public const string User = "@User";
+ public const string System = "@System";
+ public const string Host = "@Host";
+ public const string GamecardApp = "@GcApp";
+ public const string GamecardContents = "@GcS00000001";
+ public const string GamecardUpdate = "@upp";
+ public const string RegisteredUpdate = "@RegUpdate";
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/FileSystem/Content/LocationEntry.cs b/Ryujinx.HLE/FileSystem/Content/LocationEntry.cs
new file mode 100644
index 00000000..c7c6133b
--- /dev/null
+++ b/Ryujinx.HLE/FileSystem/Content/LocationEntry.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using LibHac;
+
+namespace Ryujinx.HLE.FileSystem.Content
+{
+ public struct LocationEntry
+ {
+ public string ContentPath { get; private set; }
+ public int Flag { get; private set; }
+ public long TitleId { get; private set; }
+ public ContentType ContentType { get; private set; }
+
+ public LocationEntry(string ContentPath, int Flag, long TitleId, ContentType ContentType)
+ {
+ this.ContentPath = ContentPath;
+ this.Flag = Flag;
+ this.TitleId = TitleId;
+ this.ContentType = ContentType;
+ }
+
+ public void SetFlag(int Flag)
+ {
+ this.Flag = Flag;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/FileSystem/Content/LocationHelper.cs b/Ryujinx.HLE/FileSystem/Content/LocationHelper.cs
new file mode 100644
index 00000000..75b59431
--- /dev/null
+++ b/Ryujinx.HLE/FileSystem/Content/LocationHelper.cs
@@ -0,0 +1,91 @@
+using System;
+using System.IO;
+
+using static Ryujinx.HLE.FileSystem.VirtualFileSystem;
+
+namespace Ryujinx.HLE.FileSystem.Content
+{
+ internal static class LocationHelper
+ {
+ public static string GetRealPath(VirtualFileSystem FileSystem, string SwitchContentPath)
+ {
+ string BasePath = FileSystem.GetBasePath();
+
+ switch (SwitchContentPath)
+ {
+ case ContentPath.SystemContent:
+ return Path.Combine(FileSystem.GetBasePath(), SystemNandPath, "Contents");
+ case ContentPath.UserContent:
+ return Path.Combine(FileSystem.GetBasePath(), UserNandPath, "Contents");
+ case ContentPath.SdCardContent:
+ return Path.Combine(FileSystem.GetSdCardPath(), "Nintendo", "Contents");
+ case ContentPath.System:
+ return Path.Combine(BasePath, SystemNandPath);
+ case ContentPath.User:
+ return Path.Combine(BasePath, UserNandPath);
+ default:
+ throw new NotSupportedException($"Content Path `{SwitchContentPath}` is not supported.");
+ }
+ }
+
+ public static string GetContentPath(ContentStorageId ContentStorageId)
+ {
+ switch (ContentStorageId)
+ {
+ case ContentStorageId.NandSystem:
+ return ContentPath.SystemContent;
+ case ContentStorageId.NandUser:
+ return ContentPath.UserContent;
+ case ContentStorageId.SdCard:
+ return ContentPath.SdCardContent;
+ default:
+ throw new NotSupportedException($"Content Storage `{ContentStorageId}` is not supported.");
+ }
+ }
+
+ public static string GetContentRoot(StorageId StorageId)
+ {
+ switch (StorageId)
+ {
+ case StorageId.NandSystem:
+ return ContentPath.SystemContent;
+ case StorageId.NandUser:
+ return ContentPath.UserContent;
+ case StorageId.SdCard:
+ return ContentPath.SdCardContent;
+ default:
+ throw new NotSupportedException($"Storage Id `{StorageId}` is not supported.");
+ }
+ }
+
+ public static StorageId GetStorageId(string ContentPathString)
+ {
+ string CleanedPath = ContentPathString.Split(':')[0];
+
+ switch (CleanedPath)
+ {
+ case ContentPath.SystemContent:
+ case ContentPath.System:
+ return StorageId.NandSystem;
+
+ case ContentPath.UserContent:
+ case ContentPath.User:
+ return StorageId.NandUser;
+
+ case ContentPath.SdCardContent:
+ return StorageId.SdCard;
+
+ case ContentPath.Host:
+ return StorageId.Host;
+
+ case ContentPath.GamecardApp:
+ case ContentPath.GamecardContents:
+ case ContentPath.GamecardUpdate:
+ return StorageId.GameCard;
+
+ default:
+ return StorageId.None;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/FileSystem/Content/StorageId.cs b/Ryujinx.HLE/FileSystem/Content/StorageId.cs
new file mode 100644
index 00000000..4ff3dd65
--- /dev/null
+++ b/Ryujinx.HLE/FileSystem/Content/StorageId.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.FileSystem.Content
+{
+ public enum ContentStorageId
+ {
+ NandSystem,
+ NandUser,
+ SdCard
+ }
+}
diff --git a/Ryujinx.HLE/FileSystem/Content/TitleType.cs b/Ryujinx.HLE/FileSystem/Content/TitleType.cs
new file mode 100644
index 00000000..6ad26c9c
--- /dev/null
+++ b/Ryujinx.HLE/FileSystem/Content/TitleType.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.HLE.FileSystem.Content
+{
+ enum TitleType
+ {
+ SystemPrograms = 0x01,
+ SystemDataArchive = 0x02,
+ SystemUpdate = 0x03,
+ FirmwarePackageA = 0x04,
+ FirmwarePackageB = 0x05,
+ RegularApplication = 0x80,
+ Update = 0x81,
+ AddOnContent = 0x82,
+ DeltaTitle = 0x83
+ }
+}
diff --git a/Ryujinx.HLE/FileSystem/FileSystemProvider.cs b/Ryujinx.HLE/FileSystem/FileSystemProvider.cs
new file mode 100644
index 00000000..37ccfb10
--- /dev/null
+++ b/Ryujinx.HLE/FileSystem/FileSystemProvider.cs
@@ -0,0 +1,281 @@
+using Ryujinx.HLE.HOS;
+using Ryujinx.HLE.HOS.Services.FspSrv;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.FileSystem
+{
+ class FileSystemProvider : IFileSystemProvider
+ {
+ private readonly string BasePath;
+ private readonly string RootPath;
+
+ public FileSystemProvider(string BasePath, string RootPath)
+ {
+ this.BasePath = BasePath;
+ this.RootPath = RootPath;
+
+ CheckIfDecendentOfRootPath(BasePath);
+ }
+
+ public long CreateDirectory(string Name)
+ {
+ CheckIfDecendentOfRootPath(Name);
+
+ if (Directory.Exists(Name))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
+ }
+
+ Directory.CreateDirectory(Name);
+
+ return 0;
+ }
+
+ public long CreateFile(string Name, long Size)
+ {
+ CheckIfDecendentOfRootPath(Name);
+
+ if (File.Exists(Name))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
+ }
+
+ using (FileStream NewFile = File.Create(Name))
+ {
+ NewFile.SetLength(Size);
+ }
+
+ return 0;
+ }
+
+ public long DeleteDirectory(string Name, bool Recursive)
+ {
+ CheckIfDecendentOfRootPath(Name);
+
+ string DirName = Name;
+
+ if (!Directory.Exists(DirName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ Directory.Delete(DirName, Recursive);
+
+ return 0;
+ }
+
+ public long DeleteFile(string Name)
+ {
+ CheckIfDecendentOfRootPath(Name);
+
+ if (!File.Exists(Name))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+ else
+ {
+ File.Delete(Name);
+ }
+
+ return 0;
+ }
+
+ public DirectoryEntry[] GetDirectories(string Path)
+ {
+ CheckIfDecendentOfRootPath(Path);
+
+ List<DirectoryEntry> Entries = new List<DirectoryEntry>();
+
+ foreach(string Directory in Directory.EnumerateDirectories(Path))
+ {
+ DirectoryEntry DirectoryEntry = new DirectoryEntry(Directory, DirectoryEntryType.Directory);
+
+ Entries.Add(DirectoryEntry);
+ }
+
+ return Entries.ToArray();
+ }
+
+ public DirectoryEntry[] GetEntries(string Path)
+ {
+ CheckIfDecendentOfRootPath(Path);
+
+ if (Directory.Exists(Path))
+ {
+ List<DirectoryEntry> Entries = new List<DirectoryEntry>();
+
+ foreach (string Directory in Directory.EnumerateDirectories(Path))
+ {
+ DirectoryEntry DirectoryEntry = new DirectoryEntry(Directory, DirectoryEntryType.Directory);
+
+ Entries.Add(DirectoryEntry);
+ }
+
+ foreach (string File in Directory.EnumerateFiles(Path))
+ {
+ FileInfo FileInfo = new FileInfo(File);
+ DirectoryEntry DirectoryEntry = new DirectoryEntry(File, DirectoryEntryType.File, FileInfo.Length);
+
+ Entries.Add(DirectoryEntry);
+ }
+ }
+
+ return null;
+ }
+
+ public DirectoryEntry[] GetFiles(string Path)
+ {
+ CheckIfDecendentOfRootPath(Path);
+
+ List<DirectoryEntry> Entries = new List<DirectoryEntry>();
+
+ foreach (string File in Directory.EnumerateFiles(Path))
+ {
+ FileInfo FileInfo = new FileInfo(File);
+ DirectoryEntry DirectoryEntry = new DirectoryEntry(File, DirectoryEntryType.File, FileInfo.Length);
+
+ Entries.Add(DirectoryEntry);
+ }
+
+ return Entries.ToArray();
+ }
+
+ public long GetFreeSpace(ServiceCtx Context)
+ {
+ return Context.Device.FileSystem.GetDrive().AvailableFreeSpace;
+ }
+
+ public string GetFullPath(string Name)
+ {
+ if (Name.StartsWith("//"))
+ {
+ Name = Name.Substring(2);
+ }
+ else if (Name.StartsWith('/'))
+ {
+ Name = Name.Substring(1);
+ }
+ else
+ {
+ return null;
+ }
+
+ string FullPath = Path.Combine(BasePath, Name);
+
+ CheckIfDecendentOfRootPath(FullPath);
+
+ return FullPath;
+ }
+
+ public long GetTotalSpace(ServiceCtx Context)
+ {
+ return Context.Device.FileSystem.GetDrive().TotalSize;
+ }
+
+ public bool DirectoryExists(string Name)
+ {
+ CheckIfDecendentOfRootPath(Name);
+
+ return Directory.Exists(Name);
+ }
+
+ public bool FileExists(string Name)
+ {
+ CheckIfDecendentOfRootPath(Name);
+
+ return File.Exists(Name);
+ }
+
+ public long OpenDirectory(string Name, int FilterFlags, out IDirectory DirectoryInterface)
+ {
+ CheckIfDecendentOfRootPath(Name);
+
+ if (Directory.Exists(Name))
+ {
+ DirectoryInterface = new IDirectory(Name, FilterFlags, this);
+
+ return 0;
+ }
+
+ DirectoryInterface = null;
+
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ public long OpenFile(string Name, out IFile FileInterface)
+ {
+ CheckIfDecendentOfRootPath(Name);
+
+ if (File.Exists(Name))
+ {
+ FileStream Stream = new FileStream(Name, FileMode.Open);
+
+ FileInterface = new IFile(Stream, Name);
+
+ return 0;
+ }
+
+ FileInterface = null;
+
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ public long RenameDirectory(string OldName, string NewName)
+ {
+ CheckIfDecendentOfRootPath(OldName);
+ CheckIfDecendentOfRootPath(NewName);
+
+ if (Directory.Exists(OldName))
+ {
+ Directory.Move(OldName, NewName);
+ }
+ else
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ return 0;
+ }
+
+ public long RenameFile(string OldName, string NewName)
+ {
+ CheckIfDecendentOfRootPath(OldName);
+ CheckIfDecendentOfRootPath(NewName);
+
+ if (File.Exists(OldName))
+ {
+ File.Move(OldName, NewName);
+ }
+ else
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ return 0;
+ }
+
+ public void CheckIfDecendentOfRootPath(string Path)
+ {
+ DirectoryInfo PathInfo = new DirectoryInfo(Path);
+ DirectoryInfo RootInfo = new DirectoryInfo(RootPath);
+
+ while (PathInfo.Parent != null)
+ {
+ if (PathInfo.Parent.FullName == RootInfo.FullName)
+ {
+ return;
+ }
+ else
+ {
+ PathInfo = PathInfo.Parent;
+ }
+ }
+
+ throw new InvalidOperationException($"Path {Path} is not a child directory of {RootPath}");
+ }
+ }
+}
diff --git a/Ryujinx.HLE/FileSystem/IFileSystemProvider.cs b/Ryujinx.HLE/FileSystem/IFileSystemProvider.cs
new file mode 100644
index 00000000..88a630a0
--- /dev/null
+++ b/Ryujinx.HLE/FileSystem/IFileSystemProvider.cs
@@ -0,0 +1,41 @@
+using Ryujinx.HLE.HOS;
+using Ryujinx.HLE.HOS.Services.FspSrv;
+using System;
+
+namespace Ryujinx.HLE.FileSystem
+{
+ interface IFileSystemProvider
+ {
+ long CreateFile(string Name, long Size);
+
+ long CreateDirectory(string Name);
+
+ long RenameFile(string OldName, string NewName);
+
+ long RenameDirectory(string OldName, string NewName);
+
+ DirectoryEntry[] GetEntries(string Path);
+
+ DirectoryEntry[] GetDirectories(string Path);
+
+ DirectoryEntry[] GetFiles(string Path);
+
+ long DeleteFile(string Name);
+
+ long DeleteDirectory(string Name, bool Recursive);
+
+ bool FileExists(string Name);
+
+ bool DirectoryExists(string Name);
+
+ long OpenFile(string Name, out IFile FileInterface);
+
+ long OpenDirectory(string Name, int FilterFlags, out IDirectory DirectoryInterface);
+
+ string GetFullPath(string Name);
+
+ long GetFreeSpace(ServiceCtx Context);
+
+ long GetTotalSpace(ServiceCtx Context);
+ }
+}
diff --git a/Ryujinx.HLE/FileSystem/PFsProvider.cs b/Ryujinx.HLE/FileSystem/PFsProvider.cs
new file mode 100644
index 00000000..c901f073
--- /dev/null
+++ b/Ryujinx.HLE/FileSystem/PFsProvider.cs
@@ -0,0 +1,146 @@
+using LibHac;
+using Ryujinx.HLE.HOS;
+using Ryujinx.HLE.HOS.Services.FspSrv;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.FileSystem
+{
+ class PFsProvider : IFileSystemProvider
+ {
+ private Pfs Pfs;
+
+ public PFsProvider(Pfs Pfs)
+ {
+ this.Pfs = Pfs;
+ }
+
+ public long CreateDirectory(string Name)
+ {
+ throw new NotSupportedException();
+ }
+
+ public long CreateFile(string Name, long Size)
+ {
+ throw new NotSupportedException();
+ }
+
+ public long DeleteDirectory(string Name, bool Recursive)
+ {
+ throw new NotSupportedException();
+ }
+
+ public long DeleteFile(string Name)
+ {
+ throw new NotSupportedException();
+ }
+
+ public DirectoryEntry[] GetDirectories(string Path)
+ {
+ return new DirectoryEntry[0];
+ }
+
+ public DirectoryEntry[] GetEntries(string Path)
+ {
+ List<DirectoryEntry> Entries = new List<DirectoryEntry>();
+
+ foreach (PfsFileEntry File in Pfs.Files)
+ {
+ DirectoryEntry DirectoryEntry = new DirectoryEntry(File.Name, DirectoryEntryType.File, File.Size);
+
+ Entries.Add(DirectoryEntry);
+ }
+
+ return Entries.ToArray();
+ }
+
+ public DirectoryEntry[] GetFiles(string Path)
+ {
+ List<DirectoryEntry> Entries = new List<DirectoryEntry>();
+
+ foreach (PfsFileEntry File in Pfs.Files)
+ {
+ DirectoryEntry DirectoryEntry = new DirectoryEntry(File.Name, DirectoryEntryType.File, File.Size);
+
+ Entries.Add(DirectoryEntry);
+ }
+
+ return Entries.ToArray();
+ }
+
+ public long GetFreeSpace(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ public string GetFullPath(string Name)
+ {
+ return Name;
+ }
+
+ public long GetTotalSpace(ServiceCtx Context)
+ {
+ return Pfs.Files.Sum(x => x.Size);
+ }
+
+ public bool DirectoryExists(string Name)
+ {
+ return Name == "/" ? true : false;
+ }
+
+ public bool FileExists(string Name)
+ {
+ Name = Name.TrimStart('/');
+
+ return Pfs.FileExists(Name);
+ }
+
+ public long OpenDirectory(string Name, int FilterFlags, out IDirectory DirectoryInterface)
+ {
+ if (Name == "/")
+ {
+ DirectoryInterface = new IDirectory(Name, FilterFlags, this);
+
+ return 0;
+ }
+
+ throw new NotSupportedException();
+ }
+
+ public long OpenFile(string Name, out IFile FileInterface)
+ {
+ Name = Name.TrimStart('/');
+
+ if (Pfs.FileExists(Name))
+ {
+ Stream Stream = Pfs.OpenFile(Name);
+ FileInterface = new IFile(Stream, Name);
+
+ return 0;
+ }
+
+ FileInterface = null;
+
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ public long RenameDirectory(string OldName, string NewName)
+ {
+ throw new NotSupportedException();
+ }
+
+ public long RenameFile(string OldName, string NewName)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void CheckIfOutsideBasePath(string Path)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}
diff --git a/Ryujinx.HLE/FileSystem/RomFsProvider.cs b/Ryujinx.HLE/FileSystem/RomFsProvider.cs
new file mode 100644
index 00000000..d966d3d7
--- /dev/null
+++ b/Ryujinx.HLE/FileSystem/RomFsProvider.cs
@@ -0,0 +1,163 @@
+using LibHac;
+using Ryujinx.HLE.HOS;
+using Ryujinx.HLE.HOS.Services.FspSrv;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.FileSystem
+{
+ class RomFsProvider : IFileSystemProvider
+ {
+ private Romfs RomFs;
+
+ public RomFsProvider(Stream StorageStream)
+ {
+ RomFs = new Romfs(StorageStream);
+ }
+
+ public long CreateDirectory(string Name)
+ {
+ throw new NotSupportedException();
+ }
+
+ public long CreateFile(string Name, long Size)
+ {
+ throw new NotSupportedException();
+ }
+
+ public long DeleteDirectory(string Name, bool Recursive)
+ {
+ throw new NotSupportedException();
+ }
+
+ public long DeleteFile(string Name)
+ {
+ throw new NotSupportedException();
+ }
+
+ public DirectoryEntry[] GetDirectories(string Path)
+ {
+ List<DirectoryEntry> Directories = new List<DirectoryEntry>();
+
+ foreach(RomfsDir Directory in RomFs.Directories)
+ {
+ DirectoryEntry DirectoryEntry = new DirectoryEntry(Directory.Name, DirectoryEntryType.Directory);
+
+ Directories.Add(DirectoryEntry);
+ }
+
+ return Directories.ToArray();
+ }
+
+ public DirectoryEntry[] GetEntries(string Path)
+ {
+ List<DirectoryEntry> Entries = new List<DirectoryEntry>();
+
+ foreach (RomfsDir Directory in RomFs.Directories)
+ {
+ DirectoryEntry DirectoryEntry = new DirectoryEntry(Directory.Name, DirectoryEntryType.Directory);
+
+ Entries.Add(DirectoryEntry);
+ }
+
+ foreach (RomfsFile File in RomFs.Files)
+ {
+ DirectoryEntry DirectoryEntry = new DirectoryEntry(File.Name, DirectoryEntryType.File, File.DataLength);
+
+ Entries.Add(DirectoryEntry);
+ }
+
+ return Entries.ToArray();
+ }
+
+ public DirectoryEntry[] GetFiles(string Path)
+ {
+ List<DirectoryEntry> Files = new List<DirectoryEntry>();
+
+ foreach (RomfsFile File in RomFs.Files)
+ {
+ DirectoryEntry DirectoryEntry = new DirectoryEntry(File.Name, DirectoryEntryType.File, File.DataLength);
+
+ Files.Add(DirectoryEntry);
+ }
+
+ return Files.ToArray();
+ }
+
+ public long GetFreeSpace(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ public string GetFullPath(string Name)
+ {
+ return Name;
+ }
+
+ public long GetTotalSpace(ServiceCtx Context)
+ {
+ return RomFs.Files.Sum(x => x.DataLength);
+ }
+
+ public bool DirectoryExists(string Name)
+ {
+ return RomFs.Directories.Exists(x=>x.Name == Name);
+ }
+
+ public bool FileExists(string Name)
+ {
+ return RomFs.FileExists(Name);
+ }
+
+ public long OpenDirectory(string Name, int FilterFlags, out IDirectory DirectoryInterface)
+ {
+ RomfsDir Directory = RomFs.Directories.Find(x => x.Name == Name);
+
+ if (Directory != null)
+ {
+ DirectoryInterface = new IDirectory(Name, FilterFlags, this);
+
+ return 0;
+ }
+
+ DirectoryInterface = null;
+
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ public long OpenFile(string Name, out IFile FileInterface)
+ {
+ if (RomFs.FileExists(Name))
+ {
+ Stream Stream = RomFs.OpenFile(Name);
+
+ FileInterface = new IFile(Stream, Name);
+
+ return 0;
+ }
+
+ FileInterface = null;
+
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ public long RenameDirectory(string OldName, string NewName)
+ {
+ throw new NotSupportedException();
+ }
+
+ public long RenameFile(string OldName, string NewName)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void CheckIfOutsideBasePath(string Path)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}
diff --git a/Ryujinx.HLE/FileSystem/SaveSpaceId.cs b/Ryujinx.HLE/FileSystem/SaveSpaceId.cs
index 5a2b32d6..d51922df 100644
--- a/Ryujinx.HLE/FileSystem/SaveSpaceId.cs
+++ b/Ryujinx.HLE/FileSystem/SaveSpaceId.cs
@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.FileSystem
{
- enum SaveSpaceId : byte
+ enum SaveSpaceId
{
NandSystem,
NandUser,
diff --git a/Ryujinx.HLE/FileSystem/StorageId.cs b/Ryujinx.HLE/FileSystem/StorageId.cs
new file mode 100644
index 00000000..1ef38e01
--- /dev/null
+++ b/Ryujinx.HLE/FileSystem/StorageId.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.FileSystem
+{
+ internal enum StorageId
+ {
+ None,
+ Host,
+ GameCard,
+ NandSystem,
+ NandUser,
+ SdCard
+ }
+}
diff --git a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
index e621ec2b..6bb2847f 100644
--- a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
+++ b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
@@ -1,3 +1,4 @@
+using Ryujinx.HLE.FileSystem.Content;
using Ryujinx.HLE.HOS;
using System;
using System.IO;
@@ -11,6 +12,7 @@ namespace Ryujinx.HLE.FileSystem
public const string SdCardPath = "sdmc";
public const string SystemPath = "system";
+ public static string SafeNandPath = Path.Combine(NandPath, "safe");
public static string SystemNandPath = Path.Combine(NandPath, "system");
public static string UserNandPath = Path.Combine(NandPath, "user");
@@ -63,9 +65,15 @@ namespace Ryujinx.HLE.FileSystem
return MakeDirAndGetFullPath(SaveHelper.GetSavePath(Save, Context));
}
+ public string GetFullPartitionPath(string PartitionPath)
+ {
+ return MakeDirAndGetFullPath(PartitionPath);
+ }
+
public string SwitchPathToSystemPath(string SwitchPath)
{
string[] Parts = SwitchPath.Split(":");
+
if (Parts.Length != 2)
{
return null;
@@ -76,10 +84,12 @@ namespace Ryujinx.HLE.FileSystem
public string SystemPathToSwitchPath(string SystemPath)
{
string BaseSystemPath = GetBasePath() + Path.DirectorySeparatorChar;
+
if (SystemPath.StartsWith(BaseSystemPath))
{
- string RawPath = SystemPath.Replace(BaseSystemPath, "");
- int FirstSeparatorOffset = RawPath.IndexOf(Path.DirectorySeparatorChar);
+ string RawPath = SystemPath.Replace(BaseSystemPath, "");
+ int FirstSeparatorOffset = RawPath.IndexOf(Path.DirectorySeparatorChar);
+
if (FirstSeparatorOffset == -1)
{
return $"{RawPath}:/";
@@ -87,6 +97,7 @@ namespace Ryujinx.HLE.FileSystem
string BasePath = RawPath.Substring(0, FirstSeparatorOffset);
string FileName = RawPath.Substring(FirstSeparatorOffset + 1);
+
return $"{BasePath}:/{FileName}";
}
return null;
@@ -94,6 +105,30 @@ namespace Ryujinx.HLE.FileSystem
private string MakeDirAndGetFullPath(string Dir)
{
+ // Handles Common Switch Content Paths
+ switch (Dir)
+ {
+ case ContentPath.SdCard:
+ case "@Sdcard":
+ Dir = SdCardPath;
+ break;
+ case ContentPath.User:
+ Dir = UserNandPath;
+ break;
+ case ContentPath.System:
+ Dir = SystemNandPath;
+ break;
+ case ContentPath.SdCardContent:
+ Dir = Path.Combine(SdCardPath, "Nintendo", "Contents");
+ break;
+ case ContentPath.UserContent:
+ Dir = Path.Combine(UserNandPath, "Contents");
+ break;
+ case ContentPath.SystemContent:
+ Dir = Path.Combine(SystemNandPath, "Contents");
+ break;
+ }
+
string FullPath = Path.Combine(GetBasePath(), Dir);
if (!Directory.Exists(FullPath))
diff --git a/Ryujinx.HLE/HOS/Font/SharedFontManager.cs b/Ryujinx.HLE/HOS/Font/SharedFontManager.cs
index 0be5e896..313db345 100644
--- a/Ryujinx.HLE/HOS/Font/SharedFontManager.cs
+++ b/Ryujinx.HLE/HOS/Font/SharedFontManager.cs
@@ -1,14 +1,19 @@
-using Ryujinx.HLE.Memory;
+using LibHac;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.FileSystem.Content;
using Ryujinx.HLE.Resource;
using Ryujinx.HLE.Utilities;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
+
+using static Ryujinx.HLE.Utilities.FontUtils;
namespace Ryujinx.HLE.HOS.Font
{
class SharedFontManager
{
- private DeviceMemory Memory;
+ private Switch Device;
private long PhysicalAddress;
@@ -32,21 +37,64 @@ namespace Ryujinx.HLE.HOS.Font
{
this.PhysicalAddress = PhysicalAddress;
- Memory = Device.Memory;
+ this.Device = Device;
FontsPath = Path.Combine(Device.FileSystem.GetSystemPath(), "fonts");
}
- public void EnsureInitialized()
+ public void EnsureInitialized(ContentManager ContentManager)
{
if (FontData == null)
{
- Memory.FillWithZeros(PhysicalAddress, Horizon.FontSize);
+ Device.Memory.FillWithZeros(PhysicalAddress, Horizon.FontSize);
uint FontOffset = 0;
FontInfo CreateFont(string Name)
{
+ if (ContentManager.TryGetFontTitle(Name, out long FontTitle))
+ {
+ string ContentPath = ContentManager.GetInstalledContentPath(FontTitle, StorageId.NandSystem, ContentType.Data);
+ string FontPath = Device.FileSystem.SwitchPathToSystemPath(ContentPath);
+
+ if (!string.IsNullOrWhiteSpace(FontPath))
+ {
+ int FileIndex = 0;
+
+ //Use second file in Chinese Font title for standard
+ if(Name == "FontChineseSimplified")
+ {
+ FileIndex = 1;
+ }
+
+ FileStream NcaFileStream = new FileStream(FontPath, FileMode.Open, FileAccess.Read);
+ Nca Nca = new Nca(Device.System.KeySet, NcaFileStream, false);
+ NcaSection RomfsSection = Nca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs);
+ Romfs Romfs = new Romfs(Nca.OpenSection(RomfsSection.SectionNum, false, Device.System.FsIntegrityCheckLevel));
+ Stream FontFile = Romfs.OpenFile(Romfs.Files[FileIndex]);
+
+ byte[] Data = DecryptFont(FontFile);
+
+ FontInfo Info = new FontInfo((int)FontOffset, Data.Length);
+
+ WriteMagicAndSize(PhysicalAddress + FontOffset, Data.Length);
+
+ FontOffset += 8;
+
+ uint Start = FontOffset;
+
+ for (; FontOffset - Start < Data.Length; FontOffset++)
+ {
+ Device.Memory.WriteByte(PhysicalAddress + FontOffset, Data[FontOffset - Start]);
+ }
+
+ NcaFileStream.Dispose();
+ Nca.Dispose();
+
+ return Info;
+ }
+ }
+
string FontFilePath = Path.Combine(FontsPath, Name + ".ttf");
if (File.Exists(FontFilePath))
@@ -63,7 +111,7 @@ namespace Ryujinx.HLE.HOS.Font
for (; FontOffset - Start < Data.Length; FontOffset++)
{
- Memory.WriteByte(PhysicalAddress + FontOffset, Data[FontOffset - Start]);
+ Device.Memory.WriteByte(PhysicalAddress + FontOffset, Data[FontOffset - Start]);
}
return Info;
@@ -101,20 +149,20 @@ namespace Ryujinx.HLE.HOS.Font
int EncryptedSize = EndianSwap.Swap32(Size ^ Key);
- Memory.WriteInt32(Position + 0, DecMagic);
- Memory.WriteInt32(Position + 4, EncryptedSize);
+ Device.Memory.WriteInt32(Position + 0, DecMagic);
+ Device.Memory.WriteInt32(Position + 4, EncryptedSize);
}
public int GetFontSize(SharedFontType FontType)
{
- EnsureInitialized();
+ EnsureInitialized(Device.System.ContentManager);
return FontData[FontType].Size;
}
public int GetSharedMemoryAddressOffset(SharedFontType FontType)
{
- EnsureInitialized();
+ EnsureInitialized(Device.System.ContentManager);
return FontData[FontType].Offset + 8;
}
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
index 1b336647..d967c896 100644
--- a/Ryujinx.HLE/HOS/Horizon.cs
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -1,5 +1,6 @@
using LibHac;
using Ryujinx.Common.Logging;
+using Ryujinx.HLE.FileSystem.Content;
using Ryujinx.HLE.HOS.Font;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.SystemState;
@@ -42,6 +43,8 @@ namespace Ryujinx.HLE.HOS
internal SharedFontManager Font { get; private set; }
+ internal ContentManager ContentManager { get; private set; }
+
internal KEvent VsyncEvent { get; private set; }
internal Keyset KeySet { get; private set; }
@@ -90,6 +93,8 @@ namespace Ryujinx.HLE.HOS
VsyncEvent = new KEvent(this);
LoadKeySet();
+
+ ContentManager = new ContentManager(Device);
}
public void LoadCart(string ExeFsDir, string RomFsFile = null)
@@ -156,6 +161,8 @@ namespace Ryujinx.HLE.HOS
LoadNso("subsdk*");
LoadNso("sdk");
+ ContentManager.LoadEntries();
+
MainProcess.Run();
}
@@ -174,6 +181,8 @@ namespace Ryujinx.HLE.HOS
return;
}
+ ContentManager.LoadEntries();
+
LoadNca(MainNca, ControlNca);
}
@@ -412,6 +421,8 @@ namespace Ryujinx.HLE.HOS
LoadNso("subsdk");
LoadNso("sdk");
+ ContentManager.LoadEntries();
+
MainProcess.Run();
}
@@ -419,13 +430,13 @@ namespace Ryujinx.HLE.HOS
{
bool IsNro = Path.GetExtension(FilePath).ToLower() == ".nro";
- string Name = Path.GetFileNameWithoutExtension(FilePath);
+ string Name = Path.GetFileNameWithoutExtension(FilePath);
string SwitchFilePath = Device.FileSystem.SystemPathToSwitchPath(FilePath);
if (IsNro && (SwitchFilePath == null || !SwitchFilePath.StartsWith("sdmc:/")))
{
string SwitchPath = $"sdmc:/switch/{Name}{Homebrew.TemporaryNroSuffix}";
- string TempPath = Device.FileSystem.SwitchPathToSystemPath(SwitchPath);
+ string TempPath = Device.FileSystem.SwitchPathToSystemPath(SwitchPath);
string SwitchDir = Path.GetDirectoryName(TempPath);
@@ -449,6 +460,9 @@ namespace Ryujinx.HLE.HOS
}
MainProcess.SetEmptyArgs();
+
+ ContentManager.LoadEntries();
+
MainProcess.Run(IsNro);
}
diff --git a/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs b/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs
new file mode 100644
index 00000000..f11c78cc
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs
@@ -0,0 +1,23 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Es
+{
+ class IETicketService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private bool IsInitialized;
+
+ public IETicketService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+
+ };
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/DirectoryEntry.cs b/Ryujinx.HLE/HOS/Services/FspSrv/DirectoryEntry.cs
new file mode 100644
index 00000000..74ebddc2
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/DirectoryEntry.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.FspSrv
+{
+ public struct DirectoryEntry
+ {
+ public string Path { get; private set; }
+ public long Size { get; private set; }
+
+ public DirectoryEntryType EntryType { get; set; }
+
+ public DirectoryEntry(string Path, DirectoryEntryType DirectoryEntryType, long Size = 0)
+ {
+ this.Path = Path;
+ EntryType = DirectoryEntryType;
+ this.Size = Size;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/DirectoryEntryType.cs b/Ryujinx.HLE/HOS/Services/FspSrv/DirectoryEntryType.cs
new file mode 100644
index 00000000..da075a5f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/DirectoryEntryType.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.FspSrv
+{
+ public enum DirectoryEntryType
+ {
+ Directory,
+ File
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/FileSystemType.cs b/Ryujinx.HLE/HOS/Services/FspSrv/FileSystemType.cs
new file mode 100644
index 00000000..2fd59673
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/FileSystemType.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Services.FspSrv
+{
+ enum FileSystemType : int
+ {
+ Logo = 2,
+ ContentControl = 3,
+ ContentManual = 4,
+ ContentMeta = 5,
+ ContentData = 6,
+ ApplicationPackage = 7
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/FsErr.cs b/Ryujinx.HLE/HOS/Services/FspSrv/FsErr.cs
index 39eadcec..16ef03be 100644
--- a/Ryujinx.HLE/HOS/Services/FspSrv/FsErr.cs
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/FsErr.cs
@@ -5,5 +5,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
public const int PathDoesNotExist = 1;
public const int PathAlreadyExists = 2;
public const int PathAlreadyInUse = 7;
+ public const int PartitionNotFound = 1001;
+ public const int InvalidInput = 6001;
}
} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IDirectory.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IDirectory.cs
index d6ae084f..c964eecb 100644
--- a/Ryujinx.HLE/HOS/Services/FspSrv/IDirectory.cs
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/IDirectory.cs
@@ -1,3 +1,4 @@
+using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Ipc;
using System;
using System.Collections.Generic;
@@ -14,15 +15,17 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
- private List<string> DirectoryEntries;
+ private List<DirectoryEntry> DirectoryEntries;
private int CurrentItemIndex;
public event EventHandler<EventArgs> Disposed;
- public string HostPath { get; private set; }
+ public string DirectoryPath { get; private set; }
- public IDirectory(string HostPath, int Flags)
+ private IFileSystemProvider Provider;
+
+ public IDirectory(string DirectoryPath, int Flags, IFileSystemProvider Provider)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
@@ -30,23 +33,25 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
{ 1, GetEntryCount }
};
- this.HostPath = HostPath;
+ this.Provider = Provider;
+ this.DirectoryPath = DirectoryPath;
- DirectoryEntries = new List<string>();
+ DirectoryEntries = new List<DirectoryEntry>();
if ((Flags & 1) != 0)
{
- DirectoryEntries.AddRange(Directory.GetDirectories(HostPath));
+ DirectoryEntries.AddRange(Provider.GetDirectories(DirectoryPath));
}
if ((Flags & 2) != 0)
{
- DirectoryEntries.AddRange(Directory.GetFiles(HostPath));
+ DirectoryEntries.AddRange(Provider.GetFiles(DirectoryPath));
}
CurrentItemIndex = 0;
}
+ // Read() -> (u64 count, buffer<nn::fssrv::sf::IDirectoryEntry, 6, 0> entries)
public long Read(ServiceCtx Context)
{
long BufferPosition = Context.Request.ReceiveBuff[0].Position;
@@ -68,31 +73,23 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return 0;
}
- private void WriteDirectoryEntry(ServiceCtx Context, long Position, string FullPath)
+ private void WriteDirectoryEntry(ServiceCtx Context, long Position, DirectoryEntry Entry)
{
for (int Offset = 0; Offset < 0x300; Offset += 8)
{
Context.Memory.WriteInt64(Position + Offset, 0);
}
- byte[] NameBuffer = Encoding.UTF8.GetBytes(Path.GetFileName(FullPath));
+ byte[] NameBuffer = Encoding.UTF8.GetBytes(Path.GetFileName(Entry.Path));
Context.Memory.WriteBytes(Position, NameBuffer);
- int Type = 0;
- long Size = 0;
-
- if (File.Exists(FullPath))
- {
- Type = 1;
- Size = new FileInfo(FullPath).Length;
- }
-
Context.Memory.WriteInt32(Position + 0x300, 0); //Padding?
- Context.Memory.WriteInt32(Position + 0x304, Type);
- Context.Memory.WriteInt64(Position + 0x308, Size);
+ Context.Memory.WriteInt32(Position + 0x304, (byte)Entry.EntryType);
+ Context.Memory.WriteInt64(Position + 0x308, Entry.Size);
}
+ // GetEntryCount() -> u64
public long GetEntryCount(ServiceCtx Context)
{
Context.ResponseData.Write((long)DirectoryEntries.Count);
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFile.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFile.cs
index 194d9420..9bf152c4 100644
--- a/Ryujinx.HLE/HOS/Services/FspSrv/IFile.cs
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFile.cs
@@ -32,6 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
this.HostPath = HostPath;
}
+ // Read(u32, u64 offset, u64 size) -> (u64 out_size, buffer<u8, 0x46, 0> out_buf)
public long Read(ServiceCtx Context)
{
long Position = Context.Request.ReceiveBuff[0].Position;
@@ -53,6 +54,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return 0;
}
+ // Write(u32, u64 offset, u64 size, buffer<u8, 0x45, 0>)
public long Write(ServiceCtx Context)
{
long Position = Context.Request.SendBuff[0].Position;
@@ -69,6 +71,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return 0;
}
+ // Flush()
public long Flush(ServiceCtx Context)
{
BaseStream.Flush();
@@ -76,6 +79,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return 0;
}
+ // SetSize(u64 size)
public long SetSize(ServiceCtx Context)
{
long Size = Context.RequestData.ReadInt64();
@@ -85,6 +89,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return 0;
}
+ // GetSize() -> u64 fileSize
public long GetSize(ServiceCtx Context)
{
Context.ResponseData.Write(BaseStream.Length);
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs
index bd249e50..edcdfa58 100644
--- a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs
@@ -1,10 +1,11 @@
+using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Ipc;
using System;
using System.Collections.Generic;
using System.IO;
-using System.Text;
using static Ryujinx.HLE.HOS.ErrorCode;
+using static Ryujinx.HLE.Utilities.StringUtils;
namespace Ryujinx.HLE.HOS.Services.FspSrv
{
@@ -18,7 +19,9 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
private string Path;
- public IFileSystem(string Path)
+ private IFileSystemProvider Provider;
+
+ public IFileSystem(string Path, IFileSystemProvider Provider)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
@@ -41,9 +44,11 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
OpenPaths = new HashSet<string>();
- this.Path = Path;
+ this.Path = Path;
+ this.Provider = Provider;
}
+ // CreateFile(u32 mode, u64 size, buffer<bytes<0x301>, 0x19, 0x301> path)
public long CreateFile(ServiceCtx Context)
{
string Name = ReadUtf8String(Context);
@@ -51,14 +56,14 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
long Mode = Context.RequestData.ReadInt64();
int Size = Context.RequestData.ReadInt32();
- string FileName = Context.Device.FileSystem.GetFullPath(Path, Name);
+ string FileName = Provider.GetFullPath(Name);
if (FileName == null)
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
- if (File.Exists(FileName))
+ if (Provider.FileExists(FileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
}
@@ -68,21 +73,17 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
- using (FileStream NewFile = File.Create(FileName))
- {
- NewFile.SetLength(Size);
- }
-
- return 0;
+ return Provider.CreateFile(FileName, Size);
}
+ // DeleteFile(buffer<bytes<0x301>, 0x19, 0x301> path)
public long DeleteFile(ServiceCtx Context)
{
string Name = ReadUtf8String(Context);
- string FileName = Context.Device.FileSystem.GetFullPath(Path, Name);
+ string FileName = Provider.GetFullPath(Name);
- if (!File.Exists(FileName))
+ if (!Provider.FileExists(FileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
@@ -92,23 +93,22 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
- File.Delete(FileName);
-
- return 0;
+ return Provider.DeleteFile(FileName);
}
+ // CreateDirectory(buffer<bytes<0x301>, 0x19, 0x301> path)
public long CreateDirectory(ServiceCtx Context)
{
string Name = ReadUtf8String(Context);
- string DirName = Context.Device.FileSystem.GetFullPath(Path, Name);
+ string DirName = Provider.GetFullPath(Name);
if (DirName == null)
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
- if (Directory.Exists(DirName))
+ if (Provider.DirectoryExists(DirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
}
@@ -118,26 +118,28 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
- Directory.CreateDirectory(DirName);
+ Provider.CreateDirectory(DirName);
return 0;
}
+ // DeleteDirectory(buffer<bytes<0x301>, 0x19, 0x301> path)
public long DeleteDirectory(ServiceCtx Context)
{
return DeleteDirectory(Context, false);
}
+ // DeleteDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path)
public long DeleteDirectoryRecursively(ServiceCtx Context)
{
return DeleteDirectory(Context, true);
}
-
+
private long DeleteDirectory(ServiceCtx Context, bool Recursive)
{
string Name = ReadUtf8String(Context);
- string DirName = Context.Device.FileSystem.GetFullPath(Path, Name);
+ string DirName = Provider.GetFullPath(Name);
if (!Directory.Exists(DirName))
{
@@ -149,25 +151,26 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
- Directory.Delete(DirName, Recursive);
+ Provider.DeleteDirectory(DirName, Recursive);
return 0;
}
+ // RenameFile(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath)
public long RenameFile(ServiceCtx Context)
{
string OldName = ReadUtf8String(Context, 0);
string NewName = ReadUtf8String(Context, 1);
- string OldFileName = Context.Device.FileSystem.GetFullPath(Path, OldName);
- string NewFileName = Context.Device.FileSystem.GetFullPath(Path, NewName);
+ string OldFileName = Provider.GetFullPath(OldName);
+ string NewFileName = Provider.GetFullPath(NewName);
- if (!File.Exists(OldFileName))
+ if (Provider.FileExists(OldFileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
- if (File.Exists(NewFileName))
+ if (Provider.FileExists(NewFileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
}
@@ -177,25 +180,24 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
- File.Move(OldFileName, NewFileName);
-
- return 0;
+ return Provider.RenameFile(OldFileName, NewFileName);
}
+ // RenameDirectory(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath)
public long RenameDirectory(ServiceCtx Context)
{
string OldName = ReadUtf8String(Context, 0);
string NewName = ReadUtf8String(Context, 1);
- string OldDirName = Context.Device.FileSystem.GetFullPath(Path, OldName);
- string NewDirName = Context.Device.FileSystem.GetFullPath(Path, NewName);
+ string OldDirName = Provider.GetFullPath(OldName);
+ string NewDirName = Provider.GetFullPath(NewName);
- if (!Directory.Exists(OldDirName))
+ if (!Provider.DirectoryExists(OldDirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
- if (Directory.Exists(NewDirName))
+ if (!Provider.DirectoryExists(NewDirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
}
@@ -205,22 +207,21 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
- Directory.Move(OldDirName, NewDirName);
-
- return 0;
+ return Provider.RenameDirectory(OldDirName, NewDirName);
}
+ // GetEntryType(buffer<bytes<0x301>, 0x19, 0x301> path) -> nn::fssrv::sf::DirectoryEntryType
public long GetEntryType(ServiceCtx Context)
{
string Name = ReadUtf8String(Context);
- string FileName = Context.Device.FileSystem.GetFullPath(Path, Name);
+ string FileName = Provider.GetFullPath(Name);
- if (File.Exists(FileName))
+ if (Provider.FileExists(FileName))
{
Context.ResponseData.Write(1);
}
- else if (Directory.Exists(FileName))
+ else if (Provider.DirectoryExists(FileName))
{
Context.ResponseData.Write(0);
}
@@ -234,15 +235,16 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return 0;
}
+ // OpenFile(u32 mode, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IFile> file
public long OpenFile(ServiceCtx Context)
{
int FilterFlags = Context.RequestData.ReadInt32();
string Name = ReadUtf8String(Context);
- string FileName = Context.Device.FileSystem.GetFullPath(Path, Name);
+ string FileName = Provider.GetFullPath(Name);
- if (!File.Exists(FileName))
+ if (!Provider.FileExists(FileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
@@ -252,79 +254,96 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
- FileStream Stream = new FileStream(FileName, FileMode.Open);
- IFile FileInterface = new IFile(Stream, FileName);
+ long Error = Provider.OpenFile(FileName, out IFile FileInterface);
- FileInterface.Disposed += RemoveFileInUse;
-
- lock (OpenPaths)
+ if (Error == 0)
{
- OpenPaths.Add(FileName);
- }
+ FileInterface.Disposed += RemoveFileInUse;
- MakeObject(Context, FileInterface);
+ lock (OpenPaths)
+ {
+ OpenPaths.Add(FileName);
+ }
- return 0;
+ MakeObject(Context, FileInterface);
+
+ return 0;
+ }
+
+ return Error;
}
+ // OpenDirectory(u32 filter_flags, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IDirectory> directory
public long OpenDirectory(ServiceCtx Context)
{
int FilterFlags = Context.RequestData.ReadInt32();
string Name = ReadUtf8String(Context);
- string DirName = Context.Device.FileSystem.GetFullPath(Path, Name);
+ string DirName = Provider.GetFullPath(Name);
- if (!Directory.Exists(DirName))
+ if (!Provider.DirectoryExists(DirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
- IDirectory DirInterface = new IDirectory(DirName, FilterFlags);
+ if (IsPathAlreadyInUse(DirName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
+ }
- DirInterface.Disposed += RemoveDirectoryInUse;
+ long Error = Provider.OpenDirectory(DirName, FilterFlags, out IDirectory DirInterface);
- lock (OpenPaths)
+ if (Error == 0)
{
- OpenPaths.Add(DirName);
- }
+ DirInterface.Disposed += RemoveDirectoryInUse;
- MakeObject(Context, DirInterface);
+ lock (OpenPaths)
+ {
+ OpenPaths.Add(DirName);
+ }
- return 0;
+ MakeObject(Context, DirInterface);
+ }
+
+ return Error;
}
+ // Commit()
public long Commit(ServiceCtx Context)
{
return 0;
}
+ // GetFreeSpaceSize(buffer<bytes<0x301>, 0x19, 0x301> path) -> u64 totalFreeSpace
public long GetFreeSpaceSize(ServiceCtx Context)
{
string Name = ReadUtf8String(Context);
- Context.ResponseData.Write(Context.Device.FileSystem.GetDrive().AvailableFreeSpace);
+ Context.ResponseData.Write(Provider.GetFreeSpace(Context));
return 0;
}
+ // GetTotalSpaceSize(buffer<bytes<0x301>, 0x19, 0x301> path) -> u64 totalSize
public long GetTotalSpaceSize(ServiceCtx Context)
{
string Name = ReadUtf8String(Context);
- Context.ResponseData.Write(Context.Device.FileSystem.GetDrive().TotalSize);
+ Context.ResponseData.Write(Provider.GetFreeSpace(Context));
return 0;
}
+ // CleanDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path)
public long CleanDirectoryRecursively(ServiceCtx Context)
{
string Name = ReadUtf8String(Context);
- string DirName = Context.Device.FileSystem.GetFullPath(Path, Name);
+ string DirName = Provider.GetFullPath(Name);
- if (!Directory.Exists(DirName))
+ if (!Provider.DirectoryExists(DirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
@@ -334,15 +353,15 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
- foreach (string Entry in Directory.EnumerateFileSystemEntries(DirName))
+ foreach (DirectoryEntry Entry in Provider.GetEntries(DirName))
{
- if (Directory.Exists(Entry))
+ if (Provider.DirectoryExists(Entry.Path))
{
- Directory.Delete(Entry, true);
+ Provider.DeleteDirectory(Entry.Path, true);
}
- else if (File.Exists(Entry))
+ else if (Provider.FileExists(Entry.Path))
{
- File.Delete(Entry);
+ Provider.DeleteFile(Entry.Path);
}
}
@@ -377,30 +396,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
{
DirInterface.Disposed -= RemoveDirectoryInUse;
- OpenPaths.Remove(DirInterface.HostPath);
- }
- }
-
- private string ReadUtf8String(ServiceCtx Context, int Index = 0)
- {
- long Position = Context.Request.PtrBuff[Index].Position;
- long Size = Context.Request.PtrBuff[Index].Size;
-
- using (MemoryStream MS = new MemoryStream())
- {
- while (Size-- > 0)
- {
- byte Value = Context.Memory.ReadByte(Position++);
-
- if (Value == 0)
- {
- break;
- }
-
- MS.WriteByte(Value);
- }
-
- return Encoding.UTF8.GetString(MS.ToArray());
+ OpenPaths.Remove(DirInterface.DirectoryPath);
}
}
}
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs
index daf5e0b2..0fc1eb80 100644
--- a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs
@@ -1,7 +1,14 @@
+using LibHac;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.Utilities;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+using static Ryujinx.HLE.FileSystem.VirtualFileSystem;
+using static Ryujinx.HLE.HOS.ErrorCode;
+using static Ryujinx.HLE.Utilities.StringUtils;
namespace Ryujinx.HLE.HOS.Services.FspSrv
{
@@ -15,28 +22,104 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
- { 1, SetCurrentProcess },
+ { 1, Initialize },
+ { 8, OpenFileSystemWithId },
+ { 11, OpenBisFileSystem },
{ 18, OpenSdCardFileSystem },
{ 51, OpenSaveDataFileSystem },
{ 52, OpenSaveDataFileSystemBySystemSaveDataId },
{ 200, OpenDataStorageByCurrentProcess },
+ { 202, OpenDataStorageByDataId },
{ 203, OpenPatchDataStorageByCurrentProcess },
{ 1005, GetGlobalAccessLogMode }
};
}
- public long SetCurrentProcess(ServiceCtx Context)
+ // Initialize(u64, pid)
+ public long Initialize(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ // OpenFileSystemWithId(nn::fssrv::sf::FileSystemType filesystem_type, nn::ApplicationId tid, buffer<bytes<0x301>, 0x19, 0x301> path)
+ // -> object<nn::fssrv::sf::IFileSystem> contentFs
+ public long OpenFileSystemWithId(ServiceCtx Context)
+ {
+ FileSystemType FileSystemType = (FileSystemType)Context.RequestData.ReadInt32();
+ long TitleId = Context.RequestData.ReadInt64();
+ string SwitchPath = ReadUtf8String(Context);
+ string FullPath = Context.Device.FileSystem.SwitchPathToSystemPath(SwitchPath);
+
+ if (!File.Exists(FullPath))
+ {
+ if (FullPath.Contains("."))
+ {
+ return OpenFileSystemFromInternalFile(Context, FullPath);
+ }
+
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ FileStream FileStream = new FileStream(FullPath, FileMode.Open, FileAccess.Read);
+ string Extension = Path.GetExtension(FullPath);
+
+ if (Extension == ".nca")
+ {
+ return OpenNcaFs(Context, FullPath, FileStream);
+ }
+ else if (Extension == ".nsp")
+ {
+ return OpenNsp(Context, FullPath);
+ }
+
+ return MakeError(ErrorModule.Fs, FsErr.InvalidInput);
+ }
+
+ // OpenBisFileSystem(nn::fssrv::sf::Partition partitionID, buffer<bytes<0x301>, 0x19, 0x301>) -> object<nn::fssrv::sf::IFileSystem> Bis
+ public long OpenBisFileSystem(ServiceCtx Context)
{
+ int BisPartitionId = Context.RequestData.ReadInt32();
+ string PartitionString = ReadUtf8String(Context);
+ string BisPartitonPath = string.Empty;
+
+ switch (BisPartitionId)
+ {
+ case 29:
+ BisPartitonPath = SafeNandPath;
+ break;
+ case 30:
+ case 31:
+ BisPartitonPath = SystemNandPath;
+ break;
+ case 32:
+ BisPartitonPath = UserNandPath;
+ break;
+ default:
+ return MakeError(ErrorModule.Fs, FsErr.InvalidInput);
+ }
+
+ string FullPath = Context.Device.FileSystem.GetFullPartitionPath(BisPartitonPath);
+
+ FileSystemProvider FileSystemProvider = new FileSystemProvider(FullPath, Context.Device.FileSystem.GetBasePath());
+
+ MakeObject(Context, new IFileSystem(FullPath, FileSystemProvider));
+
return 0;
}
+ // OpenSdCardFileSystem() -> object<nn::fssrv::sf::IFileSystem>
public long OpenSdCardFileSystem(ServiceCtx Context)
{
- MakeObject(Context, new IFileSystem(Context.Device.FileSystem.GetSdCardPath()));
+ string SdCardPath = Context.Device.FileSystem.GetSdCardPath();
+
+ FileSystemProvider FileSystemProvider = new FileSystemProvider(SdCardPath, Context.Device.FileSystem.GetBasePath());
+
+ MakeObject(Context, new IFileSystem(SdCardPath, FileSystemProvider));
return 0;
}
+ // OpenSaveDataFileSystem(u8 save_data_space_id, nn::fssrv::sf::SaveStruct saveStruct) -> object<nn::fssrv::sf::IFileSystem> saveDataFs
public long OpenSaveDataFileSystem(ServiceCtx Context)
{
LoadSaveDataFileSystem(Context);
@@ -44,6 +127,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return 0;
}
+ // OpenSaveDataFileSystemBySystemSaveDataId(u8 save_data_space_id, nn::fssrv::sf::SaveStruct saveStruct) -> object<nn::fssrv::sf::IFileSystem> systemSaveDataFs
public long OpenSaveDataFileSystemBySystemSaveDataId(ServiceCtx Context)
{
LoadSaveDataFileSystem(Context);
@@ -51,6 +135,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return 0;
}
+ // OpenDataStorageByCurrentProcess() -> object<nn::fssrv::sf::IStorage> dataStorage
public long OpenDataStorageByCurrentProcess(ServiceCtx Context)
{
MakeObject(Context, new IStorage(Context.Device.FileSystem.RomFs));
@@ -58,6 +143,63 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return 0;
}
+ // OpenDataStorageByDataId(u8 storageId, nn::ApplicationId tid) -> object<nn::fssrv::sf::IStorage> dataStorage
+ public long OpenDataStorageByDataId(ServiceCtx Context)
+ {
+ StorageId StorageId = (StorageId)Context.RequestData.ReadByte();
+ byte[] Padding = Context.RequestData.ReadBytes(7);
+ long TitleId = Context.RequestData.ReadInt64();
+
+ StorageId InstalledStorage =
+ Context.Device.System.ContentManager.GetInstalledStorage(TitleId, ContentType.Data, StorageId);
+
+ if (InstalledStorage == StorageId.None)
+ {
+ InstalledStorage =
+ Context.Device.System.ContentManager.GetInstalledStorage(TitleId, ContentType.AocData, StorageId);
+ }
+
+ if (InstalledStorage != StorageId.None)
+ {
+ string ContentPath = Context.Device.System.ContentManager.GetInstalledContentPath(TitleId, StorageId, ContentType.AocData);
+
+ if (string.IsNullOrWhiteSpace(ContentPath))
+ {
+ ContentPath = Context.Device.System.ContentManager.GetInstalledContentPath(TitleId, StorageId, ContentType.AocData);
+ }
+
+ string InstallPath = Context.Device.FileSystem.SwitchPathToSystemPath(ContentPath);
+
+ if (!string.IsNullOrWhiteSpace(InstallPath))
+ {
+ string NcaPath = InstallPath;
+
+ if (File.Exists(NcaPath))
+ {
+ FileStream NcaStream = new FileStream(NcaPath, FileMode.Open, FileAccess.Read);
+ Nca Nca = new Nca(Context.Device.System.KeySet, NcaStream, false);
+ NcaSection RomfsSection = Nca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs);
+ Stream RomfsStream = Nca.OpenSection(RomfsSection.SectionNum, false, Context.Device.System.FsIntegrityCheckLevel);
+
+ MakeObject(Context, new IStorage(RomfsStream));
+
+ return 0;
+ }
+ else
+ {
+ throw new FileNotFoundException($"No Nca found in Path `{NcaPath}`.");
+ }
+ }
+ else
+ {
+ throw new DirectoryNotFoundException($"Path for title id {TitleId:x16} on Storage {StorageId} was not found in Path {InstallPath}.");
+ }
+ }
+
+ throw new FileNotFoundException($"System archive with titleid {TitleId:x16} was not found on Storage {StorageId}. Found in {InstalledStorage}.");
+ }
+
+ // OpenPatchDataStorageByCurrentProcess() -> object<nn::fssrv::sf::IStorage>
public long OpenPatchDataStorageByCurrentProcess(ServiceCtx Context)
{
MakeObject(Context, new IStorage(Context.Device.FileSystem.RomFs));
@@ -65,6 +207,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return 0;
}
+ // GetGlobalAccessLogMode() -> u32 logMode
public long GetGlobalAccessLogMode(ServiceCtx Context)
{
Context.ResponseData.Write(0);
@@ -82,13 +225,102 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
Context.RequestData.ReadInt64(),
Context.RequestData.ReadInt64());
- long SaveId = Context.RequestData.ReadInt64();
+ long SaveId = Context.RequestData.ReadInt64();
+ SaveDataType SaveDataType = (SaveDataType)Context.RequestData.ReadByte();
+ SaveInfo SaveInfo = new SaveInfo(TitleId, SaveId, SaveDataType, UserId, SaveSpaceId);
+ string SavePath = Context.Device.FileSystem.GetGameSavePath(SaveInfo, Context);
+ FileSystemProvider FileSystemProvider = new FileSystemProvider(SavePath, Context.Device.FileSystem.GetBasePath());
+
+ MakeObject(Context, new IFileSystem(SavePath, FileSystemProvider));
+ }
+
+ private long OpenNsp(ServiceCtx Context, string PfsPath)
+ {
+ FileStream PfsFile = new FileStream(PfsPath, FileMode.Open, FileAccess.Read);
+ Pfs Nsp = new Pfs(PfsFile);
+ PfsFileEntry TicketFile = Nsp.Files.FirstOrDefault(x => x.Name.EndsWith(".tik"));
+
+ if (TicketFile != null)
+ {
+ Ticket Ticket = new Ticket(Nsp.OpenFile(TicketFile));
+
+ Context.Device.System.KeySet.TitleKeys[Ticket.RightsId] =
+ Ticket.GetTitleKey(Context.Device.System.KeySet);
+ }
+
+ IFileSystem NspFileSystem = new IFileSystem(PfsPath, new PFsProvider(Nsp));
+
+ MakeObject(Context, NspFileSystem);
+
+ return 0;
+ }
+
+ private long OpenNcaFs(ServiceCtx Context,string NcaPath, Stream NcaStream)
+ {
+ Nca Nca = new Nca(Context.Device.System.KeySet, NcaStream, false);
+
+ NcaSection RomfsSection = Nca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs);
+ NcaSection PfsSection = Nca.Sections.FirstOrDefault(x => x?.Type == SectionType.Pfs0);
+
+ if (RomfsSection != null)
+ {
+ Stream RomfsStream = Nca.OpenSection(RomfsSection.SectionNum, false, Context.Device.System.FsIntegrityCheckLevel);
+ IFileSystem NcaFileSystem = new IFileSystem(NcaPath, new RomFsProvider(RomfsStream));
+
+ MakeObject(Context, NcaFileSystem);
+ }
+ else if(PfsSection !=null)
+ {
+ Stream PfsStream = Nca.OpenSection(PfsSection.SectionNum, false, Context.Device.System.FsIntegrityCheckLevel);
+ Pfs Pfs = new Pfs(PfsStream);
+ IFileSystem NcaFileSystem = new IFileSystem(NcaPath, new PFsProvider(Pfs));
+
+ MakeObject(Context, NcaFileSystem);
+ }
+ else
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PartitionNotFound);
+ }
+
+ return 0;
+ }
+
+ private long OpenFileSystemFromInternalFile(ServiceCtx Context, string FullPath)
+ {
+ DirectoryInfo ArchivePath = new DirectoryInfo(FullPath).Parent;
+
+ while (string.IsNullOrWhiteSpace(ArchivePath.Extension))
+ {
+ ArchivePath = ArchivePath.Parent;
+ }
+
+ if (ArchivePath.Extension == ".nsp" && File.Exists(ArchivePath.FullName))
+ {
+ FileStream PfsFile = new FileStream(
+ ArchivePath.FullName.TrimEnd(Path.DirectorySeparatorChar),
+ FileMode.Open,
+ FileAccess.Read);
+
+ Pfs Nsp = new Pfs(PfsFile);
+ PfsFileEntry TicketFile = Nsp.Files.FirstOrDefault(x => x.Name.EndsWith(".tik"));
+
+ if (TicketFile != null)
+ {
+ Ticket Ticket = new Ticket(Nsp.OpenFile(TicketFile));
+
+ Context.Device.System.KeySet.TitleKeys[Ticket.RightsId] =
+ Ticket.GetTitleKey(Context.Device.System.KeySet);
+ }
- SaveDataType SaveDataType = (SaveDataType)Context.RequestData.ReadByte();
+ string Filename = FullPath.Replace(ArchivePath.FullName, string.Empty).TrimStart('\\');
- SaveInfo SaveInfo = new SaveInfo(TitleId, SaveId, SaveDataType, UserId, SaveSpaceId);
+ if (Nsp.FileExists(Filename))
+ {
+ return OpenNcaFs(Context, FullPath, Nsp.OpenFile(Filename));
+ }
+ }
- MakeObject(Context, new IFileSystem(Context.Device.FileSystem.GetGameSavePath(SaveInfo, Context)));
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
}
} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IStorage.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IStorage.cs
index c3f1f2c4..6a78cdfe 100644
--- a/Ryujinx.HLE/HOS/Services/FspSrv/IStorage.cs
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/IStorage.cs
@@ -22,6 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
this.BaseStream = BaseStream;
}
+ // Read(u64 offset, u64 length) -> buffer<u8, 0x46, 0> buffer
public long Read(ServiceCtx Context)
{
long Offset = Context.RequestData.ReadInt64();
diff --git a/Ryujinx.HLE/HOS/Services/Lr/ILocationResolver.cs b/Ryujinx.HLE/HOS/Services/Lr/ILocationResolver.cs
new file mode 100644
index 00000000..dc057bc1
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Lr/ILocationResolver.cs
@@ -0,0 +1,265 @@
+using LibHac;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.FileSystem.Content;
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+using System.Text;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+using static Ryujinx.HLE.Utilities.StringUtils;
+
+namespace Ryujinx.HLE.HOS.Services.Lr
+{
+ class ILocationResolver : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private StorageId StorageId;
+
+ public ILocationResolver(StorageId StorageId)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, ResolveProgramPath },
+ { 1, RedirectProgramPath },
+ { 2, ResolveApplicationControlPath },
+ { 3, ResolveApplicationHtmlDocumentPath },
+ { 4, ResolveDataPath },
+ { 5, RedirectApplicationControlPath },
+ { 6, RedirectApplicationHtmlDocumentPath },
+ { 7, ResolveApplicationLegalInformationPath },
+ { 8, RedirectApplicationLegalInformationPath },
+ { 9, Refresh },
+ { 10, SetProgramNcaPath2 },
+ { 11, ClearLocationResolver2 },
+ { 12, DeleteProgramNcaPath },
+ { 13, DeleteControlNcaPath },
+ { 14, DeleteDocHtmlNcaPath },
+ { 15, DeleteInfoHtmlNcaPath }
+ };
+
+ this.StorageId = StorageId;
+ }
+
+ // DeleteInfoHtmlNcaPath()
+ public long DeleteInfoHtmlNcaPath(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ DeleteContentPath(Context, TitleId, ContentType.Manual);
+
+ return 0;
+ }
+
+ // DeleteDocHtmlNcaPath()
+ public long DeleteDocHtmlNcaPath(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ DeleteContentPath(Context, TitleId, ContentType.Manual);
+
+ return 0;
+ }
+
+ // DeleteControlNcaPath()
+ public long DeleteControlNcaPath(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ DeleteContentPath(Context, TitleId, ContentType.Control);
+
+ return 0;
+ }
+
+ // DeleteProgramNcaPath()
+ public long DeleteProgramNcaPath(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ DeleteContentPath(Context, TitleId, ContentType.Program);
+
+ return 0;
+ }
+
+ // ClearLocationResolver2()
+ public long ClearLocationResolver2(ServiceCtx Context)
+ {
+ Context.Device.System.ContentManager.RefreshEntries(StorageId, 1);
+
+ return 0;
+ }
+
+ // SetProgramNcaPath2()
+ public long SetProgramNcaPath2(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ RedirectPath(Context, TitleId, 1, ContentType.Program);
+
+ return 0;
+ }
+
+ // RedirectApplicationControlPath()
+ public long RedirectApplicationControlPath(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ RedirectPath(Context, TitleId, 1, ContentType.Control);
+
+ return 0;
+ }
+
+ // RedirectApplicationHtmlDocumentPath()
+ public long RedirectApplicationHtmlDocumentPath(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ RedirectPath(Context, TitleId, 1, ContentType.Manual);
+
+ return 0;
+ }
+
+ // RedirectApplicationLegalInformationPath()
+ public long RedirectApplicationLegalInformationPath(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ RedirectPath(Context, TitleId, 1, ContentType.Manual);
+
+ return 0;
+ }
+
+ // ResolveDataPath()
+ public long ResolveDataPath(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ if (ResolvePath(Context, TitleId, ContentType.Data) || ResolvePath(Context, TitleId, ContentType.AocData))
+ {
+ return 0;
+ }
+ else
+ {
+ return MakeError(ErrorModule.Lr, LrErr.AccessDenied);
+ }
+ }
+
+ // ResolveApplicationHtmlDocumentPath()
+ public long ResolveApplicationHtmlDocumentPath(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ if (ResolvePath(Context, TitleId, ContentType.Manual))
+ {
+ return 0;
+ }
+ else
+ {
+ return MakeError(ErrorModule.Lr, LrErr.AccessDenied);
+ }
+ }
+
+ // ResolveApplicationLegalInformationPath()
+ public long ResolveApplicationLegalInformationPath(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ if (ResolvePath(Context, TitleId, ContentType.Manual))
+ {
+ return 0;
+ }
+ else
+ {
+ return MakeError(ErrorModule.Lr, LrErr.AccessDenied);
+ }
+ }
+
+ // ResolveApplicationControlPath()
+ public long ResolveApplicationControlPath(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ if (ResolvePath(Context, TitleId, ContentType.Control))
+ {
+ return 0;
+ }
+ else
+ {
+ return MakeError(ErrorModule.Lr, LrErr.AccessDenied);
+ }
+ }
+
+ // RedirectProgramPath()
+ public long RedirectProgramPath(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ RedirectPath(Context, TitleId, 0, ContentType.Program);
+
+ return 0;
+ }
+
+ // Refresh()
+ public long Refresh(ServiceCtx Context)
+ {
+ Context.Device.System.ContentManager.RefreshEntries(StorageId, 1);
+
+ return 0;
+ }
+
+ // ResolveProgramPath()
+ public long ResolveProgramPath(ServiceCtx Context)
+ {
+ long TitleId = Context.RequestData.ReadInt64();
+
+ if (ResolvePath(Context, TitleId, ContentType.Program))
+ {
+ return 0;
+ }
+ else
+ {
+ return MakeError(ErrorModule.Lr, LrErr.ProgramLocationEntryNotFound);
+ }
+ }
+
+ private void RedirectPath(ServiceCtx Context, long TitleId, int Flag, ContentType ContentType)
+ {
+ string ContentPath = ReadUtf8String(Context);
+ LocationEntry NewLocation = new LocationEntry(ContentPath, Flag, TitleId, ContentType);
+
+ Context.Device.System.ContentManager.RedirectLocation(NewLocation, StorageId);
+ }
+
+ private bool ResolvePath(ServiceCtx Context, long TitleId,ContentType ContentType)
+ {
+ ContentManager ContentManager = Context.Device.System.ContentManager;
+ string ContentPath = ContentManager.GetInstalledContentPath(TitleId, StorageId, ContentType.Program);
+
+ if (!string.IsNullOrWhiteSpace(ContentPath))
+ {
+ long Position = Context.Request.RecvListBuff[0].Position;
+ long Size = Context.Request.RecvListBuff[0].Size;
+
+ byte[] ContentPathBuffer = Encoding.UTF8.GetBytes(ContentPath);
+
+ Context.Memory.WriteBytes(Position, ContentPathBuffer);
+ }
+ else
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void DeleteContentPath(ServiceCtx Context, long TitleId, ContentType ContentType)
+ {
+ ContentManager ContentManager = Context.Device.System.ContentManager;
+ string ContentPath = ContentManager.GetInstalledContentPath(TitleId, StorageId, ContentType.Manual);
+
+ ContentManager.ClearEntry(TitleId, ContentType.Manual, StorageId);
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Lr/ILocationResolverManager.cs b/Ryujinx.HLE/HOS/Services/Lr/ILocationResolverManager.cs
new file mode 100644
index 00000000..77710f76
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Lr/ILocationResolverManager.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.FileSystem;
+
+namespace Ryujinx.HLE.HOS.Services.Lr
+{
+ class ILocationResolverManager : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ILocationResolverManager()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, OpenLocationResolver },
+ };
+ }
+
+ // OpenLocationResolver()
+ private long OpenLocationResolver(ServiceCtx Context)
+ {
+ StorageId StorageId = (StorageId)Context.RequestData.ReadByte();
+
+ MakeObject(Context, new ILocationResolver(StorageId));
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Lr/LrErr.cs b/Ryujinx.HLE/HOS/Services/Lr/LrErr.cs
new file mode 100644
index 00000000..5c87b73b
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Lr/LrErr.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Lr
+{
+ class LrErr
+ {
+ public const int ProgramLocationEntryNotFound = 2;
+ public const int AccessDenied = 5;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs b/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs
new file mode 100644
index 00000000..29792a1b
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Ncm
+{
+ class IContentManager : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IContentManager()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+
+ };
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Ncm/IContentStorage.cs b/Ryujinx.HLE/HOS/Services/Ncm/IContentStorage.cs
new file mode 100644
index 00000000..a19fe079
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Ncm/IContentStorage.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Ncm
+{
+ class IContentStorage : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IContentStorage()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+
+ };
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
new file mode 100644
index 00000000..ee438d99
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
@@ -0,0 +1,23 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Ns
+{
+ class IApplicationManagerInterface : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private bool IsInitialized;
+
+ public IApplicationManagerInterface()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+
+ };
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs b/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs
index d73adc0e..0495a388 100644
--- a/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs
+++ b/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs
@@ -65,7 +65,7 @@ namespace Ryujinx.HLE.HOS.Services.Pl
public long GetSharedMemoryNativeHandle(ServiceCtx Context)
{
- Context.Device.System.Font.EnsureInitialized();
+ Context.Device.System.Font.EnsureInitialized(Context.Device.System.ContentManager);
if (Context.Process.HandleTable.GenerateHandle(Context.Device.System.FontSharedMem, out int Handle) != KernelResult.Success)
{
diff --git a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs
index d91583d6..2be1db70 100644
--- a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs
+++ b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs
@@ -4,12 +4,14 @@ using Ryujinx.HLE.HOS.Services.Apm;
using Ryujinx.HLE.HOS.Services.Aud;
using Ryujinx.HLE.HOS.Services.Bsd;
using Ryujinx.HLE.HOS.Services.Caps;
+using Ryujinx.HLE.HOS.Services.Es;
using Ryujinx.HLE.HOS.Services.FspSrv;
using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.HLE.HOS.Services.Irs;
using Ryujinx.HLE.HOS.Services.Ldr;
using Ryujinx.HLE.HOS.Services.Lm;
using Ryujinx.HLE.HOS.Services.Mm;
+using Ryujinx.HLE.HOS.Services.Ncm;
using Ryujinx.HLE.HOS.Services.Nfp;
using Ryujinx.HLE.HOS.Services.Ns;
using Ryujinx.HLE.HOS.Services.Nv;
@@ -87,6 +89,9 @@ namespace Ryujinx.HLE.HOS.Services
case "csrng":
return new IRandomInterface();
+ case "es":
+ return new IETicketService();
+
case "friend:a":
return new Friend.IServiceCreator();
@@ -114,12 +119,18 @@ namespace Ryujinx.HLE.HOS.Services
case "mm:u":
return new IRequest();
+ case "ncm":
+ return new IContentManager();
+
case "nfp:user":
return new IUserManager();
case "nifm:u":
return new Nifm.IStaticService();
+ case "ns:am":
+ return new IApplicationManagerInterface();
+
case "ns:ec":
return new IServiceGetterInterface();
diff --git a/Ryujinx.HLE/HOS/Services/Set/ISystemSettingsServer.cs b/Ryujinx.HLE/HOS/Services/Set/ISystemSettingsServer.cs
index 070a4d5e..416ea1fb 100644
--- a/Ryujinx.HLE/HOS/Services/Set/ISystemSettingsServer.cs
+++ b/Ryujinx.HLE/HOS/Services/Set/ISystemSettingsServer.cs
@@ -5,6 +5,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
+using LibHac;
+using Ryujinx.HLE.FileSystem;
namespace Ryujinx.HLE.HOS.Services.Set
{
@@ -18,6 +20,7 @@ namespace Ryujinx.HLE.HOS.Services.Set
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
+ { 3, GetFirmwareVersion },
{ 4, GetFirmwareVersion2 },
{ 23, GetColorSetId },
{ 24, SetColorSetId },
@@ -25,11 +28,27 @@ namespace Ryujinx.HLE.HOS.Services.Set
};
}
+ // GetFirmwareVersion() -> buffer<nn::settings::system::FirmwareVersion, 0x1a, 0x100>
+ public static long GetFirmwareVersion(ServiceCtx Context)
+ {
+ return GetFirmwareVersion2(Context);
+ }
+
+ // GetFirmwareVersion2() -> buffer<nn::settings::system::FirmwareVersion, 0x1a, 0x100>
public static long GetFirmwareVersion2(ServiceCtx Context)
{
long ReplyPos = Context.Request.RecvListBuff[0].Position;
long ReplySize = Context.Request.RecvListBuff[0].Size;
+ byte[] FirmwareData = GetFirmwareData(Context.Device);
+
+ if (FirmwareData != null)
+ {
+ Context.Memory.WriteBytes(ReplyPos, FirmwareData);
+
+ return 0;
+ }
+
const byte MajorFWVersion = 0x03;
const byte MinorFWVersion = 0x00;
const byte MicroFWVersion = 0x00;
@@ -74,6 +93,7 @@ namespace Ryujinx.HLE.HOS.Services.Set
return 0;
}
+ // GetColorSetId() -> i32
public static long GetColorSetId(ServiceCtx Context)
{
Context.ResponseData.Write((int)Context.Device.System.State.ThemeColor);
@@ -81,6 +101,7 @@ namespace Ryujinx.HLE.HOS.Services.Set
return 0;
}
+ // GetColorSetId() -> i32
public static long SetColorSetId(ServiceCtx Context)
{
int ColorSetId = Context.RequestData.ReadInt32();
@@ -90,6 +111,7 @@ namespace Ryujinx.HLE.HOS.Services.Set
return 0;
}
+ // GetSettingsItemValue(buffer<nn::settings::SettingsName, 0x19, 0x48>, buffer<nn::settings::SettingsItemKey, 0x19, 0x48>) -> (u64, buffer<unknown, 6, 0>)
public static long GetSettingsItemValue(ServiceCtx Context)
{
long ClassPos = Context.Request.PtrBuff[0].Position;
@@ -148,5 +170,44 @@ namespace Ryujinx.HLE.HOS.Services.Set
return 0;
}
+
+ public static byte[] GetFirmwareData(Switch Device)
+ {
+ byte[] Data = null;
+ long TitleId = 0x0100000000000809;
+ string ContentPath = Device.System.ContentManager.GetInstalledContentPath(TitleId, StorageId.NandSystem, ContentType.Data);
+
+ if(string.IsNullOrWhiteSpace(ContentPath))
+ {
+ return null;
+ }
+
+ string FirmwareTitlePath = Device.FileSystem.SwitchPathToSystemPath(ContentPath);
+ FileStream FirmwareStream = File.Open(FirmwareTitlePath, FileMode.Open, FileAccess.Read);
+ Nca FirmwareContent = new Nca(Device.System.KeySet, FirmwareStream, false);
+ Stream RomFsStream = FirmwareContent.OpenSection(0, false, Device.System.FsIntegrityCheckLevel);
+
+ if(RomFsStream == null)
+ {
+ return null;
+ }
+
+ Romfs FirmwareRomFs = new Romfs(RomFsStream);
+
+ using(MemoryStream MemoryStream = new MemoryStream())
+ {
+ using (Stream FirmwareFile = FirmwareRomFs.OpenFile("/file"))
+ {
+ FirmwareFile.CopyTo(MemoryStream);
+ }
+
+ Data = MemoryStream.ToArray();
+ }
+
+ FirmwareContent.Dispose();
+ FirmwareStream.Dispose();
+
+ return Data;
+ }
}
}
diff --git a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs
index 3833ce9e..aa96a416 100644
--- a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs
+++ b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs
@@ -46,6 +46,8 @@ namespace Ryujinx.HLE.HOS.SystemState
public ColorSet ThemeColor { get; set; }
+ public bool InstallContents { get; set; }
+
private ConcurrentDictionary<string, UserProfile> Profiles;
internal UserProfile LastOpenUser { get; private set; }
diff --git a/Ryujinx.HLE/Utilities/FontUtils.cs b/Ryujinx.HLE/Utilities/FontUtils.cs
new file mode 100644
index 00000000..efe7560c
--- /dev/null
+++ b/Ryujinx.HLE/Utilities/FontUtils.cs
@@ -0,0 +1,37 @@
+using System.IO;
+
+namespace Ryujinx.HLE.Utilities
+{
+ public static class FontUtils
+ {
+ private static readonly uint FontKey = 0x06186249;
+
+ public static byte[] DecryptFont(Stream BFTTFStream)
+ {
+ uint KXor(uint In) => In ^ 0x06186249;
+
+ using (BinaryReader Reader = new BinaryReader(BFTTFStream))
+ {
+ using (MemoryStream TTFStream = new MemoryStream())
+ {
+ using (BinaryWriter Output = new BinaryWriter(TTFStream))
+ {
+ if (KXor(Reader.ReadUInt32()) != 0x18029a7f)
+ {
+ throw new InvalidDataException("Error: Input file is not in BFTTF format!");
+ }
+
+ BFTTFStream.Position += 4;
+
+ for (int i = 0; i < (BFTTFStream.Length - 8) / 4; i++)
+ {
+ Output.Write(KXor(Reader.ReadUInt32()));
+ }
+
+ return TTFStream.ToArray();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/Utilities/StringUtils.cs b/Ryujinx.HLE/Utilities/StringUtils.cs
index e8d6550a..a10273ee 100644
--- a/Ryujinx.HLE/Utilities/StringUtils.cs
+++ b/Ryujinx.HLE/Utilities/StringUtils.cs
@@ -1,5 +1,7 @@
-using System;
+using Ryujinx.HLE.HOS;
+using System;
using System.Globalization;
+using System.IO;
using System.Linq;
using System.Text;
@@ -47,5 +49,28 @@ namespace Ryujinx.HLE.Utilities
return Output;
}
+
+ public static string ReadUtf8String(ServiceCtx Context, int Index = 0)
+ {
+ long Position = Context.Request.PtrBuff[Index].Position;
+ long Size = Context.Request.PtrBuff[Index].Size;
+
+ using (MemoryStream MS = new MemoryStream())
+ {
+ while (Size-- > 0)
+ {
+ byte Value = Context.Memory.ReadByte(Position++);
+
+ if (Value == 0)
+ {
+ break;
+ }
+
+ MS.WriteByte(Value);
+ }
+
+ return Encoding.UTF8.GetString(MS.ToArray());
+ }
+ }
}
}