Keep the GUI alive when closing a game (#888)

* Keep the GUI alive when closing a game

Make HLE.Switch init when starting a game and dispose it when closing
the GlScreen.

This also make HLE in charge of disposing the audio and gpu backend.

* Address Ac_k's comments

* Make sure to dispose the Discord module and use GTK quit method

Also update Discord Precense when closing a game.

* Make sure to dispose MainWindow

* Address gdk's comments
This commit is contained in:
Thog 2020-01-21 23:23:11 +01:00 committed by Ac_K
parent b4b2b8b162
commit d6b9babe1d
16 changed files with 284 additions and 215 deletions

View file

@ -116,6 +116,7 @@ namespace Ryujinx.Graphics.Gpu
Methods.ShaderCache.Dispose(); Methods.ShaderCache.Dispose();
Methods.BufferManager.Dispose(); Methods.BufferManager.Dispose();
Methods.TextureManager.Dispose(); Methods.TextureManager.Dispose();
Renderer.Dispose();
} }
} }
} }

View file

@ -14,7 +14,7 @@ using System.Linq;
namespace Ryujinx.HLE.FileSystem.Content namespace Ryujinx.HLE.FileSystem.Content
{ {
internal class ContentManager public class ContentManager
{ {
private const ulong SystemVersionTitleId = 0x0100000000000809; private const ulong SystemVersionTitleId = 0x0100000000000809;
private const ulong SystemUpdateTitleId = 0x0100000000000816; private const ulong SystemUpdateTitleId = 0x0100000000000816;
@ -26,11 +26,11 @@ namespace Ryujinx.HLE.FileSystem.Content
private SortedDictionary<(ulong titleId, NcaContentType type), string> _contentDictionary; private SortedDictionary<(ulong titleId, NcaContentType type), string> _contentDictionary;
private Switch _device; private VirtualFileSystem _virtualFileSystem;
private readonly object _lock = new object(); private readonly object _lock = new object();
public ContentManager(Switch device) public ContentManager(VirtualFileSystem virtualFileSystem)
{ {
_contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>(); _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>();
_locationEntries = new Dictionary<StorageId, LinkedList<LocationEntry>>(); _locationEntries = new Dictionary<StorageId, LinkedList<LocationEntry>>();
@ -55,10 +55,10 @@ namespace Ryujinx.HLE.FileSystem.Content
{ "FontNintendoExtended", "nintendo_ext_003.bfttf" } { "FontNintendoExtended", "nintendo_ext_003.bfttf" }
}; };
_device = device; _virtualFileSystem = virtualFileSystem;
} }
public void LoadEntries(bool ignoreMissingFonts = false) public void LoadEntries(Switch device = null)
{ {
lock (_lock) lock (_lock)
{ {
@ -74,7 +74,7 @@ namespace Ryujinx.HLE.FileSystem.Content
try try
{ {
contentPathString = LocationHelper.GetContentRoot(storageId); contentPathString = LocationHelper.GetContentRoot(storageId);
contentDirectory = LocationHelper.GetRealPath(_device.FileSystem, contentPathString); contentDirectory = LocationHelper.GetRealPath(_virtualFileSystem, contentPathString);
registeredDirectory = Path.Combine(contentDirectory, "registered"); registeredDirectory = Path.Combine(contentDirectory, "registered");
} }
catch (NotSupportedException) catch (NotSupportedException)
@ -99,7 +99,7 @@ namespace Ryujinx.HLE.FileSystem.Content
using (FileStream ncaFile = File.OpenRead(Directory.GetFiles(directoryPath)[0])) using (FileStream ncaFile = File.OpenRead(Directory.GetFiles(directoryPath)[0]))
{ {
Nca nca = new Nca(_device.System.KeySet, ncaFile.AsStorage()); Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
string switchPath = contentPathString + ":/" + ncaFile.Name.Replace(contentDirectory, string.Empty).TrimStart(Path.DirectorySeparatorChar); string switchPath = contentPathString + ":/" + ncaFile.Name.Replace(contentDirectory, string.Empty).TrimStart(Path.DirectorySeparatorChar);
@ -126,7 +126,7 @@ namespace Ryujinx.HLE.FileSystem.Content
using (FileStream ncaFile = new FileStream(filePath, FileMode.Open, FileAccess.Read)) using (FileStream ncaFile = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{ {
Nca nca = new Nca(_device.System.KeySet, ncaFile.AsStorage()); Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
string switchPath = contentPathString + ":/" + filePath.Replace(contentDirectory, string.Empty).TrimStart(Path.DirectorySeparatorChar); string switchPath = contentPathString + ":/" + filePath.Replace(contentDirectory, string.Empty).TrimStart(Path.DirectorySeparatorChar);
@ -156,9 +156,11 @@ namespace Ryujinx.HLE.FileSystem.Content
} }
} }
TimeManager.Instance.InitializeTimeZone(_device); if (device != null)
{
_device.System.Font.Initialize(this, ignoreMissingFonts); TimeManager.Instance.InitializeTimeZone(device);
device.System.Font.Initialize(this);
}
} }
} }
@ -271,7 +273,7 @@ namespace Ryujinx.HLE.FileSystem.Content
return false; return false;
} }
string installedPath = _device.FileSystem.SwitchPathToSystemPath(locationEntry.ContentPath); string installedPath = _virtualFileSystem.SwitchPathToSystemPath(locationEntry.ContentPath);
if (!string.IsNullOrWhiteSpace(installedPath)) if (!string.IsNullOrWhiteSpace(installedPath))
{ {
@ -279,7 +281,7 @@ namespace Ryujinx.HLE.FileSystem.Content
{ {
using (FileStream file = new FileStream(installedPath, FileMode.Open, FileAccess.Read)) using (FileStream file = new FileStream(installedPath, FileMode.Open, FileAccess.Read))
{ {
Nca nca = new Nca(_device.System.KeySet, file.AsStorage()); Nca nca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
bool contentCheck = nca.Header.ContentType == contentType; bool contentCheck = nca.Header.ContentType == contentType;
return contentCheck; return contentCheck;
@ -351,7 +353,7 @@ namespace Ryujinx.HLE.FileSystem.Content
public void InstallFirmware(string firmwareSource) public void InstallFirmware(string firmwareSource)
{ {
string contentPathString = LocationHelper.GetContentRoot(StorageId.NandSystem); string contentPathString = LocationHelper.GetContentRoot(StorageId.NandSystem);
string contentDirectory = LocationHelper.GetRealPath(_device.FileSystem, contentPathString); string contentDirectory = LocationHelper.GetRealPath(_virtualFileSystem, contentPathString);
string registeredDirectory = Path.Combine(contentDirectory, "registered"); string registeredDirectory = Path.Combine(contentDirectory, "registered");
string temporaryDirectory = Path.Combine(contentDirectory, "temp"); string temporaryDirectory = Path.Combine(contentDirectory, "temp");
@ -386,7 +388,7 @@ namespace Ryujinx.HLE.FileSystem.Content
} }
break; break;
case ".xci": case ".xci":
Xci xci = new Xci(_device.System.KeySet, file.AsStorage()); Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage());
InstallFromCart(xci, temporaryDirectory); InstallFromCart(xci, temporaryDirectory);
break; break;
default: default:
@ -418,7 +420,7 @@ namespace Ryujinx.HLE.FileSystem.Content
{ {
foreach (var entry in filesystem.EnumerateEntries("/", "*.nca")) foreach (var entry in filesystem.EnumerateEntries("/", "*.nca"))
{ {
Nca nca = new Nca(_device.System.KeySet, OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage()); Nca nca = new Nca(_virtualFileSystem.KeySet, OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage());
SaveNca(nca, entry.Name.Remove(entry.Name.IndexOf('.')), temporaryDirectory); SaveNca(nca, entry.Name.Remove(entry.Name.IndexOf('.')), temporaryDirectory);
} }
@ -537,7 +539,7 @@ namespace Ryujinx.HLE.FileSystem.Content
return VerifyAndGetVersionZip(archive); return VerifyAndGetVersionZip(archive);
} }
case ".xci": case ".xci":
Xci xci = new Xci(_device.System.KeySet, file.AsStorage()); Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage());
if (xci.HasPartition(XciPartitionType.Update)) if (xci.HasPartition(XciPartitionType.Update))
{ {
@ -561,6 +563,8 @@ namespace Ryujinx.HLE.FileSystem.Content
SystemVersion VerifyAndGetVersionZip(ZipArchive archive) SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
{ {
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel();
SystemVersion systemVersion = null; SystemVersion systemVersion = null;
foreach (var entry in archive.Entries) foreach (var entry in archive.Entries)
@ -571,7 +575,7 @@ namespace Ryujinx.HLE.FileSystem.Content
{ {
IStorage storage = ncaStream.AsStorage(); IStorage storage = ncaStream.AsStorage();
Nca nca = new Nca(_device.System.KeySet, storage); Nca nca = new Nca(_virtualFileSystem.KeySet, storage);
if (updateNcas.ContainsKey(nca.Header.TitleId)) if (updateNcas.ContainsKey(nca.Header.TitleId))
{ {
@ -598,9 +602,9 @@ namespace Ryujinx.HLE.FileSystem.Content
using (Stream ncaStream = GetZipStream(fileEntry)) using (Stream ncaStream = GetZipStream(fileEntry))
{ {
Nca metaNca = new Nca(_device.System.KeySet, ncaStream.AsStorage()); Nca metaNca = new Nca(_virtualFileSystem.KeySet, ncaStream.AsStorage());
IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
@ -628,9 +632,9 @@ namespace Ryujinx.HLE.FileSystem.Content
using (Stream ncaStream = GetZipStream(archive.GetEntry(versionEntry))) using (Stream ncaStream = GetZipStream(archive.GetEntry(versionEntry)))
{ {
Nca nca = new Nca(_device.System.KeySet, ncaStream.AsStorage()); Nca nca = new Nca(_virtualFileSystem.KeySet, ncaStream.AsStorage());
var romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); var romfs = nca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
if (romfs.OpenFile(out IFile systemVersionFile, "/file", OpenMode.Read).IsSuccess()) if (romfs.OpenFile(out IFile systemVersionFile, "/file", OpenMode.Read).IsSuccess())
{ {
@ -663,9 +667,9 @@ namespace Ryujinx.HLE.FileSystem.Content
{ {
using (Stream contentNcaStream = GetZipStream(contentZipEntry)) using (Stream contentNcaStream = GetZipStream(contentZipEntry))
{ {
Nca metaNca = new Nca(_device.System.KeySet, metaNcaStream.AsStorage()); Nca metaNca = new Nca(_virtualFileSystem.KeySet, metaNcaStream.AsStorage());
IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
@ -722,6 +726,8 @@ namespace Ryujinx.HLE.FileSystem.Content
SystemVersion VerifyAndGetVersion(IFileSystem filesystem) SystemVersion VerifyAndGetVersion(IFileSystem filesystem)
{ {
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel();
SystemVersion systemVersion = null; SystemVersion systemVersion = null;
CnmtContentMetaEntry[] metaEntries = null; CnmtContentMetaEntry[] metaEntries = null;
@ -730,11 +736,11 @@ namespace Ryujinx.HLE.FileSystem.Content
{ {
IStorage ncaStorage = OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage(); IStorage ncaStorage = OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage();
Nca nca = new Nca(_device.System.KeySet, ncaStorage); Nca nca = new Nca(_virtualFileSystem.KeySet, ncaStorage);
if (nca.Header.TitleId == SystemUpdateTitleId && nca.Header.ContentType == NcaContentType.Meta) if (nca.Header.TitleId == SystemUpdateTitleId && nca.Header.ContentType == NcaContentType.Meta)
{ {
IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
@ -752,7 +758,7 @@ namespace Ryujinx.HLE.FileSystem.Content
} }
else if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data) else if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data)
{ {
var romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); var romfs = nca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
if (romfs.OpenFile(out IFile systemVersionFile, "/file", OpenMode.Read).IsSuccess()) if (romfs.OpenFile(out IFile systemVersionFile, "/file", OpenMode.Read).IsSuccess())
{ {
@ -797,9 +803,9 @@ namespace Ryujinx.HLE.FileSystem.Content
IStorage metaStorage = OpenPossibleFragmentedFile(filesystem, metaNcaEntry.path, OpenMode.Read).AsStorage(); IStorage metaStorage = OpenPossibleFragmentedFile(filesystem, metaNcaEntry.path, OpenMode.Read).AsStorage();
IStorage contentStorage = OpenPossibleFragmentedFile(filesystem, contentPath, OpenMode.Read).AsStorage(); IStorage contentStorage = OpenPossibleFragmentedFile(filesystem, contentPath, OpenMode.Read).AsStorage();
Nca metaNca = new Nca(_device.System.KeySet, metaStorage); Nca metaNca = new Nca(_virtualFileSystem.KeySet, metaStorage);
IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
@ -851,7 +857,9 @@ namespace Ryujinx.HLE.FileSystem.Content
public SystemVersion GetCurrentFirmwareVersion() public SystemVersion GetCurrentFirmwareVersion()
{ {
LoadEntries(true); IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel();
LoadEntries();
lock (_lock) lock (_lock)
{ {
@ -861,15 +869,15 @@ namespace Ryujinx.HLE.FileSystem.Content
{ {
if (entry.ContentType == NcaContentType.Data) if (entry.ContentType == NcaContentType.Data)
{ {
var path = _device.FileSystem.SwitchPathToSystemPath(entry.ContentPath); var path = _virtualFileSystem.SwitchPathToSystemPath(entry.ContentPath);
using (FileStream fileStream = File.OpenRead(path)) using (FileStream fileStream = File.OpenRead(path))
{ {
Nca nca = new Nca(_device.System.KeySet, fileStream.AsStorage()); Nca nca = new Nca(_virtualFileSystem.KeySet, fileStream.AsStorage());
if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data) if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data)
{ {
var romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); var romfs = nca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
if (romfs.OpenFile(out IFile systemVersionFile, "/file", OpenMode.Read).IsSuccess()) if (romfs.OpenFile(out IFile systemVersionFile, "/file", OpenMode.Read).IsSuccess())
{ {

View file

@ -1,6 +1,6 @@
namespace Ryujinx.HLE.FileSystem namespace Ryujinx.HLE.FileSystem
{ {
internal enum StorageId public enum StorageId
{ {
None, None,
Host, Host,

View file

@ -1,3 +1,7 @@
using LibHac;
using LibHac.Fs;
using LibHac.FsService;
using LibHac.FsSystem;
using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.FileSystem.Content;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using System; using System;
@ -16,6 +20,16 @@ namespace Ryujinx.HLE.FileSystem
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");
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 VirtualFileSystem()
{
Reload();
}
public Stream RomFs { get; private set; } public Stream RomFs { get; private set; }
public void LoadRomFs(string fileName) public void LoadRomFs(string fileName)
@ -183,6 +197,69 @@ namespace Ryujinx.HLE.FileSystem
return Path.Combine(appDataPath, BasePath); return Path.Combine(appDataPath, BasePath);
} }
public void Reload()
{
ReloadKeySet();
LocalFileSystem serverBaseFs = new LocalFileSystem(GetBasePath());
DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet);
GameCard = fsServerObjects.GameCard;
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 Unload()
{
RomFs?.Dispose();
}
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
@ -192,7 +269,7 @@ namespace Ryujinx.HLE.FileSystem
{ {
if (disposing) if (disposing)
{ {
RomFs?.Dispose(); Unload();
} }
} }
} }

View file

@ -44,15 +44,15 @@ namespace Ryujinx.HLE.HOS.Font
_fontsPath = Path.Combine(device.FileSystem.GetSystemPath(), "fonts"); _fontsPath = Path.Combine(device.FileSystem.GetSystemPath(), "fonts");
} }
public void Initialize(ContentManager contentManager, bool ignoreMissingFonts) public void Initialize(ContentManager contentManager)
{ {
_fontData?.Clear(); _fontData?.Clear();
_fontData = null; _fontData = null;
EnsureInitialized(contentManager, ignoreMissingFonts); EnsureInitialized(contentManager);
} }
public void EnsureInitialized(ContentManager contentManager, bool ignoreMissingFonts) public void EnsureInitialized(ContentManager contentManager)
{ {
if (_fontData == null) if (_fontData == null)
{ {
@ -120,12 +120,10 @@ namespace Ryujinx.HLE.HOS.Font
return info; return info;
} }
else if (!ignoreMissingFonts) else
{ {
throw new InvalidSystemResourceException($"Font \"{name}.ttf\" not found. Please provide it in \"{_fontsPath}\"."); throw new InvalidSystemResourceException($"Font \"{name}.ttf\" not found. Please provide it in \"{_fontsPath}\".");
} }
return new FontInfo();
} }
_fontData = new Dictionary<SharedFontType, FontInfo> _fontData = new Dictionary<SharedFontType, FontInfo>
@ -138,7 +136,7 @@ namespace Ryujinx.HLE.HOS.Font
{ SharedFontType.NintendoEx, CreateFont("FontNintendoExtended") } { SharedFontType.NintendoEx, CreateFont("FontNintendoExtended") }
}; };
if (fontOffset > Horizon.FontSize && !ignoreMissingFonts) if (fontOffset > Horizon.FontSize)
{ {
throw new InvalidSystemResourceException( throw new InvalidSystemResourceException(
$"The sum of all fonts size exceed the shared memory size. " + $"The sum of all fonts size exceed the shared memory size. " +
@ -161,14 +159,14 @@ namespace Ryujinx.HLE.HOS.Font
public int GetFontSize(SharedFontType fontType) public int GetFontSize(SharedFontType fontType)
{ {
EnsureInitialized(_device.System.ContentManager, false); EnsureInitialized(_device.System.ContentManager);
return _fontData[fontType].Size; return _fontData[fontType].Size;
} }
public int GetSharedMemoryAddressOffset(SharedFontType fontType) public int GetSharedMemoryAddressOffset(SharedFontType fontType)
{ {
EnsureInitialized(_device.System.ContentManager, false); EnsureInitialized(_device.System.ContentManager);
return _fontData[fontType].Offset + 8; return _fontData[fontType].Offset + 8;
} }

View file

@ -2,7 +2,6 @@ using LibHac;
using LibHac.Account; using LibHac.Account;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.FsService;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils; using LibHac.FsSystem.NcaUtils;
using LibHac.Ncm; using LibHac.Ncm;
@ -105,7 +104,7 @@ namespace Ryujinx.HLE.HOS
internal KEvent VsyncEvent { get; private set; } internal KEvent VsyncEvent { get; private set; }
public Keyset KeySet { get; private set; } public Keyset KeySet => Device.FileSystem.KeySet;
private bool _hasStarted; private bool _hasStarted;
@ -122,12 +121,7 @@ namespace Ryujinx.HLE.HOS
internal long HidBaseAddress { get; private set; } internal long HidBaseAddress { get; private set; }
internal FileSystemServer FsServer { get; private set; } public Horizon(Switch device, ContentManager contentManager)
public FileSystemClient FsClient { get; private set; }
internal EmulatedGameCard GameCard { get; private set; }
public Horizon(Switch device)
{ {
ControlData = new BlitStruct<ApplicationControlProperty>(1); ControlData = new BlitStruct<ApplicationControlProperty>(1);
@ -211,9 +205,7 @@ namespace Ryujinx.HLE.HOS
VsyncEvent = new KEvent(this); VsyncEvent = new KEvent(this);
LoadKeySet(); ContentManager = contentManager;
ContentManager = new ContentManager(device);
// TODO: use set:sys (and get external clock source id from settings) // TODO: use set:sys (and get external clock source id from settings)
// TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate. // TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate.
@ -239,22 +231,6 @@ namespace Ryujinx.HLE.HOS
// FIXME: TimeZone shoud be init here but it's actually done in ContentManager // FIXME: TimeZone shoud be init here but it's actually done in ContentManager
TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock(); TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock();
LocalFileSystem serverBaseFs = new LocalFileSystem(device.FileSystem.GetBasePath());
DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet);
GameCard = fsServerObjects.GameCard;
FileSystemServerConfig fsServerConfig = new FileSystemServerConfig
{
FsCreators = fsServerObjects.FsCreators,
DeviceOperator = fsServerObjects.DeviceOperator,
ExternalKeySet = KeySet.ExternalKeySet
};
FsServer = new FileSystemServer(fsServerConfig);
FsClient = FsServer.CreateFileSystemClient();
} }
public void LoadCart(string exeFsDir, string romFsFile = null) public void LoadCart(string exeFsDir, string romFsFile = null)
@ -284,7 +260,7 @@ namespace Ryujinx.HLE.HOS
return; return;
} }
ContentManager.LoadEntries(); ContentManager.LoadEntries(Device);
LoadNca(mainNca, patchNca, controlNca); LoadNca(mainNca, patchNca, controlNca);
} }
@ -578,7 +554,7 @@ namespace Ryujinx.HLE.HOS
LoadNso("subsdk"); LoadNso("subsdk");
LoadNso("sdk"); LoadNso("sdk");
ContentManager.LoadEntries(); ContentManager.LoadEntries(Device);
ProgramLoader.LoadStaticObjects(this, metaData, staticObjects.ToArray()); ProgramLoader.LoadStaticObjects(this, metaData, staticObjects.ToArray());
} }
@ -671,7 +647,7 @@ namespace Ryujinx.HLE.HOS
staticObject = new NxStaticObject(input); staticObject = new NxStaticObject(input);
} }
ContentManager.LoadEntries(); ContentManager.LoadEntries(Device);
TitleName = metaData.TitleName; TitleName = metaData.TitleName;
TitleId = metaData.Aci0.TitleId; TitleId = metaData.Aci0.TitleId;
@ -712,7 +688,7 @@ namespace Ryujinx.HLE.HOS
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
} }
Result rc = EnsureApplicationSaveData(FsClient, out _, titleId, ref ControlData.Value, ref user); Result rc = EnsureApplicationSaveData(Device.FileSystem.FsClient, out _, titleId, ref ControlData.Value, ref user);
if (rc.IsFailure()) if (rc.IsFailure())
{ {
@ -722,57 +698,6 @@ namespace Ryujinx.HLE.HOS
return rc; return rc;
} }
public void LoadKeySet()
{
string keyFile = null;
string titleKeyFile = null;
string consoleKeyFile = null;
string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
LoadSetAtPath(Path.Combine(home, ".switch"));
LoadSetAtPath(Device.FileSystem.GetSystemPath());
KeySet = ExternalKeyReader.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile);
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;
}
}
}
public SystemVersion VerifyFirmwarePackage(string firmwarePackage)
{
return ContentManager.VerifyFirmwarePackage(firmwarePackage);
}
public SystemVersion GetCurrentFirmwareVersion()
{
return ContentManager.GetCurrentFirmwareVersion();
}
public void InstallFirmware(string firmwarePackage)
{
ContentManager.InstallFirmware(firmwarePackage);
}
public void SignalVsync() public void SignalVsync()
{ {
VsyncEvent.ReadableEvent.Signal(); VsyncEvent.ReadableEvent.Signal();

View file

@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
} }
Result result = EnsureApplicationSaveData(context.Device.System.FsClient, out long requiredSize, titleId, Result result = EnsureApplicationSaveData(context.Device.FileSystem.FsClient, out long requiredSize, titleId,
ref context.Device.System.ControlData.Value, ref userId); ref context.Device.System.ControlData.Value, ref userId);
context.ResponseData.Write(requiredSize); context.ResponseData.Write(requiredSize);

View file

@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
public IFileSystemProxy(ServiceCtx context) public IFileSystemProxy(ServiceCtx context)
{ {
_baseFileSystemProxy = context.Device.System.FsServer.CreateFileSystemProxyService(); _baseFileSystemProxy = context.Device.FileSystem.FsServer.CreateFileSystemProxyService();
} }
[Command(1)] [Command(1)]

View file

@ -61,7 +61,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pl
// GetSharedMemoryNativeHandle() -> handle<copy> // GetSharedMemoryNativeHandle() -> handle<copy>
public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context) public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context)
{ {
context.Device.System.Font.EnsureInitialized(context.Device.System.ContentManager, false); 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)
{ {

View file

@ -4,6 +4,7 @@ using Ryujinx.Configuration;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.FileSystem.Content;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services; using Ryujinx.HLE.HOS.Services;
using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.HOS.SystemState;
@ -15,11 +16,11 @@ namespace Ryujinx.HLE
{ {
public class Switch : IDisposable public class Switch : IDisposable
{ {
internal IAalOutput AudioOut { get; private set; } public IAalOutput AudioOut { get; private set; }
internal DeviceMemory Memory { get; private set; } internal DeviceMemory Memory { get; private set; }
internal GpuContext Gpu { get; private set; } public GpuContext Gpu { get; private set; }
public VirtualFileSystem FileSystem { get; private set; } public VirtualFileSystem FileSystem { get; private set; }
@ -35,7 +36,7 @@ namespace Ryujinx.HLE
public event EventHandler Finish; public event EventHandler Finish;
public Switch(IRenderer renderer, IAalOutput audioOut) public Switch(VirtualFileSystem fileSystem, ContentManager contentManager, IRenderer renderer, IAalOutput audioOut)
{ {
if (renderer == null) if (renderer == null)
{ {
@ -53,9 +54,9 @@ namespace Ryujinx.HLE
Gpu = new GpuContext(renderer); Gpu = new GpuContext(renderer);
FileSystem = new VirtualFileSystem(); FileSystem = fileSystem;
System = new Horizon(this); System = new Horizon(this, contentManager);
Statistics = new PerformanceStatistics(); Statistics = new PerformanceStatistics();
@ -78,15 +79,20 @@ namespace Ryujinx.HLE
System.EnableMultiCoreScheduling(); System.EnableMultiCoreScheduling();
} }
System.FsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks System.FsIntegrityCheckLevel = GetIntigrityCheckLevel();
? IntegrityCheckLevel.ErrorOnInvalid
: IntegrityCheckLevel.None;
System.GlobalAccessLogMode = ConfigurationState.Instance.System.FsGlobalAccessLogMode; System.GlobalAccessLogMode = ConfigurationState.Instance.System.FsGlobalAccessLogMode;
ServiceConfiguration.IgnoreMissingServices = ConfigurationState.Instance.System.IgnoreMissingServices; ServiceConfiguration.IgnoreMissingServices = ConfigurationState.Instance.System.IgnoreMissingServices;
} }
public static IntegrityCheckLevel GetIntigrityCheckLevel()
{
return ConfigurationState.Instance.System.EnableFsIntegrityChecks
? IntegrityCheckLevel.ErrorOnInvalid
: IntegrityCheckLevel.None;
}
public void LoadCart(string exeFsDir, string romFsFile = null) public void LoadCart(string exeFsDir, string romFsFile = null)
{ {
System.LoadCart(exeFsDir, romFsFile); System.LoadCart(exeFsDir, romFsFile);
@ -129,7 +135,7 @@ namespace Ryujinx.HLE
internal void Unload() internal void Unload()
{ {
FileSystem.Dispose(); FileSystem.Unload();
Memory.Dispose(); Memory.Dispose();
} }
@ -150,6 +156,7 @@ namespace Ryujinx.HLE
{ {
System.Dispose(); System.Dispose();
VsyncEvent.Dispose(); VsyncEvent.Dispose();
AudioOut.Dispose();
} }
} }
} }

View file

@ -88,5 +88,23 @@ namespace Ryujinx.Configuration
DiscordClient?.SetPresence(DiscordPresence); DiscordClient?.SetPresence(DiscordPresence);
} }
public static void SwitchToMainMenu()
{
DiscordPresence.Details = "Main Menu";
DiscordPresence.State = "Idling";
DiscordPresence.Assets.LargeImageKey = "ryujinx";
DiscordPresence.Assets.LargeImageText = LargeDescription;
DiscordPresence.Assets.SmallImageKey = null;
DiscordPresence.Assets.SmallImageText = null;
DiscordPresence.Timestamps = new Timestamps(DateTime.UtcNow);
DiscordClient?.SetPresence(DiscordPresence);
}
public static void Exit()
{
DiscordClient?.Dispose();
}
} }
} }

View file

@ -43,7 +43,6 @@ namespace Ryujinx
ConfigurationState.Instance.ToFileFormat().SaveConfig(configurationPath); ConfigurationState.Instance.ToFileFormat().SaveConfig(configurationPath);
} }
Profile.Initialize(); Profile.Initialize();
Application.Init(); Application.Init();

View file

@ -7,6 +7,7 @@ using LibHac.FsSystem.NcaUtils;
using LibHac.Ncm; using LibHac.Ncm;
using LibHac.Spl; using LibHac.Spl;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Configuration.System;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.Loaders.Npdm; using Ryujinx.HLE.Loaders.Npdm;
using System; using System;
@ -20,7 +21,6 @@ using Utf8Json;
using Utf8Json.Resolvers; using Utf8Json.Resolvers;
using RightsId = LibHac.Fs.RightsId; using RightsId = LibHac.Fs.RightsId;
using TitleLanguage = Ryujinx.HLE.HOS.SystemState.TitleLanguage;
namespace Ryujinx.Ui namespace Ryujinx.Ui
{ {
@ -34,15 +34,15 @@ namespace Ryujinx.Ui
private static readonly byte[] _nroIcon = GetResourceBytes("Ryujinx.Ui.assets.NROIcon.png"); private static readonly byte[] _nroIcon = GetResourceBytes("Ryujinx.Ui.assets.NROIcon.png");
private static readonly byte[] _nsoIcon = GetResourceBytes("Ryujinx.Ui.assets.NSOIcon.png"); private static readonly byte[] _nsoIcon = GetResourceBytes("Ryujinx.Ui.assets.NSOIcon.png");
private static Keyset _keySet; private static Keyset _keySet;
private static TitleLanguage _desiredTitleLanguage; private static Language _desiredTitleLanguage;
public static void LoadApplications(List<string> appDirs, Keyset keySet, TitleLanguage desiredTitleLanguage, FileSystemClient fsClient = null, VirtualFileSystem vfs = null) public static void LoadApplications(List<string> appDirs, VirtualFileSystem virtualFileSystem, Language desiredTitleLanguage)
{ {
int numApplicationsFound = 0; int numApplicationsFound = 0;
int numApplicationsLoaded = 0; int numApplicationsLoaded = 0;
_keySet = keySet; _keySet = virtualFileSystem.KeySet;
_desiredTitleLanguage = desiredTitleLanguage; _desiredTitleLanguage = desiredTitleLanguage;
// Builds the applications list with paths to found applications // Builds the applications list with paths to found applications
@ -346,11 +346,11 @@ namespace Ryujinx.Ui
filter.SetUserId(new UserId(1, 0)); filter.SetUserId(new UserId(1, 0));
filter.SetTitleId(new TitleId(titleIdNum)); filter.SetTitleId(new TitleId(titleIdNum));
Result result = fsClient.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); Result result = virtualFileSystem.FsClient.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter);
if (result.IsSuccess()) if (result.IsSuccess())
{ {
saveDataPath = Path.Combine(vfs.GetNandPath(), $"user/save/{saveDataInfo.SaveDataId:x16}"); saveDataPath = Path.Combine(virtualFileSystem.GetNandPath(), $"user/save/{saveDataInfo.SaveDataId:x16}");
} }
} }

View file

@ -45,14 +45,20 @@ namespace Ryujinx.Ui
private ProfileWindowManager _profileWindow; private ProfileWindowManager _profileWindow;
#endif #endif
public GlScreen(Switch device, Renderer renderer) public GlScreen(Switch device)
: base(1280, 720, : base(1280, 720,
new GraphicsMode(), "Ryujinx", 0, new GraphicsMode(), "Ryujinx", 0,
DisplayDevice.Default, 3, 3, DisplayDevice.Default, 3, 3,
GraphicsContextFlags.ForwardCompatible) GraphicsContextFlags.ForwardCompatible)
{ {
_device = device; _device = device;
_renderer = renderer;
if (!(device.Gpu.Renderer is Renderer))
{
throw new NotSupportedException($"GPU renderer must be an OpenGL renderer when using GlScreen!");
}
_renderer = (Renderer)device.Gpu.Renderer;
_primaryController = new Input.NpadController(ConfigurationState.Instance.Hid.JoystickControls); _primaryController = new Input.NpadController(ConfigurationState.Instance.Hid.JoystickControls);
@ -108,7 +114,6 @@ namespace Ryujinx.Ui
} }
_device.DisposeGpu(); _device.DisposeGpu();
_renderer.Dispose();
} }
public void MainLoop() public void MainLoop()

View file

@ -3,8 +3,10 @@ using JsonPrettyPrinterPlus;
using Ryujinx.Audio; using Ryujinx.Audio;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Configuration; using Ryujinx.Configuration;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL; using Ryujinx.Graphics.OpenGL;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.FileSystem.Content;
using Ryujinx.Profiler; using Ryujinx.Profiler;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
@ -22,11 +24,10 @@ namespace Ryujinx.Ui
{ {
public class MainWindow : Window public class MainWindow : Window
{ {
private static HLE.Switch _device; private static VirtualFileSystem _virtualFileSystem;
private static ContentManager _contentManager;
private static Renderer _renderer; private static HLE.Switch _emulationContext;
private static IAalOutput _audioOut;
private static GlScreen _screen; private static GlScreen _screen;
@ -75,29 +76,29 @@ namespace Ryujinx.Ui
_gameTable.ButtonReleaseEvent += Row_Clicked; _gameTable.ButtonReleaseEvent += Row_Clicked;
// First we check that a migration isn't needed. (because VirtualFileSystem will create the new directory otherwise)
bool continueWithStartup = Migration.PromptIfMigrationNeededForStartup(this, out bool migrationNeeded); bool continueWithStartup = Migration.PromptIfMigrationNeededForStartup(this, out bool migrationNeeded);
if (!continueWithStartup) if (!continueWithStartup)
{ {
End(); End(null);
} }
_renderer = new Renderer(); _virtualFileSystem = new VirtualFileSystem();
_contentManager = new ContentManager(_virtualFileSystem);
_audioOut = InitializeAudioEngine();
// TODO: Initialization and dispose of HLE.Switch when starting/stoping emulation.
_device = InitializeSwitchInstance();
if (migrationNeeded) if (migrationNeeded)
{ {
bool migrationSuccessful = Migration.DoMigrationForStartup(this, _device); bool migrationSuccessful = Migration.DoMigrationForStartup(this, _virtualFileSystem);
if (!migrationSuccessful) if (!migrationSuccessful)
{ {
End(); End(null);
} }
} }
// Make sure that everything is loaded.
_virtualFileSystem.Reload();
_treeView = _gameTable; _treeView = _gameTable;
ApplyTheme(); ApplyTheme();
@ -199,7 +200,9 @@ namespace Ryujinx.Ui
private HLE.Switch InitializeSwitchInstance() private HLE.Switch InitializeSwitchInstance()
{ {
HLE.Switch instance = new HLE.Switch(_renderer, _audioOut); _virtualFileSystem.Reload();
HLE.Switch instance = new HLE.Switch(_virtualFileSystem, _contentManager, InitializeRenderer(), InitializeAudioEngine());
instance.Initialize(); instance.Initialize();
@ -218,8 +221,7 @@ namespace Ryujinx.Ui
_tableStore.Clear(); _tableStore.Clear();
await Task.Run(() => ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs, await Task.Run(() => ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs,
_device.System.KeySet, _device.System.State.DesiredTitleLanguage, _device.System.FsClient, _virtualFileSystem, ConfigurationState.Instance.System.Language));
_device.FileSystem));
_updatingGameTable = false; _updatingGameTable = false;
} }
@ -234,8 +236,10 @@ namespace Ryujinx.Ui
{ {
Logger.RestartTime(); Logger.RestartTime();
HLE.Switch device = InitializeSwitchInstance();
// TODO: Move this somewhere else + reloadable? // TODO: Move this somewhere else + reloadable?
Ryujinx.Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
if (Directory.Exists(path)) if (Directory.Exists(path))
{ {
@ -249,12 +253,12 @@ namespace Ryujinx.Ui
if (romFsFiles.Length > 0) if (romFsFiles.Length > 0)
{ {
Logger.PrintInfo(LogClass.Application, "Loading as cart with RomFS."); Logger.PrintInfo(LogClass.Application, "Loading as cart with RomFS.");
_device.LoadCart(path, romFsFiles[0]); device.LoadCart(path, romFsFiles[0]);
} }
else else
{ {
Logger.PrintInfo(LogClass.Application, "Loading as cart WITHOUT RomFS."); Logger.PrintInfo(LogClass.Application, "Loading as cart WITHOUT RomFS.");
_device.LoadCart(path); device.LoadCart(path);
} }
} }
else if (File.Exists(path)) else if (File.Exists(path))
@ -263,22 +267,22 @@ namespace Ryujinx.Ui
{ {
case ".xci": case ".xci":
Logger.PrintInfo(LogClass.Application, "Loading as XCI."); Logger.PrintInfo(LogClass.Application, "Loading as XCI.");
_device.LoadXci(path); device.LoadXci(path);
break; break;
case ".nca": case ".nca":
Logger.PrintInfo(LogClass.Application, "Loading as NCA."); Logger.PrintInfo(LogClass.Application, "Loading as NCA.");
_device.LoadNca(path); device.LoadNca(path);
break; break;
case ".nsp": case ".nsp":
case ".pfs0": case ".pfs0":
Logger.PrintInfo(LogClass.Application, "Loading as NSP."); Logger.PrintInfo(LogClass.Application, "Loading as NSP.");
_device.LoadNsp(path); device.LoadNsp(path);
break; break;
default: default:
Logger.PrintInfo(LogClass.Application, "Loading as homebrew."); Logger.PrintInfo(LogClass.Application, "Loading as homebrew.");
try try
{ {
_device.LoadProgram(path); device.LoadProgram(path);
} }
catch (ArgumentOutOfRangeException) catch (ArgumentOutOfRangeException)
{ {
@ -290,13 +294,15 @@ namespace Ryujinx.Ui
else else
{ {
Logger.PrintWarning(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); Logger.PrintWarning(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
End(); End(device);
} }
_emulationContext = device;
#if MACOS_BUILD #if MACOS_BUILD
CreateGameWindow(); CreateGameWindow(device);
#else #else
new Thread(CreateGameWindow).Start(); new Thread(() => CreateGameWindow(device)).Start();
#endif #endif
_gameLoaded = true; _gameLoaded = true;
@ -305,28 +311,55 @@ namespace Ryujinx.Ui
_firmwareInstallFile.Sensitive = false; _firmwareInstallFile.Sensitive = false;
_firmwareInstallDirectory.Sensitive = false; _firmwareInstallDirectory.Sensitive = false;
DiscordIntegrationModule.SwitchToPlayingState(_device.System.TitleIdText, _device.System.TitleName); DiscordIntegrationModule.SwitchToPlayingState(device.System.TitleIdText, device.System.TitleName);
ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleIdText, appMetadata => ApplicationLibrary.LoadAndSaveMetaData(device.System.TitleIdText, appMetadata =>
{ {
appMetadata.LastPlayed = DateTime.UtcNow.ToString(); appMetadata.LastPlayed = DateTime.UtcNow.ToString();
}); });
} }
} }
private static void CreateGameWindow() private void CreateGameWindow(HLE.Switch device)
{ {
_device.Hid.InitializePrimaryController(ConfigurationState.Instance.Hid.ControllerType); device.Hid.InitializePrimaryController(ConfigurationState.Instance.Hid.ControllerType);
using (_screen = new GlScreen(_device, _renderer)) using (_screen = new GlScreen(device))
{ {
_screen.MainLoop(); _screen.MainLoop();
}
End(); device.Dispose();
_emulationContext = null;
_screen = null;
_gameLoaded = false;
DiscordIntegrationModule.SwitchToMainMenu();
Application.Invoke(delegate
{
_stopEmulation.Sensitive = false;
_firmwareInstallFile.Sensitive = true;
_firmwareInstallDirectory.Sensitive = true;
});
}
private static void UpdateGameMetadata(string titleId)
{
if (_gameLoaded)
{
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
{
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
});
} }
} }
private static void End() private void End(HLE.Switch device)
{ {
if (_ending) if (_ending)
{ {
@ -335,22 +368,23 @@ namespace Ryujinx.Ui
_ending = true; _ending = true;
if (_gameLoaded) if (device != null)
{ {
ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleIdText, appMetadata => UpdateGameMetadata(device.System.TitleIdText);
{
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
});
} }
Dispose();
Profile.FinishProfiling(); Profile.FinishProfiling();
_device?.Dispose(); device?.Dispose();
_audioOut?.Dispose(); DiscordIntegrationModule.Exit();
Logger.Shutdown(); Logger.Shutdown();
Environment.Exit(0); Application.Quit();
}
private static IRenderer InitializeRenderer()
{
return new Renderer();
} }
/// <summary> /// <summary>
@ -427,7 +461,7 @@ namespace Ryujinx.Ui
if (treeIter.UserData == IntPtr.Zero) return; if (treeIter.UserData == IntPtr.Zero) return;
GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, treeIter, _device.System.FsClient); GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, treeIter, _virtualFileSystem.FsClient);
contextMenu.ShowAll(); contextMenu.ShowAll();
contextMenu.PopupAtPointer(null); contextMenu.PopupAtPointer(null);
} }
@ -477,20 +511,18 @@ namespace Ryujinx.Ui
private void Exit_Pressed(object sender, EventArgs args) private void Exit_Pressed(object sender, EventArgs args)
{ {
_screen?.Exit(); _screen?.Exit();
End(); End(_emulationContext);
} }
private void Window_Close(object sender, DeleteEventArgs args) private void Window_Close(object sender, DeleteEventArgs args)
{ {
_screen?.Exit(); _screen?.Exit();
End(); End(_emulationContext);
} }
private void StopEmulation_Pressed(object sender, EventArgs args) private void StopEmulation_Pressed(object sender, EventArgs args)
{ {
// TODO: Write logic to kill running game _screen?.Exit();
_gameLoaded = false;
} }
private void Installer_File_Pressed(object o, EventArgs args) private void Installer_File_Pressed(object o, EventArgs args)
@ -525,7 +557,7 @@ namespace Ryujinx.Ui
private void RefreshFirmwareLabel() private void RefreshFirmwareLabel()
{ {
var currentFirmware = _device.System.GetCurrentFirmwareVersion(); var currentFirmware = _contentManager.GetCurrentFirmwareVersion();
GLib.Idle.Add(new GLib.IdleHandler(() => GLib.Idle.Add(new GLib.IdleHandler(() =>
{ {
@ -547,7 +579,7 @@ namespace Ryujinx.Ui
fileChooser.Dispose(); fileChooser.Dispose();
var firmwareVersion = _device.System.VerifyFirmwarePackage(filename); var firmwareVersion = _contentManager.VerifyFirmwarePackage(filename);
if (firmwareVersion == null) if (firmwareVersion == null)
{ {
@ -566,7 +598,7 @@ namespace Ryujinx.Ui
return; return;
} }
var currentVersion = _device.System.GetCurrentFirmwareVersion(); var currentVersion = _contentManager.GetCurrentFirmwareVersion();
string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed."; string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed.";
@ -606,7 +638,7 @@ namespace Ryujinx.Ui
try try
{ {
_device.System.InstallFirmware(filename); _contentManager.InstallFirmware(filename);
GLib.Idle.Add(new GLib.IdleHandler(() => GLib.Idle.Add(new GLib.IdleHandler(() =>
{ {

View file

@ -1,21 +1,20 @@
using Gtk; using Gtk;
using LibHac; using LibHac;
using Ryujinx.HLE.FileSystem;
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Switch = Ryujinx.HLE.Switch;
namespace Ryujinx.Ui namespace Ryujinx.Ui
{ {
internal class Migration internal class Migration
{ {
private Switch Device { get; } private VirtualFileSystem _virtualFileSystem;
public Migration(Switch device) public Migration(VirtualFileSystem virtualFileSystem)
{ {
Device = device; _virtualFileSystem = virtualFileSystem;
} }
public static bool PromptIfMigrationNeededForStartup(Window parentWindow, out bool isMigrationNeeded) public static bool PromptIfMigrationNeededForStartup(Window parentWindow, out bool isMigrationNeeded)
@ -48,11 +47,11 @@ namespace Ryujinx.Ui
return dialogResponse == (int)ResponseType.Yes; return dialogResponse == (int)ResponseType.Yes;
} }
public static bool DoMigrationForStartup(Window parentWindow, Switch device) public static bool DoMigrationForStartup(MainWindow parentWindow, VirtualFileSystem virtualFileSystem)
{ {
try try
{ {
Migration migration = new Migration(device); Migration migration = new Migration(virtualFileSystem);
int saveCount = migration.Migrate(); int saveCount = migration.Migrate();
using MessageDialog dialogSuccess = new MessageDialog(parentWindow, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, null) using MessageDialog dialogSuccess = new MessageDialog(parentWindow, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, null)
@ -64,9 +63,6 @@ namespace Ryujinx.Ui
dialogSuccess.Run(); dialogSuccess.Run();
// Reload key set after migration to be sure to catch the keys in the system directory.
device.System.LoadKeySet();
return true; return true;
} }
catch (HorizonResultException ex) catch (HorizonResultException ex)
@ -80,6 +76,9 @@ namespace Ryujinx.Ui
// Returns the number of saves migrated // Returns the number of saves migrated
public int Migrate() public int Migrate()
{ {
// Make sure FsClient is initialized
_virtualFileSystem.Reload();
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string oldBasePath = Path.Combine(appDataPath, "RyuFs"); string oldBasePath = Path.Combine(appDataPath, "RyuFs");
@ -89,7 +88,7 @@ namespace Ryujinx.Ui
CopyRyuFs(oldBasePath, newBasePath); CopyRyuFs(oldBasePath, newBasePath);
SaveImporter importer = new SaveImporter(oldSaveDir, Device.System.FsClient); SaveImporter importer = new SaveImporter(oldSaveDir, _virtualFileSystem.FsClient);
return importer.Import(); return importer.Import();
} }