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:
parent
e603b7afbc
commit
fe8fbb6fb9
38 changed files with 2179 additions and 173 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -161,3 +161,6 @@ $RECYCLE.BIN/
|
||||||
|
|
||||||
# VS Launch Settings
|
# VS Launch Settings
|
||||||
launchSettings.json
|
launchSettings.json
|
||||||
|
|
||||||
|
# NetCore Publishing Profiles
|
||||||
|
PublishProfiles/
|
||||||
|
|
300
Ryujinx.HLE/FileSystem/Content/ContentManager.cs
Normal file
300
Ryujinx.HLE/FileSystem/Content/ContentManager.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
Ryujinx.HLE/FileSystem/Content/ContentPath.cs
Normal file
19
Ryujinx.HLE/FileSystem/Content/ContentPath.cs
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
28
Ryujinx.HLE/FileSystem/Content/LocationEntry.cs
Normal file
28
Ryujinx.HLE/FileSystem/Content/LocationEntry.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
Ryujinx.HLE/FileSystem/Content/LocationHelper.cs
Normal file
91
Ryujinx.HLE/FileSystem/Content/LocationHelper.cs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
Ryujinx.HLE/FileSystem/Content/StorageId.cs
Normal file
9
Ryujinx.HLE/FileSystem/Content/StorageId.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Ryujinx.HLE.FileSystem.Content
|
||||||
|
{
|
||||||
|
public enum ContentStorageId
|
||||||
|
{
|
||||||
|
NandSystem,
|
||||||
|
NandUser,
|
||||||
|
SdCard
|
||||||
|
}
|
||||||
|
}
|
15
Ryujinx.HLE/FileSystem/Content/TitleType.cs
Normal file
15
Ryujinx.HLE/FileSystem/Content/TitleType.cs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
281
Ryujinx.HLE/FileSystem/FileSystemProvider.cs
Normal file
281
Ryujinx.HLE/FileSystem/FileSystemProvider.cs
Normal file
|
@ -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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
Ryujinx.HLE/FileSystem/IFileSystemProvider.cs
Normal file
41
Ryujinx.HLE/FileSystem/IFileSystemProvider.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
146
Ryujinx.HLE/FileSystem/PFsProvider.cs
Normal file
146
Ryujinx.HLE/FileSystem/PFsProvider.cs
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
163
Ryujinx.HLE/FileSystem/RomFsProvider.cs
Normal file
163
Ryujinx.HLE/FileSystem/RomFsProvider.cs
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
namespace Ryujinx.HLE.FileSystem
|
namespace Ryujinx.HLE.FileSystem
|
||||||
{
|
{
|
||||||
enum SaveSpaceId : byte
|
enum SaveSpaceId
|
||||||
{
|
{
|
||||||
NandSystem,
|
NandSystem,
|
||||||
NandUser,
|
NandUser,
|
||||||
|
|
12
Ryujinx.HLE/FileSystem/StorageId.cs
Normal file
12
Ryujinx.HLE/FileSystem/StorageId.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Ryujinx.HLE.FileSystem
|
||||||
|
{
|
||||||
|
internal enum StorageId
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Host,
|
||||||
|
GameCard,
|
||||||
|
NandSystem,
|
||||||
|
NandUser,
|
||||||
|
SdCard
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Ryujinx.HLE.FileSystem.Content;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -11,6 +12,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
public const string SdCardPath = "sdmc";
|
public const string SdCardPath = "sdmc";
|
||||||
public const string SystemPath = "system";
|
public const string SystemPath = "system";
|
||||||
|
|
||||||
|
public static string SafeNandPath = Path.Combine(NandPath, "safe");
|
||||||
public static string SystemNandPath = Path.Combine(NandPath, "system");
|
public static string SystemNandPath = Path.Combine(NandPath, "system");
|
||||||
public static string UserNandPath = Path.Combine(NandPath, "user");
|
public static string UserNandPath = Path.Combine(NandPath, "user");
|
||||||
|
|
||||||
|
@ -63,9 +65,15 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
return MakeDirAndGetFullPath(SaveHelper.GetSavePath(Save, Context));
|
return MakeDirAndGetFullPath(SaveHelper.GetSavePath(Save, Context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetFullPartitionPath(string PartitionPath)
|
||||||
|
{
|
||||||
|
return MakeDirAndGetFullPath(PartitionPath);
|
||||||
|
}
|
||||||
|
|
||||||
public string SwitchPathToSystemPath(string SwitchPath)
|
public string SwitchPathToSystemPath(string SwitchPath)
|
||||||
{
|
{
|
||||||
string[] Parts = SwitchPath.Split(":");
|
string[] Parts = SwitchPath.Split(":");
|
||||||
|
|
||||||
if (Parts.Length != 2)
|
if (Parts.Length != 2)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
@ -76,10 +84,12 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
public string SystemPathToSwitchPath(string SystemPath)
|
public string SystemPathToSwitchPath(string SystemPath)
|
||||||
{
|
{
|
||||||
string BaseSystemPath = GetBasePath() + Path.DirectorySeparatorChar;
|
string BaseSystemPath = GetBasePath() + Path.DirectorySeparatorChar;
|
||||||
|
|
||||||
if (SystemPath.StartsWith(BaseSystemPath))
|
if (SystemPath.StartsWith(BaseSystemPath))
|
||||||
{
|
{
|
||||||
string RawPath = SystemPath.Replace(BaseSystemPath, "");
|
string RawPath = SystemPath.Replace(BaseSystemPath, "");
|
||||||
int FirstSeparatorOffset = RawPath.IndexOf(Path.DirectorySeparatorChar);
|
int FirstSeparatorOffset = RawPath.IndexOf(Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
if (FirstSeparatorOffset == -1)
|
if (FirstSeparatorOffset == -1)
|
||||||
{
|
{
|
||||||
return $"{RawPath}:/";
|
return $"{RawPath}:/";
|
||||||
|
@ -87,6 +97,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
|
|
||||||
string BasePath = RawPath.Substring(0, FirstSeparatorOffset);
|
string BasePath = RawPath.Substring(0, FirstSeparatorOffset);
|
||||||
string FileName = RawPath.Substring(FirstSeparatorOffset + 1);
|
string FileName = RawPath.Substring(FirstSeparatorOffset + 1);
|
||||||
|
|
||||||
return $"{BasePath}:/{FileName}";
|
return $"{BasePath}:/{FileName}";
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -94,6 +105,30 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
|
|
||||||
private string MakeDirAndGetFullPath(string Dir)
|
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);
|
string FullPath = Path.Combine(GetBasePath(), Dir);
|
||||||
|
|
||||||
if (!Directory.Exists(FullPath))
|
if (!Directory.Exists(FullPath))
|
||||||
|
|
|
@ -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.Resource;
|
||||||
using Ryujinx.HLE.Utilities;
|
using Ryujinx.HLE.Utilities;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using static Ryujinx.HLE.Utilities.FontUtils;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Font
|
namespace Ryujinx.HLE.HOS.Font
|
||||||
{
|
{
|
||||||
class SharedFontManager
|
class SharedFontManager
|
||||||
{
|
{
|
||||||
private DeviceMemory Memory;
|
private Switch Device;
|
||||||
|
|
||||||
private long PhysicalAddress;
|
private long PhysicalAddress;
|
||||||
|
|
||||||
|
@ -32,21 +37,64 @@ namespace Ryujinx.HLE.HOS.Font
|
||||||
{
|
{
|
||||||
this.PhysicalAddress = PhysicalAddress;
|
this.PhysicalAddress = PhysicalAddress;
|
||||||
|
|
||||||
Memory = Device.Memory;
|
this.Device = Device;
|
||||||
|
|
||||||
FontsPath = Path.Combine(Device.FileSystem.GetSystemPath(), "fonts");
|
FontsPath = Path.Combine(Device.FileSystem.GetSystemPath(), "fonts");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnsureInitialized()
|
public void EnsureInitialized(ContentManager ContentManager)
|
||||||
{
|
{
|
||||||
if (FontData == null)
|
if (FontData == null)
|
||||||
{
|
{
|
||||||
Memory.FillWithZeros(PhysicalAddress, Horizon.FontSize);
|
Device.Memory.FillWithZeros(PhysicalAddress, Horizon.FontSize);
|
||||||
|
|
||||||
uint FontOffset = 0;
|
uint FontOffset = 0;
|
||||||
|
|
||||||
FontInfo CreateFont(string Name)
|
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");
|
string FontFilePath = Path.Combine(FontsPath, Name + ".ttf");
|
||||||
|
|
||||||
if (File.Exists(FontFilePath))
|
if (File.Exists(FontFilePath))
|
||||||
|
@ -63,7 +111,7 @@ namespace Ryujinx.HLE.HOS.Font
|
||||||
|
|
||||||
for (; FontOffset - Start < Data.Length; FontOffset++)
|
for (; FontOffset - Start < Data.Length; FontOffset++)
|
||||||
{
|
{
|
||||||
Memory.WriteByte(PhysicalAddress + FontOffset, Data[FontOffset - Start]);
|
Device.Memory.WriteByte(PhysicalAddress + FontOffset, Data[FontOffset - Start]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Info;
|
return Info;
|
||||||
|
@ -101,20 +149,20 @@ namespace Ryujinx.HLE.HOS.Font
|
||||||
|
|
||||||
int EncryptedSize = EndianSwap.Swap32(Size ^ Key);
|
int EncryptedSize = EndianSwap.Swap32(Size ^ Key);
|
||||||
|
|
||||||
Memory.WriteInt32(Position + 0, DecMagic);
|
Device.Memory.WriteInt32(Position + 0, DecMagic);
|
||||||
Memory.WriteInt32(Position + 4, EncryptedSize);
|
Device.Memory.WriteInt32(Position + 4, EncryptedSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetFontSize(SharedFontType FontType)
|
public int GetFontSize(SharedFontType FontType)
|
||||||
{
|
{
|
||||||
EnsureInitialized();
|
EnsureInitialized(Device.System.ContentManager);
|
||||||
|
|
||||||
return FontData[FontType].Size;
|
return FontData[FontType].Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetSharedMemoryAddressOffset(SharedFontType FontType)
|
public int GetSharedMemoryAddressOffset(SharedFontType FontType)
|
||||||
{
|
{
|
||||||
EnsureInitialized();
|
EnsureInitialized(Device.System.ContentManager);
|
||||||
|
|
||||||
return FontData[FontType].Offset + 8;
|
return FontData[FontType].Offset + 8;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using LibHac;
|
using LibHac;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.FileSystem.Content;
|
||||||
using Ryujinx.HLE.HOS.Font;
|
using Ryujinx.HLE.HOS.Font;
|
||||||
using Ryujinx.HLE.HOS.Kernel;
|
using Ryujinx.HLE.HOS.Kernel;
|
||||||
using Ryujinx.HLE.HOS.SystemState;
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
|
@ -42,6 +43,8 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
internal SharedFontManager Font { get; private set; }
|
internal SharedFontManager Font { get; private set; }
|
||||||
|
|
||||||
|
internal ContentManager ContentManager { get; private set; }
|
||||||
|
|
||||||
internal KEvent VsyncEvent { get; private set; }
|
internal KEvent VsyncEvent { get; private set; }
|
||||||
|
|
||||||
internal Keyset KeySet { get; private set; }
|
internal Keyset KeySet { get; private set; }
|
||||||
|
@ -90,6 +93,8 @@ namespace Ryujinx.HLE.HOS
|
||||||
VsyncEvent = new KEvent(this);
|
VsyncEvent = new KEvent(this);
|
||||||
|
|
||||||
LoadKeySet();
|
LoadKeySet();
|
||||||
|
|
||||||
|
ContentManager = new ContentManager(Device);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadCart(string ExeFsDir, string RomFsFile = null)
|
public void LoadCart(string ExeFsDir, string RomFsFile = null)
|
||||||
|
@ -156,6 +161,8 @@ namespace Ryujinx.HLE.HOS
|
||||||
LoadNso("subsdk*");
|
LoadNso("subsdk*");
|
||||||
LoadNso("sdk");
|
LoadNso("sdk");
|
||||||
|
|
||||||
|
ContentManager.LoadEntries();
|
||||||
|
|
||||||
MainProcess.Run();
|
MainProcess.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +181,8 @@ namespace Ryujinx.HLE.HOS
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContentManager.LoadEntries();
|
||||||
|
|
||||||
LoadNca(MainNca, ControlNca);
|
LoadNca(MainNca, ControlNca);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,6 +421,8 @@ namespace Ryujinx.HLE.HOS
|
||||||
LoadNso("subsdk");
|
LoadNso("subsdk");
|
||||||
LoadNso("sdk");
|
LoadNso("sdk");
|
||||||
|
|
||||||
|
ContentManager.LoadEntries();
|
||||||
|
|
||||||
MainProcess.Run();
|
MainProcess.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,13 +430,13 @@ namespace Ryujinx.HLE.HOS
|
||||||
{
|
{
|
||||||
bool IsNro = Path.GetExtension(FilePath).ToLower() == ".nro";
|
bool IsNro = Path.GetExtension(FilePath).ToLower() == ".nro";
|
||||||
|
|
||||||
string Name = Path.GetFileNameWithoutExtension(FilePath);
|
string Name = Path.GetFileNameWithoutExtension(FilePath);
|
||||||
string SwitchFilePath = Device.FileSystem.SystemPathToSwitchPath(FilePath);
|
string SwitchFilePath = Device.FileSystem.SystemPathToSwitchPath(FilePath);
|
||||||
|
|
||||||
if (IsNro && (SwitchFilePath == null || !SwitchFilePath.StartsWith("sdmc:/")))
|
if (IsNro && (SwitchFilePath == null || !SwitchFilePath.StartsWith("sdmc:/")))
|
||||||
{
|
{
|
||||||
string SwitchPath = $"sdmc:/switch/{Name}{Homebrew.TemporaryNroSuffix}";
|
string SwitchPath = $"sdmc:/switch/{Name}{Homebrew.TemporaryNroSuffix}";
|
||||||
string TempPath = Device.FileSystem.SwitchPathToSystemPath(SwitchPath);
|
string TempPath = Device.FileSystem.SwitchPathToSystemPath(SwitchPath);
|
||||||
|
|
||||||
string SwitchDir = Path.GetDirectoryName(TempPath);
|
string SwitchDir = Path.GetDirectoryName(TempPath);
|
||||||
|
|
||||||
|
@ -449,6 +460,9 @@ namespace Ryujinx.HLE.HOS
|
||||||
}
|
}
|
||||||
|
|
||||||
MainProcess.SetEmptyArgs();
|
MainProcess.SetEmptyArgs();
|
||||||
|
|
||||||
|
ContentManager.LoadEntries();
|
||||||
|
|
||||||
MainProcess.Run(IsNro);
|
MainProcess.Run(IsNro);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
Ryujinx.HLE/HOS/Services/Es/IETicketService.cs
Normal file
23
Ryujinx.HLE/HOS/Services/Es/IETicketService.cs
Normal 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>()
|
||||||
|
{
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
Ryujinx.HLE/HOS/Services/FspSrv/DirectoryEntry.cs
Normal file
21
Ryujinx.HLE/HOS/Services/FspSrv/DirectoryEntry.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
Ryujinx.HLE/HOS/Services/FspSrv/DirectoryEntryType.cs
Normal file
8
Ryujinx.HLE/HOS/Services/FspSrv/DirectoryEntryType.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
|
{
|
||||||
|
public enum DirectoryEntryType
|
||||||
|
{
|
||||||
|
Directory,
|
||||||
|
File
|
||||||
|
}
|
||||||
|
}
|
12
Ryujinx.HLE/HOS/Services/FspSrv/FileSystemType.cs
Normal file
12
Ryujinx.HLE/HOS/Services/FspSrv/FileSystemType.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,5 +5,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
public const int PathDoesNotExist = 1;
|
public const int PathDoesNotExist = 1;
|
||||||
public const int PathAlreadyExists = 2;
|
public const int PathAlreadyExists = 2;
|
||||||
public const int PathAlreadyInUse = 7;
|
public const int PathAlreadyInUse = 7;
|
||||||
|
public const int PartitionNotFound = 1001;
|
||||||
|
public const int InvalidInput = 6001;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Ipc;
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -14,15 +15,17 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
|
|
||||||
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
|
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
|
||||||
|
|
||||||
private List<string> DirectoryEntries;
|
private List<DirectoryEntry> DirectoryEntries;
|
||||||
|
|
||||||
private int CurrentItemIndex;
|
private int CurrentItemIndex;
|
||||||
|
|
||||||
public event EventHandler<EventArgs> Disposed;
|
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>()
|
m_Commands = new Dictionary<int, ServiceProcessRequest>()
|
||||||
{
|
{
|
||||||
|
@ -30,23 +33,25 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
{ 1, GetEntryCount }
|
{ 1, GetEntryCount }
|
||||||
};
|
};
|
||||||
|
|
||||||
this.HostPath = HostPath;
|
this.Provider = Provider;
|
||||||
|
this.DirectoryPath = DirectoryPath;
|
||||||
|
|
||||||
DirectoryEntries = new List<string>();
|
DirectoryEntries = new List<DirectoryEntry>();
|
||||||
|
|
||||||
if ((Flags & 1) != 0)
|
if ((Flags & 1) != 0)
|
||||||
{
|
{
|
||||||
DirectoryEntries.AddRange(Directory.GetDirectories(HostPath));
|
DirectoryEntries.AddRange(Provider.GetDirectories(DirectoryPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((Flags & 2) != 0)
|
if ((Flags & 2) != 0)
|
||||||
{
|
{
|
||||||
DirectoryEntries.AddRange(Directory.GetFiles(HostPath));
|
DirectoryEntries.AddRange(Provider.GetFiles(DirectoryPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentItemIndex = 0;
|
CurrentItemIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read() -> (u64 count, buffer<nn::fssrv::sf::IDirectoryEntry, 6, 0> entries)
|
||||||
public long Read(ServiceCtx Context)
|
public long Read(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
long BufferPosition = Context.Request.ReceiveBuff[0].Position;
|
long BufferPosition = Context.Request.ReceiveBuff[0].Position;
|
||||||
|
@ -68,31 +73,23 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return 0;
|
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)
|
for (int Offset = 0; Offset < 0x300; Offset += 8)
|
||||||
{
|
{
|
||||||
Context.Memory.WriteInt64(Position + Offset, 0);
|
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);
|
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 + 0x300, 0); //Padding?
|
||||||
Context.Memory.WriteInt32(Position + 0x304, Type);
|
Context.Memory.WriteInt32(Position + 0x304, (byte)Entry.EntryType);
|
||||||
Context.Memory.WriteInt64(Position + 0x308, Size);
|
Context.Memory.WriteInt64(Position + 0x308, Entry.Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetEntryCount() -> u64
|
||||||
public long GetEntryCount(ServiceCtx Context)
|
public long GetEntryCount(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
Context.ResponseData.Write((long)DirectoryEntries.Count);
|
Context.ResponseData.Write((long)DirectoryEntries.Count);
|
||||||
|
|
|
@ -32,6 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
this.HostPath = HostPath;
|
this.HostPath = HostPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read(u32, u64 offset, u64 size) -> (u64 out_size, buffer<u8, 0x46, 0> out_buf)
|
||||||
public long Read(ServiceCtx Context)
|
public long Read(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
long Position = Context.Request.ReceiveBuff[0].Position;
|
long Position = Context.Request.ReceiveBuff[0].Position;
|
||||||
|
@ -53,6 +54,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write(u32, u64 offset, u64 size, buffer<u8, 0x45, 0>)
|
||||||
public long Write(ServiceCtx Context)
|
public long Write(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
long Position = Context.Request.SendBuff[0].Position;
|
long Position = Context.Request.SendBuff[0].Position;
|
||||||
|
@ -69,6 +71,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flush()
|
||||||
public long Flush(ServiceCtx Context)
|
public long Flush(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
BaseStream.Flush();
|
BaseStream.Flush();
|
||||||
|
@ -76,6 +79,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSize(u64 size)
|
||||||
public long SetSize(ServiceCtx Context)
|
public long SetSize(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
long Size = Context.RequestData.ReadInt64();
|
long Size = Context.RequestData.ReadInt64();
|
||||||
|
@ -85,6 +89,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSize() -> u64 fileSize
|
||||||
public long GetSize(ServiceCtx Context)
|
public long GetSize(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
Context.ResponseData.Write(BaseStream.Length);
|
Context.ResponseData.Write(BaseStream.Length);
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Ipc;
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using static Ryujinx.HLE.HOS.ErrorCode;
|
using static Ryujinx.HLE.HOS.ErrorCode;
|
||||||
|
using static Ryujinx.HLE.Utilities.StringUtils;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.FspSrv
|
namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
{
|
{
|
||||||
|
@ -18,7 +19,9 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
|
|
||||||
private string Path;
|
private string Path;
|
||||||
|
|
||||||
public IFileSystem(string Path)
|
private IFileSystemProvider Provider;
|
||||||
|
|
||||||
|
public IFileSystem(string Path, IFileSystemProvider Provider)
|
||||||
{
|
{
|
||||||
m_Commands = new Dictionary<int, ServiceProcessRequest>()
|
m_Commands = new Dictionary<int, ServiceProcessRequest>()
|
||||||
{
|
{
|
||||||
|
@ -41,9 +44,11 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
|
|
||||||
OpenPaths = new HashSet<string>();
|
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)
|
public long CreateFile(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
string Name = ReadUtf8String(Context);
|
string Name = ReadUtf8String(Context);
|
||||||
|
@ -51,14 +56,14 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
long Mode = Context.RequestData.ReadInt64();
|
long Mode = Context.RequestData.ReadInt64();
|
||||||
int Size = Context.RequestData.ReadInt32();
|
int Size = Context.RequestData.ReadInt32();
|
||||||
|
|
||||||
string FileName = Context.Device.FileSystem.GetFullPath(Path, Name);
|
string FileName = Provider.GetFullPath(Name);
|
||||||
|
|
||||||
if (FileName == null)
|
if (FileName == null)
|
||||||
{
|
{
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
|
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (File.Exists(FileName))
|
if (Provider.FileExists(FileName))
|
||||||
{
|
{
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
|
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
|
||||||
}
|
}
|
||||||
|
@ -68,21 +73,17 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (FileStream NewFile = File.Create(FileName))
|
return Provider.CreateFile(FileName, Size);
|
||||||
{
|
|
||||||
NewFile.SetLength(Size);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteFile(buffer<bytes<0x301>, 0x19, 0x301> path)
|
||||||
public long DeleteFile(ServiceCtx Context)
|
public long DeleteFile(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
string Name = ReadUtf8String(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);
|
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
|
||||||
}
|
}
|
||||||
|
@ -92,23 +93,22 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
File.Delete(FileName);
|
return Provider.DeleteFile(FileName);
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateDirectory(buffer<bytes<0x301>, 0x19, 0x301> path)
|
||||||
public long CreateDirectory(ServiceCtx Context)
|
public long CreateDirectory(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
string Name = ReadUtf8String(Context);
|
string Name = ReadUtf8String(Context);
|
||||||
|
|
||||||
string DirName = Context.Device.FileSystem.GetFullPath(Path, Name);
|
string DirName = Provider.GetFullPath(Name);
|
||||||
|
|
||||||
if (DirName == null)
|
if (DirName == null)
|
||||||
{
|
{
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
|
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Directory.Exists(DirName))
|
if (Provider.DirectoryExists(DirName))
|
||||||
{
|
{
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
|
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
|
||||||
}
|
}
|
||||||
|
@ -118,26 +118,28 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory.CreateDirectory(DirName);
|
Provider.CreateDirectory(DirName);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteDirectory(buffer<bytes<0x301>, 0x19, 0x301> path)
|
||||||
public long DeleteDirectory(ServiceCtx Context)
|
public long DeleteDirectory(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
return DeleteDirectory(Context, false);
|
return DeleteDirectory(Context, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path)
|
||||||
public long DeleteDirectoryRecursively(ServiceCtx Context)
|
public long DeleteDirectoryRecursively(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
return DeleteDirectory(Context, true);
|
return DeleteDirectory(Context, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long DeleteDirectory(ServiceCtx Context, bool Recursive)
|
private long DeleteDirectory(ServiceCtx Context, bool Recursive)
|
||||||
{
|
{
|
||||||
string Name = ReadUtf8String(Context);
|
string Name = ReadUtf8String(Context);
|
||||||
|
|
||||||
string DirName = Context.Device.FileSystem.GetFullPath(Path, Name);
|
string DirName = Provider.GetFullPath(Name);
|
||||||
|
|
||||||
if (!Directory.Exists(DirName))
|
if (!Directory.Exists(DirName))
|
||||||
{
|
{
|
||||||
|
@ -149,25 +151,26 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory.Delete(DirName, Recursive);
|
Provider.DeleteDirectory(DirName, Recursive);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenameFile(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath)
|
||||||
public long RenameFile(ServiceCtx Context)
|
public long RenameFile(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
string OldName = ReadUtf8String(Context, 0);
|
string OldName = ReadUtf8String(Context, 0);
|
||||||
string NewName = ReadUtf8String(Context, 1);
|
string NewName = ReadUtf8String(Context, 1);
|
||||||
|
|
||||||
string OldFileName = Context.Device.FileSystem.GetFullPath(Path, OldName);
|
string OldFileName = Provider.GetFullPath(OldName);
|
||||||
string NewFileName = Context.Device.FileSystem.GetFullPath(Path, NewName);
|
string NewFileName = Provider.GetFullPath(NewName);
|
||||||
|
|
||||||
if (!File.Exists(OldFileName))
|
if (Provider.FileExists(OldFileName))
|
||||||
{
|
{
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
|
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (File.Exists(NewFileName))
|
if (Provider.FileExists(NewFileName))
|
||||||
{
|
{
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
|
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
|
||||||
}
|
}
|
||||||
|
@ -177,25 +180,24 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
File.Move(OldFileName, NewFileName);
|
return Provider.RenameFile(OldFileName, NewFileName);
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenameDirectory(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath)
|
||||||
public long RenameDirectory(ServiceCtx Context)
|
public long RenameDirectory(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
string OldName = ReadUtf8String(Context, 0);
|
string OldName = ReadUtf8String(Context, 0);
|
||||||
string NewName = ReadUtf8String(Context, 1);
|
string NewName = ReadUtf8String(Context, 1);
|
||||||
|
|
||||||
string OldDirName = Context.Device.FileSystem.GetFullPath(Path, OldName);
|
string OldDirName = Provider.GetFullPath(OldName);
|
||||||
string NewDirName = Context.Device.FileSystem.GetFullPath(Path, NewName);
|
string NewDirName = Provider.GetFullPath(NewName);
|
||||||
|
|
||||||
if (!Directory.Exists(OldDirName))
|
if (!Provider.DirectoryExists(OldDirName))
|
||||||
{
|
{
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
|
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Directory.Exists(NewDirName))
|
if (!Provider.DirectoryExists(NewDirName))
|
||||||
{
|
{
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
|
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
|
||||||
}
|
}
|
||||||
|
@ -205,22 +207,21 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory.Move(OldDirName, NewDirName);
|
return Provider.RenameDirectory(OldDirName, NewDirName);
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetEntryType(buffer<bytes<0x301>, 0x19, 0x301> path) -> nn::fssrv::sf::DirectoryEntryType
|
||||||
public long GetEntryType(ServiceCtx Context)
|
public long GetEntryType(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
string Name = ReadUtf8String(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);
|
Context.ResponseData.Write(1);
|
||||||
}
|
}
|
||||||
else if (Directory.Exists(FileName))
|
else if (Provider.DirectoryExists(FileName))
|
||||||
{
|
{
|
||||||
Context.ResponseData.Write(0);
|
Context.ResponseData.Write(0);
|
||||||
}
|
}
|
||||||
|
@ -234,15 +235,16 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenFile(u32 mode, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IFile> file
|
||||||
public long OpenFile(ServiceCtx Context)
|
public long OpenFile(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
int FilterFlags = Context.RequestData.ReadInt32();
|
int FilterFlags = Context.RequestData.ReadInt32();
|
||||||
|
|
||||||
string Name = ReadUtf8String(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);
|
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
|
||||||
}
|
}
|
||||||
|
@ -252,79 +254,36 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
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;
|
if (Error == 0)
|
||||||
|
|
||||||
lock (OpenPaths)
|
|
||||||
{
|
{
|
||||||
OpenPaths.Add(FileName);
|
FileInterface.Disposed += RemoveFileInUse;
|
||||||
|
|
||||||
|
lock (OpenPaths)
|
||||||
|
{
|
||||||
|
OpenPaths.Add(FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
MakeObject(Context, FileInterface);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
MakeObject(Context, FileInterface);
|
return Error;
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenDirectory(u32 filter_flags, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IDirectory> directory
|
||||||
public long OpenDirectory(ServiceCtx Context)
|
public long OpenDirectory(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
int FilterFlags = Context.RequestData.ReadInt32();
|
int FilterFlags = Context.RequestData.ReadInt32();
|
||||||
|
|
||||||
string Name = ReadUtf8String(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
|
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
|
||||||
}
|
}
|
||||||
|
@ -334,15 +293,75 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
|
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;
|
DirInterface.Disposed -= RemoveDirectoryInUse;
|
||||||
|
|
||||||
OpenPaths.Remove(DirInterface.HostPath);
|
OpenPaths.Remove(DirInterface.DirectoryPath);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
|
using LibHac;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Ipc;
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
using Ryujinx.HLE.Utilities;
|
using Ryujinx.HLE.Utilities;
|
||||||
using System.Collections.Generic;
|
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
|
namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
{
|
{
|
||||||
|
@ -15,28 +22,104 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
{
|
{
|
||||||
m_Commands = new Dictionary<int, ServiceProcessRequest>()
|
m_Commands = new Dictionary<int, ServiceProcessRequest>()
|
||||||
{
|
{
|
||||||
{ 1, SetCurrentProcess },
|
{ 1, Initialize },
|
||||||
|
{ 8, OpenFileSystemWithId },
|
||||||
|
{ 11, OpenBisFileSystem },
|
||||||
{ 18, OpenSdCardFileSystem },
|
{ 18, OpenSdCardFileSystem },
|
||||||
{ 51, OpenSaveDataFileSystem },
|
{ 51, OpenSaveDataFileSystem },
|
||||||
{ 52, OpenSaveDataFileSystemBySystemSaveDataId },
|
{ 52, OpenSaveDataFileSystemBySystemSaveDataId },
|
||||||
{ 200, OpenDataStorageByCurrentProcess },
|
{ 200, OpenDataStorageByCurrentProcess },
|
||||||
|
{ 202, OpenDataStorageByDataId },
|
||||||
{ 203, OpenPatchDataStorageByCurrentProcess },
|
{ 203, OpenPatchDataStorageByCurrentProcess },
|
||||||
{ 1005, GetGlobalAccessLogMode }
|
{ 1005, GetGlobalAccessLogMode }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public long SetCurrentProcess(ServiceCtx Context)
|
// Initialize(u64, pid)
|
||||||
|
public long Initialize(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
return 0;
|
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)
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenSaveDataFileSystem(u8 save_data_space_id, nn::fssrv::sf::SaveStruct saveStruct) -> object<nn::fssrv::sf::IFileSystem> saveDataFs
|
||||||
public long OpenSaveDataFileSystem(ServiceCtx Context)
|
public long OpenSaveDataFileSystem(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
LoadSaveDataFileSystem(Context);
|
LoadSaveDataFileSystem(Context);
|
||||||
|
@ -44,6 +127,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenSaveDataFileSystemBySystemSaveDataId(u8 save_data_space_id, nn::fssrv::sf::SaveStruct saveStruct) -> object<nn::fssrv::sf::IFileSystem> systemSaveDataFs
|
||||||
public long OpenSaveDataFileSystemBySystemSaveDataId(ServiceCtx Context)
|
public long OpenSaveDataFileSystemBySystemSaveDataId(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
LoadSaveDataFileSystem(Context);
|
LoadSaveDataFileSystem(Context);
|
||||||
|
@ -51,6 +135,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenDataStorageByCurrentProcess() -> object<nn::fssrv::sf::IStorage> dataStorage
|
||||||
public long OpenDataStorageByCurrentProcess(ServiceCtx Context)
|
public long OpenDataStorageByCurrentProcess(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
MakeObject(Context, new IStorage(Context.Device.FileSystem.RomFs));
|
MakeObject(Context, new IStorage(Context.Device.FileSystem.RomFs));
|
||||||
|
@ -58,6 +143,63 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return 0;
|
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)
|
public long OpenPatchDataStorageByCurrentProcess(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
MakeObject(Context, new IStorage(Context.Device.FileSystem.RomFs));
|
MakeObject(Context, new IStorage(Context.Device.FileSystem.RomFs));
|
||||||
|
@ -65,6 +207,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetGlobalAccessLogMode() -> u32 logMode
|
||||||
public long GetGlobalAccessLogMode(ServiceCtx Context)
|
public long GetGlobalAccessLogMode(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
Context.ResponseData.Write(0);
|
Context.ResponseData.Write(0);
|
||||||
|
@ -82,13 +225,102 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
Context.RequestData.ReadInt64(),
|
Context.RequestData.ReadInt64(),
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,6 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
this.BaseStream = BaseStream;
|
this.BaseStream = BaseStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read(u64 offset, u64 length) -> buffer<u8, 0x46, 0> buffer
|
||||||
public long Read(ServiceCtx Context)
|
public long Read(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
long Offset = Context.RequestData.ReadInt64();
|
long Offset = Context.RequestData.ReadInt64();
|
||||||
|
|
265
Ryujinx.HLE/HOS/Services/Lr/ILocationResolver.cs
Normal file
265
Ryujinx.HLE/HOS/Services/Lr/ILocationResolver.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
Ryujinx.HLE/HOS/Services/Lr/ILocationResolverManager.cs
Normal file
32
Ryujinx.HLE/HOS/Services/Lr/ILocationResolverManager.cs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
Ryujinx.HLE/HOS/Services/Lr/LrErr.cs
Normal file
8
Ryujinx.HLE/HOS/Services/Lr/LrErr.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Lr
|
||||||
|
{
|
||||||
|
class LrErr
|
||||||
|
{
|
||||||
|
public const int ProgramLocationEntryNotFound = 2;
|
||||||
|
public const int AccessDenied = 5;
|
||||||
|
}
|
||||||
|
}
|
20
Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs
Normal file
20
Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs
Normal 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>()
|
||||||
|
{
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
Ryujinx.HLE/HOS/Services/Ncm/IContentStorage.cs
Normal file
20
Ryujinx.HLE/HOS/Services/Ncm/IContentStorage.cs
Normal 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>()
|
||||||
|
{
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
Normal file
23
Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
Normal 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>()
|
||||||
|
{
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,7 +65,7 @@ namespace Ryujinx.HLE.HOS.Services.Pl
|
||||||
|
|
||||||
public long GetSharedMemoryNativeHandle(ServiceCtx Context)
|
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)
|
if (Context.Process.HandleTable.GenerateHandle(Context.Device.System.FontSharedMem, out int Handle) != KernelResult.Success)
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,12 +4,14 @@ using Ryujinx.HLE.HOS.Services.Apm;
|
||||||
using Ryujinx.HLE.HOS.Services.Aud;
|
using Ryujinx.HLE.HOS.Services.Aud;
|
||||||
using Ryujinx.HLE.HOS.Services.Bsd;
|
using Ryujinx.HLE.HOS.Services.Bsd;
|
||||||
using Ryujinx.HLE.HOS.Services.Caps;
|
using Ryujinx.HLE.HOS.Services.Caps;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Es;
|
||||||
using Ryujinx.HLE.HOS.Services.FspSrv;
|
using Ryujinx.HLE.HOS.Services.FspSrv;
|
||||||
using Ryujinx.HLE.HOS.Services.Hid;
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
using Ryujinx.HLE.HOS.Services.Irs;
|
using Ryujinx.HLE.HOS.Services.Irs;
|
||||||
using Ryujinx.HLE.HOS.Services.Ldr;
|
using Ryujinx.HLE.HOS.Services.Ldr;
|
||||||
using Ryujinx.HLE.HOS.Services.Lm;
|
using Ryujinx.HLE.HOS.Services.Lm;
|
||||||
using Ryujinx.HLE.HOS.Services.Mm;
|
using Ryujinx.HLE.HOS.Services.Mm;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Ncm;
|
||||||
using Ryujinx.HLE.HOS.Services.Nfp;
|
using Ryujinx.HLE.HOS.Services.Nfp;
|
||||||
using Ryujinx.HLE.HOS.Services.Ns;
|
using Ryujinx.HLE.HOS.Services.Ns;
|
||||||
using Ryujinx.HLE.HOS.Services.Nv;
|
using Ryujinx.HLE.HOS.Services.Nv;
|
||||||
|
@ -87,6 +89,9 @@ namespace Ryujinx.HLE.HOS.Services
|
||||||
case "csrng":
|
case "csrng":
|
||||||
return new IRandomInterface();
|
return new IRandomInterface();
|
||||||
|
|
||||||
|
case "es":
|
||||||
|
return new IETicketService();
|
||||||
|
|
||||||
case "friend:a":
|
case "friend:a":
|
||||||
return new Friend.IServiceCreator();
|
return new Friend.IServiceCreator();
|
||||||
|
|
||||||
|
@ -114,12 +119,18 @@ namespace Ryujinx.HLE.HOS.Services
|
||||||
case "mm:u":
|
case "mm:u":
|
||||||
return new IRequest();
|
return new IRequest();
|
||||||
|
|
||||||
|
case "ncm":
|
||||||
|
return new IContentManager();
|
||||||
|
|
||||||
case "nfp:user":
|
case "nfp:user":
|
||||||
return new IUserManager();
|
return new IUserManager();
|
||||||
|
|
||||||
case "nifm:u":
|
case "nifm:u":
|
||||||
return new Nifm.IStaticService();
|
return new Nifm.IStaticService();
|
||||||
|
|
||||||
|
case "ns:am":
|
||||||
|
return new IApplicationManagerInterface();
|
||||||
|
|
||||||
case "ns:ec":
|
case "ns:ec":
|
||||||
return new IServiceGetterInterface();
|
return new IServiceGetterInterface();
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using LibHac;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Set
|
namespace Ryujinx.HLE.HOS.Services.Set
|
||||||
{
|
{
|
||||||
|
@ -18,6 +20,7 @@ namespace Ryujinx.HLE.HOS.Services.Set
|
||||||
{
|
{
|
||||||
m_Commands = new Dictionary<int, ServiceProcessRequest>()
|
m_Commands = new Dictionary<int, ServiceProcessRequest>()
|
||||||
{
|
{
|
||||||
|
{ 3, GetFirmwareVersion },
|
||||||
{ 4, GetFirmwareVersion2 },
|
{ 4, GetFirmwareVersion2 },
|
||||||
{ 23, GetColorSetId },
|
{ 23, GetColorSetId },
|
||||||
{ 24, SetColorSetId },
|
{ 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)
|
public static long GetFirmwareVersion2(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
long ReplyPos = Context.Request.RecvListBuff[0].Position;
|
long ReplyPos = Context.Request.RecvListBuff[0].Position;
|
||||||
long ReplySize = Context.Request.RecvListBuff[0].Size;
|
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 MajorFWVersion = 0x03;
|
||||||
const byte MinorFWVersion = 0x00;
|
const byte MinorFWVersion = 0x00;
|
||||||
const byte MicroFWVersion = 0x00;
|
const byte MicroFWVersion = 0x00;
|
||||||
|
@ -74,6 +93,7 @@ namespace Ryujinx.HLE.HOS.Services.Set
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetColorSetId() -> i32
|
||||||
public static long GetColorSetId(ServiceCtx Context)
|
public static long GetColorSetId(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
Context.ResponseData.Write((int)Context.Device.System.State.ThemeColor);
|
Context.ResponseData.Write((int)Context.Device.System.State.ThemeColor);
|
||||||
|
@ -81,6 +101,7 @@ namespace Ryujinx.HLE.HOS.Services.Set
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetColorSetId() -> i32
|
||||||
public static long SetColorSetId(ServiceCtx Context)
|
public static long SetColorSetId(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
int ColorSetId = Context.RequestData.ReadInt32();
|
int ColorSetId = Context.RequestData.ReadInt32();
|
||||||
|
@ -90,6 +111,7 @@ namespace Ryujinx.HLE.HOS.Services.Set
|
||||||
return 0;
|
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)
|
public static long GetSettingsItemValue(ServiceCtx Context)
|
||||||
{
|
{
|
||||||
long ClassPos = Context.Request.PtrBuff[0].Position;
|
long ClassPos = Context.Request.PtrBuff[0].Position;
|
||||||
|
@ -148,5 +170,44 @@ namespace Ryujinx.HLE.HOS.Services.Set
|
||||||
|
|
||||||
return 0;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,8 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||||
|
|
||||||
public ColorSet ThemeColor { get; set; }
|
public ColorSet ThemeColor { get; set; }
|
||||||
|
|
||||||
|
public bool InstallContents { get; set; }
|
||||||
|
|
||||||
private ConcurrentDictionary<string, UserProfile> Profiles;
|
private ConcurrentDictionary<string, UserProfile> Profiles;
|
||||||
|
|
||||||
internal UserProfile LastOpenUser { get; private set; }
|
internal UserProfile LastOpenUser { get; private set; }
|
||||||
|
|
37
Ryujinx.HLE/Utilities/FontUtils.cs
Normal file
37
Ryujinx.HLE/Utilities/FontUtils.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using Ryujinx.HLE.HOS;
|
||||||
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
@ -47,5 +49,28 @@ namespace Ryujinx.HLE.Utilities
|
||||||
|
|
||||||
return Output;
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue