diff options
author | Ac_K <Acoustik666@gmail.com> | 2023-03-31 21:16:46 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-31 21:16:46 +0200 |
commit | 4c2d9ff3ff9d7afb1fd0bd764bee5931fa5f053c (patch) | |
tree | 1245f5ec356551bd20a9594d114d06a53c41d036 | |
parent | 8198b99935f562ffb2fb9a75175a8df24d235152 (diff) |
HLE: Refactoring of ApplicationLoader (#4480)1.1.689
* HLE: Refactoring of ApplicationLoader
* Fix SDL2 Headless
* Addresses gdkchan feedback
* Fixes LoadUnpackedNca RomFS loading
* remove useless casting
* Cleanup and fixe empty application name
* Remove ProcessInfo
* Fixes typo
* ActiveProcess to ActiveApplication
* Update check
* Clean using.
* Use the correct filepath when loading Homebrew.npdm
* Fix NRE in ProcessResult if MetaLoader is null
* Add more checks for valid processId & return success
* Add missing logging statement for npdm error
* Return result for LoadKip()
* Move error logging out of PFS load extension method
This avoids logging "Could not find Main NCA"
followed by "Loading main..." when trying to start hbl.
* Fix GUIs not checking load results
* Fix style and formatting issues
* Fix formatting and wording
* gtk: Refactor LoadApplication()
---------
Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
41 files changed, 1560 insertions, 1315 deletions
diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs index eb22b39e9..3cdb3906f 100644 --- a/Ryujinx.Ava/AppHost.cs +++ b/Ryujinx.Ava/AppHost.cs | |||
@@ -320,10 +320,14 @@ namespace Ryujinx.Ava | |||
320 | 320 | ||
321 | _viewModel.IsGameRunning = true; | 321 | _viewModel.IsGameRunning = true; |
322 | 322 | ||
323 | string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty : $" - {Device.Application.TitleName}"; | 323 | var activeProcess = Device.Processes.ActiveApplication; |
324 | string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty : $" v{Device.Application.DisplayVersion}"; | 324 | var nacp = activeProcess.ApplicationControlProperties; |
325 | string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty : $" ({Device.Application.TitleIdText.ToUpper()})"; | 325 | int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage; |
326 | string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)"; | 326 | |
327 | string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}"; | ||
328 | string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}"; | ||
329 | string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})"; | ||
330 | string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)"; | ||
327 | 331 | ||
328 | Dispatcher.UIThread.InvokeAsync(() => | 332 | Dispatcher.UIThread.InvokeAsync(() => |
329 | { | 333 | { |
@@ -423,9 +427,9 @@ namespace Ryujinx.Ava | |||
423 | 427 | ||
424 | private void Dispose() | 428 | private void Dispose() |
425 | { | 429 | { |
426 | if (Device.Application != null) | 430 | if (Device.Processes != null) |
427 | { | 431 | { |
428 | _viewModel.UpdateGameMetadata(Device.Application.TitleIdText); | 432 | _viewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText); |
429 | } | 433 | } |
430 | 434 | ||
431 | ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; | 435 | ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; |
@@ -539,7 +543,12 @@ namespace Ryujinx.Ava | |||
539 | { | 543 | { |
540 | Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); | 544 | Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); |
541 | 545 | ||
542 | Device.LoadNca(ApplicationPath); | 546 | if (!Device.LoadNca(ApplicationPath)) |
547 | { | ||
548 | Device.Dispose(); | ||
549 | |||
550 | return false; | ||
551 | } | ||
543 | } | 552 | } |
544 | else if (Directory.Exists(ApplicationPath)) | 553 | else if (Directory.Exists(ApplicationPath)) |
545 | { | 554 | { |
@@ -554,13 +563,23 @@ namespace Ryujinx.Ava | |||
554 | { | 563 | { |
555 | Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); | 564 | Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); |
556 | 565 | ||
557 | Device.LoadCart(ApplicationPath, romFsFiles[0]); | 566 | if (!Device.LoadCart(ApplicationPath, romFsFiles[0])) |
567 | { | ||
568 | Device.Dispose(); | ||
569 | |||
570 | return false; | ||
571 | } | ||
558 | } | 572 | } |
559 | else | 573 | else |
560 | { | 574 | { |
561 | Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); | 575 | Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); |
562 | 576 | ||
563 | Device.LoadCart(ApplicationPath); | 577 | if (!Device.LoadCart(ApplicationPath)) |
578 | { | ||
579 | Device.Dispose(); | ||
580 | |||
581 | return false; | ||
582 | } | ||
564 | } | 583 | } |
565 | } | 584 | } |
566 | else if (File.Exists(ApplicationPath)) | 585 | else if (File.Exists(ApplicationPath)) |
@@ -571,7 +590,12 @@ namespace Ryujinx.Ava | |||
571 | { | 590 | { |
572 | Logger.Info?.Print(LogClass.Application, "Loading as XCI."); | 591 | Logger.Info?.Print(LogClass.Application, "Loading as XCI."); |
573 | 592 | ||
574 | Device.LoadXci(ApplicationPath); | 593 | if (!Device.LoadXci(ApplicationPath)) |
594 | { | ||
595 | Device.Dispose(); | ||
596 | |||
597 | return false; | ||
598 | } | ||
575 | 599 | ||
576 | break; | 600 | break; |
577 | } | 601 | } |
@@ -579,7 +603,12 @@ namespace Ryujinx.Ava | |||
579 | { | 603 | { |
580 | Logger.Info?.Print(LogClass.Application, "Loading as NCA."); | 604 | Logger.Info?.Print(LogClass.Application, "Loading as NCA."); |
581 | 605 | ||
582 | Device.LoadNca(ApplicationPath); | 606 | if (!Device.LoadNca(ApplicationPath)) |
607 | { | ||
608 | Device.Dispose(); | ||
609 | |||
610 | return false; | ||
611 | } | ||
583 | 612 | ||
584 | break; | 613 | break; |
585 | } | 614 | } |
@@ -588,7 +617,12 @@ namespace Ryujinx.Ava | |||
588 | { | 617 | { |
589 | Logger.Info?.Print(LogClass.Application, "Loading as NSP."); | 618 | Logger.Info?.Print(LogClass.Application, "Loading as NSP."); |
590 | 619 | ||
591 | Device.LoadNsp(ApplicationPath); | 620 | if (!Device.LoadNsp(ApplicationPath)) |
621 | { | ||
622 | Device.Dispose(); | ||
623 | |||
624 | return false; | ||
625 | } | ||
592 | 626 | ||
593 | break; | 627 | break; |
594 | } | 628 | } |
@@ -598,13 +632,18 @@ namespace Ryujinx.Ava | |||
598 | 632 | ||
599 | try | 633 | try |
600 | { | 634 | { |
601 | Device.LoadProgram(ApplicationPath); | 635 | if (!Device.LoadProgram(ApplicationPath)) |
636 | { | ||
637 | Device.Dispose(); | ||
638 | |||
639 | return false; | ||
640 | } | ||
602 | } | 641 | } |
603 | catch (ArgumentOutOfRangeException) | 642 | catch (ArgumentOutOfRangeException) |
604 | { | 643 | { |
605 | Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); | 644 | Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); |
606 | 645 | ||
607 | Dispose(); | 646 | Device.Dispose(); |
608 | 647 | ||
609 | return false; | 648 | return false; |
610 | } | 649 | } |
@@ -617,14 +656,14 @@ namespace Ryujinx.Ava | |||
617 | { | 656 | { |
618 | Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); | 657 | Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); |
619 | 658 | ||
620 | Dispose(); | 659 | Device.Dispose(); |
621 | 660 | ||
622 | return false; | 661 | return false; |
623 | } | 662 | } |
624 | 663 | ||
625 | DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName); | 664 | DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name); |
626 | 665 | ||
627 | _viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata => | 666 | _viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata => |
628 | { | 667 | { |
629 | appMetadata.LastPlayed = DateTime.UtcNow.ToString(); | 668 | appMetadata.LastPlayed = DateTime.UtcNow.ToString(); |
630 | }); | 669 | }); |
@@ -950,7 +989,7 @@ namespace Ryujinx.Ava | |||
950 | { | 989 | { |
951 | if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen) | 990 | if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen) |
952 | { | 991 | { |
953 | Device.Application.DiskCacheLoadState?.Cancel(); | 992 | Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel(); |
954 | } | 993 | } |
955 | }); | 994 | }); |
956 | 995 | ||
@@ -1088,4 +1127,4 @@ namespace Ryujinx.Ava | |||
1088 | return state; | 1127 | return state; |
1089 | } | 1128 | } |
1090 | } | 1129 | } |
1091 | } \ No newline at end of file | 1130 | } |
diff --git a/Ryujinx.Ava/Common/ApplicationHelper.cs b/Ryujinx.Ava/Common/ApplicationHelper.cs index 276d18745..161ef8596 100644 --- a/Ryujinx.Ava/Common/ApplicationHelper.cs +++ b/Ryujinx.Ava/Common/ApplicationHelper.cs | |||
@@ -19,6 +19,7 @@ using Ryujinx.Common.Logging; | |||
19 | using Ryujinx.HLE.FileSystem; | 19 | using Ryujinx.HLE.FileSystem; |
20 | using Ryujinx.HLE.HOS; | 20 | using Ryujinx.HLE.HOS; |
21 | using Ryujinx.HLE.HOS.Services.Account.Acc; | 21 | using Ryujinx.HLE.HOS.Services.Account.Acc; |
22 | using Ryujinx.Ui.App.Common; | ||
22 | using Ryujinx.Ui.Common.Helper; | 23 | using Ryujinx.Ui.Common.Helper; |
23 | using System; | 24 | using System; |
24 | using System.Buffers; | 25 | using System.Buffers; |
@@ -227,7 +228,7 @@ namespace Ryujinx.Ava.Common | |||
227 | return; | 228 | return; |
228 | } | 229 | } |
229 | 230 | ||
230 | (Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); | 231 | (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); |
231 | if (updatePatchNca != null) | 232 | if (updatePatchNca != null) |
232 | { | 233 | { |
233 | patchNca = updatePatchNca; | 234 | patchNca = updatePatchNca; |
diff --git a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs index a3663af3e..d5ff78545 100644 --- a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs | |||
@@ -1208,10 +1208,10 @@ namespace Ryujinx.Ava.UI.ViewModels | |||
1208 | 1208 | ||
1209 | public void SetUIProgressHandlers(Switch emulationContext) | 1209 | public void SetUIProgressHandlers(Switch emulationContext) |
1210 | { | 1210 | { |
1211 | if (emulationContext.Application.DiskCacheLoadState != null) | 1211 | if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) |
1212 | { | 1212 | { |
1213 | emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler; | 1213 | emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler; |
1214 | emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler; | 1214 | emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler; |
1215 | } | 1215 | } |
1216 | 1216 | ||
1217 | emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; | 1217 | emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; |
@@ -1705,8 +1705,8 @@ namespace Ryujinx.Ava.UI.ViewModels | |||
1705 | 1705 | ||
1706 | if (string.IsNullOrWhiteSpace(titleName)) | 1706 | if (string.IsNullOrWhiteSpace(titleName)) |
1707 | { | 1707 | { |
1708 | LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Application.TitleName); | 1708 | LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name); |
1709 | TitleName = AppHost.Device.Application.TitleName; | 1709 | TitleName = AppHost.Device.Processes.ActiveApplication.Name; |
1710 | } | 1710 | } |
1711 | 1711 | ||
1712 | SwitchToRenderer(startFullscreen); | 1712 | SwitchToRenderer(startFullscreen); |
diff --git a/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs b/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs index dd9e1b961..0798502cd 100644 --- a/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs | |||
@@ -17,6 +17,7 @@ using Ryujinx.Common.Logging; | |||
17 | using Ryujinx.Common.Utilities; | 17 | using Ryujinx.Common.Utilities; |
18 | using Ryujinx.HLE.FileSystem; | 18 | using Ryujinx.HLE.FileSystem; |
19 | using Ryujinx.HLE.HOS; | 19 | using Ryujinx.HLE.HOS; |
20 | using Ryujinx.Ui.App.Common; | ||
20 | using System; | 21 | using System; |
21 | using System.Collections.Generic; | 22 | using System.Collections.Generic; |
22 | using System.IO; | 23 | using System.IO; |
@@ -162,7 +163,7 @@ public class TitleUpdateViewModel : BaseModel | |||
162 | 163 | ||
163 | try | 164 | try |
164 | { | 165 | { |
165 | (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0); | 166 | (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0); |
166 | 167 | ||
167 | if (controlNca != null && patchNca != null) | 168 | if (controlNca != null && patchNca != null) |
168 | { | 169 | { |
diff --git a/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs b/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs index 1c6f4265c..30d411508 100644 --- a/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs | |||
@@ -126,7 +126,7 @@ namespace Ryujinx.Ava.UI.Views.Main | |||
126 | 126 | ||
127 | if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId)) | 127 | if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId)) |
128 | { | 128 | { |
129 | string titleId = ViewModel.AppHost.Device.Application.TitleIdText.ToUpper(); | 129 | string titleId = ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(); |
130 | AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId); | 130 | AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId); |
131 | 131 | ||
132 | await window.ShowDialog(Window); | 132 | await window.ShowDialog(Window); |
@@ -148,13 +148,11 @@ namespace Ryujinx.Ava.UI.Views.Main | |||
148 | return; | 148 | return; |
149 | } | 149 | } |
150 | 150 | ||
151 | ApplicationLoader application = ViewModel.AppHost.Device.Application; | 151 | string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString(); |
152 | if (application != null) | ||
153 | { | ||
154 | await new CheatWindow(Window.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(Window); | ||
155 | 152 | ||
156 | ViewModel.AppHost.Device.EnableCheats(); | 153 | await new CheatWindow(Window.VirtualFileSystem, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, name).ShowDialog(Window); |
157 | } | 154 | |
155 | ViewModel.AppHost.Device.EnableCheats(); | ||
158 | } | 156 | } |
159 | 157 | ||
160 | private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) | 158 | private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) |
diff --git a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index 3f94ce61b..1b3968eab 100644 --- a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | |||
@@ -20,7 +20,6 @@ using System.Collections.Concurrent; | |||
20 | using System.Collections.Generic; | 20 | using System.Collections.Generic; |
21 | using System.IO; | 21 | using System.IO; |
22 | using System.Runtime.CompilerServices; | 22 | using System.Runtime.CompilerServices; |
23 | |||
24 | using Path = System.IO.Path; | 23 | using Path = System.IO.Path; |
25 | using RightsId = LibHac.Fs.RightsId; | 24 | using RightsId = LibHac.Fs.RightsId; |
26 | 25 | ||
@@ -146,6 +145,7 @@ namespace Ryujinx.HLE.FileSystem | |||
146 | 145 | ||
147 | return $"{basePath}:/{fileName}"; | 146 | return $"{basePath}:/{fileName}"; |
148 | } | 147 | } |
148 | |||
149 | return null; | 149 | return null; |
150 | } | 150 | } |
151 | 151 | ||
@@ -191,7 +191,7 @@ namespace Ryujinx.HLE.FileSystem | |||
191 | fsServerClient = horizon.CreatePrivilegedHorizonClient(); | 191 | fsServerClient = horizon.CreatePrivilegedHorizonClient(); |
192 | var fsServer = new FileSystemServer(fsServerClient); | 192 | var fsServer = new FileSystemServer(fsServerClient); |
193 | 193 | ||
194 | RandomDataGenerator randomGenerator = buffer => Random.Shared.NextBytes(buffer); | 194 | RandomDataGenerator randomGenerator = Random.Shared.NextBytes; |
195 | 195 | ||
196 | DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator); | 196 | DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator); |
197 | 197 | ||
@@ -264,7 +264,7 @@ namespace Ryujinx.HLE.FileSystem | |||
264 | 264 | ||
265 | if (result.IsSuccess()) | 265 | if (result.IsSuccess()) |
266 | { | 266 | { |
267 | Ticket ticket = new Ticket(ticketFile.Get.AsStream()); | 267 | Ticket ticket = new(ticketFile.Get.AsStream()); |
268 | var titleKey = ticket.GetTitleKey(KeySet); | 268 | var titleKey = ticket.GetTitleKey(KeySet); |
269 | 269 | ||
270 | if (titleKey != null) | 270 | if (titleKey != null) |
diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs deleted file mode 100644 index 82bd9b312..000000000 --- a/Ryujinx.HLE/HOS/ApplicationLoader.cs +++ /dev/null | |||
@@ -1,908 +0,0 @@ | |||
1 | using LibHac; | ||
2 | using LibHac.Account; | ||
3 | using LibHac.Common; | ||
4 | using LibHac.Fs; | ||
5 | using LibHac.Fs.Fsa; | ||
6 | using LibHac.Fs.Shim; | ||
7 | using LibHac.FsSystem; | ||
8 | using LibHac.Loader; | ||
9 | using LibHac.Ncm; | ||
10 | using LibHac.Ns; | ||
11 | using LibHac.Tools.Fs; | ||
12 | using LibHac.Tools.FsSystem; | ||
13 | using LibHac.Tools.FsSystem.NcaUtils; | ||
14 | using Ryujinx.Common.Configuration; | ||
15 | using Ryujinx.Common.Logging; | ||
16 | using Ryujinx.Cpu; | ||
17 | using Ryujinx.HLE.FileSystem; | ||
18 | using Ryujinx.HLE.Loaders.Executables; | ||
19 | using Ryujinx.Memory; | ||
20 | using System; | ||
21 | using System.Collections.Generic; | ||
22 | using System.Globalization; | ||
23 | using System.IO; | ||
24 | using System.Linq; | ||
25 | using System.Reflection; | ||
26 | using System.Text; | ||
27 | using static Ryujinx.HLE.HOS.ModLoader; | ||
28 | using ApplicationId = LibHac.Ncm.ApplicationId; | ||
29 | using Path = System.IO.Path; | ||
30 | |||
31 | namespace Ryujinx.HLE.HOS | ||
32 | { | ||
33 | using JsonHelper = Common.Utilities.JsonHelper; | ||
34 | |||
35 | public class ApplicationLoader | ||
36 | { | ||
37 | // Binaries from exefs are loaded into mem in this order. Do not change. | ||
38 | internal static readonly string[] ExeFsPrefixes = | ||
39 | { | ||
40 | "rtld", | ||
41 | "main", | ||
42 | "subsdk0", | ||
43 | "subsdk1", | ||
44 | "subsdk2", | ||
45 | "subsdk3", | ||
46 | "subsdk4", | ||
47 | "subsdk5", | ||
48 | "subsdk6", | ||
49 | "subsdk7", | ||
50 | "subsdk8", | ||
51 | "subsdk9", | ||
52 | "sdk" | ||
53 | }; | ||
54 | |||
55 | private readonly Switch _device; | ||
56 | private string _titleName; | ||
57 | private string _displayVersion; | ||
58 | private BlitStruct<ApplicationControlProperty> _controlData; | ||
59 | |||
60 | public BlitStruct<ApplicationControlProperty> ControlData => _controlData; | ||
61 | public string TitleName => _titleName; | ||
62 | public string DisplayVersion => _displayVersion; | ||
63 | |||
64 | public ulong TitleId { get; private set; } | ||
65 | public bool TitleIs64Bit { get; private set; } | ||
66 | |||
67 | public string TitleIdText => TitleId.ToString("x16"); | ||
68 | |||
69 | public IDiskCacheLoadState DiskCacheLoadState { get; private set; } | ||
70 | |||
71 | public ApplicationLoader(Switch device) | ||
72 | { | ||
73 | _device = device; | ||
74 | _controlData = new BlitStruct<ApplicationControlProperty>(1); | ||
75 | } | ||
76 | |||
77 | public void LoadCart(string exeFsDir, string romFsFile = null) | ||
78 | { | ||
79 | LocalFileSystem codeFs = new LocalFileSystem(exeFsDir); | ||
80 | |||
81 | MetaLoader metaData = ReadNpdm(codeFs); | ||
82 | |||
83 | _device.Configuration.VirtualFileSystem.ModLoader.CollectMods( | ||
84 | new[] { TitleId }, | ||
85 | _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), | ||
86 | _device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath()); | ||
87 | |||
88 | if (TitleId != 0) | ||
89 | { | ||
90 | EnsureSaveData(new ApplicationId(TitleId)); | ||
91 | } | ||
92 | |||
93 | ulong pid = LoadExeFs(codeFs, string.Empty, metaData); | ||
94 | |||
95 | if (romFsFile != null) | ||
96 | { | ||
97 | _device.Configuration.VirtualFileSystem.LoadRomFs(pid, romFsFile); | ||
98 | } | ||
99 | } | ||
100 | |||
101 | public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex) | ||
102 | { | ||
103 | Nca mainNca = null; | ||
104 | Nca patchNca = null; | ||
105 | Nca controlNca = null; | ||
106 | |||
107 | fileSystem.ImportTickets(pfs); | ||
108 | |||
109 | foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) | ||
110 | { | ||
111 | using var ncaFile = new UniqueRef<IFile>(); | ||
112 | |||
113 | pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); | ||
114 | |||
115 | Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage()); | ||
116 | |||
117 | int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF); | ||
118 | |||
119 | if (ncaProgramIndex != programIndex) | ||
120 | { | ||
121 | continue; | ||
122 | } | ||
123 | |||
124 | if (nca.Header.ContentType == NcaContentType.Program) | ||
125 | { | ||
126 | int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); | ||
127 | |||
128 | if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()) | ||
129 | { | ||
130 | patchNca = nca; | ||
131 | } | ||
132 | else | ||
133 | { | ||
134 | mainNca = nca; | ||
135 | } | ||
136 | } | ||
137 | else if (nca.Header.ContentType == NcaContentType.Control) | ||
138 | { | ||
139 | controlNca = nca; | ||
140 | } | ||
141 | } | ||
142 | |||
143 | return (mainNca, patchNca, controlNca); | ||
144 | } | ||
145 | |||
146 | public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex) | ||
147 | { | ||
148 | Nca patchNca = null; | ||
149 | Nca controlNca = null; | ||
150 | |||
151 | fileSystem.ImportTickets(pfs); | ||
152 | |||
153 | foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) | ||
154 | { | ||
155 | using var ncaFile = new UniqueRef<IFile>(); | ||
156 | |||
157 | pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); | ||
158 | |||
159 | Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage()); | ||
160 | |||
161 | int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF); | ||
162 | |||
163 | if (ncaProgramIndex != programIndex) | ||
164 | { | ||
165 | continue; | ||
166 | } | ||
167 | |||
168 | if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId) | ||
169 | { | ||
170 | break; | ||
171 | } | ||
172 | |||
173 | if (nca.Header.ContentType == NcaContentType.Program) | ||
174 | { | ||
175 | patchNca = nca; | ||
176 | } | ||
177 | else if (nca.Header.ContentType == NcaContentType.Control) | ||
178 | { | ||
179 | controlNca = nca; | ||
180 | } | ||
181 | } | ||
182 | |||
183 | return (patchNca, controlNca); | ||
184 | } | ||
185 | |||
186 | public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath) | ||
187 | { | ||
188 | updatePath = null; | ||
189 | |||
190 | if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase)) | ||
191 | { | ||
192 | // Clear the program index part. | ||
193 | titleIdBase &= 0xFFFFFFFFFFFFFFF0; | ||
194 | |||
195 | // Load update informations if existing. | ||
196 | string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json"); | ||
197 | |||
198 | if (File.Exists(titleUpdateMetadataPath)) | ||
199 | { | ||
200 | updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected; | ||
201 | |||
202 | if (File.Exists(updatePath)) | ||
203 | { | ||
204 | FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read); | ||
205 | PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); | ||
206 | |||
207 | return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex); | ||
208 | } | ||
209 | } | ||
210 | } | ||
211 | |||
212 | return (null, null); | ||
213 | } | ||
214 | |||
215 | public void LoadXci(string xciFile) | ||
216 | { | ||
217 | FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read); | ||
218 | Xci xci = new Xci(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage()); | ||
219 | |||
220 | if (!xci.HasPartition(XciPartitionType.Secure)) | ||
221 | { | ||
222 | Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI secure partition"); | ||
223 | |||
224 | return; | ||
225 | } | ||
226 | |||
227 | PartitionFileSystem securePartition = xci.OpenPartition(XciPartitionType.Secure); | ||
228 | |||
229 | Nca mainNca; | ||
230 | Nca patchNca; | ||
231 | Nca controlNca; | ||
232 | |||
233 | try | ||
234 | { | ||
235 | (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index); | ||
236 | |||
237 | RegisterProgramMapInfo(securePartition).ThrowIfFailure(); | ||
238 | } | ||
239 | catch (Exception e) | ||
240 | { | ||
241 | Logger.Error?.Print(LogClass.Loader, $"Unable to load XCI: {e.Message}"); | ||
242 | |||
243 | return; | ||
244 | } | ||
245 | |||
246 | if (mainNca == null) | ||
247 | { | ||
248 | Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find Main NCA"); | ||
249 | |||
250 | return; | ||
251 | } | ||
252 | |||
253 | _device.Configuration.ContentManager.LoadEntries(_device); | ||
254 | _device.Configuration.ContentManager.ClearAocData(); | ||
255 | _device.Configuration.ContentManager.AddAocData(securePartition, xciFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel); | ||
256 | |||
257 | LoadNca(mainNca, patchNca, controlNca); | ||
258 | } | ||
259 | |||
260 | public void LoadNsp(string nspFile) | ||
261 | { | ||
262 | FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read); | ||
263 | PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); | ||
264 | |||
265 | Nca mainNca; | ||
266 | Nca patchNca; | ||
267 | Nca controlNca; | ||
268 | |||
269 | try | ||
270 | { | ||
271 | (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index); | ||
272 | |||
273 | RegisterProgramMapInfo(nsp).ThrowIfFailure(); | ||
274 | } | ||
275 | catch (Exception e) | ||
276 | { | ||
277 | Logger.Error?.Print(LogClass.Loader, $"Unable to load NSP: {e.Message}"); | ||
278 | |||
279 | return; | ||
280 | } | ||
281 | |||
282 | if (mainNca != null) | ||
283 | { | ||
284 | _device.Configuration.ContentManager.ClearAocData(); | ||
285 | _device.Configuration.ContentManager.AddAocData(nsp, nspFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel); | ||
286 | |||
287 | LoadNca(mainNca, patchNca, controlNca); | ||
288 | |||
289 | return; | ||
290 | } | ||
291 | |||
292 | // This is not a normal NSP, it's actually a ExeFS as a NSP | ||
293 | LoadExeFs(nsp, null, isHomebrew: true); | ||
294 | } | ||
295 | |||
296 | public void LoadNca(string ncaFile) | ||
297 | { | ||
298 | FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read); | ||
299 | Nca nca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); | ||
300 | |||
301 | LoadNca(nca, null, null); | ||
302 | } | ||
303 | |||
304 | public void LoadServiceNca(string ncaFile) | ||
305 | { | ||
306 | FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read); | ||
307 | Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); | ||
308 | |||
309 | if (mainNca.Header.ContentType != NcaContentType.Program) | ||
310 | { | ||
311 | Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA"); | ||
312 | |||
313 | return; | ||
314 | } | ||
315 | |||
316 | IFileSystem codeFs = null; | ||
317 | |||
318 | if (mainNca.CanOpenSection(NcaSectionType.Code)) | ||
319 | { | ||
320 | codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel); | ||
321 | } | ||
322 | |||
323 | if (codeFs == null) | ||
324 | { | ||
325 | Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA"); | ||
326 | |||
327 | return; | ||
328 | } | ||
329 | |||
330 | using var npdmFile = new UniqueRef<IFile>(); | ||
331 | |||
332 | Result result = codeFs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read); | ||
333 | |||
334 | MetaLoader metaData; | ||
335 | |||
336 | npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure(); | ||
337 | |||
338 | var npdmBuffer = new byte[fileSize]; | ||
339 | npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure(); | ||
340 | |||
341 | metaData = new MetaLoader(); | ||
342 | metaData.Load(npdmBuffer).ThrowIfFailure(); | ||
343 | |||
344 | NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length]; | ||
345 | |||
346 | for (int i = 0; i < nsos.Length; i++) | ||
347 | { | ||
348 | string name = ExeFsPrefixes[i]; | ||
349 | |||
350 | if (!codeFs.FileExists($"/{name}")) | ||
351 | { | ||
352 | continue; // File doesn't exist, skip. | ||
353 | } | ||
354 | |||
355 | Logger.Info?.Print(LogClass.Loader, $"Loading {name}..."); | ||
356 | |||
357 | using var nsoFile = new UniqueRef<IFile>(); | ||
358 | |||
359 | codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure(); | ||
360 | |||
361 | nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name); | ||
362 | } | ||
363 | |||
364 | // Collect the nsos, ignoring ones that aren't used. | ||
365 | NsoExecutable[] programs = nsos.Where(x => x != null).ToArray(); | ||
366 | |||
367 | string displayVersion = _device.System.ContentManager.GetCurrentFirmwareVersion().VersionString; | ||
368 | bool usePtc = _device.System.EnablePtc; | ||
369 | |||
370 | metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); | ||
371 | ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit: false); | ||
372 | ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs); | ||
373 | |||
374 | string titleIdText = npdm.Aci.ProgramId.Value.ToString("x16"); | ||
375 | bool titleIs64Bit = (npdm.Meta.Flags & 1) != 0; | ||
376 | |||
377 | string programName = Encoding.ASCII.GetString(npdm.Meta.ProgramName).TrimEnd('\0'); | ||
378 | |||
379 | Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]"); | ||
380 | } | ||
381 | |||
382 | private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca) | ||
383 | { | ||
384 | if (mainNca.Header.ContentType != NcaContentType.Program) | ||
385 | { | ||
386 | Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA"); | ||
387 | |||
388 | return; | ||
389 | } | ||
390 | |||
391 | IStorage dataStorage = null; | ||
392 | IFileSystem codeFs = null; | ||
393 | |||
394 | (Nca updatePatchNca, Nca updateControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), _device.Configuration.UserChannelPersistence.Index, out _); | ||
395 | |||
396 | if (updatePatchNca != null) | ||
397 | { | ||
398 | patchNca = updatePatchNca; | ||
399 | } | ||
400 | |||
401 | if (updateControlNca != null) | ||
402 | { | ||
403 | controlNca = updateControlNca; | ||
404 | } | ||
405 | |||
406 | // Load program 0 control NCA as we are going to need it for display version. | ||
407 | (_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _); | ||
408 | |||
409 | // Load Aoc | ||
410 | string titleAocMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json"); | ||
411 | |||
412 | if (File.Exists(titleAocMetadataPath)) | ||
413 | { | ||
414 | List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(titleAocMetadataPath); | ||
415 | |||
416 | foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList) | ||
417 | { | ||
418 | foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) | ||
419 | { | ||
420 | if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled) | ||
421 | { | ||
422 | _device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath); | ||
423 | } | ||
424 | else | ||
425 | { | ||
426 | Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed."); | ||
427 | } | ||
428 | } | ||
429 | } | ||
430 | } | ||
431 | |||
432 | if (patchNca == null) | ||
433 | { | ||
434 | if (mainNca.CanOpenSection(NcaSectionType.Data)) | ||
435 | { | ||
436 | dataStorage = mainNca.OpenStorage(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); | ||
437 | } | ||
438 | |||
439 | if (mainNca.CanOpenSection(NcaSectionType.Code)) | ||
440 | { | ||
441 | codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel); | ||
442 | } | ||
443 | } | ||
444 | else | ||
445 | { | ||
446 | if (patchNca.CanOpenSection(NcaSectionType.Data)) | ||
447 | { | ||
448 | dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); | ||
449 | } | ||
450 | |||
451 | if (patchNca.CanOpenSection(NcaSectionType.Code)) | ||
452 | { | ||
453 | codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, _device.System.FsIntegrityCheckLevel); | ||
454 | } | ||
455 | } | ||
456 | |||
457 | if (codeFs == null) | ||
458 | { | ||
459 | Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA"); | ||
460 | |||
461 | return; | ||
462 | } | ||
463 | |||
464 | MetaLoader metaData = ReadNpdm(codeFs); | ||
465 | |||
466 | _device.Configuration.VirtualFileSystem.ModLoader.CollectMods( | ||
467 | _device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId), | ||
468 | _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), | ||
469 | _device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath()); | ||
470 | |||
471 | string displayVersion = string.Empty; | ||
472 | |||
473 | if (controlNca != null) | ||
474 | { | ||
475 | ReadControlData(_device, controlNca, ref _controlData, ref _titleName, ref displayVersion); | ||
476 | } | ||
477 | else | ||
478 | { | ||
479 | ControlData.ByteSpan.Clear(); | ||
480 | } | ||
481 | |||
482 | // NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application. | ||
483 | // BODY: As such, to avoid PTC cache confusion, we only trust the the program 0 display version when launching a sub program. | ||
484 | if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0) | ||
485 | { | ||
486 | string dummyTitleName = ""; | ||
487 | BlitStruct<ApplicationControlProperty> dummyControl = new BlitStruct<ApplicationControlProperty>(1); | ||
488 | |||
489 | ReadControlData(_device, updateProgram0ControlNca, ref dummyControl, ref dummyTitleName, ref displayVersion); | ||
490 | } | ||
491 | |||
492 | _displayVersion = displayVersion; | ||
493 | |||
494 | ulong pid = LoadExeFs(codeFs, displayVersion, metaData); | ||
495 | |||
496 | if (dataStorage == null) | ||
497 | { | ||
498 | Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA"); | ||
499 | } | ||
500 | else | ||
501 | { | ||
502 | IStorage newStorage = _device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(TitleId, dataStorage); | ||
503 | |||
504 | _device.Configuration.VirtualFileSystem.SetRomFs(pid, newStorage.AsStream(FileAccess.Read)); | ||
505 | } | ||
506 | |||
507 | // Don't create save data for system programs. | ||
508 | if (TitleId != 0 && (TitleId < SystemProgramId.Start.Value || TitleId > SystemAppletId.End.Value)) | ||
509 | { | ||
510 | // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble. | ||
511 | // We'll know if this changes in the future because stuff will get errors when trying to mount the correct save. | ||
512 | EnsureSaveData(new ApplicationId(TitleId & ~0xFul)); | ||
513 | } | ||
514 | |||
515 | Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {TitleName} v{DisplayVersion} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]"); | ||
516 | } | ||
517 | |||
518 | // Sets TitleId, so be sure to call before using it | ||
519 | private MetaLoader ReadNpdm(IFileSystem fs) | ||
520 | { | ||
521 | using var npdmFile = new UniqueRef<IFile>(); | ||
522 | |||
523 | Result result = fs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read); | ||
524 | |||
525 | MetaLoader metaData; | ||
526 | |||
527 | if (ResultFs.PathNotFound.Includes(result)) | ||
528 | { | ||
529 | Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!"); | ||
530 | |||
531 | metaData = GetDefaultNpdm(); | ||
532 | } | ||
533 | else | ||
534 | { | ||
535 | npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure(); | ||
536 | |||
537 | var npdmBuffer = new byte[fileSize]; | ||
538 | npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure(); | ||
539 | |||
540 | metaData = new MetaLoader(); | ||
541 | metaData.Load(npdmBuffer).ThrowIfFailure(); | ||
542 | } | ||
543 | |||
544 | metaData.GetNpdm(out var npdm).ThrowIfFailure(); | ||
545 | |||
546 | TitleId = npdm.Aci.ProgramId.Value; | ||
547 | TitleIs64Bit = (npdm.Meta.Flags & 1) != 0; | ||
548 | _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId); | ||
549 | |||
550 | return metaData; | ||
551 | } | ||
552 | |||
553 | private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct<ApplicationControlProperty> controlData, ref string titleName, ref string displayVersion) | ||
554 | { | ||
555 | using var controlFile = new UniqueRef<IFile>(); | ||
556 | |||
557 | IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel); | ||
558 | Result result = controlFs.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read); | ||
559 | |||
560 | if (result.IsSuccess()) | ||
561 | { | ||
562 | result = controlFile.Get.Read(out long bytesRead, 0, controlData.ByteSpan, ReadOption.None); | ||
563 | |||
564 | if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length) | ||
565 | { | ||
566 | titleName = controlData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString(); | ||
567 | |||
568 | if (string.IsNullOrWhiteSpace(titleName)) | ||
569 | { | ||
570 | titleName = controlData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString(); | ||
571 | } | ||
572 | |||
573 | displayVersion = controlData.Value.DisplayVersionString.ToString(); | ||
574 | } | ||
575 | } | ||
576 | else | ||
577 | { | ||
578 | controlData.ByteSpan.Clear(); | ||
579 | } | ||
580 | } | ||
581 | |||
582 | private ulong LoadExeFs(IFileSystem codeFs, string displayVersion, MetaLoader metaData = null, bool isHomebrew = false) | ||
583 | { | ||
584 | if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs)) | ||
585 | { | ||
586 | metaData = null; // TODO: Check if we should retain old npdm. | ||
587 | } | ||
588 | |||
589 | metaData ??= ReadNpdm(codeFs); | ||
590 | |||
591 | NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length]; | ||
592 | |||
593 | for (int i = 0; i < nsos.Length; i++) | ||
594 | { | ||
595 | string name = ExeFsPrefixes[i]; | ||
596 | |||
597 | if (!codeFs.FileExists($"/{name}")) | ||
598 | { | ||
599 | continue; // File doesn't exist, skip. | ||
600 | } | ||
601 | |||
602 | Logger.Info?.Print(LogClass.Loader, $"Loading {name}..."); | ||
603 | |||
604 | using var nsoFile = new UniqueRef<IFile>(); | ||
605 | |||
606 | codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure(); | ||
607 | |||
608 | nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name); | ||
609 | } | ||
610 | |||
611 | // ExeFs file replacements. | ||
612 | ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos); | ||
613 | |||
614 | // Collect the nsos, ignoring ones that aren't used. | ||
615 | NsoExecutable[] programs = nsos.Where(x => x != null).ToArray(); | ||
616 | |||
617 | // Take the npdm from mods if present. | ||
618 | if (modLoadResult.Npdm != null) | ||
619 | { | ||
620 | metaData = modLoadResult.Npdm; | ||
621 | } | ||
622 | |||
623 | _device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(TitleId, programs); | ||
624 | |||
625 | _device.Configuration.ContentManager.LoadEntries(_device); | ||
626 | |||
627 | bool usePtc = _device.System.EnablePtc; | ||
628 | |||
629 | // Don't use PPTC if ExeFs files have been replaced. | ||
630 | usePtc &= !modLoadResult.Modified; | ||
631 | |||
632 | if (_device.System.EnablePtc && !usePtc) | ||
633 | { | ||
634 | Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PPTC disabled."); | ||
635 | } | ||
636 | |||
637 | Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText; | ||
638 | _device.Gpu.HostInitalized.Set(); | ||
639 | |||
640 | MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode; | ||
641 | |||
642 | if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible)) | ||
643 | { | ||
644 | memoryManagerMode = MemoryManagerMode.SoftwarePageTable; | ||
645 | } | ||
646 | |||
647 | // We allow it for nx-hbloader because it can be used to launch homebrew. | ||
648 | bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL || isHomebrew; | ||
649 | |||
650 | metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); | ||
651 | ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit); | ||
652 | ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs); | ||
653 | |||
654 | DiskCacheLoadState = result.DiskCacheLoadState; | ||
655 | |||
656 | _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine); | ||
657 | |||
658 | return result.ProcessId; | ||
659 | } | ||
660 | |||
661 | public void LoadProgram(string filePath) | ||
662 | { | ||
663 | MetaLoader metaData = GetDefaultNpdm(); | ||
664 | metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); | ||
665 | ProgramInfo programInfo = new ProgramInfo(in npdm, string.Empty, diskCacheEnabled: false, allowCodeMemoryForJit: true); | ||
666 | |||
667 | bool isNro = Path.GetExtension(filePath).ToLower() == ".nro"; | ||
668 | |||
669 | IExecutable executable; | ||
670 | Stream romfsStream = null; | ||
671 | |||
672 | if (isNro) | ||
673 | { | ||
674 | FileStream input = new FileStream(filePath, FileMode.Open); | ||
675 | NroExecutable obj = new NroExecutable(input.AsStorage()); | ||
676 | |||
677 | executable = obj; | ||
678 | |||
679 | // Homebrew NRO can actually have some data after the actual NRO. | ||
680 | if (input.Length > obj.FileSize) | ||
681 | { | ||
682 | input.Position = obj.FileSize; | ||
683 | |||
684 | BinaryReader reader = new BinaryReader(input); | ||
685 | |||
686 | uint asetMagic = reader.ReadUInt32(); | ||
687 | if (asetMagic == 0x54455341) | ||
688 | { | ||
689 | uint asetVersion = reader.ReadUInt32(); | ||
690 | if (asetVersion == 0) | ||
691 | { | ||
692 | ulong iconOffset = reader.ReadUInt64(); | ||
693 | ulong iconSize = reader.ReadUInt64(); | ||
694 | |||
695 | ulong nacpOffset = reader.ReadUInt64(); | ||
696 | ulong nacpSize = reader.ReadUInt64(); | ||
697 | |||
698 | ulong romfsOffset = reader.ReadUInt64(); | ||
699 | ulong romfsSize = reader.ReadUInt64(); | ||
700 | |||
701 | if (romfsSize != 0) | ||
702 | { | ||
703 | romfsStream = new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset); | ||
704 | } | ||
705 | |||
706 | if (nacpSize != 0) | ||
707 | { | ||
708 | input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin); | ||
709 | |||
710 | reader.Read(ControlData.ByteSpan); | ||
711 | |||
712 | ref ApplicationControlProperty nacp = ref ControlData.Value; | ||
713 | |||
714 | programInfo.Name = nacp.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString(); | ||
715 | |||
716 | if (string.IsNullOrWhiteSpace(programInfo.Name)) | ||
717 | { | ||
718 | programInfo.Name = nacp.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString(); | ||
719 | } | ||
720 | |||
721 | if (nacp.PresenceGroupId != 0) | ||
722 | { | ||
723 | programInfo.ProgramId = nacp.PresenceGroupId; | ||
724 | } | ||
725 | else if (nacp.SaveDataOwnerId != 0) | ||
726 | { | ||
727 | programInfo.ProgramId = nacp.SaveDataOwnerId; | ||
728 | } | ||
729 | else if (nacp.AddOnContentBaseId != 0) | ||
730 | { | ||
731 | programInfo.ProgramId = nacp.AddOnContentBaseId - 0x1000; | ||
732 | } | ||
733 | else | ||
734 | { | ||
735 | programInfo.ProgramId = 0000000000000000; | ||
736 | } | ||
737 | } | ||
738 | } | ||
739 | else | ||
740 | { | ||
741 | Logger.Warning?.Print(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\""); | ||
742 | } | ||
743 | } | ||
744 | } | ||
745 | } | ||
746 | else | ||
747 | { | ||
748 | executable = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read), Path.GetFileNameWithoutExtension(filePath)); | ||
749 | } | ||
750 | |||
751 | _device.Configuration.ContentManager.LoadEntries(_device); | ||
752 | |||
753 | _titleName = programInfo.Name; | ||
754 | TitleId = programInfo.ProgramId; | ||
755 | TitleIs64Bit = (npdm.Meta.Flags & 1) != 0; | ||
756 | _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId); | ||
757 | |||
758 | // Explicitly null titleid to disable the shader cache. | ||
759 | Graphics.Gpu.GraphicsConfig.TitleId = null; | ||
760 | _device.Gpu.HostInitalized.Set(); | ||
761 | |||
762 | ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: executable); | ||
763 | |||
764 | if (romfsStream != null) | ||
765 | { | ||
766 | _device.Configuration.VirtualFileSystem.SetRomFs(result.ProcessId, romfsStream); | ||
767 | } | ||
768 | |||
769 | DiskCacheLoadState = result.DiskCacheLoadState; | ||
770 | |||
771 | _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine); | ||
772 | } | ||
773 | |||
774 | private MetaLoader GetDefaultNpdm() | ||
775 | { | ||
776 | Assembly asm = Assembly.GetCallingAssembly(); | ||
777 | |||
778 | using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm")) | ||
779 | { | ||
780 | var npdmBuffer = new byte[npdmStream.Length]; | ||
781 | npdmStream.Read(npdmBuffer); | ||
782 | |||
783 | var metaLoader = new MetaLoader(); | ||
784 | metaLoader.Load(npdmBuffer).ThrowIfFailure(); | ||
785 | |||
786 | return metaLoader; | ||
787 | } | ||
788 | } | ||
789 | |||
790 | private static (ulong applicationId, int programCount) GetMultiProgramInfo(VirtualFileSystem fileSystem, PartitionFileSystem pfs) | ||
791 | { | ||
792 | ulong mainProgramId = 0; | ||
793 | Span<bool> hasIndex = stackalloc bool[0x10]; | ||
794 | |||
795 | fileSystem.ImportTickets(pfs); | ||
796 | |||
797 | foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) | ||
798 | { | ||
799 | using var ncaFile = new UniqueRef<IFile>(); | ||
800 | |||
801 | pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); | ||
802 | |||
803 | Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage()); | ||
804 | |||
805 | if (nca.Header.ContentType != NcaContentType.Program) | ||
806 | { | ||
807 | continue; | ||
808 | } | ||
809 | |||
810 | int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); | ||
811 | |||
812 | if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()) | ||
813 | { | ||
814 | continue; | ||
815 | } | ||
816 | |||
817 | ulong currentProgramId = nca.Header.TitleId; | ||
818 | ulong currentMainProgramId = currentProgramId & ~0xFFFul; | ||
819 | |||
820 | if (mainProgramId == 0 && currentMainProgramId != 0) | ||
821 | { | ||
822 | mainProgramId = currentMainProgramId; | ||
823 | } | ||
824 | |||
825 | if (mainProgramId != currentMainProgramId) | ||
826 | { | ||
827 | // As far as I know there aren't any multi-application game cards containing multi-program applications, | ||
828 | // so because multi-application game cards are the only way we should run into multiple applications | ||
829 | // we'll just return that there's a single program. | ||
830 | return (mainProgramId, 1); | ||
831 | } | ||
832 | |||
833 | hasIndex[(int)(currentProgramId & 0xF)] = true; | ||
834 | } | ||
835 | |||
836 | int programCount = 0; | ||
837 | |||
838 | for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++) | ||
839 | { | ||
840 | programCount++; | ||
841 | } | ||
842 | |||
843 | return (mainProgramId, programCount); | ||
844 | } | ||
845 | |||
846 | private Result RegisterProgramMapInfo(PartitionFileSystem pfs) | ||
847 | { | ||
848 | (ulong applicationId, int programCount) = GetMultiProgramInfo(_device.Configuration.VirtualFileSystem, pfs); | ||
849 | |||
850 | if (programCount <= 0) | ||
851 | return Result.Success; | ||
852 | |||
853 | Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10]; | ||
854 | |||
855 | for (int i = 0; i < programCount; i++) | ||
856 | { | ||
857 | mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i); | ||
858 | mapInfo[i].MainProgramId = new ApplicationId(applicationId); | ||
859 | mapInfo[i].ProgramIndex = (byte)i; | ||
860 | } | ||
861 | |||
862 | return _device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo.Slice(0, programCount)); | ||
863 | } | ||
864 | |||
865 | private Result EnsureSaveData(ApplicationId applicationId) | ||
866 | { | ||
867 | Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists."); | ||
868 | |||
869 | Uid user = _device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid(); | ||
870 | |||
871 | ref ApplicationControlProperty control = ref ControlData.Value; | ||
872 | |||
873 | if (LibHac.Common.Utilities.IsZeros(ControlData.ByteSpan)) | ||
874 | { | ||
875 | // If the current application doesn't have a loaded control property, create a dummy one | ||
876 | // and set the savedata sizes so a user savedata will be created. | ||
877 | control = ref new BlitStruct<ApplicationControlProperty>(1).Value; | ||
878 | |||
879 | // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. | ||
880 | control.UserAccountSaveDataSize = 0x4000; | ||
881 | control.UserAccountSaveDataJournalSize = 0x4000; | ||
882 | control.SaveDataOwnerId = applicationId.Value; | ||
883 | |||
884 | Logger.Warning?.Print(LogClass.Application, | ||
885 | "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); | ||
886 | } | ||
887 | |||
888 | HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient; | ||
889 | Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control); | ||
890 | |||
891 | if (resultCode.IsFailure()) | ||
892 | { | ||
893 | Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}"); | ||
894 | |||
895 | return resultCode; | ||
896 | } | ||
897 | |||
898 | resultCode = hos.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in user); | ||
899 | |||
900 | if (resultCode.IsFailure()) | ||
901 | { | ||
902 | Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}"); | ||
903 | } | ||
904 | |||
905 | return resultCode; | ||
906 | } | ||
907 | } | ||
908 | } | ||
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 2b77a7c2e..1639532ed 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs | |||
@@ -35,6 +35,7 @@ using Ryujinx.HLE.HOS.Services.SurfaceFlinger; | |||
35 | using Ryujinx.HLE.HOS.Services.Time.Clock; | 35 | using Ryujinx.HLE.HOS.Services.Time.Clock; |
36 | using Ryujinx.HLE.HOS.SystemState; | 36 | using Ryujinx.HLE.HOS.SystemState; |
37 | using Ryujinx.HLE.Loaders.Executables; | 37 | using Ryujinx.HLE.Loaders.Executables; |
38 | using Ryujinx.HLE.Loaders.Processes; | ||
38 | using Ryujinx.Horizon; | 39 | using Ryujinx.Horizon; |
39 | using System; | 40 | using System; |
40 | using System.Collections.Generic; | 41 | using System.Collections.Generic; |
@@ -358,11 +359,11 @@ namespace Ryujinx.HLE.HOS | |||
358 | } | 359 | } |
359 | } | 360 | } |
360 | 361 | ||
361 | public void LoadKip(string kipPath) | 362 | public bool LoadKip(string kipPath) |
362 | { | 363 | { |
363 | using var kipFile = new SharedRef<IStorage>(new LocalStorage(kipPath, FileAccess.Read)); | 364 | using var kipFile = new SharedRef<IStorage>(new LocalStorage(kipPath, FileAccess.Read)); |
364 | 365 | ||
365 | ProgramLoader.LoadKip(KernelContext, new KipExecutable(in kipFile)); | 366 | return ProcessLoaderHelper.LoadKip(KernelContext, new KipExecutable(in kipFile)); |
366 | } | 367 | } |
367 | 368 | ||
368 | public void ChangeDockedModeState(bool newState) | 369 | public void ChangeDockedModeState(bool newState) |
diff --git a/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs b/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs index 556703cf6..4cf67172d 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs | |||
@@ -2,7 +2,7 @@ using System.Collections.Generic; | |||
2 | 2 | ||
3 | namespace Ryujinx.HLE.HOS.Kernel.Process | 3 | namespace Ryujinx.HLE.HOS.Kernel.Process |
4 | { | 4 | { |
5 | internal class ProcessTamperInfo | 5 | class ProcessTamperInfo |
6 | { | 6 | { |
7 | public KProcess Process { get; } | 7 | public KProcess Process { get; } |
8 | public IEnumerable<string> BuildIds { get; } | 8 | public IEnumerable<string> BuildIds { get; } |
diff --git a/Ryujinx.HLE/HOS/ModLoader.cs b/Ryujinx.HLE/HOS/ModLoader.cs index a6dc90135..165125414 100644 --- a/Ryujinx.HLE/HOS/ModLoader.cs +++ b/Ryujinx.HLE/HOS/ModLoader.cs | |||
@@ -10,6 +10,7 @@ using Ryujinx.Common.Logging; | |||
10 | using Ryujinx.HLE.HOS.Kernel.Process; | 10 | using Ryujinx.HLE.HOS.Kernel.Process; |
11 | using Ryujinx.HLE.Loaders.Executables; | 11 | using Ryujinx.HLE.Loaders.Executables; |
12 | using Ryujinx.HLE.Loaders.Mods; | 12 | using Ryujinx.HLE.Loaders.Mods; |
13 | using Ryujinx.HLE.Loaders.Processes; | ||
13 | using System; | 14 | using System; |
14 | using System.Collections.Generic; | 15 | using System.Collections.Generic; |
15 | using System.Collections.Specialized; | 16 | using System.Collections.Specialized; |
@@ -547,7 +548,7 @@ namespace Ryujinx.HLE.HOS | |||
547 | return modLoadResult; | 548 | return modLoadResult; |
548 | } | 549 | } |
549 | 550 | ||
550 | if (nsos.Length != ApplicationLoader.ExeFsPrefixes.Length) | 551 | if (nsos.Length != ProcessConst.ExeFsPrefixes.Length) |
551 | { | 552 | { |
552 | throw new ArgumentOutOfRangeException("NSO Count is incorrect"); | 553 | throw new ArgumentOutOfRangeException("NSO Count is incorrect"); |
553 | } | 554 | } |
@@ -556,9 +557,9 @@ namespace Ryujinx.HLE.HOS | |||
556 | 557 | ||
557 | foreach (var mod in exeMods) | 558 | foreach (var mod in exeMods) |
558 | { | 559 | { |
559 | for (int i = 0; i < ApplicationLoader.ExeFsPrefixes.Length; ++i) | 560 | for (int i = 0; i < ProcessConst.ExeFsPrefixes.Length; ++i) |
560 | { | 561 | { |
561 | var nsoName = ApplicationLoader.ExeFsPrefixes[i]; | 562 | var nsoName = ProcessConst.ExeFsPrefixes[i]; |
562 | 563 | ||
563 | FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName)); | 564 | FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName)); |
564 | if (nsoFile.Exists) | 565 | if (nsoFile.Exists) |
@@ -596,7 +597,7 @@ namespace Ryujinx.HLE.HOS | |||
596 | } | 597 | } |
597 | } | 598 | } |
598 | 599 | ||
599 | for (int i = ApplicationLoader.ExeFsPrefixes.Length - 1; i >= 0; --i) | 600 | for (int i = ProcessConst.ExeFsPrefixes.Length - 1; i >= 0; --i) |
600 | { | 601 | { |
601 | if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs | 602 | if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs |
602 | { | 603 | { |
diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs index 1b412d74e..413bedced 100644 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs | |||
@@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc | |||
190 | // TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally. | 190 | // TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally. |
191 | // But since we use LibHac and we load one Application at a time, it's not necessary. | 191 | // But since we use LibHac and we load one Application at a time, it's not necessary. |
192 | 192 | ||
193 | context.ResponseData.Write((byte)context.Device.Application.ControlData.Value.UserAccountSwitchLock); | 193 | context.ResponseData.Write((byte)context.Device.Processes.ActiveApplication.ApplicationControlProperties.UserAccountSwitchLock); |
194 | 194 | ||
195 | Logger.Stub?.PrintStub(LogClass.ServiceAcc); | 195 | Logger.Stub?.PrintStub(LogClass.ServiceAcc); |
196 | 196 | ||
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs index 00081e1b1..720497141 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs | |||
@@ -9,7 +9,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib | |||
9 | 9 | ||
10 | public ILibraryAppletSelfAccessor(ServiceCtx context) | 10 | public ILibraryAppletSelfAccessor(ServiceCtx context) |
11 | { | 11 | { |
12 | if (context.Device.Application.TitleId == 0x0100000000001009) | 12 | if (context.Device.Processes.ActiveApplication.ProgramId == 0x0100000000001009) |
13 | { | 13 | { |
14 | // Create MiiEdit data. | 14 | // Create MiiEdit data. |
15 | _appletStandalone = new AppletStandalone() | 15 | _appletStandalone = new AppletStandalone() |
@@ -25,7 +25,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib | |||
25 | } | 25 | } |
26 | else | 26 | else |
27 | { | 27 | { |
28 | throw new NotImplementedException($"{context.Device.Application.TitleId} applet is not implemented."); | 28 | throw new NotImplementedException($"{context.Device.Processes.ActiveApplication.ProgramId} applet is not implemented."); |
29 | } | 29 | } |
30 | } | 30 | } |
31 | 31 | ||
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs index f8f88a1cb..924f54298 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs | |||
@@ -115,28 +115,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati | |||
115 | Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid(); | 115 | Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid(); |
116 | 116 | ||
117 | // Mask out the low nibble of the program ID to get the application ID | 117 | // Mask out the low nibble of the program ID to get the application ID |
118 | ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul); | 118 | ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul); |
119 | 119 | ||
120 | BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData; | 120 | ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; |
121 | |||
122 | ref ApplicationControlProperty control = ref controlHolder.Value; | ||
123 | |||
124 | if (LibHac.Common.Utilities.IsZeros(controlHolder.ByteSpan)) | ||
125 | { | ||
126 | // If the current application doesn't have a loaded control property, create a dummy one | ||
127 | // and set the savedata sizes so a user savedata will be created. | ||
128 | control = ref new BlitStruct<ApplicationControlProperty>(1).Value; | ||
129 | |||
130 | // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. | ||
131 | control.UserAccountSaveDataSize = 0x4000; | ||
132 | control.UserAccountSaveDataJournalSize = 0x4000; | ||
133 | |||
134 | Logger.Warning?.Print(LogClass.ServiceAm, | ||
135 | "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); | ||
136 | } | ||
137 | 121 | ||
138 | LibHac.HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient; | 122 | LibHac.HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient; |
139 | LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in control, in userId); | 123 | LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in nacp, in userId); |
140 | 124 | ||
141 | context.ResponseData.Write(requiredSize); | 125 | context.ResponseData.Write(requiredSize); |
142 | 126 | ||
@@ -153,7 +137,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati | |||
153 | // TODO: When above calls are implemented, switch to using ns:am | 137 | // TODO: When above calls are implemented, switch to using ns:am |
154 | 138 | ||
155 | long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode; | 139 | long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode; |
156 | int supportedLanguages = (int)context.Device.Application.ControlData.Value.SupportedLanguageFlag; | 140 | int supportedLanguages = (int)context.Device.Processes.ActiveApplication.ApplicationControlProperties.SupportedLanguageFlag; |
157 | int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages); | 141 | int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages); |
158 | 142 | ||
159 | if (firstSupported > (int)TitleLanguage.BrazilianPortuguese) | 143 | if (firstSupported > (int)TitleLanguage.BrazilianPortuguese) |
@@ -196,7 +180,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati | |||
196 | public ResultCode GetDisplayVersion(ServiceCtx context) | 180 | public ResultCode GetDisplayVersion(ServiceCtx context) |
197 | { | 181 | { |
198 | // If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation. | 182 | // If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation. |
199 | context.ResponseData.Write(context.Device.Application.ControlData.Value.DisplayVersion); | 183 | context.ResponseData.Write(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion); |
200 | 184 | ||
201 | return ResultCode.Success; | 185 | return ResultCode.Success; |
202 | } | 186 | } |
@@ -251,13 +235,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati | |||
251 | long journalSize = context.RequestData.ReadInt64(); | 235 | long journalSize = context.RequestData.ReadInt64(); |
252 | 236 | ||
253 | // Mask out the low nibble of the program ID to get the application ID | 237 | // Mask out the low nibble of the program ID to get the application ID |
254 | ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul); | 238 | ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul); |
255 | 239 | ||
256 | BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData; | 240 | ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; |
257 | 241 | ||
258 | LibHac.Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize, | 242 | LibHac.Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize, |
259 | out CacheStorageTargetMedia storageTarget, applicationId, in controlHolder.Value, index, saveSize, | 243 | out CacheStorageTargetMedia storageTarget, applicationId, in nacp, index, saveSize, journalSize); |
260 | journalSize); | ||
261 | 244 | ||
262 | if (result.IsFailure()) return (ResultCode)result.Value; | 245 | if (result.IsFailure()) return (ResultCode)result.Value; |
263 | 246 | ||
@@ -677,7 +660,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati | |||
677 | throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)"); | 660 | throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)"); |
678 | } | 661 | } |
679 | 662 | ||
680 | context.Device.Application.LoadServiceNca(filePath); | 663 | context.Device.LoadNca(filePath); |
681 | 664 | ||
682 | // FIXME: Most likely not how this should be done? | 665 | // FIXME: Most likely not how this should be done? |
683 | while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u")) | 666 | while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u")) |
diff --git a/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs index c985092b8..3e4eca0ac 100644 --- a/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs +++ b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs | |||
@@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp | |||
33 | 33 | ||
34 | return new ApplicationLaunchProperty | 34 | return new ApplicationLaunchProperty |
35 | { | 35 | { |
36 | TitleId = context.Device.Application.TitleId, | 36 | TitleId = context.Device.Processes.ActiveApplication.ProgramId, |
37 | Version = 0x00, | 37 | Version = 0x00, |
38 | BaseGameStorageId = (byte)StorageId.BuiltInSystem, | 38 | BaseGameStorageId = (byte)StorageId.BuiltInSystem, |
39 | UpdateGameStorageId = (byte)StorageId.None | 39 | UpdateGameStorageId = (byte)StorageId.None |
diff --git a/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs index 1789122e4..e0c65f440 100644 --- a/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs +++ b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs | |||
@@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps | |||
31 | 31 | ||
32 | byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); | 32 | byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); |
33 | 33 | ||
34 | ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry); | 34 | ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); |
35 | 35 | ||
36 | context.ResponseData.WriteStruct(applicationAlbumEntry); | 36 | context.ResponseData.WriteStruct(applicationAlbumEntry); |
37 | 37 | ||
@@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps | |||
60 | 60 | ||
61 | byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); | 61 | byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); |
62 | 62 | ||
63 | ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry); | 63 | ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); |
64 | 64 | ||
65 | context.ResponseData.WriteStruct(applicationAlbumEntry); | 65 | context.ResponseData.WriteStruct(applicationAlbumEntry); |
66 | 66 | ||
@@ -88,7 +88,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps | |||
88 | 88 | ||
89 | byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); | 89 | byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); |
90 | 90 | ||
91 | ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry); | 91 | ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); |
92 | 92 | ||
93 | context.ResponseData.WriteStruct(applicationAlbumEntry); | 93 | context.ResponseData.WriteStruct(applicationAlbumEntry); |
94 | 94 | ||
diff --git a/Ryujinx.HLE/HOS/Services/Fatal/IService.cs b/Ryujinx.HLE/HOS/Services/Fatal/IService.cs index 6d663a4de..c884e8803 100644 --- a/Ryujinx.HLE/HOS/Services/Fatal/IService.cs +++ b/Ryujinx.HLE/HOS/Services/Fatal/IService.cs | |||
@@ -55,7 +55,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal | |||
55 | errorReport.AppendLine(); | 55 | errorReport.AppendLine(); |
56 | errorReport.AppendLine("ErrorReport log:"); | 56 | errorReport.AppendLine("ErrorReport log:"); |
57 | 57 | ||
58 | errorReport.AppendLine($"\tTitleId: {context.Device.Application.TitleId:x16}"); | 58 | errorReport.AppendLine($"\tTitleId: {context.Device.Processes.ActiveApplication.ProgramIdText}"); |
59 | errorReport.AppendLine($"\tPid: {pid}"); | 59 | errorReport.AppendLine($"\tPid: {pid}"); |
60 | errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}"); | 60 | errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}"); |
61 | errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}"); | 61 | errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}"); |
@@ -64,7 +64,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal | |||
64 | { | 64 | { |
65 | errorReport.AppendLine("CPU Context:"); | 65 | errorReport.AppendLine("CPU Context:"); |
66 | 66 | ||
67 | if (context.Device.Application.TitleIs64Bit) | 67 | if (context.Device.Processes.ActiveApplication.Is64Bit) |
68 | { | 68 | { |
69 | CpuContext64 cpuContext64 = MemoryMarshal.Cast<byte, CpuContext64>(cpuContext)[0]; | 69 | CpuContext64 cpuContext64 = MemoryMarshal.Cast<byte, CpuContext64>(cpuContext)[0]; |
70 | 70 | ||
diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs index 17a33b799..4317c8f61 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs | |||
@@ -334,7 +334,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator | |||
334 | } | 334 | } |
335 | 335 | ||
336 | // TODO: Call nn::arp::GetApplicationControlProperty here when implemented. | 336 | // TODO: Call nn::arp::GetApplicationControlProperty here when implemented. |
337 | ApplicationControlProperty controlProperty = context.Device.Application.ControlData.Value; | 337 | ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties; |
338 | 338 | ||
339 | /* | 339 | /* |
340 | 340 | ||
diff --git a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs index 37143a5aa..1b63f362e 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs | |||
@@ -808,7 +808,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs | |||
808 | { | 808 | { |
809 | byte programIndex = context.RequestData.ReadByte(); | 809 | byte programIndex = context.RequestData.ReadByte(); |
810 | 810 | ||
811 | if ((context.Device.Application.TitleId & 0xf) != programIndex) | 811 | if ((context.Device.Processes.ActiveApplication.ProgramId & 0xf) != programIndex) |
812 | { | 812 | { |
813 | throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex})."); | 813 | throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex})."); |
814 | } | 814 | } |
diff --git a/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs b/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs index 0d5520038..b8f9e3b97 100644 --- a/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs +++ b/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs | |||
@@ -48,7 +48,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc | |||
48 | 48 | ||
49 | // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. | 49 | // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. |
50 | 50 | ||
51 | return CountAddOnContentImpl(context, context.Device.Application.TitleId); | 51 | return CountAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId); |
52 | } | 52 | } |
53 | 53 | ||
54 | [CommandHipc(3)] | 54 | [CommandHipc(3)] |
@@ -59,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc | |||
59 | 59 | ||
60 | // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. | 60 | // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. |
61 | 61 | ||
62 | return ListAddContentImpl(context, context.Device.Application.TitleId); | 62 | return ListAddContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId); |
63 | } | 63 | } |
64 | 64 | ||
65 | [CommandHipc(4)] // 1.0.0-6.2.0 | 65 | [CommandHipc(4)] // 1.0.0-6.2.0 |
@@ -79,7 +79,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc | |||
79 | 79 | ||
80 | // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. | 80 | // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. |
81 | 81 | ||
82 | return GetAddOnContentBaseIdImpl(context, context.Device.Application.TitleId); | 82 | return GetAddOnContentBaseIdImpl(context, context.Device.Processes.ActiveApplication.ProgramId); |
83 | } | 83 | } |
84 | 84 | ||
85 | [CommandHipc(6)] // 1.0.0-6.2.0 | 85 | [CommandHipc(6)] // 1.0.0-6.2.0 |
@@ -99,7 +99,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc | |||
99 | 99 | ||
100 | // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. | 100 | // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. |
101 | 101 | ||
102 | return PrepareAddOnContentImpl(context, context.Device.Application.TitleId); | 102 | return PrepareAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId); |
103 | } | 103 | } |
104 | 104 | ||
105 | [CommandHipc(8)] // 4.0.0+ | 105 | [CommandHipc(8)] // 4.0.0+ |
@@ -128,7 +128,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc | |||
128 | // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. | 128 | // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. |
129 | 129 | ||
130 | // TODO: Found where stored value is used. | 130 | // TODO: Found where stored value is used. |
131 | ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Application.TitleId); | 131 | ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId); |
132 | 132 | ||
133 | if (resultCode != ResultCode.Success) | 133 | if (resultCode != ResultCode.Success) |
134 | { | 134 | { |
@@ -294,7 +294,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc | |||
294 | // NOTE: Service calls arp:r GetApplicationControlProperty to get AddOnContentBaseId using TitleId, | 294 | // NOTE: Service calls arp:r GetApplicationControlProperty to get AddOnContentBaseId using TitleId, |
295 | // If the call fails, it returns ResultCode.InvalidPid. | 295 | // If the call fails, it returns ResultCode.InvalidPid. |
296 | 296 | ||
297 | _addOnContentBaseId = context.Device.Application.ControlData.Value.AddOnContentBaseId; | 297 | _addOnContentBaseId = context.Device.Processes.ActiveApplication.ApplicationControlProperties.AddOnContentBaseId; |
298 | 298 | ||
299 | if (_addOnContentBaseId == 0) | 299 | if (_addOnContentBaseId == 0) |
300 | { | 300 | { |
@@ -308,7 +308,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc | |||
308 | { | 308 | { |
309 | uint index = context.RequestData.ReadUInt32(); | 309 | uint index = context.RequestData.ReadUInt32(); |
310 | 310 | ||
311 | ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Application.TitleId); | 311 | ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId); |
312 | 312 | ||
313 | if (resultCode != ResultCode.Success) | 313 | if (resultCode != ResultCode.Success) |
314 | { | 314 | { |
diff --git a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs index d3a891782..249343d79 100644 --- a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs | |||
@@ -1,4 +1,8 @@ | |||
1 | namespace Ryujinx.HLE.HOS.Services.Ns | 1 | using LibHac.Ns; |
2 | using Ryujinx.Common.Utilities; | ||
3 | using System; | ||
4 | |||
5 | namespace Ryujinx.HLE.HOS.Services.Ns | ||
2 | { | 6 | { |
3 | [Service("ns:am")] | 7 | [Service("ns:am")] |
4 | class IApplicationManagerInterface : IpcService | 8 | class IApplicationManagerInterface : IpcService |
@@ -14,9 +18,9 @@ | |||
14 | 18 | ||
15 | ulong position = context.Request.ReceiveBuff[0].Position; | 19 | ulong position = context.Request.ReceiveBuff[0].Position; |
16 | 20 | ||
17 | byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray(); | 21 | ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; |
18 | 22 | ||
19 | context.Memory.Write(position, nacpData); | 23 | context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray()); |
20 | 24 | ||
21 | return ResultCode.Success; | 25 | return ResultCode.Success; |
22 | } | 26 | } |
diff --git a/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs index 3b6965d0a..8f6acc1c6 100644 --- a/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs | |||
@@ -1,4 +1,7 @@ | |||
1 | namespace Ryujinx.HLE.HOS.Services.Ns | 1 | using LibHac.Common; |
2 | using LibHac.Ns; | ||
3 | |||
4 | namespace Ryujinx.HLE.HOS.Services.Ns | ||
2 | { | 5 | { |
3 | class IReadOnlyApplicationControlDataInterface : IpcService | 6 | class IReadOnlyApplicationControlDataInterface : IpcService |
4 | { | 7 | { |
@@ -13,9 +16,9 @@ | |||
13 | 16 | ||
14 | ulong position = context.Request.ReceiveBuff[0].Position; | 17 | ulong position = context.Request.ReceiveBuff[0].Position; |
15 | 18 | ||
16 | byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray(); | 19 | ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; |
17 | 20 | ||
18 | context.Memory.Write(position, nacpData); | 21 | context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray()); |
19 | 22 | ||
20 | return ResultCode.Success; | 23 | return ResultCode.Success; |
21 | } | 24 | } |
diff --git a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs index e0017808d..02964749c 100644 --- a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs +++ b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs | |||
@@ -56,8 +56,8 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory | |||
56 | _titleId = titleId; | 56 | _titleId = titleId; |
57 | 57 | ||
58 | // TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields. | 58 | // TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields. |
59 | _ratingAge = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ItemsRo.ToArray(), Convert.ToInt32); | 59 | _ratingAge = Array.ConvertAll(context.Device.Processes.ActiveApplication.ApplicationControlProperties.RatingAge.ItemsRo.ToArray(), Convert.ToInt32); |
60 | _parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControlFlag; | 60 | _parentalControlFlag = context.Device.Processes.ActiveApplication.ApplicationControlProperties.ParentalControlFlag; |
61 | } | 61 | } |
62 | } | 62 | } |
63 | 63 | ||
diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs index 1d6cc118e..52a07d466 100644 --- a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs | |||
@@ -6,7 +6,6 @@ using System; | |||
6 | using System.Collections.Generic; | 6 | using System.Collections.Generic; |
7 | using System.Linq; | 7 | using System.Linq; |
8 | using System.Runtime.CompilerServices; | 8 | using System.Runtime.CompilerServices; |
9 | using System.Runtime.InteropServices; | ||
10 | 9 | ||
11 | namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService | 10 | namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService |
12 | { | 11 | { |
@@ -16,8 +15,6 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService | |||
16 | 15 | ||
17 | internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false) | 16 | internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false) |
18 | { | 17 | { |
19 | ref readonly var controlProperty = ref context.Device.Application.ControlData.Value; | ||
20 | |||
21 | ulong inputPosition = context.Request.SendBuff[0].Position; | 18 | ulong inputPosition = context.Request.SendBuff[0].Position; |
22 | ulong inputSize = context.Request.SendBuff[0].Size; | 19 | ulong inputSize = context.Request.SendBuff[0].Size; |
23 | 20 | ||
@@ -34,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService | |||
34 | } | 31 | } |
35 | } | 32 | } |
36 | 33 | ||
37 | PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)controlProperty.PlayLogQueryCapability; | 34 | PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryCapability; |
38 | 35 | ||
39 | List<ulong> titleIds = new List<ulong>(); | 36 | List<ulong> titleIds = new List<ulong>(); |
40 | 37 | ||
@@ -48,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService | |||
48 | // Check if input title ids are in the whitelist. | 45 | // Check if input title ids are in the whitelist. |
49 | foreach (ulong titleId in titleIds) | 46 | foreach (ulong titleId in titleIds) |
50 | { | 47 | { |
51 | if (!controlProperty.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId)) | 48 | if (!context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId)) |
52 | { | 49 | { |
53 | return (ResultCode)Am.ResultCode.ObjectInvalid; | 50 | return (ResultCode)Am.ResultCode.ObjectInvalid; |
54 | } | 51 | } |
diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs new file mode 100644 index 000000000..58759ddb1 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs | |||
@@ -0,0 +1,133 @@ | |||
1 | using LibHac.Common; | ||
2 | using LibHac.Fs; | ||
3 | using LibHac.Fs.Fsa; | ||
4 | using LibHac.Loader; | ||
5 | using LibHac.Ns; | ||
6 | using LibHac.Tools.FsSystem; | ||
7 | using Ryujinx.Common.Configuration; | ||
8 | using Ryujinx.Common.Logging; | ||
9 | using Ryujinx.HLE.Loaders.Executables; | ||
10 | using Ryujinx.Memory; | ||
11 | using System.Linq; | ||
12 | using static Ryujinx.HLE.HOS.ModLoader; | ||
13 | |||
14 | namespace Ryujinx.HLE.Loaders.Processes.Extensions | ||
15 | { | ||
16 | static class FileSystemExtensions | ||
17 | { | ||
18 | public static MetaLoader GetNpdm(this IFileSystem fileSystem) | ||
19 | { | ||
20 | MetaLoader metaLoader = new(); | ||
21 | |||
22 | if (fileSystem == null || !fileSystem.FileExists(ProcessConst.MainNpdmPath)) | ||
23 | { | ||
24 | Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!"); | ||
25 | |||
26 | metaLoader.LoadDefault(); | ||
27 | } | ||
28 | else | ||
29 | { | ||
30 | metaLoader.LoadFromFile(fileSystem); | ||
31 | } | ||
32 | |||
33 | return metaLoader; | ||
34 | } | ||
35 | |||
36 | public static ProcessResult Load(this IFileSystem exeFs, Switch device, BlitStruct<ApplicationControlProperty> nacpData, MetaLoader metaLoader, bool isHomebrew = false) | ||
37 | { | ||
38 | ulong programId = metaLoader.GetProgramId(); | ||
39 | |||
40 | // Replace the whole ExeFs partition by the modded one. | ||
41 | if (device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(programId, ref exeFs)) | ||
42 | { | ||
43 | metaLoader = null; | ||
44 | } | ||
45 | |||
46 | // Reload the MetaLoader in case of ExeFs partition replacement. | ||
47 | metaLoader ??= exeFs.GetNpdm(); | ||
48 | |||
49 | NsoExecutable[] nsoExecutables = new NsoExecutable[ProcessConst.ExeFsPrefixes.Length]; | ||
50 | |||
51 | for (int i = 0; i < nsoExecutables.Length; i++) | ||
52 | { | ||
53 | string name = ProcessConst.ExeFsPrefixes[i]; | ||
54 | |||
55 | if (!exeFs.FileExists($"/{name}")) | ||
56 | { | ||
57 | continue; // File doesn't exist, skip. | ||
58 | } | ||
59 | |||
60 | Logger.Info?.Print(LogClass.Loader, $"Loading {name}..."); | ||
61 | |||
62 | using var nsoFile = new UniqueRef<IFile>(); | ||
63 | |||
64 | exeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure(); | ||
65 | |||
66 | nsoExecutables[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name); | ||
67 | } | ||
68 | |||
69 | // ExeFs file replacements. | ||
70 | ModLoadResult modLoadResult = device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(programId, nsoExecutables); | ||
71 | |||
72 | // Take the Npdm from mods if present. | ||
73 | if (modLoadResult.Npdm != null) | ||
74 | { | ||
75 | metaLoader = modLoadResult.Npdm; | ||
76 | } | ||
77 | |||
78 | // Collect the Nsos, ignoring ones that aren't used. | ||
79 | nsoExecutables = nsoExecutables.Where(x => x != null).ToArray(); | ||
80 | |||
81 | // Apply Nsos patches. | ||
82 | device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(programId, nsoExecutables); | ||
83 | |||
84 | // Don't use PTC if ExeFS files have been replaced. | ||
85 | bool enablePtc = device.System.EnablePtc && !modLoadResult.Modified; | ||
86 | if (!enablePtc) | ||
87 | { | ||
88 | Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PTC disabled."); | ||
89 | } | ||
90 | |||
91 | // We allow it for nx-hbloader because it can be used to launch homebrew. | ||
92 | bool allowCodeMemoryForJit = programId == 0x010000000000100DUL || isHomebrew; | ||
93 | |||
94 | string programName = ""; | ||
95 | |||
96 | if (!isHomebrew && programId > 0x010000000000FFFF) | ||
97 | { | ||
98 | programName = nacpData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString(); | ||
99 | |||
100 | if (string.IsNullOrWhiteSpace(programName)) | ||
101 | { | ||
102 | programName = nacpData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString(); | ||
103 | } | ||
104 | } | ||
105 | |||
106 | // Initialize GPU. | ||
107 | Graphics.Gpu.GraphicsConfig.TitleId = $"{programId:x16}"; | ||
108 | device.Gpu.HostInitalized.Set(); | ||
109 | |||
110 | if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible)) | ||
111 | { | ||
112 | device.Configuration.MemoryManagerMode = MemoryManagerMode.SoftwarePageTable; | ||
113 | } | ||
114 | |||
115 | ProcessResult processResult = ProcessLoaderHelper.LoadNsos( | ||
116 | device, | ||
117 | device.System.KernelContext, | ||
118 | metaLoader, | ||
119 | nacpData.Value, | ||
120 | enablePtc, | ||
121 | allowCodeMemoryForJit, | ||
122 | programName, | ||
123 | metaLoader.GetProgramId(), | ||
124 | null, | ||
125 | nsoExecutables); | ||
126 | |||
127 | // TODO: This should be stored using ProcessId instead. | ||
128 | device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(metaLoader.GetProgramId()); | ||
129 | |||
130 | return processResult; | ||
131 | } | ||
132 | } | ||
133 | } \ No newline at end of file | ||
diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs new file mode 100644 index 000000000..28d907851 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs | |||
@@ -0,0 +1,39 @@ | |||
1 | using LibHac.Common; | ||
2 | using LibHac.FsSystem; | ||
3 | using LibHac.Loader; | ||
4 | using LibHac.Ns; | ||
5 | using Ryujinx.HLE.Loaders.Processes.Extensions; | ||
6 | using ApplicationId = LibHac.Ncm.ApplicationId; | ||
7 | |||
8 | namespace Ryujinx.HLE.Loaders.Processes | ||
9 | { | ||
10 | static class LocalFileSystemExtensions | ||
11 | { | ||
12 | public static ProcessResult Load(this LocalFileSystem exeFs, Switch device, string romFsPath = "") | ||
13 | { | ||
14 | MetaLoader metaLoader = exeFs.GetNpdm(); | ||
15 | var nacpData = new BlitStruct<ApplicationControlProperty>(1); | ||
16 | ulong programId = metaLoader.GetProgramId(); | ||
17 | |||
18 | device.Configuration.VirtualFileSystem.ModLoader.CollectMods( | ||
19 | new[] { programId }, | ||
20 | device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), | ||
21 | device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath()); | ||
22 | |||
23 | if (programId != 0) | ||
24 | { | ||
25 | ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(programId), nacpData); | ||
26 | } | ||
27 | |||
28 | ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader); | ||
29 | |||
30 | // Load RomFS. | ||
31 | if (!string.IsNullOrEmpty(romFsPath)) | ||
32 | { | ||
33 | device.Configuration.VirtualFileSystem.LoadRomFs(processResult.ProcessId, romFsPath); | ||
34 | } | ||
35 | |||
36 | return processResult; | ||
37 | } | ||
38 | } | ||
39 | } | ||
diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs new file mode 100644 index 000000000..c639ee524 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs | |||
@@ -0,0 +1,61 @@ | |||
1 | using LibHac.Common; | ||
2 | using LibHac.Fs; | ||
3 | using LibHac.Fs.Fsa; | ||
4 | using LibHac.Loader; | ||
5 | using LibHac.Util; | ||
6 | using Ryujinx.Common; | ||
7 | using System; | ||
8 | |||
9 | namespace Ryujinx.HLE.Loaders.Processes.Extensions | ||
10 | { | ||
11 | public static class MetaLoaderExtensions | ||
12 | { | ||
13 | public static ulong GetProgramId(this MetaLoader metaLoader) | ||
14 | { | ||
15 | metaLoader.GetNpdm(out var npdm).ThrowIfFailure(); | ||
16 | |||
17 | return npdm.Aci.ProgramId.Value; | ||
18 | } | ||
19 | |||
20 | public static string GetProgramName(this MetaLoader metaLoader) | ||
21 | { | ||
22 | metaLoader.GetNpdm(out var npdm).ThrowIfFailure(); | ||
23 | |||
24 | return StringUtils.Utf8ZToString(npdm.Meta.ProgramName); | ||
25 | } | ||
26 | |||
27 | public static bool IsProgram64Bit(this MetaLoader metaLoader) | ||
28 | { | ||
29 | metaLoader.GetNpdm(out var npdm).ThrowIfFailure(); | ||
30 | |||
31 | return (npdm.Meta.Flags & 1) != 0; | ||
32 | } | ||
33 | |||
34 | public static void LoadDefault(this MetaLoader metaLoader) | ||
35 | { | ||
36 | byte[] npdmBuffer = EmbeddedResources.Read("Ryujinx.HLE/Homebrew.npdm"); | ||
37 | |||
38 | metaLoader.Load(npdmBuffer).ThrowIfFailure(); | ||
39 | } | ||
40 | |||
41 | public static void LoadFromFile(this MetaLoader metaLoader, IFileSystem fileSystem, string path = "") | ||
42 | { | ||
43 | if (string.IsNullOrEmpty(path)) | ||
44 | { | ||
45 | path = ProcessConst.MainNpdmPath; | ||
46 | } | ||
47 | |||
48 | using var npdmFile = new UniqueRef<IFile>(); | ||
49 | |||
50 | fileSystem.OpenFile(ref npdmFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure(); | ||
51 | |||
52 | npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure(); | ||
53 | |||
54 | Span<byte> npdmBuffer = new byte[fileSize]; | ||
55 | |||
56 | npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure(); | ||
57 | |||
58 | metaLoader.Load(npdmBuffer).ThrowIfFailure(); | ||
59 | } | ||
60 | } | ||
61 | } \ No newline at end of file | ||
diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs new file mode 100644 index 000000000..473f374db --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs | |||
@@ -0,0 +1,175 @@ | |||
1 | using LibHac; | ||
2 | using LibHac.Common; | ||
3 | using LibHac.Fs; | ||
4 | using LibHac.Fs.Fsa; | ||
5 | using LibHac.Loader; | ||
6 | using LibHac.Ncm; | ||
7 | using LibHac.Ns; | ||
8 | using LibHac.Tools.FsSystem; | ||
9 | using LibHac.Tools.FsSystem.NcaUtils; | ||
10 | using Ryujinx.Common.Logging; | ||
11 | using System.IO; | ||
12 | using System.Linq; | ||
13 | using ApplicationId = LibHac.Ncm.ApplicationId; | ||
14 | |||
15 | namespace Ryujinx.HLE.Loaders.Processes.Extensions | ||
16 | { | ||
17 | static class NcaExtensions | ||
18 | { | ||
19 | public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca) | ||
20 | { | ||
21 | // Extract RomFs and ExeFs from NCA. | ||
22 | IStorage romFs = nca.GetRomFs(device, patchNca); | ||
23 | IFileSystem exeFs = nca.GetExeFs(device, patchNca); | ||
24 | |||
25 | if (exeFs == null) | ||
26 | { | ||
27 | Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA"); | ||
28 | |||
29 | return ProcessResult.Failed; | ||
30 | } | ||
31 | |||
32 | // Load Npdm file. | ||
33 | MetaLoader metaLoader = exeFs.GetNpdm(); | ||
34 | |||
35 | // Collecting mods related to AocTitleIds and ProgramId. | ||
36 | device.Configuration.VirtualFileSystem.ModLoader.CollectMods( | ||
37 | device.Configuration.ContentManager.GetAocTitleIds().Prepend(metaLoader.GetProgramId()), | ||
38 | device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), | ||
39 | device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath()); | ||
40 | |||
41 | // Load Nacp file. | ||
42 | var nacpData = new BlitStruct<ApplicationControlProperty>(1); | ||
43 | |||
44 | if (controlNca != null) | ||
45 | { | ||
46 | nacpData = controlNca.GetNacp(device); | ||
47 | } | ||
48 | |||
49 | /* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" inexistant update. | ||
50 | |||
51 | // Load program 0 control NCA as we are going to need it for display version. | ||
52 | (_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _); | ||
53 | |||
54 | // NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application. | ||
55 | // As such, to avoid PTC cache confusion, we only trust the program 0 display version when launching a sub program. | ||
56 | if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0) | ||
57 | { | ||
58 | nacpData.Value.DisplayVersion = updateProgram0ControlNca.GetNacp(_device).Value.DisplayVersion; | ||
59 | } | ||
60 | |||
61 | */ | ||
62 | |||
63 | ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader); | ||
64 | |||
65 | // Load RomFS. | ||
66 | if (romFs == null) | ||
67 | { | ||
68 | Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA"); | ||
69 | } | ||
70 | else | ||
71 | { | ||
72 | romFs = device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(processResult.ProgramId, romFs); | ||
73 | |||
74 | device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romFs.AsStream(FileAccess.Read)); | ||
75 | } | ||
76 | |||
77 | // Don't create save data for system programs. | ||
78 | if (processResult.ProgramId != 0 && (processResult.ProgramId < SystemProgramId.Start.Value || processResult.ProgramId > SystemAppletId.End.Value)) | ||
79 | { | ||
80 | // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble. | ||
81 | // We'll know if this changes in the future because applications will get errors when trying to mount the correct save. | ||
82 | ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(processResult.ProgramId & ~0xFul), nacpData); | ||
83 | } | ||
84 | |||
85 | return processResult; | ||
86 | } | ||
87 | |||
88 | public static int GetProgramIndex(this Nca nca) | ||
89 | { | ||
90 | return (int)(nca.Header.TitleId & 0xF); | ||
91 | } | ||
92 | |||
93 | public static bool IsProgram(this Nca nca) | ||
94 | { | ||
95 | return nca.Header.ContentType == NcaContentType.Program; | ||
96 | } | ||
97 | |||
98 | public static bool IsPatch(this Nca nca) | ||
99 | { | ||
100 | int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); | ||
101 | |||
102 | return nca.IsProgram() && nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection(); | ||
103 | } | ||
104 | |||
105 | public static bool IsControl(this Nca nca) | ||
106 | { | ||
107 | return nca.Header.ContentType == NcaContentType.Control; | ||
108 | } | ||
109 | |||
110 | public static IFileSystem GetExeFs(this Nca nca, Switch device, Nca patchNca = null) | ||
111 | { | ||
112 | IFileSystem exeFs = null; | ||
113 | |||
114 | if (patchNca == null) | ||
115 | { | ||
116 | if (nca.CanOpenSection(NcaSectionType.Code)) | ||
117 | { | ||
118 | exeFs = nca.OpenFileSystem(NcaSectionType.Code, device.System.FsIntegrityCheckLevel); | ||
119 | } | ||
120 | } | ||
121 | else | ||
122 | { | ||
123 | if (patchNca.CanOpenSection(NcaSectionType.Code)) | ||
124 | { | ||
125 | exeFs = nca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, device.System.FsIntegrityCheckLevel); | ||
126 | } | ||
127 | } | ||
128 | |||
129 | return exeFs; | ||
130 | } | ||
131 | |||
132 | public static IStorage GetRomFs(this Nca nca, Switch device, Nca patchNca = null) | ||
133 | { | ||
134 | IStorage romFs = null; | ||
135 | |||
136 | if (patchNca == null) | ||
137 | { | ||
138 | if (nca.CanOpenSection(NcaSectionType.Data)) | ||
139 | { | ||
140 | romFs = nca.OpenStorage(NcaSectionType.Data, device.System.FsIntegrityCheckLevel); | ||
141 | } | ||
142 | } | ||
143 | else | ||
144 | { | ||
145 | if (patchNca.CanOpenSection(NcaSectionType.Data)) | ||
146 | { | ||
147 | romFs = nca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, device.System.FsIntegrityCheckLevel); | ||
148 | } | ||
149 | } | ||
150 | |||
151 | return romFs; | ||
152 | } | ||
153 | |||
154 | public static BlitStruct<ApplicationControlProperty> GetNacp(this Nca controlNca, Switch device) | ||
155 | { | ||
156 | var nacpData = new BlitStruct<ApplicationControlProperty>(1); | ||
157 | |||
158 | using var controlFile = new UniqueRef<IFile>(); | ||
159 | |||
160 | Result result = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel) | ||
161 | .OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read); | ||
162 | |||
163 | if (result.IsSuccess()) | ||
164 | { | ||
165 | result = controlFile.Get.Read(out long bytesRead, 0, nacpData.ByteSpan, ReadOption.None); | ||
166 | } | ||
167 | else | ||
168 | { | ||
169 | nacpData.ByteSpan.Clear(); | ||
170 | } | ||
171 | |||
172 | return nacpData; | ||
173 | } | ||
174 | } | ||
175 | } \ No newline at end of file | ||
diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs new file mode 100644 index 000000000..5147f5c3a --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs | |||
@@ -0,0 +1,177 @@ | |||
1 | using LibHac.Common; | ||
2 | using LibHac.Fs; | ||
3 | using LibHac.Fs.Fsa; | ||
4 | using LibHac.FsSystem; | ||
5 | using LibHac.Tools.Fs; | ||
6 | using LibHac.Tools.FsSystem; | ||
7 | using LibHac.Tools.FsSystem.NcaUtils; | ||
8 | using Ryujinx.Common.Configuration; | ||
9 | using Ryujinx.Common.Logging; | ||
10 | using Ryujinx.Common.Utilities; | ||
11 | using System; | ||
12 | using System.Collections.Generic; | ||
13 | using System.Globalization; | ||
14 | using System.IO; | ||
15 | |||
16 | namespace Ryujinx.HLE.Loaders.Processes.Extensions | ||
17 | { | ||
18 | public static class PartitionFileSystemExtensions | ||
19 | { | ||
20 | internal static (bool, ProcessResult) TryLoad(this PartitionFileSystem partitionFileSystem, Switch device, string path, out string errorMessage) | ||
21 | { | ||
22 | errorMessage = null; | ||
23 | |||
24 | // Load required NCAs. | ||
25 | Nca mainNca = null; | ||
26 | Nca patchNca = null; | ||
27 | Nca controlNca = null; | ||
28 | |||
29 | try | ||
30 | { | ||
31 | device.Configuration.VirtualFileSystem.ImportTickets(partitionFileSystem); | ||
32 | |||
33 | // TODO: To support multi-games container, this should use CNMT NCA instead. | ||
34 | foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca")) | ||
35 | { | ||
36 | Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath); | ||
37 | |||
38 | if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index) | ||
39 | { | ||
40 | continue; | ||
41 | } | ||
42 | |||
43 | if (nca.IsPatch()) | ||
44 | { | ||
45 | patchNca = nca; | ||
46 | } | ||
47 | else if (nca.IsProgram()) | ||
48 | { | ||
49 | mainNca = nca; | ||
50 | } | ||
51 | else if (nca.IsControl()) | ||
52 | { | ||
53 | controlNca = nca; | ||
54 | } | ||
55 | } | ||
56 | |||
57 | ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure(); | ||
58 | } | ||
59 | catch (Exception ex) | ||
60 | { | ||
61 | errorMessage = $"Unable to load: {ex.Message}"; | ||
62 | |||
63 | return (false, ProcessResult.Failed); | ||
64 | } | ||
65 | |||
66 | if (mainNca != null) | ||
67 | { | ||
68 | if (mainNca.Header.ContentType != NcaContentType.Program) | ||
69 | { | ||
70 | errorMessage = "Selected NCA file is not a \"Program\" NCA"; | ||
71 | |||
72 | return (false, ProcessResult.Failed); | ||
73 | } | ||
74 | |||
75 | // Load Update NCAs. | ||
76 | Nca updatePatchNca = null; | ||
77 | Nca updateControlNca = null; | ||
78 | |||
79 | if (ulong.TryParse(mainNca.Header.TitleId.ToString("x16"), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase)) | ||
80 | { | ||
81 | // Clear the program index part. | ||
82 | titleIdBase &= ~0xFUL; | ||
83 | |||
84 | // Load update information if exists. | ||
85 | string titleUpdateMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json"); | ||
86 | if (File.Exists(titleUpdateMetadataPath)) | ||
87 | { | ||
88 | string updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected; | ||
89 | if (File.Exists(updatePath)) | ||
90 | { | ||
91 | PartitionFileSystem updatePartitionFileSystem = new(new FileStream(updatePath, FileMode.Open, FileAccess.Read).AsStorage()); | ||
92 | |||
93 | device.Configuration.VirtualFileSystem.ImportTickets(updatePartitionFileSystem); | ||
94 | |||
95 | // TODO: This should use CNMT NCA instead. | ||
96 | foreach (DirectoryEntryEx fileEntry in updatePartitionFileSystem.EnumerateEntries("/", "*.nca")) | ||
97 | { | ||
98 | Nca nca = updatePartitionFileSystem.GetNca(device, fileEntry.FullPath); | ||
99 | |||
100 | if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index) | ||
101 | { | ||
102 | continue; | ||
103 | } | ||
104 | |||
105 | if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleIdBase.ToString("x16")) | ||
106 | { | ||
107 | break; | ||
108 | } | ||
109 | |||
110 | if (nca.IsProgram()) | ||
111 | { | ||
112 | updatePatchNca = nca; | ||
113 | } | ||
114 | else if (nca.IsControl()) | ||
115 | { | ||
116 | updateControlNca = nca; | ||
117 | } | ||
118 | } | ||
119 | } | ||
120 | } | ||
121 | } | ||
122 | |||
123 | if (updatePatchNca != null) | ||
124 | { | ||
125 | patchNca = updatePatchNca; | ||
126 | } | ||
127 | |||
128 | if (updateControlNca != null) | ||
129 | { | ||
130 | controlNca = updateControlNca; | ||
131 | } | ||
132 | |||
133 | // Load contained DownloadableContents. | ||
134 | // TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here. | ||
135 | device.Configuration.ContentManager.ClearAocData(); | ||
136 | device.Configuration.ContentManager.AddAocData(partitionFileSystem, path, mainNca.Header.TitleId, device.Configuration.FsIntegrityCheckLevel); | ||
137 | |||
138 | // Load DownloadableContents. | ||
139 | string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json"); | ||
140 | if (File.Exists(addOnContentMetadataPath)) | ||
141 | { | ||
142 | List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(addOnContentMetadataPath); | ||
143 | |||
144 | foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList) | ||
145 | { | ||
146 | foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) | ||
147 | { | ||
148 | if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled) | ||
149 | { | ||
150 | device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath); | ||
151 | } | ||
152 | else | ||
153 | { | ||
154 | Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed."); | ||
155 | } | ||
156 | } | ||
157 | } | ||
158 | } | ||
159 | |||
160 | return (true, mainNca.Load(device, patchNca, controlNca)); | ||
161 | } | ||
162 | |||
163 | errorMessage = "Unable to load: Could not find Main NCA"; | ||
164 | |||
165 | return (false, ProcessResult.Failed); | ||
166 | } | ||
167 | |||
168 | public static Nca GetNca(this IFileSystem fileSystem, Switch device, string path) | ||
169 | { | ||
170 | using var ncaFile = new UniqueRef<IFile>(); | ||
171 | |||
172 | fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure(); | ||
173 | |||
174 | return new Nca(device.Configuration.VirtualFileSystem.KeySet, ncaFile.Release().AsStorage()); | ||
175 | } | ||
176 | } | ||
177 | } \ No newline at end of file | ||
diff --git a/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs b/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs new file mode 100644 index 000000000..42ae2e89b --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs | |||
@@ -0,0 +1,33 @@ | |||
1 | namespace Ryujinx.HLE.Loaders.Processes | ||
2 | { | ||
3 | static class ProcessConst | ||
4 | { | ||
5 | // Binaries from exefs are loaded into mem in this order. Do not change. | ||
6 | public static readonly string[] ExeFsPrefixes = | ||
7 | { | ||
8 | "rtld", | ||
9 | "main", | ||
10 | "subsdk0", | ||
11 | "subsdk1", | ||
12 | "subsdk2", | ||
13 | "subsdk3", | ||
14 | "subsdk4", | ||
15 | "subsdk5", | ||
16 | "subsdk6", | ||
17 | "subsdk7", | ||
18 | "subsdk8", | ||
19 | "subsdk9", | ||
20 | "sdk" | ||
21 | }; | ||
22 | |||
23 | public static readonly string MainNpdmPath = "/main.npdm"; | ||
24 | |||
25 | public const int NroAsetMagic = ('A' << 0) | ('S' << 8) | ('E' << 16) | ('T' << 24); | ||
26 | |||
27 | public const bool AslrEnabled = true; | ||
28 | |||
29 | public const int NsoArgsHeaderSize = 8; | ||
30 | public const int NsoArgsDataSize = 0x9000; | ||
31 | public const int NsoArgsTotalSize = NsoArgsHeaderSize + NsoArgsDataSize; | ||
32 | } | ||
33 | } \ No newline at end of file | ||
diff --git a/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs new file mode 100644 index 000000000..785db0e50 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs | |||
@@ -0,0 +1,244 @@ | |||
1 | using LibHac.Common; | ||
2 | using LibHac.Fs; | ||
3 | using LibHac.Fs.Fsa; | ||
4 | using LibHac.FsSystem; | ||
5 | using LibHac.Ns; | ||
6 | using LibHac.Tools.Fs; | ||
7 | using LibHac.Tools.FsSystem; | ||
8 | using LibHac.Tools.FsSystem.NcaUtils; | ||
9 | using Ryujinx.Common.Logging; | ||
10 | using Ryujinx.HLE.Loaders.Executables; | ||
11 | using Ryujinx.HLE.Loaders.Processes.Extensions; | ||
12 | using System.Collections.Concurrent; | ||
13 | using System.IO; | ||
14 | using System.Linq; | ||
15 | using Path = System.IO.Path; | ||
16 | |||
17 | namespace Ryujinx.HLE.Loaders.Processes | ||
18 | { | ||
19 | public class ProcessLoader | ||
20 | { | ||
21 | private readonly Switch _device; | ||
22 | |||
23 | private readonly ConcurrentDictionary<ulong, ProcessResult> _processesByPid; | ||
24 | |||
25 | private ulong _latestPid; | ||
26 | |||
27 | public ProcessResult ActiveApplication => _processesByPid[_latestPid]; | ||
28 | |||
29 | public ProcessLoader(Switch device) | ||
30 | { | ||
31 | _device = device; | ||
32 | _processesByPid = new ConcurrentDictionary<ulong, ProcessResult>(); | ||
33 | } | ||
34 | |||
35 | public bool LoadXci(string path) | ||
36 | { | ||
37 | FileStream stream = new(path, FileMode.Open, FileAccess.Read); | ||
38 | Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage()); | ||
39 | |||
40 | if (!xci.HasPartition(XciPartitionType.Secure)) | ||
41 | { | ||
42 | Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI Secure partition"); | ||
43 | |||
44 | return false; | ||
45 | } | ||
46 | |||
47 | (bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, out string errorMessage); | ||
48 | |||
49 | if (!success) | ||
50 | { | ||
51 | Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad)); | ||
52 | |||
53 | return false; | ||
54 | } | ||
55 | |||
56 | if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) | ||
57 | { | ||
58 | if (processResult.Start(_device)) | ||
59 | { | ||
60 | _latestPid = processResult.ProcessId; | ||
61 | |||
62 | return true; | ||
63 | } | ||
64 | } | ||
65 | |||
66 | return false; | ||
67 | } | ||
68 | |||
69 | public bool LoadNsp(string path) | ||
70 | { | ||
71 | FileStream file = new(path, FileMode.Open, FileAccess.Read); | ||
72 | PartitionFileSystem partitionFileSystem = new(file.AsStorage()); | ||
73 | |||
74 | (bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, out string errorMessage); | ||
75 | |||
76 | if (processResult.ProcessId == 0) | ||
77 | { | ||
78 | // This is not a normal NSP, it's actually a ExeFS as a NSP | ||
79 | processResult = partitionFileSystem.Load(_device, new BlitStruct<ApplicationControlProperty>(1), partitionFileSystem.GetNpdm(), true); | ||
80 | } | ||
81 | |||
82 | if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) | ||
83 | { | ||
84 | if (processResult.Start(_device)) | ||
85 | { | ||
86 | _latestPid = processResult.ProcessId; | ||
87 | |||
88 | return true; | ||
89 | } | ||
90 | } | ||
91 | |||
92 | if (!success) | ||
93 | { | ||
94 | Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad)); | ||
95 | } | ||
96 | |||
97 | return false; | ||
98 | } | ||
99 | |||
100 | public bool LoadNca(string path) | ||
101 | { | ||
102 | FileStream file = new(path, FileMode.Open, FileAccess.Read); | ||
103 | Nca nca = new(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); | ||
104 | |||
105 | ProcessResult processResult = nca.Load(_device, null, null); | ||
106 | |||
107 | if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) | ||
108 | { | ||
109 | if (processResult.Start(_device)) | ||
110 | { | ||
111 | // NOTE: Check if process is SystemApplicationId or ApplicationId | ||
112 | if (processResult.ProgramId > 0x01000000000007FF) | ||
113 | { | ||
114 | _latestPid = processResult.ProcessId; | ||
115 | } | ||
116 | |||
117 | return true; | ||
118 | } | ||
119 | } | ||
120 | |||
121 | return false; | ||
122 | } | ||
123 | |||
124 | public bool LoadUnpackedNca(string exeFsDirPath, string romFsPath = null) | ||
125 | { | ||
126 | ProcessResult processResult = new LocalFileSystem(exeFsDirPath).Load(_device, romFsPath); | ||
127 | |||
128 | if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) | ||
129 | { | ||
130 | if (processResult.Start(_device)) | ||
131 | { | ||
132 | _latestPid = processResult.ProcessId; | ||
133 | |||
134 | return true; | ||
135 | } | ||
136 | } | ||
137 | |||
138 | return false; | ||
139 | } | ||
140 | |||
141 | public bool LoadNxo(string path) | ||
142 | { | ||
143 | var nacpData = new BlitStruct<ApplicationControlProperty>(1); | ||
144 | IFileSystem dummyExeFs = null; | ||
145 | Stream romfsStream = null; | ||
146 | |||
147 | string programName = ""; | ||
148 | ulong programId = 0000000000000000; | ||
149 | |||
150 | // Load executable. | ||
151 | IExecutable executable; | ||
152 | |||
153 | if (Path.GetExtension(path).ToLower() == ".nro") | ||
154 | { | ||
155 | FileStream input = new(path, FileMode.Open); | ||
156 | NroExecutable nro = new(input.AsStorage()); | ||
157 | |||
158 | executable = nro; | ||
159 | |||
160 | // Open RomFS if exists. | ||
161 | IStorage romFsStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.RomFs, false); | ||
162 | romFsStorage.GetSize(out long romFsSize).ThrowIfFailure(); | ||
163 | if (romFsSize != 0) | ||
164 | { | ||
165 | romfsStream = romFsStorage.AsStream(); | ||
166 | } | ||
167 | |||
168 | // Load Nacp if exists. | ||
169 | IStorage nacpStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.Nacp, false); | ||
170 | nacpStorage.GetSize(out long nacpSize).ThrowIfFailure(); | ||
171 | if (nacpSize != 0) | ||
172 | { | ||
173 | nacpStorage.Read(0, nacpData.ByteSpan); | ||
174 | |||
175 | programName = nacpData.Value.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString(); | ||
176 | |||
177 | if (string.IsNullOrWhiteSpace(programName)) | ||
178 | { | ||
179 | programName = nacpData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString(); | ||
180 | } | ||
181 | |||
182 | if (nacpData.Value.PresenceGroupId != 0) | ||
183 | { | ||
184 | programId = nacpData.Value.PresenceGroupId; | ||
185 | } | ||
186 | else if (nacpData.Value.SaveDataOwnerId != 0) | ||
187 | { | ||
188 | programId = nacpData.Value.SaveDataOwnerId; | ||
189 | } | ||
190 | else if (nacpData.Value.AddOnContentBaseId != 0) | ||
191 | { | ||
192 | programId = nacpData.Value.AddOnContentBaseId - 0x1000; | ||
193 | } | ||
194 | } | ||
195 | |||
196 | // TODO: Add icon maybe ? | ||
197 | } | ||
198 | else | ||
199 | { | ||
200 | programName = System.IO.Path.GetFileNameWithoutExtension(path); | ||
201 | |||
202 | executable = new NsoExecutable(new LocalStorage(path, FileAccess.Read), programName); | ||
203 | } | ||
204 | |||
205 | // Explicitly null TitleId to disable the shader cache. | ||
206 | Graphics.Gpu.GraphicsConfig.TitleId = null; | ||
207 | _device.Gpu.HostInitalized.Set(); | ||
208 | |||
209 | ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device, | ||
210 | _device.System.KernelContext, | ||
211 | dummyExeFs.GetNpdm(), | ||
212 | nacpData.Value, | ||
213 | diskCacheEnabled: false, | ||
214 | allowCodeMemoryForJit: true, | ||
215 | programName, | ||
216 | programId, | ||
217 | null, | ||
218 | executable); | ||
219 | |||
220 | // Make sure the process id is valid. | ||
221 | if (processResult.ProcessId != 0) | ||
222 | { | ||
223 | // Load RomFS. | ||
224 | if (romfsStream != null) | ||
225 | { | ||
226 | _device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romfsStream); | ||
227 | } | ||
228 | |||
229 | // Start process. | ||
230 | if (_processesByPid.TryAdd(processResult.ProcessId, processResult)) | ||
231 | { | ||
232 | if (processResult.Start(_device)) | ||
233 | { | ||
234 | _latestPid = processResult.ProcessId; | ||
235 | |||
236 | return true; | ||
237 | } | ||
238 | } | ||
239 | } | ||
240 | |||
241 | return false; | ||
242 | } | ||
243 | } | ||
244 | } \ No newline at end of file | ||
diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs index 4ebcb7e7d..7176445e5 100644 --- a/Ryujinx.HLE/HOS/ProgramLoader.cs +++ b/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs | |||
@@ -1,69 +1,132 @@ | |||
1 | using LibHac.Account; | ||
2 | using LibHac.Common; | ||
3 | using LibHac.Fs; | ||
4 | using LibHac.Fs.Shim; | ||
5 | using LibHac.FsSystem; | ||
1 | using LibHac.Loader; | 6 | using LibHac.Loader; |
2 | using LibHac.Ncm; | 7 | using LibHac.Ncm; |
3 | using LibHac.Util; | 8 | using LibHac.Ns; |
9 | using LibHac.Tools.Fs; | ||
10 | using LibHac.Tools.FsSystem; | ||
11 | using LibHac.Tools.FsSystem.NcaUtils; | ||
4 | using Ryujinx.Common; | 12 | using Ryujinx.Common; |
5 | using Ryujinx.Common.Logging; | 13 | using Ryujinx.Common.Logging; |
6 | using Ryujinx.Cpu; | 14 | using Ryujinx.HLE.HOS; |
7 | using Ryujinx.HLE.HOS.Kernel; | 15 | using Ryujinx.HLE.HOS.Kernel; |
8 | using Ryujinx.HLE.HOS.Kernel.Common; | 16 | using Ryujinx.HLE.HOS.Kernel.Common; |
9 | using Ryujinx.HLE.HOS.Kernel.Memory; | 17 | using Ryujinx.HLE.HOS.Kernel.Memory; |
10 | using Ryujinx.HLE.HOS.Kernel.Process; | 18 | using Ryujinx.HLE.HOS.Kernel.Process; |
11 | using Ryujinx.HLE.Loaders.Executables; | 19 | using Ryujinx.HLE.Loaders.Executables; |
20 | using Ryujinx.HLE.Loaders.Processes.Extensions; | ||
12 | using Ryujinx.Horizon.Common; | 21 | using Ryujinx.Horizon.Common; |
13 | using System; | 22 | using System; |
14 | using System.Linq; | 23 | using System.Linq; |
15 | using System.Runtime.InteropServices; | 24 | using System.Runtime.InteropServices; |
16 | using Npdm = LibHac.Loader.Npdm; | 25 | using ApplicationId = LibHac.Ncm.ApplicationId; |
17 | 26 | ||
18 | namespace Ryujinx.HLE.HOS | 27 | namespace Ryujinx.HLE.Loaders.Processes |
19 | { | 28 | { |
20 | struct ProgramInfo | 29 | static class ProcessLoaderHelper |
21 | { | 30 | { |
22 | public string Name; | 31 | public static LibHac.Result RegisterProgramMapInfo(Switch device, PartitionFileSystem partitionFileSystem) |
23 | public ulong ProgramId; | ||
24 | public readonly string TitleIdText; | ||
25 | public readonly string DisplayVersion; | ||
26 | public readonly bool DiskCacheEnabled; | ||
27 | public readonly bool AllowCodeMemoryForJit; | ||
28 | |||
29 | public ProgramInfo(in Npdm npdm, string displayVersion, bool diskCacheEnabled, bool allowCodeMemoryForJit) | ||
30 | { | 32 | { |
31 | ulong programId = npdm.Aci.ProgramId.Value; | 33 | ulong applicationId = 0; |
32 | 34 | int programCount = 0; | |
33 | Name = StringUtils.Utf8ZToString(npdm.Meta.ProgramName); | ||
34 | ProgramId = programId; | ||
35 | TitleIdText = programId.ToString("x16"); | ||
36 | DisplayVersion = displayVersion; | ||
37 | DiskCacheEnabled = diskCacheEnabled; | ||
38 | AllowCodeMemoryForJit = allowCodeMemoryForJit; | ||
39 | } | ||
40 | } | ||
41 | 35 | ||
42 | struct ProgramLoadResult | 36 | Span<bool> hasIndex = stackalloc bool[0x10]; |
43 | { | ||
44 | public static ProgramLoadResult Failed => new ProgramLoadResult(false, null, null, 0); | ||
45 | 37 | ||
46 | public readonly bool Success; | 38 | foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca")) |
47 | public readonly ProcessTamperInfo TamperInfo; | 39 | { |
48 | public readonly IDiskCacheLoadState DiskCacheLoadState; | 40 | Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath); |
49 | public readonly ulong ProcessId; | ||
50 | 41 | ||
51 | public ProgramLoadResult(bool success, ProcessTamperInfo tamperInfo, IDiskCacheLoadState diskCacheLoadState, ulong pid) | 42 | if (!nca.IsProgram() && nca.IsPatch()) |
52 | { | 43 | { |
53 | Success = success; | 44 | continue; |
54 | TamperInfo = tamperInfo; | 45 | } |
55 | DiskCacheLoadState = diskCacheLoadState; | 46 | |
56 | ProcessId = pid; | 47 | ulong currentProgramId = nca.Header.TitleId; |
48 | ulong currentMainProgramId = currentProgramId & ~0xFFFul; | ||
49 | |||
50 | if (applicationId == 0 && currentMainProgramId != 0) | ||
51 | { | ||
52 | applicationId = currentMainProgramId; | ||
53 | } | ||
54 | |||
55 | if (applicationId != currentMainProgramId) | ||
56 | { | ||
57 | // Currently there aren't any known multi-application game cards containing multi-program applications, | ||
58 | // so because multi-application game cards are the only way we could run into multiple applications | ||
59 | // we'll just return that there's a single program. | ||
60 | programCount = 1; | ||
61 | |||
62 | break; | ||
63 | } | ||
64 | |||
65 | hasIndex[(int)(currentProgramId & 0xF)] = true; | ||
66 | } | ||
67 | |||
68 | if (programCount == 0) | ||
69 | { | ||
70 | for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++) | ||
71 | { | ||
72 | programCount++; | ||
73 | } | ||
74 | } | ||
75 | |||
76 | if (programCount <= 0) | ||
77 | { | ||
78 | return LibHac.Result.Success; | ||
79 | } | ||
80 | |||
81 | Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10]; | ||
82 | |||
83 | for (int i = 0; i < programCount; i++) | ||
84 | { | ||
85 | mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i); | ||
86 | mapInfo[i].MainProgramId = new ApplicationId(applicationId); | ||
87 | mapInfo[i].ProgramIndex = (byte)i; | ||
88 | } | ||
89 | |||
90 | return device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo[..programCount]); | ||
57 | } | 91 | } |
58 | } | ||
59 | 92 | ||
60 | static class ProgramLoader | 93 | public static LibHac.Result EnsureSaveData(Switch device, ApplicationId applicationId, BlitStruct<ApplicationControlProperty> applicationControlProperty) |
61 | { | 94 | { |
62 | private const bool AslrEnabled = true; | 95 | Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists."); |
63 | 96 | ||
64 | private const int ArgsHeaderSize = 8; | 97 | ref ApplicationControlProperty control = ref applicationControlProperty.Value; |
65 | private const int ArgsDataSize = 0x9000; | 98 | |
66 | private const int ArgsTotalSize = ArgsHeaderSize + ArgsDataSize; | 99 | if (LibHac.Common.Utilities.IsZeros(applicationControlProperty.ByteSpan)) |
100 | { | ||
101 | // 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. | ||
102 | control = ref new BlitStruct<ApplicationControlProperty>(1).Value; | ||
103 | |||
104 | // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. | ||
105 | control.UserAccountSaveDataSize = 0x4000; | ||
106 | control.UserAccountSaveDataJournalSize = 0x4000; | ||
107 | control.SaveDataOwnerId = applicationId.Value; | ||
108 | |||
109 | Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); | ||
110 | } | ||
111 | |||
112 | LibHac.Result resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control); | ||
113 | if (resultCode.IsFailure()) | ||
114 | { | ||
115 | Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}"); | ||
116 | |||
117 | return resultCode; | ||
118 | } | ||
119 | |||
120 | Uid userId = device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid(); | ||
121 | |||
122 | resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in userId); | ||
123 | if (resultCode.IsFailure()) | ||
124 | { | ||
125 | Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}"); | ||
126 | } | ||
127 | |||
128 | return resultCode; | ||
129 | } | ||
67 | 130 | ||
68 | public static bool LoadKip(KernelContext context, KipExecutable kip) | 131 | public static bool LoadKip(KernelContext context, KipExecutable kip) |
69 | { | 132 | { |
@@ -74,17 +137,14 @@ namespace Ryujinx.HLE.HOS | |||
74 | endOffset = kip.BssOffset + kip.BssSize; | 137 | endOffset = kip.BssOffset + kip.BssSize; |
75 | } | 138 | } |
76 | 139 | ||
77 | uint codeSize = BitUtils.AlignUp<uint>(kip.TextOffset + endOffset, KPageTableBase.PageSize); | 140 | uint codeSize = BitUtils.AlignUp<uint>(kip.TextOffset + endOffset, KPageTableBase.PageSize); |
78 | 141 | int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); | |
79 | int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); | ||
80 | |||
81 | ulong codeBaseAddress = kip.Is64BitAddressSpace ? 0x8000000UL : 0x200000UL; | 142 | ulong codeBaseAddress = kip.Is64BitAddressSpace ? 0x8000000UL : 0x200000UL; |
82 | 143 | ulong codeAddress = codeBaseAddress + kip.TextOffset; | |
83 | ulong codeAddress = codeBaseAddress + kip.TextOffset; | ||
84 | 144 | ||
85 | ProcessCreationFlags flags = 0; | 145 | ProcessCreationFlags flags = 0; |
86 | 146 | ||
87 | if (AslrEnabled) | 147 | if (ProcessConst.AslrEnabled) |
88 | { | 148 | { |
89 | // TODO: Randomization. | 149 | // TODO: Randomization. |
90 | 150 | ||
@@ -101,24 +161,11 @@ namespace Ryujinx.HLE.HOS | |||
101 | flags |= ProcessCreationFlags.Is64Bit; | 161 | flags |= ProcessCreationFlags.Is64Bit; |
102 | } | 162 | } |
103 | 163 | ||
104 | ProcessCreationInfo creationInfo = new ProcessCreationInfo( | 164 | ProcessCreationInfo creationInfo = new(kip.Name, kip.Version, kip.ProgramId, codeAddress, codePagesCount, flags, 0, 0); |
105 | kip.Name, | 165 | MemoryRegion memoryRegion = kip.UsesSecureMemory ? MemoryRegion.Service : MemoryRegion.Application; |
106 | kip.Version, | 166 | KMemoryRegionManager region = context.MemoryManager.MemoryRegions[(int)memoryRegion]; |
107 | kip.ProgramId, | ||
108 | codeAddress, | ||
109 | codePagesCount, | ||
110 | flags, | ||
111 | 0, | ||
112 | 0); | ||
113 | |||
114 | MemoryRegion memoryRegion = kip.UsesSecureMemory | ||
115 | ? MemoryRegion.Service | ||
116 | : MemoryRegion.Application; | ||
117 | |||
118 | KMemoryRegionManager region = context.MemoryManager.MemoryRegions[(int)memoryRegion]; | ||
119 | 167 | ||
120 | Result result = region.AllocatePages(out KPageList pageList, (ulong)codePagesCount); | 168 | Result result = region.AllocatePages(out KPageList pageList, (ulong)codePagesCount); |
121 | |||
122 | if (result != Result.Success) | 169 | if (result != Result.Success) |
123 | { | 170 | { |
124 | Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); | 171 | Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); |
@@ -126,7 +173,7 @@ namespace Ryujinx.HLE.HOS | |||
126 | return false; | 173 | return false; |
127 | } | 174 | } |
128 | 175 | ||
129 | KProcess process = new KProcess(context); | 176 | KProcess process = new(context); |
130 | 177 | ||
131 | var processContextFactory = new ArmProcessContextFactory( | 178 | var processContextFactory = new ArmProcessContextFactory( |
132 | context.Device.System.TickSource, | 179 | context.Device.System.TickSource, |
@@ -137,14 +184,7 @@ namespace Ryujinx.HLE.HOS | |||
137 | codeAddress, | 184 | codeAddress, |
138 | codeSize); | 185 | codeSize); |
139 | 186 | ||
140 | result = process.InitializeKip( | 187 | result = process.InitializeKip(creationInfo, kip.Capabilities, pageList, context.ResourceLimit, memoryRegion, processContextFactory); |
141 | creationInfo, | ||
142 | kip.Capabilities, | ||
143 | pageList, | ||
144 | context.ResourceLimit, | ||
145 | memoryRegion, | ||
146 | processContextFactory); | ||
147 | |||
148 | if (result != Result.Success) | 188 | if (result != Result.Success) |
149 | { | 189 | { |
150 | Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); | 190 | Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); |
@@ -153,7 +193,6 @@ namespace Ryujinx.HLE.HOS | |||
153 | } | 193 | } |
154 | 194 | ||
155 | result = LoadIntoMemory(process, kip, codeBaseAddress); | 195 | result = LoadIntoMemory(process, kip, codeBaseAddress); |
156 | |||
157 | if (result != Result.Success) | 196 | if (result != Result.Success) |
158 | { | 197 | { |
159 | Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); | 198 | Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); |
@@ -164,7 +203,6 @@ namespace Ryujinx.HLE.HOS | |||
164 | process.DefaultCpuCore = kip.IdealCoreId; | 203 | process.DefaultCpuCore = kip.IdealCoreId; |
165 | 204 | ||
166 | result = process.Start(kip.Priority, (ulong)kip.StackSize); | 205 | result = process.Start(kip.Priority, (ulong)kip.StackSize); |
167 | |||
168 | if (result != Result.Success) | 206 | if (result != Result.Success) |
169 | { | 207 | { |
170 | Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\"."); | 208 | Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\"."); |
@@ -177,20 +215,27 @@ namespace Ryujinx.HLE.HOS | |||
177 | return true; | 215 | return true; |
178 | } | 216 | } |
179 | 217 | ||
180 | public static ProgramLoadResult LoadNsos( | 218 | public static ProcessResult LoadNsos( |
219 | Switch device, | ||
181 | KernelContext context, | 220 | KernelContext context, |
182 | MetaLoader metaData, | 221 | MetaLoader metaLoader, |
183 | ProgramInfo programInfo, | 222 | ApplicationControlProperty applicationControlProperties, |
223 | bool diskCacheEnabled, | ||
224 | bool allowCodeMemoryForJit, | ||
225 | string name, | ||
226 | ulong programId, | ||
184 | byte[] arguments = null, | 227 | byte[] arguments = null, |
185 | params IExecutable[] executables) | 228 | params IExecutable[] executables) |
186 | { | 229 | { |
187 | context.Device.System.ServiceTable.WaitServicesReady(); | 230 | context.Device.System.ServiceTable.WaitServicesReady(); |
188 | 231 | ||
189 | LibHac.Result rc = metaData.GetNpdm(out var npdm); | 232 | LibHac.Result resultCode = metaLoader.GetNpdm(out var npdm); |
190 | 233 | ||
191 | if (rc.IsFailure()) | 234 | if (resultCode.IsFailure()) |
192 | { | 235 | { |
193 | return ProgramLoadResult.Failed; | 236 | Logger.Error?.Print(LogClass.Loader, $"Process initialization failed getting npdm. Result Code {resultCode.ToStringWithName()}"); |
237 | |||
238 | return ProcessResult.Failed; | ||
194 | } | 239 | } |
195 | 240 | ||
196 | ref readonly var meta = ref npdm.Meta; | 241 | ref readonly var meta = ref npdm.Meta; |
@@ -202,10 +247,10 @@ namespace Ryujinx.HLE.HOS | |||
202 | 247 | ||
203 | var buildIds = executables.Select(e => (e switch | 248 | var buildIds = executables.Select(e => (e switch |
204 | { | 249 | { |
205 | NsoExecutable nso => BitConverter.ToString(nso.BuildId.ItemsRo.ToArray()), | 250 | NsoExecutable nso => Convert.ToHexString(nso.BuildId.ItemsRo.ToArray()), |
206 | NroExecutable nro => BitConverter.ToString(nro.Header.BuildId), | 251 | NroExecutable nro => Convert.ToHexString(nro.Header.BuildId), |
207 | _ => "" | 252 | _ => "" |
208 | }).Replace("-", "").ToUpper()); | 253 | }).ToUpper()); |
209 | 254 | ||
210 | ulong[] nsoBase = new ulong[executables.Length]; | 255 | ulong[] nsoBase = new ulong[executables.Length]; |
211 | 256 | ||
@@ -214,7 +259,7 @@ namespace Ryujinx.HLE.HOS | |||
214 | IExecutable nso = executables[index]; | 259 | IExecutable nso = executables[index]; |
215 | 260 | ||
216 | uint textEnd = nso.TextOffset + (uint)nso.Text.Length; | 261 | uint textEnd = nso.TextOffset + (uint)nso.Text.Length; |
217 | uint roEnd = nso.RoOffset + (uint)nso.Ro.Length; | 262 | uint roEnd = nso.RoOffset + (uint)nso.Ro.Length; |
218 | uint dataEnd = nso.DataOffset + (uint)nso.Data.Length + nso.BssSize; | 263 | uint dataEnd = nso.DataOffset + (uint)nso.Data.Length + nso.BssSize; |
219 | 264 | ||
220 | uint nsoSize = textEnd; | 265 | uint nsoSize = textEnd; |
@@ -239,31 +284,30 @@ namespace Ryujinx.HLE.HOS | |||
239 | { | 284 | { |
240 | argsStart = codeSize; | 285 | argsStart = codeSize; |
241 | 286 | ||
242 | argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ArgsTotalSize - 1, KPageTableBase.PageSize); | 287 | argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ProcessConst.NsoArgsTotalSize - 1, KPageTableBase.PageSize); |
243 | 288 | ||
244 | codeSize += argsSize; | 289 | codeSize += argsSize; |
245 | } | 290 | } |
246 | } | 291 | } |
247 | 292 | ||
248 | int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); | 293 | int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); |
249 | |||
250 | int personalMmHeapPagesCount = (int)(meta.SystemResourceSize / KPageTableBase.PageSize); | 294 | int personalMmHeapPagesCount = (int)(meta.SystemResourceSize / KPageTableBase.PageSize); |
251 | 295 | ||
252 | ProcessCreationInfo creationInfo = new ProcessCreationInfo( | 296 | ProcessCreationInfo creationInfo = new( |
253 | programInfo.Name, | 297 | name, |
254 | (int)meta.Version, | 298 | (int)meta.Version, |
255 | programInfo.ProgramId, | 299 | programId, |
256 | codeStart, | 300 | codeStart, |
257 | codePagesCount, | 301 | codePagesCount, |
258 | (ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication, | 302 | (ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication, |
259 | 0, | 303 | 0, |
260 | personalMmHeapPagesCount); | 304 | personalMmHeapPagesCount); |
261 | 305 | ||
262 | context.Device.System.LibHacHorizonManager.InitializeApplicationClient(new ProgramId(programInfo.ProgramId), in npdm); | 306 | context.Device.System.LibHacHorizonManager.InitializeApplicationClient(new ProgramId(programId), in npdm); |
263 | 307 | ||
264 | Result result; | 308 | Result result; |
265 | 309 | ||
266 | KResourceLimit resourceLimit = new KResourceLimit(context); | 310 | KResourceLimit resourceLimit = new(context); |
267 | 311 | ||
268 | long applicationRgSize = (long)context.MemoryManager.MemoryRegions[(int)MemoryRegion.Application].Size; | 312 | long applicationRgSize = (long)context.MemoryManager.MemoryRegions[(int)MemoryRegion.Application].Size; |
269 | 313 | ||
@@ -293,26 +337,26 @@ namespace Ryujinx.HLE.HOS | |||
293 | { | 337 | { |
294 | Logger.Error?.Print(LogClass.Loader, $"Process initialization failed setting resource limit values."); | 338 | Logger.Error?.Print(LogClass.Loader, $"Process initialization failed setting resource limit values."); |
295 | 339 | ||
296 | return ProgramLoadResult.Failed; | 340 | return ProcessResult.Failed; |
297 | } | 341 | } |
298 | 342 | ||
299 | KProcess process = new KProcess(context, programInfo.AllowCodeMemoryForJit); | 343 | KProcess process = new(context, allowCodeMemoryForJit); |
300 | |||
301 | MemoryRegion memoryRegion = (MemoryRegion)((npdm.Acid.Flags >> 2) & 0xf); | ||
302 | 344 | ||
345 | // NOTE: This field doesn't exists one firmware pre-5.0.0, a workaround have to be found. | ||
346 | MemoryRegion memoryRegion = (MemoryRegion)(npdm.Acid.Flags >> 2 & 0xf); | ||
303 | if (memoryRegion > MemoryRegion.NvServices) | 347 | if (memoryRegion > MemoryRegion.NvServices) |
304 | { | 348 | { |
305 | Logger.Error?.Print(LogClass.Loader, $"Process initialization failed due to invalid ACID flags."); | 349 | Logger.Error?.Print(LogClass.Loader, $"Process initialization failed due to invalid ACID flags."); |
306 | 350 | ||
307 | return ProgramLoadResult.Failed; | 351 | return ProcessResult.Failed; |
308 | } | 352 | } |
309 | 353 | ||
310 | var processContextFactory = new ArmProcessContextFactory( | 354 | var processContextFactory = new ArmProcessContextFactory( |
311 | context.Device.System.TickSource, | 355 | context.Device.System.TickSource, |
312 | context.Device.Gpu, | 356 | context.Device.Gpu, |
313 | programInfo.TitleIdText, | 357 | $"{programId:x16}", |
314 | programInfo.DisplayVersion, | 358 | applicationControlProperties.DisplayVersionString.ToString(), |
315 | programInfo.DiskCacheEnabled, | 359 | diskCacheEnabled, |
316 | codeStart, | 360 | codeStart, |
317 | codeSize); | 361 | codeSize); |
318 | 362 | ||
@@ -327,7 +371,7 @@ namespace Ryujinx.HLE.HOS | |||
327 | { | 371 | { |
328 | Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); | 372 | Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); |
329 | 373 | ||
330 | return ProgramLoadResult.Failed; | 374 | return ProcessResult.Failed; |
331 | } | 375 | } |
332 | 376 | ||
333 | for (int index = 0; index < executables.Length; index++) | 377 | for (int index = 0; index < executables.Length; index++) |
@@ -335,32 +379,22 @@ namespace Ryujinx.HLE.HOS | |||
335 | Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}..."); | 379 | Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}..."); |
336 | 380 | ||
337 | result = LoadIntoMemory(process, executables[index], nsoBase[index]); | 381 | result = LoadIntoMemory(process, executables[index], nsoBase[index]); |
338 | |||
339 | if (result != Result.Success) | 382 | if (result != Result.Success) |
340 | { | 383 | { |
341 | Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); | 384 | Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); |
342 | 385 | ||
343 | return ProgramLoadResult.Failed; | 386 | return ProcessResult.Failed; |
344 | } | 387 | } |
345 | } | 388 | } |
346 | 389 | ||
347 | process.DefaultCpuCore = meta.DefaultCpuId; | 390 | process.DefaultCpuCore = meta.DefaultCpuId; |
348 | 391 | ||
349 | result = process.Start(meta.MainThreadPriority, meta.MainThreadStackSize); | ||
350 | |||
351 | if (result != Result.Success) | ||
352 | { | ||
353 | Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\"."); | ||
354 | |||
355 | return ProgramLoadResult.Failed; | ||
356 | } | ||
357 | |||
358 | context.Processes.TryAdd(process.Pid, process); | 392 | context.Processes.TryAdd(process.Pid, process); |
359 | 393 | ||
360 | // Keep the build ids because the tamper machine uses them to know which process to associate a | 394 | // Keep the build ids because the tamper machine uses them to know which process to associate a |
361 | // tamper to and also keep the starting address of each executable inside a process because some | 395 | // tamper to and also keep the starting address of each executable inside a process because some |
362 | // memory modifications are relative to this address. | 396 | // memory modifications are relative to this address. |
363 | ProcessTamperInfo tamperInfo = new ProcessTamperInfo( | 397 | ProcessTamperInfo tamperInfo = new( |
364 | process, | 398 | process, |
365 | buildIds, | 399 | buildIds, |
366 | nsoBase, | 400 | nsoBase, |
@@ -368,10 +402,13 @@ namespace Ryujinx.HLE.HOS | |||
368 | process.MemoryManager.AliasRegionStart, | 402 | process.MemoryManager.AliasRegionStart, |
369 | process.MemoryManager.CodeRegionStart); | 403 | process.MemoryManager.CodeRegionStart); |
370 | 404 | ||
371 | return new ProgramLoadResult(true, tamperInfo, processContextFactory.DiskCacheLoadState, process.Pid); | 405 | // Once everything is loaded, we can load cheats. |
406 | device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(programId, tamperInfo, device.TamperMachine); | ||
407 | |||
408 | return new ProcessResult(metaLoader, applicationControlProperties, diskCacheEnabled, allowCodeMemoryForJit, processContextFactory.DiskCacheLoadState, process.Pid, meta.MainThreadPriority, meta.MainThreadStackSize); | ||
372 | } | 409 | } |
373 | 410 | ||
374 | private static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress) | 411 | public static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress) |
375 | { | 412 | { |
376 | ulong textStart = baseAddress + image.TextOffset; | 413 | ulong textStart = baseAddress + image.TextOffset; |
377 | ulong roStart = baseAddress + image.RoOffset; | 414 | ulong roStart = baseAddress + image.RoOffset; |
@@ -404,14 +441,12 @@ namespace Ryujinx.HLE.HOS | |||
404 | } | 441 | } |
405 | 442 | ||
406 | Result result = SetProcessMemoryPermission(textStart, (ulong)image.Text.Length, KMemoryPermission.ReadAndExecute); | 443 | Result result = SetProcessMemoryPermission(textStart, (ulong)image.Text.Length, KMemoryPermission.ReadAndExecute); |
407 | |||
408 | if (result != Result.Success) | 444 | if (result != Result.Success) |
409 | { | 445 | { |
410 | return result; | 446 | return result; |
411 | } | 447 | } |
412 | 448 | ||
413 | result = SetProcessMemoryPermission(roStart, (ulong)image.Ro.Length, KMemoryPermission.Read); | 449 | result = SetProcessMemoryPermission(roStart, (ulong)image.Ro.Length, KMemoryPermission.Read); |
414 | |||
415 | if (result != Result.Success) | 450 | if (result != Result.Success) |
416 | { | 451 | { |
417 | return result; | 452 | return result; |
diff --git a/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs b/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs new file mode 100644 index 000000000..6bbeee1b8 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs | |||
@@ -0,0 +1,92 @@ | |||
1 | using LibHac.Loader; | ||
2 | using LibHac.Ns; | ||
3 | using Ryujinx.Common.Logging; | ||
4 | using Ryujinx.Cpu; | ||
5 | using Ryujinx.HLE.Loaders.Processes.Extensions; | ||
6 | using Ryujinx.Horizon.Common; | ||
7 | |||
8 | namespace Ryujinx.HLE.Loaders.Processes | ||
9 | { | ||
10 | public struct ProcessResult | ||
11 | { | ||
12 | public static ProcessResult Failed => new(null, new ApplicationControlProperty(), false, false, null, 0, 0, 0); | ||
13 | |||
14 | private readonly byte _mainThreadPriority; | ||
15 | private readonly uint _mainThreadStackSize; | ||
16 | |||
17 | public readonly IDiskCacheLoadState DiskCacheLoadState; | ||
18 | |||
19 | public readonly MetaLoader MetaLoader; | ||
20 | public readonly ApplicationControlProperty ApplicationControlProperties; | ||
21 | |||
22 | public readonly ulong ProcessId; | ||
23 | public string Name; | ||
24 | public ulong ProgramId; | ||
25 | public readonly string ProgramIdText; | ||
26 | public readonly bool Is64Bit; | ||
27 | public readonly bool DiskCacheEnabled; | ||
28 | public readonly bool AllowCodeMemoryForJit; | ||
29 | |||
30 | public ProcessResult( | ||
31 | MetaLoader metaLoader, | ||
32 | ApplicationControlProperty applicationControlProperties, | ||
33 | bool diskCacheEnabled, | ||
34 | bool allowCodeMemoryForJit, | ||
35 | IDiskCacheLoadState diskCacheLoadState, | ||
36 | ulong pid, | ||
37 | byte mainThreadPriority, | ||
38 | uint mainThreadStackSize) | ||
39 | { | ||
40 | _mainThreadPriority = mainThreadPriority; | ||
41 | _mainThreadStackSize = mainThreadStackSize; | ||
42 | |||
43 | DiskCacheLoadState = diskCacheLoadState; | ||
44 | ProcessId = pid; | ||
45 | |||
46 | MetaLoader = metaLoader; | ||
47 | ApplicationControlProperties = applicationControlProperties; | ||
48 | |||
49 | if (metaLoader is not null) | ||
50 | { | ||
51 | ulong programId = metaLoader.GetProgramId(); | ||
52 | |||
53 | Name = metaLoader.GetProgramName(); | ||
54 | ProgramId = programId; | ||
55 | ProgramIdText = $"{programId:x16}"; | ||
56 | Is64Bit = metaLoader.IsProgram64Bit(); | ||
57 | } | ||
58 | |||
59 | DiskCacheEnabled = diskCacheEnabled; | ||
60 | AllowCodeMemoryForJit = allowCodeMemoryForJit; | ||
61 | } | ||
62 | |||
63 | public bool Start(Switch device) | ||
64 | { | ||
65 | device.Configuration.ContentManager.LoadEntries(device); | ||
66 | |||
67 | Result result = device.System.KernelContext.Processes[ProcessId].Start(_mainThreadPriority, _mainThreadStackSize); | ||
68 | if (result != Result.Success) | ||
69 | { | ||
70 | Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\"."); | ||
71 | |||
72 | return false; | ||
73 | } | ||
74 | |||
75 | // TODO: LibHac npdm currently doesn't support version field. | ||
76 | string version; | ||
77 | |||
78 | if (ProgramId > 0x0100000000007FFF) | ||
79 | { | ||
80 | version = ApplicationControlProperties.DisplayVersionString.ToString(); | ||
81 | } | ||
82 | else | ||
83 | { | ||
84 | version = device.System.ContentManager.GetCurrentFirmwareVersion().VersionString; | ||
85 | } | ||
86 | |||
87 | Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {Name} v{version} [{ProgramIdText}] [{(Is64Bit ? "64-bit" : "32-bit")}]"); | ||
88 | |||
89 | return true; | ||
90 | } | ||
91 | } | ||
92 | } \ No newline at end of file | ||
diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 61e5e5727..62d14a546 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs | |||
@@ -6,6 +6,7 @@ using Ryujinx.HLE.FileSystem; | |||
6 | using Ryujinx.HLE.HOS; | 6 | using Ryujinx.HLE.HOS; |
7 | using Ryujinx.HLE.HOS.Services.Apm; | 7 | using Ryujinx.HLE.HOS.Services.Apm; |
8 | using Ryujinx.HLE.HOS.Services.Hid; | 8 | using Ryujinx.HLE.HOS.Services.Hid; |
9 | using Ryujinx.HLE.Loaders.Processes; | ||
9 | using Ryujinx.HLE.Ui; | 10 | using Ryujinx.HLE.Ui; |
10 | using Ryujinx.Memory; | 11 | using Ryujinx.Memory; |
11 | using System; | 12 | using System; |
@@ -20,7 +21,7 @@ namespace Ryujinx.HLE | |||
20 | public GpuContext Gpu { get; } | 21 | public GpuContext Gpu { get; } |
21 | public VirtualFileSystem FileSystem { get; } | 22 | public VirtualFileSystem FileSystem { get; } |
22 | public HOS.Horizon System { get; } | 23 | public HOS.Horizon System { get; } |
23 | public ApplicationLoader Application { get; } | 24 | public ProcessLoader Processes { get; } |
24 | public PerformanceStatistics Statistics { get; } | 25 | public PerformanceStatistics Statistics { get; } |
25 | public Hid Hid { get; } | 26 | public Hid Hid { get; } |
26 | public TamperMachine TamperMachine { get; } | 27 | public TamperMachine TamperMachine { get; } |
@@ -50,7 +51,7 @@ namespace Ryujinx.HLE | |||
50 | System = new HOS.Horizon(this); | 51 | System = new HOS.Horizon(this); |
51 | Statistics = new PerformanceStatistics(); | 52 | Statistics = new PerformanceStatistics(); |
52 | Hid = new Hid(this, System.HidStorage); | 53 | Hid = new Hid(this, System.HidStorage); |
53 | Application = new ApplicationLoader(this); | 54 | Processes = new ProcessLoader(this); |
54 | TamperMachine = new TamperMachine(); | 55 | TamperMachine = new TamperMachine(); |
55 | 56 | ||
56 | System.State.SetLanguage(Configuration.SystemLanguage); | 57 | System.State.SetLanguage(Configuration.SystemLanguage); |
@@ -64,29 +65,29 @@ namespace Ryujinx.HLE | |||
64 | System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode; | 65 | System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode; |
65 | } | 66 | } |
66 | 67 | ||
67 | public void LoadCart(string exeFsDir, string romFsFile = null) | 68 | public bool LoadCart(string exeFsDir, string romFsFile = null) |
68 | { | 69 | { |
69 | Application.LoadCart(exeFsDir, romFsFile); | 70 | return Processes.LoadUnpackedNca(exeFsDir, romFsFile); |
70 | } | 71 | } |
71 | 72 | ||
72 | public void LoadXci(string xciFile) | 73 | public bool LoadXci(string xciFile) |
73 | { | 74 | { |
74 | Application.LoadXci(xciFile); | 75 | return Processes.LoadXci(xciFile); |
75 | } | 76 | } |
76 | 77 | ||
77 | public void LoadNca(string ncaFile) | 78 | public bool LoadNca(string ncaFile) |
78 | { | 79 | { |
79 | Application.LoadNca(ncaFile); | 80 | return Processes.LoadNca(ncaFile); |
80 | } | 81 | } |
81 | 82 | ||
82 | public void LoadNsp(string nspFile) | 83 | public bool LoadNsp(string nspFile) |
83 | { | 84 | { |
84 | Application.LoadNsp(nspFile); | 85 | return Processes.LoadNsp(nspFile); |
85 | } | 86 | } |
86 | 87 | ||
87 | public void LoadProgram(string fileName) | 88 | public bool LoadProgram(string fileName) |
88 | { | 89 | { |
89 | Application.LoadProgram(fileName); | 90 | return Processes.LoadNxo(fileName); |
90 | } | 91 | } |
91 | 92 | ||
92 | public bool WaitFifo() | 93 | public bool WaitFifo() |
@@ -123,7 +124,7 @@ namespace Ryujinx.HLE | |||
123 | 124 | ||
124 | public void EnableCheats() | 125 | public void EnableCheats() |
125 | { | 126 | { |
126 | FileSystem.ModLoader.EnableCheats(Application.TitleId, TamperMachine); | 127 | FileSystem.ModLoader.EnableCheats(Processes.ActiveApplication.ProgramId, TamperMachine); |
127 | } | 128 | } |
128 | 129 | ||
129 | public bool IsAudioMuted() | 130 | public bool IsAudioMuted() |
@@ -152,4 +153,4 @@ namespace Ryujinx.HLE | |||
152 | } | 153 | } |
153 | } | 154 | } |
154 | } | 155 | } |
155 | } \ No newline at end of file | 156 | } |
diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs index f618e38d6..54ab18cda 100644 --- a/Ryujinx.Headless.SDL2/Program.cs +++ b/Ryujinx.Headless.SDL2/Program.cs | |||
@@ -447,10 +447,10 @@ namespace Ryujinx.Headless.SDL2 | |||
447 | 447 | ||
448 | private static void SetupProgressHandler() | 448 | private static void SetupProgressHandler() |
449 | { | 449 | { |
450 | if (_emulationContext.Application.DiskCacheLoadState != null) | 450 | if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) |
451 | { | 451 | { |
452 | _emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler; | 452 | _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler; |
453 | _emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler; | 453 | _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler; |
454 | } | 454 | } |
455 | 455 | ||
456 | _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; | 456 | _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; |
@@ -608,12 +608,24 @@ namespace Ryujinx.Headless.SDL2 | |||
608 | if (romFsFiles.Length > 0) | 608 | if (romFsFiles.Length > 0) |
609 | { | 609 | { |
610 | Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); | 610 | Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); |
611 | _emulationContext.LoadCart(path, romFsFiles[0]); | 611 | |
612 | if (!_emulationContext.LoadCart(path, romFsFiles[0])) | ||
613 | { | ||
614 | _emulationContext.Dispose(); | ||
615 | |||
616 | return false; | ||
617 | } | ||
612 | } | 618 | } |
613 | else | 619 | else |
614 | { | 620 | { |
615 | Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); | 621 | Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); |
616 | _emulationContext.LoadCart(path); | 622 | |
623 | if (!_emulationContext.LoadCart(path)) | ||
624 | { | ||
625 | _emulationContext.Dispose(); | ||
626 | |||
627 | return false; | ||
628 | } | ||
617 | } | 629 | } |
618 | } | 630 | } |
619 | else if (File.Exists(path)) | 631 | else if (File.Exists(path)) |
@@ -622,27 +634,52 @@ namespace Ryujinx.Headless.SDL2 | |||
622 | { | 634 | { |
623 | case ".xci": | 635 | case ".xci": |
624 | Logger.Info?.Print(LogClass.Application, "Loading as XCI."); | 636 | Logger.Info?.Print(LogClass.Application, "Loading as XCI."); |
625 | _emulationContext.LoadXci(path); | 637 | |
638 | if (!_emulationContext.LoadXci(path)) | ||
639 | { | ||
640 | _emulationContext.Dispose(); | ||
641 | |||
642 | return false; | ||
643 | } | ||
626 | break; | 644 | break; |
627 | case ".nca": | 645 | case ".nca": |
628 | Logger.Info?.Print(LogClass.Application, "Loading as NCA."); | 646 | Logger.Info?.Print(LogClass.Application, "Loading as NCA."); |
629 | _emulationContext.LoadNca(path); | 647 | |
648 | if (!_emulationContext.LoadNca(path)) | ||
649 | { | ||
650 | _emulationContext.Dispose(); | ||
651 | |||
652 | return false; | ||
653 | } | ||
630 | break; | 654 | break; |
631 | case ".nsp": | 655 | case ".nsp": |
632 | case ".pfs0": | 656 | case ".pfs0": |
633 | Logger.Info?.Print(LogClass.Application, "Loading as NSP."); | 657 | Logger.Info?.Print(LogClass.Application, "Loading as NSP."); |
634 | _emulationContext.LoadNsp(path); | 658 | |
659 | if (!_emulationContext.LoadNsp(path)) | ||
660 | { | ||
661 | _emulationContext.Dispose(); | ||
662 | |||
663 | return false; | ||
664 | } | ||
635 | break; | 665 | break; |
636 | default: | 666 | default: |
637 | Logger.Info?.Print(LogClass.Application, "Loading as Homebrew."); | 667 | Logger.Info?.Print(LogClass.Application, "Loading as Homebrew."); |
638 | try | 668 | try |
639 | { | 669 | { |
640 | _emulationContext.LoadProgram(path); | 670 | if (!_emulationContext.LoadProgram(path)) |
671 | { | ||
672 | _emulationContext.Dispose(); | ||
673 | |||
674 | return false; | ||
675 | } | ||
641 | } | 676 | } |
642 | catch (ArgumentOutOfRangeException) | 677 | catch (ArgumentOutOfRangeException) |
643 | { | 678 | { |
644 | Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); | 679 | Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); |
645 | 680 | ||
681 | _emulationContext.Dispose(); | ||
682 | |||
646 | return false; | 683 | return false; |
647 | } | 684 | } |
648 | break; | 685 | break; |
@@ -664,4 +701,4 @@ namespace Ryujinx.Headless.SDL2 | |||
664 | return true; | 701 | return true; |
665 | } | 702 | } |
666 | } | 703 | } |
667 | } \ No newline at end of file | 704 | } |
diff --git a/Ryujinx.Headless.SDL2/WindowBase.cs b/Ryujinx.Headless.SDL2/WindowBase.cs index db6c8ec4d..e33710421 100644 --- a/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/Ryujinx.Headless.SDL2/WindowBase.cs | |||
@@ -145,16 +145,14 @@ namespace Ryujinx.Headless.SDL2 | |||
145 | 145 | ||
146 | private void InitializeWindow() | 146 | private void InitializeWindow() |
147 | { | 147 | { |
148 | string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty | 148 | var activeProcess = Device.Processes.ActiveApplication; |
149 | : $" - {Device.Application.TitleName}"; | 149 | var nacp = activeProcess.ApplicationControlProperties; |
150 | 150 | int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage; | |
151 | string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty | 151 | |
152 | : $" v{Device.Application.DisplayVersion}"; | 152 | string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}"; |
153 | 153 | string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}"; | |
154 | string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty | 154 | string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})"; |
155 | : $" ({Device.Application.TitleIdText.ToUpper()})"; | 155 | string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)"; |
156 | |||
157 | string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)"; | ||
158 | 156 | ||
159 | WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DefaultWidth, DefaultHeight, DefaultFlags | GetWindowFlags()); | 157 | WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DefaultWidth, DefaultHeight, DefaultFlags | GetWindowFlags()); |
160 | 158 | ||
diff --git a/Ryujinx.Ui.Common/App/ApplicationLibrary.cs b/Ryujinx.Ui.Common/App/ApplicationLibrary.cs index 43510d5ec..113e9cb3e 100644 --- a/Ryujinx.Ui.Common/App/ApplicationLibrary.cs +++ b/Ryujinx.Ui.Common/App/ApplicationLibrary.cs | |||
@@ -11,12 +11,12 @@ using LibHac.Tools.FsSystem.NcaUtils; | |||
11 | using Ryujinx.Common.Configuration; | 11 | using Ryujinx.Common.Configuration; |
12 | using Ryujinx.Common.Logging; | 12 | using Ryujinx.Common.Logging; |
13 | using Ryujinx.HLE.FileSystem; | 13 | using Ryujinx.HLE.FileSystem; |
14 | using Ryujinx.HLE.HOS; | ||
15 | using Ryujinx.HLE.HOS.SystemState; | 14 | using Ryujinx.HLE.HOS.SystemState; |
16 | using Ryujinx.HLE.Loaders.Npdm; | 15 | using Ryujinx.HLE.Loaders.Npdm; |
17 | using Ryujinx.Ui.Common.Configuration.System; | 16 | using Ryujinx.Ui.Common.Configuration.System; |
18 | using System; | 17 | using System; |
19 | using System.Collections.Generic; | 18 | using System.Collections.Generic; |
19 | using System.Globalization; | ||
20 | using System.IO; | 20 | using System.IO; |
21 | using System.Reflection; | 21 | using System.Reflection; |
22 | using System.Text; | 22 | using System.Text; |
@@ -112,9 +112,9 @@ namespace Ryujinx.Ui.App.Common | |||
112 | { | 112 | { |
113 | return; | 113 | return; |
114 | } | 114 | } |
115 | 115 | ||
116 | string extension = Path.GetExtension(app).ToLower(); | 116 | string extension = Path.GetExtension(app).ToLower(); |
117 | 117 | ||
118 | if (!File.GetAttributes(app).HasFlag(FileAttributes.Hidden) && extension is ".nsp" or ".pfs0" or ".xci" or ".nca" or ".nro" or ".nso") | 118 | if (!File.GetAttributes(app).HasFlag(FileAttributes.Hidden) && extension is ".nsp" or ".pfs0" or ".xci" or ".nca" or ".nro" or ".nso") |
119 | { | 119 | { |
120 | applications.Add(app); | 120 | applications.Add(app); |
@@ -262,10 +262,9 @@ namespace Ryujinx.Ui.App.Common | |||
262 | controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); | 262 | controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); |
263 | 263 | ||
264 | using MemoryStream stream = new(); | 264 | using MemoryStream stream = new(); |
265 | 265 | ||
266 | icon.Get.AsStream().CopyTo(stream); | 266 | icon.Get.AsStream().CopyTo(stream); |
267 | applicationIcon = stream.ToArray(); | 267 | applicationIcon = stream.ToArray(); |
268 | |||
269 | 268 | ||
270 | if (applicationIcon != null) | 269 | if (applicationIcon != null) |
271 | { | 270 | { |
@@ -400,7 +399,7 @@ namespace Ryujinx.Ui.App.Common | |||
400 | }); | 399 | }); |
401 | 400 | ||
402 | if (appMetadata.LastPlayed != "Never") | 401 | if (appMetadata.LastPlayed != "Never") |
403 | { | 402 | { |
404 | if (!DateTime.TryParse(appMetadata.LastPlayed, out _)) | 403 | if (!DateTime.TryParse(appMetadata.LastPlayed, out _)) |
405 | { | 404 | { |
406 | Logger.Warning?.Print(LogClass.Application, $"Last played datetime \"{appMetadata.LastPlayed}\" is invalid for current system culture, skipping (did current culture change?)"); | 405 | Logger.Warning?.Print(LogClass.Application, $"Last played datetime \"{appMetadata.LastPlayed}\" is invalid for current system culture, skipping (did current culture change?)"); |
@@ -470,7 +469,7 @@ namespace Ryujinx.Ui.App.Common | |||
470 | 469 | ||
471 | private void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId) | 470 | private void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId) |
472 | { | 471 | { |
473 | (_, _, Nca controlNca) = ApplicationLoader.GetGameData(_virtualFileSystem, pfs, 0); | 472 | (_, _, Nca controlNca) = GetGameData(_virtualFileSystem, pfs, 0); |
474 | 473 | ||
475 | // Return the ControlFS | 474 | // Return the ControlFS |
476 | controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None); | 475 | controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None); |
@@ -766,12 +765,12 @@ namespace Ryujinx.Ui.App.Common | |||
766 | private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs) | 765 | private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs) |
767 | { | 766 | { |
768 | updatedControlFs = null; | 767 | updatedControlFs = null; |
769 | 768 | ||
770 | string updatePath = "(unknown)"; | 769 | string updatePath = "(unknown)"; |
771 | 770 | ||
772 | try | 771 | try |
773 | { | 772 | { |
774 | (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, titleId, 0, out updatePath); | 773 | (Nca patchNca, Nca controlNca) = GetGameUpdateData(_virtualFileSystem, titleId, 0, out updatePath); |
775 | 774 | ||
776 | if (patchNca != null && controlNca != null) | 775 | if (patchNca != null && controlNca != null) |
777 | { | 776 | { |
@@ -791,5 +790,119 @@ namespace Ryujinx.Ui.App.Common | |||
791 | 790 | ||
792 | return false; | 791 | return false; |
793 | } | 792 | } |
793 | |||
794 | public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex) | ||
795 | { | ||
796 | Nca mainNca = null; | ||
797 | Nca patchNca = null; | ||
798 | Nca controlNca = null; | ||
799 | |||
800 | fileSystem.ImportTickets(pfs); | ||
801 | |||
802 | foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) | ||
803 | { | ||
804 | using var ncaFile = new UniqueRef<IFile>(); | ||
805 | |||
806 | pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); | ||
807 | |||
808 | Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage()); | ||
809 | |||
810 | int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF); | ||
811 | |||
812 | if (ncaProgramIndex != programIndex) | ||
813 | { | ||
814 | continue; | ||
815 | } | ||
816 | |||
817 | if (nca.Header.ContentType == NcaContentType.Program) | ||
818 | { | ||
819 | int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); | ||
820 | |||
821 | if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()) | ||
822 | { | ||
823 | patchNca = nca; | ||
824 | } | ||
825 | else | ||
826 | { | ||
827 | mainNca = nca; | ||
828 | } | ||
829 | } | ||
830 | else if (nca.Header.ContentType == NcaContentType.Control) | ||
831 | { | ||
832 | controlNca = nca; | ||
833 | } | ||
834 | } | ||
835 | |||
836 | return (mainNca, patchNca, controlNca); | ||
837 | } | ||
838 | |||
839 | public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex) | ||
840 | { | ||
841 | Nca patchNca = null; | ||
842 | Nca controlNca = null; | ||
843 | |||
844 | fileSystem.ImportTickets(pfs); | ||
845 | |||
846 | foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) | ||
847 | { | ||
848 | using var ncaFile = new UniqueRef<IFile>(); | ||
849 | |||
850 | pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); | ||
851 | |||
852 | Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage()); | ||
853 | |||
854 | int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF); | ||
855 | |||
856 | if (ncaProgramIndex != programIndex) | ||
857 | { | ||
858 | continue; | ||
859 | } | ||
860 | |||
861 | if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId) | ||
862 | { | ||
863 | break; | ||
864 | } | ||
865 | |||
866 | if (nca.Header.ContentType == NcaContentType.Program) | ||
867 | { | ||
868 | patchNca = nca; | ||
869 | } | ||
870 | else if (nca.Header.ContentType == NcaContentType.Control) | ||
871 | { | ||
872 | controlNca = nca; | ||
873 | } | ||
874 | } | ||
875 | |||
876 | return (patchNca, controlNca); | ||
877 | } | ||
878 | |||
879 | public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath) | ||
880 | { | ||
881 | updatePath = null; | ||
882 | |||
883 | if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase)) | ||
884 | { | ||
885 | // Clear the program index part. | ||
886 | titleIdBase &= ~0xFUL; | ||
887 | |||
888 | // Load update information if exists. | ||
889 | string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json"); | ||
890 | |||
891 | if (File.Exists(titleUpdateMetadataPath)) | ||
892 | { | ||
893 | updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected; | ||
894 | |||
895 | if (File.Exists(updatePath)) | ||
896 | { | ||
897 | FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read); | ||
898 | PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); | ||
899 | |||
900 | return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex); | ||
901 | } | ||
902 | } | ||
903 | } | ||
904 | |||
905 | return (null, null); | ||
906 | } | ||
794 | } | 907 | } |
795 | } \ No newline at end of file | 908 | } \ No newline at end of file |
diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index ace8b87f9..dca87abcb 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs | |||
@@ -252,7 +252,7 @@ namespace Ryujinx | |||
252 | 252 | ||
253 | if (CommandLineState.LaunchPathArg != null) | 253 | if (CommandLineState.LaunchPathArg != null) |
254 | { | 254 | { |
255 | mainWindow.LoadApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg); | 255 | mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg); |
256 | } | 256 | } |
257 | 257 | ||
258 | if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) | 258 | if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) |
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 6d3d4aad6..e0252016f 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs | |||
@@ -590,10 +590,10 @@ namespace Ryujinx.Ui | |||
590 | 590 | ||
591 | private void SetupProgressUiHandlers() | 591 | private void SetupProgressUiHandlers() |
592 | { | 592 | { |
593 | if (_emulationContext.Application.DiskCacheLoadState != null) | 593 | if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) |
594 | { | 594 | { |
595 | _emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler; | 595 | _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler; |
596 | _emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler; | 596 | _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler; |
597 | } | 597 | } |
598 | 598 | ||
599 | _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; | 599 | _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; |
@@ -690,156 +690,143 @@ namespace Ryujinx.Ui | |||
690 | } | 690 | } |
691 | } | 691 | } |
692 | 692 | ||
693 | public void LoadApplication(string path, bool startFullscreen = false) | 693 | private bool LoadApplication(string path, bool isFirmwareTitle) |
694 | { | 694 | { |
695 | if (_gameLoaded) | 695 | SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); |
696 | { | 696 | |
697 | GtkDialog.CreateInfoDialog("A game has already been loaded", "Please stop emulation or close the emulator before launching another game."); | 697 | if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError)) |
698 | } | ||
699 | else | ||
700 | { | 698 | { |
701 | PerformanceCheck(); | 699 | if (SetupValidator.CanFixStartApplication(_contentManager, path, userError, out firmwareVersion)) |
700 | { | ||
701 | string message = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})"; | ||
702 | 702 | ||
703 | Logger.RestartTime(); | 703 | ResponseType responseDialog = (ResponseType)GtkDialog.CreateConfirmationDialog("No Firmware Installed", message).Run(); |
704 | 704 | ||
705 | RendererWidget = CreateRendererWidget(); | 705 | if (responseDialog != ResponseType.Yes || !SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _)) |
706 | { | ||
707 | UserErrorDialog.CreateUserErrorDialog(userError); | ||
706 | 708 | ||
707 | SwitchToRenderWidget(startFullscreen); | 709 | return false; |
710 | } | ||
708 | 711 | ||
709 | InitializeSwitchInstance(); | 712 | // Tell the user that we installed a firmware for them. |
710 | 713 | ||
711 | UpdateGraphicsConfig(); | 714 | firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); |
712 | 715 | ||
713 | SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); | 716 | RefreshFirmwareLabel(); |
714 | 717 | ||
715 | bool isDirectory = Directory.Exists(path); | 718 | message = $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start."; |
716 | bool isFirmwareTitle = false; | ||
717 | 719 | ||
718 | if (path.StartsWith("@SystemContent")) | 720 | GtkDialog.CreateInfoDialog($"Firmware {firmwareVersion.VersionString} was installed", message); |
721 | } | ||
722 | else | ||
719 | { | 723 | { |
720 | path = _virtualFileSystem.SwitchPathToSystemPath(path); | 724 | UserErrorDialog.CreateUserErrorDialog(userError); |
721 | 725 | ||
722 | isFirmwareTitle = true; | 726 | return false; |
723 | } | 727 | } |
728 | } | ||
724 | 729 | ||
725 | if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError)) | 730 | Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); |
726 | { | ||
727 | if (SetupValidator.CanFixStartApplication(_contentManager, path, userError, out firmwareVersion)) | ||
728 | { | ||
729 | if (userError == UserError.NoFirmware) | ||
730 | { | ||
731 | string message = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})"; | ||
732 | 731 | ||
733 | ResponseType responseDialog = (ResponseType)GtkDialog.CreateConfirmationDialog("No Firmware Installed", message).Run(); | 732 | if (isFirmwareTitle) |
733 | { | ||
734 | Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); | ||
734 | 735 | ||
735 | if (responseDialog != ResponseType.Yes) | 736 | return _emulationContext.LoadNca(path); |
736 | { | 737 | } |
737 | UserErrorDialog.CreateUserErrorDialog(userError); | ||
738 | 738 | ||
739 | _emulationContext.Dispose(); | 739 | if (Directory.Exists(path)) |
740 | SwitchToGameTable(); | 740 | { |
741 | string[] romFsFiles = Directory.GetFiles(path, "*.istorage"); | ||
741 | 742 | ||
742 | return; | 743 | if (romFsFiles.Length == 0) |
743 | } | 744 | { |
744 | } | 745 | romFsFiles = Directory.GetFiles(path, "*.romfs"); |
746 | } | ||
745 | 747 | ||
746 | if (!SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _)) | 748 | if (romFsFiles.Length > 0) |
747 | { | 749 | { |
748 | UserErrorDialog.CreateUserErrorDialog(userError); | 750 | Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); |
751 | |||
752 | return _emulationContext.LoadCart(path, romFsFiles[0]); | ||
753 | } | ||
749 | 754 | ||
750 | _emulationContext.Dispose(); | 755 | Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); |
751 | SwitchToGameTable(); | ||
752 | 756 | ||
753 | return; | 757 | return _emulationContext.LoadCart(path); |
754 | } | 758 | } |
755 | 759 | ||
756 | // Tell the user that we installed a firmware for them. | 760 | if (File.Exists(path)) |
757 | if (userError == UserError.NoFirmware) | 761 | { |
762 | switch (System.IO.Path.GetExtension(path).ToLowerInvariant()) | ||
763 | { | ||
764 | case ".xci": | ||
765 | Logger.Info?.Print(LogClass.Application, "Loading as XCI."); | ||
766 | |||
767 | return _emulationContext.LoadXci(path); | ||
768 | case ".nca": | ||
769 | Logger.Info?.Print(LogClass.Application, "Loading as NCA."); | ||
770 | |||
771 | return _emulationContext.LoadNca(path); | ||
772 | case ".nsp": | ||
773 | case ".pfs0": | ||
774 | Logger.Info?.Print(LogClass.Application, "Loading as NSP."); | ||
775 | |||
776 | return _emulationContext.LoadNsp(path); | ||
777 | default: | ||
778 | Logger.Info?.Print(LogClass.Application, "Loading as Homebrew."); | ||
779 | try | ||
780 | { | ||
781 | return _emulationContext.LoadProgram(path); | ||
782 | } | ||
783 | catch (ArgumentOutOfRangeException) | ||
758 | { | 784 | { |
759 | firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); | 785 | Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); |
760 | 786 | ||
761 | RefreshFirmwareLabel(); | 787 | return false; |
788 | } | ||
789 | } | ||
790 | } | ||
762 | 791 | ||
763 | string message = $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start."; | 792 | Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); |
764 | 793 | ||
765 | GtkDialog.CreateInfoDialog($"Firmware {firmwareVersion.VersionString} was installed", message); | 794 | return false; |
766 | } | 795 | } |
767 | } | ||
768 | else | ||
769 | { | ||
770 | UserErrorDialog.CreateUserErrorDialog(userError); | ||
771 | 796 | ||
772 | _emulationContext.Dispose(); | 797 | public void RunApplication(string path, bool startFullscreen = false) |
773 | SwitchToGameTable(); | 798 | { |
799 | if (_gameLoaded) | ||
800 | { | ||
801 | GtkDialog.CreateInfoDialog("A game has already been loaded", "Please stop emulation or close the emulator before launching another game."); | ||
802 | } | ||
803 | else | ||
804 | { | ||
805 | PerformanceCheck(); | ||
774 | 806 | ||
775 | return; | 807 | Logger.RestartTime(); |
776 | } | ||
777 | } | ||
778 | 808 | ||
779 | Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); | 809 | RendererWidget = CreateRendererWidget(); |
780 | 810 | ||
781 | if (isFirmwareTitle) | 811 | SwitchToRenderWidget(startFullscreen); |
782 | { | ||
783 | Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); | ||
784 | 812 | ||
785 | _emulationContext.LoadNca(path); | 813 | InitializeSwitchInstance(); |
786 | } | ||
787 | else if (Directory.Exists(path)) | ||
788 | { | ||
789 | string[] romFsFiles = Directory.GetFiles(path, "*.istorage"); | ||
790 | 814 | ||
791 | if (romFsFiles.Length == 0) | 815 | UpdateGraphicsConfig(); |
792 | { | ||
793 | romFsFiles = Directory.GetFiles(path, "*.romfs"); | ||
794 | } | ||
795 | 816 | ||
796 | if (romFsFiles.Length > 0) | 817 | bool isFirmwareTitle = false; |
797 | { | 818 | |
798 | Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); | 819 | if (path.StartsWith("@SystemContent")) |
799 | _emulationContext.LoadCart(path, romFsFiles[0]); | ||
800 | } | ||
801 | else | ||
802 | { | ||
803 | Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); | ||
804 | _emulationContext.LoadCart(path); | ||
805 | } | ||
806 | } | ||
807 | else if (File.Exists(path)) | ||
808 | { | 820 | { |
809 | switch (System.IO.Path.GetExtension(path).ToLowerInvariant()) | 821 | path = _virtualFileSystem.SwitchPathToSystemPath(path); |
810 | { | 822 | |
811 | case ".xci": | 823 | isFirmwareTitle = true; |
812 | Logger.Info?.Print(LogClass.Application, "Loading as XCI."); | ||
813 | _emulationContext.LoadXci(path); | ||
814 | break; | ||
815 | case ".nca": | ||
816 | Logger.Info?.Print(LogClass.Application, "Loading as NCA."); | ||
817 | _emulationContext.LoadNca(path); | ||
818 | break; | ||
819 | case ".nsp": | ||
820 | case ".pfs0": | ||
821 | Logger.Info?.Print(LogClass.Application, "Loading as NSP."); | ||
822 | _emulationContext.LoadNsp(path); | ||
823 | break; | ||
824 | default: | ||
825 | Logger.Info?.Print(LogClass.Application, "Loading as Homebrew."); | ||
826 | try | ||
827 | { | ||
828 | _emulationContext.LoadProgram(path); | ||
829 | } | ||
830 | catch (ArgumentOutOfRangeException) | ||
831 | { | ||
832 | Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); | ||
833 | } | ||
834 | break; | ||
835 | } | ||
836 | } | 824 | } |
837 | else | ||
838 | { | ||
839 | Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); | ||
840 | 825 | ||
826 | if (!LoadApplication(path, isFirmwareTitle)) | ||
827 | { | ||
841 | _emulationContext.Dispose(); | 828 | _emulationContext.Dispose(); |
842 | RendererWidget.Dispose(); | 829 | SwitchToGameTable(); |
843 | 830 | ||
844 | return; | 831 | return; |
845 | } | 832 | } |
@@ -852,10 +839,7 @@ namespace Ryujinx.Ui | |||
852 | 839 | ||
853 | Translator.IsReadyForTranslation.Reset(); | 840 | Translator.IsReadyForTranslation.Reset(); |
854 | 841 | ||
855 | Thread windowThread = new Thread(() => | 842 | Thread windowThread = new(CreateGameWindow) |
856 | { | ||
857 | CreateGameWindow(); | ||
858 | }) | ||
859 | { | 843 | { |
860 | Name = "GUI.WindowThread" | 844 | Name = "GUI.WindowThread" |
861 | }; | 845 | }; |
@@ -871,9 +855,10 @@ namespace Ryujinx.Ui | |||
871 | _firmwareInstallFile.Sensitive = false; | 855 | _firmwareInstallFile.Sensitive = false; |
872 | _firmwareInstallDirectory.Sensitive = false; | 856 | _firmwareInstallDirectory.Sensitive = false; |
873 | 857 | ||
874 | DiscordIntegrationModule.SwitchToPlayingState(_emulationContext.Application.TitleIdText, _emulationContext.Application.TitleName); | 858 | DiscordIntegrationModule.SwitchToPlayingState(_emulationContext.Processes.ActiveApplication.ProgramIdText, |
859 | _emulationContext.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString()); | ||
875 | 860 | ||
876 | _applicationLibrary.LoadAndSaveMetaData(_emulationContext.Application.TitleIdText, appMetadata => | 861 | _applicationLibrary.LoadAndSaveMetaData(_emulationContext.Processes.ActiveApplication.ProgramIdText, appMetadata => |
877 | { | 862 | { |
878 | appMetadata.LastPlayed = DateTime.UtcNow.ToString(); | 863 | appMetadata.LastPlayed = DateTime.UtcNow.ToString(); |
879 | }); | 864 | }); |
@@ -1055,7 +1040,7 @@ namespace Ryujinx.Ui | |||
1055 | 1040 | ||
1056 | if (_emulationContext != null) | 1041 | if (_emulationContext != null) |
1057 | { | 1042 | { |
1058 | UpdateGameMetadata(_emulationContext.Application.TitleIdText); | 1043 | UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText); |
1059 | 1044 | ||
1060 | if (RendererWidget != null) | 1045 | if (RendererWidget != null) |
1061 | { | 1046 | { |
@@ -1174,7 +1159,7 @@ namespace Ryujinx.Ui | |||
1174 | 1159 | ||
1175 | string path = (string)_tableStore.GetValue(treeIter, 9); | 1160 | string path = (string)_tableStore.GetValue(treeIter, 9); |
1176 | 1161 | ||
1177 | LoadApplication(path); | 1162 | RunApplication(path); |
1178 | } | 1163 | } |
1179 | 1164 | ||
1180 | private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args) | 1165 | private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args) |
@@ -1260,7 +1245,7 @@ namespace Ryujinx.Ui | |||
1260 | 1245 | ||
1261 | if (fileChooser.Run() == (int)ResponseType.Accept) | 1246 | if (fileChooser.Run() == (int)ResponseType.Accept) |
1262 | { | 1247 | { |
1263 | LoadApplication(fileChooser.Filename); | 1248 | RunApplication(fileChooser.Filename); |
1264 | } | 1249 | } |
1265 | } | 1250 | } |
1266 | } | 1251 | } |
@@ -1271,7 +1256,7 @@ namespace Ryujinx.Ui | |||
1271 | { | 1256 | { |
1272 | if (fileChooser.Run() == (int)ResponseType.Accept) | 1257 | if (fileChooser.Run() == (int)ResponseType.Accept) |
1273 | { | 1258 | { |
1274 | LoadApplication(fileChooser.Filename); | 1259 | RunApplication(fileChooser.Filename); |
1275 | } | 1260 | } |
1276 | } | 1261 | } |
1277 | } | 1262 | } |
@@ -1287,7 +1272,7 @@ namespace Ryujinx.Ui | |||
1287 | { | 1272 | { |
1288 | string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); | 1273 | string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); |
1289 | 1274 | ||
1290 | LoadApplication(contentPath); | 1275 | RunApplication(contentPath); |
1291 | } | 1276 | } |
1292 | 1277 | ||
1293 | private void Open_Ryu_Folder(object sender, EventArgs args) | 1278 | private void Open_Ryu_Folder(object sender, EventArgs args) |
@@ -1328,7 +1313,7 @@ namespace Ryujinx.Ui | |||
1328 | { | 1313 | { |
1329 | if (_emulationContext != null) | 1314 | if (_emulationContext != null) |
1330 | { | 1315 | { |
1331 | UpdateGameMetadata(_emulationContext.Application.TitleIdText); | 1316 | UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText); |
1332 | } | 1317 | } |
1333 | 1318 | ||
1334 | _pauseEmulation.Sensitive = false; | 1319 | _pauseEmulation.Sensitive = false; |
@@ -1533,7 +1518,7 @@ namespace Ryujinx.Ui | |||
1533 | { | 1518 | { |
1534 | _userChannelPersistence.ShouldRestart = false; | 1519 | _userChannelPersistence.ShouldRestart = false; |
1535 | 1520 | ||
1536 | LoadApplication(_currentEmulatedGamePath); | 1521 | RunApplication(_currentEmulatedGamePath); |
1537 | } | 1522 | } |
1538 | else | 1523 | else |
1539 | { | 1524 | { |
@@ -1596,7 +1581,9 @@ namespace Ryujinx.Ui | |||
1596 | 1581 | ||
1597 | private void ManageCheats_Pressed(object sender, EventArgs args) | 1582 | private void ManageCheats_Pressed(object sender, EventArgs args) |
1598 | { | 1583 | { |
1599 | var window = new CheatWindow(_virtualFileSystem, _emulationContext.Application.TitleId, _emulationContext.Application.TitleName); | 1584 | var window = new CheatWindow(_virtualFileSystem, |
1585 | _emulationContext.Processes.ActiveApplication.ProgramId, | ||
1586 | _emulationContext.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString()); | ||
1600 | 1587 | ||
1601 | window.Destroyed += CheatWindow_Destroyed; | 1588 | window.Destroyed += CheatWindow_Destroyed; |
1602 | window.Show(); | 1589 | window.Show(); |
@@ -1639,7 +1626,7 @@ namespace Ryujinx.Ui | |||
1639 | LastScannedAmiiboShowAll = _lastScannedAmiiboShowAll, | 1626 | LastScannedAmiiboShowAll = _lastScannedAmiiboShowAll, |
1640 | LastScannedAmiiboId = _lastScannedAmiiboId, | 1627 | LastScannedAmiiboId = _lastScannedAmiiboId, |
1641 | DeviceId = deviceId, | 1628 | DeviceId = deviceId, |
1642 | TitleId = _emulationContext.Application.TitleIdText.ToUpper() | 1629 | TitleId = _emulationContext.Processes.ActiveApplication.ProgramIdText.ToUpper() |
1643 | }; | 1630 | }; |
1644 | 1631 | ||
1645 | amiiboWindow.DeleteEvent += AmiiboWindow_DeleteEvent; | 1632 | amiiboWindow.DeleteEvent += AmiiboWindow_DeleteEvent; |
diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs index e5d22d65c..7cb5b3275 100644 --- a/Ryujinx/Ui/RendererWidgetBase.cs +++ b/Ryujinx/Ui/RendererWidgetBase.cs | |||
@@ -495,16 +495,14 @@ namespace Ryujinx.Ui | |||
495 | { | 495 | { |
496 | parent.Present(); | 496 | parent.Present(); |
497 | 497 | ||
498 | string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty | 498 | var activeProcess = Device.Processes.ActiveApplication; |
499 | : $" - {Device.Application.TitleName}"; | 499 | var nacp = activeProcess.ApplicationControlProperties; |
500 | int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage; | ||
500 | 501 | ||
501 | string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty | 502 | string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}"; |
502 | : $" v{Device.Application.DisplayVersion}"; | 503 | string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}"; |
503 | 504 | string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})"; | |
504 | string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty | 505 | string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)"; |
505 | : $" ({Device.Application.TitleIdText.ToUpper()})"; | ||
506 | |||
507 | string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)"; | ||
508 | 506 | ||
509 | parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}"; | 507 | parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}"; |
510 | }); | 508 | }); |
@@ -612,7 +610,7 @@ namespace Ryujinx.Ui | |||
612 | { | 610 | { |
613 | if (!ParentWindow.State.HasFlag(WindowState.Fullscreen)) | 611 | if (!ParentWindow.State.HasFlag(WindowState.Fullscreen)) |
614 | { | 612 | { |
615 | Device.Application.DiskCacheLoadState?.Cancel(); | 613 | Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel(); |
616 | } | 614 | } |
617 | } | 615 | } |
618 | }); | 616 | }); |
diff --git a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs index a63d68ff2..558288aab 100644 --- a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs +++ b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs | |||
@@ -15,6 +15,7 @@ using Ryujinx.Common.Logging; | |||
15 | using Ryujinx.HLE.FileSystem; | 15 | using Ryujinx.HLE.FileSystem; |
16 | using Ryujinx.HLE.HOS; | 16 | using Ryujinx.HLE.HOS; |
17 | using Ryujinx.HLE.HOS.Services.Account.Acc; | 17 | using Ryujinx.HLE.HOS.Services.Account.Acc; |
18 | using Ryujinx.Ui.App.Common; | ||
18 | using Ryujinx.Ui.Common.Configuration; | 19 | using Ryujinx.Ui.Common.Configuration; |
19 | using Ryujinx.Ui.Common.Helper; | 20 | using Ryujinx.Ui.Common.Helper; |
20 | using Ryujinx.Ui.Windows; | 21 | using Ryujinx.Ui.Windows; |
@@ -260,7 +261,7 @@ namespace Ryujinx.Ui.Widgets | |||
260 | return; | 261 | return; |
261 | } | 262 | } |
262 | 263 | ||
263 | (Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); | 264 | (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); |
264 | 265 | ||
265 | if (updatePatchNca != null) | 266 | if (updatePatchNca != null) |
266 | { | 267 | { |
diff --git a/Ryujinx/Ui/Windows/TitleUpdateWindow.cs b/Ryujinx/Ui/Windows/TitleUpdateWindow.cs index 4aea58955..fce751da1 100644 --- a/Ryujinx/Ui/Windows/TitleUpdateWindow.cs +++ b/Ryujinx/Ui/Windows/TitleUpdateWindow.cs | |||
@@ -9,6 +9,7 @@ using LibHac.Tools.FsSystem.NcaUtils; | |||
9 | using Ryujinx.Common.Configuration; | 9 | using Ryujinx.Common.Configuration; |
10 | using Ryujinx.HLE.FileSystem; | 10 | using Ryujinx.HLE.FileSystem; |
11 | using Ryujinx.HLE.HOS; | 11 | using Ryujinx.HLE.HOS; |
12 | using Ryujinx.Ui.App.Common; | ||
12 | using Ryujinx.Ui.Widgets; | 13 | using Ryujinx.Ui.Widgets; |
13 | using System; | 14 | using System; |
14 | using System.Collections.Generic; | 15 | using System.Collections.Generic; |
@@ -94,7 +95,7 @@ namespace Ryujinx.Ui.Windows | |||
94 | 95 | ||
95 | try | 96 | try |
96 | { | 97 | { |
97 | (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0); | 98 | (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0); |
98 | 99 | ||
99 | if (controlNca != null && patchNca != null) | 100 | if (controlNca != null && patchNca != null) |
100 | { | 101 | { |