Ryujinx/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs

311 lines
9.6 KiB
C#
Raw Normal View History

using LibHac;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsService;
using LibHac.FsSystem;
using LibHac.Spl;
using Ryujinx.HLE.FileSystem.Content;
using Ryujinx.HLE.HOS;
2018-02-05 00:08:20 +01:00
using System;
using System.IO;
namespace Ryujinx.HLE.FileSystem
2018-02-05 00:08:20 +01:00
{
Added GUI to Ryujinx (#695) * Added GUI to Ryujinx * Updated to use Glade Also added scrollbar and default dark theme * Added support for loading icon from .nro files and cleaned up the code a bit * Added General Settings Menu (read-only for now) and moved some functionality from MainMenu.cs to ApplicationLibrary.cs * Added custom GUI theme support and changed the defualt theme to one I just wrote * Added GTK to process path, fixed a bug and minor edits * some more edits and a bug fix * general settings menu is now fully functional. also fixed the bug where ryujinx crashes when it trys to load an invalid gamedir * big rewrite * aesthetic changes to General Settings menu * Added Control Settings one day done feature :P * minor changes * 1st wave of changes * 2nd wave of changes * 3rd wave of changes * Cleanup settings ui * minor edits * new about window added, still needs styling * added spin button for new option and tooltips to settings * Game icons and names are now shown in the games list * add nuget package which contains gtk dependencies * requested changes have been changed * put CreateGameWindow on a new thread and stopped destroying the main menu when a game loads * fixed bug that allowed a user to attempt to load multiple games at a time which causes a crash * Added LastPlayed and TimePlayed columns to the game list * Did some testing and fixed some bugs Im not happy with one of the fixes so i will do it properly an upcoming commit * did some more bug testing and fixed another 2 bugs * caught an exception when ryujinx tries to load non-homebrew as homebrew * Large changes Rewrote ApplicationLibrary.cs (added comments too) so any devs reading it wont get eye cancer, also its probably more efficient now. Added 2 new columns (Developer name and application version) to the game list and wrote the logic for it. Ryujinx now loads NRO's TitleName and TitleID from the NACP file instead of the default NPDM. I also killed a lot of bugs * Moved Files moved ApplicationLibrary.cs to Ryujinx.HLE as that is a better place for it. Moved contents of GUI folder to Ui folder and changed the namespaces of the gui files from Ryujinx to Ryujinx.Ui * Added 'Open Ryujinx Folder' button to the file menu and did some small fixes * New features * updated nuget package with missing dlls and changed emmauss' requested changes * fixed some minor issues * all requested changes marked as resolved have been changed * gdkchan's requested changes * fixed an issue with settings window getting chopped on small res * fixed 2 problems caused by rebase * changed the default theme * applied Thog's patch to fix issue on linux * fixed issue caused by rebase * added update check button that runs ryujinx-updater * reads version info from installer and displays it in about menu * changes completed * requested changes changed * fixed issue with default theme * fixed a bug and completed requested changes * added more tooltips and changed some text
2019-09-02 18:03:57 +02:00
public class VirtualFileSystem : IDisposable
2018-02-05 00:08:20 +01:00
{
public const string BasePath = "Ryujinx";
public const string NandPath = "bis";
public const string SdCardPath = "sdcard";
public const string SystemPath = "system";
public static string SafeNandPath = Path.Combine(NandPath, "safe");
public static string SystemNandPath = Path.Combine(NandPath, "system");
public static string UserNandPath = Path.Combine(NandPath, "user");
2018-02-05 00:08:20 +01:00
private static bool _isInitialized = false;
public Keyset KeySet { get; private set; }
public FileSystemServer FsServer { get; private set; }
public FileSystemClient FsClient { get; private set; }
public EmulatedGameCard GameCard { get; private set; }
public EmulatedSdCard SdCard { get; private set; }
private VirtualFileSystem()
{
Reload();
}
2018-02-05 00:08:20 +01:00
public Stream RomFs { get; private set; }
public void LoadRomFs(string fileName)
2018-02-05 00:08:20 +01:00
{
RomFs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
2018-02-05 00:08:20 +01:00
}
public void SetRomFs(Stream romfsStream)
{
RomFs?.Close();
RomFs = romfsStream;
}
public string GetFullPath(string basePath, string fileName)
2018-02-05 00:08:20 +01:00
{
if (fileName.StartsWith("//"))
{
fileName = fileName.Substring(2);
}
else if (fileName.StartsWith('/'))
2018-02-05 00:08:20 +01:00
{
fileName = fileName.Substring(1);
2018-02-05 00:08:20 +01:00
}
else
{
return null;
}
2018-02-05 00:08:20 +01:00
string fullPath = Path.GetFullPath(Path.Combine(basePath, fileName));
2018-02-05 00:08:20 +01:00
if (!fullPath.StartsWith(GetBasePath()))
2018-02-05 00:08:20 +01:00
{
return null;
}
return fullPath;
2018-02-05 00:08:20 +01:00
}
public string GetSdCardPath() => MakeFullPath(SdCardPath);
public string GetNandPath() => MakeFullPath(NandPath);
public string GetSystemPath() => MakeFullPath(SystemPath);
internal string GetSavePath(ServiceCtx context, SaveInfo saveInfo, bool isDirectory = true)
{
string saveUserPath = "";
string baseSavePath = NandPath;
ulong currentTitleId = saveInfo.TitleId;
switch (saveInfo.SaveSpaceId)
{
case SaveSpaceId.NandUser: baseSavePath = UserNandPath; break;
case SaveSpaceId.NandSystem: baseSavePath = SystemNandPath; break;
case SaveSpaceId.SdCard: baseSavePath = Path.Combine(SdCardPath, "Nintendo"); break;
}
baseSavePath = Path.Combine(baseSavePath, "save");
if (saveInfo.TitleId == 0 && saveInfo.SaveDataType == SaveDataType.SaveData)
{
currentTitleId = context.Process.TitleId;
}
if (saveInfo.SaveSpaceId == SaveSpaceId.NandUser)
{
saveUserPath = saveInfo.UserId.IsNull ? "savecommon" : saveInfo.UserId.ToString();
}
string savePath = Path.Combine(baseSavePath,
saveInfo.SaveId.ToString("x16"),
saveUserPath,
saveInfo.SaveDataType == SaveDataType.SaveData ? currentTitleId.ToString("x16") : string.Empty);
return MakeFullPath(savePath, isDirectory);
}
public string GetFullPartitionPath(string partitionPath)
{
return MakeFullPath(partitionPath);
}
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 = GetBasePath() + Path.DirectorySeparatorChar;
if (systemPath.StartsWith(baseSystemPath))
{
string rawPath = systemPath.Replace(baseSystemPath, "");
int firstSeparatorOffset = rawPath.IndexOf(Path.DirectorySeparatorChar);
if (firstSeparatorOffset == -1)
{
return $"{rawPath}:/";
}
string basePath = rawPath.Substring(0, firstSeparatorOffset);
string fileName = rawPath.Substring(firstSeparatorOffset + 1);
return $"{basePath}:/{fileName}";
}
return null;
}
private string MakeFullPath(string path, bool isDirectory = true)
2018-02-05 00:08:20 +01:00
{
// Handles Common Switch Content Paths
switch (path)
{
case ContentPath.SdCard:
case "@Sdcard":
path = SdCardPath;
break;
case ContentPath.User:
path = UserNandPath;
break;
case ContentPath.System:
path = SystemNandPath;
break;
case ContentPath.SdCardContent:
path = Path.Combine(SdCardPath, "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(GetBasePath(), path);
2018-02-05 00:08:20 +01:00
if (isDirectory)
2018-02-05 00:08:20 +01:00
{
if (!Directory.Exists(fullPath))
{
Directory.CreateDirectory(fullPath);
}
2018-02-05 00:08:20 +01:00
}
return fullPath;
2018-02-05 00:08:20 +01:00
}
public DriveInfo GetDrive()
{
return new DriveInfo(Path.GetPathRoot(GetBasePath()));
}
public string GetBasePath()
2018-02-05 00:08:20 +01:00
{
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
return Path.Combine(appDataPath, BasePath);
2018-02-05 00:08:20 +01:00
}
public void Reload()
{
ReloadKeySet();
LocalFileSystem serverBaseFs = new LocalFileSystem(GetBasePath());
DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet);
GameCard = fsServerObjects.GameCard;
SdCard = fsServerObjects.SdCard;
2020-03-09 23:34:35 +01:00
SdCard.SetSdCardInsertionStatus(true);
FileSystemServerConfig fsServerConfig = new FileSystemServerConfig
{
FsCreators = fsServerObjects.FsCreators,
DeviceOperator = fsServerObjects.DeviceOperator,
ExternalKeySet = KeySet.ExternalKeySet
};
FsServer = new FileSystemServer(fsServerConfig);
FsClient = FsServer.CreateFileSystemClient();
}
private void ReloadKeySet()
{
string keyFile = null;
string titleKeyFile = null;
string consoleKeyFile = null;
string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
LoadSetAtPath(Path.Combine(home, ".switch"));
LoadSetAtPath(GetSystemPath());
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;
}
}
KeySet = ExternalKeyReader.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile);
}
public void ImportTickets(IFileSystem fs)
{
foreach (DirectoryEntryEx ticketEntry in fs.EnumerateEntries("/", "*.tik"))
{
Result result = fs.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
if (result.IsSuccess())
{
Ticket ticket = new Ticket(ticketFile.AsStream());
KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet)));
}
}
}
public void Unload()
{
RomFs?.Dispose();
}
2018-02-05 00:08:20 +01:00
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
2018-02-05 00:08:20 +01:00
{
Unload();
2018-02-05 00:08:20 +01:00
}
}
public static VirtualFileSystem CreateInstance()
{
if (_isInitialized)
{
throw new InvalidOperationException($"VirtualFileSystem can only be instantiated once!");
}
_isInitialized = true;
return new VirtualFileSystem();
}
2018-02-05 00:08:20 +01:00
}
}