diff options
Diffstat (limited to 'Ryujinx.HLE/FileSystem/VirtualFileSystem.cs')
| -rw-r--r-- | Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | 615 |
1 files changed, 0 insertions, 615 deletions
diff --git a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs deleted file mode 100644 index 1b3968ea..00000000 --- a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ /dev/null @@ -1,615 +0,0 @@ -using LibHac; -using LibHac.Common; -using LibHac.Common.Keys; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.Fs.Shim; -using LibHac.FsSrv; -using LibHac.FsSystem; -using LibHac.Ncm; -using LibHac.Spl; -using LibHac.Tools.Es; -using LibHac.Tools.Fs; -using LibHac.Tools.FsSystem; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; -using Ryujinx.HLE.HOS; -using System; -using System.Buffers.Text; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; -using Path = System.IO.Path; -using RightsId = LibHac.Fs.RightsId; - -namespace Ryujinx.HLE.FileSystem -{ - public class VirtualFileSystem : IDisposable - { - public static string SafeNandPath = Path.Combine(AppDataManager.DefaultNandDir, "safe"); - public static string SystemNandPath = Path.Combine(AppDataManager.DefaultNandDir, "system"); - public static string UserNandPath = Path.Combine(AppDataManager.DefaultNandDir, "user"); - - public KeySet KeySet { get; private set; } - public EmulatedGameCard GameCard { get; private set; } - public EmulatedSdCard SdCard { get; private set; } - public ModLoader ModLoader { get; private set; } - - private readonly ConcurrentDictionary<ulong, Stream> _romFsByPid; - - private static bool _isInitialized = false; - - public static VirtualFileSystem CreateInstance() - { - if (_isInitialized) - { - throw new InvalidOperationException("VirtualFileSystem can only be instantiated once!"); - } - - _isInitialized = true; - - return new VirtualFileSystem(); - } - - private VirtualFileSystem() - { - ReloadKeySet(); - ModLoader = new ModLoader(); // Should only be created once - _romFsByPid = new ConcurrentDictionary<ulong, Stream>(); - } - - public void LoadRomFs(ulong pid, string fileName) - { - var romfsStream = new FileStream(fileName, FileMode.Open, FileAccess.Read); - - _romFsByPid.AddOrUpdate(pid, romfsStream, (pid, oldStream) => - { - oldStream.Close(); - - return romfsStream; - }); - } - - public void SetRomFs(ulong pid, Stream romfsStream) - { - _romFsByPid.AddOrUpdate(pid, romfsStream, (pid, oldStream) => - { - oldStream.Close(); - - return romfsStream; - }); - } - - public Stream GetRomFs(ulong pid) - { - return _romFsByPid[pid]; - } - - public string GetFullPath(string basePath, string fileName) - { - if (fileName.StartsWith("//")) - { - fileName = fileName.Substring(2); - } - else if (fileName.StartsWith('/')) - { - fileName = fileName.Substring(1); - } - else - { - return null; - } - - string fullPath = Path.GetFullPath(Path.Combine(basePath, fileName)); - - if (!fullPath.StartsWith(AppDataManager.BaseDirPath)) - { - return null; - } - - return fullPath; - } - - internal string GetSdCardPath() => MakeFullPath(AppDataManager.DefaultSdcardDir); - public string GetNandPath() => MakeFullPath(AppDataManager.DefaultNandDir); - - public string SwitchPathToSystemPath(string switchPath) - { - string[] parts = switchPath.Split(":"); - - if (parts.Length != 2) - { - return null; - } - - return GetFullPath(MakeFullPath(parts[0]), parts[1]); - } - - public string SystemPathToSwitchPath(string systemPath) - { - string baseSystemPath = AppDataManager.BaseDirPath + Path.DirectorySeparatorChar; - - if (systemPath.StartsWith(baseSystemPath)) - { - string rawPath = systemPath.Replace(baseSystemPath, ""); - int firstSeparatorOffset = rawPath.IndexOf(Path.DirectorySeparatorChar); - - if (firstSeparatorOffset == -1) - { - return $"{rawPath}:/"; - } - - var basePath = rawPath.AsSpan(0, firstSeparatorOffset); - var fileName = rawPath.AsSpan(firstSeparatorOffset + 1); - - return $"{basePath}:/{fileName}"; - } - - return null; - } - - private string MakeFullPath(string path, bool isDirectory = true) - { - // Handles Common Switch Content Paths - switch (path) - { - case ContentPath.SdCard: - path = AppDataManager.DefaultSdcardDir; - break; - case ContentPath.User: - path = UserNandPath; - break; - case ContentPath.System: - path = SystemNandPath; - break; - case ContentPath.SdCardContent: - path = Path.Combine(AppDataManager.DefaultSdcardDir, "Nintendo", "Contents"); - break; - case ContentPath.UserContent: - path = Path.Combine(UserNandPath, "Contents"); - break; - case ContentPath.SystemContent: - path = Path.Combine(SystemNandPath, "Contents"); - break; - } - - string fullPath = Path.Combine(AppDataManager.BaseDirPath, path); - - if (isDirectory && !Directory.Exists(fullPath)) - { - Directory.CreateDirectory(fullPath); - } - - return fullPath; - } - - public void InitializeFsServer(LibHac.Horizon horizon, out HorizonClient fsServerClient) - { - LocalFileSystem serverBaseFs = new LocalFileSystem(AppDataManager.BaseDirPath); - - fsServerClient = horizon.CreatePrivilegedHorizonClient(); - var fsServer = new FileSystemServer(fsServerClient); - - RandomDataGenerator randomGenerator = Random.Shared.NextBytes; - - DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator); - - // Use our own encrypted fs creator that doesn't actually do any encryption - fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(); - - GameCard = fsServerObjects.GameCard; - SdCard = fsServerObjects.SdCard; - - SdCard.SetSdCardInsertionStatus(true); - - var fsServerConfig = new FileSystemServerConfig - { - DeviceOperator = fsServerObjects.DeviceOperator, - ExternalKeySet = KeySet.ExternalKeySet, - FsCreators = fsServerObjects.FsCreators, - RandomGenerator = randomGenerator - }; - - FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig); - } - - public void ReloadKeySet() - { - KeySet ??= KeySet.CreateDefaultKeySet(); - - string keyFile = null; - string titleKeyFile = null; - string consoleKeyFile = null; - - if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile) - { - LoadSetAtPath(AppDataManager.KeysDirPathUser); - } - - LoadSetAtPath(AppDataManager.KeysDirPath); - - void LoadSetAtPath(string basePath) - { - string localKeyFile = Path.Combine(basePath, "prod.keys"); - string localTitleKeyFile = Path.Combine(basePath, "title.keys"); - string localConsoleKeyFile = Path.Combine(basePath, "console.keys"); - - if (File.Exists(localKeyFile)) - { - keyFile = localKeyFile; - } - - if (File.Exists(localTitleKeyFile)) - { - titleKeyFile = localTitleKeyFile; - } - - if (File.Exists(localConsoleKeyFile)) - { - consoleKeyFile = localConsoleKeyFile; - } - } - - ExternalKeyReader.ReadKeyFile(KeySet, keyFile, titleKeyFile, consoleKeyFile, null); - } - - public void ImportTickets(IFileSystem fs) - { - foreach (DirectoryEntryEx ticketEntry in fs.EnumerateEntries("/", "*.tik")) - { - using var ticketFile = new UniqueRef<IFile>(); - - Result result = fs.OpenFile(ref ticketFile.Ref, ticketEntry.FullPath.ToU8Span(), OpenMode.Read); - - if (result.IsSuccess()) - { - Ticket ticket = new(ticketFile.Get.AsStream()); - var titleKey = ticket.GetTitleKey(KeySet); - - if (titleKey != null) - { - KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(titleKey)); - } - } - } - } - - // Save data created before we supported extra data in directory save data will not work properly if - // given empty extra data. Luckily some of that extra data can be created using the data from the - // save data indexer, which should be enough to check access permissions for user saves. - // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened. - // Consider removing this at some point in the future when we don't need to worry about old saves. - public static Result FixExtraData(HorizonClient hos) - { - Result rc = GetSystemSaveList(hos, out List<ulong> systemSaveIds); - if (rc.IsFailure()) return rc; - - rc = FixUnindexedSystemSaves(hos, systemSaveIds); - if (rc.IsFailure()) return rc; - - rc = FixExtraDataInSpaceId(hos, SaveDataSpaceId.System); - if (rc.IsFailure()) return rc; - - rc = FixExtraDataInSpaceId(hos, SaveDataSpaceId.User); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - private static Result FixExtraDataInSpaceId(HorizonClient hos, SaveDataSpaceId spaceId) - { - Span<SaveDataInfo> info = stackalloc SaveDataInfo[8]; - - using var iterator = new UniqueRef<SaveDataIterator>(); - - Result rc = hos.Fs.OpenSaveDataIterator(ref iterator.Ref, spaceId); - if (rc.IsFailure()) return rc; - - while (true) - { - rc = iterator.Get.ReadSaveDataInfo(out long count, info); - if (rc.IsFailure()) return rc; - - if (count == 0) - return Result.Success; - - for (int i = 0; i < count; i++) - { - rc = FixExtraData(out bool wasFixNeeded, hos, in info[i]); - - if (ResultFs.TargetNotFound.Includes(rc)) - { - // If the save wasn't found, try to create the directory for its save data ID - rc = CreateSaveDataDirectory(hos, in info[i]); - - if (rc.IsFailure()) - { - Logger.Warning?.Print(LogClass.Application, $"Error {rc.ToStringWithName()} when creating save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space"); - - // Don't bother fixing the extra data if we couldn't create the directory - continue; - } - - Logger.Info?.Print(LogClass.Application, $"Recreated directory for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space"); - - // Try to fix the extra data in the new directory - rc = FixExtraData(out wasFixNeeded, hos, in info[i]); - } - - if (rc.IsFailure()) - { - Logger.Warning?.Print(LogClass.Application, $"Error {rc.ToStringWithName()} when fixing extra data for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space"); - } - else if (wasFixNeeded) - { - Logger.Info?.Print(LogClass.Application, $"Fixed extra data for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space"); - } - } - } - } - - private static Result CreateSaveDataDirectory(HorizonClient hos, in SaveDataInfo info) - { - if (info.SpaceId != SaveDataSpaceId.User && info.SpaceId != SaveDataSpaceId.System) - return Result.Success; - - const string mountName = "SaveDir"; - var mountNameU8 = mountName.ToU8Span(); - - BisPartitionId partitionId = info.SpaceId switch - { - SaveDataSpaceId.System => BisPartitionId.System, - SaveDataSpaceId.User => BisPartitionId.User, - _ => throw new ArgumentOutOfRangeException() - }; - - Result rc = hos.Fs.MountBis(mountNameU8, partitionId); - if (rc.IsFailure()) return rc; - try - { - var path = $"{mountName}:/save/{info.SaveDataId:x16}".ToU8Span(); - - rc = hos.Fs.GetEntryType(out _, path); - - if (ResultFs.PathNotFound.Includes(rc)) - { - rc = hos.Fs.CreateDirectory(path); - } - - return rc; - } - finally - { - hos.Fs.Unmount(mountNameU8); - } - } - - // Gets a list of all the save data files or directories in the system partition. - private static Result GetSystemSaveList(HorizonClient hos, out List<ulong> list) - { - list = null; - - var mountName = "system".ToU8Span(); - DirectoryHandle handle = default; - List<ulong> localList = new List<ulong>(); - - try - { - Result rc = hos.Fs.MountBis(mountName, BisPartitionId.System); - if (rc.IsFailure()) return rc; - - rc = hos.Fs.OpenDirectory(out handle, "system:/save".ToU8Span(), OpenDirectoryMode.All); - if (rc.IsFailure()) return rc; - - DirectoryEntry entry = new DirectoryEntry(); - - while (true) - { - rc = hos.Fs.ReadDirectory(out long readCount, SpanHelpers.AsSpan(ref entry), handle); - if (rc.IsFailure()) return rc; - - if (readCount == 0) - break; - - if (Utf8Parser.TryParse(entry.Name, out ulong saveDataId, out int bytesRead, 'x') && - bytesRead == 16 && (long)saveDataId < 0) - { - localList.Add(saveDataId); - } - } - - list = localList; - - return Result.Success; - } - finally - { - if (handle.IsValid) - { - hos.Fs.CloseDirectory(handle); - } - - if (hos.Fs.IsMounted(mountName)) - { - hos.Fs.Unmount(mountName); - } - } - } - - // Adds system save data that isn't in the save data indexer to the indexer and creates extra data for it. - // Only save data IDs added to SystemExtraDataFixInfo will be fixed. - private static Result FixUnindexedSystemSaves(HorizonClient hos, List<ulong> existingSaveIds) - { - foreach (var fixInfo in SystemExtraDataFixInfo) - { - if (!existingSaveIds.Contains(fixInfo.StaticSaveDataId)) - { - continue; - } - - Result rc = FixSystemExtraData(out bool wasFixNeeded, hos, in fixInfo); - - if (rc.IsFailure()) - { - Logger.Warning?.Print(LogClass.Application, - $"Error {rc.ToStringWithName()} when fixing extra data for system save data 0x{fixInfo.StaticSaveDataId:x}"); - } - else if (wasFixNeeded) - { - Logger.Info?.Print(LogClass.Application, - $"Tried to rebuild extra data for system save data 0x{fixInfo.StaticSaveDataId:x}"); - } - } - - return Result.Success; - } - - private static Result FixSystemExtraData(out bool wasFixNeeded, HorizonClient hos, in ExtraDataFixInfo info) - { - wasFixNeeded = true; - - Result rc = hos.Fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, info.StaticSaveDataId); - if (!rc.IsSuccess()) - { - if (!ResultFs.TargetNotFound.Includes(rc)) - return rc; - - // We'll reach this point only if the save data directory exists but it's not in the save data indexer. - // Creating the save will add it to the indexer while leaving its existing contents intact. - return hos.Fs.CreateSystemSaveData(info.StaticSaveDataId, UserId.InvalidId, info.OwnerId, info.DataSize, - info.JournalSize, info.Flags); - } - - if (extraData.Attribute.StaticSaveDataId != 0 && extraData.OwnerId != 0) - { - wasFixNeeded = false; - return Result.Success; - } - - extraData = new SaveDataExtraData - { - Attribute = { StaticSaveDataId = info.StaticSaveDataId }, - OwnerId = info.OwnerId, - Flags = info.Flags, - DataSize = info.DataSize, - JournalSize = info.JournalSize - }; - - // Make a mask for writing the entire extra data - Unsafe.SkipInit(out SaveDataExtraData extraDataMask); - SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); - - return hos.Fs.Impl.WriteSaveDataFileSystemExtraData(SaveDataSpaceId.System, info.StaticSaveDataId, - in extraData, in extraDataMask); - } - - private static Result FixExtraData(out bool wasFixNeeded, HorizonClient hos, in SaveDataInfo info) - { - wasFixNeeded = true; - - Result rc = hos.Fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, info.SpaceId, - info.SaveDataId); - if (rc.IsFailure()) return rc; - - // The extra data should have program ID or static save data ID set if it's valid. - // We only try to fix the extra data if the info from the save data indexer has a program ID or static save data ID. - bool canFixByProgramId = extraData.Attribute.ProgramId == ProgramId.InvalidId && - info.ProgramId != ProgramId.InvalidId; - - bool canFixBySaveDataId = extraData.Attribute.StaticSaveDataId == 0 && info.StaticSaveDataId != 0; - - bool hasEmptyOwnerId = extraData.OwnerId == 0 && info.Type != SaveDataType.System; - - if (!canFixByProgramId && !canFixBySaveDataId && !hasEmptyOwnerId) - { - wasFixNeeded = false; - return Result.Success; - } - - // The save data attribute struct can be completely created from the save data info. - extraData.Attribute.ProgramId = info.ProgramId; - extraData.Attribute.UserId = info.UserId; - extraData.Attribute.StaticSaveDataId = info.StaticSaveDataId; - extraData.Attribute.Type = info.Type; - extraData.Attribute.Rank = info.Rank; - extraData.Attribute.Index = info.Index; - - // The rest of the extra data can't be created from the save data info. - // On user saves the owner ID will almost certainly be the same as the program ID. - if (info.Type != SaveDataType.System) - { - extraData.OwnerId = info.ProgramId.Value; - } - else - { - // Try to match the system save with one of the known saves - foreach (ExtraDataFixInfo fixInfo in SystemExtraDataFixInfo) - { - if (extraData.Attribute.StaticSaveDataId == fixInfo.StaticSaveDataId) - { - extraData.OwnerId = fixInfo.OwnerId; - extraData.Flags = fixInfo.Flags; - extraData.DataSize = fixInfo.DataSize; - extraData.JournalSize = fixInfo.JournalSize; - - break; - } - } - } - - // Make a mask for writing the entire extra data - Unsafe.SkipInit(out SaveDataExtraData extraDataMask); - SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); - - return hos.Fs.Impl.WriteSaveDataFileSystemExtraData(info.SpaceId, info.SaveDataId, in extraData, in extraDataMask); - } - - struct ExtraDataFixInfo - { - public ulong StaticSaveDataId; - public ulong OwnerId; - public SaveDataFlags Flags; - public long DataSize; - public long JournalSize; - } - - private static readonly ExtraDataFixInfo[] SystemExtraDataFixInfo = - { - new ExtraDataFixInfo() - { - StaticSaveDataId = 0x8000000000000030, - OwnerId = 0x010000000000001F, - Flags = SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData, - DataSize = 0x10000, - JournalSize = 0x10000 - }, - new ExtraDataFixInfo() - { - StaticSaveDataId = 0x8000000000001040, - OwnerId = 0x0100000000001009, - Flags = SaveDataFlags.None, - DataSize = 0xC000, - JournalSize = 0xC000 - } - }; - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - foreach (var stream in _romFsByPid.Values) - { - stream.Close(); - } - - _romFsByPid.Clear(); - } - } - } -}
\ No newline at end of file |
