diff --git a/Ryujinx/OsHle/Objects/FspSrv/IDirectory.cs b/Ryujinx/OsHle/Objects/FspSrv/IDirectory.cs new file mode 100644 index 0000000000..8286745115 --- /dev/null +++ b/Ryujinx/OsHle/Objects/FspSrv/IDirectory.cs @@ -0,0 +1,133 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Ipc; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.OsHle.Objects.FspSrv +{ + [StructLayout(LayoutKind.Sequential, Size = 0x310)] + struct DirectoryEntry + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x300)] + public byte[] Name; + public int Unknown; + public byte Type; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3)] + public byte[] Padding; + public long Size; + } + + enum DirectoryEntryType + { + Directory, + File + } + + class IDirectory : IIpcInterface + { + private List DirectoryEntries = new List(); + private Dictionary m_Commands; + + public IReadOnlyDictionary Commands => m_Commands; + + private string HostPath; + + public IDirectory(string HostPath, int flags) + { + m_Commands = new Dictionary() + { + { 0, Read }, + { 1, GetEntryCount } + }; + + this.HostPath = HostPath; + + if ((flags & 1) == 1) + { + string[] Directories = Directory.GetDirectories(HostPath, "*", SearchOption.TopDirectoryOnly). + Where(x => (new FileInfo(x).Attributes & FileAttributes.Hidden) == 0).ToArray(); + + foreach (string Directory in Directories) + { + DirectoryEntry Info = new DirectoryEntry + { + Name = Encoding.UTF8.GetBytes(Directory), + Type = (byte)DirectoryEntryType.Directory, + Size = 0 + }; + + Array.Resize(ref Info.Name, 0x300); + DirectoryEntries.Add(Info); + } + } + + if ((flags & 2) == 2) + { + string[] Files = Directory.GetFiles(HostPath, "*", SearchOption.TopDirectoryOnly). + Where(x => (new FileInfo(x).Attributes & FileAttributes.Hidden) == 0).ToArray(); + + foreach (string FileName in Files) + { + DirectoryEntry Info = new DirectoryEntry + { + Name = Encoding.UTF8.GetBytes(Path.GetFileName(FileName)), + Type = (byte)DirectoryEntryType.File, + Size = new FileInfo(Path.Combine(HostPath, FileName)).Length + }; + + Array.Resize(ref Info.Name, 0x300); + DirectoryEntries.Add(Info); + } + } + } + + private int LastItem = 0; + public long Read(ServiceCtx Context) + { + long BufferPosition = Context.Request.ReceiveBuff[0].Position; + long BufferLen = Context.Request.ReceiveBuff[0].Size; + long MaxDirectories = BufferLen / Marshal.SizeOf(typeof(DirectoryEntry)); + + if (MaxDirectories > DirectoryEntries.Count - LastItem) + { + MaxDirectories = DirectoryEntries.Count - LastItem; + } + + int CurrentIndex; + for (CurrentIndex = 0; CurrentIndex < MaxDirectories; CurrentIndex++) + { + int CurrentItem = LastItem + CurrentIndex; + + byte[] DirectoryEntry = new byte[Marshal.SizeOf(typeof(DirectoryEntry))]; + IntPtr Ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(DirectoryEntry))); + Marshal.StructureToPtr(DirectoryEntries[CurrentItem], Ptr, true); + Marshal.Copy(Ptr, DirectoryEntry, 0, Marshal.SizeOf(typeof(DirectoryEntry))); + Marshal.FreeHGlobal(Ptr); + + AMemoryHelper.WriteBytes(Context.Memory, BufferPosition + Marshal.SizeOf(typeof(DirectoryEntry)) * CurrentIndex, DirectoryEntry); + } + + if (LastItem < DirectoryEntries.Count) + { + LastItem += CurrentIndex; + Context.ResponseData.Write((long)CurrentIndex); // index = number of entries written this call. + } + else + { + Context.ResponseData.Write((long)0); + } + + return 0; + } + + public long GetEntryCount(ServiceCtx Context) + { + Context.ResponseData.Write((long)DirectoryEntries.Count); + return 0; + } + } +} diff --git a/Ryujinx/OsHle/Objects/FspSrv/IFile.cs b/Ryujinx/OsHle/Objects/FspSrv/IFile.cs index 1c6cc2e29e..2f38999092 100644 --- a/Ryujinx/OsHle/Objects/FspSrv/IFile.cs +++ b/Ryujinx/OsHle/Objects/FspSrv/IFile.cs @@ -19,7 +19,10 @@ namespace Ryujinx.OsHle.Objects.FspSrv m_Commands = new Dictionary() { { 0, Read }, - { 1, Write } + { 1, Write }, + // { 2, Flush }, + { 3, SetSize }, + { 4, GetSize } }; this.BaseStream = BaseStream; @@ -35,14 +38,12 @@ namespace Ryujinx.OsHle.Objects.FspSrv byte[] Data = new byte[Size]; + BaseStream.Seek(Offset, SeekOrigin.Begin); int ReadSize = BaseStream.Read(Data, 0, (int)Size); AMemoryHelper.WriteBytes(Context.Memory, Position, Data); - //TODO: Use ReadSize, we need to return the size that was REALLY read from the file. - //This is a workaround because we are doing something wrong and the game expects to read - //data from a file that doesn't yet exists -- and breaks if it can't read anything. - Context.ResponseData.Write((long)Size); + Context.ResponseData.Write((long)ReadSize); return 0; } @@ -63,6 +64,19 @@ namespace Ryujinx.OsHle.Objects.FspSrv return 0; } + public long GetSize(ServiceCtx Context) + { + Context.ResponseData.Write(BaseStream.Length); + return 0; + } + + public long SetSize(ServiceCtx Context) + { + long Size = Context.RequestData.ReadInt64(); + BaseStream.SetLength(Size); + return 0; + } + public void Dispose() { Dispose(true); diff --git a/Ryujinx/OsHle/Objects/FspSrv/IFileSystem.cs b/Ryujinx/OsHle/Objects/FspSrv/IFileSystem.cs index bf501594f7..c3edf2712b 100644 --- a/Ryujinx/OsHle/Objects/FspSrv/IFileSystem.cs +++ b/Ryujinx/OsHle/Objects/FspSrv/IFileSystem.cs @@ -17,16 +17,151 @@ namespace Ryujinx.OsHle.Objects.FspSrv public IFileSystem(string Path) { + //TODO: implement. m_Commands = new Dictionary() { + { 0, CreateFile }, + { 1, DeleteFile }, + { 2, CreateDirectory }, + { 3, DeleteDirectory }, + { 4, DeleteDirectoryRecursively }, + { 5, RenameFile }, + { 6, RenameDirectory }, { 7, GetEntryType }, { 8, OpenFile }, - { 10, Commit } + { 9, OpenDirectory }, + { 10, Commit }, + //{ 11, GetFreeSpaceSize }, + //{ 12, GetTotalSpaceSize }, + //{ 13, CleanDirectoryRecursively }, + //{ 14, GetFileTimeStampRaw } }; this.Path = Path; } + public long CreateFile(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position); + ulong Mode = Context.RequestData.ReadUInt64(); + uint Size = Context.RequestData.ReadUInt32(); + string FileName = Context.Ns.VFs.GetFullPath(Path, Name); + + if (FileName != null) + { + FileStream NewFile = File.Create(FileName); + NewFile.SetLength(Size); + NewFile.Close(); + return 0; + } + + //TODO: Correct error code. + return -1; + } + + public long DeleteFile(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position); + string FileName = Context.Ns.VFs.GetFullPath(Path, Name); + + if (FileName != null) + { + File.Delete(FileName); + return 0; + } + + //TODO: Correct error code. + return -1; + } + + public long CreateDirectory(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position); + string FileName = Context.Ns.VFs.GetFullPath(Path, Name); + + if (FileName != null) + { + Directory.CreateDirectory(FileName); + return 0; + } + + //TODO: Correct error code. + return -1; + } + + public long DeleteDirectory(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position); + string FileName = Context.Ns.VFs.GetFullPath(Path, Name); + + if (FileName != null) + { + Directory.Delete(FileName); + return 0; + } + + // TODO: Correct error code. + return -1; + } + + public long DeleteDirectoryRecursively(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position); + string FileName = Context.Ns.VFs.GetFullPath(Path, Name); + + if (FileName != null) + { + Directory.Delete(FileName, true); // recursive = true + return 0; + } + + // TODO: Correct error code. + return -1; + } + + public long RenameFile(ServiceCtx Context) + { + long OldPosition = Context.Request.PtrBuff[0].Position; + long NewPosition = Context.Request.PtrBuff[0].Position; + string OldName = AMemoryHelper.ReadAsciiString(Context.Memory, OldPosition); + string NewName = AMemoryHelper.ReadAsciiString(Context.Memory, NewPosition); + string OldFileName = Context.Ns.VFs.GetFullPath(Path, OldName); + string NewFileName = Context.Ns.VFs.GetFullPath(Path, NewName); + + if (OldFileName != null && NewFileName != null) + { + File.Move(OldFileName, NewFileName); + return 0; + } + + // TODO: Correct error code. + return -1; + } + + public long RenameDirectory(ServiceCtx Context) + { + long OldPosition = Context.Request.PtrBuff[0].Position; + long NewPosition = Context.Request.PtrBuff[0].Position; + string OldName = AMemoryHelper.ReadAsciiString(Context.Memory, OldPosition); + string NewName = AMemoryHelper.ReadAsciiString(Context.Memory, NewPosition); + string OldDirName = Context.Ns.VFs.GetFullPath(Path, OldName); + string NewDirName = Context.Ns.VFs.GetFullPath(Path, NewName); + + if (OldDirName != null && NewDirName != null) + { + Directory.Move(OldDirName, NewDirName); + return 0; + } + + // TODO: Correct error code. + return -1; + } + public long GetEntryType(ServiceCtx Context) { long Position = Context.Request.PtrBuff[0].Position; @@ -64,11 +199,44 @@ namespace Ryujinx.OsHle.Objects.FspSrv return -1; } - FileStream Stream = new FileStream(FileName, FileMode.OpenOrCreate); + if (File.Exists(FileName)) + { + FileStream Stream = new FileStream(FileName, FileMode.OpenOrCreate); + MakeObject(Context, new IFile(Stream)); - MakeObject(Context, new IFile(Stream)); + return 0; + } - return 0; + //TODO: Correct error code. + return -1; + } + + public long OpenDirectory(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + int FilterFlags = Context.RequestData.ReadInt32(); + + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position); + + string DirName = Context.Ns.VFs.GetFullPath(Path, Name); + + if(DirName != null) + { + if (Directory.Exists(DirName)) + { + MakeObject(Context, new IDirectory(DirName, FilterFlags)); + return 0; + } + else + { + // TODO: correct error code. + return -1; + } + } + + // TODO: Correct error code. + return -1; } public long Commit(ServiceCtx Context)