public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleFilePath, string titleName, string titleId, BlitStruct <ApplicationControlProperty> controlData) { _parent = parent; InitializeComponent(); _virtualFileSystem = virtualFileSystem; _titleFilePath = titleFilePath; _titleName = titleName; _titleIdText = titleId; _controlData = controlData; if (!ulong.TryParse(_titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _titleId)) { GtkDialog.CreateErrorDialog("The selected game did not have a valid Title Id"); return; } _openSaveUserDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0; _openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0; _openSaveBcatDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0; string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower(); bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci"; _extractRomFsMenuItem.Sensitive = hasNca; _extractExeFsMenuItem.Sensitive = hasNca; _extractLogoMenuItem.Sensitive = hasNca; PopupAtPointer(null); }
private GameTableContextMenu(Builder builder, ListStore gameTableStore, BlitStruct <ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_contextMenu").Handle) { builder.Autoconnect(this); _gameTableStore = gameTableStore; _rowIter = rowIter; _virtualFileSystem = virtualFileSystem; _controlData = controlData; _openSaveUserDir.Activated += OpenSaveUserDir_Clicked; _openSaveDeviceDir.Activated += OpenSaveDeviceDir_Clicked; _openSaveBcatDir.Activated += OpenSaveBcatDir_Clicked; _manageTitleUpdates.Activated += ManageTitleUpdates_Clicked; _extractRomFs.Activated += ExtractRomFs_Clicked; _extractExeFs.Activated += ExtractExeFs_Clicked; _extractLogo.Activated += ExtractLogo_Clicked; _openSaveUserDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0; _openSaveDeviceDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0; _openSaveBcatDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0; string ext = System.IO.Path.GetExtension(_gameTableStore.GetValue(_rowIter, 9).ToString()).ToLower(); if (ext != ".nca" && ext != ".nsp" && ext != ".pfs0" && ext != ".xci") { _extractRomFs.Sensitive = false; _extractExeFs.Sensitive = false; _extractLogo.Sensitive = false; } }
// EnsureSaveData(nn::account::Uid) -> u64 public ResultCode EnsureSaveData(ServiceCtx context) { Uid userId = context.RequestData.ReadStruct <AccountUid>().ToLibHacUid(); TitleId titleId = new TitleId(context.Process.TitleId); BlitStruct <ApplicationControlProperty> controlHolder = context.Device.Application.ControlData; ref ApplicationControlProperty control = ref controlHolder.Value;
public ApplicationLoader(Switch device, VirtualFileSystem fileSystem, ContentManager contentManager) { _device = device; _contentManager = contentManager; _fileSystem = fileSystem; _controlData = new BlitStruct <ApplicationControlProperty>(1); }
// EnsureSaveData(nn::account::Uid) -> u64 public ResultCode EnsureSaveData(ServiceCtx context) { Uid userId = context.RequestData.ReadStruct <AccountUid>().ToLibHacUid(); // Mask out the low nibble of the program ID to get the application ID ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul); BlitStruct <ApplicationControlProperty> controlHolder = context.Device.Application.ControlData; ref ApplicationControlProperty control = ref controlHolder.Value;
private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct <ApplicationControlProperty> controlHolder, SaveDataFilter filter, out ulong saveDataId) { saveDataId = default; Result result = _virtualFileSystem.FsClient.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); if (ResultFs.TargetNotFound.Includes(result)) { // Savedata was not found. Ask the user if they want to create it using MessageDialog messageDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, null) { Title = "PangoNX Debugger", Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"), Text = $"There is no savedata for {titleName} [{titleId:x16}]", SecondaryText = "Would you like to create savedata for this game?", WindowPosition = WindowPosition.Center }; if (messageDialog.Run() != (int)ResponseType.Yes) { return(false); } ref ApplicationControlProperty control = ref controlHolder.Value; if (LibHac.Util.IsEmpty(controlHolder.ByteSpan)) { // If the current application doesn't have a loaded control property, create a dummy one // and set the savedata sizes so a user savedata will be created. control = ref new BlitStruct <ApplicationControlProperty>(1).Value; // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. control.UserAccountSaveDataSize = 0x4000; control.UserAccountSaveDataJournalSize = 0x4000; Logger.PrintWarning(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); } Uid user = new Uid(1, 0); result = EnsureApplicationSaveData(_virtualFileSystem.FsClient, out _, new TitleId(titleId), ref control, ref user); if (result.IsFailure()) { GtkDialog.CreateErrorDialog($"There was an error creating the specified savedata: {result.ToStringWithName()}"); return(false); } // Try to find the savedata again after creating it result = _virtualFileSystem.FsClient.FindSaveDataWithFilter(out saveDataInfo, SaveDataSpaceId.User, ref filter); }
private void Row_Clicked(object sender, ButtonReleaseEventArgs args) { if (args.Event.Button != 3) { return; } _gameTableSelection.GetSelected(out TreeIter treeIter); if (treeIter.UserData == IntPtr.Zero) { return; } BlitStruct <ApplicationControlProperty> controlData = (BlitStruct <ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10); GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, controlData, treeIter, _virtualFileSystem); contextMenu.ShowAll(); contextMenu.PopupAtPointer(null); }
private void Row_Clicked(object sender, ButtonReleaseEventArgs args) { if (args.Event.Button != 3 /* Right Click */) { return; } _gameTableSelection.GetSelected(out TreeIter treeIter); if (treeIter.UserData == IntPtr.Zero) { return; } string titleFilePath = _tableStore.GetValue(treeIter, 9).ToString(); string titleName = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[0]; string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower(); BlitStruct <ApplicationControlProperty> controlData = (BlitStruct <ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10); _ = new GameTableContextMenu(this, _virtualFileSystem, titleFilePath, titleName, titleId, controlData); }
public void SerializeBlitTest() { var s = new BlitStruct { l2 = 7L, g1 = Guid.NewGuid(), b1 = true, ui1 = 8, f1 = 4.2f, b2 = true, us1 = 3, b3 = 4 }; using (var ptr = MarshaledStructSerializer.Serialize(s)) { Assert.That(ptr.IsInvalid, Is.False); Assert.That(ptr.Size, Is.EqualTo(40)); TestContext.WriteLine(ptr.Dump); var obj = MarshaledStructSerializer.Deserialize <BlitStruct>(ptr); Assert.That(obj, Is.Not.Null); Assert.That(obj, Is.TypeOf <BlitStruct>()); foreach (var fi in typeof(BlitStruct).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance)) { if (fi.FieldType.FindElementType() is null) { Assert.That(fi.GetValue(obj), Is.EqualTo(fi.GetValue(s))); } else { Assert.That(fi.GetValue(obj), Is.EquivalentTo(fi.GetValue(s) as IEnumerable)); } } } }
private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct <ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId)
public void LoadApplications(List <string> appDirs, Language desiredTitleLanguage) { int numApplicationsFound = 0; int numApplicationsLoaded = 0; _desiredTitleLanguage = desiredTitleLanguage; _cancellationToken = new CancellationTokenSource(); // Builds the applications list with paths to found applications List <string> applications = new List <string>(); try { foreach (string appDir in appDirs) { if (_cancellationToken.Token.IsCancellationRequested) { return; } if (!Directory.Exists(appDir)) { Logger.Warning?.Print(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{appDir}\""); continue; } foreach (string app in GetFilesInDirectory(appDir)) { if (_cancellationToken.Token.IsCancellationRequested) { return; } string extension = Path.GetExtension(app).ToLower(); if ((extension == ".nsp") || (extension == ".pfs0") || (extension == ".xci") || (extension == ".nca") || (extension == ".nro") || (extension == ".nso")) { applications.Add(app); numApplicationsFound++; } } } // Loops through applications list, creating a struct and then firing an event containing the struct for each application foreach (string applicationPath in applications) { if (_cancellationToken.Token.IsCancellationRequested) { return; } double fileSize = new FileInfo(applicationPath).Length * 0.000000000931; string titleName = "Unknown"; string titleId = "0000000000000000"; string developer = "Unknown"; string version = "0"; byte[] applicationIcon = null; BlitStruct <ApplicationControlProperty> controlHolder = new BlitStruct <ApplicationControlProperty>(1); try { string extension = Path.GetExtension(applicationPath).ToLower(); using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read)) { if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci") { try { PartitionFileSystem pfs; bool isExeFs = false; if (extension == ".xci") { Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()); pfs = xci.OpenPartition(XciPartitionType.Secure); } else { pfs = new PartitionFileSystem(file.AsStorage()); // If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application. bool hasMainNca = false; foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*")) { if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca") { using var ncaFile = new UniqueRef <IFile>(); pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage()); int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); // Some main NCAs don't have a data partition, so check if the partition exists before opening it if (nca.Header.ContentType == NcaContentType.Program && !(nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())) { hasMainNca = true; break; } } else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main") { isExeFs = true; } } if (!hasMainNca && !isExeFs) { numApplicationsFound--; continue; } } if (isExeFs) { applicationIcon = _nspIcon; using var npdmFile = new UniqueRef <IFile>(); Result result = pfs.OpenFile(ref npdmFile.Ref(), "/main.npdm".ToU8Span(), OpenMode.Read); if (ResultFs.PathNotFound.Includes(result)) { Npdm npdm = new Npdm(npdmFile.Get.AsStream()); titleName = npdm.TitleName; titleId = npdm.Aci0.TitleId.ToString("x16"); } } else { GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out titleId); // Check if there is an update available. if (IsUpdateApplied(titleId, out IFileSystem updatedControlFs)) { // Replace the original ControlFs by the updated one. controlFs = updatedControlFs; } ReadControlData(controlFs, controlHolder.ByteSpan); GetGameInformation(ref controlHolder.Value, out titleName, out _, out developer, out version); // Read the icon from the ControlFS and store it as a byte array try { using var icon = new UniqueRef <IFile>(); controlFs.OpenFile(ref icon.Ref(), $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); using (MemoryStream stream = new MemoryStream()) { icon.Get.AsStream().CopyTo(stream); applicationIcon = stream.ToArray(); } } catch (HorizonResultException) { foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*")) { if (entry.Name == "control.nacp") { continue; } using var icon = new UniqueRef <IFile>(); controlFs.OpenFile(ref icon.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); using (MemoryStream stream = new MemoryStream()) { icon.Get.AsStream().CopyTo(stream); applicationIcon = stream.ToArray(); } if (applicationIcon != null) { break; } } if (applicationIcon == null) { applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon; } } } } catch (MissingKeyException exception) { applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon; Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}"); } catch (InvalidDataException) { applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon; Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}"); } catch (Exception exception) { Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}"); numApplicationsFound--; continue; } } else if (extension == ".nro") { BinaryReader reader = new BinaryReader(file); byte[] Read(long position, int size) { file.Seek(position, SeekOrigin.Begin); return(reader.ReadBytes(size)); } try { file.Seek(24, SeekOrigin.Begin); int assetOffset = reader.ReadInt32(); if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET") { byte[] iconSectionInfo = Read(assetOffset + 8, 0x10); long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0); long iconSize = BitConverter.ToInt64(iconSectionInfo, 8); ulong nacpOffset = reader.ReadUInt64(); ulong nacpSize = reader.ReadUInt64(); // Reads and stores game icon as byte array applicationIcon = Read(assetOffset + iconOffset, (int)iconSize); // Read the NACP data Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan); GetGameInformation(ref controlHolder.Value, out titleName, out titleId, out developer, out version); } else { applicationIcon = _nroIcon; titleName = Path.GetFileNameWithoutExtension(applicationPath); } } catch { Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}"); numApplicationsFound--; continue; } } else if (extension == ".nca") { try { Nca nca = new Nca(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage()); int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); if (nca.Header.ContentType != NcaContentType.Program || (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())) { numApplicationsFound--; continue; } } catch (InvalidDataException) { Logger.Warning?.Print(LogClass.Application, $"The NCA header content type check has failed. This is usually because the header key is incorrect or missing. Errored File: {applicationPath}"); } catch { Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}"); numApplicationsFound--; continue; } applicationIcon = _ncaIcon; titleName = Path.GetFileNameWithoutExtension(applicationPath); } // If its an NSO we just set defaults else if (extension == ".nso") { applicationIcon = _nsoIcon; titleName = Path.GetFileNameWithoutExtension(applicationPath); } } } catch (IOException exception) { Logger.Warning?.Print(LogClass.Application, exception.Message); numApplicationsFound--; continue; } ApplicationMetadata appMetadata = LoadAndSaveMetaData(titleId); if (appMetadata.LastPlayed != "Never" && !DateTime.TryParse(appMetadata.LastPlayed, out _)) { Logger.Warning?.Print(LogClass.Application, $"Last played datetime \"{appMetadata.LastPlayed}\" is invalid for current system culture, skipping (did current culture change?)"); appMetadata.LastPlayed = "Never"; } ApplicationData data = new ApplicationData { Favorite = appMetadata.Favorite, Icon = applicationIcon, TitleName = titleName, TitleId = titleId, Developer = developer, Version = version, TimePlayed = ConvertSecondsToReadableString(appMetadata.TimePlayed), LastPlayed = appMetadata.LastPlayed, FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0, 1), FileSize = (fileSize < 1) ? (fileSize * 1024).ToString("0.##") + "MB" : fileSize.ToString("0.##") + "GB", Path = applicationPath, ControlHolder = controlHolder }; numApplicationsLoaded++; OnApplicationAdded(new ApplicationAddedEventArgs() { AppData = data }); OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs() { NumAppsFound = numApplicationsFound, NumAppsLoaded = numApplicationsLoaded }); } OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs() { NumAppsFound = numApplicationsFound, NumAppsLoaded = numApplicationsLoaded }); } finally { _cancellationToken.Dispose(); _cancellationToken = null; } }
public Horizon(Switch device, ContentManager contentManager) { ControlData = new BlitStruct <ApplicationControlProperty>(1); Device = device; State = new SystemStateMgr(); ResourceLimit = new KResourceLimit(this); KernelInit.InitializeResourceLimit(ResourceLimit); MemoryRegions = KernelInit.GetMemoryRegions(); LargeMemoryBlockAllocator = new KMemoryBlockAllocator(MemoryBlockAllocatorSize * 2); SmallMemoryBlockAllocator = new KMemoryBlockAllocator(MemoryBlockAllocatorSize); UserSlabHeapPages = new KSlabHeap( UserSlabHeapBase, UserSlabHeapItemSize, UserSlabHeapSize); CriticalSection = new KCriticalSection(this); Scheduler = new KScheduler(this); TimeManager = new KTimeManager(); Synchronization = new KSynchronization(this); ContextIdManager = new KContextIdManager(); _kipId = InitialKipId; _processId = InitialProcessId; Scheduler.StartAutoPreemptionThread(); KernelInitialized = true; ThreadCounter = new CountdownEvent(1); Processes = new SortedDictionary <long, KProcess>(); AutoObjectNames = new ConcurrentDictionary <string, KAutoObject>(); // Note: This is not really correct, but with HLE of services, the only memory // region used that is used is Application, so we can use the other ones for anything. KMemoryRegionManager region = MemoryRegions[(int)MemoryRegion.NvServices]; ulong hidPa = region.Address; ulong fontPa = region.Address + HidSize; ulong iirsPa = region.Address + HidSize + FontSize; ulong timePa = region.Address + HidSize + FontSize + IirsSize; HidBaseAddress = (long)(hidPa - DramMemoryMap.DramBase); KPageList hidPageList = new KPageList(); KPageList fontPageList = new KPageList(); KPageList iirsPageList = new KPageList(); KPageList timePageList = new KPageList(); hidPageList.AddRange(hidPa, HidSize / KMemoryManager.PageSize); fontPageList.AddRange(fontPa, FontSize / KMemoryManager.PageSize); iirsPageList.AddRange(iirsPa, IirsSize / KMemoryManager.PageSize); timePageList.AddRange(timePa, TimeSize / KMemoryManager.PageSize); HidSharedMem = new KSharedMemory(this, hidPageList, 0, 0, MemoryPermission.Read); FontSharedMem = new KSharedMemory(this, fontPageList, 0, 0, MemoryPermission.Read); IirsSharedMem = new KSharedMemory(this, iirsPageList, 0, 0, MemoryPermission.Read); KSharedMemory timeSharedMemory = new KSharedMemory(this, timePageList, 0, 0, MemoryPermission.Read); TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, (long)(timePa - DramMemoryMap.DramBase), TimeSize); AppletState = new AppletStateMgr(this); AppletState.SetFocus(true); Font = new SharedFontManager(device, (long)(fontPa - DramMemoryMap.DramBase)); IUserInterface.InitializePort(this); VsyncEvent = new KEvent(this); ContentManager = contentManager; // 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. UInt128 clockSourceId = new UInt128(Guid.NewGuid().ToByteArray()); IRtcManager.GetExternalRtcValue(out ulong rtcValue); // We assume the rtc is system time. TimeSpanType systemTime = TimeSpanType.FromSeconds((long)rtcValue); // First init the standard steady clock TimeServiceManager.Instance.SetupStandardSteadyClock(null, clockSourceId, systemTime, TimeSpanType.Zero, TimeSpanType.Zero, false); TimeServiceManager.Instance.SetupStandardLocalSystemClock(null, new SystemClockContext(), systemTime.ToSeconds()); if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes)) { TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000); TimeServiceManager.Instance.SetupStandardNetworkSystemClock(new SystemClockContext(), standardNetworkClockSufficientAccuracy); } TimeServiceManager.Instance.SetupStandardUserSystemClock(null, false, SteadyClockTimePoint.GetRandom()); // FIXME: TimeZone shoud be init here but it's actually done in ContentManager TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock(); }
public static void LoadApplications(List <string> appDirs, VirtualFileSystem virtualFileSystem, Language desiredTitleLanguage) { int numApplicationsFound = 0; int numApplicationsLoaded = 0; _loadingError = false; _virtualFileSystem = virtualFileSystem; _desiredTitleLanguage = desiredTitleLanguage; // Builds the applications list with paths to found applications List <string> applications = new List <string>(); foreach (string appDir in appDirs) { if (!Directory.Exists(appDir)) { Logger.PrintWarning(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{appDir}\""); continue; } foreach (string app in GetFilesInDirectory(appDir)) { if ((Path.GetExtension(app).ToLower() == ".nsp") || (Path.GetExtension(app).ToLower() == ".pfs0") || (Path.GetExtension(app).ToLower() == ".xci") || (Path.GetExtension(app).ToLower() == ".nca") || (Path.GetExtension(app).ToLower() == ".nro") || (Path.GetExtension(app).ToLower() == ".nso")) { applications.Add(app); numApplicationsFound++; } } } // Loops through applications list, creating a struct and then firing an event containing the struct for each application foreach (string applicationPath in applications) { double fileSize = new FileInfo(applicationPath).Length * 0.000000000931; string titleName = "Unknown"; string titleId = "0000000000000000"; string developer = "Unknown"; string version = "0"; string saveDataPath = null; byte[] applicationIcon = null; BlitStruct <ApplicationControlProperty> controlHolder = new BlitStruct <ApplicationControlProperty>(1); try { using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read)) { if ((Path.GetExtension(applicationPath).ToLower() == ".nsp") || (Path.GetExtension(applicationPath).ToLower() == ".pfs0") || (Path.GetExtension(applicationPath).ToLower() == ".xci")) { try { PartitionFileSystem pfs; bool isExeFs = false; if (Path.GetExtension(applicationPath).ToLower() == ".xci") { Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()); pfs = xci.OpenPartition(XciPartitionType.Secure); } else { pfs = new PartitionFileSystem(file.AsStorage()); // If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application. bool hasMainNca = false; foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*")) { if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca") { pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage()); int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); if (nca.Header.ContentType == NcaContentType.Program && !nca.Header.GetFsHeader(dataIndex).IsPatchSection()) { hasMainNca = true; break; } } else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main") { isExeFs = true; } } if (!hasMainNca && !isExeFs) { numApplicationsFound--; continue; } } if (isExeFs) { applicationIcon = _nspIcon; Result result = pfs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read); if (ResultFs.PathNotFound.Includes(result)) { Npdm npdm = new Npdm(npdmFile.AsStream()); titleName = npdm.TitleName; titleId = npdm.Aci0.TitleId.ToString("x16"); } } else { // Store the ControlFS in variable called controlFs GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out titleId); ReadControlData(controlFs, controlHolder.ByteSpan); // Creates NACP class from the NACP file controlFs.OpenFile(out IFile controlNacpFile, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); // Get the title name, title ID, developer name and version number from the NACP version = IsUpdateApplied(titleId, out string updateVersion) ? updateVersion : controlHolder.Value.DisplayVersion.ToString(); GetNameIdDeveloper(ref controlHolder.Value, out titleName, out _, out developer); // Read the icon from the ControlFS and store it as a byte array try { controlFs.OpenFile(out IFile icon, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); using (MemoryStream stream = new MemoryStream()) { icon.AsStream().CopyTo(stream); applicationIcon = stream.ToArray(); } } catch (HorizonResultException) { foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*")) { if (entry.Name == "control.nacp") { continue; } controlFs.OpenFile(out IFile icon, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); using (MemoryStream stream = new MemoryStream()) { icon.AsStream().CopyTo(stream); applicationIcon = stream.ToArray(); } if (applicationIcon != null) { break; } } if (applicationIcon == null) { applicationIcon = Path.GetExtension(applicationPath).ToLower() == ".xci" ? _xciIcon : _nspIcon; } } } } catch (MissingKeyException exception) { applicationIcon = Path.GetExtension(applicationPath).ToLower() == ".xci" ? _xciIcon : _nspIcon; Logger.PrintWarning(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}"); } catch (InvalidDataException) { applicationIcon = Path.GetExtension(applicationPath).ToLower() == ".xci" ? _xciIcon : _nspIcon; Logger.PrintWarning(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}"); } catch (Exception exception) { Logger.PrintWarning(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}"); Logger.PrintDebug(LogClass.Application, exception.ToString()); numApplicationsFound--; _loadingError = true; continue; } } else if (Path.GetExtension(applicationPath).ToLower() == ".nro") { BinaryReader reader = new BinaryReader(file); byte[] Read(long position, int size) { file.Seek(position, SeekOrigin.Begin); return(reader.ReadBytes(size)); } try { file.Seek(24, SeekOrigin.Begin); int assetOffset = reader.ReadInt32(); if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET") { byte[] iconSectionInfo = Read(assetOffset + 8, 0x10); long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0); long iconSize = BitConverter.ToInt64(iconSectionInfo, 8); ulong nacpOffset = reader.ReadUInt64(); ulong nacpSize = reader.ReadUInt64(); // Reads and stores game icon as byte array applicationIcon = Read(assetOffset + iconOffset, (int)iconSize); // Read the NACP data Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan); // Get the title name, title ID, developer name and version number from the NACP version = controlHolder.Value.DisplayVersion.ToString(); GetNameIdDeveloper(ref controlHolder.Value, out titleName, out titleId, out developer); } else { applicationIcon = _nroIcon; titleName = Path.GetFileNameWithoutExtension(applicationPath); } } catch { Logger.PrintWarning(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}"); numApplicationsFound--; continue; } } else if (Path.GetExtension(applicationPath).ToLower() == ".nca") { try { Nca nca = new Nca(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage()); int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); if (nca.Header.ContentType != NcaContentType.Program || nca.Header.GetFsHeader(dataIndex).IsPatchSection()) { numApplicationsFound--; continue; } } catch (InvalidDataException) { Logger.PrintWarning(LogClass.Application, $"The NCA header content type check has failed. This is usually because the header key is incorrect or missing. Errored File: {applicationPath}"); } catch { Logger.PrintWarning(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}"); numApplicationsFound--; _loadingError = true; continue; } applicationIcon = _ncaIcon; titleName = Path.GetFileNameWithoutExtension(applicationPath); } // If its an NSO we just set defaults else if (Path.GetExtension(applicationPath).ToLower() == ".nso") { applicationIcon = _nsoIcon; titleName = Path.GetFileNameWithoutExtension(applicationPath); } } } catch (IOException exception) { Logger.PrintWarning(LogClass.Application, exception.Message); numApplicationsFound--; _loadingError = true; continue; } ApplicationMetadata appMetadata = LoadAndSaveMetaData(titleId); if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNum)) { SaveDataFilter filter = new SaveDataFilter(); filter.SetUserId(new UserId(1, 0)); filter.SetProgramId(new TitleId(titleIdNum)); Result result = virtualFileSystem.FsClient.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); if (result.IsSuccess()) { saveDataPath = Path.Combine(virtualFileSystem.GetNandPath(), "user", "save", saveDataInfo.SaveDataId.ToString("x16")); } } ApplicationData data = new ApplicationData { Favorite = appMetadata.Favorite, Icon = applicationIcon, TitleName = titleName, TitleId = titleId, Developer = developer, Version = version, TimePlayed = ConvertSecondsToReadableString(appMetadata.TimePlayed), LastPlayed = appMetadata.LastPlayed, FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0, 1), FileSize = (fileSize < 1) ? (fileSize * 1024).ToString("0.##") + "MB" : fileSize.ToString("0.##") + "GB", Path = applicationPath, SaveDataPath = saveDataPath, ControlHolder = controlHolder }; numApplicationsLoaded++; OnApplicationAdded(new ApplicationAddedEventArgs() { AppData = data }); OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs() { NumAppsFound = numApplicationsFound, NumAppsLoaded = numApplicationsLoaded }); } OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs() { NumAppsFound = numApplicationsFound, NumAppsLoaded = numApplicationsLoaded }); if (_loadingError) { Gtk.Application.Invoke(delegate { GtkDialog.CreateErrorDialog("One or more files encountered could not be loaded, check logs for more info."); }); } }
public Horizon(Switch device, ContentManager contentManager) { ControlData = new BlitStruct <ApplicationControlProperty>(1); KernelContext = new KernelContext(device, device.Memory); Device = device; State = new SystemStateMgr(); // Note: This is not really correct, but with HLE of services, the only memory // region used that is used is Application, so we can use the other ones for anything. KMemoryRegionManager region = KernelContext.MemoryRegions[(int)MemoryRegion.NvServices]; ulong hidPa = region.Address; ulong fontPa = region.Address + HidSize; ulong iirsPa = region.Address + HidSize + FontSize; ulong timePa = region.Address + HidSize + FontSize + IirsSize; HidBaseAddress = hidPa - DramMemoryMap.DramBase; KPageList hidPageList = new KPageList(); KPageList fontPageList = new KPageList(); KPageList iirsPageList = new KPageList(); KPageList timePageList = new KPageList(); hidPageList.AddRange(hidPa, HidSize / KMemoryManager.PageSize); fontPageList.AddRange(fontPa, FontSize / KMemoryManager.PageSize); iirsPageList.AddRange(iirsPa, IirsSize / KMemoryManager.PageSize); timePageList.AddRange(timePa, TimeSize / KMemoryManager.PageSize); HidSharedMem = new KSharedMemory(KernelContext, hidPageList, 0, 0, MemoryPermission.Read); FontSharedMem = new KSharedMemory(KernelContext, fontPageList, 0, 0, MemoryPermission.Read); IirsSharedMem = new KSharedMemory(KernelContext, iirsPageList, 0, 0, MemoryPermission.Read); KSharedMemory timeSharedMemory = new KSharedMemory(KernelContext, timePageList, 0, 0, MemoryPermission.Read); TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, timePa - DramMemoryMap.DramBase, TimeSize); AppletState = new AppletStateMgr(this); AppletState.SetFocus(true); Font = new SharedFontManager(device, fontPa - DramMemoryMap.DramBase); IUserInterface.InitializePort(this); VsyncEvent = new KEvent(KernelContext); DisplayResolutionChangeEvent = new KEvent(KernelContext); ContentManager = contentManager; // 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. UInt128 clockSourceId = new UInt128(Guid.NewGuid().ToByteArray()); IRtcManager.GetExternalRtcValue(out ulong rtcValue); // We assume the rtc is system time. TimeSpanType systemTime = TimeSpanType.FromSeconds((long)rtcValue); // Configure and setup internal offset TimeSpanType internalOffset = TimeSpanType.FromSeconds(ConfigurationState.Instance.System.SystemTimeOffset); TimeSpanType systemTimeOffset = new TimeSpanType(systemTime.NanoSeconds + internalOffset.NanoSeconds); if (systemTime.IsDaylightSavingTime() && !systemTimeOffset.IsDaylightSavingTime()) { internalOffset = internalOffset.AddSeconds(3600L); } else if (!systemTime.IsDaylightSavingTime() && systemTimeOffset.IsDaylightSavingTime()) { internalOffset = internalOffset.AddSeconds(-3600L); } internalOffset = new TimeSpanType(-internalOffset.NanoSeconds); // First init the standard steady clock TimeServiceManager.Instance.SetupStandardSteadyClock(null, clockSourceId, systemTime, internalOffset, TimeSpanType.Zero, false); TimeServiceManager.Instance.SetupStandardLocalSystemClock(null, new SystemClockContext(), systemTime.ToSeconds()); if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes)) { TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000); TimeServiceManager.Instance.SetupStandardNetworkSystemClock(new SystemClockContext(), standardNetworkClockSufficientAccuracy); } TimeServiceManager.Instance.SetupStandardUserSystemClock(null, false, SteadyClockTimePoint.GetRandom()); // FIXME: TimeZone shoud be init here but it's actually done in ContentManager TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock(); DatabaseImpl.Instance.InitializeDatabase(device); HostSyncpoint = new NvHostSyncpt(device); SurfaceFlinger = new SurfaceFlinger(device); ConfigurationState.Instance.System.EnableDockedMode.Event += OnDockedModeChange; InitLibHacHorizon(); }
public GameTableContextMenu(ListStore gameTableStore, BlitStruct <ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem) { _gameTableStore = gameTableStore; _rowIter = rowIter; _virtualFileSystem = virtualFileSystem; _controlData = controlData; MenuItem openSaveUserDir = new MenuItem("Open User Save Directory") { Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0, TooltipText = "Open the directory which contains Application's User Saves." }; MenuItem openSaveDeviceDir = new MenuItem("Open Device Save Directory") { Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0, TooltipText = "Open the directory which contains Application's Device Saves." }; MenuItem openSaveBcatDir = new MenuItem("Open BCAT Save Directory") { Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0, TooltipText = "Open the directory which contains Application's BCAT Saves." }; MenuItem manageTitleUpdates = new MenuItem("Manage Title Updates") { TooltipText = "Open the Title Update management window" }; MenuItem manageDlc = new MenuItem("Manage DLC") { TooltipText = "Open the DLC management window" }; MenuItem openTitleModDir = new MenuItem("Open Mods Directory") { TooltipText = "Open the directory which contains Application's Mods." }; string ext = System.IO.Path.GetExtension(_gameTableStore.GetValue(_rowIter, 9).ToString()).ToLower(); bool hasNca = ext == ".nca" || ext == ".nsp" || ext == ".pfs0" || ext == ".xci"; MenuItem extractMenu = new MenuItem("Extract Data"); MenuItem extractRomFs = new MenuItem("RomFS") { Sensitive = hasNca, TooltipText = "Extract the RomFS section from Application's current config (including updates)." }; MenuItem extractExeFs = new MenuItem("ExeFS") { Sensitive = hasNca, TooltipText = "Extract the ExeFS section from Application's current config (including updates)." }; MenuItem extractLogo = new MenuItem("Logo") { Sensitive = hasNca, TooltipText = "Extract the Logo section from Application's current config (including updates)." }; Menu extractSubMenu = new Menu(); extractSubMenu.Append(extractExeFs); extractSubMenu.Append(extractRomFs); extractSubMenu.Append(extractLogo); extractMenu.Submenu = extractSubMenu; MenuItem managePtcMenu = new MenuItem("Cache Management"); MenuItem purgePtcCache = new MenuItem("Purge PPTC cache") { TooltipText = "Delete the Application's PPTC cache." }; MenuItem openPtcDir = new MenuItem("Open PPTC directory") { TooltipText = "Open the directory which contains Application's PPTC cache." }; Menu managePtcSubMenu = new Menu(); managePtcSubMenu.Append(purgePtcCache); managePtcSubMenu.Append(openPtcDir); managePtcMenu.Submenu = managePtcSubMenu; openSaveUserDir.Activated += OpenSaveUserDir_Clicked; openSaveDeviceDir.Activated += OpenSaveDeviceDir_Clicked; openSaveBcatDir.Activated += OpenSaveBcatDir_Clicked; manageTitleUpdates.Activated += ManageTitleUpdates_Clicked; manageDlc.Activated += ManageDlc_Clicked; openTitleModDir.Activated += OpenTitleModDir_Clicked; extractRomFs.Activated += ExtractRomFs_Clicked; extractExeFs.Activated += ExtractExeFs_Clicked; extractLogo.Activated += ExtractLogo_Clicked; purgePtcCache.Activated += PurgePtcCache_Clicked; openPtcDir.Activated += OpenPtcDir_Clicked; this.Add(openSaveUserDir); this.Add(openSaveDeviceDir); this.Add(openSaveBcatDir); this.Add(new SeparatorMenuItem()); this.Add(manageTitleUpdates); this.Add(manageDlc); this.Add(openTitleModDir); this.Add(new SeparatorMenuItem()); this.Add(managePtcMenu); this.Add(extractMenu); }
private void BuildChildItems(SectionItem parentItem) { try { const string?ROOT_PATH = "/"; var fileSystem = parentItem.FileSystem; if (fileSystem == null) { return; } var directoryEntries = SafeGetDirectoryEntries(fileSystem, ROOT_PATH, parentItem); foreach (var directoryEntry in directoryEntries) { var entryName = directoryEntry.Name; var entryPath = directoryEntry.FullPath; // NACP File if (parentItem.ParentItem.ContentType == NcaContentType.Control && string.Equals(entryName, NacpItem.NacpFileName, StringComparison.OrdinalIgnoreCase) && directoryEntry.Type == DirectoryEntryType.File) { IFile nacpFile; try { using var uniqueRefFile = new UniqueRef <IFile>(); fileSystem.OpenFile(ref uniqueRefFile.Ref(), entryPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); nacpFile = uniqueRefFile.Release(); } catch (Exception ex) { OnLoadingException(ex, parentItem); var message = LocalizationManager.Instance.Current.Keys.LoadingError_FailedToOpenNacpFile.SafeFormat(ex.Message); parentItem.Errors.Add(TREE_LOADING_CATEGORY, message); _logger.LogError(ex, message); continue; } ApplicationControlProperty nacp; try { var blitStruct = new BlitStruct <ApplicationControlProperty>(1); nacpFile.Read(out _, 0, blitStruct.ByteSpan).ThrowIfFailure(); nacp = blitStruct.Value; } catch (Exception ex) { OnLoadingException(ex, parentItem); var message = LocalizationManager.Instance.Current.Keys.LoadingError_FailedToLoadNacpFile.SafeFormat(ex.Message); parentItem.Errors.Add(TREE_LOADING_CATEGORY, message); _logger.LogError(ex, message); continue; } parentItem.ChildItems.Add(new NacpItem(nacp, parentItem, directoryEntry)); } // CNMT File else if (parentItem.ParentItem.ContentType == NcaContentType.Meta && entryName.EndsWith(".cnmt", StringComparison.OrdinalIgnoreCase) && directoryEntry.Type == DirectoryEntryType.File) { IFile cnmtFile; try { using var uniqueRefFile = new UniqueRef <IFile>(); fileSystem.OpenFile(ref uniqueRefFile.Ref(), entryPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); cnmtFile = uniqueRefFile.Release(); } catch (Exception ex) { OnLoadingException(ex, parentItem); var message = LocalizationManager.Instance.Current.Keys.LoadingError_FailedToOpenCnmtFile.SafeFormat(ex.Message); parentItem.Errors.Add(TREE_LOADING_CATEGORY, message); _logger.LogError(ex, message); continue; } Cnmt cnmt; try { cnmt = new Cnmt(cnmtFile.AsStream()); } catch (Exception ex) { OnLoadingException(ex, parentItem); var message = LocalizationManager.Instance.Current.Keys.LoadingError_FailedToLoadCnmtFile.SafeFormat(ex.Message); parentItem.Errors.Add(TREE_LOADING_CATEGORY, message); _logger.LogError(ex, message); continue; } parentItem.ChildItems.Add(new CnmtItem(cnmt, parentItem, directoryEntry)); } // MAIN file else if (parentItem.ParentItem.ContentType == NcaContentType.Program && string.Equals(entryName, "main", StringComparison.OrdinalIgnoreCase) && directoryEntry.Type == DirectoryEntryType.File) { IFile nsoFile; try { using var uniqueRefFile = new UniqueRef <IFile>(); fileSystem.OpenFile(ref uniqueRefFile.Ref(), entryPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); nsoFile = uniqueRefFile.Release(); } catch (Exception ex) { OnLoadingException(ex, parentItem); var message = LocalizationManager.Instance.Current.Keys.LoadingError_FailedToOpenMainFile.SafeFormat(ex.Message); parentItem.Errors.Add(TREE_LOADING_CATEGORY, message); _logger.LogError(ex, message); continue; } NsoHeader?nsoHeader; try { var nsoReader = new NsoReader(); nsoReader.Initialize(nsoFile).ThrowIfFailure(); nsoHeader = nsoReader.Header; } catch (Exception ex) { OnLoadingException(ex, parentItem); var message = LocalizationManager.Instance.Current.Keys.LoadingError_FailedToLoadMainFile.SafeFormat(ex.Message); parentItem.Errors.Add(TREE_LOADING_CATEGORY, message); _logger.LogError(ex, message); continue; } parentItem.ChildItems.Add(new MainItem(nsoHeader.Value, parentItem, directoryEntry)); } else { var directoryEntryItem = new DirectoryEntryItem(parentItem, directoryEntry); BuildChildItems(directoryEntryItem); parentItem.ChildItems.Add(directoryEntryItem); } } } catch (Exception ex) { OnLoadingException(ex, parentItem); var message = LocalizationManager.Instance.Current.Keys.LoadingError_FailedToLoadSectionContent.SafeFormat(ex.Message); parentItem.Errors.Add(TREE_LOADING_CATEGORY, message); _logger.LogError(ex, message); } }
#pragma warning restore CS0649 #pragma warning restore IDE0044 public GameTableContextMenu(ListStore gameTableStore, BlitStruct <ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem) : this(new Builder("Ryujinx.Ui.GameTableContextMenu.glade"), gameTableStore, controlData, rowIter, virtualFileSystem) { }
public ApplicationLoader(Switch device) { _device = device; _controlData = new BlitStruct <ApplicationControlProperty>(1); }
public GameTableContextMenu(ListStore gameTableStore, BlitStruct <ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem) { _gameTableStore = gameTableStore; _rowIter = rowIter; _virtualFileSystem = virtualFileSystem; _controlData = controlData; MenuItem openSaveUserDir = new MenuItem("Open User Save Directory") { Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0, TooltipText = "Open the folder where the User save for the application is loaded" }; MenuItem openSaveDeviceDir = new MenuItem("Open Device Save Directory") { Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0, TooltipText = "Open the folder where the Device save for the application is loaded" }; MenuItem openSaveBcatDir = new MenuItem("Open BCAT Save Directory") { Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0, TooltipText = "Open the folder where the BCAT save for the application is loaded" }; MenuItem manageTitleUpdates = new MenuItem("Manage Title Updates") { TooltipText = "Open the title update management window" }; MenuItem manageDlc = new MenuItem("Manage DLC") { TooltipText = "Open the DLC management window" }; string ext = System.IO.Path.GetExtension(_gameTableStore.GetValue(_rowIter, 9).ToString()).ToLower(); bool hasNca = ext == ".nca" || ext == ".nsp" || ext == ".pfs0" || ext == ".xci"; MenuItem extractRomFs = new MenuItem("Extract RomFS Section") { Sensitive = hasNca, TooltipText = "Exctact the RomFs section present in the main NCA" }; MenuItem extractExeFs = new MenuItem("Extract ExeFS Section") { Sensitive = hasNca, TooltipText = "Exctact the ExeFs section present in the main NCA" }; MenuItem extractLogo = new MenuItem("Extract Logo Section") { Sensitive = hasNca, TooltipText = "Exctact the Logo section present in the main NCA" }; openSaveUserDir.Activated += OpenSaveUserDir_Clicked; openSaveDeviceDir.Activated += OpenSaveDeviceDir_Clicked; openSaveBcatDir.Activated += OpenSaveBcatDir_Clicked; manageTitleUpdates.Activated += ManageTitleUpdates_Clicked; manageDlc.Activated += ManageDlc_Clicked; extractRomFs.Activated += ExtractRomFs_Clicked; extractExeFs.Activated += ExtractExeFs_Clicked; extractLogo.Activated += ExtractLogo_Clicked; this.Add(openSaveUserDir); this.Add(openSaveDeviceDir); this.Add(openSaveBcatDir); this.Add(new SeparatorMenuItem()); this.Add(manageTitleUpdates); this.Add(manageDlc); this.Add(new SeparatorMenuItem()); this.Add(extractRomFs); this.Add(extractExeFs); this.Add(extractLogo); }