Implement contentmanager and related services

This commit is contained in:
emmaus 2018-09-09 15:36:52 +00:00
parent 44c1cf3fe5
commit 401569b7e6
30 changed files with 1694 additions and 112 deletions

View file

@ -0,0 +1,293 @@
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 ContentInstallationDirectory = null;
string ContentPathString = null;
try
{
ContentPathString = LocationHelper.GetContentPath(StorageId);
ContentInstallationDirectory = LocationHelper.GetRealPath(Device.FileSystem, ContentPathString);
}
catch (NotSupportedException NEx)
{
continue;
}
Directory.CreateDirectory(ContentInstallationDirectory);
LinkedList<LocationEntry> LocationList = new LinkedList<LocationEntry>();
void AddEntry(LocationEntry Entry)
{
LocationList.AddLast(Entry);
}
foreach (string DirectoryPath in Directory.EnumerateDirectories(ContentInstallationDirectory))
{
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);
LocationEntry Entry = new LocationEntry(ContentPathString,
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(ContentInstallationDirectory))
{
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);
LocationEntry Entry = new LocationEntry(ContentPathString,
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 UInt128 GetInstalledNcaId(long TitleId, ContentType ContentType)
{
if (ContentDictionary.ContainsKey(((ulong)TitleId,ContentType)))
{
return new UInt128(ContentDictionary[((ulong)TitleId,ContentType)]);
}
return new UInt128();
}
public string GetInstalledPath(long TitleId, ContentType ContentType, StorageId StorageId)
{
LocationEntry LocationEntry = GetLocation(TitleId, ContentType, StorageId);
string ContentPath = LocationHelper.GetRealPath(Device.FileSystem, LocationEntry.ContentPath);
string NcaPath = Path.Combine(ContentPath, ContentDictionary[((ulong)TitleId, ContentType)]) + ".nca";
if (!File.Exists(NcaPath))
{
NcaPath = Path.Combine(NcaPath, "00");
}
return NcaPath;
}
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 GetInstalledContentStorage(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 = GetInstalledPath(LocationEntry.TitleId, ContentType, StorageId);
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);
return Nca.Header.ContentType == ContentType;
}
}
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);
}
}
}

View file

@ -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";
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,89 @@
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", "registered");
case ContentPath.UserContent:
return Path.Combine(FileSystem.GetBasePath(), UserNandPath, "Contents", "registered");
case ContentPath.SdCardContent:
return Path.Combine(FileSystem.GetSdCardPath(), "Nintendo", "Contents", "registered");
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 GetContentPath(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)
{
switch (ContentPathString)
{
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;
}
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.FileSystem
{
public enum ContentStorageId : byte
{
NandSystem,
NandUser,
SdCard
}
}

View file

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ryujinx.HLE.FileSystem.Content
{
enum TitleType : byte
{
SystemPrograms = 0x01,
SystemDataArchive = 0x02,
SystemUpdate = 0x03,
FirmwarePackageA = 0x04,
FirmwarePackageB = 0x05,
RegularApplication = 0x80,
Update = 0x81,
AddOnContent = 0x82,
DeltaTitle = 0x83
}
}

View file

@ -0,0 +1,221 @@
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.FspSrv;
using System;
using System.IO;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.FileSystem
{
class FileSystemProvider : IFileSystemProvider
{
private string BasePath;
private string RootPath;
public FileSystemProvider(string BasePath, string RootPath)
{
this.BasePath = BasePath;
this.RootPath = RootPath;
CheckIfDecendentOfRootPath(BasePath);
}
public long CreateDirectory(string Name)
{
if (Directory.Exists(Name))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
}
Directory.CreateDirectory(Name);
return 0;
}
public long CreateFile(string Name, long Size)
{
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)
{
string DirName = Name;
if (!Directory.Exists(DirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
Directory.Delete(DirName, Recursive);
return 0;
}
public long DeleteFile(string Name)
{
if (!File.Exists(Name))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
else
{
File.Delete(Name);
}
return 0;
}
public string[] GetDirectories(string Path)
{
return Directory.GetDirectories(Path);
}
public string[] GetEntries(string Path)
{
string DirName = GetFullPath(Path);
if (Directory.Exists(DirName))
{
return Directory.GetFileSystemEntries(DirName);
}
return null;
}
public string[] GetFiles(string Path)
{
return Directory.GetFiles(Path);
}
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)
{
return Directory.Exists(Name);
}
public bool FileExists(string Name)
{
return File.Exists(Name);
}
public long OpenDirectory(string Name, int FilterFlags,out IDirectory DirectoryInterface)
{
DirectoryInterface = null;
if (Directory.Exists(Name))
{
DirectoryInterface = new IDirectory(Name, FilterFlags, this);
return 0;
}
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
public long OpenFile(string Name, out IFile FileInterface)
{
FileInterface = null;
if (File.Exists(Name))
{
FileStream Stream = new FileStream(Name, FileMode.Open);
FileInterface = new IFile(Stream, Name);
return 0;
}
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
public long RenameDirectory(string OldName, string 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)
{
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 {BasePath} is not a child directory of {RootPath}");
}
}
}

View file

@ -0,0 +1,40 @@
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.FspSrv;
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);
string[] GetEntries(string Path);
string[] GetDirectories(string Path);
string[] 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);
}
}

View file

@ -0,0 +1,153 @@
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 RomFileSystemProvider : IFileSystemProvider
{
private Romfs RomFs;
public RomFileSystemProvider(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 string[] GetDirectories(string Path)
{
List<string> Directories = new List<string>();
foreach(RomfsDir Directory in RomFs.Directories)
{
Directories.Add(Directory.Name);
}
return Directories.ToArray();
}
public string[] GetEntries(string Path)
{
List<string> Entries = new List<string>();
foreach (RomfsDir Directory in RomFs.Directories)
{
Entries.Add(Directory.Name);
}
foreach (RomfsFile File in RomFs.Files)
{
Entries.Add(File.Name);
}
return Entries.ToArray();
}
public string[] GetFiles(string Path)
{
List<string> Files = new List<string>();
foreach (RomfsFile File in RomFs.Files)
{
Files.Add(File.Name);
}
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)
{
DirectoryInterface = null;
RomfsDir Directory = RomFs.Directories.Find(x => x.Name == Name);
if (Directory != null)
{
DirectoryInterface = new IDirectory(Name, FilterFlags, this);
}
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
public long OpenFile(string Name, out IFile FileInterface)
{
FileInterface = null;
if (File.Exists(Name))
{
Stream Stream = RomFs.OpenFile(Name);
FileInterface = new IFile(Stream, Name);
return 0;
}
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();
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.HLE.FileSystem
{
internal enum StorageId : byte
{
None,
Host,
GameCard,
NandSystem,
NandUser,
SdCard
}
}

View file

@ -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,13 @@ 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);
if (FirstSeparatorOffset == -1)
{
return $"{RawPath}:/";
@ -87,6 +98,7 @@ namespace Ryujinx.HLE.FileSystem
string BasePath = RawPath.Substring(0, FirstSeparatorOffset);
string FileName = RawPath.Substring(FirstSeparatorOffset + 1);
return $"{BasePath}:/{FileName}";
}
return null;
@ -94,6 +106,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))

View file

@ -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,68 @@ 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 FontContentPath = ContentManager.GetInstalledPath(FontTitle, ContentType.Data, StorageId.NandSystem);
if (!string.IsNullOrWhiteSpace(FontContentPath))
{
int FileIndex = 0;
//Use second file in Chinese Font title for standard
if(Name == "FontChineseSimplified")
{
FileIndex = 1;
}
FileStream NcaFileStream = new FileStream(FontContentPath, 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 EncryptedFontFile = Romfs.OpenFile(Romfs.Files[FileIndex]);
byte[] Data = DecryptFont(EncryptedFontFile);
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 +115,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 +153,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;
}

View file

@ -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);
}

View file

@ -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>()
{
};
}
}
}

View file

@ -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
}
}

View file

@ -5,5 +5,6 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
public const int PathDoesNotExist = 1;
public const int PathAlreadyExists = 2;
public const int PathAlreadyInUse = 7;
public const int InvalidInput = 6001;
}
}

View file

@ -1,3 +1,4 @@
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Ipc;
using System;
using System.Collections.Generic;
@ -20,9 +21,11 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
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,18 +33,20 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
{ 1, GetEntryCount }
};
this.HostPath = HostPath;
this.Provider = Provider;
this.DirectoryPath = DirectoryPath;
DirectoryEntries = new List<string>();
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;

View file

@ -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>()
{
@ -42,6 +45,8 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
OpenPaths = new HashSet<string>();
this.Path = Path;
this.Provider = Provider;
}
public long CreateFile(ServiceCtx 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,16 @@ 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);
}
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 +92,21 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
File.Delete(FileName);
return 0;
return Provider.DeleteFile(FileName);
}
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,7 +116,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
Directory.CreateDirectory(DirName);
Provider.CreateDirectory(DirName);
return 0;
}
@ -137,7 +135,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
{
string Name = ReadUtf8String(Context);
string DirName = Context.Device.FileSystem.GetFullPath(Path, Name);
string DirName = Provider.GetFullPath(Name);
if (!Directory.Exists(DirName))
{
@ -149,7 +147,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
Directory.Delete(DirName, Recursive);
Provider.DeleteDirectory(DirName, Recursive);
return 0;
}
@ -159,15 +157,15 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
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,9 +175,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
File.Move(OldFileName, NewFileName);
return 0;
return Provider.RenameFile(OldFileName, NewFileName);
}
public long RenameDirectory(ServiceCtx Context)
@ -187,15 +183,15 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
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 +201,20 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
Directory.Move(OldDirName, NewDirName);
return 0;
return Provider.RenameDirectory(OldDirName, NewDirName);
}
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);
}
@ -240,9 +234,9 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
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,20 +246,24 @@ 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;
lock (OpenPaths)
{
OpenPaths.Add(FileName);
}
MakeObject(Context, FileInterface);
return 0;
}
MakeObject(Context, FileInterface);
return 0;
return Error;
}
public long OpenDirectory(ServiceCtx Context)
@ -274,25 +272,33 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
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);
DirInterface.Disposed += RemoveDirectoryInUse;
lock (OpenPaths)
if (IsPathAlreadyInUse(DirName))
{
OpenPaths.Add(DirName);
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
MakeObject(Context, DirInterface);
long Error = Provider.OpenDirectory(DirName, FilterFlags, out IDirectory DirInterface);
return 0;
if (Error == 0)
{
DirInterface.Disposed += RemoveDirectoryInUse;
lock (OpenPaths)
{
OpenPaths.Add(DirName);
}
MakeObject(Context, DirInterface);
}
return Error;
}
public long Commit(ServiceCtx Context)
@ -304,7 +310,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
{
string Name = ReadUtf8String(Context);
Context.ResponseData.Write(Context.Device.FileSystem.GetDrive().AvailableFreeSpace);
Context.ResponseData.Write(Provider.GetFreeSpace(Context));
return 0;
}
@ -313,7 +319,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
{
string Name = ReadUtf8String(Context);
Context.ResponseData.Write(Context.Device.FileSystem.GetDrive().TotalSize);
Context.ResponseData.Write(Provider.GetFreeSpace(Context));
return 0;
}
@ -322,9 +328,9 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
{
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 +340,15 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
foreach (string Entry in Directory.EnumerateFileSystemEntries(DirName))
foreach (string Entry in Provider.GetEntries(DirName))
{
if (Directory.Exists(Entry))
if (Provider.DirectoryExists(Entry))
{
Directory.Delete(Entry, true);
Provider.DeleteDirectory(Entry, true);
}
else if (File.Exists(Entry))
else if (Provider.FileExists(Entry))
{
File.Delete(Entry);
Provider.DeleteFile(Entry);
}
}
@ -377,30 +383,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);
}
}
}

View file

@ -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
{
@ -16,10 +23,13 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 1, SetCurrentProcess },
{ 11, OpenBisFileSystem },
{ 8, OpenFileSystemWithId },
{ 18, OpenSdCardFileSystem },
{ 51, OpenSaveDataFileSystem },
{ 52, OpenSaveDataFileSystemBySystemSaveDataId },
{ 200, OpenDataStorageByCurrentProcess },
{ 202, OpenDataStorageByDataId },
{ 203, OpenPatchDataStorageByCurrentProcess },
{ 1005, GetGlobalAccessLogMode }
};
@ -30,9 +40,76 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return 0;
}
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;
}
public long OpenFileSystemWithId(ServiceCtx Context)
{
FileSystemType FileSystemType = (FileSystemType)Context.RequestData.ReadInt32();
long TitleId = Context.RequestData.ReadInt64();
string Path = ReadUtf8String(Context);
string FullPath = Context.Device.FileSystem.SwitchPathToSystemPath(Path);
FileStream FileStream = new FileStream(FullPath, FileMode.Open, FileAccess.Read);
Nca Nca = new Nca(Context.Device.System.KeySet, FileStream, false);
NcaSection RomfsSection = Nca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs);
if (RomfsSection != null)
{
Stream RomfsStream = Nca.OpenSection(RomfsSection.SectionNum, false, Context.Device.System.FsIntegrityCheckLevel);
IFileSystem NcaFileSystem = new IFileSystem(Path, new RomFileSystemProvider(RomfsStream));
MakeObject(Context, NcaFileSystem);
return 0;
}
return MakeError(ErrorModule.Fs, FsErr.InvalidInput);
}
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;
}
@ -58,6 +135,69 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return 0;
}
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 InstallPath =
Context.Device.System.ContentManager.GetInstalledPath(TitleId, ContentType.AocData, StorageId);
UInt128 NcaId = Context.Device.System.ContentManager.GetInstalledNcaId(TitleId, ContentType.AocData);
if (string.IsNullOrWhiteSpace(InstallPath))
{
InstallPath =
Context.Device.System.ContentManager.GetInstalledPath(TitleId, ContentType.Data, StorageId);
}
if ((NcaId.High | NcaId.Low) == 0)
{
NcaId = Context.Device.System.ContentManager.GetInstalledNcaId(TitleId, ContentType.Data);
}
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}.");
}
public long OpenPatchDataStorageByCurrentProcess(ServiceCtx Context)
{
MakeObject(Context, new IStorage(Context.Device.FileSystem.RomFs));
@ -88,7 +228,11 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
SaveInfo SaveInfo = new SaveInfo(TitleId, SaveId, SaveDataType, UserId, SaveSpaceId);
MakeObject(Context, new IFileSystem(Context.Device.FileSystem.GetGameSavePath(SaveInfo, Context)));
string SavePath = Context.Device.FileSystem.GetGameSavePath(SaveInfo, Context);
FileSystemProvider FileSystemProvider = new FileSystemProvider(SavePath, Context.Device.FileSystem.GetBasePath());
MakeObject(Context, new IFileSystem(SavePath, FileSystemProvider));
}
}
}

View file

@ -0,0 +1,252 @@
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;
}
public long DeleteInfoHtmlNcaPath(ServiceCtx Context)
{
long TitleId = Context.RequestData.ReadInt64();
DeleteContentPath(Context, TitleId, ContentType.Manual);
return 0;
}
public long DeleteDocHtmlNcaPath(ServiceCtx Context)
{
long TitleId = Context.RequestData.ReadInt64();
DeleteContentPath(Context, TitleId, ContentType.Manual);
return 0;
}
public long DeleteControlNcaPath(ServiceCtx Context)
{
long TitleId = Context.RequestData.ReadInt64();
DeleteContentPath(Context, TitleId, ContentType.Control);
return 0;
}
public long DeleteProgramNcaPath(ServiceCtx Context)
{
long TitleId = Context.RequestData.ReadInt64();
DeleteContentPath(Context, TitleId, ContentType.Program);
return 0;
}
public long ClearLocationResolver2(ServiceCtx Context)
{
Context.Device.System.ContentManager.RefreshEntries(StorageId, 1);
return 0;
}
public long SetProgramNcaPath2(ServiceCtx Context)
{
long TitleId = Context.RequestData.ReadInt64();
RedirectPath(Context, TitleId, 1, ContentType.Program);
return 0;
}
public long RedirectApplicationControlPath(ServiceCtx Context)
{
long TitleId = Context.RequestData.ReadInt64();
RedirectPath(Context, TitleId, 1, ContentType.Control);
return 0;
}
public long RedirectApplicationHtmlDocumentPath(ServiceCtx Context)
{
long TitleId = Context.RequestData.ReadInt64();
RedirectPath(Context, TitleId, 1, ContentType.Manual);
return 0;
}
public long RedirectApplicationLegalInformationPath(ServiceCtx Context)
{
long TitleId = Context.RequestData.ReadInt64();
RedirectPath(Context, TitleId, 1, ContentType.Manual);
return 0;
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
public long RedirectProgramPath(ServiceCtx Context)
{
long TitleId = Context.RequestData.ReadInt64();
RedirectPath(Context, TitleId, 0, ContentType.Program);
return 0;
}
public long Refresh(ServiceCtx Context)
{
Context.Device.System.ContentManager.RefreshEntries(StorageId, 1);
return 0;
}
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.GetInstalledContentStorage(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.GetInstalledContentStorage(TitleId, StorageId, ContentType.Manual);
ContentManager.ClearEntry(TitleId, ContentType.Manual, StorageId);
}
}
}

View file

@ -0,0 +1,31 @@
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 },
};
}
private long OpenLocationResolver(ServiceCtx Context)
{
StorageId StorageId = (StorageId)Context.RequestData.ReadByte();
MakeObject(Context, new ILocationResolver(StorageId));
return 0;
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Lr
{
class LrErr
{
public const int ProgramLocationEntryNotFound = 2;
public const int AccessDenied = 5;
}
}

View file

@ -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>()
{
};
}
}
}

View file

@ -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>()
{
};
}
}
}

View file

@ -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>()
{
};
}
}
}

View file

@ -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)
{

View file

@ -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();
@ -111,12 +116,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();

View file

@ -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; }

View file

@ -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();
}
}
}
}
}
}

View file

@ -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());
}
}
}
}