Implement ContentManager and related services (#438)

* Implement contentmanager and related services

* small changes

* read system firmware version from nand

* add pfs support, write directoryentry info for romfs files

* add file check in fsp-srv:8

* add support for open fs of internal files

* fix filename when accessing pfs

* use switch style paths for contentpath

* close nca after verifying type

* removed publishing profiles, align directory entry

* fix style

* lots of style fixes

* yasf(yet another style fix)

* yasf(yet another style fix) plus symbols

* enforce path check on every fs access

* change enum type to default

* fix typo
This commit is contained in:
emmauss 2018-11-18 21:37:41 +02:00 committed by gdkchan
parent e603b7afbc
commit fe8fbb6fb9
38 changed files with 2179 additions and 173 deletions

View file

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

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.FspSrv
{
public enum DirectoryEntryType
{
Directory,
File
}
}

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

View file

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

View file

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

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>()
{
@ -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,36 @@ 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;
}
// 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))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
IDirectory DirInterface = new IDirectory(DirName, FilterFlags);
DirInterface.Disposed += RemoveDirectoryInUse;
lock (OpenPaths)
{
OpenPaths.Add(DirName);
}
MakeObject(Context, DirInterface);
return 0;
}
public long Commit(ServiceCtx Context)
{
return 0;
}
public long GetFreeSpaceSize(ServiceCtx Context)
{
string Name = ReadUtf8String(Context);
Context.ResponseData.Write(Context.Device.FileSystem.GetDrive().AvailableFreeSpace);
return 0;
}
public long GetTotalSpaceSize(ServiceCtx Context)
{
string Name = ReadUtf8String(Context);
Context.ResponseData.Write(Context.Device.FileSystem.GetDrive().TotalSize);
return 0;
}
public long CleanDirectoryRecursively(ServiceCtx Context)
{
string Name = ReadUtf8String(Context);
string DirName = Context.Device.FileSystem.GetFullPath(Path, Name);
if (!Directory.Exists(DirName))
if (!Provider.DirectoryExists(DirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
@ -334,15 +293,75 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
foreach (string Entry in Directory.EnumerateFileSystemEntries(DirName))
long Error = Provider.OpenDirectory(DirName, FilterFlags, out IDirectory DirInterface);
if (Error == 0)
{
if (Directory.Exists(Entry))
DirInterface.Disposed += RemoveDirectoryInUse;
lock (OpenPaths)
{
Directory.Delete(Entry, true);
OpenPaths.Add(DirName);
}
else if (File.Exists(Entry))
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(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(Provider.GetFreeSpace(Context));
return 0;
}
// CleanDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path)
public long CleanDirectoryRecursively(ServiceCtx Context)
{
string Name = ReadUtf8String(Context);
string DirName = Provider.GetFullPath(Name);
if (!Provider.DirectoryExists(DirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
if (IsPathAlreadyInUse(DirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
foreach (DirectoryEntry Entry in Provider.GetEntries(DirName))
{
if (Provider.DirectoryExists(Entry.Path))
{
File.Delete(Entry);
Provider.DeleteDirectory(Entry.Path, true);
}
else if (Provider.FileExists(Entry.Path))
{
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);
}
}
}

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
{
@ -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());
SaveDataType SaveDataType = (SaveDataType)Context.RequestData.ReadByte();
MakeObject(Context, new IFileSystem(SavePath, FileSystemProvider));
}
SaveInfo SaveInfo = new SaveInfo(TitleId, SaveId, SaveDataType, UserId, SaveSpaceId);
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"));
MakeObject(Context, new IFileSystem(Context.Device.FileSystem.GetGameSavePath(SaveInfo, Context)));
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);
}
string Filename = FullPath.Replace(ArchivePath.FullName, string.Empty).TrimStart('\\');
if (Nsp.FileExists(Filename))
{
return OpenNcaFs(Context, FullPath, Nsp.OpenFile(Filename));
}
}
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
}
}

View file

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