summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md6
m---------externals/dynarmic0
-rw-r--r--src/common/logging/backend.cpp1
-rw-r--r--src/common/logging/log.h1
-rw-r--r--src/core/CMakeLists.txt7
-rw-r--r--src/core/core.cpp74
-rw-r--r--src/core/core.h18
-rw-r--r--src/core/crypto/partition_data_manager.cpp4
-rw-r--r--src/core/crypto/partition_data_manager.h1
-rw-r--r--src/core/file_sys/bis_factory.cpp101
-rw-r--r--src/core/file_sys/bis_factory.h38
-rw-r--r--src/core/file_sys/card_image.cpp24
-rw-r--r--src/core/file_sys/card_image.h9
-rw-r--r--src/core/file_sys/cheat_engine.cpp492
-rw-r--r--src/core/file_sys/cheat_engine.h234
-rw-r--r--src/core/file_sys/content_archive.cpp8
-rw-r--r--src/core/file_sys/content_archive.h2
-rw-r--r--src/core/file_sys/patch_manager.cpp69
-rw-r--r--src/core/file_sys/patch_manager.h6
-rw-r--r--src/core/file_sys/registered_cache.cpp188
-rw-r--r--src/core/file_sys/registered_cache.h25
-rw-r--r--src/core/file_sys/romfs_factory.cpp16
-rw-r--r--src/core/file_sys/romfs_factory.h4
-rw-r--r--src/core/file_sys/savedata_factory.cpp68
-rw-r--r--src/core/file_sys/savedata_factory.h6
-rw-r--r--src/core/file_sys/sdmc_factory.cpp27
-rw-r--r--src/core/file_sys/sdmc_factory.h13
-rw-r--r--src/core/file_sys/submission_package.cpp29
-rw-r--r--src/core/hle/service/am/am.cpp24
-rw-r--r--src/core/hle/service/am/applet_ae.h4
-rw-r--r--src/core/hle/service/am/applet_oe.h4
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp329
-rw-r--r--src/core/hle/service/filesystem/filesystem.h116
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp80
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h4
-rw-r--r--src/core/hle/service/ns/ns.cpp4
-rw-r--r--src/core/hle/service/ns/ns.h14
-rw-r--r--src/core/hle/service/ns/pl_u.cpp5
-rw-r--r--src/core/hle/service/ns/pl_u.h14
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp8
-rw-r--r--src/core/hle/service/prepo/prepo.cpp105
-rw-r--r--src/core/hle/service/prepo/prepo.h2
-rw-r--r--src/core/hle/service/service.cpp5
-rw-r--r--src/core/hle/service/service.h8
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp4
-rw-r--r--src/core/loader/nca.cpp4
-rw-r--r--src/core/loader/nro.cpp4
-rw-r--r--src/core/loader/nso.cpp3
-rw-r--r--src/core/loader/nsp.cpp31
-rw-r--r--src/core/loader/xci.cpp4
-rw-r--r--src/core/memory.cpp9
-rw-r--r--src/core/memory/cheat_engine.cpp234
-rw-r--r--src/core/memory/cheat_engine.h86
-rw-r--r--src/core/memory/dmnt_cheat_types.h58
-rw-r--r--src/core/memory/dmnt_cheat_vm.cpp1212
-rw-r--r--src/core/memory/dmnt_cheat_vm.h321
-rw-r--r--src/core/perf_stats.cpp47
-rw-r--r--src/core/perf_stats.h21
-rw-r--r--src/core/reporter.cpp10
-rw-r--r--src/core/reporter.h10
-rw-r--r--src/core/settings.cpp5
-rw-r--r--src/core/settings.h35
-rw-r--r--src/video_core/engines/maxwell_3d.cpp2
-rw-r--r--src/video_core/engines/shader_bytecode.h31
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp3
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp110
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp50
-rw-r--r--src/video_core/shader/decode/arithmetic_integer.cpp29
-rw-r--r--src/video_core/shader/decode/memory.cpp48
-rw-r--r--src/video_core/shader/decode/warp.cpp47
-rw-r--r--src/video_core/shader/node.h26
-rw-r--r--src/video_core/shader/shader_ir.cpp9
-rw-r--r--src/video_core/shader/shader_ir.h6
-rw-r--r--src/yuzu/CMakeLists.txt3
-rw-r--r--src/yuzu/configuration/config.cpp75
-rw-r--r--src/yuzu/configuration/configure.ui11
-rw-r--r--src/yuzu/configuration/configure_debug.cpp4
-rw-r--r--src/yuzu/configuration/configure_debug.ui80
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp4
-rw-r--r--src/yuzu/configuration/configure_filesystem.cpp177
-rw-r--r--src/yuzu/configuration/configure_filesystem.h43
-rw-r--r--src/yuzu/configuration/configure_filesystem.ui395
-rw-r--r--src/yuzu/configuration/configure_general.cpp1
-rw-r--r--src/yuzu/configuration/configure_input.cpp2
-rw-r--r--src/yuzu/main.cpp55
-rw-r--r--src/yuzu_cmd/config.cpp25
-rw-r--r--src/yuzu_cmd/default_ini.h16
-rw-r--r--src/yuzu_cmd/yuzu.cpp2
-rw-r--r--src/yuzu_tester/yuzu.cpp4
90 files changed, 4426 insertions, 1131 deletions
diff --git a/README.md b/README.md
index 430c6dd65..ecace43f2 100644
--- a/README.md
+++ b/README.md
@@ -6,9 +6,11 @@ yuzu emulator
6 6
7yuzu is an experimental open-source emulator for the Nintendo Switch from the creators of [Citra](https://citra-emu.org/). 7yuzu is an experimental open-source emulator for the Nintendo Switch from the creators of [Citra](https://citra-emu.org/).
8 8
9It is written in C++ with portability in mind, with builds actively maintained for Windows, Linux and macOS. The emulator is currently only useful for homebrew development and research purposes. 9It is written in C++ with portability in mind, with builds actively maintained for Windows and Linux. The emulator is capable of running several commercial games.
10 10
11yuzu only emulates a subset of Switch hardware and therefore is generally only useful for running/debugging homebrew applications. yuzu can boot some games, to varying degrees of success. 11yuzu only emulates a subset of Switch hardware and therefore most commercial games **do not** run at full speed or are not fully functional.
12
13Do you want to check which games are compatible and which ones are not? Please visit our [Compatibility page](https://yuzu-emu.org/game/)!
12 14
13yuzu is licensed under the GPLv2 (or any later version). Refer to the license.txt file included. 15yuzu is licensed under the GPLv2 (or any later version). Refer to the license.txt file included.
14 16
diff --git a/externals/dynarmic b/externals/dynarmic
Subproject 2683a9a3e316b5c3f387bbe6787732b9ff44b8d Subproject 087a74417abfb0a8ae3bc1463d0d476a9bf94e5
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index a03179520..1111cfbad 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -255,6 +255,7 @@ void DebuggerBackend::Write(const Entry& entry) {
255 CLS(Input) \ 255 CLS(Input) \
256 CLS(Network) \ 256 CLS(Network) \
257 CLS(Loader) \ 257 CLS(Loader) \
258 CLS(CheatEngine) \
258 CLS(Crypto) \ 259 CLS(Crypto) \
259 CLS(WebService) 260 CLS(WebService)
260 261
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 8ed6d5050..259708116 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -117,6 +117,7 @@ enum class Class : ClassType {
117 Audio_DSP, ///< The HLE implementation of the DSP 117 Audio_DSP, ///< The HLE implementation of the DSP
118 Audio_Sink, ///< Emulator audio output backend 118 Audio_Sink, ///< Emulator audio output backend
119 Loader, ///< ROM loader 119 Loader, ///< ROM loader
120 CheatEngine, ///< Memory manipulation and engine VM functions
120 Crypto, ///< Cryptographic engine/functions 121 Crypto, ///< Cryptographic engine/functions
121 Input, ///< Input emulation 122 Input, ///< Input emulation
122 Network, ///< Network emulation 123 Network, ///< Network emulation
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 877a9e353..a6b56c9c6 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -33,8 +33,6 @@ add_library(core STATIC
33 file_sys/bis_factory.h 33 file_sys/bis_factory.h
34 file_sys/card_image.cpp 34 file_sys/card_image.cpp
35 file_sys/card_image.h 35 file_sys/card_image.h
36 file_sys/cheat_engine.cpp
37 file_sys/cheat_engine.h
38 file_sys/content_archive.cpp 36 file_sys/content_archive.cpp
39 file_sys/content_archive.h 37 file_sys/content_archive.h
40 file_sys/control_metadata.cpp 38 file_sys/control_metadata.cpp
@@ -477,6 +475,11 @@ add_library(core STATIC
477 loader/nsp.h 475 loader/nsp.h
478 loader/xci.cpp 476 loader/xci.cpp
479 loader/xci.h 477 loader/xci.h
478 memory/cheat_engine.cpp
479 memory/cheat_engine.h
480 memory/dmnt_cheat_types.h
481 memory/dmnt_cheat_vm.cpp
482 memory/dmnt_cheat_vm.h
480 memory.cpp 483 memory.cpp
481 memory.h 484 memory.h
482 memory_setup.h 485 memory_setup.h
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 3d0978cbf..76bb2bae9 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -14,8 +14,14 @@
14#include "core/core_cpu.h" 14#include "core/core_cpu.h"
15#include "core/core_timing.h" 15#include "core/core_timing.h"
16#include "core/cpu_core_manager.h" 16#include "core/cpu_core_manager.h"
17#include "core/file_sys/bis_factory.h"
18#include "core/file_sys/card_image.h"
17#include "core/file_sys/mode.h" 19#include "core/file_sys/mode.h"
20#include "core/file_sys/patch_manager.h"
18#include "core/file_sys/registered_cache.h" 21#include "core/file_sys/registered_cache.h"
22#include "core/file_sys/romfs_factory.h"
23#include "core/file_sys/savedata_factory.h"
24#include "core/file_sys/sdmc_factory.h"
19#include "core/file_sys/vfs_concat.h" 25#include "core/file_sys/vfs_concat.h"
20#include "core/file_sys/vfs_real.h" 26#include "core/file_sys/vfs_real.h"
21#include "core/gdbstub/gdbstub.h" 27#include "core/gdbstub/gdbstub.h"
@@ -27,17 +33,17 @@
27#include "core/hle/kernel/thread.h" 33#include "core/hle/kernel/thread.h"
28#include "core/hle/service/am/applets/applets.h" 34#include "core/hle/service/am/applets/applets.h"
29#include "core/hle/service/apm/controller.h" 35#include "core/hle/service/apm/controller.h"
36#include "core/hle/service/filesystem/filesystem.h"
30#include "core/hle/service/glue/manager.h" 37#include "core/hle/service/glue/manager.h"
31#include "core/hle/service/service.h" 38#include "core/hle/service/service.h"
32#include "core/hle/service/sm/sm.h" 39#include "core/hle/service/sm/sm.h"
33#include "core/loader/loader.h" 40#include "core/loader/loader.h"
41#include "core/memory/cheat_engine.h"
34#include "core/perf_stats.h" 42#include "core/perf_stats.h"
35#include "core/reporter.h" 43#include "core/reporter.h"
36#include "core/settings.h" 44#include "core/settings.h"
37#include "core/telemetry_session.h" 45#include "core/telemetry_session.h"
38#include "core/tools/freezer.h" 46#include "core/tools/freezer.h"
39#include "file_sys/cheat_engine.h"
40#include "file_sys/patch_manager.h"
41#include "video_core/debug_utils/debug_utils.h" 47#include "video_core/debug_utils/debug_utils.h"
42#include "video_core/renderer_base.h" 48#include "video_core/renderer_base.h"
43#include "video_core/video_core.h" 49#include "video_core/video_core.h"
@@ -160,10 +166,6 @@ struct System::Impl {
160 166
161 LOG_DEBUG(Core, "Initialized OK"); 167 LOG_DEBUG(Core, "Initialized OK");
162 168
163 // Reset counters and set time origin to current frame
164 GetAndResetPerfStats();
165 perf_stats.BeginSystemFrame();
166
167 return ResultStatus::Success; 169 return ResultStatus::Success;
168 } 170 }
169 171
@@ -202,10 +204,34 @@ struct System::Impl {
202 gpu_core->Start(); 204 gpu_core->Start();
203 cpu_core_manager.StartThreads(); 205 cpu_core_manager.StartThreads();
204 206
207 // Initialize cheat engine
208 if (cheat_engine) {
209 cheat_engine->Initialize();
210 }
211
205 // All threads are started, begin main process execution, now that we're in the clear. 212 // All threads are started, begin main process execution, now that we're in the clear.
206 main_process->Run(load_parameters->main_thread_priority, 213 main_process->Run(load_parameters->main_thread_priority,
207 load_parameters->main_thread_stack_size); 214 load_parameters->main_thread_stack_size);
208 215
216 if (Settings::values.gamecard_inserted) {
217 if (Settings::values.gamecard_current_game) {
218 fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, filepath));
219 } else if (!Settings::values.gamecard_path.empty()) {
220 fs_controller.SetGameCard(
221 GetGameFileFromPath(virtual_filesystem, Settings::values.gamecard_path));
222 }
223 }
224
225 u64 title_id{0};
226 if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
227 LOG_ERROR(Core, "Failed to find title id for ROM (Error {})",
228 static_cast<u32>(load_result));
229 }
230 perf_stats = std::make_unique<PerfStats>(title_id);
231 // Reset counters and set time origin to current frame
232 GetAndResetPerfStats();
233 perf_stats->BeginSystemFrame();
234
209 status = ResultStatus::Success; 235 status = ResultStatus::Success;
210 return status; 236 return status;
211 } 237 }
@@ -219,6 +245,8 @@ struct System::Impl {
219 perf_results.game_fps); 245 perf_results.game_fps);
220 telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime", 246 telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
221 perf_results.frametime * 1000.0); 247 perf_results.frametime * 1000.0);
248 telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS",
249 perf_stats->GetMeanFrametime());
222 250
223 is_powered_on = false; 251 is_powered_on = false;
224 252
@@ -229,6 +257,7 @@ struct System::Impl {
229 service_manager.reset(); 257 service_manager.reset();
230 cheat_engine.reset(); 258 cheat_engine.reset();
231 telemetry_session.reset(); 259 telemetry_session.reset();
260 perf_stats.reset();
232 gpu_core.reset(); 261 gpu_core.reset();
233 262
234 // Close all CPU/threading state 263 // Close all CPU/threading state
@@ -286,7 +315,7 @@ struct System::Impl {
286 } 315 }
287 316
288 PerfStatsResults GetAndResetPerfStats() { 317 PerfStatsResults GetAndResetPerfStats() {
289 return perf_stats.GetAndResetStats(core_timing.GetGlobalTimeUs()); 318 return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs());
290 } 319 }
291 320
292 Timing::CoreTiming core_timing; 321 Timing::CoreTiming core_timing;
@@ -295,6 +324,7 @@ struct System::Impl {
295 FileSys::VirtualFilesystem virtual_filesystem; 324 FileSys::VirtualFilesystem virtual_filesystem;
296 /// ContentProviderUnion instance 325 /// ContentProviderUnion instance
297 std::unique_ptr<FileSys::ContentProviderUnion> content_provider; 326 std::unique_ptr<FileSys::ContentProviderUnion> content_provider;
327 Service::FileSystem::FileSystemController fs_controller;
298 /// AppLoader used to load the current executing application 328 /// AppLoader used to load the current executing application
299 std::unique_ptr<Loader::AppLoader> app_loader; 329 std::unique_ptr<Loader::AppLoader> app_loader;
300 std::unique_ptr<VideoCore::RendererBase> renderer; 330 std::unique_ptr<VideoCore::RendererBase> renderer;
@@ -304,7 +334,7 @@ struct System::Impl {
304 CpuCoreManager cpu_core_manager; 334 CpuCoreManager cpu_core_manager;
305 bool is_powered_on = false; 335 bool is_powered_on = false;
306 336
307 std::unique_ptr<FileSys::CheatEngine> cheat_engine; 337 std::unique_ptr<Memory::CheatEngine> cheat_engine;
308 std::unique_ptr<Tools::Freezer> memory_freezer; 338 std::unique_ptr<Tools::Freezer> memory_freezer;
309 339
310 /// Frontend applets 340 /// Frontend applets
@@ -327,7 +357,7 @@ struct System::Impl {
327 ResultStatus status = ResultStatus::Success; 357 ResultStatus status = ResultStatus::Success;
328 std::string status_details = ""; 358 std::string status_details = "";
329 359
330 Core::PerfStats perf_stats; 360 std::unique_ptr<Core::PerfStats> perf_stats;
331 Core::FrameLimiter frame_limiter; 361 Core::FrameLimiter frame_limiter;
332}; 362};
333 363
@@ -480,11 +510,11 @@ const Timing::CoreTiming& System::CoreTiming() const {
480} 510}
481 511
482Core::PerfStats& System::GetPerfStats() { 512Core::PerfStats& System::GetPerfStats() {
483 return impl->perf_stats; 513 return *impl->perf_stats;
484} 514}
485 515
486const Core::PerfStats& System::GetPerfStats() const { 516const Core::PerfStats& System::GetPerfStats() const {
487 return impl->perf_stats; 517 return *impl->perf_stats;
488} 518}
489 519
490Core::FrameLimiter& System::FrameLimiter() { 520Core::FrameLimiter& System::FrameLimiter() {
@@ -519,13 +549,6 @@ Tegra::DebugContext* System::GetGPUDebugContext() const {
519 return impl->debug_context.get(); 549 return impl->debug_context.get();
520} 550}
521 551
522void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list,
523 const std::string& build_id, VAddr code_region_start,
524 VAddr code_region_end) {
525 impl->cheat_engine = std::make_unique<FileSys::CheatEngine>(*this, list, build_id,
526 code_region_start, code_region_end);
527}
528
529void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { 552void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
530 impl->virtual_filesystem = std::move(vfs); 553 impl->virtual_filesystem = std::move(vfs);
531} 554}
@@ -534,6 +557,13 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
534 return impl->virtual_filesystem; 557 return impl->virtual_filesystem;
535} 558}
536 559
560void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
561 const std::array<u8, 32>& build_id, VAddr main_region_begin,
562 u64 main_region_size) {
563 impl->cheat_engine = std::make_unique<Memory::CheatEngine>(*this, list, build_id);
564 impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
565}
566
537void System::SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set) { 567void System::SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set) {
538 impl->applet_manager.SetAppletFrontendSet(std::move(set)); 568 impl->applet_manager.SetAppletFrontendSet(std::move(set));
539} 569}
@@ -562,6 +592,14 @@ const FileSys::ContentProvider& System::GetContentProvider() const {
562 return *impl->content_provider; 592 return *impl->content_provider;
563} 593}
564 594
595Service::FileSystem::FileSystemController& System::GetFileSystemController() {
596 return impl->fs_controller;
597}
598
599const Service::FileSystem::FileSystemController& System::GetFileSystemController() const {
600 return impl->fs_controller;
601}
602
565void System::RegisterContentProvider(FileSys::ContentProviderUnionSlot slot, 603void System::RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
566 FileSys::ContentProvider* provider) { 604 FileSys::ContentProvider* provider) {
567 impl->content_provider->SetSlot(slot, provider); 605 impl->content_provider->SetSlot(slot, provider);
diff --git a/src/core/core.h b/src/core/core.h
index 0138d93b0..d2a3c82d8 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -18,7 +18,6 @@ class EmuWindow;
18} // namespace Core::Frontend 18} // namespace Core::Frontend
19 19
20namespace FileSys { 20namespace FileSys {
21class CheatList;
22class ContentProvider; 21class ContentProvider;
23class ContentProviderUnion; 22class ContentProviderUnion;
24enum class ContentProviderUnionSlot; 23enum class ContentProviderUnionSlot;
@@ -36,6 +35,10 @@ class AppLoader;
36enum class ResultStatus : u16; 35enum class ResultStatus : u16;
37} // namespace Loader 36} // namespace Loader
38 37
38namespace Memory {
39struct CheatEntry;
40} // namespace Memory
41
39namespace Service { 42namespace Service {
40 43
41namespace AM::Applets { 44namespace AM::Applets {
@@ -47,6 +50,10 @@ namespace APM {
47class Controller; 50class Controller;
48} 51}
49 52
53namespace FileSystem {
54class FileSystemController;
55} // namespace FileSystem
56
50namespace Glue { 57namespace Glue {
51class ARPManager; 58class ARPManager;
52} 59}
@@ -282,8 +289,9 @@ public:
282 289
283 std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; 290 std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
284 291
285 void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id, 292 void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
286 VAddr code_region_start, VAddr code_region_end); 293 const std::array<u8, 0x20>& build_id, VAddr main_region_begin,
294 u64 main_region_size);
287 295
288 void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set); 296 void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set);
289 297
@@ -299,6 +307,10 @@ public:
299 307
300 const FileSys::ContentProvider& GetContentProvider() const; 308 const FileSys::ContentProvider& GetContentProvider() const;
301 309
310 Service::FileSystem::FileSystemController& GetFileSystemController();
311
312 const Service::FileSystem::FileSystemController& GetFileSystemController() const;
313
302 void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot, 314 void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
303 FileSys::ContentProvider* provider); 315 FileSys::ContentProvider* provider);
304 316
diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp
index 01a969be9..594cd82c5 100644
--- a/src/core/crypto/partition_data_manager.cpp
+++ b/src/core/crypto/partition_data_manager.cpp
@@ -480,6 +480,10 @@ void PartitionDataManager::DecryptProdInfo(std::array<u8, 0x20> bis_key) {
480 prodinfo_decrypted = std::make_shared<XTSEncryptionLayer>(prodinfo, bis_key); 480 prodinfo_decrypted = std::make_shared<XTSEncryptionLayer>(prodinfo, bis_key);
481} 481}
482 482
483FileSys::VirtualFile PartitionDataManager::GetDecryptedProdInfo() const {
484 return prodinfo_decrypted;
485}
486
483std::array<u8, 576> PartitionDataManager::GetETicketExtendedKek() const { 487std::array<u8, 576> PartitionDataManager::GetETicketExtendedKek() const {
484 std::array<u8, 0x240> out{}; 488 std::array<u8, 0x240> out{};
485 if (prodinfo_decrypted != nullptr) 489 if (prodinfo_decrypted != nullptr)
diff --git a/src/core/crypto/partition_data_manager.h b/src/core/crypto/partition_data_manager.h
index 0ad007c72..7a7b5d038 100644
--- a/src/core/crypto/partition_data_manager.h
+++ b/src/core/crypto/partition_data_manager.h
@@ -84,6 +84,7 @@ public:
84 bool HasProdInfo() const; 84 bool HasProdInfo() const;
85 FileSys::VirtualFile GetProdInfoRaw() const; 85 FileSys::VirtualFile GetProdInfoRaw() const;
86 void DecryptProdInfo(std::array<u8, 0x20> bis_key); 86 void DecryptProdInfo(std::array<u8, 0x20> bis_key);
87 FileSys::VirtualFile GetDecryptedProdInfo() const;
87 std::array<u8, 0x240> GetETicketExtendedKek() const; 88 std::array<u8, 0x240> GetETicketExtendedKek() const;
88 89
89private: 90private:
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index e29f70b3a..8f758d6d9 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -3,8 +3,12 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <fmt/format.h> 5#include <fmt/format.h>
6#include "common/file_util.h"
7#include "core/core.h"
6#include "core/file_sys/bis_factory.h" 8#include "core/file_sys/bis_factory.h"
9#include "core/file_sys/mode.h"
7#include "core/file_sys/registered_cache.h" 10#include "core/file_sys/registered_cache.h"
11#include "core/settings.h"
8 12
9namespace FileSys { 13namespace FileSys {
10 14
@@ -14,10 +18,22 @@ BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_, VirtualDir
14 sysnand_cache(std::make_unique<RegisteredCache>( 18 sysnand_cache(std::make_unique<RegisteredCache>(
15 GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))), 19 GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
16 usrnand_cache(std::make_unique<RegisteredCache>( 20 usrnand_cache(std::make_unique<RegisteredCache>(
17 GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {} 21 GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))),
22 sysnand_placeholder(std::make_unique<PlaceholderCache>(
23 GetOrCreateDirectoryRelative(nand_root, "/system/Contents/placehld"))),
24 usrnand_placeholder(std::make_unique<PlaceholderCache>(
25 GetOrCreateDirectoryRelative(nand_root, "/user/Contents/placehld"))) {}
18 26
19BISFactory::~BISFactory() = default; 27BISFactory::~BISFactory() = default;
20 28
29VirtualDir BISFactory::GetSystemNANDContentDirectory() const {
30 return GetOrCreateDirectoryRelative(nand_root, "/system/Contents");
31}
32
33VirtualDir BISFactory::GetUserNANDContentDirectory() const {
34 return GetOrCreateDirectoryRelative(nand_root, "/user/Contents");
35}
36
21RegisteredCache* BISFactory::GetSystemNANDContents() const { 37RegisteredCache* BISFactory::GetSystemNANDContents() const {
22 return sysnand_cache.get(); 38 return sysnand_cache.get();
23} 39}
@@ -26,9 +42,17 @@ RegisteredCache* BISFactory::GetUserNANDContents() const {
26 return usrnand_cache.get(); 42 return usrnand_cache.get();
27} 43}
28 44
45PlaceholderCache* BISFactory::GetSystemNANDPlaceholder() const {
46 return sysnand_placeholder.get();
47}
48
49PlaceholderCache* BISFactory::GetUserNANDPlaceholder() const {
50 return usrnand_placeholder.get();
51}
52
29VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const { 53VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
30 // LayeredFS doesn't work on updates and title id-less homebrew 54 // LayeredFS doesn't work on updates and title id-less homebrew
31 if (title_id == 0 || (title_id & 0x800) > 0) 55 if (title_id == 0 || (title_id & 0xFFF) == 0x800)
32 return nullptr; 56 return nullptr;
33 return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id)); 57 return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id));
34} 58}
@@ -39,4 +63,77 @@ VirtualDir BISFactory::GetModificationDumpRoot(u64 title_id) const {
39 return GetOrCreateDirectoryRelative(dump_root, fmt::format("/{:016X}", title_id)); 63 return GetOrCreateDirectoryRelative(dump_root, fmt::format("/{:016X}", title_id));
40} 64}
41 65
66VirtualDir BISFactory::OpenPartition(BisPartitionId id) const {
67 switch (id) {
68 case BisPartitionId::CalibrationFile:
69 return GetOrCreateDirectoryRelative(nand_root, "/prodinfof");
70 case BisPartitionId::SafeMode:
71 return GetOrCreateDirectoryRelative(nand_root, "/safe");
72 case BisPartitionId::System:
73 return GetOrCreateDirectoryRelative(nand_root, "/system");
74 case BisPartitionId::User:
75 return GetOrCreateDirectoryRelative(nand_root, "/user");
76 default:
77 return nullptr;
78 }
79}
80
81VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const {
82 Core::Crypto::KeyManager keys;
83 Core::Crypto::PartitionDataManager pdm{
84 Core::System::GetInstance().GetFilesystem()->OpenDirectory(
85 FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), Mode::Read)};
86 keys.PopulateFromPartitionData(pdm);
87
88 switch (id) {
89 case BisPartitionId::CalibrationBinary:
90 return pdm.GetDecryptedProdInfo();
91 case BisPartitionId::BootConfigAndPackage2Part1:
92 case BisPartitionId::BootConfigAndPackage2Part2:
93 case BisPartitionId::BootConfigAndPackage2Part3:
94 case BisPartitionId::BootConfigAndPackage2Part4:
95 case BisPartitionId::BootConfigAndPackage2Part5:
96 case BisPartitionId::BootConfigAndPackage2Part6: {
97 const auto new_id = static_cast<u8>(id) -
98 static_cast<u8>(BisPartitionId::BootConfigAndPackage2Part1) +
99 static_cast<u8>(Core::Crypto::Package2Type::NormalMain);
100 return pdm.GetPackage2Raw(static_cast<Core::Crypto::Package2Type>(new_id));
101 }
102 default:
103 return nullptr;
104 }
105}
106
107VirtualDir BISFactory::GetImageDirectory() const {
108 return GetOrCreateDirectoryRelative(nand_root, "/user/Album");
109}
110
111u64 BISFactory::GetSystemNANDFreeSpace() const {
112 const auto sys_dir = GetOrCreateDirectoryRelative(nand_root, "/system");
113 if (sys_dir == nullptr)
114 return 0;
115
116 return GetSystemNANDTotalSpace() - sys_dir->GetSize();
117}
118
119u64 BISFactory::GetSystemNANDTotalSpace() const {
120 return static_cast<u64>(Settings::values.nand_system_size);
121}
122
123u64 BISFactory::GetUserNANDFreeSpace() const {
124 const auto usr_dir = GetOrCreateDirectoryRelative(nand_root, "/user");
125 if (usr_dir == nullptr)
126 return 0;
127
128 return GetUserNANDTotalSpace() - usr_dir->GetSize();
129}
130
131u64 BISFactory::GetUserNANDTotalSpace() const {
132 return static_cast<u64>(Settings::values.nand_user_size);
133}
134
135u64 BISFactory::GetFullNANDTotalSpace() const {
136 return static_cast<u64>(Settings::values.nand_total_size);
137}
138
42} // namespace FileSys 139} // namespace FileSys
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index 453c11ad2..bdfe728c9 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -10,7 +10,25 @@
10 10
11namespace FileSys { 11namespace FileSys {
12 12
13enum class BisPartitionId : u32 {
14 UserDataRoot = 20,
15 CalibrationBinary = 27,
16 CalibrationFile = 28,
17 BootConfigAndPackage2Part1 = 21,
18 BootConfigAndPackage2Part2 = 22,
19 BootConfigAndPackage2Part3 = 23,
20 BootConfigAndPackage2Part4 = 24,
21 BootConfigAndPackage2Part5 = 25,
22 BootConfigAndPackage2Part6 = 26,
23 SafeMode = 29,
24 System = 31,
25 SystemProperEncryption = 32,
26 SystemProperPartition = 33,
27 User = 30,
28};
29
13class RegisteredCache; 30class RegisteredCache;
31class PlaceholderCache;
14 32
15/// File system interface to the Built-In Storage 33/// File system interface to the Built-In Storage
16/// This is currently missing accessors to BIS partitions, but seemed like a good place for the NAND 34/// This is currently missing accessors to BIS partitions, but seemed like a good place for the NAND
@@ -20,12 +38,29 @@ public:
20 explicit BISFactory(VirtualDir nand_root, VirtualDir load_root, VirtualDir dump_root); 38 explicit BISFactory(VirtualDir nand_root, VirtualDir load_root, VirtualDir dump_root);
21 ~BISFactory(); 39 ~BISFactory();
22 40
41 VirtualDir GetSystemNANDContentDirectory() const;
42 VirtualDir GetUserNANDContentDirectory() const;
43
23 RegisteredCache* GetSystemNANDContents() const; 44 RegisteredCache* GetSystemNANDContents() const;
24 RegisteredCache* GetUserNANDContents() const; 45 RegisteredCache* GetUserNANDContents() const;
25 46
47 PlaceholderCache* GetSystemNANDPlaceholder() const;
48 PlaceholderCache* GetUserNANDPlaceholder() const;
49
26 VirtualDir GetModificationLoadRoot(u64 title_id) const; 50 VirtualDir GetModificationLoadRoot(u64 title_id) const;
27 VirtualDir GetModificationDumpRoot(u64 title_id) const; 51 VirtualDir GetModificationDumpRoot(u64 title_id) const;
28 52
53 VirtualDir OpenPartition(BisPartitionId id) const;
54 VirtualFile OpenPartitionStorage(BisPartitionId id) const;
55
56 VirtualDir GetImageDirectory() const;
57
58 u64 GetSystemNANDFreeSpace() const;
59 u64 GetSystemNANDTotalSpace() const;
60 u64 GetUserNANDFreeSpace() const;
61 u64 GetUserNANDTotalSpace() const;
62 u64 GetFullNANDTotalSpace() const;
63
29private: 64private:
30 VirtualDir nand_root; 65 VirtualDir nand_root;
31 VirtualDir load_root; 66 VirtualDir load_root;
@@ -33,6 +68,9 @@ private:
33 68
34 std::unique_ptr<RegisteredCache> sysnand_cache; 69 std::unique_ptr<RegisteredCache> sysnand_cache;
35 std::unique_ptr<RegisteredCache> usrnand_cache; 70 std::unique_ptr<RegisteredCache> usrnand_cache;
71
72 std::unique_ptr<PlaceholderCache> sysnand_placeholder;
73 std::unique_ptr<PlaceholderCache> usrnand_placeholder;
36}; 74};
37 75
38} // namespace FileSys 76} // namespace FileSys
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 626ed0042..db54113a0 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -12,12 +12,16 @@
12#include "core/file_sys/content_archive.h" 12#include "core/file_sys/content_archive.h"
13#include "core/file_sys/nca_metadata.h" 13#include "core/file_sys/nca_metadata.h"
14#include "core/file_sys/partition_filesystem.h" 14#include "core/file_sys/partition_filesystem.h"
15#include "core/file_sys/romfs.h"
15#include "core/file_sys/submission_package.h" 16#include "core/file_sys/submission_package.h"
17#include "core/file_sys/vfs_concat.h"
16#include "core/file_sys/vfs_offset.h" 18#include "core/file_sys/vfs_offset.h"
19#include "core/file_sys/vfs_vector.h"
17#include "core/loader/loader.h" 20#include "core/loader/loader.h"
18 21
19namespace FileSys { 22namespace FileSys {
20 23
24constexpr u64 GAMECARD_CERTIFICATE_OFFSET = 0x7000;
21constexpr std::array partition_names{ 25constexpr std::array partition_names{
22 "update", 26 "update",
23 "normal", 27 "normal",
@@ -175,6 +179,26 @@ VirtualDir XCI::GetParentDirectory() const {
175 return file->GetContainingDirectory(); 179 return file->GetContainingDirectory();
176} 180}
177 181
182VirtualDir XCI::ConcatenatedPseudoDirectory() {
183 const auto out = std::make_shared<VectorVfsDirectory>();
184 for (const auto& part_id : {XCIPartition::Normal, XCIPartition::Logo, XCIPartition::Secure}) {
185 const auto& part = GetPartition(part_id);
186 if (part == nullptr)
187 continue;
188
189 for (const auto& file : part->GetFiles())
190 out->AddFile(file);
191 }
192
193 return out;
194}
195
196std::array<u8, 0x200> XCI::GetCertificate() const {
197 std::array<u8, 0x200> out;
198 file->Read(out.data(), out.size(), GAMECARD_CERTIFICATE_OFFSET);
199 return out;
200}
201
178Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { 202Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
179 const auto partition_index = static_cast<std::size_t>(part); 203 const auto partition_index = static_cast<std::size_t>(part);
180 const auto& partition = partitions[partition_index]; 204 const auto& partition = partitions[partition_index];
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index a350496f7..3e6b92ff3 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -91,6 +91,8 @@ public:
91 VirtualDir GetLogoPartition() const; 91 VirtualDir GetLogoPartition() const;
92 92
93 u64 GetProgramTitleID() const; 93 u64 GetProgramTitleID() const;
94 u32 GetSystemUpdateVersion();
95 u64 GetSystemUpdateTitleID() const;
94 96
95 bool HasProgramNCA() const; 97 bool HasProgramNCA() const;
96 VirtualFile GetProgramNCAFile() const; 98 VirtualFile GetProgramNCAFile() const;
@@ -106,6 +108,11 @@ public:
106 108
107 VirtualDir GetParentDirectory() const override; 109 VirtualDir GetParentDirectory() const override;
108 110
111 // Creates a directory that contains all the NCAs in the gamecard
112 VirtualDir ConcatenatedPseudoDirectory();
113
114 std::array<u8, 0x200> GetCertificate() const;
115
109private: 116private:
110 Loader::ResultStatus AddNCAFromPartition(XCIPartition part); 117 Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
111 118
@@ -120,6 +127,8 @@ private:
120 std::shared_ptr<NCA> program; 127 std::shared_ptr<NCA> program;
121 std::vector<std::shared_ptr<NCA>> ncas; 128 std::vector<std::shared_ptr<NCA>> ncas;
122 129
130 u64 update_normal_partition_end;
131
123 Core::Crypto::KeyManager keys; 132 Core::Crypto::KeyManager keys;
124}; 133};
125} // namespace FileSys 134} // namespace FileSys
diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp
deleted file mode 100644
index b06c2f20a..000000000
--- a/src/core/file_sys/cheat_engine.cpp
+++ /dev/null
@@ -1,492 +0,0 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <locale>
6#include "common/hex_util.h"
7#include "common/microprofile.h"
8#include "common/swap.h"
9#include "core/core.h"
10#include "core/core_timing.h"
11#include "core/core_timing_util.h"
12#include "core/file_sys/cheat_engine.h"
13#include "core/hle/kernel/process.h"
14#include "core/hle/service/hid/controllers/npad.h"
15#include "core/hle/service/hid/hid.h"
16#include "core/hle/service/sm/sm.h"
17
18namespace FileSys {
19
20constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
21constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
22
23u64 Cheat::Address() const {
24 u64 out;
25 std::memcpy(&out, raw.data(), sizeof(u64));
26 return Common::swap64(out) & 0xFFFFFFFFFF;
27}
28
29u64 Cheat::ValueWidth(u64 offset) const {
30 return Value(offset, width);
31}
32
33u64 Cheat::Value(u64 offset, u64 width) const {
34 u64 out;
35 std::memcpy(&out, raw.data() + offset, sizeof(u64));
36 out = Common::swap64(out);
37 if (width == 8)
38 return out;
39 return out & ((1ull << (width * CHAR_BIT)) - 1);
40}
41
42u32 Cheat::KeypadValue() const {
43 u32 out;
44 std::memcpy(&out, raw.data(), sizeof(u32));
45 return Common::swap32(out) & 0x0FFFFFFF;
46}
47
48void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end,
49 VAddr heap_end, MemoryWriter writer, MemoryReader reader) {
50 this->main_region_begin = main_begin;
51 this->main_region_end = main_end;
52 this->heap_region_begin = heap_begin;
53 this->heap_region_end = heap_end;
54 this->writer = writer;
55 this->reader = reader;
56}
57
58MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
59
60void CheatList::Execute() {
61 MICROPROFILE_SCOPE(Cheat_Engine);
62
63 std::fill(scratch.begin(), scratch.end(), 0);
64 in_standard = false;
65 for (std::size_t i = 0; i < master_list.size(); ++i) {
66 LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first);
67 current_block = i;
68 ExecuteBlock(master_list[i].second);
69 }
70
71 in_standard = true;
72 for (std::size_t i = 0; i < standard_list.size(); ++i) {
73 LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first);
74 current_block = i;
75 ExecuteBlock(standard_list[i].second);
76 }
77}
78
79CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard)
80 : master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {}
81
82bool CheatList::EvaluateConditional(const Cheat& cheat) const {
83 using ComparisonFunction = bool (*)(u64, u64);
84 constexpr std::array<ComparisonFunction, 6> comparison_functions{
85 [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; },
86 [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; },
87 [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; },
88 };
89
90 if (cheat.type == CodeType::ConditionalInput) {
91 const auto applet_resource =
92 system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
93 if (applet_resource == nullptr) {
94 LOG_WARNING(
95 Common_Filesystem,
96 "Attempted to evaluate input conditional, but applet resource is not initialized!");
97 return false;
98 }
99
100 const auto press_state =
101 applet_resource
102 ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
103 .GetAndResetPressState();
104 return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0;
105 }
106
107 ASSERT(cheat.type == CodeType::Conditional);
108
109 const auto offset =
110 cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
111 ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6);
112 auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())];
113 const auto addr = cheat.Address() + offset;
114
115 return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8));
116}
117
118void CheatList::ProcessBlockPairs(const Block& block) {
119 block_pairs.clear();
120
121 u64 scope = 0;
122 std::map<u64, u64> pairs;
123
124 for (std::size_t i = 0; i < block.size(); ++i) {
125 const auto& cheat = block[i];
126
127 switch (cheat.type) {
128 case CodeType::Conditional:
129 case CodeType::ConditionalInput:
130 pairs.insert_or_assign(scope, i);
131 ++scope;
132 break;
133 case CodeType::EndConditional: {
134 --scope;
135 const auto idx = pairs.at(scope);
136 block_pairs.insert_or_assign(idx, i);
137 break;
138 }
139 case CodeType::Loop: {
140 if (cheat.end_of_loop) {
141 --scope;
142 const auto idx = pairs.at(scope);
143 block_pairs.insert_or_assign(idx, i);
144 } else {
145 pairs.insert_or_assign(scope, i);
146 ++scope;
147 }
148 break;
149 }
150 }
151 }
152}
153
154void CheatList::WriteImmediate(const Cheat& cheat) {
155 const auto offset =
156 cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
157 const auto& register_3 = scratch.at(cheat.register_3);
158
159 const auto addr = cheat.Address() + offset + register_3;
160 LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr,
161 cheat.Value(8, cheat.width));
162 writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8));
163}
164
165void CheatList::BeginConditional(const Cheat& cheat) {
166 if (EvaluateConditional(cheat)) {
167 return;
168 }
169
170 const auto iter = block_pairs.find(current_index);
171 ASSERT(iter != block_pairs.end());
172 current_index = iter->second - 1;
173}
174
175void CheatList::EndConditional(const Cheat& cheat) {
176 LOG_DEBUG(Common_Filesystem, "Ending conditional block.");
177}
178
179void CheatList::Loop(const Cheat& cheat) {
180 if (cheat.end_of_loop.Value())
181 ASSERT(!cheat.end_of_loop.Value());
182
183 auto& register_3 = scratch.at(cheat.register_3);
184 const auto iter = block_pairs.find(current_index);
185 ASSERT(iter != block_pairs.end());
186 ASSERT(iter->first < iter->second);
187
188 const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32)));
189 for (s32 i = initial_value; i >= 0; --i) {
190 register_3 = static_cast<u64>(i);
191 for (std::size_t c = iter->first + 1; c < iter->second; ++c) {
192 current_index = c;
193 ExecuteSingleCheat(
194 (in_standard ? standard_list : master_list)[current_block].second[c]);
195 }
196 }
197
198 current_index = iter->second;
199}
200
201void CheatList::LoadImmediate(const Cheat& cheat) {
202 auto& register_3 = scratch.at(cheat.register_3);
203
204 LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3,
205 cheat.Value(4, 8));
206 register_3 = cheat.Value(4, 8);
207}
208
209void CheatList::LoadIndexed(const Cheat& cheat) {
210 const auto offset =
211 cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
212 auto& register_3 = scratch.at(cheat.register_3);
213
214 const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address();
215 LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}",
216 cheat.register_3, addr);
217 register_3 = reader(cheat.width, SanitizeAddress(addr));
218}
219
220void CheatList::StoreIndexed(const Cheat& cheat) {
221 const auto& register_3 = scratch.at(cheat.register_3);
222
223 const auto addr =
224 register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0);
225 LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}",
226 cheat.Value(4, cheat.width), addr);
227 writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4));
228}
229
230void CheatList::RegisterArithmetic(const Cheat& cheat) {
231 using ArithmeticFunction = u64 (*)(u64, u64);
232 constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{
233 [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; },
234 [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; },
235 [](u64 a, u64 b) { return a >> b; },
236 };
237
238 using ArithmeticOverflowCheck = bool (*)(u64, u64);
239 constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{
240 [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b
241 [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b
242 [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b
243 [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b
244 [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b
245 };
246
247 static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks),
248 "Missing or have extra arithmetic overflow checks compared to functions!");
249
250 auto& register_3 = scratch.at(cheat.register_3);
251
252 ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5);
253 auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())];
254 auto* overflow_function =
255 arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())];
256 LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}",
257 cheat.register_3, cheat.ValueWidth(4));
258
259 if (overflow_function(register_3, cheat.ValueWidth(4))) {
260 LOG_WARNING(Common_Filesystem,
261 "overflow will occur when performing arithmetic operation={:02X} with operands "
262 "a={:016X}, b={:016X}!",
263 static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4));
264 }
265
266 register_3 = function(register_3, cheat.ValueWidth(4));
267}
268
269void CheatList::BeginConditionalInput(const Cheat& cheat) {
270 if (EvaluateConditional(cheat))
271 return;
272
273 const auto iter = block_pairs.find(current_index);
274 ASSERT(iter != block_pairs.end());
275 current_index = iter->second - 1;
276}
277
278VAddr CheatList::SanitizeAddress(VAddr in) const {
279 if ((in < main_region_begin || in >= main_region_end) &&
280 (in < heap_region_begin || in >= heap_region_end)) {
281 LOG_ERROR(Common_Filesystem,
282 "Cheat attempting to access memory at invalid address={:016X}, if this persists, "
283 "the cheat may be incorrect. However, this may be normal early in execution if "
284 "the game has not properly set up yet.",
285 in);
286 return 0; ///< Invalid addresses will hard crash
287 }
288
289 return in;
290}
291
292void CheatList::ExecuteSingleCheat(const Cheat& cheat) {
293 using CheatOperationFunction = void (CheatList::*)(const Cheat&);
294 constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{
295 &CheatList::WriteImmediate, &CheatList::BeginConditional,
296 &CheatList::EndConditional, &CheatList::Loop,
297 &CheatList::LoadImmediate, &CheatList::LoadIndexed,
298 &CheatList::StoreIndexed, &CheatList::RegisterArithmetic,
299 &CheatList::BeginConditionalInput,
300 };
301
302 const auto index = static_cast<u8>(cheat.type.Value());
303 ASSERT(index < sizeof(cheat_operation_functions));
304 const auto op = cheat_operation_functions[index];
305 (this->*op)(cheat);
306}
307
308void CheatList::ExecuteBlock(const Block& block) {
309 encountered_loops.clear();
310
311 ProcessBlockPairs(block);
312 for (std::size_t i = 0; i < block.size(); ++i) {
313 current_index = i;
314 ExecuteSingleCheat(block[i]);
315 i = current_index;
316 }
317}
318
319CheatParser::~CheatParser() = default;
320
321CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master,
322 CheatList::ProgramSegment standard) const {
323 return {system, std::move(master), std::move(standard)};
324}
325
326TextCheatParser::~TextCheatParser() = default;
327
328CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const {
329 std::stringstream ss;
330 ss.write(reinterpret_cast<const char*>(data.data()), data.size());
331
332 std::vector<std::string> lines;
333 std::string stream_line;
334 while (std::getline(ss, stream_line)) {
335 // Remove a trailing \r
336 if (!stream_line.empty() && stream_line.back() == '\r')
337 stream_line.pop_back();
338 lines.push_back(std::move(stream_line));
339 }
340
341 CheatList::ProgramSegment master_list;
342 CheatList::ProgramSegment standard_list;
343
344 for (std::size_t i = 0; i < lines.size(); ++i) {
345 auto line = lines[i];
346
347 if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
348 const auto master = line[0] == '{';
349 const auto begin = master ? line.find('{') : line.find('[');
350 const auto end = master ? line.rfind('}') : line.rfind(']');
351
352 ASSERT(begin != std::string::npos && end != std::string::npos);
353
354 const std::string patch_name{line.begin() + begin + 1, line.begin() + end};
355 CheatList::Block block{};
356
357 while (i < lines.size() - 1) {
358 line = lines[++i];
359 if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
360 --i;
361 break;
362 }
363
364 if (line.size() < 8)
365 continue;
366
367 Cheat out{};
368 out.raw = ParseSingleLineCheat(line);
369 block.push_back(out);
370 }
371
372 (master ? master_list : standard_list).emplace_back(patch_name, block);
373 }
374 }
375
376 return MakeCheatList(system, master_list, standard_list);
377}
378
379std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const {
380 std::array<u8, 16> out{};
381
382 if (line.size() < 8)
383 return out;
384
385 const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8});
386 std::memcpy(out.data(), word1.data(), sizeof(u32));
387
388 if (line.size() < 17 || line[8] != ' ')
389 return out;
390
391 const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8});
392 std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32));
393
394 if (line.size() < 26 || line[17] != ' ') {
395 // Perform shifting in case value is truncated early.
396 const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
397 if (type == CodeType::Loop || type == CodeType::LoadImmediate ||
398 type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) {
399 std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32));
400 std::memset(out.data() + 4, 0, sizeof(u32));
401 }
402
403 return out;
404 }
405
406 const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8});
407 std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32));
408
409 if (line.size() < 35 || line[26] != ' ') {
410 // Perform shifting in case value is truncated early.
411 const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
412 if (type == CodeType::WriteImmediate || type == CodeType::Conditional) {
413 std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32));
414 std::memset(out.data() + 8, 0, sizeof(u32));
415 }
416
417 return out;
418 }
419
420 const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8});
421 std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32));
422
423 return out;
424}
425
426namespace {
427u64 MemoryReadImpl(u32 width, VAddr addr) {
428 switch (width) {
429 case 1:
430 return Memory::Read8(addr);
431 case 2:
432 return Memory::Read16(addr);
433 case 4:
434 return Memory::Read32(addr);
435 case 8:
436 return Memory::Read64(addr);
437 default:
438 UNREACHABLE();
439 return 0;
440 }
441}
442
443void MemoryWriteImpl(u32 width, VAddr addr, u64 value) {
444 switch (width) {
445 case 1:
446 Memory::Write8(addr, static_cast<u8>(value));
447 break;
448 case 2:
449 Memory::Write16(addr, static_cast<u16>(value));
450 break;
451 case 4:
452 Memory::Write32(addr, static_cast<u32>(value));
453 break;
454 case 8:
455 Memory::Write64(addr, value);
456 break;
457 default:
458 UNREACHABLE();
459 }
460}
461} // Anonymous namespace
462
463CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_,
464 const std::string& build_id, VAddr code_region_start,
465 VAddr code_region_end)
466 : cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} {
467 event = core_timing.RegisterEvent(
468 "CheatEngine::FrameCallback::" + build_id,
469 [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
470 core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
471
472 const auto& vm_manager = system.CurrentProcess()->VMManager();
473 for (auto& list : this->cheats) {
474 list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(),
475 code_region_end, vm_manager.GetHeapRegionEndAddress(),
476 &MemoryWriteImpl, &MemoryReadImpl);
477 }
478}
479
480CheatEngine::~CheatEngine() {
481 core_timing.UnscheduleEvent(event, 0);
482}
483
484void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
485 for (auto& list : cheats) {
486 list.Execute();
487 }
488
489 core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
490}
491
492} // namespace FileSys
diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h
deleted file mode 100644
index ac22a82cb..000000000
--- a/src/core/file_sys/cheat_engine.h
+++ /dev/null
@@ -1,234 +0,0 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <map>
8#include <set>
9#include <vector>
10#include "common/bit_field.h"
11#include "common/common_types.h"
12
13namespace Core {
14class System;
15}
16
17namespace Core::Timing {
18class CoreTiming;
19struct EventType;
20} // namespace Core::Timing
21
22namespace FileSys {
23
24enum class CodeType : u32 {
25 // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY
26 // Writes a T sized value Y to the address A added to the value of register R in memory domain M
27 WriteImmediate = 0,
28
29 // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY
30 // Compares the T sized value Y to the value at address A in memory domain M using the
31 // conditional function C. If success, continues execution. If failure, jumps to the matching
32 // EndConditional statement.
33 Conditional = 1,
34
35 // 20000000
36 // Terminates a Conditional or ConditionalInput block.
37 EndConditional = 2,
38
39 // 300R0000 VVVVVVVV
40 // Starts looping V times, storing the current count in register R.
41 // Loop block is terminated with a matching 310R0000.
42 Loop = 3,
43
44 // 400R0000 VVVVVVVV VVVVVVVV
45 // Sets the value of register R to the value V.
46 LoadImmediate = 4,
47
48 // 5TMRI0AA AAAAAAAA
49 // Sets the value of register R to the value of width T at address A in memory domain M, with
50 // the current value of R added to the address if I == 1.
51 LoadIndexed = 5,
52
53 // 6T0RIFG0 VVVVVVVV VVVVVVVV
54 // Writes the value V of width T to the memory address stored in register R. Adds the value of
55 // register G to the final calculation if F is nonzero. Increments the value of register R by T
56 // after operation if I is nonzero.
57 StoreIndexed = 6,
58
59 // 7T0RA000 VVVVVVVV
60 // Performs the arithmetic operation A on the value in register R and the value V of width T,
61 // storing the result in register R.
62 RegisterArithmetic = 7,
63
64 // 8KKKKKKK
65 // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are,
66 // execution continues. If none are, execution skips to the next EndConditional command.
67 ConditionalInput = 8,
68};
69
70enum class MemoryType : u32 {
71 // Addressed relative to start of main NSO
72 MainNSO = 0,
73
74 // Addressed relative to start of heap
75 Heap = 1,
76};
77
78enum class ArithmeticOp : u32 {
79 Add = 0,
80 Sub = 1,
81 Mult = 2,
82 LShift = 3,
83 RShift = 4,
84};
85
86enum class ComparisonOp : u32 {
87 GreaterThan = 1,
88 GreaterThanEqual = 2,
89 LessThan = 3,
90 LessThanEqual = 4,
91 Equal = 5,
92 Inequal = 6,
93};
94
95union Cheat {
96 std::array<u8, 16> raw;
97
98 BitField<4, 4, CodeType> type;
99 BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes.
100 BitField<0, 4, u32> end_of_loop;
101 BitField<12, 4, MemoryType> memory_type;
102 BitField<8, 4, u32> register_3;
103 BitField<8, 4, ComparisonOp> comparison_op;
104 BitField<20, 4, u32> load_from_register;
105 BitField<20, 4, u32> increment_register;
106 BitField<20, 4, ArithmeticOp> arithmetic_op;
107 BitField<16, 4, u32> add_additional_register;
108 BitField<28, 4, u32> register_6;
109
110 u64 Address() const;
111 u64 ValueWidth(u64 offset) const;
112 u64 Value(u64 offset, u64 width) const;
113 u32 KeypadValue() const;
114};
115
116class CheatParser;
117
118// Represents a full collection of cheats for a game. The Execute function should be called every
119// interval that all cheats should be executed. Clients should not directly instantiate this class
120// (hence private constructor), they should instead receive an instance from CheatParser, which
121// guarantees the list is always in an acceptable state.
122class CheatList {
123public:
124 friend class CheatParser;
125
126 using Block = std::vector<Cheat>;
127 using ProgramSegment = std::vector<std::pair<std::string, Block>>;
128
129 // (width in bytes, address, value)
130 using MemoryWriter = void (*)(u32, VAddr, u64);
131 // (width in bytes, address) -> value
132 using MemoryReader = u64 (*)(u32, VAddr);
133
134 void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end,
135 MemoryWriter writer, MemoryReader reader);
136
137 void Execute();
138
139private:
140 CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard);
141
142 void ProcessBlockPairs(const Block& block);
143 void ExecuteSingleCheat(const Cheat& cheat);
144
145 void ExecuteBlock(const Block& block);
146
147 bool EvaluateConditional(const Cheat& cheat) const;
148
149 // Individual cheat operations
150 void WriteImmediate(const Cheat& cheat);
151 void BeginConditional(const Cheat& cheat);
152 void EndConditional(const Cheat& cheat);
153 void Loop(const Cheat& cheat);
154 void LoadImmediate(const Cheat& cheat);
155 void LoadIndexed(const Cheat& cheat);
156 void StoreIndexed(const Cheat& cheat);
157 void RegisterArithmetic(const Cheat& cheat);
158 void BeginConditionalInput(const Cheat& cheat);
159
160 VAddr SanitizeAddress(VAddr in) const;
161
162 // Master Codes are defined as codes that cannot be disabled and are run prior to all
163 // others.
164 ProgramSegment master_list;
165 // All other codes
166 ProgramSegment standard_list;
167
168 bool in_standard = false;
169
170 // 16 (0x0-0xF) scratch registers that can be used by cheats
171 std::array<u64, 16> scratch{};
172
173 MemoryWriter writer = nullptr;
174 MemoryReader reader = nullptr;
175
176 u64 main_region_begin{};
177 u64 heap_region_begin{};
178 u64 main_region_end{};
179 u64 heap_region_end{};
180
181 u64 current_block{};
182 // The current index of the cheat within the current Block
183 u64 current_index{};
184
185 // The 'stack' of the program. When a conditional or loop statement is encountered, its index is
186 // pushed onto this queue. When a end block is encountered, the condition is checked.
187 std::map<u64, u64> block_pairs;
188
189 std::set<u64> encountered_loops;
190
191 const Core::System* system;
192};
193
194// Intermediary class that parses a text file or other disk format for storing cheats into a
195// CheatList object, that can be used for execution.
196class CheatParser {
197public:
198 virtual ~CheatParser();
199
200 virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0;
201
202protected:
203 CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master,
204 CheatList::ProgramSegment standard) const;
205};
206
207// CheatParser implementation that parses text files
208class TextCheatParser final : public CheatParser {
209public:
210 ~TextCheatParser() override;
211
212 CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override;
213
214private:
215 std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const;
216};
217
218// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
219class CheatEngine final {
220public:
221 CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id,
222 VAddr code_region_start, VAddr code_region_end);
223 ~CheatEngine();
224
225private:
226 void FrameCallback(u64 userdata, s64 cycles_late);
227
228 std::vector<CheatList> cheats;
229
230 Core::Timing::EventType* event;
231 Core::Timing::CoreTiming& core_timing;
232};
233
234} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index ce5c69b41..ea5c92f61 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -528,6 +528,14 @@ u64 NCA::GetTitleId() const {
528 return header.title_id; 528 return header.title_id;
529} 529}
530 530
531std::array<u8, 16> NCA::GetRightsId() const {
532 return header.rights_id;
533}
534
535u32 NCA::GetSDKVersion() const {
536 return header.sdk_version;
537}
538
531bool NCA::IsUpdate() const { 539bool NCA::IsUpdate() const {
532 return is_update; 540 return is_update;
533} 541}
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 15b9e6624..e249079b5 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -112,6 +112,8 @@ public:
112 112
113 NCAContentType GetType() const; 113 NCAContentType GetType() const;
114 u64 GetTitleId() const; 114 u64 GetTitleId() const;
115 std::array<u8, 0x10> GetRightsId() const;
116 u32 GetSDKVersion() const;
115 bool IsUpdate() const; 117 bool IsUpdate() const;
116 118
117 VirtualFile GetRomFS() const; 119 VirtualFile GetRomFS() const;
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index a8f80e2c6..df0ecb15c 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -22,6 +22,7 @@
22#include "core/hle/service/filesystem/filesystem.h" 22#include "core/hle/service/filesystem/filesystem.h"
23#include "core/loader/loader.h" 23#include "core/loader/loader.h"
24#include "core/loader/nso.h" 24#include "core/loader/nso.h"
25#include "core/memory/cheat_engine.h"
25#include "core/settings.h" 26#include "core/settings.h"
26 27
27namespace FileSys { 28namespace FileSys {
@@ -63,7 +64,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
63 64
64 if (Settings::values.dump_exefs) { 65 if (Settings::values.dump_exefs) {
65 LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id); 66 LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id);
66 const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id); 67 const auto dump_dir =
68 Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id);
67 if (dump_dir != nullptr) { 69 if (dump_dir != nullptr) {
68 const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs"); 70 const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs");
69 VfsRawCopyD(exefs, exefs_dir); 71 VfsRawCopyD(exefs, exefs_dir);
@@ -88,7 +90,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
88 } 90 }
89 91
90 // LayeredExeFS 92 // LayeredExeFS
91 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); 93 const auto load_dir =
94 Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
92 if (load_dir != nullptr && load_dir->GetSize() > 0) { 95 if (load_dir != nullptr && load_dir->GetSize() > 0) {
93 auto patch_dirs = load_dir->GetSubdirectories(); 96 auto patch_dirs = load_dir->GetSubdirectories();
94 std::sort( 97 std::sort(
@@ -174,7 +177,8 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
174 if (Settings::values.dump_nso) { 177 if (Settings::values.dump_nso) {
175 LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id, 178 LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id,
176 title_id); 179 title_id);
177 const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id); 180 const auto dump_dir =
181 Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id);
178 if (dump_dir != nullptr) { 182 if (dump_dir != nullptr) {
179 const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso"); 183 const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso");
180 const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id)); 184 const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id));
@@ -186,7 +190,13 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
186 190
187 LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id); 191 LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
188 192
189 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); 193 const auto load_dir =
194 Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
195 if (load_dir == nullptr) {
196 LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
197 return nso;
198 }
199
190 auto patch_dirs = load_dir->GetSubdirectories(); 200 auto patch_dirs = load_dir->GetSubdirectories();
191 std::sort(patch_dirs.begin(), patch_dirs.end(), 201 std::sort(patch_dirs.begin(), patch_dirs.end(),
192 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); 202 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
@@ -224,7 +234,13 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
224 234
225 LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); 235 LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
226 236
227 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); 237 const auto load_dir =
238 Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
239 if (load_dir == nullptr) {
240 LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
241 return false;
242 }
243
228 auto patch_dirs = load_dir->GetSubdirectories(); 244 auto patch_dirs = load_dir->GetSubdirectories();
229 std::sort(patch_dirs.begin(), patch_dirs.end(), 245 std::sort(patch_dirs.begin(), patch_dirs.end(),
230 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); 246 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
@@ -232,9 +248,10 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
232 return !CollectPatches(patch_dirs, build_id).empty(); 248 return !CollectPatches(patch_dirs, build_id).empty();
233} 249}
234 250
235static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id, 251namespace {
236 const std::array<u8, 0x20>& build_id_, 252std::optional<std::vector<Memory::CheatEntry>> ReadCheatFileFromFolder(
237 const VirtualDir& base_path, bool upper) { 253 const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_,
254 const VirtualDir& base_path, bool upper) {
238 const auto build_id_raw = Common::HexToString(build_id_, upper); 255 const auto build_id_raw = Common::HexToString(build_id_, upper);
239 const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); 256 const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
240 const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); 257 const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
@@ -252,31 +269,39 @@ static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& syst
252 return std::nullopt; 269 return std::nullopt;
253 } 270 }
254 271
255 TextCheatParser parser; 272 Memory::TextCheatParser parser;
256 return parser.Parse(system, data); 273 return parser.Parse(
274 system, std::string_view(reinterpret_cast<const char* const>(data.data()), data.size()));
257} 275}
258 276
259std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system, 277} // Anonymous namespace
260 const std::array<u8, 32>& build_id_) const { 278
261 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); 279std::vector<Memory::CheatEntry> PatchManager::CreateCheatList(
280 const Core::System& system, const std::array<u8, 32>& build_id_) const {
281 const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id);
282 if (load_dir == nullptr) {
283 LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
284 return {};
285 }
286
262 auto patch_dirs = load_dir->GetSubdirectories(); 287 auto patch_dirs = load_dir->GetSubdirectories();
263 std::sort(patch_dirs.begin(), patch_dirs.end(), 288 std::sort(patch_dirs.begin(), patch_dirs.end(),
264 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); 289 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
265 290
266 std::vector<CheatList> out; 291 std::vector<Memory::CheatEntry> out;
267 out.reserve(patch_dirs.size());
268 for (const auto& subdir : patch_dirs) { 292 for (const auto& subdir : patch_dirs) {
269 auto cheats_dir = subdir->GetSubdirectory("cheats"); 293 auto cheats_dir = subdir->GetSubdirectory("cheats");
270 if (cheats_dir != nullptr) { 294 if (cheats_dir != nullptr) {
271 auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true); 295 auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true);
272 if (res.has_value()) { 296 if (res.has_value()) {
273 out.push_back(std::move(*res)); 297 std::copy(res->begin(), res->end(), std::back_inserter(out));
274 continue; 298 continue;
275 } 299 }
276 300
277 res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false); 301 res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false);
278 if (res.has_value()) 302 if (res.has_value()) {
279 out.push_back(std::move(*res)); 303 std::copy(res->begin(), res->end(), std::back_inserter(out));
304 }
280 } 305 }
281 } 306 }
282 307
@@ -284,7 +309,8 @@ std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system,
284} 309}
285 310
286static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { 311static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
287 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); 312 const auto load_dir =
313 Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
288 if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || 314 if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
289 load_dir == nullptr || load_dir->GetSize() <= 0) { 315 load_dir == nullptr || load_dir->GetSize() <= 0) {
290 return; 316 return;
@@ -393,6 +419,8 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
393 419
394std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames( 420std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
395 VirtualFile update_raw) const { 421 VirtualFile update_raw) const {
422 if (title_id == 0)
423 return {};
396 std::map<std::string, std::string, std::less<>> out; 424 std::map<std::string, std::string, std::less<>> out;
397 const auto& installed = Core::System::GetInstance().GetContentProvider(); 425 const auto& installed = Core::System::GetInstance().GetContentProvider();
398 const auto& disabled = Settings::values.disabled_addons[title_id]; 426 const auto& disabled = Settings::values.disabled_addons[title_id];
@@ -423,7 +451,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
423 } 451 }
424 452
425 // General Mods (LayeredFS and IPS) 453 // General Mods (LayeredFS and IPS)
426 const auto mod_dir = Service::FileSystem::GetModificationLoadRoot(title_id); 454 const auto mod_dir =
455 Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
427 if (mod_dir != nullptr && mod_dir->GetSize() > 0) { 456 if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
428 for (const auto& mod : mod_dir->GetSubdirectories()) { 457 for (const auto& mod : mod_dir->GetSubdirectories()) {
429 std::string types; 458 std::string types;
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index a363c6577..e857e6e82 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -8,9 +8,9 @@
8#include <memory> 8#include <memory>
9#include <string> 9#include <string>
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "core/file_sys/cheat_engine.h"
12#include "core/file_sys/nca_metadata.h" 11#include "core/file_sys/nca_metadata.h"
13#include "core/file_sys/vfs.h" 12#include "core/file_sys/vfs.h"
13#include "core/memory/dmnt_cheat_types.h"
14 14
15namespace Core { 15namespace Core {
16class System; 16class System;
@@ -51,8 +51,8 @@ public:
51 bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const; 51 bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
52 52
53 // Creates a CheatList object with all 53 // Creates a CheatList object with all
54 std::vector<CheatList> CreateCheatList(const Core::System& system, 54 std::vector<Memory::CheatEntry> CreateCheatList(const Core::System& system,
55 const std::array<u8, 0x20>& build_id) const; 55 const std::array<u8, 0x20>& build_id) const;
56 56
57 // Currently tracked RomFS patches: 57 // Currently tracked RomFS patches:
58 // - Game Updates 58 // - Game Updates
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 3725b10f7..ac3fbd849 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -3,6 +3,7 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm> 5#include <algorithm>
6#include <random>
6#include <regex> 7#include <regex>
7#include <mbedtls/sha256.h> 8#include <mbedtls/sha256.h>
8#include "common/assert.h" 9#include "common/assert.h"
@@ -48,18 +49,21 @@ static bool FollowsTwoDigitDirFormat(std::string_view name) {
48static bool FollowsNcaIdFormat(std::string_view name) { 49static bool FollowsNcaIdFormat(std::string_view name) {
49 static const std::regex nca_id_regex("[0-9A-F]{32}\\.nca", std::regex_constants::ECMAScript | 50 static const std::regex nca_id_regex("[0-9A-F]{32}\\.nca", std::regex_constants::ECMAScript |
50 std::regex_constants::icase); 51 std::regex_constants::icase);
51 return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex); 52 static const std::regex nca_id_cnmt_regex(
53 "[0-9A-F]{32}\\.cnmt.nca", std::regex_constants::ECMAScript | std::regex_constants::icase);
54 return (name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex)) ||
55 (name.size() == 41 && std::regex_match(name.begin(), name.end(), nca_id_cnmt_regex));
52} 56}
53 57
54static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper, 58static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,
55 bool within_two_digit) { 59 bool within_two_digit, bool cnmt_suffix) {
56 if (!within_two_digit) { 60 if (!within_two_digit)
57 return fmt::format("/{}.nca", Common::HexToString(nca_id, second_hex_upper)); 61 return fmt::format(cnmt_suffix ? "{}.cnmt.nca" : "/{}.nca",
58 } 62 Common::HexToString(nca_id, second_hex_upper));
59 63
60 Core::Crypto::SHA256Hash hash{}; 64 Core::Crypto::SHA256Hash hash{};
61 mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0); 65 mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
62 return fmt::format("/000000{:02X}/{}.nca", hash[0], 66 return fmt::format(cnmt_suffix ? "/000000{:02X}/{}.cnmt.nca" : "/000000{:02X}/{}.nca", hash[0],
63 Common::HexToString(nca_id, second_hex_upper)); 67 Common::HexToString(nca_id, second_hex_upper));
64} 68}
65 69
@@ -127,6 +131,156 @@ std::vector<ContentProviderEntry> ContentProvider::ListEntries() const {
127 return ListEntriesFilter(std::nullopt, std::nullopt, std::nullopt); 131 return ListEntriesFilter(std::nullopt, std::nullopt, std::nullopt);
128} 132}
129 133
134PlaceholderCache::PlaceholderCache(VirtualDir dir_) : dir(std::move(dir_)) {}
135
136bool PlaceholderCache::Create(const NcaID& id, u64 size) const {
137 const auto path = GetRelativePathFromNcaID(id, false, true, false);
138
139 if (dir->GetFileRelative(path) != nullptr) {
140 return false;
141 }
142
143 Core::Crypto::SHA256Hash hash{};
144 mbedtls_sha256(id.data(), id.size(), hash.data(), 0);
145 const auto dirname = fmt::format("000000{:02X}", hash[0]);
146
147 const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname);
148
149 if (dir2 == nullptr)
150 return false;
151
152 const auto file = dir2->CreateFile(fmt::format("{}.nca", Common::HexToString(id, false)));
153
154 if (file == nullptr)
155 return false;
156
157 return file->Resize(size);
158}
159
160bool PlaceholderCache::Delete(const NcaID& id) const {
161 const auto path = GetRelativePathFromNcaID(id, false, true, false);
162
163 if (dir->GetFileRelative(path) == nullptr) {
164 return false;
165 }
166
167 Core::Crypto::SHA256Hash hash{};
168 mbedtls_sha256(id.data(), id.size(), hash.data(), 0);
169 const auto dirname = fmt::format("000000{:02X}", hash[0]);
170
171 const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname);
172
173 const auto res = dir2->DeleteFile(fmt::format("{}.nca", Common::HexToString(id, false)));
174
175 return res;
176}
177
178bool PlaceholderCache::Exists(const NcaID& id) const {
179 const auto path = GetRelativePathFromNcaID(id, false, true, false);
180
181 return dir->GetFileRelative(path) != nullptr;
182}
183
184bool PlaceholderCache::Write(const NcaID& id, u64 offset, const std::vector<u8>& data) const {
185 const auto path = GetRelativePathFromNcaID(id, false, true, false);
186 const auto file = dir->GetFileRelative(path);
187
188 if (file == nullptr)
189 return false;
190
191 return file->WriteBytes(data, offset) == data.size();
192}
193
194bool PlaceholderCache::Register(RegisteredCache* cache, const NcaID& placeholder,
195 const NcaID& install) const {
196 const auto path = GetRelativePathFromNcaID(placeholder, false, true, false);
197 const auto file = dir->GetFileRelative(path);
198
199 if (file == nullptr)
200 return false;
201
202 const auto res = cache->RawInstallNCA(NCA{file}, &VfsRawCopy, false, install);
203
204 if (res != InstallResult::Success)
205 return false;
206
207 return Delete(placeholder);
208}
209
210bool PlaceholderCache::CleanAll() const {
211 return dir->GetParentDirectory()->CleanSubdirectoryRecursive(dir->GetName());
212}
213
214std::optional<std::array<u8, 0x10>> PlaceholderCache::GetRightsID(const NcaID& id) const {
215 const auto path = GetRelativePathFromNcaID(id, false, true, false);
216 const auto file = dir->GetFileRelative(path);
217
218 if (file == nullptr)
219 return std::nullopt;
220
221 NCA nca{file};
222
223 if (nca.GetStatus() != Loader::ResultStatus::Success &&
224 nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
225 return std::nullopt;
226 }
227
228 const auto rights_id = nca.GetRightsId();
229 if (rights_id == NcaID{})
230 return std::nullopt;
231
232 return rights_id;
233}
234
235u64 PlaceholderCache::Size(const NcaID& id) const {
236 const auto path = GetRelativePathFromNcaID(id, false, true, false);
237 const auto file = dir->GetFileRelative(path);
238
239 if (file == nullptr)
240 return 0;
241
242 return file->GetSize();
243}
244
245bool PlaceholderCache::SetSize(const NcaID& id, u64 new_size) const {
246 const auto path = GetRelativePathFromNcaID(id, false, true, false);
247 const auto file = dir->GetFileRelative(path);
248
249 if (file == nullptr)
250 return false;
251
252 return file->Resize(new_size);
253}
254
255std::vector<NcaID> PlaceholderCache::List() const {
256 std::vector<NcaID> out;
257 for (const auto& sdir : dir->GetSubdirectories()) {
258 for (const auto& file : sdir->GetFiles()) {
259 const auto name = file->GetName();
260 if (name.length() == 36 && name[32] == '.' && name[33] == 'n' && name[34] == 'c' &&
261 name[35] == 'a') {
262 out.push_back(Common::HexStringToArray<0x10>(name.substr(0, 32)));
263 }
264 }
265 }
266 return out;
267}
268
269NcaID PlaceholderCache::Generate() {
270 std::random_device device;
271 std::mt19937 gen(device());
272 std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
273
274 NcaID out{};
275
276 const auto v1 = distribution(gen);
277 const auto v2 = distribution(gen);
278 std::memcpy(out.data(), &v1, sizeof(u64));
279 std::memcpy(out.data() + sizeof(u64), &v2, sizeof(u64));
280
281 return out;
282}
283
130VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, 284VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
131 std::string_view path) const { 285 std::string_view path) const {
132 const auto file = dir->GetFileRelative(path); 286 const auto file = dir->GetFileRelative(path);
@@ -169,14 +323,18 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
169 323
170VirtualFile RegisteredCache::GetFileAtID(NcaID id) const { 324VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
171 VirtualFile file; 325 VirtualFile file;
172 // Try all four modes of file storage: 326 // Try all five relevant modes of file storage:
173 // (bit 1 = uppercase/lower, bit 0 = within a two-digit dir) 327 // (bit 2 = uppercase/lower, bit 1 = within a two-digit dir, bit 0 = .cnmt suffix)
174 // 00: /000000**/{:032X}.nca 328 // 000: /000000**/{:032X}.nca
175 // 01: /{:032X}.nca 329 // 010: /{:032X}.nca
176 // 10: /000000**/{:032x}.nca 330 // 100: /000000**/{:032x}.nca
177 // 11: /{:032x}.nca 331 // 110: /{:032x}.nca
178 for (u8 i = 0; i < 4; ++i) { 332 // 111: /{:032x}.cnmt.nca
179 const auto path = GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0); 333 for (u8 i = 0; i < 8; ++i) {
334 if ((i % 2) == 1 && i != 7)
335 continue;
336 const auto path =
337 GetRelativePathFromNcaID(id, (i & 0b100) == 0, (i & 0b010) == 0, (i & 0b001) == 0b001);
180 file = OpenFileOrDirectoryConcat(dir, path); 338 file = OpenFileOrDirectoryConcat(dir, path);
181 if (file != nullptr) 339 if (file != nullptr)
182 return file; 340 return file;
@@ -472,7 +630,7 @@ InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFuncti
472 memcpy(id.data(), hash.data(), 16); 630 memcpy(id.data(), hash.data(), 16);
473 } 631 }
474 632
475 std::string path = GetRelativePathFromNcaID(id, false, true); 633 std::string path = GetRelativePathFromNcaID(id, false, true, false);
476 634
477 if (GetFileAtID(id) != nullptr && !overwrite_if_exists) { 635 if (GetFileAtID(id) != nullptr && !overwrite_if_exists) {
478 LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping..."); 636 LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping...");
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index 4398d63e1..d1eec240e 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -25,6 +25,8 @@ enum class NCAContentType : u8;
25enum class TitleType : u8; 25enum class TitleType : u8;
26 26
27struct ContentRecord; 27struct ContentRecord;
28struct MetaRecord;
29class RegisteredCache;
28 30
29using NcaID = std::array<u8, 0x10>; 31using NcaID = std::array<u8, 0x10>;
30using ContentProviderParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; 32using ContentProviderParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
@@ -89,6 +91,27 @@ protected:
89 Core::Crypto::KeyManager keys; 91 Core::Crypto::KeyManager keys;
90}; 92};
91 93
94class PlaceholderCache {
95public:
96 explicit PlaceholderCache(VirtualDir dir);
97
98 bool Create(const NcaID& id, u64 size) const;
99 bool Delete(const NcaID& id) const;
100 bool Exists(const NcaID& id) const;
101 bool Write(const NcaID& id, u64 offset, const std::vector<u8>& data) const;
102 bool Register(RegisteredCache* cache, const NcaID& placeholder, const NcaID& install) const;
103 bool CleanAll() const;
104 std::optional<std::array<u8, 0x10>> GetRightsID(const NcaID& id) const;
105 u64 Size(const NcaID& id) const;
106 bool SetSize(const NcaID& id, u64 new_size) const;
107 std::vector<NcaID> List() const;
108
109 static NcaID Generate();
110
111private:
112 VirtualDir dir;
113};
114
92/* 115/*
93 * A class that catalogues NCAs in the registered directory structure. 116 * A class that catalogues NCAs in the registered directory structure.
94 * Nintendo's registered format follows this structure: 117 * Nintendo's registered format follows this structure:
@@ -103,6 +126,8 @@ protected:
103 * when 4GB splitting can be ignored.) 126 * when 4GB splitting can be ignored.)
104 */ 127 */
105class RegisteredCache : public ContentProvider { 128class RegisteredCache : public ContentProvider {
129 friend class PlaceholderCache;
130
106public: 131public:
107 // Parsing function defines the conversion from raw file to NCA. If there are other steps 132 // Parsing function defines the conversion from raw file to NCA. If there are other steps
108 // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom 133 // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index b2ccb2926..84cd4684c 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -7,6 +7,7 @@
7#include "common/common_types.h" 7#include "common/common_types.h"
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "core/core.h" 9#include "core/core.h"
10#include "core/file_sys/card_image.h"
10#include "core/file_sys/content_archive.h" 11#include "core/file_sys/content_archive.h"
11#include "core/file_sys/nca_metadata.h" 12#include "core/file_sys/nca_metadata.h"
12#include "core/file_sys/patch_manager.h" 13#include "core/file_sys/patch_manager.h"
@@ -34,7 +35,7 @@ void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
34 this->update_raw = std::move(update_raw); 35 this->update_raw = std::move(update_raw);
35} 36}
36 37
37ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() { 38ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() const {
38 if (!updatable) 39 if (!updatable)
39 return MakeResult<VirtualFile>(file); 40 return MakeResult<VirtualFile>(file);
40 41
@@ -43,7 +44,8 @@ ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
43 patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw)); 44 patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
44} 45}
45 46
46ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) { 47ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage,
48 ContentRecordType type) const {
47 std::shared_ptr<NCA> res; 49 std::shared_ptr<NCA> res;
48 50
49 switch (storage) { 51 switch (storage) {
@@ -51,13 +53,17 @@ ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, Conte
51 res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type); 53 res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type);
52 break; 54 break;
53 case StorageId::NandSystem: 55 case StorageId::NandSystem:
54 res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type); 56 res =
57 Core::System::GetInstance().GetFileSystemController().GetSystemNANDContents()->GetEntry(
58 title_id, type);
55 break; 59 break;
56 case StorageId::NandUser: 60 case StorageId::NandUser:
57 res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type); 61 res = Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->GetEntry(
62 title_id, type);
58 break; 63 break;
59 case StorageId::SdCard: 64 case StorageId::SdCard:
60 res = Service::FileSystem::GetSDMCContents()->GetEntry(title_id, type); 65 res = Core::System::GetInstance().GetFileSystemController().GetSDMCContents()->GetEntry(
66 title_id, type);
61 break; 67 break;
62 default: 68 default:
63 UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage)); 69 UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index 7724c0b23..da63a313a 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -33,8 +33,8 @@ public:
33 ~RomFSFactory(); 33 ~RomFSFactory();
34 34
35 void SetPackedUpdate(VirtualFile update_raw); 35 void SetPackedUpdate(VirtualFile update_raw);
36 ResultVal<VirtualFile> OpenCurrentProcess(); 36 ResultVal<VirtualFile> OpenCurrentProcess() const;
37 ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type); 37 ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const;
38 38
39private: 39private:
40 VirtualFile file; 40 VirtualFile file;
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 7974b031d..f77cc02ac 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -15,22 +15,8 @@ namespace FileSys {
15 15
16constexpr char SAVE_DATA_SIZE_FILENAME[] = ".yuzu_save_size"; 16constexpr char SAVE_DATA_SIZE_FILENAME[] = ".yuzu_save_size";
17 17
18std::string SaveDataDescriptor::DebugInfo() const { 18namespace {
19 return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}, " 19void PrintSaveDataDescriptorWarnings(SaveDataDescriptor meta) {
20 "rank={}, index={}]",
21 static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id,
22 static_cast<u8>(rank), index);
23}
24
25SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {
26 // Delete all temporary storages
27 // On hardware, it is expected that temporary storage be empty at first use.
28 dir->DeleteSubdirectoryRecursive("temp");
29}
30
31SaveDataFactory::~SaveDataFactory() = default;
32
33ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) {
34 if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) { 20 if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) {
35 if (meta.zero_1 != 0) { 21 if (meta.zero_1 != 0) {
36 LOG_WARNING(Service_FS, 22 LOG_WARNING(Service_FS,
@@ -65,23 +51,51 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, const SaveDat
65 "non-zero ({:016X}{:016X})", 51 "non-zero ({:016X}{:016X})",
66 meta.user_id[1], meta.user_id[0]); 52 meta.user_id[1], meta.user_id[0]);
67 } 53 }
54}
55} // Anonymous namespace
68 56
69 std::string save_directory = 57std::string SaveDataDescriptor::DebugInfo() const {
70 GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); 58 return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, "
59 "save_id={:016X}, "
60 "rank={}, index={}]",
61 static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id,
62 static_cast<u8>(rank), index);
63}
71 64
72 // TODO(DarkLordZach): Try to not create when opening, there are dedicated create save methods. 65SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {
73 // But, user_ids don't match so this works for now. 66 // Delete all temporary storages
67 // On hardware, it is expected that temporary storage be empty at first use.
68 dir->DeleteSubdirectoryRecursive("temp");
69}
74 70
75 auto out = dir->GetDirectoryRelative(save_directory); 71SaveDataFactory::~SaveDataFactory() = default;
72
73ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space,
74 const SaveDataDescriptor& meta) const {
75 PrintSaveDataDescriptorWarnings(meta);
76
77 const auto save_directory =
78 GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
79
80 auto out = dir->CreateDirectoryRelative(save_directory);
76 81
82 // Return an error if the save data doesn't actually exist.
77 if (out == nullptr) { 83 if (out == nullptr) {
78 // TODO(bunnei): This is a work-around to always create a save data directory if it does not 84 // TODO(DarkLordZach): Find out correct error code.
79 // already exist. This is a hack, as we do not understand yet how this works on hardware. 85 return ResultCode(-1);
80 // Without a save data directory, many games will assert on boot. This should not have any
81 // bad side-effects.
82 out = dir->CreateDirectoryRelative(save_directory);
83 } 86 }
84 87
88 return MakeResult<VirtualDir>(std::move(out));
89}
90
91ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space,
92 const SaveDataDescriptor& meta) const {
93
94 const auto save_directory =
95 GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
96
97 auto out = dir->GetDirectoryRelative(save_directory);
98
85 // Return an error if the save data doesn't actually exist. 99 // Return an error if the save data doesn't actually exist.
86 if (out == nullptr) { 100 if (out == nullptr) {
87 // TODO(Subv): Find out correct error code. 101 // TODO(Subv): Find out correct error code.
@@ -152,7 +166,7 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
152} 166}
153 167
154void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, 168void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
155 SaveDataSize new_value) { 169 SaveDataSize new_value) const {
156 const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0); 170 const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
157 const auto dir = GetOrCreateDirectoryRelative(this->dir, path); 171 const auto dir = GetOrCreateDirectoryRelative(this->dir, path);
158 172
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index b73654571..991e57aa1 100644
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.h
@@ -64,7 +64,8 @@ public:
64 explicit SaveDataFactory(VirtualDir dir); 64 explicit SaveDataFactory(VirtualDir dir);
65 ~SaveDataFactory(); 65 ~SaveDataFactory();
66 66
67 ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta); 67 ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataDescriptor& meta) const;
68 ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) const;
68 69
69 VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const; 70 VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
70 71
@@ -73,7 +74,8 @@ public:
73 u128 user_id, u64 save_id); 74 u128 user_id, u64 save_id);
74 75
75 SaveDataSize ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const; 76 SaveDataSize ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const;
76 void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, SaveDataSize new_value); 77 void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
78 SaveDataSize new_value) const;
77 79
78private: 80private:
79 VirtualDir dir; 81 VirtualDir dir;
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index bd3a57058..5113a1ca6 100644
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -6,6 +6,7 @@
6#include "core/file_sys/registered_cache.h" 6#include "core/file_sys/registered_cache.h"
7#include "core/file_sys/sdmc_factory.h" 7#include "core/file_sys/sdmc_factory.h"
8#include "core/file_sys/xts_archive.h" 8#include "core/file_sys/xts_archive.h"
9#include "core/settings.h"
9 10
10namespace FileSys { 11namespace FileSys {
11 12
@@ -14,16 +15,38 @@ SDMCFactory::SDMCFactory(VirtualDir dir_)
14 GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"), 15 GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"),
15 [](const VirtualFile& file, const NcaID& id) { 16 [](const VirtualFile& file, const NcaID& id) {
16 return NAX{file, id}.GetDecrypted(); 17 return NAX{file, id}.GetDecrypted();
17 })) {} 18 })),
19 placeholder(std::make_unique<PlaceholderCache>(
20 GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/placehld"))) {}
18 21
19SDMCFactory::~SDMCFactory() = default; 22SDMCFactory::~SDMCFactory() = default;
20 23
21ResultVal<VirtualDir> SDMCFactory::Open() { 24ResultVal<VirtualDir> SDMCFactory::Open() const {
22 return MakeResult<VirtualDir>(dir); 25 return MakeResult<VirtualDir>(dir);
23} 26}
24 27
28VirtualDir SDMCFactory::GetSDMCContentDirectory() const {
29 return GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents");
30}
31
25RegisteredCache* SDMCFactory::GetSDMCContents() const { 32RegisteredCache* SDMCFactory::GetSDMCContents() const {
26 return contents.get(); 33 return contents.get();
27} 34}
28 35
36PlaceholderCache* SDMCFactory::GetSDMCPlaceholder() const {
37 return placeholder.get();
38}
39
40VirtualDir SDMCFactory::GetImageDirectory() const {
41 return GetOrCreateDirectoryRelative(dir, "/Nintendo/Album");
42}
43
44u64 SDMCFactory::GetSDMCFreeSpace() const {
45 return GetSDMCTotalSpace() - dir->GetSize();
46}
47
48u64 SDMCFactory::GetSDMCTotalSpace() const {
49 return static_cast<u64>(Settings::values.sdmc_size);
50}
51
29} // namespace FileSys 52} // namespace FileSys
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index 42794ba5b..42dc4e08a 100644
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.h
@@ -11,6 +11,7 @@
11namespace FileSys { 11namespace FileSys {
12 12
13class RegisteredCache; 13class RegisteredCache;
14class PlaceholderCache;
14 15
15/// File system interface to the SDCard archive 16/// File system interface to the SDCard archive
16class SDMCFactory { 17class SDMCFactory {
@@ -18,13 +19,23 @@ public:
18 explicit SDMCFactory(VirtualDir dir); 19 explicit SDMCFactory(VirtualDir dir);
19 ~SDMCFactory(); 20 ~SDMCFactory();
20 21
21 ResultVal<VirtualDir> Open(); 22 ResultVal<VirtualDir> Open() const;
23
24 VirtualDir GetSDMCContentDirectory() const;
25
22 RegisteredCache* GetSDMCContents() const; 26 RegisteredCache* GetSDMCContents() const;
27 PlaceholderCache* GetSDMCPlaceholder() const;
28
29 VirtualDir GetImageDirectory() const;
30
31 u64 GetSDMCFreeSpace() const;
32 u64 GetSDMCTotalSpace() const;
23 33
24private: 34private:
25 VirtualDir dir; 35 VirtualDir dir;
26 36
27 std::unique_ptr<RegisteredCache> contents; 37 std::unique_ptr<RegisteredCache> contents;
38 std::unique_ptr<PlaceholderCache> placeholder;
28}; 39};
29 40
30} // namespace FileSys 41} // namespace FileSys
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index 8b3b14e25..ef3084681 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -14,6 +14,7 @@
14#include "core/file_sys/content_archive.h" 14#include "core/file_sys/content_archive.h"
15#include "core/file_sys/nca_metadata.h" 15#include "core/file_sys/nca_metadata.h"
16#include "core/file_sys/partition_filesystem.h" 16#include "core/file_sys/partition_filesystem.h"
17#include "core/file_sys/program_metadata.h"
17#include "core/file_sys/submission_package.h" 18#include "core/file_sys/submission_package.h"
18#include "core/loader/loader.h" 19#include "core/loader/loader.h"
19 20
@@ -78,6 +79,10 @@ Loader::ResultStatus NSP::GetStatus() const {
78} 79}
79 80
80Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const { 81Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const {
82 if (IsExtractedType() && GetExeFS() != nullptr && FileSys::IsDirectoryExeFS(GetExeFS())) {
83 return Loader::ResultStatus::Success;
84 }
85
81 const auto iter = program_status.find(title_id); 86 const auto iter = program_status.find(title_id);
82 if (iter == program_status.end()) 87 if (iter == program_status.end())
83 return Loader::ResultStatus::ErrorNSPMissingProgramNCA; 88 return Loader::ResultStatus::ErrorNSPMissingProgramNCA;
@@ -85,12 +90,29 @@ Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const {
85} 90}
86 91
87u64 NSP::GetFirstTitleID() const { 92u64 NSP::GetFirstTitleID() const {
93 if (IsExtractedType()) {
94 return GetProgramTitleID();
95 }
96
88 if (program_status.empty()) 97 if (program_status.empty())
89 return 0; 98 return 0;
90 return program_status.begin()->first; 99 return program_status.begin()->first;
91} 100}
92 101
93u64 NSP::GetProgramTitleID() const { 102u64 NSP::GetProgramTitleID() const {
103 if (IsExtractedType()) {
104 if (GetExeFS() == nullptr || !IsDirectoryExeFS(GetExeFS())) {
105 return 0;
106 }
107
108 ProgramMetadata meta;
109 if (meta.Load(GetExeFS()->GetFile("main.npdm")) == Loader::ResultStatus::Success) {
110 return meta.GetTitleID();
111 } else {
112 return 0;
113 }
114 }
115
94 const auto out = GetFirstTitleID(); 116 const auto out = GetFirstTitleID();
95 if ((out & 0x800) == 0) 117 if ((out & 0x800) == 0)
96 return out; 118 return out;
@@ -102,6 +124,10 @@ u64 NSP::GetProgramTitleID() const {
102} 124}
103 125
104std::vector<u64> NSP::GetTitleIDs() const { 126std::vector<u64> NSP::GetTitleIDs() const {
127 if (IsExtractedType()) {
128 return {GetProgramTitleID()};
129 }
130
105 std::vector<u64> out; 131 std::vector<u64> out;
106 out.reserve(ncas.size()); 132 out.reserve(ncas.size());
107 for (const auto& kv : ncas) 133 for (const auto& kv : ncas)
@@ -222,7 +248,8 @@ void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
222 248
223void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { 249void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
224 for (const auto& outer_file : files) { 250 for (const auto& outer_file : files) {
225 if (outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") { 251 if (outer_file->GetName().size() < 9 ||
252 outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") {
226 continue; 253 continue;
227 } 254 }
228 255
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index aa2c83937..6c594dcaf 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -1143,13 +1143,21 @@ void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(
1143 1143
1144void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) { 1144void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) {
1145 IPC::RequestParser rp{ctx}; 1145 IPC::RequestParser rp{ctx};
1146 u128 uid = rp.PopRaw<u128>(); // What does this do? 1146 u128 user_id = rp.PopRaw<u128>();
1147 LOG_WARNING(Service, "(STUBBED) called uid = {:016X}{:016X}", uid[1], uid[0]); 1147
1148 LOG_DEBUG(Service_AM, "called, uid={:016X}{:016X}", user_id[1], user_id[0]);
1149
1150 FileSys::SaveDataDescriptor descriptor{};
1151 descriptor.title_id = Core::CurrentProcess()->GetTitleID();
1152 descriptor.user_id = user_id;
1153 descriptor.type = FileSys::SaveDataType::SaveData;
1154 const auto res = system.GetFileSystemController().CreateSaveData(
1155 FileSys::SaveDataSpaceId::NandUser, descriptor);
1148 1156
1149 IPC::ResponseBuilder rb{ctx, 4}; 1157 IPC::ResponseBuilder rb{ctx, 4};
1150 rb.Push(RESULT_SUCCESS); 1158 rb.Push(res.Code());
1151 rb.Push<u64>(0); 1159 rb.Push<u64>(0);
1152} // namespace Service::AM 1160}
1153 1161
1154void IApplicationFunctions::SetTerminateResult(Kernel::HLERequestContext& ctx) { 1162void IApplicationFunctions::SetTerminateResult(Kernel::HLERequestContext& ctx) {
1155 // Takes an input u32 Result, no output. 1163 // Takes an input u32 Result, no output.
@@ -1261,8 +1269,8 @@ void IApplicationFunctions::ExtendSaveData(Kernel::HLERequestContext& ctx) {
1261 "new_journal={:016X}", 1269 "new_journal={:016X}",
1262 static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size); 1270 static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size);
1263 1271
1264 const auto title_id = system.CurrentProcess()->GetTitleID(); 1272 system.GetFileSystemController().WriteSaveDataSize(
1265 FileSystem::WriteSaveDataSize(type, title_id, user_id, {new_normal_size, new_journal_size}); 1273 type, system.CurrentProcess()->GetTitleID(), user_id, {new_normal_size, new_journal_size});
1266 1274
1267 IPC::ResponseBuilder rb{ctx, 4}; 1275 IPC::ResponseBuilder rb{ctx, 4};
1268 rb.Push(RESULT_SUCCESS); 1276 rb.Push(RESULT_SUCCESS);
@@ -1281,8 +1289,8 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) {
1281 LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type), 1289 LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type),
1282 user_id[1], user_id[0]); 1290 user_id[1], user_id[0]);
1283 1291
1284 const auto title_id = system.CurrentProcess()->GetTitleID(); 1292 const auto size = system.GetFileSystemController().ReadSaveDataSize(
1285 const auto size = FileSystem::ReadSaveDataSize(type, title_id, user_id); 1293 type, system.CurrentProcess()->GetTitleID(), user_id);
1286 1294
1287 IPC::ResponseBuilder rb{ctx, 6}; 1295 IPC::ResponseBuilder rb{ctx, 6};
1288 rb.Push(RESULT_SUCCESS); 1296 rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h
index 9e006cd9d..0e0d10858 100644
--- a/src/core/hle/service/am/applet_ae.h
+++ b/src/core/hle/service/am/applet_ae.h
@@ -9,6 +9,10 @@
9#include "core/hle/service/service.h" 9#include "core/hle/service/service.h"
10 10
11namespace Service { 11namespace Service {
12namespace FileSystem {
13class FileSystemController;
14}
15
12namespace NVFlinger { 16namespace NVFlinger {
13class NVFlinger; 17class NVFlinger;
14} 18}
diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h
index 22c05419d..99a65e7b5 100644
--- a/src/core/hle/service/am/applet_oe.h
+++ b/src/core/hle/service/am/applet_oe.h
@@ -9,6 +9,10 @@
9#include "core/hle/service/service.h" 9#include "core/hle/service/service.h"
10 10
11namespace Service { 11namespace Service {
12namespace FileSystem {
13class FileSystemController;
14}
15
12namespace NVFlinger { 16namespace NVFlinger {
13class NVFlinger; 17class NVFlinger;
14} 18}
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 8ce110dd1..14cd0e322 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -8,6 +8,7 @@
8#include "common/file_util.h" 8#include "common/file_util.h"
9#include "core/core.h" 9#include "core/core.h"
10#include "core/file_sys/bis_factory.h" 10#include "core/file_sys/bis_factory.h"
11#include "core/file_sys/card_image.h"
11#include "core/file_sys/control_metadata.h" 12#include "core/file_sys/control_metadata.h"
12#include "core/file_sys/errors.h" 13#include "core/file_sys/errors.h"
13#include "core/file_sys/mode.h" 14#include "core/file_sys/mode.h"
@@ -25,14 +26,10 @@
25#include "core/hle/service/filesystem/fsp_pr.h" 26#include "core/hle/service/filesystem/fsp_pr.h"
26#include "core/hle/service/filesystem/fsp_srv.h" 27#include "core/hle/service/filesystem/fsp_srv.h"
27#include "core/loader/loader.h" 28#include "core/loader/loader.h"
29#include "core/settings.h"
28 30
29namespace Service::FileSystem { 31namespace Service::FileSystem {
30 32
31// Size of emulated sd card free space, reported in bytes.
32// Just using 32GB because thats reasonable
33// TODO(DarkLordZach): Eventually make this configurable in settings.
34constexpr u64 EMULATED_SD_REPORTED_SIZE = 32000000000;
35
36// A default size for normal/journal save data size if application control metadata cannot be found. 33// A default size for normal/journal save data size if application control metadata cannot be found.
37// This should be large enough to satisfy even the most extreme requirements (~4.2GB) 34// This should be large enough to satisfy even the most extreme requirements (~4.2GB)
38constexpr u64 SUFFICIENT_SAVE_DATA_SIZE = 0xF0000000; 35constexpr u64 SUFFICIENT_SAVE_DATA_SIZE = 0xF0000000;
@@ -226,13 +223,6 @@ ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const s
226 return MakeResult(dir); 223 return MakeResult(dir);
227} 224}
228 225
229u64 VfsDirectoryServiceWrapper::GetFreeSpaceSize() const {
230 if (backing->IsWritable())
231 return EMULATED_SD_REPORTED_SIZE;
232
233 return 0;
234}
235
236ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType( 226ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
237 const std::string& path_) const { 227 const std::string& path_) const {
238 std::string path(FileUtil::SanitizePath(path_)); 228 std::string path(FileUtil::SanitizePath(path_));
@@ -251,44 +241,39 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
251 return FileSys::ERROR_PATH_NOT_FOUND; 241 return FileSys::ERROR_PATH_NOT_FOUND;
252} 242}
253 243
254/** 244FileSystemController::FileSystemController() = default;
255 * Map of registered file systems, identified by type. Once an file system is registered here, it 245
256 * is never removed until UnregisterFileSystems is called. 246FileSystemController::~FileSystemController() = default;
257 */
258static std::unique_ptr<FileSys::RomFSFactory> romfs_factory;
259static std::unique_ptr<FileSys::SaveDataFactory> save_data_factory;
260static std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
261static std::unique_ptr<FileSys::BISFactory> bis_factory;
262 247
263ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) { 248ResultCode FileSystemController::RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
264 ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second RomFS");
265 romfs_factory = std::move(factory); 249 romfs_factory = std::move(factory);
266 LOG_DEBUG(Service_FS, "Registered RomFS"); 250 LOG_DEBUG(Service_FS, "Registered RomFS");
267 return RESULT_SUCCESS; 251 return RESULT_SUCCESS;
268} 252}
269 253
270ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory) { 254ResultCode FileSystemController::RegisterSaveData(
271 ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second save data"); 255 std::unique_ptr<FileSys::SaveDataFactory>&& factory) {
256 ASSERT_MSG(save_data_factory == nullptr, "Tried to register a second save data");
272 save_data_factory = std::move(factory); 257 save_data_factory = std::move(factory);
273 LOG_DEBUG(Service_FS, "Registered save data"); 258 LOG_DEBUG(Service_FS, "Registered save data");
274 return RESULT_SUCCESS; 259 return RESULT_SUCCESS;
275} 260}
276 261
277ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) { 262ResultCode FileSystemController::RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
278 ASSERT_MSG(sdmc_factory == nullptr, "Tried to register a second SDMC"); 263 ASSERT_MSG(sdmc_factory == nullptr, "Tried to register a second SDMC");
279 sdmc_factory = std::move(factory); 264 sdmc_factory = std::move(factory);
280 LOG_DEBUG(Service_FS, "Registered SDMC"); 265 LOG_DEBUG(Service_FS, "Registered SDMC");
281 return RESULT_SUCCESS; 266 return RESULT_SUCCESS;
282} 267}
283 268
284ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) { 269ResultCode FileSystemController::RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
285 ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS"); 270 ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS");
286 bis_factory = std::move(factory); 271 bis_factory = std::move(factory);
287 LOG_DEBUG(Service_FS, "Registered BIS"); 272 LOG_DEBUG(Service_FS, "Registered BIS");
288 return RESULT_SUCCESS; 273 return RESULT_SUCCESS;
289} 274}
290 275
291void SetPackedUpdate(FileSys::VirtualFile update_raw) { 276void FileSystemController::SetPackedUpdate(FileSys::VirtualFile update_raw) {
292 LOG_TRACE(Service_FS, "Setting packed update for romfs"); 277 LOG_TRACE(Service_FS, "Setting packed update for romfs");
293 278
294 if (romfs_factory == nullptr) 279 if (romfs_factory == nullptr)
@@ -297,7 +282,7 @@ void SetPackedUpdate(FileSys::VirtualFile update_raw) {
297 romfs_factory->SetPackedUpdate(std::move(update_raw)); 282 romfs_factory->SetPackedUpdate(std::move(update_raw));
298} 283}
299 284
300ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() { 285ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess() const {
301 LOG_TRACE(Service_FS, "Opening RomFS for current process"); 286 LOG_TRACE(Service_FS, "Opening RomFS for current process");
302 287
303 if (romfs_factory == nullptr) { 288 if (romfs_factory == nullptr) {
@@ -308,8 +293,8 @@ ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() {
308 return romfs_factory->OpenCurrentProcess(); 293 return romfs_factory->OpenCurrentProcess();
309} 294}
310 295
311ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id, 296ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS(
312 FileSys::ContentRecordType type) { 297 u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const {
313 LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}", 298 LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}",
314 title_id, static_cast<u8>(storage_id), static_cast<u8>(type)); 299 title_id, static_cast<u8>(storage_id), static_cast<u8>(type));
315 300
@@ -321,8 +306,20 @@ ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId stora
321 return romfs_factory->Open(title_id, storage_id, type); 306 return romfs_factory->Open(title_id, storage_id, type);
322} 307}
323 308
324ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, 309ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData(
325 const FileSys::SaveDataDescriptor& descriptor) { 310 FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const {
311 LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}",
312 static_cast<u8>(space), save_struct.DebugInfo());
313
314 if (save_data_factory == nullptr) {
315 return FileSys::ERROR_ENTITY_NOT_FOUND;
316 }
317
318 return save_data_factory->Create(space, save_struct);
319}
320
321ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData(
322 FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& descriptor) const {
326 LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", 323 LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}",
327 static_cast<u8>(space), descriptor.DebugInfo()); 324 static_cast<u8>(space), descriptor.DebugInfo());
328 325
@@ -333,7 +330,8 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
333 return save_data_factory->Open(space, descriptor); 330 return save_data_factory->Open(space, descriptor);
334} 331}
335 332
336ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) { 333ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveDataSpace(
334 FileSys::SaveDataSpaceId space) const {
337 LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", static_cast<u8>(space)); 335 LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", static_cast<u8>(space));
338 336
339 if (save_data_factory == nullptr) { 337 if (save_data_factory == nullptr) {
@@ -343,7 +341,7 @@ ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space)
343 return MakeResult(save_data_factory->GetSaveDataSpaceDirectory(space)); 341 return MakeResult(save_data_factory->GetSaveDataSpaceDirectory(space));
344} 342}
345 343
346ResultVal<FileSys::VirtualDir> OpenSDMC() { 344ResultVal<FileSys::VirtualDir> FileSystemController::OpenSDMC() const {
347 LOG_TRACE(Service_FS, "Opening SDMC"); 345 LOG_TRACE(Service_FS, "Opening SDMC");
348 346
349 if (sdmc_factory == nullptr) { 347 if (sdmc_factory == nullptr) {
@@ -353,7 +351,92 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
353 return sdmc_factory->Open(); 351 return sdmc_factory->Open();
354} 352}
355 353
356FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id) { 354ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition(
355 FileSys::BisPartitionId id) const {
356 LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", static_cast<u32>(id));
357
358 if (bis_factory == nullptr) {
359 return FileSys::ERROR_ENTITY_NOT_FOUND;
360 }
361
362 auto part = bis_factory->OpenPartition(id);
363 if (part == nullptr) {
364 return FileSys::ERROR_INVALID_ARGUMENT;
365 }
366
367 return MakeResult<FileSys::VirtualDir>(std::move(part));
368}
369
370ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage(
371 FileSys::BisPartitionId id) const {
372 LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", static_cast<u32>(id));
373
374 if (bis_factory == nullptr) {
375 return FileSys::ERROR_ENTITY_NOT_FOUND;
376 }
377
378 auto part = bis_factory->OpenPartitionStorage(id);
379 if (part == nullptr) {
380 return FileSys::ERROR_INVALID_ARGUMENT;
381 }
382
383 return MakeResult<FileSys::VirtualFile>(std::move(part));
384}
385
386u64 FileSystemController::GetFreeSpaceSize(FileSys::StorageId id) const {
387 switch (id) {
388 case FileSys::StorageId::None:
389 case FileSys::StorageId::GameCard:
390 return 0;
391 case FileSys::StorageId::SdCard:
392 if (sdmc_factory == nullptr)
393 return 0;
394 return sdmc_factory->GetSDMCFreeSpace();
395 case FileSys::StorageId::Host:
396 if (bis_factory == nullptr)
397 return 0;
398 return bis_factory->GetSystemNANDFreeSpace() + bis_factory->GetUserNANDFreeSpace();
399 case FileSys::StorageId::NandSystem:
400 if (bis_factory == nullptr)
401 return 0;
402 return bis_factory->GetSystemNANDFreeSpace();
403 case FileSys::StorageId::NandUser:
404 if (bis_factory == nullptr)
405 return 0;
406 return bis_factory->GetUserNANDFreeSpace();
407 }
408
409 return 0;
410}
411
412u64 FileSystemController::GetTotalSpaceSize(FileSys::StorageId id) const {
413 switch (id) {
414 case FileSys::StorageId::None:
415 case FileSys::StorageId::GameCard:
416 return 0;
417 case FileSys::StorageId::SdCard:
418 if (sdmc_factory == nullptr)
419 return 0;
420 return sdmc_factory->GetSDMCTotalSpace();
421 case FileSys::StorageId::Host:
422 if (bis_factory == nullptr)
423 return 0;
424 return bis_factory->GetFullNANDTotalSpace();
425 case FileSys::StorageId::NandSystem:
426 if (bis_factory == nullptr)
427 return 0;
428 return bis_factory->GetSystemNANDTotalSpace();
429 case FileSys::StorageId::NandUser:
430 if (bis_factory == nullptr)
431 return 0;
432 return bis_factory->GetUserNANDTotalSpace();
433 }
434
435 return 0;
436}
437
438FileSys::SaveDataSize FileSystemController::ReadSaveDataSize(FileSys::SaveDataType type,
439 u64 title_id, u128 user_id) const {
357 if (save_data_factory == nullptr) { 440 if (save_data_factory == nullptr) {
358 return {0, 0}; 441 return {0, 0};
359 } 442 }
@@ -385,13 +468,32 @@ FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id,
385 return value; 468 return value;
386} 469}
387 470
388void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id, 471void FileSystemController::WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
389 FileSys::SaveDataSize new_value) { 472 FileSys::SaveDataSize new_value) const {
390 if (save_data_factory != nullptr) 473 if (save_data_factory != nullptr)
391 save_data_factory->WriteSaveDataSize(type, title_id, user_id, new_value); 474 save_data_factory->WriteSaveDataSize(type, title_id, user_id, new_value);
392} 475}
393 476
394FileSys::RegisteredCache* GetSystemNANDContents() { 477void FileSystemController::SetGameCard(FileSys::VirtualFile file) {
478 gamecard = std::make_unique<FileSys::XCI>(file);
479 const auto dir = gamecard->ConcatenatedPseudoDirectory();
480 gamecard_registered = std::make_unique<FileSys::RegisteredCache>(dir);
481 gamecard_placeholder = std::make_unique<FileSys::PlaceholderCache>(dir);
482}
483
484FileSys::XCI* FileSystemController::GetGameCard() const {
485 return gamecard.get();
486}
487
488FileSys::RegisteredCache* FileSystemController::GetGameCardContents() const {
489 return gamecard_registered.get();
490}
491
492FileSys::PlaceholderCache* FileSystemController::GetGameCardPlaceholder() const {
493 return gamecard_placeholder.get();
494}
495
496FileSys::RegisteredCache* FileSystemController::GetSystemNANDContents() const {
395 LOG_TRACE(Service_FS, "Opening System NAND Contents"); 497 LOG_TRACE(Service_FS, "Opening System NAND Contents");
396 498
397 if (bis_factory == nullptr) 499 if (bis_factory == nullptr)
@@ -400,7 +502,7 @@ FileSys::RegisteredCache* GetSystemNANDContents() {
400 return bis_factory->GetSystemNANDContents(); 502 return bis_factory->GetSystemNANDContents();
401} 503}
402 504
403FileSys::RegisteredCache* GetUserNANDContents() { 505FileSys::RegisteredCache* FileSystemController::GetUserNANDContents() const {
404 LOG_TRACE(Service_FS, "Opening User NAND Contents"); 506 LOG_TRACE(Service_FS, "Opening User NAND Contents");
405 507
406 if (bis_factory == nullptr) 508 if (bis_factory == nullptr)
@@ -409,7 +511,7 @@ FileSys::RegisteredCache* GetUserNANDContents() {
409 return bis_factory->GetUserNANDContents(); 511 return bis_factory->GetUserNANDContents();
410} 512}
411 513
412FileSys::RegisteredCache* GetSDMCContents() { 514FileSys::RegisteredCache* FileSystemController::GetSDMCContents() const {
413 LOG_TRACE(Service_FS, "Opening SDMC Contents"); 515 LOG_TRACE(Service_FS, "Opening SDMC Contents");
414 516
415 if (sdmc_factory == nullptr) 517 if (sdmc_factory == nullptr)
@@ -418,7 +520,143 @@ FileSys::RegisteredCache* GetSDMCContents() {
418 return sdmc_factory->GetSDMCContents(); 520 return sdmc_factory->GetSDMCContents();
419} 521}
420 522
421FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) { 523FileSys::PlaceholderCache* FileSystemController::GetSystemNANDPlaceholder() const {
524 LOG_TRACE(Service_FS, "Opening System NAND Placeholder");
525
526 if (bis_factory == nullptr)
527 return nullptr;
528
529 return bis_factory->GetSystemNANDPlaceholder();
530}
531
532FileSys::PlaceholderCache* FileSystemController::GetUserNANDPlaceholder() const {
533 LOG_TRACE(Service_FS, "Opening User NAND Placeholder");
534
535 if (bis_factory == nullptr)
536 return nullptr;
537
538 return bis_factory->GetUserNANDPlaceholder();
539}
540
541FileSys::PlaceholderCache* FileSystemController::GetSDMCPlaceholder() const {
542 LOG_TRACE(Service_FS, "Opening SDMC Placeholder");
543
544 if (sdmc_factory == nullptr)
545 return nullptr;
546
547 return sdmc_factory->GetSDMCPlaceholder();
548}
549
550FileSys::RegisteredCache* FileSystemController::GetRegisteredCacheForStorage(
551 FileSys::StorageId id) const {
552 switch (id) {
553 case FileSys::StorageId::None:
554 case FileSys::StorageId::Host:
555 UNIMPLEMENTED();
556 return nullptr;
557 case FileSys::StorageId::GameCard:
558 return GetGameCardContents();
559 case FileSys::StorageId::NandSystem:
560 return GetSystemNANDContents();
561 case FileSys::StorageId::NandUser:
562 return GetUserNANDContents();
563 case FileSys::StorageId::SdCard:
564 return GetSDMCContents();
565 }
566
567 return nullptr;
568}
569
570FileSys::PlaceholderCache* FileSystemController::GetPlaceholderCacheForStorage(
571 FileSys::StorageId id) const {
572 switch (id) {
573 case FileSys::StorageId::None:
574 case FileSys::StorageId::Host:
575 UNIMPLEMENTED();
576 return nullptr;
577 case FileSys::StorageId::GameCard:
578 return GetGameCardPlaceholder();
579 case FileSys::StorageId::NandSystem:
580 return GetSystemNANDPlaceholder();
581 case FileSys::StorageId::NandUser:
582 return GetUserNANDPlaceholder();
583 case FileSys::StorageId::SdCard:
584 return GetSDMCPlaceholder();
585 }
586
587 return nullptr;
588}
589
590FileSys::VirtualDir FileSystemController::GetSystemNANDContentDirectory() const {
591 LOG_TRACE(Service_FS, "Opening system NAND content directory");
592
593 if (bis_factory == nullptr)
594 return nullptr;
595
596 return bis_factory->GetSystemNANDContentDirectory();
597}
598
599FileSys::VirtualDir FileSystemController::GetUserNANDContentDirectory() const {
600 LOG_TRACE(Service_FS, "Opening user NAND content directory");
601
602 if (bis_factory == nullptr)
603 return nullptr;
604
605 return bis_factory->GetUserNANDContentDirectory();
606}
607
608FileSys::VirtualDir FileSystemController::GetSDMCContentDirectory() const {
609 LOG_TRACE(Service_FS, "Opening SDMC content directory");
610
611 if (sdmc_factory == nullptr)
612 return nullptr;
613
614 return sdmc_factory->GetSDMCContentDirectory();
615}
616
617FileSys::VirtualDir FileSystemController::GetNANDImageDirectory() const {
618 LOG_TRACE(Service_FS, "Opening NAND image directory");
619
620 if (bis_factory == nullptr)
621 return nullptr;
622
623 return bis_factory->GetImageDirectory();
624}
625
626FileSys::VirtualDir FileSystemController::GetSDMCImageDirectory() const {
627 LOG_TRACE(Service_FS, "Opening SDMC image directory");
628
629 if (sdmc_factory == nullptr)
630 return nullptr;
631
632 return sdmc_factory->GetImageDirectory();
633}
634
635FileSys::VirtualDir FileSystemController::GetContentDirectory(ContentStorageId id) const {
636 switch (id) {
637 case ContentStorageId::System:
638 return GetSystemNANDContentDirectory();
639 case ContentStorageId::User:
640 return GetUserNANDContentDirectory();
641 case ContentStorageId::SdCard:
642 return GetSDMCContentDirectory();
643 }
644
645 return nullptr;
646}
647
648FileSys::VirtualDir FileSystemController::GetImageDirectory(ImageDirectoryId id) const {
649 switch (id) {
650 case ImageDirectoryId::NAND:
651 return GetNANDImageDirectory();
652 case ImageDirectoryId::SdCard:
653 return GetSDMCImageDirectory();
654 }
655
656 return nullptr;
657}
658
659FileSys::VirtualDir FileSystemController::GetModificationLoadRoot(u64 title_id) const {
422 LOG_TRACE(Service_FS, "Opening mod load root for tid={:016X}", title_id); 660 LOG_TRACE(Service_FS, "Opening mod load root for tid={:016X}", title_id);
423 661
424 if (bis_factory == nullptr) 662 if (bis_factory == nullptr)
@@ -427,7 +665,7 @@ FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) {
427 return bis_factory->GetModificationLoadRoot(title_id); 665 return bis_factory->GetModificationLoadRoot(title_id);
428} 666}
429 667
430FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) { 668FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id) const {
431 LOG_TRACE(Service_FS, "Opening mod dump root for tid={:016X}", title_id); 669 LOG_TRACE(Service_FS, "Opening mod dump root for tid={:016X}", title_id);
432 670
433 if (bis_factory == nullptr) 671 if (bis_factory == nullptr)
@@ -436,7 +674,7 @@ FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) {
436 return bis_factory->GetModificationDumpRoot(title_id); 674 return bis_factory->GetModificationDumpRoot(title_id);
437} 675}
438 676
439void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) { 677void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
440 if (overwrite) { 678 if (overwrite) {
441 bis_factory = nullptr; 679 bis_factory = nullptr;
442 save_data_factory = nullptr; 680 save_data_factory = nullptr;
@@ -473,11 +711,10 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
473} 711}
474 712
475void InstallInterfaces(Core::System& system) { 713void InstallInterfaces(Core::System& system) {
476 romfs_factory = nullptr;
477 CreateFactories(*system.GetFilesystem(), false);
478 std::make_shared<FSP_LDR>()->InstallAsService(system.ServiceManager()); 714 std::make_shared<FSP_LDR>()->InstallAsService(system.ServiceManager());
479 std::make_shared<FSP_PR>()->InstallAsService(system.ServiceManager()); 715 std::make_shared<FSP_PR>()->InstallAsService(system.ServiceManager());
480 std::make_shared<FSP_SRV>(system.GetReporter())->InstallAsService(system.ServiceManager()); 716 std::make_shared<FSP_SRV>(system.GetFileSystemController(), system.GetReporter())
717 ->InstallAsService(system.ServiceManager());
481} 718}
482 719
483} // namespace Service::FileSystem 720} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 3849dd89e..3e0c03ec0 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -14,10 +14,13 @@ namespace FileSys {
14class BISFactory; 14class BISFactory;
15class RegisteredCache; 15class RegisteredCache;
16class RegisteredCacheUnion; 16class RegisteredCacheUnion;
17class PlaceholderCache;
17class RomFSFactory; 18class RomFSFactory;
18class SaveDataFactory; 19class SaveDataFactory;
19class SDMCFactory; 20class SDMCFactory;
21class XCI;
20 22
23enum class BisPartitionId : u32;
21enum class ContentRecordType : u8; 24enum class ContentRecordType : u8;
22enum class Mode : u32; 25enum class Mode : u32;
23enum class SaveDataSpaceId : u8; 26enum class SaveDataSpaceId : u8;
@@ -36,34 +39,91 @@ class ServiceManager;
36 39
37namespace FileSystem { 40namespace FileSystem {
38 41
39ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory); 42enum class ContentStorageId : u32 {
40ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory); 43 System,
41ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory); 44 User,
42ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory); 45 SdCard,
43 46};
44void SetPackedUpdate(FileSys::VirtualFile update_raw);
45ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess();
46ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
47 FileSys::ContentRecordType type);
48ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
49 const FileSys::SaveDataDescriptor& descriptor);
50ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space);
51ResultVal<FileSys::VirtualDir> OpenSDMC();
52
53FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id);
54void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
55 FileSys::SaveDataSize new_value);
56 47
57FileSys::RegisteredCache* GetSystemNANDContents(); 48enum class ImageDirectoryId : u32 {
58FileSys::RegisteredCache* GetUserNANDContents(); 49 NAND,
59FileSys::RegisteredCache* GetSDMCContents(); 50 SdCard,
51};
60 52
61FileSys::VirtualDir GetModificationLoadRoot(u64 title_id); 53class FileSystemController {
62FileSys::VirtualDir GetModificationDumpRoot(u64 title_id); 54public:
55 FileSystemController();
56 ~FileSystemController();
57
58 ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
59 ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory);
60 ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
61 ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
62
63 void SetPackedUpdate(FileSys::VirtualFile update_raw);
64 ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() const;
65 ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
66 FileSys::ContentRecordType type) const;
67 ResultVal<FileSys::VirtualDir> CreateSaveData(
68 FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const;
69 ResultVal<FileSys::VirtualDir> OpenSaveData(
70 FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const;
71 ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) const;
72 ResultVal<FileSys::VirtualDir> OpenSDMC() const;
73 ResultVal<FileSys::VirtualDir> OpenBISPartition(FileSys::BisPartitionId id) const;
74 ResultVal<FileSys::VirtualFile> OpenBISPartitionStorage(FileSys::BisPartitionId id) const;
75
76 u64 GetFreeSpaceSize(FileSys::StorageId id) const;
77 u64 GetTotalSpaceSize(FileSys::StorageId id) const;
78
79 FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id,
80 u128 user_id) const;
81 void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
82 FileSys::SaveDataSize new_value) const;
83
84 void SetGameCard(FileSys::VirtualFile file);
85 FileSys::XCI* GetGameCard() const;
86
87 FileSys::RegisteredCache* GetSystemNANDContents() const;
88 FileSys::RegisteredCache* GetUserNANDContents() const;
89 FileSys::RegisteredCache* GetSDMCContents() const;
90 FileSys::RegisteredCache* GetGameCardContents() const;
91
92 FileSys::PlaceholderCache* GetSystemNANDPlaceholder() const;
93 FileSys::PlaceholderCache* GetUserNANDPlaceholder() const;
94 FileSys::PlaceholderCache* GetSDMCPlaceholder() const;
95 FileSys::PlaceholderCache* GetGameCardPlaceholder() const;
96
97 FileSys::RegisteredCache* GetRegisteredCacheForStorage(FileSys::StorageId id) const;
98 FileSys::PlaceholderCache* GetPlaceholderCacheForStorage(FileSys::StorageId id) const;
99
100 FileSys::VirtualDir GetSystemNANDContentDirectory() const;
101 FileSys::VirtualDir GetUserNANDContentDirectory() const;
102 FileSys::VirtualDir GetSDMCContentDirectory() const;
103
104 FileSys::VirtualDir GetNANDImageDirectory() const;
105 FileSys::VirtualDir GetSDMCImageDirectory() const;
106
107 FileSys::VirtualDir GetContentDirectory(ContentStorageId id) const;
108 FileSys::VirtualDir GetImageDirectory(ImageDirectoryId id) const;
109
110 FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const;
111 FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const;
112
113 // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
114 // above is called.
115 void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
63 116
64// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function 117private:
65// above is called. 118 std::unique_ptr<FileSys::RomFSFactory> romfs_factory;
66void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true); 119 std::unique_ptr<FileSys::SaveDataFactory> save_data_factory;
120 std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
121 std::unique_ptr<FileSys::BISFactory> bis_factory;
122
123 std::unique_ptr<FileSys::XCI> gamecard;
124 std::unique_ptr<FileSys::RegisteredCache> gamecard_registered;
125 std::unique_ptr<FileSys::PlaceholderCache> gamecard_placeholder;
126};
67 127
68void InstallInterfaces(Core::System& system); 128void InstallInterfaces(Core::System& system);
69 129
@@ -160,12 +220,6 @@ public:
160 ResultVal<FileSys::VirtualDir> OpenDirectory(const std::string& path); 220 ResultVal<FileSys::VirtualDir> OpenDirectory(const std::string& path);
161 221
162 /** 222 /**
163 * Get the free space
164 * @return The number of free bytes in the archive
165 */
166 u64 GetFreeSpaceSize() const;
167
168 /**
169 * Get the type of the specified path 223 * Get the type of the specified path
170 * @return The type of the specified path or error code 224 * @return The type of the specified path or error code
171 */ 225 */
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index d3cd46a9b..eb982ad49 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -19,6 +19,7 @@
19#include "core/file_sys/mode.h" 19#include "core/file_sys/mode.h"
20#include "core/file_sys/nca_metadata.h" 20#include "core/file_sys/nca_metadata.h"
21#include "core/file_sys/patch_manager.h" 21#include "core/file_sys/patch_manager.h"
22#include "core/file_sys/romfs_factory.h"
22#include "core/file_sys/savedata_factory.h" 23#include "core/file_sys/savedata_factory.h"
23#include "core/file_sys/system_archive/system_archive.h" 24#include "core/file_sys/system_archive/system_archive.h"
24#include "core/file_sys/vfs.h" 25#include "core/file_sys/vfs.h"
@@ -30,6 +31,18 @@
30 31
31namespace Service::FileSystem { 32namespace Service::FileSystem {
32 33
34struct SizeGetter {
35 std::function<u64()> get_free_size;
36 std::function<u64()> get_total_size;
37
38 static SizeGetter FromStorageId(const FileSystemController& fsc, FileSys::StorageId id) {
39 return {
40 [&fsc, id] { return fsc.GetFreeSpaceSize(id); },
41 [&fsc, id] { return fsc.GetTotalSpaceSize(id); },
42 };
43 }
44};
45
33enum class FileSystemType : u8 { 46enum class FileSystemType : u8 {
34 Invalid0 = 0, 47 Invalid0 = 0,
35 Invalid1 = 1, 48 Invalid1 = 1,
@@ -289,8 +302,8 @@ private:
289 302
290class IFileSystem final : public ServiceFramework<IFileSystem> { 303class IFileSystem final : public ServiceFramework<IFileSystem> {
291public: 304public:
292 explicit IFileSystem(FileSys::VirtualDir backend) 305 explicit IFileSystem(FileSys::VirtualDir backend, SizeGetter size)
293 : ServiceFramework("IFileSystem"), backend(std::move(backend)) { 306 : ServiceFramework("IFileSystem"), backend(std::move(backend)), size(std::move(size)) {
294 static const FunctionInfo functions[] = { 307 static const FunctionInfo functions[] = {
295 {0, &IFileSystem::CreateFile, "CreateFile"}, 308 {0, &IFileSystem::CreateFile, "CreateFile"},
296 {1, &IFileSystem::DeleteFile, "DeleteFile"}, 309 {1, &IFileSystem::DeleteFile, "DeleteFile"},
@@ -467,14 +480,31 @@ public:
467 rb.Push(RESULT_SUCCESS); 480 rb.Push(RESULT_SUCCESS);
468 } 481 }
469 482
483 void GetFreeSpaceSize(Kernel::HLERequestContext& ctx) {
484 LOG_DEBUG(Service_FS, "called");
485
486 IPC::ResponseBuilder rb{ctx, 4};
487 rb.Push(RESULT_SUCCESS);
488 rb.Push(size.get_free_size());
489 }
490
491 void GetTotalSpaceSize(Kernel::HLERequestContext& ctx) {
492 LOG_DEBUG(Service_FS, "called");
493
494 IPC::ResponseBuilder rb{ctx, 4};
495 rb.Push(RESULT_SUCCESS);
496 rb.Push(size.get_total_size());
497 }
498
470private: 499private:
471 VfsDirectoryServiceWrapper backend; 500 VfsDirectoryServiceWrapper backend;
501 SizeGetter size;
472}; 502};
473 503
474class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> { 504class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
475public: 505public:
476 explicit ISaveDataInfoReader(FileSys::SaveDataSpaceId space) 506 explicit ISaveDataInfoReader(FileSys::SaveDataSpaceId space, FileSystemController& fsc)
477 : ServiceFramework("ISaveDataInfoReader") { 507 : ServiceFramework("ISaveDataInfoReader"), fsc(fsc) {
478 static const FunctionInfo functions[] = { 508 static const FunctionInfo functions[] = {
479 {0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"}, 509 {0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"},
480 }; 510 };
@@ -520,8 +550,13 @@ private:
520 } 550 }
521 551
522 void FindAllSaves(FileSys::SaveDataSpaceId space) { 552 void FindAllSaves(FileSys::SaveDataSpaceId space) {
523 const auto save_root = OpenSaveDataSpace(space); 553 const auto save_root = fsc.OpenSaveDataSpace(space);
524 ASSERT(save_root.Succeeded()); 554
555 if (save_root.Failed() || *save_root == nullptr) {
556 LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!",
557 static_cast<u8>(space));
558 return;
559 }
525 560
526 for (const auto& type : (*save_root)->GetSubdirectories()) { 561 for (const auto& type : (*save_root)->GetSubdirectories()) {
527 if (type->GetName() == "save") { 562 if (type->GetName() == "save") {
@@ -610,11 +645,13 @@ private:
610 }; 645 };
611 static_assert(sizeof(SaveDataInfo) == 0x60, "SaveDataInfo has incorrect size."); 646 static_assert(sizeof(SaveDataInfo) == 0x60, "SaveDataInfo has incorrect size.");
612 647
648 FileSystemController& fsc;
613 std::vector<SaveDataInfo> info; 649 std::vector<SaveDataInfo> info;
614 u64 next_entry_index = 0; 650 u64 next_entry_index = 0;
615}; 651};
616 652
617FSP_SRV::FSP_SRV(const Core::Reporter& reporter) : ServiceFramework("fsp-srv"), reporter(reporter) { 653FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
654 : ServiceFramework("fsp-srv"), fsc(fsc), reporter(reporter) {
618 // clang-format off 655 // clang-format off
619 static const FunctionInfo functions[] = { 656 static const FunctionInfo functions[] = {
620 {0, nullptr, "OpenFileSystem"}, 657 {0, nullptr, "OpenFileSystem"},
@@ -754,7 +791,8 @@ void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) {
754void FSP_SRV::OpenSdCardFileSystem(Kernel::HLERequestContext& ctx) { 791void FSP_SRV::OpenSdCardFileSystem(Kernel::HLERequestContext& ctx) {
755 LOG_DEBUG(Service_FS, "called"); 792 LOG_DEBUG(Service_FS, "called");
756 793
757 IFileSystem filesystem(OpenSDMC().Unwrap()); 794 IFileSystem filesystem(fsc.OpenSDMC().Unwrap(),
795 SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
758 796
759 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 797 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
760 rb.Push(RESULT_SUCCESS); 798 rb.Push(RESULT_SUCCESS);
@@ -768,8 +806,10 @@ void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
768 auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>(); 806 auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
769 u128 uid = rp.PopRaw<u128>(); 807 u128 uid = rp.PopRaw<u128>();
770 808
771 LOG_WARNING(Service_FS, "(STUBBED) called save_struct = {}, uid = {:016X}{:016X}", 809 LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(),
772 save_struct.DebugInfo(), uid[1], uid[0]); 810 uid[1], uid[0]);
811
812 fsc.CreateSaveData(FileSys::SaveDataSpaceId::NandUser, save_struct);
773 813
774 IPC::ResponseBuilder rb{ctx, 2}; 814 IPC::ResponseBuilder rb{ctx, 2};
775 rb.Push(RESULT_SUCCESS); 815 rb.Push(RESULT_SUCCESS);
@@ -786,14 +826,24 @@ void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
786 IPC::RequestParser rp{ctx}; 826 IPC::RequestParser rp{ctx};
787 const auto parameters = rp.PopRaw<Parameters>(); 827 const auto parameters = rp.PopRaw<Parameters>();
788 828
789 auto dir = OpenSaveData(parameters.save_data_space_id, parameters.descriptor); 829 auto dir = fsc.OpenSaveData(parameters.save_data_space_id, parameters.descriptor);
790 if (dir.Failed()) { 830 if (dir.Failed()) {
791 IPC::ResponseBuilder rb{ctx, 2, 0, 0}; 831 IPC::ResponseBuilder rb{ctx, 2, 0, 0};
792 rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND); 832 rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
793 return; 833 return;
794 } 834 }
795 835
796 IFileSystem filesystem(std::move(dir.Unwrap())); 836 FileSys::StorageId id;
837 if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::NandUser) {
838 id = FileSys::StorageId::NandUser;
839 } else if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardSystem ||
840 parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardUser) {
841 id = FileSys::StorageId::SdCard;
842 } else {
843 id = FileSys::StorageId::NandSystem;
844 }
845
846 IFileSystem filesystem(std::move(dir.Unwrap()), SizeGetter::FromStorageId(fsc, id));
797 847
798 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 848 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
799 rb.Push(RESULT_SUCCESS); 849 rb.Push(RESULT_SUCCESS);
@@ -812,7 +862,7 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext&
812 862
813 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 863 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
814 rb.Push(RESULT_SUCCESS); 864 rb.Push(RESULT_SUCCESS);
815 rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space)); 865 rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space, fsc));
816} 866}
817 867
818void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { 868void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
@@ -836,7 +886,7 @@ void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
836void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { 886void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
837 LOG_DEBUG(Service_FS, "called"); 887 LOG_DEBUG(Service_FS, "called");
838 888
839 auto romfs = OpenRomFSCurrentProcess(); 889 auto romfs = fsc.OpenRomFSCurrentProcess();
840 if (romfs.Failed()) { 890 if (romfs.Failed()) {
841 // TODO (bunnei): Find the right error code to use here 891 // TODO (bunnei): Find the right error code to use here
842 LOG_CRITICAL(Service_FS, "no file system interface available!"); 892 LOG_CRITICAL(Service_FS, "no file system interface available!");
@@ -861,7 +911,7 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
861 LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}", 911 LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}",
862 static_cast<u8>(storage_id), unknown, title_id); 912 static_cast<u8>(storage_id), unknown, title_id);
863 913
864 auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data); 914 auto data = fsc.OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
865 915
866 if (data.Failed()) { 916 if (data.Failed()) {
867 const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id); 917 const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index b5486a193..d52b55999 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -32,7 +32,7 @@ enum class LogMode : u32 {
32 32
33class FSP_SRV final : public ServiceFramework<FSP_SRV> { 33class FSP_SRV final : public ServiceFramework<FSP_SRV> {
34public: 34public:
35 explicit FSP_SRV(const Core::Reporter& reporter); 35 explicit FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter);
36 ~FSP_SRV() override; 36 ~FSP_SRV() override;
37 37
38private: 38private:
@@ -51,6 +51,8 @@ private:
51 void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx); 51 void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx);
52 void GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx); 52 void GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx);
53 53
54 FileSystemController& fsc;
55
54 FileSys::VirtualFile romfs; 56 FileSys::VirtualFile romfs;
55 u64 current_process_id = 0; 57 u64 current_process_id = 0;
56 u32 access_log_program_index = 0; 58 u32 access_log_program_index = 0;
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index ce88a2941..13121c4f1 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -617,7 +617,7 @@ public:
617 } 617 }
618}; 618};
619 619
620void InstallInterfaces(SM::ServiceManager& service_manager) { 620void InstallInterfaces(SM::ServiceManager& service_manager, FileSystem::FileSystemController& fsc) {
621 std::make_shared<NS>("ns:am2")->InstallAsService(service_manager); 621 std::make_shared<NS>("ns:am2")->InstallAsService(service_manager);
622 std::make_shared<NS>("ns:ec")->InstallAsService(service_manager); 622 std::make_shared<NS>("ns:ec")->InstallAsService(service_manager);
623 std::make_shared<NS>("ns:rid")->InstallAsService(service_manager); 623 std::make_shared<NS>("ns:rid")->InstallAsService(service_manager);
@@ -628,7 +628,7 @@ void InstallInterfaces(SM::ServiceManager& service_manager) {
628 std::make_shared<NS_SU>()->InstallAsService(service_manager); 628 std::make_shared<NS_SU>()->InstallAsService(service_manager);
629 std::make_shared<NS_VM>()->InstallAsService(service_manager); 629 std::make_shared<NS_VM>()->InstallAsService(service_manager);
630 630
631 std::make_shared<PL_U>()->InstallAsService(service_manager); 631 std::make_shared<PL_U>(fsc)->InstallAsService(service_manager);
632} 632}
633 633
634} // namespace Service::NS 634} // namespace Service::NS
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index 0e8256cb4..d067e7a9a 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -6,7 +6,13 @@
6 6
7#include "core/hle/service/service.h" 7#include "core/hle/service/service.h"
8 8
9namespace Service::NS { 9namespace Service {
10
11namespace FileSystem {
12class FileSystemController;
13} // namespace FileSystem
14
15namespace NS {
10 16
11class IAccountProxyInterface final : public ServiceFramework<IAccountProxyInterface> { 17class IAccountProxyInterface final : public ServiceFramework<IAccountProxyInterface> {
12public: 18public:
@@ -91,6 +97,8 @@ private:
91}; 97};
92 98
93/// Registers all NS services with the specified service manager. 99/// Registers all NS services with the specified service manager.
94void InstallInterfaces(SM::ServiceManager& service_manager); 100void InstallInterfaces(SM::ServiceManager& service_manager, FileSystem::FileSystemController& fsc);
101
102} // namespace NS
95 103
96} // namespace Service::NS 104} // namespace Service
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
index 2a522136d..9d49f36e8 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -150,7 +150,8 @@ struct PL_U::Impl {
150 std::vector<FontRegion> shared_font_regions; 150 std::vector<FontRegion> shared_font_regions;
151}; 151};
152 152
153PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} { 153PL_U::PL_U(FileSystem::FileSystemController& fsc)
154 : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} {
154 static const FunctionInfo functions[] = { 155 static const FunctionInfo functions[] = {
155 {0, &PL_U::RequestLoad, "RequestLoad"}, 156 {0, &PL_U::RequestLoad, "RequestLoad"},
156 {1, &PL_U::GetLoadState, "GetLoadState"}, 157 {1, &PL_U::GetLoadState, "GetLoadState"},
@@ -161,7 +162,7 @@ PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} {
161 }; 162 };
162 RegisterHandlers(functions); 163 RegisterHandlers(functions);
163 // Attempt to load shared font data from disk 164 // Attempt to load shared font data from disk
164 const auto* nand = FileSystem::GetSystemNANDContents(); 165 const auto* nand = fsc.GetSystemNANDContents();
165 std::size_t offset = 0; 166 std::size_t offset = 0;
166 // Rebuild shared fonts from data ncas 167 // Rebuild shared fonts from data ncas
167 if (nand->HasEntry(static_cast<u64>(FontArchives::Standard), 168 if (nand->HasEntry(static_cast<u64>(FontArchives::Standard),
diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h
index 253f26a2a..35ca424d2 100644
--- a/src/core/hle/service/ns/pl_u.h
+++ b/src/core/hle/service/ns/pl_u.h
@@ -7,11 +7,17 @@
7#include <memory> 7#include <memory>
8#include "core/hle/service/service.h" 8#include "core/hle/service/service.h"
9 9
10namespace Service::NS { 10namespace Service {
11
12namespace FileSystem {
13class FileSystemController;
14} // namespace FileSystem
15
16namespace NS {
11 17
12class PL_U final : public ServiceFramework<PL_U> { 18class PL_U final : public ServiceFramework<PL_U> {
13public: 19public:
14 PL_U(); 20 PL_U(FileSystem::FileSystemController& fsc);
15 ~PL_U() override; 21 ~PL_U() override;
16 22
17private: 23private:
@@ -26,4 +32,6 @@ private:
26 std::unique_ptr<Impl> impl; 32 std::unique_ptr<Impl> impl;
27}; 33};
28 34
29} // namespace Service::NS 35} // namespace NS
36
37} // namespace Service
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index 241dac881..b4ee2a255 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -146,8 +146,8 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp
146 } 146 }
147 IoctlSubmitGpfifo params{}; 147 IoctlSubmitGpfifo params{};
148 std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo)); 148 std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
149 LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}", 149 LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address,
150 params.address, params.num_entries, params.flags.raw); 150 params.num_entries, params.flags.raw);
151 151
152 ASSERT_MSG(input.size() == sizeof(IoctlSubmitGpfifo) + 152 ASSERT_MSG(input.size() == sizeof(IoctlSubmitGpfifo) +
153 params.num_entries * sizeof(Tegra::CommandListHeader), 153 params.num_entries * sizeof(Tegra::CommandListHeader),
@@ -179,8 +179,8 @@ u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output)
179 } 179 }
180 IoctlSubmitGpfifo params{}; 180 IoctlSubmitGpfifo params{};
181 std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo)); 181 std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
182 LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}", 182 LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address,
183 params.address, params.num_entries, params.flags.raw); 183 params.num_entries, params.flags.raw);
184 184
185 Tegra::CommandList entries(params.num_entries); 185 Tegra::CommandList entries(params.num_entries);
186 Memory::ReadBlock(params.address, entries.data(), 186 Memory::ReadBlock(params.address, entries.data(),
diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp
index 7e134f5c1..0f79135ff 100644
--- a/src/core/hle/service/prepo/prepo.cpp
+++ b/src/core/hle/service/prepo/prepo.cpp
@@ -2,34 +2,31 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <json.hpp>
6#include "common/file_util.h"
7#include "common/hex_util.h" 5#include "common/hex_util.h"
8#include "common/logging/log.h" 6#include "common/logging/log.h"
9#include "common/scm_rev.h"
10#include "core/hle/ipc_helpers.h" 7#include "core/hle/ipc_helpers.h"
11#include "core/hle/kernel/process.h" 8#include "core/hle/kernel/process.h"
12#include "core/hle/service/acc/profile_manager.h" 9#include "core/hle/service/acc/profile_manager.h"
13#include "core/hle/service/prepo/prepo.h" 10#include "core/hle/service/prepo/prepo.h"
14#include "core/hle/service/service.h" 11#include "core/hle/service/service.h"
15#include "core/reporter.h" 12#include "core/reporter.h"
16#include "core/settings.h"
17 13
18namespace Service::PlayReport { 14namespace Service::PlayReport {
19 15
20class PlayReport final : public ServiceFramework<PlayReport> { 16class PlayReport final : public ServiceFramework<PlayReport> {
21public: 17public:
22 explicit PlayReport(const char* name) : ServiceFramework{name} { 18 explicit PlayReport(Core::System& system, const char* name)
19 : ServiceFramework{name}, system(system) {
23 // clang-format off 20 // clang-format off
24 static const FunctionInfo functions[] = { 21 static const FunctionInfo functions[] = {
25 {10100, nullptr, "SaveReportOld"}, 22 {10100, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old>, "SaveReportOld"},
26 {10101, &PlayReport::SaveReportWithUserOld, "SaveReportWithUserOld"}, 23 {10101, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old>, "SaveReportWithUserOld"},
27 {10102, nullptr, "SaveReport"}, 24 {10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
28 {10103, nullptr, "SaveReportWithUser"}, 25 {10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
29 {10200, nullptr, "RequestImmediateTransmission"}, 26 {10200, nullptr, "RequestImmediateTransmission"},
30 {10300, nullptr, "GetTransmissionStatus"}, 27 {10300, nullptr, "GetTransmissionStatus"},
31 {20100, nullptr, "SaveSystemReport"}, 28 {20100, &PlayReport::SaveSystemReport, "SaveSystemReport"},
32 {20101, nullptr, "SaveSystemReportWithUser"}, 29 {20101, &PlayReport::SaveSystemReportWithUser, "SaveSystemReportWithUser"},
33 {20200, nullptr, "SetOperationMode"}, 30 {20200, nullptr, "SetOperationMode"},
34 {30100, nullptr, "ClearStorage"}, 31 {30100, nullptr, "ClearStorage"},
35 {30200, nullptr, "ClearStatistics"}, 32 {30200, nullptr, "ClearStatistics"},
@@ -47,7 +44,28 @@ public:
47 } 44 }
48 45
49private: 46private:
50 void SaveReportWithUserOld(Kernel::HLERequestContext& ctx) { 47 template <Core::Reporter::PlayReportType Type>
48 void SaveReport(Kernel::HLERequestContext& ctx) {
49 IPC::RequestParser rp{ctx};
50 const auto process_id = rp.PopRaw<u64>();
51
52 const auto data1 = ctx.ReadBuffer(0);
53 const auto data2 = ctx.ReadBuffer(1);
54
55 LOG_DEBUG(Service_PREPO,
56 "called, type={:02X}, process_id={:016X}, data1_size={:016X}, data2_size={:016X}",
57 static_cast<u8>(Type), process_id, data1.size(), data2.size());
58
59 const auto& reporter{system.GetReporter()};
60 reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), {data1, data2},
61 process_id);
62
63 IPC::ResponseBuilder rb{ctx, 2};
64 rb.Push(RESULT_SUCCESS);
65 }
66
67 template <Core::Reporter::PlayReportType Type>
68 void SaveReportWithUser(Kernel::HLERequestContext& ctx) {
51 IPC::RequestParser rp{ctx}; 69 IPC::RequestParser rp{ctx};
52 const auto user_id = rp.PopRaw<u128>(); 70 const auto user_id = rp.PopRaw<u128>();
53 const auto process_id = rp.PopRaw<u64>(); 71 const auto process_id = rp.PopRaw<u64>();
@@ -57,24 +75,65 @@ private:
57 75
58 LOG_DEBUG( 76 LOG_DEBUG(
59 Service_PREPO, 77 Service_PREPO,
60 "called, user_id={:016X}{:016X}, unk1={:016X}, data1_size={:016X}, data2_size={:016X}", 78 "called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, data1_size={:016X}, "
61 user_id[1], user_id[0], process_id, data1.size(), data2.size()); 79 "data2_size={:016X}",
80 static_cast<u8>(Type), user_id[1], user_id[0], process_id, data1.size(), data2.size());
81
82 const auto& reporter{system.GetReporter()};
83 reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), {data1, data2},
84 process_id, user_id);
85
86 IPC::ResponseBuilder rb{ctx, 2};
87 rb.Push(RESULT_SUCCESS);
88 }
89
90 void SaveSystemReport(Kernel::HLERequestContext& ctx) {
91 IPC::RequestParser rp{ctx};
92 const auto title_id = rp.PopRaw<u64>();
93
94 const auto data1 = ctx.ReadBuffer(0);
95 const auto data2 = ctx.ReadBuffer(1);
96
97 LOG_DEBUG(Service_PREPO, "called, title_id={:016X}, data1_size={:016X}, data2_size={:016X}",
98 title_id, data1.size(), data2.size());
99
100 const auto& reporter{system.GetReporter()};
101 reporter.SavePlayReport(Core::Reporter::PlayReportType::System, title_id, {data1, data2});
102
103 IPC::ResponseBuilder rb{ctx, 2};
104 rb.Push(RESULT_SUCCESS);
105 }
62 106
63 const auto& reporter{Core::System::GetInstance().GetReporter()}; 107 void SaveSystemReportWithUser(Kernel::HLERequestContext& ctx) {
64 reporter.SavePlayReport(Core::CurrentProcess()->GetTitleID(), process_id, {data1, data2}, 108 IPC::RequestParser rp{ctx};
65 user_id); 109 const auto user_id = rp.PopRaw<u128>();
110 const auto title_id = rp.PopRaw<u64>();
111
112 const auto data1 = ctx.ReadBuffer(0);
113 const auto data2 = ctx.ReadBuffer(1);
114
115 LOG_DEBUG(Service_PREPO,
116 "called, user_id={:016X}{:016X}, title_id={:016X}, data1_size={:016X}, "
117 "data2_size={:016X}",
118 user_id[1], user_id[0], title_id, data1.size(), data2.size());
119
120 const auto& reporter{system.GetReporter()};
121 reporter.SavePlayReport(Core::Reporter::PlayReportType::System, title_id, {data1, data2},
122 std::nullopt, user_id);
66 123
67 IPC::ResponseBuilder rb{ctx, 2}; 124 IPC::ResponseBuilder rb{ctx, 2};
68 rb.Push(RESULT_SUCCESS); 125 rb.Push(RESULT_SUCCESS);
69 } 126 }
127
128 Core::System& system;
70}; 129};
71 130
72void InstallInterfaces(SM::ServiceManager& service_manager) { 131void InstallInterfaces(Core::System& system) {
73 std::make_shared<PlayReport>("prepo:a")->InstallAsService(service_manager); 132 std::make_shared<PlayReport>(system, "prepo:a")->InstallAsService(system.ServiceManager());
74 std::make_shared<PlayReport>("prepo:a2")->InstallAsService(service_manager); 133 std::make_shared<PlayReport>(system, "prepo:a2")->InstallAsService(system.ServiceManager());
75 std::make_shared<PlayReport>("prepo:m")->InstallAsService(service_manager); 134 std::make_shared<PlayReport>(system, "prepo:m")->InstallAsService(system.ServiceManager());
76 std::make_shared<PlayReport>("prepo:s")->InstallAsService(service_manager); 135 std::make_shared<PlayReport>(system, "prepo:s")->InstallAsService(system.ServiceManager());
77 std::make_shared<PlayReport>("prepo:u")->InstallAsService(service_manager); 136 std::make_shared<PlayReport>(system, "prepo:u")->InstallAsService(system.ServiceManager());
78} 137}
79 138
80} // namespace Service::PlayReport 139} // namespace Service::PlayReport
diff --git a/src/core/hle/service/prepo/prepo.h b/src/core/hle/service/prepo/prepo.h
index 0e7b01331..0ebc3a938 100644
--- a/src/core/hle/service/prepo/prepo.h
+++ b/src/core/hle/service/prepo/prepo.h
@@ -10,6 +10,6 @@ class ServiceManager;
10 10
11namespace Service::PlayReport { 11namespace Service::PlayReport {
12 12
13void InstallInterfaces(SM::ServiceManager& service_manager); 13void InstallInterfaces(Core::System& system);
14 14
15} // namespace Service::PlayReport 15} // namespace Service::PlayReport
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 3a0f8c3f6..906fdc415 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -199,6 +199,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
199 // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it 199 // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
200 // here and pass it into the respective InstallInterfaces functions. 200 // here and pass it into the respective InstallInterfaces functions.
201 auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(system.CoreTiming()); 201 auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(system.CoreTiming());
202 system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false);
202 203
203 SM::ServiceManager::InstallInterfaces(sm); 204 SM::ServiceManager::InstallInterfaces(sm);
204 205
@@ -235,12 +236,12 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
235 NIFM::InstallInterfaces(*sm); 236 NIFM::InstallInterfaces(*sm);
236 NIM::InstallInterfaces(*sm); 237 NIM::InstallInterfaces(*sm);
237 NPNS::InstallInterfaces(*sm); 238 NPNS::InstallInterfaces(*sm);
238 NS::InstallInterfaces(*sm); 239 NS::InstallInterfaces(*sm, system.GetFileSystemController());
239 Nvidia::InstallInterfaces(*sm, *nv_flinger, system); 240 Nvidia::InstallInterfaces(*sm, *nv_flinger, system);
240 PCIe::InstallInterfaces(*sm); 241 PCIe::InstallInterfaces(*sm);
241 PCTL::InstallInterfaces(*sm); 242 PCTL::InstallInterfaces(*sm);
242 PCV::InstallInterfaces(*sm); 243 PCV::InstallInterfaces(*sm);
243 PlayReport::InstallInterfaces(*sm); 244 PlayReport::InstallInterfaces(system);
244 PM::InstallInterfaces(system); 245 PM::InstallInterfaces(system);
245 PSC::InstallInterfaces(*sm); 246 PSC::InstallInterfaces(*sm);
246 PSM::InstallInterfaces(*sm); 247 PSM::InstallInterfaces(*sm);
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index c6c4bdae5..aef964861 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -18,10 +18,6 @@ namespace Core {
18class System; 18class System;
19} 19}
20 20
21namespace FileSys {
22class VfsFilesystem;
23}
24
25namespace Kernel { 21namespace Kernel {
26class ClientPort; 22class ClientPort;
27class ServerPort; 23class ServerPort;
@@ -31,6 +27,10 @@ class HLERequestContext;
31 27
32namespace Service { 28namespace Service {
33 29
30namespace FileSystem {
31class FileSystemController;
32} // namespace FileSystem
33
34namespace SM { 34namespace SM {
35class ServiceManager; 35class ServiceManager;
36} 36}
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index f9e88be2b..d19c3623c 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -7,6 +7,7 @@
7#include "common/common_funcs.h" 7#include "common/common_funcs.h"
8#include "common/file_util.h" 8#include "common/file_util.h"
9#include "common/logging/log.h" 9#include "common/logging/log.h"
10#include "core/core.h"
10#include "core/file_sys/content_archive.h" 11#include "core/file_sys/content_archive.h"
11#include "core/file_sys/control_metadata.h" 12#include "core/file_sys/control_metadata.h"
12#include "core/file_sys/patch_manager.h" 13#include "core/file_sys/patch_manager.h"
@@ -176,7 +177,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
176 // Register the RomFS if a ".romfs" file was found 177 // Register the RomFS if a ".romfs" file was found
177 if (romfs_iter != files.end() && *romfs_iter != nullptr) { 178 if (romfs_iter != files.end() && *romfs_iter != nullptr) {
178 romfs = *romfs_iter; 179 romfs = *romfs_iter;
179 Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this)); 180 Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
181 std::make_unique<FileSys::RomFSFactory>(*this));
180 } 182 }
181 183
182 is_loaded = true; 184 is_loaded = true;
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index 0f65fb637..5a0469978 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -6,6 +6,7 @@
6 6
7#include "common/file_util.h" 7#include "common/file_util.h"
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "core/core.h"
9#include "core/file_sys/content_archive.h" 10#include "core/file_sys/content_archive.h"
10#include "core/file_sys/romfs_factory.h" 11#include "core/file_sys/romfs_factory.h"
11#include "core/hle/kernel/process.h" 12#include "core/hle/kernel/process.h"
@@ -57,7 +58,8 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) {
57 } 58 }
58 59
59 if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0) { 60 if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0) {
60 Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this)); 61 Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
62 std::make_unique<FileSys::RomFSFactory>(*this));
61 } 63 }
62 64
63 is_loaded = true; 65 is_loaded = true;
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 3a5361fdd..175898b91 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -10,6 +10,7 @@
10#include "common/file_util.h" 10#include "common/file_util.h"
11#include "common/logging/log.h" 11#include "common/logging/log.h"
12#include "common/swap.h" 12#include "common/swap.h"
13#include "core/core.h"
13#include "core/file_sys/control_metadata.h" 14#include "core/file_sys/control_metadata.h"
14#include "core/file_sys/romfs_factory.h" 15#include "core/file_sys/romfs_factory.h"
15#include "core/file_sys/vfs_offset.h" 16#include "core/file_sys/vfs_offset.h"
@@ -214,7 +215,8 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) {
214 } 215 }
215 216
216 if (romfs != nullptr) { 217 if (romfs != nullptr) {
217 Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this)); 218 Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
219 std::make_unique<FileSys::RomFSFactory>(*this));
218 } 220 }
219 221
220 is_loaded = true; 222 is_loaded = true;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 70c90109f..e75c700ad 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -152,8 +152,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
152 auto& system = Core::System::GetInstance(); 152 auto& system = Core::System::GetInstance();
153 const auto cheats = pm->CreateCheatList(system, nso_header.build_id); 153 const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
154 if (!cheats.empty()) { 154 if (!cheats.empty()) {
155 system.RegisterCheatList(cheats, Common::HexToString(nso_header.build_id), load_base, 155 system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size);
156 load_base + program_image.size());
157 } 156 }
158 } 157 }
159 158
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index b1171ce65..13950fc08 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -5,6 +5,7 @@
5#include <vector> 5#include <vector>
6 6
7#include "common/common_types.h" 7#include "common/common_types.h"
8#include "core/core.h"
8#include "core/file_sys/card_image.h" 9#include "core/file_sys/card_image.h"
9#include "core/file_sys/content_archive.h" 10#include "core/file_sys/content_archive.h"
10#include "core/file_sys/control_metadata.h" 11#include "core/file_sys/control_metadata.h"
@@ -26,20 +27,18 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
26 27
27 if (nsp->GetStatus() != ResultStatus::Success) 28 if (nsp->GetStatus() != ResultStatus::Success)
28 return; 29 return;
29 if (nsp->IsExtractedType())
30 return;
31
32 const auto control_nca =
33 nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
34 if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
35 return;
36
37 std::tie(nacp_file, icon_file) =
38 FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca);
39 30
40 if (nsp->IsExtractedType()) { 31 if (nsp->IsExtractedType()) {
41 secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); 32 secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
42 } else { 33 } else {
34 const auto control_nca =
35 nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
36 if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
37 return;
38
39 std::tie(nacp_file, icon_file) =
40 FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca);
41
43 if (title_id == 0) 42 if (title_id == 0)
44 return; 43 return;
45 44
@@ -56,11 +55,11 @@ FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) {
56 if (nsp.GetStatus() == ResultStatus::Success) { 55 if (nsp.GetStatus() == ResultStatus::Success) {
57 // Extracted Type case 56 // Extracted Type case
58 if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr && 57 if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
59 FileSys::IsDirectoryExeFS(nsp.GetExeFS()) && nsp.GetRomFS() != nullptr) { 58 FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
60 return FileType::NSP; 59 return FileType::NSP;
61 } 60 }
62 61
63 // Non-Ectracted Type case 62 // Non-Extracted Type case
64 if (!nsp.IsExtractedType() && 63 if (!nsp.IsExtractedType() &&
65 nsp.GetNCA(nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program) != nullptr && 64 nsp.GetNCA(nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program) != nullptr &&
66 AppLoader_NCA::IdentifyType(nsp.GetNCAFile( 65 AppLoader_NCA::IdentifyType(nsp.GetNCAFile(
@@ -77,7 +76,7 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
77 return {ResultStatus::ErrorAlreadyLoaded, {}}; 76 return {ResultStatus::ErrorAlreadyLoaded, {}};
78 } 77 }
79 78
80 if (title_id == 0) { 79 if (!nsp->IsExtractedType() && title_id == 0) {
81 return {ResultStatus::ErrorNSPMissingProgramNCA, {}}; 80 return {ResultStatus::ErrorNSPMissingProgramNCA, {}};
82 } 81 }
83 82
@@ -91,7 +90,8 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
91 return {nsp_program_status, {}}; 90 return {nsp_program_status, {}};
92 } 91 }
93 92
94 if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) { 93 if (!nsp->IsExtractedType() &&
94 nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) {
95 if (!Core::Crypto::KeyManager::KeyFileExists(false)) { 95 if (!Core::Crypto::KeyManager::KeyFileExists(false)) {
96 return {ResultStatus::ErrorMissingProductionKeyFile, {}}; 96 return {ResultStatus::ErrorMissingProductionKeyFile, {}};
97 } 97 }
@@ -106,7 +106,8 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
106 106
107 FileSys::VirtualFile update_raw; 107 FileSys::VirtualFile update_raw;
108 if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) { 108 if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
109 Service::FileSystem::SetPackedUpdate(std::move(update_raw)); 109 Core::System::GetInstance().GetFileSystemController().SetPackedUpdate(
110 std::move(update_raw));
110 } 111 }
111 112
112 is_loaded = true; 113 is_loaded = true;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 5e8553db9..7186ad1ff 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -5,6 +5,7 @@
5#include <vector> 5#include <vector>
6 6
7#include "common/common_types.h" 7#include "common/common_types.h"
8#include "core/core.h"
8#include "core/file_sys/card_image.h" 9#include "core/file_sys/card_image.h"
9#include "core/file_sys/content_archive.h" 10#include "core/file_sys/content_archive.h"
10#include "core/file_sys/control_metadata.h" 11#include "core/file_sys/control_metadata.h"
@@ -72,7 +73,8 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) {
72 73
73 FileSys::VirtualFile update_raw; 74 FileSys::VirtualFile update_raw;
74 if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) { 75 if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
75 Service::FileSystem::SetPackedUpdate(std::move(update_raw)); 76 Core::System::GetInstance().GetFileSystemController().SetPackedUpdate(
77 std::move(update_raw));
76 } 78 }
77 79
78 is_loaded = true; 80 is_loaded = true;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 8555691c0..9e030789d 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -43,8 +43,13 @@ static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* me
43 43
44 // During boot, current_page_table might not be set yet, in which case we need not flush 44 // During boot, current_page_table might not be set yet, in which case we need not flush
45 if (Core::System::GetInstance().IsPoweredOn()) { 45 if (Core::System::GetInstance().IsPoweredOn()) {
46 Core::System::GetInstance().GPU().FlushAndInvalidateRegion(base << PAGE_BITS, 46 auto& gpu = Core::System::GetInstance().GPU();
47 size * PAGE_SIZE); 47 for (u64 i = 0; i < size; i++) {
48 const auto page = base + i;
49 if (page_table.attributes[page] == Common::PageType::RasterizerCachedMemory) {
50 gpu.FlushAndInvalidateRegion(page << PAGE_BITS, PAGE_SIZE);
51 }
52 }
48 } 53 }
49 54
50 VAddr end = base + size; 55 VAddr end = base + size;
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
new file mode 100644
index 000000000..b56cb0627
--- /dev/null
+++ b/src/core/memory/cheat_engine.cpp
@@ -0,0 +1,234 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <locale>
6#include "common/hex_util.h"
7#include "common/microprofile.h"
8#include "common/swap.h"
9#include "core/core.h"
10#include "core/core_timing.h"
11#include "core/core_timing_util.h"
12#include "core/hle/kernel/process.h"
13#include "core/hle/service/hid/controllers/npad.h"
14#include "core/hle/service/hid/hid.h"
15#include "core/hle/service/sm/sm.h"
16#include "core/memory/cheat_engine.h"
17
18namespace Memory {
19
20constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 12);
21constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
22
23StandardVmCallbacks::StandardVmCallbacks(const Core::System& system,
24 const CheatProcessMetadata& metadata)
25 : system(system), metadata(metadata) {}
26
27StandardVmCallbacks::~StandardVmCallbacks() = default;
28
29void StandardVmCallbacks::MemoryRead(VAddr address, void* data, u64 size) {
30 ReadBlock(SanitizeAddress(address), data, size);
31}
32
33void StandardVmCallbacks::MemoryWrite(VAddr address, const void* data, u64 size) {
34 WriteBlock(SanitizeAddress(address), data, size);
35}
36
37u64 StandardVmCallbacks::HidKeysDown() {
38 const auto applet_resource =
39 system.ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
40 if (applet_resource == nullptr) {
41 LOG_WARNING(CheatEngine,
42 "Attempted to read input state, but applet resource is not initialized!");
43 return false;
44 }
45
46 const auto press_state =
47 applet_resource
48 ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
49 .GetAndResetPressState();
50 return press_state & KEYPAD_BITMASK;
51}
52
53void StandardVmCallbacks::DebugLog(u8 id, u64 value) {
54 LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
55}
56
57void StandardVmCallbacks::CommandLog(std::string_view data) {
58 LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
59 data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
60}
61
62VAddr StandardVmCallbacks::SanitizeAddress(VAddr in) const {
63 if ((in < metadata.main_nso_extents.base ||
64 in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
65 (in < metadata.heap_extents.base ||
66 in >= metadata.heap_extents.base + metadata.heap_extents.size)) {
67 LOG_ERROR(CheatEngine,
68 "Cheat attempting to access memory at invalid address={:016X}, if this "
69 "persists, "
70 "the cheat may be incorrect. However, this may be normal early in execution if "
71 "the game has not properly set up yet.",
72 in);
73 return 0; ///< Invalid addresses will hard crash
74 }
75
76 return in;
77}
78
79CheatParser::~CheatParser() = default;
80
81TextCheatParser::~TextCheatParser() = default;
82
83namespace {
84template <char match>
85std::string_view ExtractName(std::string_view data, std::size_t start_index) {
86 auto end_index = start_index;
87 while (data[end_index] != match) {
88 ++end_index;
89 if (end_index > data.size() ||
90 (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
91 return {};
92 }
93 }
94
95 return data.substr(start_index, end_index - start_index);
96}
97} // Anonymous namespace
98
99std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
100 std::string_view data) const {
101 std::vector<CheatEntry> out(1);
102 std::optional<u64> current_entry = std::nullopt;
103
104 for (std::size_t i = 0; i < data.size(); ++i) {
105 if (::isspace(data[i])) {
106 continue;
107 }
108
109 if (data[i] == '{') {
110 current_entry = 0;
111
112 if (out[*current_entry].definition.num_opcodes > 0) {
113 return {};
114 }
115
116 const auto name = ExtractName<'}'>(data, i + 1);
117 if (name.empty()) {
118 return {};
119 }
120
121 std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
122 std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
123 name.size()));
124 out[*current_entry]
125 .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
126 '\0';
127
128 i += name.length() + 1;
129 } else if (data[i] == '[') {
130 current_entry = out.size();
131 out.emplace_back();
132
133 const auto name = ExtractName<']'>(data, i + 1);
134 if (name.empty()) {
135 return {};
136 }
137
138 std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
139 std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
140 name.size()));
141 out[*current_entry]
142 .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
143 '\0';
144
145 i += name.length() + 1;
146 } else if (::isxdigit(data[i])) {
147 if (!current_entry || out[*current_entry].definition.num_opcodes >=
148 out[*current_entry].definition.opcodes.size()) {
149 return {};
150 }
151
152 const auto hex = std::string(data.substr(i, 8));
153 if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
154 return {};
155 }
156
157 out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
158 std::stoul(hex, nullptr, 0x10);
159
160 i += 8;
161 } else {
162 return {};
163 }
164 }
165
166 out[0].enabled = out[0].definition.num_opcodes > 0;
167 out[0].cheat_id = 0;
168
169 for (u32 i = 1; i < out.size(); ++i) {
170 out[i].enabled = out[i].definition.num_opcodes > 0;
171 out[i].cheat_id = i;
172 }
173
174 return out;
175}
176
177CheatEngine::CheatEngine(Core::System& system, std::vector<CheatEntry> cheats,
178 const std::array<u8, 0x20>& build_id)
179 : system{system}, core_timing{system.CoreTiming()}, vm{std::make_unique<StandardVmCallbacks>(
180 system, metadata)},
181 cheats(std::move(cheats)) {
182 metadata.main_nso_build_id = build_id;
183}
184
185CheatEngine::~CheatEngine() {
186 core_timing.UnscheduleEvent(event, 0);
187}
188
189void CheatEngine::Initialize() {
190 event = core_timing.RegisterEvent(
191 "CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
192 [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
193 core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
194
195 metadata.process_id = system.CurrentProcess()->GetProcessID();
196 metadata.title_id = system.CurrentProcess()->GetTitleID();
197
198 const auto& vm_manager = system.CurrentProcess()->VMManager();
199 metadata.heap_extents = {vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionSize()};
200 metadata.address_space_extents = {vm_manager.GetAddressSpaceBaseAddress(),
201 vm_manager.GetAddressSpaceSize()};
202 metadata.alias_extents = {vm_manager.GetMapRegionBaseAddress(), vm_manager.GetMapRegionSize()};
203
204 is_pending_reload.exchange(true);
205}
206
207void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
208 metadata.main_nso_extents = {main_region_begin, main_region_size};
209}
210
211void CheatEngine::Reload(std::vector<CheatEntry> cheats) {
212 this->cheats = std::move(cheats);
213 is_pending_reload.exchange(true);
214}
215
216MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
217
218void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
219 if (is_pending_reload.exchange(false)) {
220 vm.LoadProgram(cheats);
221 }
222
223 if (vm.GetProgramSize() == 0) {
224 return;
225 }
226
227 MICROPROFILE_SCOPE(Cheat_Engine);
228
229 vm.Execute(metadata);
230
231 core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
232}
233
234} // namespace Memory
diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h
new file mode 100644
index 000000000..0f012e9b5
--- /dev/null
+++ b/src/core/memory/cheat_engine.h
@@ -0,0 +1,86 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <atomic>
8#include <vector>
9#include "common/common_types.h"
10#include "core/memory/dmnt_cheat_types.h"
11#include "core/memory/dmnt_cheat_vm.h"
12
13namespace Core {
14class System;
15}
16
17namespace Core::Timing {
18class CoreTiming;
19struct EventType;
20} // namespace Core::Timing
21
22namespace Memory {
23
24class StandardVmCallbacks : public DmntCheatVm::Callbacks {
25public:
26 StandardVmCallbacks(const Core::System& system, const CheatProcessMetadata& metadata);
27 ~StandardVmCallbacks() override;
28
29 void MemoryRead(VAddr address, void* data, u64 size) override;
30 void MemoryWrite(VAddr address, const void* data, u64 size) override;
31 u64 HidKeysDown() override;
32 void DebugLog(u8 id, u64 value) override;
33 void CommandLog(std::string_view data) override;
34
35private:
36 VAddr SanitizeAddress(VAddr address) const;
37
38 const CheatProcessMetadata& metadata;
39 const Core::System& system;
40};
41
42// Intermediary class that parses a text file or other disk format for storing cheats into a
43// CheatList object, that can be used for execution.
44class CheatParser {
45public:
46 virtual ~CheatParser();
47
48 virtual std::vector<CheatEntry> Parse(const Core::System& system,
49 std::string_view data) const = 0;
50};
51
52// CheatParser implementation that parses text files
53class TextCheatParser final : public CheatParser {
54public:
55 ~TextCheatParser() override;
56
57 std::vector<CheatEntry> Parse(const Core::System& system, std::string_view data) const override;
58};
59
60// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
61class CheatEngine final {
62public:
63 CheatEngine(Core::System& system_, std::vector<CheatEntry> cheats_,
64 const std::array<u8, 0x20>& build_id);
65 ~CheatEngine();
66
67 void Initialize();
68 void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size);
69
70 void Reload(std::vector<CheatEntry> cheats);
71
72private:
73 void FrameCallback(u64 userdata, s64 cycles_late);
74
75 DmntCheatVm vm;
76 CheatProcessMetadata metadata;
77
78 std::vector<CheatEntry> cheats;
79 std::atomic_bool is_pending_reload{false};
80
81 Core::Timing::EventType* event{};
82 Core::Timing::CoreTiming& core_timing;
83 Core::System& system;
84};
85
86} // namespace Memory
diff --git a/src/core/memory/dmnt_cheat_types.h b/src/core/memory/dmnt_cheat_types.h
new file mode 100644
index 000000000..bf68fa0fe
--- /dev/null
+++ b/src/core/memory/dmnt_cheat_types.h
@@ -0,0 +1,58 @@
1/*
2 * Copyright (c) 2018-2019 Atmosphère-NX
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Adapted by DarkLordZach for use/interaction with yuzu
19 *
20 * Modifications Copyright 2019 yuzu emulator team
21 * Licensed under GPLv2 or any later version
22 * Refer to the license.txt file included.
23 */
24
25#pragma once
26
27#include "common/common_types.h"
28
29namespace Memory {
30
31struct MemoryRegionExtents {
32 u64 base{};
33 u64 size{};
34};
35
36struct CheatProcessMetadata {
37 u64 process_id{};
38 u64 title_id{};
39 MemoryRegionExtents main_nso_extents{};
40 MemoryRegionExtents heap_extents{};
41 MemoryRegionExtents alias_extents{};
42 MemoryRegionExtents address_space_extents{};
43 std::array<u8, 0x20> main_nso_build_id{};
44};
45
46struct CheatDefinition {
47 std::array<char, 0x40> readable_name{};
48 u32 num_opcodes{};
49 std::array<u32, 0x100> opcodes{};
50};
51
52struct CheatEntry {
53 bool enabled{};
54 u32 cheat_id{};
55 CheatDefinition definition{};
56};
57
58} // namespace Memory
diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp
new file mode 100644
index 000000000..cc16d15a4
--- /dev/null
+++ b/src/core/memory/dmnt_cheat_vm.cpp
@@ -0,0 +1,1212 @@
1/*
2 * Copyright (c) 2018-2019 Atmosphère-NX
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Adapted by DarkLordZach for use/interaction with yuzu
19 *
20 * Modifications Copyright 2019 yuzu emulator team
21 * Licensed under GPLv2 or any later version
22 * Refer to the license.txt file included.
23 */
24
25#include "common/assert.h"
26#include "common/scope_exit.h"
27#include "core/memory/dmnt_cheat_types.h"
28#include "core/memory/dmnt_cheat_vm.h"
29
30namespace Memory {
31
32DmntCheatVm::DmntCheatVm(std::unique_ptr<Callbacks> callbacks) : callbacks(std::move(callbacks)) {}
33
34DmntCheatVm::~DmntCheatVm() = default;
35
36void DmntCheatVm::DebugLog(u32 log_id, u64 value) {
37 callbacks->DebugLog(static_cast<u8>(log_id), value);
38}
39
40void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) {
41 if (auto store_static = std::get_if<StoreStaticOpcode>(&opcode.opcode)) {
42 callbacks->CommandLog("Opcode: Store Static");
43 callbacks->CommandLog(fmt::format("Bit Width: {:X}", store_static->bit_width));
44 callbacks->CommandLog(
45 fmt::format("Mem Type: {:X}", static_cast<u32>(store_static->mem_type)));
46 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", store_static->offset_register));
47 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", store_static->rel_address));
48 callbacks->CommandLog(fmt::format("Value: {:X}", store_static->value.bit64));
49 } else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&opcode.opcode)) {
50 callbacks->CommandLog("Opcode: Begin Conditional");
51 callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_cond->bit_width));
52 callbacks->CommandLog(
53 fmt::format("Mem Type: {:X}", static_cast<u32>(begin_cond->mem_type)));
54 callbacks->CommandLog(
55 fmt::format("Cond Type: {:X}", static_cast<u32>(begin_cond->cond_type)));
56 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address));
57 callbacks->CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64));
58 } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&opcode.opcode)) {
59 callbacks->CommandLog("Opcode: End Conditional");
60 } else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&opcode.opcode)) {
61 if (ctrl_loop->start_loop) {
62 callbacks->CommandLog("Opcode: Start Loop");
63 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
64 callbacks->CommandLog(fmt::format("Num Iters: {:X}", ctrl_loop->num_iters));
65 } else {
66 callbacks->CommandLog("Opcode: End Loop");
67 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
68 }
69 } else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&opcode.opcode)) {
70 callbacks->CommandLog("Opcode: Load Register Static");
71 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_static->reg_index));
72 callbacks->CommandLog(fmt::format("Value: {:X}", ldr_static->value));
73 } else if (auto ldr_memory = std::get_if<LoadRegisterMemoryOpcode>(&opcode.opcode)) {
74 callbacks->CommandLog("Opcode: Load Register Memory");
75 callbacks->CommandLog(fmt::format("Bit Width: {:X}", ldr_memory->bit_width));
76 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_memory->reg_index));
77 callbacks->CommandLog(
78 fmt::format("Mem Type: {:X}", static_cast<u32>(ldr_memory->mem_type)));
79 callbacks->CommandLog(fmt::format("From Reg: {:d}", ldr_memory->load_from_reg));
80 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", ldr_memory->rel_address));
81 } else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&opcode.opcode)) {
82 callbacks->CommandLog("Opcode: Store Static to Address");
83 callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_static->bit_width));
84 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", str_static->reg_index));
85 if (str_static->add_offset_reg) {
86 callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_static->offset_reg_index));
87 }
88 callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_static->increment_reg));
89 callbacks->CommandLog(fmt::format("Value: {:X}", str_static->value));
90 } else if (auto perform_math_static =
91 std::get_if<PerformArithmeticStaticOpcode>(&opcode.opcode)) {
92 callbacks->CommandLog("Opcode: Perform Static Arithmetic");
93 callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_static->bit_width));
94 callbacks->CommandLog(fmt::format("Reg Idx: {:X}", perform_math_static->reg_index));
95 callbacks->CommandLog(
96 fmt::format("Math Type: {:X}", static_cast<u32>(perform_math_static->math_type)));
97 callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_static->value));
98 } else if (auto begin_keypress_cond =
99 std::get_if<BeginKeypressConditionalOpcode>(&opcode.opcode)) {
100 callbacks->CommandLog("Opcode: Begin Keypress Conditional");
101 callbacks->CommandLog(fmt::format("Key Mask: {:X}", begin_keypress_cond->key_mask));
102 } else if (auto perform_math_reg =
103 std::get_if<PerformArithmeticRegisterOpcode>(&opcode.opcode)) {
104 callbacks->CommandLog("Opcode: Perform Register Arithmetic");
105 callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_reg->bit_width));
106 callbacks->CommandLog(fmt::format("Dst Idx: {:X}", perform_math_reg->dst_reg_index));
107 callbacks->CommandLog(fmt::format("Src1 Idx: {:X}", perform_math_reg->src_reg_1_index));
108 if (perform_math_reg->has_immediate) {
109 callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_reg->value.bit64));
110 } else {
111 callbacks->CommandLog(
112 fmt::format("Src2 Idx: {:X}", perform_math_reg->src_reg_2_index));
113 }
114 } else if (auto str_register = std::get_if<StoreRegisterToAddressOpcode>(&opcode.opcode)) {
115 callbacks->CommandLog("Opcode: Store Register to Address");
116 callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_register->bit_width));
117 callbacks->CommandLog(fmt::format("S Reg Idx: {:X}", str_register->str_reg_index));
118 callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", str_register->addr_reg_index));
119 callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_register->increment_reg));
120 switch (str_register->ofs_type) {
121 case StoreRegisterOffsetType::None:
122 break;
123 case StoreRegisterOffsetType::Reg:
124 callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index));
125 break;
126 case StoreRegisterOffsetType::Imm:
127 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
128 break;
129 case StoreRegisterOffsetType::MemReg:
130 callbacks->CommandLog(
131 fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type)));
132 break;
133 case StoreRegisterOffsetType::MemImm:
134 case StoreRegisterOffsetType::MemImmReg:
135 callbacks->CommandLog(
136 fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type)));
137 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
138 break;
139 }
140 } else if (auto begin_reg_cond = std::get_if<BeginRegisterConditionalOpcode>(&opcode.opcode)) {
141 callbacks->CommandLog("Opcode: Begin Register Conditional");
142 callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_reg_cond->bit_width));
143 callbacks->CommandLog(
144 fmt::format("Cond Type: {:X}", static_cast<u32>(begin_reg_cond->cond_type)));
145 callbacks->CommandLog(fmt::format("V Reg Idx: {:X}", begin_reg_cond->val_reg_index));
146 switch (begin_reg_cond->comp_type) {
147 case CompareRegisterValueType::StaticValue:
148 callbacks->CommandLog("Comp Type: Static Value");
149 callbacks->CommandLog(fmt::format("Value: {:X}", begin_reg_cond->value.bit64));
150 break;
151 case CompareRegisterValueType::OtherRegister:
152 callbacks->CommandLog("Comp Type: Other Register");
153 callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", begin_reg_cond->other_reg_index));
154 break;
155 case CompareRegisterValueType::MemoryRelAddr:
156 callbacks->CommandLog("Comp Type: Memory Relative Address");
157 callbacks->CommandLog(
158 fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type)));
159 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
160 break;
161 case CompareRegisterValueType::MemoryOfsReg:
162 callbacks->CommandLog("Comp Type: Memory Offset Register");
163 callbacks->CommandLog(
164 fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type)));
165 callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
166 break;
167 case CompareRegisterValueType::RegisterRelAddr:
168 callbacks->CommandLog("Comp Type: Register Relative Address");
169 callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
170 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
171 break;
172 case CompareRegisterValueType::RegisterOfsReg:
173 callbacks->CommandLog("Comp Type: Register Offset Register");
174 callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
175 callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
176 break;
177 }
178 } else if (auto save_restore_reg = std::get_if<SaveRestoreRegisterOpcode>(&opcode.opcode)) {
179 callbacks->CommandLog("Opcode: Save or Restore Register");
180 callbacks->CommandLog(fmt::format("Dst Idx: {:X}", save_restore_reg->dst_index));
181 callbacks->CommandLog(fmt::format("Src Idx: {:X}", save_restore_reg->src_index));
182 callbacks->CommandLog(
183 fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_reg->op_type)));
184 } else if (auto save_restore_regmask =
185 std::get_if<SaveRestoreRegisterMaskOpcode>(&opcode.opcode)) {
186 callbacks->CommandLog("Opcode: Save or Restore Register Mask");
187 callbacks->CommandLog(
188 fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_regmask->op_type)));
189 for (std::size_t i = 0; i < NumRegisters; i++) {
190 callbacks->CommandLog(
191 fmt::format("Act[{:02X}]: {:d}", i, save_restore_regmask->should_operate[i]));
192 }
193 } else if (auto debug_log = std::get_if<DebugLogOpcode>(&opcode.opcode)) {
194 callbacks->CommandLog("Opcode: Debug Log");
195 callbacks->CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width));
196 callbacks->CommandLog(fmt::format("Log ID: {:X}", debug_log->log_id));
197 callbacks->CommandLog(
198 fmt::format("Val Type: {:X}", static_cast<u32>(debug_log->val_type)));
199 switch (debug_log->val_type) {
200 case DebugLogValueType::RegisterValue:
201 callbacks->CommandLog("Val Type: Register Value");
202 callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", debug_log->val_reg_index));
203 break;
204 case DebugLogValueType::MemoryRelAddr:
205 callbacks->CommandLog("Val Type: Memory Relative Address");
206 callbacks->CommandLog(
207 fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type)));
208 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
209 break;
210 case DebugLogValueType::MemoryOfsReg:
211 callbacks->CommandLog("Val Type: Memory Offset Register");
212 callbacks->CommandLog(
213 fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type)));
214 callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
215 break;
216 case DebugLogValueType::RegisterRelAddr:
217 callbacks->CommandLog("Val Type: Register Relative Address");
218 callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
219 callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
220 break;
221 case DebugLogValueType::RegisterOfsReg:
222 callbacks->CommandLog("Val Type: Register Offset Register");
223 callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
224 callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
225 break;
226 }
227 } else if (auto instr = std::get_if<UnrecognizedInstruction>(&opcode.opcode)) {
228 callbacks->CommandLog(fmt::format("Unknown opcode: {:X}", static_cast<u32>(instr->opcode)));
229 }
230}
231
232DmntCheatVm::Callbacks::~Callbacks() = default;
233
234bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
235 // If we've ever seen a decode failure, return false.
236 bool valid = decode_success;
237 CheatVmOpcode opcode = {};
238 SCOPE_EXIT({
239 decode_success &= valid;
240 if (valid) {
241 out = opcode;
242 }
243 });
244
245 // Helper function for getting instruction dwords.
246 const auto GetNextDword = [&] {
247 if (instruction_ptr >= num_opcodes) {
248 valid = false;
249 return static_cast<u32>(0);
250 }
251 return program[instruction_ptr++];
252 };
253
254 // Helper function for parsing a VmInt.
255 const auto GetNextVmInt = [&](const u32 bit_width) {
256 VmInt val{};
257
258 const u32 first_dword = GetNextDword();
259 switch (bit_width) {
260 case 1:
261 val.bit8 = static_cast<u8>(first_dword);
262 break;
263 case 2:
264 val.bit16 = static_cast<u16>(first_dword);
265 break;
266 case 4:
267 val.bit32 = first_dword;
268 break;
269 case 8:
270 val.bit64 = (static_cast<u64>(first_dword) << 32ul) | static_cast<u64>(GetNextDword());
271 break;
272 }
273
274 return val;
275 };
276
277 // Read opcode.
278 const u32 first_dword = GetNextDword();
279 if (!valid) {
280 return valid;
281 }
282
283 auto opcode_type = static_cast<CheatVmOpcodeType>(((first_dword >> 28) & 0xF));
284 if (opcode_type >= CheatVmOpcodeType::ExtendedWidth) {
285 opcode_type = static_cast<CheatVmOpcodeType>((static_cast<u32>(opcode_type) << 4) |
286 ((first_dword >> 24) & 0xF));
287 }
288 if (opcode_type >= CheatVmOpcodeType::DoubleExtendedWidth) {
289 opcode_type = static_cast<CheatVmOpcodeType>((static_cast<u32>(opcode_type) << 4) |
290 ((first_dword >> 20) & 0xF));
291 }
292
293 // detect condition start.
294 switch (opcode_type) {
295 case CheatVmOpcodeType::BeginConditionalBlock:
296 case CheatVmOpcodeType::BeginKeypressConditionalBlock:
297 case CheatVmOpcodeType::BeginRegisterConditionalBlock:
298 opcode.begin_conditional_block = true;
299 break;
300 default:
301 opcode.begin_conditional_block = false;
302 break;
303 }
304
305 switch (opcode_type) {
306 case CheatVmOpcodeType::StoreStatic: {
307 StoreStaticOpcode store_static{};
308 // 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
309 // Read additional words.
310 const u32 second_dword = GetNextDword();
311 store_static.bit_width = (first_dword >> 24) & 0xF;
312 store_static.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
313 store_static.offset_register = ((first_dword >> 16) & 0xF);
314 store_static.rel_address =
315 (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
316 store_static.value = GetNextVmInt(store_static.bit_width);
317 opcode.opcode = store_static;
318 } break;
319 case CheatVmOpcodeType::BeginConditionalBlock: {
320 BeginConditionalOpcode begin_cond{};
321 // 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
322 // Read additional words.
323 const u32 second_dword = GetNextDword();
324 begin_cond.bit_width = (first_dword >> 24) & 0xF;
325 begin_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
326 begin_cond.cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF);
327 begin_cond.rel_address =
328 (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
329 begin_cond.value = GetNextVmInt(begin_cond.bit_width);
330 opcode.opcode = begin_cond;
331 } break;
332 case CheatVmOpcodeType::EndConditionalBlock: {
333 // 20000000
334 // There's actually nothing left to process here!
335 opcode.opcode = EndConditionalOpcode{};
336 } break;
337 case CheatVmOpcodeType::ControlLoop: {
338 ControlLoopOpcode ctrl_loop{};
339 // 300R0000 VVVVVVVV
340 // 310R0000
341 // Parse register, whether loop start or loop end.
342 ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0;
343 ctrl_loop.reg_index = ((first_dword >> 20) & 0xF);
344
345 // Read number of iters if loop start.
346 if (ctrl_loop.start_loop) {
347 ctrl_loop.num_iters = GetNextDword();
348 }
349 opcode.opcode = ctrl_loop;
350 } break;
351 case CheatVmOpcodeType::LoadRegisterStatic: {
352 LoadRegisterStaticOpcode ldr_static{};
353 // 400R0000 VVVVVVVV VVVVVVVV
354 // Read additional words.
355 ldr_static.reg_index = ((first_dword >> 16) & 0xF);
356 ldr_static.value =
357 (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword());
358 opcode.opcode = ldr_static;
359 } break;
360 case CheatVmOpcodeType::LoadRegisterMemory: {
361 LoadRegisterMemoryOpcode ldr_memory{};
362 // 5TMRI0AA AAAAAAAA
363 // Read additional words.
364 const u32 second_dword = GetNextDword();
365 ldr_memory.bit_width = (first_dword >> 24) & 0xF;
366 ldr_memory.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
367 ldr_memory.reg_index = ((first_dword >> 16) & 0xF);
368 ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0;
369 ldr_memory.rel_address =
370 (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
371 opcode.opcode = ldr_memory;
372 } break;
373 case CheatVmOpcodeType::StoreStaticToAddress: {
374 StoreStaticToAddressOpcode str_static{};
375 // 6T0RIor0 VVVVVVVV VVVVVVVV
376 // Read additional words.
377 str_static.bit_width = (first_dword >> 24) & 0xF;
378 str_static.reg_index = ((first_dword >> 16) & 0xF);
379 str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0;
380 str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0;
381 str_static.offset_reg_index = ((first_dword >> 4) & 0xF);
382 str_static.value =
383 (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword());
384 opcode.opcode = str_static;
385 } break;
386 case CheatVmOpcodeType::PerformArithmeticStatic: {
387 PerformArithmeticStaticOpcode perform_math_static{};
388 // 7T0RC000 VVVVVVVV
389 // Read additional words.
390 perform_math_static.bit_width = (first_dword >> 24) & 0xF;
391 perform_math_static.reg_index = ((first_dword >> 16) & 0xF);
392 perform_math_static.math_type =
393 static_cast<RegisterArithmeticType>((first_dword >> 12) & 0xF);
394 perform_math_static.value = GetNextDword();
395 opcode.opcode = perform_math_static;
396 } break;
397 case CheatVmOpcodeType::BeginKeypressConditionalBlock: {
398 BeginKeypressConditionalOpcode begin_keypress_cond{};
399 // 8kkkkkkk
400 // Just parse the mask.
401 begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF;
402 } break;
403 case CheatVmOpcodeType::PerformArithmeticRegister: {
404 PerformArithmeticRegisterOpcode perform_math_reg{};
405 // 9TCRSIs0 (VVVVVVVV (VVVVVVVV))
406 perform_math_reg.bit_width = (first_dword >> 24) & 0xF;
407 perform_math_reg.math_type = static_cast<RegisterArithmeticType>((first_dword >> 20) & 0xF);
408 perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF);
409 perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF);
410 perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0;
411 if (perform_math_reg.has_immediate) {
412 perform_math_reg.src_reg_2_index = 0;
413 perform_math_reg.value = GetNextVmInt(perform_math_reg.bit_width);
414 } else {
415 perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF);
416 }
417 opcode.opcode = perform_math_reg;
418 } break;
419 case CheatVmOpcodeType::StoreRegisterToAddress: {
420 StoreRegisterToAddressOpcode str_register{};
421 // ATSRIOxa (aaaaaaaa)
422 // A = opcode 10
423 // T = bit width
424 // S = src register index
425 // R = address register index
426 // I = 1 if increment address register, 0 if not increment address register
427 // O = offset type, 0 = None, 1 = Register, 2 = Immediate, 3 = Memory Region,
428 // 4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region +
429 // Relative Address
430 // x = offset register (for offset type 1), memory type (for offset type 3)
431 // a = relative address (for offset type 2+3)
432 str_register.bit_width = (first_dword >> 24) & 0xF;
433 str_register.str_reg_index = ((first_dword >> 20) & 0xF);
434 str_register.addr_reg_index = ((first_dword >> 16) & 0xF);
435 str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0;
436 str_register.ofs_type = static_cast<StoreRegisterOffsetType>(((first_dword >> 8) & 0xF));
437 str_register.ofs_reg_index = ((first_dword >> 4) & 0xF);
438 switch (str_register.ofs_type) {
439 case StoreRegisterOffsetType::None:
440 case StoreRegisterOffsetType::Reg:
441 // Nothing more to do
442 break;
443 case StoreRegisterOffsetType::Imm:
444 str_register.rel_address =
445 ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
446 break;
447 case StoreRegisterOffsetType::MemReg:
448 str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
449 break;
450 case StoreRegisterOffsetType::MemImm:
451 case StoreRegisterOffsetType::MemImmReg:
452 str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
453 str_register.rel_address =
454 ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
455 break;
456 default:
457 str_register.ofs_type = StoreRegisterOffsetType::None;
458 break;
459 }
460 opcode.opcode = str_register;
461 } break;
462 case CheatVmOpcodeType::BeginRegisterConditionalBlock: {
463 BeginRegisterConditionalOpcode begin_reg_cond{};
464 // C0TcSX##
465 // C0TcS0Ma aaaaaaaa
466 // C0TcS1Mr
467 // C0TcS2Ra aaaaaaaa
468 // C0TcS3Rr
469 // C0TcS400 VVVVVVVV (VVVVVVVV)
470 // C0TcS5X0
471 // C0 = opcode 0xC0
472 // T = bit width
473 // c = condition type.
474 // S = source register.
475 // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset
476 // register,
477 // 2 = register with relative offset, 3 = register with offset register, 4 = static
478 // value, 5 = other register.
479 // M = memory type.
480 // R = address register.
481 // a = relative address.
482 // r = offset register.
483 // X = other register.
484 // V = value.
485 begin_reg_cond.bit_width = (first_dword >> 20) & 0xF;
486 begin_reg_cond.cond_type =
487 static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF);
488 begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF);
489 begin_reg_cond.comp_type = static_cast<CompareRegisterValueType>((first_dword >> 8) & 0xF);
490
491 switch (begin_reg_cond.comp_type) {
492 case CompareRegisterValueType::StaticValue:
493 begin_reg_cond.value = GetNextVmInt(begin_reg_cond.bit_width);
494 break;
495 case CompareRegisterValueType::OtherRegister:
496 begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF);
497 break;
498 case CompareRegisterValueType::MemoryRelAddr:
499 begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
500 begin_reg_cond.rel_address =
501 ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
502 break;
503 case CompareRegisterValueType::MemoryOfsReg:
504 begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
505 begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
506 break;
507 case CompareRegisterValueType::RegisterRelAddr:
508 begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
509 begin_reg_cond.rel_address =
510 ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
511 break;
512 case CompareRegisterValueType::RegisterOfsReg:
513 begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
514 begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
515 break;
516 }
517 opcode.opcode = begin_reg_cond;
518 } break;
519 case CheatVmOpcodeType::SaveRestoreRegister: {
520 SaveRestoreRegisterOpcode save_restore_reg{};
521 // C10D0Sx0
522 // C1 = opcode 0xC1
523 // D = destination index.
524 // S = source index.
525 // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring
526 // a register.
527 // NOTE: If we add more save slots later, current encoding is backwards compatible.
528 save_restore_reg.dst_index = (first_dword >> 16) & 0xF;
529 save_restore_reg.src_index = (first_dword >> 8) & 0xF;
530 save_restore_reg.op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 4) & 0xF);
531 opcode.opcode = save_restore_reg;
532 } break;
533 case CheatVmOpcodeType::SaveRestoreRegisterMask: {
534 SaveRestoreRegisterMaskOpcode save_restore_regmask{};
535 // C2x0XXXX
536 // C2 = opcode 0xC2
537 // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring.
538 // X = 16-bit bitmask, bit i --> save or restore register i.
539 save_restore_regmask.op_type =
540 static_cast<SaveRestoreRegisterOpType>((first_dword >> 20) & 0xF);
541 for (std::size_t i = 0; i < NumRegisters; i++) {
542 save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0;
543 }
544 opcode.opcode = save_restore_regmask;
545 } break;
546 case CheatVmOpcodeType::DebugLog: {
547 DebugLogOpcode debug_log{};
548 // FFFTIX##
549 // FFFTI0Ma aaaaaaaa
550 // FFFTI1Mr
551 // FFFTI2Ra aaaaaaaa
552 // FFFTI3Rr
553 // FFFTI4X0
554 // FFF = opcode 0xFFF
555 // T = bit width.
556 // I = log id.
557 // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset
558 // register,
559 // 2 = register with relative offset, 3 = register with offset register, 4 = register
560 // value.
561 // M = memory type.
562 // R = address register.
563 // a = relative address.
564 // r = offset register.
565 // X = value register.
566 debug_log.bit_width = (first_dword >> 16) & 0xF;
567 debug_log.log_id = ((first_dword >> 12) & 0xF);
568 debug_log.val_type = static_cast<DebugLogValueType>((first_dword >> 8) & 0xF);
569
570 switch (debug_log.val_type) {
571 case DebugLogValueType::RegisterValue:
572 debug_log.val_reg_index = ((first_dword >> 4) & 0xF);
573 break;
574 case DebugLogValueType::MemoryRelAddr:
575 debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
576 debug_log.rel_address =
577 ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
578 break;
579 case DebugLogValueType::MemoryOfsReg:
580 debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
581 debug_log.ofs_reg_index = (first_dword & 0xF);
582 break;
583 case DebugLogValueType::RegisterRelAddr:
584 debug_log.addr_reg_index = ((first_dword >> 4) & 0xF);
585 debug_log.rel_address =
586 ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
587 break;
588 case DebugLogValueType::RegisterOfsReg:
589 debug_log.addr_reg_index = ((first_dword >> 4) & 0xF);
590 debug_log.ofs_reg_index = (first_dword & 0xF);
591 break;
592 }
593 opcode.opcode = debug_log;
594 } break;
595 case CheatVmOpcodeType::ExtendedWidth:
596 case CheatVmOpcodeType::DoubleExtendedWidth:
597 default:
598 // Unrecognized instruction cannot be decoded.
599 valid = false;
600 opcode.opcode = UnrecognizedInstruction{opcode_type};
601 break;
602 }
603
604 // End decoding.
605 return valid;
606}
607
608void DmntCheatVm::SkipConditionalBlock() {
609 if (condition_depth > 0) {
610 // We want to continue until we're out of the current block.
611 const std::size_t desired_depth = condition_depth - 1;
612
613 CheatVmOpcode skip_opcode{};
614 while (condition_depth > desired_depth && DecodeNextOpcode(skip_opcode)) {
615 // Decode instructions until we see end of the current conditional block.
616 // NOTE: This is broken in gateway's implementation.
617 // Gateway currently checks for "0x2" instead of "0x20000000"
618 // In addition, they do a linear scan instead of correctly decoding opcodes.
619 // This causes issues if "0x2" appears as an immediate in the conditional block...
620
621 // We also support nesting of conditional blocks, and Gateway does not.
622 if (skip_opcode.begin_conditional_block) {
623 condition_depth++;
624 } else if (std::holds_alternative<EndConditionalOpcode>(skip_opcode.opcode)) {
625 condition_depth--;
626 }
627 }
628 } else {
629 // Skipping, but condition_depth = 0.
630 // This is an error condition.
631 // However, I don't actually believe it is possible for this to happen.
632 // I guess we'll throw a fatal error here, so as to encourage me to fix the VM
633 // in the event that someone triggers it? I don't know how you'd do that.
634 UNREACHABLE_MSG("Invalid condition depth in DMNT Cheat VM");
635 }
636}
637
638u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) {
639 switch (bit_width) {
640 case 1:
641 return value.bit8;
642 case 2:
643 return value.bit16;
644 case 4:
645 return value.bit32;
646 case 8:
647 return value.bit64;
648 default:
649 // Invalid bit width -> return 0.
650 return 0;
651 }
652}
653
654u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata,
655 MemoryAccessType mem_type, u64 rel_address) {
656 switch (mem_type) {
657 case MemoryAccessType::MainNso:
658 default:
659 return metadata.main_nso_extents.base + rel_address;
660 case MemoryAccessType::Heap:
661 return metadata.heap_extents.base + rel_address;
662 }
663}
664
665void DmntCheatVm::ResetState() {
666 registers.fill(0);
667 saved_values.fill(0);
668 loop_tops.fill(0);
669 instruction_ptr = 0;
670 condition_depth = 0;
671 decode_success = true;
672}
673
674bool DmntCheatVm::LoadProgram(const std::vector<CheatEntry>& entries) {
675 // Reset opcode count.
676 num_opcodes = 0;
677
678 for (std::size_t i = 0; i < entries.size(); i++) {
679 if (entries[i].enabled) {
680 // Bounds check.
681 if (entries[i].definition.num_opcodes + num_opcodes > MaximumProgramOpcodeCount) {
682 num_opcodes = 0;
683 return false;
684 }
685
686 for (std::size_t n = 0; n < entries[i].definition.num_opcodes; n++) {
687 program[num_opcodes++] = entries[i].definition.opcodes[n];
688 }
689 }
690 }
691
692 return true;
693}
694
695void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
696 CheatVmOpcode cur_opcode{};
697
698 // Get Keys down.
699 u64 kDown = callbacks->HidKeysDown();
700
701 callbacks->CommandLog("Started VM execution.");
702 callbacks->CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base));
703 callbacks->CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base));
704 callbacks->CommandLog(fmt::format("Keys Down: {:08X}", static_cast<u32>(kDown & 0x0FFFFFFF)));
705
706 // Clear VM state.
707 ResetState();
708
709 // Loop until program finishes.
710 while (DecodeNextOpcode(cur_opcode)) {
711 callbacks->CommandLog(
712 fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(instruction_ptr)));
713
714 for (std::size_t i = 0; i < NumRegisters; i++) {
715 callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i]));
716 }
717
718 for (std::size_t i = 0; i < NumRegisters; i++) {
719 callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i]));
720 }
721 LogOpcode(cur_opcode);
722
723 // Increment conditional depth, if relevant.
724 if (cur_opcode.begin_conditional_block) {
725 condition_depth++;
726 }
727
728 if (auto store_static = std::get_if<StoreStaticOpcode>(&cur_opcode.opcode)) {
729 // Calculate address, write value to memory.
730 u64 dst_address = GetCheatProcessAddress(metadata, store_static->mem_type,
731 store_static->rel_address +
732 registers[store_static->offset_register]);
733 u64 dst_value = GetVmInt(store_static->value, store_static->bit_width);
734 switch (store_static->bit_width) {
735 case 1:
736 case 2:
737 case 4:
738 case 8:
739 callbacks->MemoryWrite(dst_address, &dst_value, store_static->bit_width);
740 break;
741 }
742 } else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&cur_opcode.opcode)) {
743 // Read value from memory.
744 u64 src_address =
745 GetCheatProcessAddress(metadata, begin_cond->mem_type, begin_cond->rel_address);
746 u64 src_value = 0;
747 switch (store_static->bit_width) {
748 case 1:
749 case 2:
750 case 4:
751 case 8:
752 callbacks->MemoryRead(src_address, &src_value, begin_cond->bit_width);
753 break;
754 }
755 // Check against condition.
756 u64 cond_value = GetVmInt(begin_cond->value, begin_cond->bit_width);
757 bool cond_met = false;
758 switch (begin_cond->cond_type) {
759 case ConditionalComparisonType::GT:
760 cond_met = src_value > cond_value;
761 break;
762 case ConditionalComparisonType::GE:
763 cond_met = src_value >= cond_value;
764 break;
765 case ConditionalComparisonType::LT:
766 cond_met = src_value < cond_value;
767 break;
768 case ConditionalComparisonType::LE:
769 cond_met = src_value <= cond_value;
770 break;
771 case ConditionalComparisonType::EQ:
772 cond_met = src_value == cond_value;
773 break;
774 case ConditionalComparisonType::NE:
775 cond_met = src_value != cond_value;
776 break;
777 }
778 // Skip conditional block if condition not met.
779 if (!cond_met) {
780 SkipConditionalBlock();
781 }
782 } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&cur_opcode.opcode)) {
783 // Decrement the condition depth.
784 // We will assume, graciously, that mismatched conditional block ends are a nop.
785 if (condition_depth > 0) {
786 condition_depth--;
787 }
788 } else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&cur_opcode.opcode)) {
789 if (ctrl_loop->start_loop) {
790 // Start a loop.
791 registers[ctrl_loop->reg_index] = ctrl_loop->num_iters;
792 loop_tops[ctrl_loop->reg_index] = instruction_ptr;
793 } else {
794 // End a loop.
795 registers[ctrl_loop->reg_index]--;
796 if (registers[ctrl_loop->reg_index] != 0) {
797 instruction_ptr = loop_tops[ctrl_loop->reg_index];
798 }
799 }
800 } else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&cur_opcode.opcode)) {
801 // Set a register to a static value.
802 registers[ldr_static->reg_index] = ldr_static->value;
803 } else if (auto ldr_memory = std::get_if<LoadRegisterMemoryOpcode>(&cur_opcode.opcode)) {
804 // Choose source address.
805 u64 src_address;
806 if (ldr_memory->load_from_reg) {
807 src_address = registers[ldr_memory->reg_index] + ldr_memory->rel_address;
808 } else {
809 src_address =
810 GetCheatProcessAddress(metadata, ldr_memory->mem_type, ldr_memory->rel_address);
811 }
812 // Read into register. Gateway only reads on valid bitwidth.
813 switch (ldr_memory->bit_width) {
814 case 1:
815 case 2:
816 case 4:
817 case 8:
818 callbacks->MemoryRead(src_address, &registers[ldr_memory->reg_index],
819 ldr_memory->bit_width);
820 break;
821 }
822 } else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&cur_opcode.opcode)) {
823 // Calculate address.
824 u64 dst_address = registers[str_static->reg_index];
825 u64 dst_value = str_static->value;
826 if (str_static->add_offset_reg) {
827 dst_address += registers[str_static->offset_reg_index];
828 }
829 // Write value to memory. Gateway only writes on valid bitwidth.
830 switch (str_static->bit_width) {
831 case 1:
832 case 2:
833 case 4:
834 case 8:
835 callbacks->MemoryWrite(dst_address, &dst_value, str_static->bit_width);
836 break;
837 }
838 // Increment register if relevant.
839 if (str_static->increment_reg) {
840 registers[str_static->reg_index] += str_static->bit_width;
841 }
842 } else if (auto perform_math_static =
843 std::get_if<PerformArithmeticStaticOpcode>(&cur_opcode.opcode)) {
844 // Do requested math.
845 switch (perform_math_static->math_type) {
846 case RegisterArithmeticType::Addition:
847 registers[perform_math_static->reg_index] +=
848 static_cast<u64>(perform_math_static->value);
849 break;
850 case RegisterArithmeticType::Subtraction:
851 registers[perform_math_static->reg_index] -=
852 static_cast<u64>(perform_math_static->value);
853 break;
854 case RegisterArithmeticType::Multiplication:
855 registers[perform_math_static->reg_index] *=
856 static_cast<u64>(perform_math_static->value);
857 break;
858 case RegisterArithmeticType::LeftShift:
859 registers[perform_math_static->reg_index] <<=
860 static_cast<u64>(perform_math_static->value);
861 break;
862 case RegisterArithmeticType::RightShift:
863 registers[perform_math_static->reg_index] >>=
864 static_cast<u64>(perform_math_static->value);
865 break;
866 default:
867 // Do not handle extensions here.
868 break;
869 }
870 // Apply bit width.
871 switch (perform_math_static->bit_width) {
872 case 1:
873 registers[perform_math_static->reg_index] =
874 static_cast<u8>(registers[perform_math_static->reg_index]);
875 break;
876 case 2:
877 registers[perform_math_static->reg_index] =
878 static_cast<u16>(registers[perform_math_static->reg_index]);
879 break;
880 case 4:
881 registers[perform_math_static->reg_index] =
882 static_cast<u32>(registers[perform_math_static->reg_index]);
883 break;
884 case 8:
885 registers[perform_math_static->reg_index] =
886 static_cast<u64>(registers[perform_math_static->reg_index]);
887 break;
888 }
889 } else if (auto begin_keypress_cond =
890 std::get_if<BeginKeypressConditionalOpcode>(&cur_opcode.opcode)) {
891 // Check for keypress.
892 if ((begin_keypress_cond->key_mask & kDown) != begin_keypress_cond->key_mask) {
893 // Keys not pressed. Skip conditional block.
894 SkipConditionalBlock();
895 }
896 } else if (auto perform_math_reg =
897 std::get_if<PerformArithmeticRegisterOpcode>(&cur_opcode.opcode)) {
898 const u64 operand_1_value = registers[perform_math_reg->src_reg_1_index];
899 const u64 operand_2_value =
900 perform_math_reg->has_immediate
901 ? GetVmInt(perform_math_reg->value, perform_math_reg->bit_width)
902 : registers[perform_math_reg->src_reg_2_index];
903
904 u64 res_val = 0;
905 // Do requested math.
906 switch (perform_math_reg->math_type) {
907 case RegisterArithmeticType::Addition:
908 res_val = operand_1_value + operand_2_value;
909 break;
910 case RegisterArithmeticType::Subtraction:
911 res_val = operand_1_value - operand_2_value;
912 break;
913 case RegisterArithmeticType::Multiplication:
914 res_val = operand_1_value * operand_2_value;
915 break;
916 case RegisterArithmeticType::LeftShift:
917 res_val = operand_1_value << operand_2_value;
918 break;
919 case RegisterArithmeticType::RightShift:
920 res_val = operand_1_value >> operand_2_value;
921 break;
922 case RegisterArithmeticType::LogicalAnd:
923 res_val = operand_1_value & operand_2_value;
924 break;
925 case RegisterArithmeticType::LogicalOr:
926 res_val = operand_1_value | operand_2_value;
927 break;
928 case RegisterArithmeticType::LogicalNot:
929 res_val = ~operand_1_value;
930 break;
931 case RegisterArithmeticType::LogicalXor:
932 res_val = operand_1_value ^ operand_2_value;
933 break;
934 case RegisterArithmeticType::None:
935 res_val = operand_1_value;
936 break;
937 }
938
939 // Apply bit width.
940 switch (perform_math_reg->bit_width) {
941 case 1:
942 res_val = static_cast<u8>(res_val);
943 break;
944 case 2:
945 res_val = static_cast<u16>(res_val);
946 break;
947 case 4:
948 res_val = static_cast<u32>(res_val);
949 break;
950 case 8:
951 res_val = static_cast<u64>(res_val);
952 break;
953 }
954
955 // Save to register.
956 registers[perform_math_reg->dst_reg_index] = res_val;
957 } else if (auto str_register =
958 std::get_if<StoreRegisterToAddressOpcode>(&cur_opcode.opcode)) {
959 // Calculate address.
960 u64 dst_value = registers[str_register->str_reg_index];
961 u64 dst_address = registers[str_register->addr_reg_index];
962 switch (str_register->ofs_type) {
963 case StoreRegisterOffsetType::None:
964 // Nothing more to do
965 break;
966 case StoreRegisterOffsetType::Reg:
967 dst_address += registers[str_register->ofs_reg_index];
968 break;
969 case StoreRegisterOffsetType::Imm:
970 dst_address += str_register->rel_address;
971 break;
972 case StoreRegisterOffsetType::MemReg:
973 dst_address = GetCheatProcessAddress(metadata, str_register->mem_type,
974 registers[str_register->addr_reg_index]);
975 break;
976 case StoreRegisterOffsetType::MemImm:
977 dst_address = GetCheatProcessAddress(metadata, str_register->mem_type,
978 str_register->rel_address);
979 break;
980 case StoreRegisterOffsetType::MemImmReg:
981 dst_address = GetCheatProcessAddress(metadata, str_register->mem_type,
982 registers[str_register->addr_reg_index] +
983 str_register->rel_address);
984 break;
985 }
986
987 // Write value to memory. Write only on valid bitwidth.
988 switch (str_register->bit_width) {
989 case 1:
990 case 2:
991 case 4:
992 case 8:
993 callbacks->MemoryWrite(dst_address, &dst_value, str_register->bit_width);
994 break;
995 }
996
997 // Increment register if relevant.
998 if (str_register->increment_reg) {
999 registers[str_register->addr_reg_index] += str_register->bit_width;
1000 }
1001 } else if (auto begin_reg_cond =
1002 std::get_if<BeginRegisterConditionalOpcode>(&cur_opcode.opcode)) {
1003 // Get value from register.
1004 u64 src_value = 0;
1005 switch (begin_reg_cond->bit_width) {
1006 case 1:
1007 src_value = static_cast<u8>(registers[begin_reg_cond->val_reg_index] & 0xFFul);
1008 break;
1009 case 2:
1010 src_value = static_cast<u16>(registers[begin_reg_cond->val_reg_index] & 0xFFFFul);
1011 break;
1012 case 4:
1013 src_value =
1014 static_cast<u32>(registers[begin_reg_cond->val_reg_index] & 0xFFFFFFFFul);
1015 break;
1016 case 8:
1017 src_value = static_cast<u64>(registers[begin_reg_cond->val_reg_index] &
1018 0xFFFFFFFFFFFFFFFFul);
1019 break;
1020 }
1021
1022 // Read value from memory.
1023 u64 cond_value = 0;
1024 if (begin_reg_cond->comp_type == CompareRegisterValueType::StaticValue) {
1025 cond_value = GetVmInt(begin_reg_cond->value, begin_reg_cond->bit_width);
1026 } else if (begin_reg_cond->comp_type == CompareRegisterValueType::OtherRegister) {
1027 switch (begin_reg_cond->bit_width) {
1028 case 1:
1029 cond_value =
1030 static_cast<u8>(registers[begin_reg_cond->other_reg_index] & 0xFFul);
1031 break;
1032 case 2:
1033 cond_value =
1034 static_cast<u16>(registers[begin_reg_cond->other_reg_index] & 0xFFFFul);
1035 break;
1036 case 4:
1037 cond_value =
1038 static_cast<u32>(registers[begin_reg_cond->other_reg_index] & 0xFFFFFFFFul);
1039 break;
1040 case 8:
1041 cond_value = static_cast<u64>(registers[begin_reg_cond->other_reg_index] &
1042 0xFFFFFFFFFFFFFFFFul);
1043 break;
1044 }
1045 } else {
1046 u64 cond_address = 0;
1047 switch (begin_reg_cond->comp_type) {
1048 case CompareRegisterValueType::MemoryRelAddr:
1049 cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type,
1050 begin_reg_cond->rel_address);
1051 break;
1052 case CompareRegisterValueType::MemoryOfsReg:
1053 cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type,
1054 registers[begin_reg_cond->ofs_reg_index]);
1055 break;
1056 case CompareRegisterValueType::RegisterRelAddr:
1057 cond_address =
1058 registers[begin_reg_cond->addr_reg_index] + begin_reg_cond->rel_address;
1059 break;
1060 case CompareRegisterValueType::RegisterOfsReg:
1061 cond_address = registers[begin_reg_cond->addr_reg_index] +
1062 registers[begin_reg_cond->ofs_reg_index];
1063 break;
1064 default:
1065 break;
1066 }
1067 switch (begin_reg_cond->bit_width) {
1068 case 1:
1069 case 2:
1070 case 4:
1071 case 8:
1072 callbacks->MemoryRead(cond_address, &cond_value, begin_reg_cond->bit_width);
1073 break;
1074 }
1075 }
1076
1077 // Check against condition.
1078 bool cond_met = false;
1079 switch (begin_reg_cond->cond_type) {
1080 case ConditionalComparisonType::GT:
1081 cond_met = src_value > cond_value;
1082 break;
1083 case ConditionalComparisonType::GE:
1084 cond_met = src_value >= cond_value;
1085 break;
1086 case ConditionalComparisonType::LT:
1087 cond_met = src_value < cond_value;
1088 break;
1089 case ConditionalComparisonType::LE:
1090 cond_met = src_value <= cond_value;
1091 break;
1092 case ConditionalComparisonType::EQ:
1093 cond_met = src_value == cond_value;
1094 break;
1095 case ConditionalComparisonType::NE:
1096 cond_met = src_value != cond_value;
1097 break;
1098 }
1099
1100 // Skip conditional block if condition not met.
1101 if (!cond_met) {
1102 SkipConditionalBlock();
1103 }
1104 } else if (auto save_restore_reg =
1105 std::get_if<SaveRestoreRegisterOpcode>(&cur_opcode.opcode)) {
1106 // Save or restore a register.
1107 switch (save_restore_reg->op_type) {
1108 case SaveRestoreRegisterOpType::ClearRegs:
1109 registers[save_restore_reg->dst_index] = 0ul;
1110 break;
1111 case SaveRestoreRegisterOpType::ClearSaved:
1112 saved_values[save_restore_reg->dst_index] = 0ul;
1113 break;
1114 case SaveRestoreRegisterOpType::Save:
1115 saved_values[save_restore_reg->dst_index] = registers[save_restore_reg->src_index];
1116 break;
1117 case SaveRestoreRegisterOpType::Restore:
1118 default:
1119 registers[save_restore_reg->dst_index] = saved_values[save_restore_reg->src_index];
1120 break;
1121 }
1122 } else if (auto save_restore_regmask =
1123 std::get_if<SaveRestoreRegisterMaskOpcode>(&cur_opcode.opcode)) {
1124 // Save or restore register mask.
1125 u64* src;
1126 u64* dst;
1127 switch (save_restore_regmask->op_type) {
1128 case SaveRestoreRegisterOpType::ClearSaved:
1129 case SaveRestoreRegisterOpType::Save:
1130 src = registers.data();
1131 dst = saved_values.data();
1132 break;
1133 case SaveRestoreRegisterOpType::ClearRegs:
1134 case SaveRestoreRegisterOpType::Restore:
1135 default:
1136 src = registers.data();
1137 dst = saved_values.data();
1138 break;
1139 }
1140 for (std::size_t i = 0; i < NumRegisters; i++) {
1141 if (save_restore_regmask->should_operate[i]) {
1142 switch (save_restore_regmask->op_type) {
1143 case SaveRestoreRegisterOpType::ClearSaved:
1144 case SaveRestoreRegisterOpType::ClearRegs:
1145 dst[i] = 0ul;
1146 break;
1147 case SaveRestoreRegisterOpType::Save:
1148 case SaveRestoreRegisterOpType::Restore:
1149 default:
1150 dst[i] = src[i];
1151 break;
1152 }
1153 }
1154 }
1155 } else if (auto debug_log = std::get_if<DebugLogOpcode>(&cur_opcode.opcode)) {
1156 // Read value from memory.
1157 u64 log_value = 0;
1158 if (debug_log->val_type == DebugLogValueType::RegisterValue) {
1159 switch (debug_log->bit_width) {
1160 case 1:
1161 log_value = static_cast<u8>(registers[debug_log->val_reg_index] & 0xFFul);
1162 break;
1163 case 2:
1164 log_value = static_cast<u16>(registers[debug_log->val_reg_index] & 0xFFFFul);
1165 break;
1166 case 4:
1167 log_value =
1168 static_cast<u32>(registers[debug_log->val_reg_index] & 0xFFFFFFFFul);
1169 break;
1170 case 8:
1171 log_value = static_cast<u64>(registers[debug_log->val_reg_index] &
1172 0xFFFFFFFFFFFFFFFFul);
1173 break;
1174 }
1175 } else {
1176 u64 val_address = 0;
1177 switch (debug_log->val_type) {
1178 case DebugLogValueType::MemoryRelAddr:
1179 val_address = GetCheatProcessAddress(metadata, debug_log->mem_type,
1180 debug_log->rel_address);
1181 break;
1182 case DebugLogValueType::MemoryOfsReg:
1183 val_address = GetCheatProcessAddress(metadata, debug_log->mem_type,
1184 registers[debug_log->ofs_reg_index]);
1185 break;
1186 case DebugLogValueType::RegisterRelAddr:
1187 val_address = registers[debug_log->addr_reg_index] + debug_log->rel_address;
1188 break;
1189 case DebugLogValueType::RegisterOfsReg:
1190 val_address =
1191 registers[debug_log->addr_reg_index] + registers[debug_log->ofs_reg_index];
1192 break;
1193 default:
1194 break;
1195 }
1196 switch (debug_log->bit_width) {
1197 case 1:
1198 case 2:
1199 case 4:
1200 case 8:
1201 callbacks->MemoryRead(val_address, &log_value, debug_log->bit_width);
1202 break;
1203 }
1204 }
1205
1206 // Log value.
1207 DebugLog(debug_log->log_id, log_value);
1208 }
1209 }
1210}
1211
1212} // namespace Memory
diff --git a/src/core/memory/dmnt_cheat_vm.h b/src/core/memory/dmnt_cheat_vm.h
new file mode 100644
index 000000000..c36212cf1
--- /dev/null
+++ b/src/core/memory/dmnt_cheat_vm.h
@@ -0,0 +1,321 @@
1/*
2 * Copyright (c) 2018-2019 Atmosphère-NX
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Adapted by DarkLordZach for use/interaction with yuzu
19 *
20 * Modifications Copyright 2019 yuzu emulator team
21 * Licensed under GPLv2 or any later version
22 * Refer to the license.txt file included.
23 */
24
25#pragma once
26
27#include <variant>
28#include <vector>
29#include <fmt/printf.h>
30#include "common/common_types.h"
31#include "core/memory/dmnt_cheat_types.h"
32
33namespace Memory {
34
35enum class CheatVmOpcodeType : u32 {
36 StoreStatic = 0,
37 BeginConditionalBlock = 1,
38 EndConditionalBlock = 2,
39 ControlLoop = 3,
40 LoadRegisterStatic = 4,
41 LoadRegisterMemory = 5,
42 StoreStaticToAddress = 6,
43 PerformArithmeticStatic = 7,
44 BeginKeypressConditionalBlock = 8,
45
46 // These are not implemented by Gateway's VM.
47 PerformArithmeticRegister = 9,
48 StoreRegisterToAddress = 10,
49 Reserved11 = 11,
50
51 // This is a meta entry, and not a real opcode.
52 // This is to facilitate multi-nybble instruction decoding.
53 ExtendedWidth = 12,
54
55 // Extended width opcodes.
56 BeginRegisterConditionalBlock = 0xC0,
57 SaveRestoreRegister = 0xC1,
58 SaveRestoreRegisterMask = 0xC2,
59
60 // This is a meta entry, and not a real opcode.
61 // This is to facilitate multi-nybble instruction decoding.
62 DoubleExtendedWidth = 0xF0,
63
64 // Double-extended width opcodes.
65 DebugLog = 0xFFF,
66};
67
68enum class MemoryAccessType : u32 {
69 MainNso = 0,
70 Heap = 1,
71};
72
73enum class ConditionalComparisonType : u32 {
74 GT = 1,
75 GE = 2,
76 LT = 3,
77 LE = 4,
78 EQ = 5,
79 NE = 6,
80};
81
82enum class RegisterArithmeticType : u32 {
83 Addition = 0,
84 Subtraction = 1,
85 Multiplication = 2,
86 LeftShift = 3,
87 RightShift = 4,
88
89 // These are not supported by Gateway's VM.
90 LogicalAnd = 5,
91 LogicalOr = 6,
92 LogicalNot = 7,
93 LogicalXor = 8,
94
95 None = 9,
96};
97
98enum class StoreRegisterOffsetType : u32 {
99 None = 0,
100 Reg = 1,
101 Imm = 2,
102 MemReg = 3,
103 MemImm = 4,
104 MemImmReg = 5,
105};
106
107enum class CompareRegisterValueType : u32 {
108 MemoryRelAddr = 0,
109 MemoryOfsReg = 1,
110 RegisterRelAddr = 2,
111 RegisterOfsReg = 3,
112 StaticValue = 4,
113 OtherRegister = 5,
114};
115
116enum class SaveRestoreRegisterOpType : u32 {
117 Restore = 0,
118 Save = 1,
119 ClearSaved = 2,
120 ClearRegs = 3,
121};
122
123enum class DebugLogValueType : u32 {
124 MemoryRelAddr = 0,
125 MemoryOfsReg = 1,
126 RegisterRelAddr = 2,
127 RegisterOfsReg = 3,
128 RegisterValue = 4,
129};
130
131union VmInt {
132 u8 bit8;
133 u16 bit16;
134 u32 bit32;
135 u64 bit64;
136};
137
138struct StoreStaticOpcode {
139 u32 bit_width{};
140 MemoryAccessType mem_type{};
141 u32 offset_register{};
142 u64 rel_address{};
143 VmInt value{};
144};
145
146struct BeginConditionalOpcode {
147 u32 bit_width{};
148 MemoryAccessType mem_type{};
149 ConditionalComparisonType cond_type{};
150 u64 rel_address{};
151 VmInt value{};
152};
153
154struct EndConditionalOpcode {};
155
156struct ControlLoopOpcode {
157 bool start_loop{};
158 u32 reg_index{};
159 u32 num_iters{};
160};
161
162struct LoadRegisterStaticOpcode {
163 u32 reg_index{};
164 u64 value{};
165};
166
167struct LoadRegisterMemoryOpcode {
168 u32 bit_width{};
169 MemoryAccessType mem_type{};
170 u32 reg_index{};
171 bool load_from_reg{};
172 u64 rel_address{};
173};
174
175struct StoreStaticToAddressOpcode {
176 u32 bit_width{};
177 u32 reg_index{};
178 bool increment_reg{};
179 bool add_offset_reg{};
180 u32 offset_reg_index{};
181 u64 value{};
182};
183
184struct PerformArithmeticStaticOpcode {
185 u32 bit_width{};
186 u32 reg_index{};
187 RegisterArithmeticType math_type{};
188 u32 value{};
189};
190
191struct BeginKeypressConditionalOpcode {
192 u32 key_mask{};
193};
194
195struct PerformArithmeticRegisterOpcode {
196 u32 bit_width{};
197 RegisterArithmeticType math_type{};
198 u32 dst_reg_index{};
199 u32 src_reg_1_index{};
200 u32 src_reg_2_index{};
201 bool has_immediate{};
202 VmInt value{};
203};
204
205struct StoreRegisterToAddressOpcode {
206 u32 bit_width{};
207 u32 str_reg_index{};
208 u32 addr_reg_index{};
209 bool increment_reg{};
210 StoreRegisterOffsetType ofs_type{};
211 MemoryAccessType mem_type{};
212 u32 ofs_reg_index{};
213 u64 rel_address{};
214};
215
216struct BeginRegisterConditionalOpcode {
217 u32 bit_width{};
218 ConditionalComparisonType cond_type{};
219 u32 val_reg_index{};
220 CompareRegisterValueType comp_type{};
221 MemoryAccessType mem_type{};
222 u32 addr_reg_index{};
223 u32 other_reg_index{};
224 u32 ofs_reg_index{};
225 u64 rel_address{};
226 VmInt value{};
227};
228
229struct SaveRestoreRegisterOpcode {
230 u32 dst_index{};
231 u32 src_index{};
232 SaveRestoreRegisterOpType op_type{};
233};
234
235struct SaveRestoreRegisterMaskOpcode {
236 SaveRestoreRegisterOpType op_type{};
237 std::array<bool, 0x10> should_operate{};
238};
239
240struct DebugLogOpcode {
241 u32 bit_width{};
242 u32 log_id{};
243 DebugLogValueType val_type{};
244 MemoryAccessType mem_type{};
245 u32 addr_reg_index{};
246 u32 val_reg_index{};
247 u32 ofs_reg_index{};
248 u64 rel_address{};
249};
250
251struct UnrecognizedInstruction {
252 CheatVmOpcodeType opcode{};
253};
254
255struct CheatVmOpcode {
256 bool begin_conditional_block{};
257 std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
258 LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
259 PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
260 PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
261 BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
262 SaveRestoreRegisterMaskOpcode, DebugLogOpcode, UnrecognizedInstruction>
263 opcode{};
264};
265
266class DmntCheatVm {
267public:
268 /// Helper Type for DmntCheatVm <=> yuzu Interface
269 class Callbacks {
270 public:
271 virtual ~Callbacks();
272
273 virtual void MemoryRead(VAddr address, void* data, u64 size) = 0;
274 virtual void MemoryWrite(VAddr address, const void* data, u64 size) = 0;
275
276 virtual u64 HidKeysDown() = 0;
277
278 virtual void DebugLog(u8 id, u64 value) = 0;
279 virtual void CommandLog(std::string_view data) = 0;
280 };
281
282 static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
283 static constexpr std::size_t NumRegisters = 0x10;
284
285 explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks);
286 ~DmntCheatVm();
287
288 std::size_t GetProgramSize() const {
289 return this->num_opcodes;
290 }
291
292 bool LoadProgram(const std::vector<CheatEntry>& cheats);
293 void Execute(const CheatProcessMetadata& metadata);
294
295private:
296 std::unique_ptr<Callbacks> callbacks;
297
298 std::size_t num_opcodes = 0;
299 std::size_t instruction_ptr = 0;
300 std::size_t condition_depth = 0;
301 bool decode_success = false;
302 std::array<u32, MaximumProgramOpcodeCount> program{};
303 std::array<u64, NumRegisters> registers{};
304 std::array<u64, NumRegisters> saved_values{};
305 std::array<std::size_t, NumRegisters> loop_tops{};
306
307 bool DecodeNextOpcode(CheatVmOpcode& out);
308 void SkipConditionalBlock();
309 void ResetState();
310
311 // For implementing the DebugLog opcode.
312 void DebugLog(u32 log_id, u64 value);
313
314 void LogOpcode(const CheatVmOpcode& opcode);
315
316 static u64 GetVmInt(VmInt value, u32 bit_width);
317 static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata,
318 MemoryAccessType mem_type, u64 rel_address);
319};
320
321}; // namespace Memory
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index 4afd6c8a3..d2c69d1a0 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -4,8 +4,14 @@
4 4
5#include <algorithm> 5#include <algorithm>
6#include <chrono> 6#include <chrono>
7#include <iterator>
7#include <mutex> 8#include <mutex>
9#include <numeric>
10#include <sstream>
8#include <thread> 11#include <thread>
12#include <fmt/chrono.h>
13#include <fmt/format.h>
14#include "common/file_util.h"
9#include "common/math_util.h" 15#include "common/math_util.h"
10#include "core/perf_stats.h" 16#include "core/perf_stats.h"
11#include "core/settings.h" 17#include "core/settings.h"
@@ -15,8 +21,31 @@ using DoubleSecs = std::chrono::duration<double, std::chrono::seconds::period>;
15using std::chrono::duration_cast; 21using std::chrono::duration_cast;
16using std::chrono::microseconds; 22using std::chrono::microseconds;
17 23
24// Purposefully ignore the first five frames, as there's a significant amount of overhead in
25// booting that we shouldn't account for
26constexpr std::size_t IgnoreFrames = 5;
27
18namespace Core { 28namespace Core {
19 29
30PerfStats::PerfStats(u64 title_id) : title_id(title_id) {}
31
32PerfStats::~PerfStats() {
33 if (!Settings::values.record_frame_times || title_id == 0) {
34 return;
35 }
36
37 const std::time_t t = std::time(nullptr);
38 std::ostringstream stream;
39 std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index,
40 std::ostream_iterator<double>(stream, "\n"));
41 const std::string& path = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
42 // %F Date format expanded is "%Y-%m-%d"
43 const std::string filename =
44 fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id);
45 FileUtil::IOFile file(filename, "w");
46 file.WriteString(stream.str());
47}
48
20void PerfStats::BeginSystemFrame() { 49void PerfStats::BeginSystemFrame() {
21 std::lock_guard lock{object_mutex}; 50 std::lock_guard lock{object_mutex};
22 51
@@ -27,7 +56,12 @@ void PerfStats::EndSystemFrame() {
27 std::lock_guard lock{object_mutex}; 56 std::lock_guard lock{object_mutex};
28 57
29 auto frame_end = Clock::now(); 58 auto frame_end = Clock::now();
30 accumulated_frametime += frame_end - frame_begin; 59 const auto frame_time = frame_end - frame_begin;
60 if (current_index < perf_history.size()) {
61 perf_history[current_index++] =
62 std::chrono::duration<double, std::milli>(frame_time).count();
63 }
64 accumulated_frametime += frame_time;
31 system_frames += 1; 65 system_frames += 1;
32 66
33 previous_frame_length = frame_end - previous_frame_end; 67 previous_frame_length = frame_end - previous_frame_end;
@@ -40,6 +74,17 @@ void PerfStats::EndGameFrame() {
40 game_frames += 1; 74 game_frames += 1;
41} 75}
42 76
77double PerfStats::GetMeanFrametime() {
78 std::lock_guard lock{object_mutex};
79
80 if (current_index <= IgnoreFrames) {
81 return 0;
82 }
83 const double sum = std::accumulate(perf_history.begin() + IgnoreFrames,
84 perf_history.begin() + current_index, 0);
85 return sum / (current_index - IgnoreFrames);
86}
87
43PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us) { 88PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us) {
44 std::lock_guard lock{object_mutex}; 89 std::lock_guard lock{object_mutex};
45 90
diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h
index 222ac1a63..d9a64f072 100644
--- a/src/core/perf_stats.h
+++ b/src/core/perf_stats.h
@@ -4,7 +4,9 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <array>
7#include <chrono> 8#include <chrono>
9#include <cstddef>
8#include <mutex> 10#include <mutex>
9#include "common/common_types.h" 11#include "common/common_types.h"
10 12
@@ -27,6 +29,10 @@ struct PerfStatsResults {
27 */ 29 */
28class PerfStats { 30class PerfStats {
29public: 31public:
32 explicit PerfStats(u64 title_id);
33
34 ~PerfStats();
35
30 using Clock = std::chrono::high_resolution_clock; 36 using Clock = std::chrono::high_resolution_clock;
31 37
32 void BeginSystemFrame(); 38 void BeginSystemFrame();
@@ -36,13 +42,26 @@ public:
36 PerfStatsResults GetAndResetStats(std::chrono::microseconds current_system_time_us); 42 PerfStatsResults GetAndResetStats(std::chrono::microseconds current_system_time_us);
37 43
38 /** 44 /**
45 * Returns the Arthimetic Mean of all frametime values stored in the performance history.
46 */
47 double GetMeanFrametime();
48
49 /**
39 * Gets the ratio between walltime and the emulated time of the previous system frame. This is 50 * Gets the ratio between walltime and the emulated time of the previous system frame. This is
40 * useful for scaling inputs or outputs moving between the two time domains. 51 * useful for scaling inputs or outputs moving between the two time domains.
41 */ 52 */
42 double GetLastFrameTimeScale(); 53 double GetLastFrameTimeScale();
43 54
44private: 55private:
45 std::mutex object_mutex; 56 std::mutex object_mutex{};
57
58 /// Title ID for the game that is running. 0 if there is no game running yet
59 u64 title_id{0};
60 /// Current index for writing to the perf_history array
61 std::size_t current_index{0};
62 /// Stores an hour of historical frametime data useful for processing and tracking performance
63 /// regressions with code changes.
64 std::array<double, 216000> perf_history = {};
46 65
47 /// Point when the cumulative counters were reset 66 /// Point when the cumulative counters were reset
48 Clock::time_point reset_point = Clock::now(); 67 Clock::time_point reset_point = Clock::now();
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index cfe0771e2..9c657929e 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -304,8 +304,8 @@ void Reporter::SaveUnimplementedAppletReport(
304 SaveToFile(std::move(out), GetPath("unimpl_applet_report", title_id, timestamp)); 304 SaveToFile(std::move(out), GetPath("unimpl_applet_report", title_id, timestamp));
305} 305}
306 306
307void Reporter::SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vector<u8>> data, 307void Reporter::SavePlayReport(PlayReportType type, u64 title_id, std::vector<std::vector<u8>> data,
308 std::optional<u128> user_id) const { 308 std::optional<u64> process_id, std::optional<u128> user_id) const {
309 if (!IsReportingEnabled()) { 309 if (!IsReportingEnabled()) {
310 return; 310 return;
311 } 311 }
@@ -321,7 +321,11 @@ void Reporter::SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vec
321 data_out.push_back(Common::HexToString(d)); 321 data_out.push_back(Common::HexToString(d));
322 } 322 }
323 323
324 out["play_report_process_id"] = fmt::format("{:016X}", process_id); 324 if (process_id.has_value()) {
325 out["play_report_process_id"] = fmt::format("{:016X}", *process_id);
326 }
327
328 out["play_report_type"] = fmt::format("{:02}", static_cast<u8>(type));
325 out["play_report_data"] = std::move(data_out); 329 out["play_report_data"] = std::move(data_out);
326 330
327 SaveToFile(std::move(out), GetPath("play_report", title_id, timestamp)); 331 SaveToFile(std::move(out), GetPath("play_report", title_id, timestamp));
diff --git a/src/core/reporter.h b/src/core/reporter.h
index 44256de50..f08aa11fb 100644
--- a/src/core/reporter.h
+++ b/src/core/reporter.h
@@ -46,8 +46,14 @@ public:
46 std::vector<std::vector<u8>> normal_channel, 46 std::vector<std::vector<u8>> normal_channel,
47 std::vector<std::vector<u8>> interactive_channel) const; 47 std::vector<std::vector<u8>> interactive_channel) const;
48 48
49 void SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vector<u8>> data, 49 enum class PlayReportType {
50 std::optional<u128> user_id = {}) const; 50 Old,
51 New,
52 System,
53 };
54
55 void SavePlayReport(PlayReportType type, u64 title_id, std::vector<std::vector<u8>> data,
56 std::optional<u64> process_id = {}, std::optional<u128> user_id = {}) const;
51 57
52 void SaveErrorReport(u64 title_id, ResultCode result, 58 void SaveErrorReport(u64 title_id, ResultCode result,
53 std::optional<std::string> custom_text_main = {}, 59 std::optional<std::string> custom_text_main = {},
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 0dd1632ac..7de3fd1e5 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -2,6 +2,7 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "common/file_util.h"
5#include "core/core.h" 6#include "core/core.h"
6#include "core/gdbstub/gdbstub.h" 7#include "core/gdbstub/gdbstub.h"
7#include "core/hle/service/hid/hid.h" 8#include "core/hle/service/hid/hid.h"
@@ -97,8 +98,8 @@ void LogSettings() {
97 LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching); 98 LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching);
98 LogSetting("Audio_OutputDevice", Settings::values.audio_device_id); 99 LogSetting("Audio_OutputDevice", Settings::values.audio_device_id);
99 LogSetting("DataStorage_UseVirtualSd", Settings::values.use_virtual_sd); 100 LogSetting("DataStorage_UseVirtualSd", Settings::values.use_virtual_sd);
100 LogSetting("DataStorage_NandDir", Settings::values.nand_dir); 101 LogSetting("DataStorage_NandDir", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
101 LogSetting("DataStorage_SdmcDir", Settings::values.sdmc_dir); 102 LogSetting("DataStorage_SdmcDir", FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir));
102 LogSetting("Debugging_UseGdbstub", Settings::values.use_gdbstub); 103 LogSetting("Debugging_UseGdbstub", Settings::values.use_gdbstub);
103 LogSetting("Debugging_GdbstubPort", Settings::values.gdbstub_port); 104 LogSetting("Debugging_GdbstubPort", Settings::values.gdbstub_port);
104 LogSetting("Debugging_ProgramArgs", Settings::values.program_args); 105 LogSetting("Debugging_ProgramArgs", Settings::values.program_args);
diff --git a/src/core/settings.h b/src/core/settings.h
index 6638ce8f9..47bddfb30 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -346,6 +346,31 @@ struct TouchscreenInput {
346 u32 rotation_angle; 346 u32 rotation_angle;
347}; 347};
348 348
349enum class NANDTotalSize : u64 {
350 S29_1GB = 0x747C00000ULL,
351};
352
353enum class NANDUserSize : u64 {
354 S26GB = 0x680000000ULL,
355};
356
357enum class NANDSystemSize : u64 {
358 S2_5GB = 0xA0000000,
359};
360
361enum class SDMCSize : u64 {
362 S1GB = 0x40000000,
363 S2GB = 0x80000000,
364 S4GB = 0x100000000ULL,
365 S8GB = 0x200000000ULL,
366 S16GB = 0x400000000ULL,
367 S32GB = 0x800000000ULL,
368 S64GB = 0x1000000000ULL,
369 S128GB = 0x2000000000ULL,
370 S256GB = 0x4000000000ULL,
371 S1TB = 0x10000000000ULL,
372};
373
349struct Values { 374struct Values {
350 // System 375 // System
351 bool use_docked_mode; 376 bool use_docked_mode;
@@ -382,8 +407,13 @@ struct Values {
382 407
383 // Data Storage 408 // Data Storage
384 bool use_virtual_sd; 409 bool use_virtual_sd;
385 std::string nand_dir; 410 bool gamecard_inserted;
386 std::string sdmc_dir; 411 bool gamecard_current_game;
412 std::string gamecard_path;
413 NANDTotalSize nand_total_size;
414 NANDSystemSize nand_system_size;
415 NANDUserSize nand_user_size;
416 SDMCSize sdmc_size;
387 417
388 // Renderer 418 // Renderer
389 float resolution_factor; 419 float resolution_factor;
@@ -409,6 +439,7 @@ struct Values {
409 float volume; 439 float volume;
410 440
411 // Debugging 441 // Debugging
442 bool record_frame_times;
412 bool use_gdbstub; 443 bool use_gdbstub;
413 u16 gdbstub_port; 444 u16 gdbstub_port;
414 std::string program_args; 445 std::string program_args;
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index c7a3c85a0..fb3d1112c 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -541,7 +541,7 @@ void Maxwell3D::ProcessSyncPoint() {
541} 541}
542 542
543void Maxwell3D::DrawArrays() { 543void Maxwell3D::DrawArrays() {
544 LOG_DEBUG(HW_GPU, "called, topology={}, count={}", static_cast<u32>(regs.draw.topology.Value()), 544 LOG_TRACE(HW_GPU, "called, topology={}, count={}", static_cast<u32>(regs.draw.topology.Value()),
545 regs.vertex_buffer.count); 545 regs.vertex_buffer.count);
546 ASSERT_MSG(!(regs.index_array.count && regs.vertex_buffer.count), "Both indexed and direct?"); 546 ASSERT_MSG(!(regs.index_array.count && regs.vertex_buffer.count), "Both indexed and direct?");
547 547
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 052e6d24e..28272ef6f 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -566,6 +566,13 @@ enum class ImageAtomicOperation : u64 {
566 Exch = 8, 566 Exch = 8,
567}; 567};
568 568
569enum class ShuffleOperation : u64 {
570 Idx = 0, // shuffleNV
571 Up = 1, // shuffleUpNV
572 Down = 2, // shuffleDownNV
573 Bfly = 3, // shuffleXorNV
574};
575
569union Instruction { 576union Instruction {
570 Instruction& operator=(const Instruction& instr) { 577 Instruction& operator=(const Instruction& instr) {
571 value = instr.value; 578 value = instr.value;
@@ -600,6 +607,15 @@ union Instruction {
600 } vote; 607 } vote;
601 608
602 union { 609 union {
610 BitField<30, 2, ShuffleOperation> operation;
611 BitField<48, 3, u64> pred48;
612 BitField<28, 1, u64> is_index_imm;
613 BitField<29, 1, u64> is_mask_imm;
614 BitField<20, 5, u64> index_imm;
615 BitField<34, 13, u64> mask_imm;
616 } shfl;
617
618 union {
603 BitField<8, 8, Register> gpr; 619 BitField<8, 8, Register> gpr;
604 BitField<20, 24, s64> offset; 620 BitField<20, 24, s64> offset;
605 } gmem; 621 } gmem;
@@ -934,6 +950,11 @@ union Instruction {
934 } isetp; 950 } isetp;
935 951
936 union { 952 union {
953 BitField<48, 1, u64> is_signed;
954 BitField<49, 3, PredCondition> cond;
955 } icmp;
956
957 union {
937 BitField<0, 3, u64> pred0; 958 BitField<0, 3, u64> pred0;
938 BitField<3, 3, u64> pred3; 959 BitField<3, 3, u64> pred3;
939 BitField<12, 3, u64> pred12; 960 BitField<12, 3, u64> pred12;
@@ -1542,6 +1563,7 @@ public:
1542 BRK, 1563 BRK,
1543 DEPBAR, 1564 DEPBAR,
1544 VOTE, 1565 VOTE,
1566 SHFL,
1545 BFE_C, 1567 BFE_C,
1546 BFE_R, 1568 BFE_R,
1547 BFE_IMM, 1569 BFE_IMM,
@@ -1628,6 +1650,10 @@ public:
1628 SEL_C, 1650 SEL_C,
1629 SEL_R, 1651 SEL_R,
1630 SEL_IMM, 1652 SEL_IMM,
1653 ICMP_RC,
1654 ICMP_R,
1655 ICMP_CR,
1656 ICMP_IMM,
1631 MUFU, // Multi-Function Operator 1657 MUFU, // Multi-Function Operator
1632 RRO_C, // Range Reduction Operator 1658 RRO_C, // Range Reduction Operator
1633 RRO_R, 1659 RRO_R,
@@ -1833,6 +1859,7 @@ private:
1833 INST("111000110000----", Id::EXIT, Type::Flow, "EXIT"), 1859 INST("111000110000----", Id::EXIT, Type::Flow, "EXIT"),
1834 INST("1111000011110---", Id::DEPBAR, Type::Synch, "DEPBAR"), 1860 INST("1111000011110---", Id::DEPBAR, Type::Synch, "DEPBAR"),
1835 INST("0101000011011---", Id::VOTE, Type::Warp, "VOTE"), 1861 INST("0101000011011---", Id::VOTE, Type::Warp, "VOTE"),
1862 INST("1110111100010---", Id::SHFL, Type::Warp, "SHFL"),
1836 INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"), 1863 INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"),
1837 INST("1110111101001---", Id::LD_S, Type::Memory, "LD_S"), 1864 INST("1110111101001---", Id::LD_S, Type::Memory, "LD_S"),
1838 INST("1110111101000---", Id::LD_L, Type::Memory, "LD_L"), 1865 INST("1110111101000---", Id::LD_L, Type::Memory, "LD_L"),
@@ -1892,6 +1919,10 @@ private:
1892 INST("0100110010100---", Id::SEL_C, Type::ArithmeticInteger, "SEL_C"), 1919 INST("0100110010100---", Id::SEL_C, Type::ArithmeticInteger, "SEL_C"),
1893 INST("0101110010100---", Id::SEL_R, Type::ArithmeticInteger, "SEL_R"), 1920 INST("0101110010100---", Id::SEL_R, Type::ArithmeticInteger, "SEL_R"),
1894 INST("0011100-10100---", Id::SEL_IMM, Type::ArithmeticInteger, "SEL_IMM"), 1921 INST("0011100-10100---", Id::SEL_IMM, Type::ArithmeticInteger, "SEL_IMM"),
1922 INST("010100110100----", Id::ICMP_RC, Type::ArithmeticInteger, "ICMP_RC"),
1923 INST("010110110100----", Id::ICMP_R, Type::ArithmeticInteger, "ICMP_R"),
1924 INST("010010110100----", Id::ICMP_CR, Type::ArithmeticInteger, "ICMP_CR"),
1925 INST("0011011-0100----", Id::ICMP_IMM, Type::ArithmeticInteger, "ICMP_IMM"),
1895 INST("0101101111011---", Id::LEA_R2, Type::ArithmeticInteger, "LEA_R2"), 1926 INST("0101101111011---", Id::LEA_R2, Type::ArithmeticInteger, "LEA_R2"),
1896 INST("0101101111010---", Id::LEA_R1, Type::ArithmeticInteger, "LEA_R1"), 1927 INST("0101101111010---", Id::LEA_R1, Type::ArithmeticInteger, "LEA_R1"),
1897 INST("001101101101----", Id::LEA_IMM, Type::ArithmeticInteger, "LEA_IMM"), 1928 INST("001101101101----", Id::LEA_IMM, Type::ArithmeticInteger, "LEA_IMM"),
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 909ccb82c..0dbc4c02f 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -214,7 +214,8 @@ CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEn
214 std::string source = "#version 430 core\n" 214 std::string source = "#version 430 core\n"
215 "#extension GL_ARB_separate_shader_objects : enable\n" 215 "#extension GL_ARB_separate_shader_objects : enable\n"
216 "#extension GL_NV_gpu_shader5 : enable\n" 216 "#extension GL_NV_gpu_shader5 : enable\n"
217 "#extension GL_NV_shader_thread_group : enable\n"; 217 "#extension GL_NV_shader_thread_group : enable\n"
218 "#extension GL_NV_shader_thread_shuffle : enable\n";
218 if (entries.shader_viewport_layer_array) { 219 if (entries.shader_viewport_layer_array) {
219 source += "#extension GL_ARB_shader_viewport_layer_array : enable\n"; 220 source += "#extension GL_ARB_shader_viewport_layer_array : enable\n";
220 } 221 }
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 137b23740..76439e7ab 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -325,6 +325,7 @@ public:
325 DeclareRegisters(); 325 DeclareRegisters();
326 DeclarePredicates(); 326 DeclarePredicates();
327 DeclareLocalMemory(); 327 DeclareLocalMemory();
328 DeclareSharedMemory();
328 DeclareInternalFlags(); 329 DeclareInternalFlags();
329 DeclareInputAttributes(); 330 DeclareInputAttributes();
330 DeclareOutputAttributes(); 331 DeclareOutputAttributes();
@@ -499,6 +500,13 @@ private:
499 code.AddNewLine(); 500 code.AddNewLine();
500 } 501 }
501 502
503 void DeclareSharedMemory() {
504 if (stage != ProgramType::Compute) {
505 return;
506 }
507 code.AddLine("shared uint {}[];", GetSharedMemory());
508 }
509
502 void DeclareInternalFlags() { 510 void DeclareInternalFlags() {
503 for (u32 flag = 0; flag < static_cast<u32>(InternalFlag::Amount); flag++) { 511 for (u32 flag = 0; flag < static_cast<u32>(InternalFlag::Amount); flag++) {
504 const auto flag_code = static_cast<InternalFlag>(flag); 512 const auto flag_code = static_cast<InternalFlag>(flag);
@@ -881,6 +889,12 @@ private:
881 Type::Uint}; 889 Type::Uint};
882 } 890 }
883 891
892 if (const auto smem = std::get_if<SmemNode>(&*node)) {
893 return {
894 fmt::format("{}[{} >> 2]", GetSharedMemory(), Visit(smem->GetAddress()).AsUint()),
895 Type::Uint};
896 }
897
884 if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) { 898 if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) {
885 return {GetInternalFlag(internal_flag->GetFlag()), Type::Bool}; 899 return {GetInternalFlag(internal_flag->GetFlag()), Type::Bool};
886 } 900 }
@@ -1007,10 +1021,10 @@ private:
1007 return {std::move(temporary), value.GetType()}; 1021 return {std::move(temporary), value.GetType()};
1008 } 1022 }
1009 1023
1010 Expression GetOutputAttribute(const AbufNode* abuf) { 1024 std::optional<Expression> GetOutputAttribute(const AbufNode* abuf) {
1011 switch (const auto attribute = abuf->GetIndex()) { 1025 switch (const auto attribute = abuf->GetIndex()) {
1012 case Attribute::Index::Position: 1026 case Attribute::Index::Position:
1013 return {"gl_Position"s + GetSwizzle(abuf->GetElement()), Type::Float}; 1027 return {{"gl_Position"s + GetSwizzle(abuf->GetElement()), Type::Float}};
1014 case Attribute::Index::LayerViewportPointSize: 1028 case Attribute::Index::LayerViewportPointSize:
1015 switch (abuf->GetElement()) { 1029 switch (abuf->GetElement()) {
1016 case 0: 1030 case 0:
@@ -1020,25 +1034,25 @@ private:
1020 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { 1034 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) {
1021 return {}; 1035 return {};
1022 } 1036 }
1023 return {"gl_Layer", Type::Int}; 1037 return {{"gl_Layer", Type::Int}};
1024 case 2: 1038 case 2:
1025 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { 1039 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) {
1026 return {}; 1040 return {};
1027 } 1041 }
1028 return {"gl_ViewportIndex", Type::Int}; 1042 return {{"gl_ViewportIndex", Type::Int}};
1029 case 3: 1043 case 3:
1030 UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader"); 1044 UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader");
1031 return {"gl_PointSize", Type::Float}; 1045 return {{"gl_PointSize", Type::Float}};
1032 } 1046 }
1033 return {}; 1047 return {};
1034 case Attribute::Index::ClipDistances0123: 1048 case Attribute::Index::ClipDistances0123:
1035 return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), Type::Float}; 1049 return {{fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), Type::Float}};
1036 case Attribute::Index::ClipDistances4567: 1050 case Attribute::Index::ClipDistances4567:
1037 return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), Type::Float}; 1051 return {{fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), Type::Float}};
1038 default: 1052 default:
1039 if (IsGenericAttribute(attribute)) { 1053 if (IsGenericAttribute(attribute)) {
1040 return {GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()), 1054 return {
1041 Type::Float}; 1055 {GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()), Type::Float}};
1042 } 1056 }
1043 UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute)); 1057 UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute));
1044 return {}; 1058 return {};
@@ -1278,7 +1292,11 @@ private:
1278 target = {GetRegister(gpr->GetIndex()), Type::Float}; 1292 target = {GetRegister(gpr->GetIndex()), Type::Float};
1279 } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) { 1293 } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) {
1280 UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer()); 1294 UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer());
1281 target = GetOutputAttribute(abuf); 1295 auto output = GetOutputAttribute(abuf);
1296 if (!output) {
1297 return {};
1298 }
1299 target = std::move(*output);
1282 } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) { 1300 } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) {
1283 if (stage == ProgramType::Compute) { 1301 if (stage == ProgramType::Compute) {
1284 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); 1302 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders");
@@ -1286,6 +1304,11 @@ private:
1286 target = { 1304 target = {
1287 fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()), 1305 fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()),
1288 Type::Uint}; 1306 Type::Uint};
1307 } else if (const auto smem = std::get_if<SmemNode>(&*dest)) {
1308 ASSERT(stage == ProgramType::Compute);
1309 target = {
1310 fmt::format("{}[{} >> 2]", GetSharedMemory(), Visit(smem->GetAddress()).AsUint()),
1311 Type::Uint};
1289 } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) { 1312 } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) {
1290 const std::string real = Visit(gmem->GetRealAddress()).AsUint(); 1313 const std::string real = Visit(gmem->GetRealAddress()).AsUint();
1291 const std::string base = Visit(gmem->GetBaseAddress()).AsUint(); 1314 const std::string base = Visit(gmem->GetBaseAddress()).AsUint();
@@ -1934,8 +1957,7 @@ private:
1934 Expression BallotThread(Operation operation) { 1957 Expression BallotThread(Operation operation) {
1935 const std::string value = VisitOperand(operation, 0).AsBool(); 1958 const std::string value = VisitOperand(operation, 0).AsBool();
1936 if (!device.HasWarpIntrinsics()) { 1959 if (!device.HasWarpIntrinsics()) {
1937 LOG_ERROR(Render_OpenGL, 1960 LOG_ERROR(Render_OpenGL, "Nvidia vote intrinsics are required by this shader");
1938 "Nvidia warp intrinsics are not available and its required by a shader");
1939 // Stub on non-Nvidia devices by simulating all threads voting the same as the active 1961 // Stub on non-Nvidia devices by simulating all threads voting the same as the active
1940 // one. 1962 // one.
1941 return {fmt::format("({} ? 0xFFFFFFFFU : 0U)", value), Type::Uint}; 1963 return {fmt::format("({} ? 0xFFFFFFFFU : 0U)", value), Type::Uint};
@@ -1946,8 +1968,7 @@ private:
1946 Expression Vote(Operation operation, const char* func) { 1968 Expression Vote(Operation operation, const char* func) {
1947 const std::string value = VisitOperand(operation, 0).AsBool(); 1969 const std::string value = VisitOperand(operation, 0).AsBool();
1948 if (!device.HasWarpIntrinsics()) { 1970 if (!device.HasWarpIntrinsics()) {
1949 LOG_ERROR(Render_OpenGL, 1971 LOG_ERROR(Render_OpenGL, "Nvidia vote intrinsics are required by this shader");
1950 "Nvidia vote intrinsics are not available and its required by a shader");
1951 // Stub with a warp size of one. 1972 // Stub with a warp size of one.
1952 return {value, Type::Bool}; 1973 return {value, Type::Bool};
1953 } 1974 }
@@ -1964,15 +1985,54 @@ private:
1964 1985
1965 Expression VoteEqual(Operation operation) { 1986 Expression VoteEqual(Operation operation) {
1966 if (!device.HasWarpIntrinsics()) { 1987 if (!device.HasWarpIntrinsics()) {
1967 LOG_ERROR(Render_OpenGL, 1988 LOG_ERROR(Render_OpenGL, "Nvidia vote intrinsics are required by this shader");
1968 "Nvidia vote intrinsics are not available and its required by a shader"); 1989 // We must return true here since a stub for a theoretical warp size of 1.
1969 // We must return true here since a stub for a theoretical warp size of 1 will always 1990 // This will always return an equal result across all votes.
1970 // return an equal result for all its votes.
1971 return {"true", Type::Bool}; 1991 return {"true", Type::Bool};
1972 } 1992 }
1973 return Vote(operation, "allThreadsEqualNV"); 1993 return Vote(operation, "allThreadsEqualNV");
1974 } 1994 }
1975 1995
1996 template <const std::string_view& func>
1997 Expression Shuffle(Operation operation) {
1998 const std::string value = VisitOperand(operation, 0).AsFloat();
1999 if (!device.HasWarpIntrinsics()) {
2000 LOG_ERROR(Render_OpenGL, "Nvidia shuffle intrinsics are required by this shader");
2001 // On a "single-thread" device we are either on the same thread or out of bounds. Both
2002 // cases return the passed value.
2003 return {value, Type::Float};
2004 }
2005
2006 const std::string index = VisitOperand(operation, 1).AsUint();
2007 const std::string width = VisitOperand(operation, 2).AsUint();
2008 return {fmt::format("{}({}, {}, {})", func, value, index, width), Type::Float};
2009 }
2010
2011 template <const std::string_view& func>
2012 Expression InRangeShuffle(Operation operation) {
2013 const std::string index = VisitOperand(operation, 0).AsUint();
2014 const std::string width = VisitOperand(operation, 1).AsUint();
2015 if (!device.HasWarpIntrinsics()) {
2016 // On a "single-thread" device we are only in bounds when the requested index is 0.
2017 return {fmt::format("({} == 0U)", index), Type::Bool};
2018 }
2019
2020 const std::string in_range = code.GenerateTemporary();
2021 code.AddLine("bool {};", in_range);
2022 code.AddLine("{}(0U, {}, {}, {});", func, index, width, in_range);
2023 return {in_range, Type::Bool};
2024 }
2025
2026 struct Func final {
2027 Func() = delete;
2028 ~Func() = delete;
2029
2030 static constexpr std::string_view ShuffleIndexed = "shuffleNV";
2031 static constexpr std::string_view ShuffleUp = "shuffleUpNV";
2032 static constexpr std::string_view ShuffleDown = "shuffleDownNV";
2033 static constexpr std::string_view ShuffleButterfly = "shuffleXorNV";
2034 };
2035
1976 static constexpr std::array operation_decompilers = { 2036 static constexpr std::array operation_decompilers = {
1977 &GLSLDecompiler::Assign, 2037 &GLSLDecompiler::Assign,
1978 2038
@@ -2135,6 +2195,16 @@ private:
2135 &GLSLDecompiler::VoteAll, 2195 &GLSLDecompiler::VoteAll,
2136 &GLSLDecompiler::VoteAny, 2196 &GLSLDecompiler::VoteAny,
2137 &GLSLDecompiler::VoteEqual, 2197 &GLSLDecompiler::VoteEqual,
2198
2199 &GLSLDecompiler::Shuffle<Func::ShuffleIndexed>,
2200 &GLSLDecompiler::Shuffle<Func::ShuffleUp>,
2201 &GLSLDecompiler::Shuffle<Func::ShuffleDown>,
2202 &GLSLDecompiler::Shuffle<Func::ShuffleButterfly>,
2203
2204 &GLSLDecompiler::InRangeShuffle<Func::ShuffleIndexed>,
2205 &GLSLDecompiler::InRangeShuffle<Func::ShuffleUp>,
2206 &GLSLDecompiler::InRangeShuffle<Func::ShuffleDown>,
2207 &GLSLDecompiler::InRangeShuffle<Func::ShuffleButterfly>,
2138 }; 2208 };
2139 static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount)); 2209 static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount));
2140 2210
@@ -2175,6 +2245,10 @@ private:
2175 return "lmem_" + suffix; 2245 return "lmem_" + suffix;
2176 } 2246 }
2177 2247
2248 std::string GetSharedMemory() const {
2249 return fmt::format("smem_{}", suffix);
2250 }
2251
2178 std::string GetInternalFlag(InternalFlag flag) const { 2252 std::string GetInternalFlag(InternalFlag flag) const {
2179 constexpr std::array InternalFlagNames = {"zero_flag", "sign_flag", "carry_flag", 2253 constexpr std::array InternalFlagNames = {"zero_flag", "sign_flag", "carry_flag",
2180 "overflow_flag"}; 2254 "overflow_flag"};
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index ea77dd211..9ed738171 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -145,7 +145,7 @@ inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode,
145 case Tegra::Texture::TextureMipmapFilter::None: 145 case Tegra::Texture::TextureMipmapFilter::None:
146 return GL_LINEAR; 146 return GL_LINEAR;
147 case Tegra::Texture::TextureMipmapFilter::Nearest: 147 case Tegra::Texture::TextureMipmapFilter::Nearest:
148 return GL_NEAREST_MIPMAP_LINEAR; 148 return GL_LINEAR_MIPMAP_NEAREST;
149 case Tegra::Texture::TextureMipmapFilter::Linear: 149 case Tegra::Texture::TextureMipmapFilter::Linear:
150 return GL_LINEAR_MIPMAP_LINEAR; 150 return GL_LINEAR_MIPMAP_LINEAR;
151 } 151 }
@@ -157,7 +157,7 @@ inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode,
157 case Tegra::Texture::TextureMipmapFilter::Nearest: 157 case Tegra::Texture::TextureMipmapFilter::Nearest:
158 return GL_NEAREST_MIPMAP_NEAREST; 158 return GL_NEAREST_MIPMAP_NEAREST;
159 case Tegra::Texture::TextureMipmapFilter::Linear: 159 case Tegra::Texture::TextureMipmapFilter::Linear:
160 return GL_LINEAR_MIPMAP_NEAREST; 160 return GL_NEAREST_MIPMAP_LINEAR;
161 } 161 }
162 } 162 }
163 } 163 }
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
index b9153934e..f7fbbb6e4 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
@@ -1127,6 +1127,46 @@ private:
1127 return {}; 1127 return {};
1128 } 1128 }
1129 1129
1130 Id ShuffleIndexed(Operation) {
1131 UNIMPLEMENTED();
1132 return {};
1133 }
1134
1135 Id ShuffleUp(Operation) {
1136 UNIMPLEMENTED();
1137 return {};
1138 }
1139
1140 Id ShuffleDown(Operation) {
1141 UNIMPLEMENTED();
1142 return {};
1143 }
1144
1145 Id ShuffleButterfly(Operation) {
1146 UNIMPLEMENTED();
1147 return {};
1148 }
1149
1150 Id InRangeShuffleIndexed(Operation) {
1151 UNIMPLEMENTED();
1152 return {};
1153 }
1154
1155 Id InRangeShuffleUp(Operation) {
1156 UNIMPLEMENTED();
1157 return {};
1158 }
1159
1160 Id InRangeShuffleDown(Operation) {
1161 UNIMPLEMENTED();
1162 return {};
1163 }
1164
1165 Id InRangeShuffleButterfly(Operation) {
1166 UNIMPLEMENTED();
1167 return {};
1168 }
1169
1130 Id DeclareBuiltIn(spv::BuiltIn builtin, spv::StorageClass storage, Id type, 1170 Id DeclareBuiltIn(spv::BuiltIn builtin, spv::StorageClass storage, Id type,
1131 const std::string& name) { 1171 const std::string& name) {
1132 const Id id = OpVariable(type, storage); 1172 const Id id = OpVariable(type, storage);
@@ -1431,6 +1471,16 @@ private:
1431 &SPIRVDecompiler::VoteAll, 1471 &SPIRVDecompiler::VoteAll,
1432 &SPIRVDecompiler::VoteAny, 1472 &SPIRVDecompiler::VoteAny,
1433 &SPIRVDecompiler::VoteEqual, 1473 &SPIRVDecompiler::VoteEqual,
1474
1475 &SPIRVDecompiler::ShuffleIndexed,
1476 &SPIRVDecompiler::ShuffleUp,
1477 &SPIRVDecompiler::ShuffleDown,
1478 &SPIRVDecompiler::ShuffleButterfly,
1479
1480 &SPIRVDecompiler::InRangeShuffleIndexed,
1481 &SPIRVDecompiler::InRangeShuffleUp,
1482 &SPIRVDecompiler::InRangeShuffleDown,
1483 &SPIRVDecompiler::InRangeShuffleButterfly,
1434 }; 1484 };
1435 static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount)); 1485 static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount));
1436 1486
diff --git a/src/video_core/shader/decode/arithmetic_integer.cpp b/src/video_core/shader/decode/arithmetic_integer.cpp
index c8c1a7f40..b73f6536e 100644
--- a/src/video_core/shader/decode/arithmetic_integer.cpp
+++ b/src/video_core/shader/decode/arithmetic_integer.cpp
@@ -138,6 +138,35 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) {
138 SetRegister(bb, instr.gpr0, value); 138 SetRegister(bb, instr.gpr0, value);
139 break; 139 break;
140 } 140 }
141 case OpCode::Id::ICMP_CR:
142 case OpCode::Id::ICMP_R:
143 case OpCode::Id::ICMP_RC:
144 case OpCode::Id::ICMP_IMM: {
145 const Node zero = Immediate(0);
146
147 const auto [op_b, test] = [&]() -> std::pair<Node, Node> {
148 switch (opcode->get().GetId()) {
149 case OpCode::Id::ICMP_CR:
150 return {GetConstBuffer(instr.cbuf34.index, instr.cbuf34.offset),
151 GetRegister(instr.gpr39)};
152 case OpCode::Id::ICMP_R:
153 return {GetRegister(instr.gpr20), GetRegister(instr.gpr39)};
154 case OpCode::Id::ICMP_RC:
155 return {GetRegister(instr.gpr39),
156 GetConstBuffer(instr.cbuf34.index, instr.cbuf34.offset)};
157 case OpCode::Id::ICMP_IMM:
158 return {Immediate(instr.alu.GetSignedImm20_20()), GetRegister(instr.gpr39)};
159 default:
160 UNREACHABLE();
161 return {zero, zero};
162 }
163 }();
164 const Node op_a = GetRegister(instr.gpr8);
165 const Node comparison =
166 GetPredicateComparisonInteger(instr.icmp.cond, instr.icmp.is_signed != 0, test, zero);
167 SetRegister(bb, instr.gpr0, Operation(OperationCode::Select, comparison, op_a, op_b));
168 break;
169 }
141 case OpCode::Id::LOP_C: 170 case OpCode::Id::LOP_C:
142 case OpCode::Id::LOP_R: 171 case OpCode::Id::LOP_R:
143 case OpCode::Id::LOP_IMM: { 172 case OpCode::Id::LOP_IMM: {
diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp
index ed108bea8..7923d4d69 100644
--- a/src/video_core/shader/decode/memory.cpp
+++ b/src/video_core/shader/decode/memory.cpp
@@ -35,7 +35,7 @@ u32 GetUniformTypeElementsCount(Tegra::Shader::UniformType uniform_type) {
35 return 1; 35 return 1;
36 } 36 }
37} 37}
38} // namespace 38} // Anonymous namespace
39 39
40u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { 40u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
41 const Instruction instr = {program_code[pc]}; 41 const Instruction instr = {program_code[pc]};
@@ -106,16 +106,17 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
106 } 106 }
107 break; 107 break;
108 } 108 }
109 case OpCode::Id::LD_L: { 109 case OpCode::Id::LD_L:
110 LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}", 110 LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}", static_cast<u64>(instr.ld_l.unknown));
111 static_cast<u64>(instr.ld_l.unknown.Value())); 111 [[fallthrough]];
112 112 case OpCode::Id::LD_S: {
113 const auto GetLmem = [&](s32 offset) { 113 const auto GetMemory = [&](s32 offset) {
114 ASSERT(offset % 4 == 0); 114 ASSERT(offset % 4 == 0);
115 const Node immediate_offset = Immediate(static_cast<s32>(instr.smem_imm) + offset); 115 const Node immediate_offset = Immediate(static_cast<s32>(instr.smem_imm) + offset);
116 const Node address = Operation(OperationCode::IAdd, NO_PRECISE, GetRegister(instr.gpr8), 116 const Node address = Operation(OperationCode::IAdd, NO_PRECISE, GetRegister(instr.gpr8),
117 immediate_offset); 117 immediate_offset);
118 return GetLocalMemory(address); 118 return opcode->get().GetId() == OpCode::Id::LD_S ? GetSharedMemory(address)
119 : GetLocalMemory(address);
119 }; 120 };
120 121
121 switch (instr.ldst_sl.type.Value()) { 122 switch (instr.ldst_sl.type.Value()) {
@@ -135,14 +136,16 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
135 return 0; 136 return 0;
136 } 137 }
137 }(); 138 }();
138 for (u32 i = 0; i < count; ++i) 139 for (u32 i = 0; i < count; ++i) {
139 SetTemporary(bb, i, GetLmem(i * 4)); 140 SetTemporary(bb, i, GetMemory(i * 4));
140 for (u32 i = 0; i < count; ++i) 141 }
142 for (u32 i = 0; i < count; ++i) {
141 SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i)); 143 SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i));
144 }
142 break; 145 break;
143 } 146 }
144 default: 147 default:
145 UNIMPLEMENTED_MSG("LD_L Unhandled type: {}", 148 UNIMPLEMENTED_MSG("{} Unhandled type: {}", opcode->get().GetName(),
146 static_cast<u32>(instr.ldst_sl.type.Value())); 149 static_cast<u32>(instr.ldst_sl.type.Value()));
147 } 150 }
148 break; 151 break;
@@ -209,27 +212,34 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
209 212
210 break; 213 break;
211 } 214 }
212 case OpCode::Id::ST_L: { 215 case OpCode::Id::ST_L:
213 LOG_DEBUG(HW_GPU, "ST_L cache management mode: {}", 216 LOG_DEBUG(HW_GPU, "ST_L cache management mode: {}",
214 static_cast<u64>(instr.st_l.cache_management.Value())); 217 static_cast<u64>(instr.st_l.cache_management.Value()));
215 218 [[fallthrough]];
216 const auto GetLmemAddr = [&](s32 offset) { 219 case OpCode::Id::ST_S: {
220 const auto GetAddress = [&](s32 offset) {
217 ASSERT(offset % 4 == 0); 221 ASSERT(offset % 4 == 0);
218 const Node immediate = Immediate(static_cast<s32>(instr.smem_imm) + offset); 222 const Node immediate = Immediate(static_cast<s32>(instr.smem_imm) + offset);
219 return Operation(OperationCode::IAdd, NO_PRECISE, GetRegister(instr.gpr8), immediate); 223 return Operation(OperationCode::IAdd, NO_PRECISE, GetRegister(instr.gpr8), immediate);
220 }; 224 };
221 225
226 const auto set_memory = opcode->get().GetId() == OpCode::Id::ST_L
227 ? &ShaderIR::SetLocalMemory
228 : &ShaderIR::SetSharedMemory;
229
222 switch (instr.ldst_sl.type.Value()) { 230 switch (instr.ldst_sl.type.Value()) {
223 case Tegra::Shader::StoreType::Bits128: 231 case Tegra::Shader::StoreType::Bits128:
224 SetLocalMemory(bb, GetLmemAddr(12), GetRegister(instr.gpr0.Value() + 3)); 232 (this->*set_memory)(bb, GetAddress(12), GetRegister(instr.gpr0.Value() + 3));
225 SetLocalMemory(bb, GetLmemAddr(8), GetRegister(instr.gpr0.Value() + 2)); 233 (this->*set_memory)(bb, GetAddress(8), GetRegister(instr.gpr0.Value() + 2));
234 [[fallthrough]];
226 case Tegra::Shader::StoreType::Bits64: 235 case Tegra::Shader::StoreType::Bits64:
227 SetLocalMemory(bb, GetLmemAddr(4), GetRegister(instr.gpr0.Value() + 1)); 236 (this->*set_memory)(bb, GetAddress(4), GetRegister(instr.gpr0.Value() + 1));
237 [[fallthrough]];
228 case Tegra::Shader::StoreType::Bits32: 238 case Tegra::Shader::StoreType::Bits32:
229 SetLocalMemory(bb, GetLmemAddr(0), GetRegister(instr.gpr0)); 239 (this->*set_memory)(bb, GetAddress(0), GetRegister(instr.gpr0));
230 break; 240 break;
231 default: 241 default:
232 UNIMPLEMENTED_MSG("ST_L Unhandled type: {}", 242 UNIMPLEMENTED_MSG("{} unhandled type: {}", opcode->get().GetName(),
233 static_cast<u32>(instr.ldst_sl.type.Value())); 243 static_cast<u32>(instr.ldst_sl.type.Value()));
234 } 244 }
235 break; 245 break;
diff --git a/src/video_core/shader/decode/warp.cpp b/src/video_core/shader/decode/warp.cpp
index 04ca74f46..a8e481b3c 100644
--- a/src/video_core/shader/decode/warp.cpp
+++ b/src/video_core/shader/decode/warp.cpp
@@ -13,6 +13,7 @@ namespace VideoCommon::Shader {
13using Tegra::Shader::Instruction; 13using Tegra::Shader::Instruction;
14using Tegra::Shader::OpCode; 14using Tegra::Shader::OpCode;
15using Tegra::Shader::Pred; 15using Tegra::Shader::Pred;
16using Tegra::Shader::ShuffleOperation;
16using Tegra::Shader::VoteOperation; 17using Tegra::Shader::VoteOperation;
17 18
18namespace { 19namespace {
@@ -44,6 +45,52 @@ u32 ShaderIR::DecodeWarp(NodeBlock& bb, u32 pc) {
44 SetPredicate(bb, instr.vote.dest_pred, vote); 45 SetPredicate(bb, instr.vote.dest_pred, vote);
45 break; 46 break;
46 } 47 }
48 case OpCode::Id::SHFL: {
49 Node mask = instr.shfl.is_mask_imm ? Immediate(static_cast<u32>(instr.shfl.mask_imm))
50 : GetRegister(instr.gpr39);
51 Node width = [&] {
52 // Convert the obscure SHFL mask back into GL_NV_shader_thread_shuffle's width. This has
53 // been done reversing Nvidia's math. It won't work on all cases due to SHFL having
54 // different parameters that don't properly map to GLSL's interface, but it should work
55 // for cases emitted by Nvidia's compiler.
56 if (instr.shfl.operation == ShuffleOperation::Up) {
57 return Operation(
58 OperationCode::ILogicalShiftRight,
59 Operation(OperationCode::IAdd, std::move(mask), Immediate(-0x2000)),
60 Immediate(8));
61 } else {
62 return Operation(OperationCode::ILogicalShiftRight,
63 Operation(OperationCode::IAdd, Immediate(0x201F),
64 Operation(OperationCode::INegate, std::move(mask))),
65 Immediate(8));
66 }
67 }();
68
69 const auto [operation, in_range] = [instr]() -> std::pair<OperationCode, OperationCode> {
70 switch (instr.shfl.operation) {
71 case ShuffleOperation::Idx:
72 return {OperationCode::ShuffleIndexed, OperationCode::InRangeShuffleIndexed};
73 case ShuffleOperation::Up:
74 return {OperationCode::ShuffleUp, OperationCode::InRangeShuffleUp};
75 case ShuffleOperation::Down:
76 return {OperationCode::ShuffleDown, OperationCode::InRangeShuffleDown};
77 case ShuffleOperation::Bfly:
78 return {OperationCode::ShuffleButterfly, OperationCode::InRangeShuffleButterfly};
79 }
80 UNREACHABLE_MSG("Invalid SHFL operation: {}",
81 static_cast<u64>(instr.shfl.operation.Value()));
82 return {};
83 }();
84
85 // Setting the predicate before the register is intentional to avoid overwriting.
86 Node index = instr.shfl.is_index_imm ? Immediate(static_cast<u32>(instr.shfl.index_imm))
87 : GetRegister(instr.gpr20);
88 SetPredicate(bb, instr.shfl.pred48, Operation(in_range, index, width));
89 SetRegister(
90 bb, instr.gpr0,
91 Operation(operation, GetRegister(instr.gpr8), std::move(index), std::move(width)));
92 break;
93 }
47 default: 94 default:
48 UNIMPLEMENTED_MSG("Unhandled warp instruction: {}", opcode->get().GetName()); 95 UNIMPLEMENTED_MSG("Unhandled warp instruction: {}", opcode->get().GetName());
49 break; 96 break;
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h
index b47b201cf..abf2cb1ab 100644
--- a/src/video_core/shader/node.h
+++ b/src/video_core/shader/node.h
@@ -181,6 +181,16 @@ enum class OperationCode {
181 VoteAny, /// (bool) -> bool 181 VoteAny, /// (bool) -> bool
182 VoteEqual, /// (bool) -> bool 182 VoteEqual, /// (bool) -> bool
183 183
184 ShuffleIndexed, /// (uint value, uint index, uint width) -> uint
185 ShuffleUp, /// (uint value, uint index, uint width) -> uint
186 ShuffleDown, /// (uint value, uint index, uint width) -> uint
187 ShuffleButterfly, /// (uint value, uint index, uint width) -> uint
188
189 InRangeShuffleIndexed, /// (uint index, uint width) -> bool
190 InRangeShuffleUp, /// (uint index, uint width) -> bool
191 InRangeShuffleDown, /// (uint index, uint width) -> bool
192 InRangeShuffleButterfly, /// (uint index, uint width) -> bool
193
184 Amount, 194 Amount,
185}; 195};
186 196
@@ -206,12 +216,13 @@ class PredicateNode;
206class AbufNode; 216class AbufNode;
207class CbufNode; 217class CbufNode;
208class LmemNode; 218class LmemNode;
219class SmemNode;
209class GmemNode; 220class GmemNode;
210class CommentNode; 221class CommentNode;
211 222
212using NodeData = 223using NodeData =
213 std::variant<OperationNode, ConditionalNode, GprNode, ImmediateNode, InternalFlagNode, 224 std::variant<OperationNode, ConditionalNode, GprNode, ImmediateNode, InternalFlagNode,
214 PredicateNode, AbufNode, CbufNode, LmemNode, GmemNode, CommentNode>; 225 PredicateNode, AbufNode, CbufNode, LmemNode, SmemNode, GmemNode, CommentNode>;
215using Node = std::shared_ptr<NodeData>; 226using Node = std::shared_ptr<NodeData>;
216using Node4 = std::array<Node, 4>; 227using Node4 = std::array<Node, 4>;
217using NodeBlock = std::vector<Node>; 228using NodeBlock = std::vector<Node>;
@@ -583,6 +594,19 @@ private:
583 Node address; 594 Node address;
584}; 595};
585 596
597/// Shared memory node
598class SmemNode final {
599public:
600 explicit SmemNode(Node address) : address{std::move(address)} {}
601
602 const Node& GetAddress() const {
603 return address;
604 }
605
606private:
607 Node address;
608};
609
586/// Global memory node 610/// Global memory node
587class GmemNode final { 611class GmemNode final {
588public: 612public:
diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp
index 1e5c7f660..bbbab0bca 100644
--- a/src/video_core/shader/shader_ir.cpp
+++ b/src/video_core/shader/shader_ir.cpp
@@ -137,6 +137,10 @@ Node ShaderIR::GetLocalMemory(Node address) {
137 return MakeNode<LmemNode>(std::move(address)); 137 return MakeNode<LmemNode>(std::move(address));
138} 138}
139 139
140Node ShaderIR::GetSharedMemory(Node address) {
141 return MakeNode<SmemNode>(std::move(address));
142}
143
140Node ShaderIR::GetTemporary(u32 id) { 144Node ShaderIR::GetTemporary(u32 id) {
141 return GetRegister(Register::ZeroIndex + 1 + id); 145 return GetRegister(Register::ZeroIndex + 1 + id);
142} 146}
@@ -378,6 +382,11 @@ void ShaderIR::SetLocalMemory(NodeBlock& bb, Node address, Node value) {
378 Operation(OperationCode::Assign, GetLocalMemory(std::move(address)), std::move(value))); 382 Operation(OperationCode::Assign, GetLocalMemory(std::move(address)), std::move(value)));
379} 383}
380 384
385void ShaderIR::SetSharedMemory(NodeBlock& bb, Node address, Node value) {
386 bb.push_back(
387 Operation(OperationCode::Assign, GetSharedMemory(std::move(address)), std::move(value)));
388}
389
381void ShaderIR::SetTemporary(NodeBlock& bb, u32 id, Node value) { 390void ShaderIR::SetTemporary(NodeBlock& bb, u32 id, Node value) {
382 SetRegister(bb, Register::ZeroIndex + 1 + id, std::move(value)); 391 SetRegister(bb, Register::ZeroIndex + 1 + id, std::move(value));
383} 392}
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index 62816bd56..6aed9bb84 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -208,6 +208,8 @@ private:
208 Node GetInternalFlag(InternalFlag flag, bool negated = false); 208 Node GetInternalFlag(InternalFlag flag, bool negated = false);
209 /// Generates a node representing a local memory address 209 /// Generates a node representing a local memory address
210 Node GetLocalMemory(Node address); 210 Node GetLocalMemory(Node address);
211 /// Generates a node representing a shared memory address
212 Node GetSharedMemory(Node address);
211 /// Generates a temporary, internally it uses a post-RZ register 213 /// Generates a temporary, internally it uses a post-RZ register
212 Node GetTemporary(u32 id); 214 Node GetTemporary(u32 id);
213 215
@@ -217,8 +219,10 @@ private:
217 void SetPredicate(NodeBlock& bb, u64 dest, Node src); 219 void SetPredicate(NodeBlock& bb, u64 dest, Node src);
218 /// Sets an internal flag. src value must be a bool-evaluated node 220 /// Sets an internal flag. src value must be a bool-evaluated node
219 void SetInternalFlag(NodeBlock& bb, InternalFlag flag, Node value); 221 void SetInternalFlag(NodeBlock& bb, InternalFlag flag, Node value);
220 /// Sets a local memory address. address and value must be a number-evaluated node 222 /// Sets a local memory address with a value.
221 void SetLocalMemory(NodeBlock& bb, Node address, Node value); 223 void SetLocalMemory(NodeBlock& bb, Node address, Node value);
224 /// Sets a shared memory address with a value.
225 void SetSharedMemory(NodeBlock& bb, Node address, Node value);
222 /// Sets a temporary. Internally it uses a post-RZ register 226 /// Sets a temporary. Internally it uses a post-RZ register
223 void SetTemporary(NodeBlock& bb, u32 id, Node value); 227 void SetTemporary(NodeBlock& bb, u32 id, Node value);
224 228
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index f051e17b4..dc6fa07fc 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -33,6 +33,9 @@ add_executable(yuzu
33 configuration/configure_debug.ui 33 configuration/configure_debug.ui
34 configuration/configure_dialog.cpp 34 configuration/configure_dialog.cpp
35 configuration/configure_dialog.h 35 configuration/configure_dialog.h
36 configuration/configure_filesystem.cpp
37 configuration/configure_filesystem.h
38 configuration/configure_filesystem.ui
36 configuration/configure_gamelist.cpp 39 configuration/configure_gamelist.cpp
37 configuration/configure_gamelist.h 40 configuration/configure_gamelist.h
38 configuration/configure_gamelist.ui 41 configuration/configure_gamelist.ui
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index f594106bf..92d9fb161 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -459,6 +459,49 @@ void Config::ReadDataStorageValues() {
459 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))) 459 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)))
460 .toString() 460 .toString()
461 .toStdString()); 461 .toStdString());
462 FileUtil::GetUserPath(
463 FileUtil::UserPath::LoadDir,
464 qt_config
465 ->value(QStringLiteral("load_directory"),
466 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)))
467 .toString()
468 .toStdString());
469 FileUtil::GetUserPath(
470 FileUtil::UserPath::DumpDir,
471 qt_config
472 ->value(QStringLiteral("dump_directory"),
473 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)))
474 .toString()
475 .toStdString());
476 FileUtil::GetUserPath(
477 FileUtil::UserPath::CacheDir,
478 qt_config
479 ->value(QStringLiteral("cache_directory"),
480 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)))
481 .toString()
482 .toStdString());
483 Settings::values.gamecard_inserted =
484 ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool();
485 Settings::values.gamecard_current_game =
486 ReadSetting(QStringLiteral("gamecard_current_game"), false).toBool();
487 Settings::values.gamecard_path =
488 ReadSetting(QStringLiteral("gamecard_path"), QStringLiteral("")).toString().toStdString();
489 Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>(
490 ReadSetting(QStringLiteral("nand_total_size"),
491 QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB)))
492 .toULongLong());
493 Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>(
494 ReadSetting(QStringLiteral("nand_user_size"),
495 QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB)))
496 .toULongLong());
497 Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>(
498 ReadSetting(QStringLiteral("nand_system_size"),
499 QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB)))
500 .toULongLong());
501 Settings::values.sdmc_size = static_cast<Settings::SDMCSize>(
502 ReadSetting(QStringLiteral("sdmc_size"),
503 QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB)))
504 .toULongLong());
462 505
463 qt_config->endGroup(); 506 qt_config->endGroup();
464} 507}
@@ -466,6 +509,9 @@ void Config::ReadDataStorageValues() {
466void Config::ReadDebuggingValues() { 509void Config::ReadDebuggingValues() {
467 qt_config->beginGroup(QStringLiteral("Debugging")); 510 qt_config->beginGroup(QStringLiteral("Debugging"));
468 511
512 // Intentionally not using the QT default setting as this is intended to be changed in the ini
513 Settings::values.record_frame_times =
514 qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
469 Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool(); 515 Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
470 Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt(); 516 Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
471 Settings::values.program_args = 517 Settings::values.program_args =
@@ -872,13 +918,40 @@ void Config::SaveDataStorageValues() {
872 WriteSetting(QStringLiteral("sdmc_directory"), 918 WriteSetting(QStringLiteral("sdmc_directory"),
873 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)), 919 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
874 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); 920 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
875 921 WriteSetting(QStringLiteral("load_directory"),
922 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)),
923 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
924 WriteSetting(QStringLiteral("dump_directory"),
925 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)),
926 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
927 WriteSetting(QStringLiteral("cache_directory"),
928 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)),
929 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
930 WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false);
931 WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game,
932 false);
933 WriteSetting(QStringLiteral("gamecard_path"),
934 QString::fromStdString(Settings::values.gamecard_path), QStringLiteral(""));
935 WriteSetting(QStringLiteral("nand_total_size"),
936 QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_total_size)),
937 QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB)));
938 WriteSetting(QStringLiteral("nand_user_size"),
939 QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_user_size)),
940 QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB)));
941 WriteSetting(QStringLiteral("nand_system_size"),
942 QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_system_size)),
943 QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB)));
944 WriteSetting(QStringLiteral("sdmc_size"),
945 QVariant::fromValue<u64>(static_cast<u64>(Settings::values.sdmc_size)),
946 QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB)));
876 qt_config->endGroup(); 947 qt_config->endGroup();
877} 948}
878 949
879void Config::SaveDebuggingValues() { 950void Config::SaveDebuggingValues() {
880 qt_config->beginGroup(QStringLiteral("Debugging")); 951 qt_config->beginGroup(QStringLiteral("Debugging"));
881 952
953 // Intentionally not using the QT default setting as this is intended to be changed in the ini
954 qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
882 WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false); 955 WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
883 WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689); 956 WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
884 WriteSetting(QStringLiteral("program_args"), 957 WriteSetting(QStringLiteral("program_args"),
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 267717bc9..49fadd0ef 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -63,6 +63,11 @@
63 <string>Profiles</string> 63 <string>Profiles</string>
64 </attribute> 64 </attribute>
65 </widget> 65 </widget>
66 <widget class="ConfigureFilesystem" name="filesystemTab">
67 <attribute name="title">
68 <string>Filesystem</string>
69 </attribute>
70 </widget>
66 <widget class="ConfigureInputSimple" name="inputTab"> 71 <widget class="ConfigureInputSimple" name="inputTab">
67 <attribute name="title"> 72 <attribute name="title">
68 <string>Input</string> 73 <string>Input</string>
@@ -126,6 +131,12 @@
126 <container>1</container> 131 <container>1</container>
127 </customwidget> 132 </customwidget>
128 <customwidget> 133 <customwidget>
134 <class>ConfigureFilesystem</class>
135 <extends>QWidget</extends>
136 <header>configuration/configure_filesystem.h</header>
137 <container>1</container>
138 </customwidget>
139 <customwidget>
129 <class>ConfigureAudio</class> 140 <class>ConfigureAudio</class>
130 <extends>QWidget</extends> 141 <extends>QWidget</extends>
131 <header>configuration/configure_audio.h</header> 142 <header>configuration/configure_audio.h</header>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 5b7e03056..90c1f9459 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -34,8 +34,6 @@ void ConfigureDebug::SetConfiguration() {
34 ui->toggle_console->setChecked(UISettings::values.show_console); 34 ui->toggle_console->setChecked(UISettings::values.show_console);
35 ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter)); 35 ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
36 ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args)); 36 ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args));
37 ui->dump_exefs->setChecked(Settings::values.dump_exefs);
38 ui->dump_decompressed_nso->setChecked(Settings::values.dump_nso);
39 ui->reporting_services->setChecked(Settings::values.reporting_services); 37 ui->reporting_services->setChecked(Settings::values.reporting_services);
40 ui->quest_flag->setChecked(Settings::values.quest_flag); 38 ui->quest_flag->setChecked(Settings::values.quest_flag);
41} 39}
@@ -46,8 +44,6 @@ void ConfigureDebug::ApplyConfiguration() {
46 UISettings::values.show_console = ui->toggle_console->isChecked(); 44 UISettings::values.show_console = ui->toggle_console->isChecked();
47 Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); 45 Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
48 Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); 46 Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
49 Settings::values.dump_exefs = ui->dump_exefs->isChecked();
50 Settings::values.dump_nso = ui->dump_decompressed_nso->isChecked();
51 Settings::values.reporting_services = ui->reporting_services->isChecked(); 47 Settings::values.reporting_services = ui->reporting_services->isChecked();
52 Settings::values.quest_flag = ui->quest_flag->isChecked(); 48 Settings::values.quest_flag = ui->quest_flag->isChecked();
53 Debugger::ToggleConsole(); 49 Debugger::ToggleConsole();
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 7e109cef0..ce49569bb 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -103,58 +103,6 @@
103 </item> 103 </item>
104 </layout> 104 </layout>
105 </item> 105 </item>
106 </layout>
107 </widget>
108 </item>
109 <item>
110 <widget class="QGroupBox" name="groupBox_3">
111 <property name="title">
112 <string>Homebrew</string>
113 </property>
114 <layout class="QVBoxLayout" name="verticalLayout_5">
115 <item>
116 <layout class="QHBoxLayout" name="horizontalLayout_4">
117 <item>
118 <widget class="QLabel" name="label_3">
119 <property name="text">
120 <string>Arguments String</string>
121 </property>
122 </widget>
123 </item>
124 <item>
125 <widget class="QLineEdit" name="homebrew_args_edit"/>
126 </item>
127 </layout>
128 </item>
129 </layout>
130 </widget>
131 </item>
132 <item>
133 <widget class="QGroupBox" name="groupBox_4">
134 <property name="title">
135 <string>Dump</string>
136 </property>
137 <layout class="QVBoxLayout" name="verticalLayout_6">
138 <item>
139 <widget class="QCheckBox" name="dump_decompressed_nso">
140 <property name="whatsThis">
141 <string>When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory.</string>
142 </property>
143 <property name="text">
144 <string>Dump Decompressed NSOs</string>
145 </property>
146 </widget>
147 </item>
148 <item>
149 <widget class="QCheckBox" name="dump_exefs">
150 <property name="whatsThis">
151 <string>When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory.</string>
152 </property>
153 <property name="text">
154 <string>Dump ExeFS</string>
155 </property>
156 </widget>
157 </item>
158 <item> 106 <item>
159 <widget class="QCheckBox" name="reporting_services"> 107 <widget class="QCheckBox" name="reporting_services">
160 <property name="text"> 108 <property name="text">
@@ -163,7 +111,7 @@
163 </widget> 111 </widget>
164 </item> 112 </item>
165 <item> 113 <item>
166 <widget class="QLabel" name="label_3"> 114 <widget class="QLabel" name="label">
167 <property name="font"> 115 <property name="font">
168 <font> 116 <font>
169 <italic>true</italic> 117 <italic>true</italic>
@@ -197,10 +145,36 @@
197 </widget> 145 </widget>
198 </item> 146 </item>
199 <item> 147 <item>
148 <widget class="QGroupBox" name="groupBox_3">
149 <property name="title">
150 <string>Homebrew</string>
151 </property>
152 <layout class="QVBoxLayout" name="verticalLayout_5">
153 <item>
154 <layout class="QHBoxLayout" name="horizontalLayout_4">
155 <item>
156 <widget class="QLabel" name="label_3">
157 <property name="text">
158 <string>Arguments String</string>
159 </property>
160 </widget>
161 </item>
162 <item>
163 <widget class="QLineEdit" name="homebrew_args_edit"/>
164 </item>
165 </layout>
166 </item>
167 </layout>
168 </widget>
169 </item>
170 <item>
200 <spacer name="verticalSpacer"> 171 <spacer name="verticalSpacer">
201 <property name="orientation"> 172 <property name="orientation">
202 <enum>Qt::Vertical</enum> 173 <enum>Qt::Vertical</enum>
203 </property> 174 </property>
175 <property name="sizeType">
176 <enum>QSizePolicy::Expanding</enum>
177 </property>
204 <property name="sizeHint" stdset="0"> 178 <property name="sizeHint" stdset="0">
205 <size> 179 <size>
206 <width>20</width> 180 <width>20</width>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 775e3f2ea..7c875ae87 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -37,6 +37,7 @@ void ConfigureDialog::ApplyConfiguration() {
37 ui->gameListTab->ApplyConfiguration(); 37 ui->gameListTab->ApplyConfiguration();
38 ui->systemTab->ApplyConfiguration(); 38 ui->systemTab->ApplyConfiguration();
39 ui->profileManagerTab->ApplyConfiguration(); 39 ui->profileManagerTab->ApplyConfiguration();
40 ui->filesystemTab->applyConfiguration();
40 ui->inputTab->ApplyConfiguration(); 41 ui->inputTab->ApplyConfiguration();
41 ui->hotkeysTab->ApplyConfiguration(registry); 42 ui->hotkeysTab->ApplyConfiguration(registry);
42 ui->graphicsTab->ApplyConfiguration(); 43 ui->graphicsTab->ApplyConfiguration();
@@ -73,7 +74,7 @@ Q_DECLARE_METATYPE(QList<QWidget*>);
73void ConfigureDialog::PopulateSelectionList() { 74void ConfigureDialog::PopulateSelectionList() {
74 const std::array<std::pair<QString, QList<QWidget*>>, 4> items{ 75 const std::array<std::pair<QString, QList<QWidget*>>, 4> items{
75 {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}}, 76 {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}},
76 {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->audioTab}}, 77 {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->filesystemTab, ui->audioTab}},
77 {tr("Graphics"), {ui->graphicsTab}}, 78 {tr("Graphics"), {ui->graphicsTab}},
78 {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}}, 79 {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}},
79 }; 80 };
@@ -106,6 +107,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
106 {ui->debugTab, tr("Debug")}, 107 {ui->debugTab, tr("Debug")},
107 {ui->webTab, tr("Web")}, 108 {ui->webTab, tr("Web")},
108 {ui->gameListTab, tr("Game List")}, 109 {ui->gameListTab, tr("Game List")},
110 {ui->filesystemTab, tr("Filesystem")},
109 }; 111 };
110 112
111 [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget); 113 [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp
new file mode 100644
index 000000000..29f540eb7
--- /dev/null
+++ b/src/yuzu/configuration/configure_filesystem.cpp
@@ -0,0 +1,177 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QFileDialog>
6#include <QMessageBox>
7#include "common/common_paths.h"
8#include "common/file_util.h"
9#include "core/settings.h"
10#include "ui_configure_filesystem.h"
11#include "yuzu/configuration/configure_filesystem.h"
12#include "yuzu/uisettings.h"
13
14namespace {
15
16template <typename T>
17void SetComboBoxFromData(QComboBox* combo_box, T data) {
18 const auto index = combo_box->findData(QVariant::fromValue(static_cast<u64>(data)));
19 if (index >= combo_box->count() || index < 0)
20 return;
21
22 combo_box->setCurrentIndex(index);
23}
24
25} // Anonymous namespace
26
27ConfigureFilesystem::ConfigureFilesystem(QWidget* parent)
28 : QWidget(parent), ui(std::make_unique<Ui::ConfigureFilesystem>()) {
29 ui->setupUi(this);
30 this->setConfiguration();
31
32 connect(ui->nand_directory_button, &QToolButton::pressed, this,
33 [this] { SetDirectory(DirectoryTarget::NAND, ui->nand_directory_edit); });
34 connect(ui->sdmc_directory_button, &QToolButton::pressed, this,
35 [this] { SetDirectory(DirectoryTarget::SD, ui->sdmc_directory_edit); });
36 connect(ui->gamecard_path_button, &QToolButton::pressed, this,
37 [this] { SetDirectory(DirectoryTarget::Gamecard, ui->gamecard_path_edit); });
38 connect(ui->dump_path_button, &QToolButton::pressed, this,
39 [this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); });
40 connect(ui->load_path_button, &QToolButton::pressed, this,
41 [this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); });
42 connect(ui->cache_directory_button, &QToolButton::pressed, this,
43 [this] { SetDirectory(DirectoryTarget::Cache, ui->cache_directory_edit); });
44
45 connect(ui->reset_game_list_cache, &QPushButton::pressed, this,
46 &ConfigureFilesystem::ResetMetadata);
47
48 connect(ui->gamecard_inserted, &QCheckBox::stateChanged, this,
49 &ConfigureFilesystem::UpdateEnabledControls);
50 connect(ui->gamecard_current_game, &QCheckBox::stateChanged, this,
51 &ConfigureFilesystem::UpdateEnabledControls);
52}
53
54ConfigureFilesystem::~ConfigureFilesystem() = default;
55
56void ConfigureFilesystem::setConfiguration() {
57 ui->nand_directory_edit->setText(
58 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
59 ui->sdmc_directory_edit->setText(
60 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
61 ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path));
62 ui->dump_path_edit->setText(
63 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
64 ui->load_path_edit->setText(
65 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
66 ui->cache_directory_edit->setText(
67 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
68
69 ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted);
70 ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game);
71 ui->dump_exefs->setChecked(Settings::values.dump_exefs);
72 ui->dump_nso->setChecked(Settings::values.dump_nso);
73
74 ui->cache_game_list->setChecked(UISettings::values.cache_game_list);
75
76 SetComboBoxFromData(ui->nand_size, Settings::values.nand_total_size);
77 SetComboBoxFromData(ui->usrnand_size, Settings::values.nand_user_size);
78 SetComboBoxFromData(ui->sysnand_size, Settings::values.nand_system_size);
79 SetComboBoxFromData(ui->sdmc_size, Settings::values.sdmc_size);
80
81 UpdateEnabledControls();
82}
83
84void ConfigureFilesystem::applyConfiguration() {
85 FileUtil::GetUserPath(FileUtil::UserPath::NANDDir,
86 ui->nand_directory_edit->text().toStdString());
87 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir,
88 ui->sdmc_directory_edit->text().toStdString());
89 FileUtil::GetUserPath(FileUtil::UserPath::DumpDir, ui->dump_path_edit->text().toStdString());
90 FileUtil::GetUserPath(FileUtil::UserPath::LoadDir, ui->load_path_edit->text().toStdString());
91 FileUtil::GetUserPath(FileUtil::UserPath::CacheDir,
92 ui->cache_directory_edit->text().toStdString());
93 Settings::values.gamecard_path = ui->gamecard_path_edit->text().toStdString();
94
95 Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked();
96 Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked();
97 Settings::values.dump_exefs = ui->dump_exefs->isChecked();
98 Settings::values.dump_nso = ui->dump_nso->isChecked();
99
100 UISettings::values.cache_game_list = ui->cache_game_list->isChecked();
101
102 Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>(
103 ui->nand_size->itemData(ui->nand_size->currentIndex()).toULongLong());
104 Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>(
105 ui->nand_size->itemData(ui->sysnand_size->currentIndex()).toULongLong());
106 Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>(
107 ui->nand_size->itemData(ui->usrnand_size->currentIndex()).toULongLong());
108 Settings::values.sdmc_size = static_cast<Settings::SDMCSize>(
109 ui->nand_size->itemData(ui->sdmc_size->currentIndex()).toULongLong());
110}
111
112void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) {
113 QString caption;
114
115 switch (target) {
116 case DirectoryTarget::NAND:
117 caption = tr("Select Emulated NAND Directory...");
118 break;
119 case DirectoryTarget::SD:
120 caption = tr("Select Emulated SD Directory...");
121 break;
122 case DirectoryTarget::Gamecard:
123 caption = tr("Select Gamecard Path...");
124 break;
125 case DirectoryTarget::Dump:
126 caption = tr("Select Dump Directory...");
127 break;
128 case DirectoryTarget::Load:
129 caption = tr("Select Mod Load Directory...");
130 break;
131 case DirectoryTarget::Cache:
132 caption = tr("Select Cache Directory...");
133 break;
134 }
135
136 QString str;
137 if (target == DirectoryTarget::Gamecard) {
138 str = QFileDialog::getOpenFileName(this, caption, QFileInfo(edit->text()).dir().path(),
139 QStringLiteral("NX Gamecard;*.xci"));
140 } else {
141 str = QFileDialog::getExistingDirectory(this, caption, edit->text());
142 }
143
144 if (str.isEmpty())
145 return;
146
147 edit->setText(str);
148}
149
150void ConfigureFilesystem::ResetMetadata() {
151 if (!FileUtil::Exists(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
152 "game_list")) {
153 QMessageBox::information(this, tr("Reset Metadata Cache"),
154 tr("The metadata cache is already empty."));
155 } else if (FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
156 DIR_SEP + "game_list")) {
157 QMessageBox::information(this, tr("Reset Metadata Cache"),
158 tr("The operation completed successfully."));
159 UISettings::values.is_game_list_reload_pending.exchange(true);
160 } else {
161 QMessageBox::warning(
162 this, tr("Reset Metadata Cache"),
163 tr("The metadata cache couldn't be deleted. It might be in use or non-existent."));
164 }
165}
166
167void ConfigureFilesystem::UpdateEnabledControls() {
168 ui->gamecard_current_game->setEnabled(ui->gamecard_inserted->isChecked());
169 ui->gamecard_path_edit->setEnabled(ui->gamecard_inserted->isChecked() &&
170 !ui->gamecard_current_game->isChecked());
171 ui->gamecard_path_button->setEnabled(ui->gamecard_inserted->isChecked() &&
172 !ui->gamecard_current_game->isChecked());
173}
174
175void ConfigureFilesystem::retranslateUi() {
176 ui->retranslateUi(this);
177}
diff --git a/src/yuzu/configuration/configure_filesystem.h b/src/yuzu/configuration/configure_filesystem.h
new file mode 100644
index 000000000..a79303760
--- /dev/null
+++ b/src/yuzu/configuration/configure_filesystem.h
@@ -0,0 +1,43 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QWidget>
9
10class QLineEdit;
11
12namespace Ui {
13class ConfigureFilesystem;
14}
15
16class ConfigureFilesystem : public QWidget {
17 Q_OBJECT
18
19public:
20 explicit ConfigureFilesystem(QWidget* parent = nullptr);
21 ~ConfigureFilesystem() override;
22
23 void applyConfiguration();
24 void retranslateUi();
25
26private:
27 void setConfiguration();
28
29 enum class DirectoryTarget {
30 NAND,
31 SD,
32 Gamecard,
33 Dump,
34 Load,
35 Cache,
36 };
37
38 void SetDirectory(DirectoryTarget target, QLineEdit* edit);
39 void ResetMetadata();
40 void UpdateEnabledControls();
41
42 std::unique_ptr<Ui::ConfigureFilesystem> ui;
43};
diff --git a/src/yuzu/configuration/configure_filesystem.ui b/src/yuzu/configuration/configure_filesystem.ui
new file mode 100644
index 000000000..58cd07f52
--- /dev/null
+++ b/src/yuzu/configuration/configure_filesystem.ui
@@ -0,0 +1,395 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureFilesystem</class>
4 <widget class="QWidget" name="ConfigureFilesystem">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>453</width>
10 <height>561</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Form</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout_3">
19 <item>
20 <widget class="QGroupBox" name="groupBox">
21 <property name="title">
22 <string>Storage Directories</string>
23 </property>
24 <layout class="QGridLayout" name="gridLayout">
25 <item row="0" column="0">
26 <widget class="QLabel" name="label">
27 <property name="text">
28 <string>NAND</string>
29 </property>
30 </widget>
31 </item>
32 <item row="0" column="3">
33 <widget class="QToolButton" name="nand_directory_button">
34 <property name="text">
35 <string>...</string>
36 </property>
37 </widget>
38 </item>
39 <item row="0" column="2">
40 <widget class="QLineEdit" name="nand_directory_edit"/>
41 </item>
42 <item row="1" column="2">
43 <widget class="QLineEdit" name="sdmc_directory_edit"/>
44 </item>
45 <item row="1" column="0">
46 <widget class="QLabel" name="label_2">
47 <property name="text">
48 <string>SD Card</string>
49 </property>
50 </widget>
51 </item>
52 <item row="1" column="3">
53 <widget class="QToolButton" name="sdmc_directory_button">
54 <property name="text">
55 <string>...</string>
56 </property>
57 </widget>
58 </item>
59 <item row="0" column="1">
60 <spacer name="horizontalSpacer">
61 <property name="orientation">
62 <enum>Qt::Horizontal</enum>
63 </property>
64 <property name="sizeType">
65 <enum>QSizePolicy::Maximum</enum>
66 </property>
67 <property name="sizeHint" stdset="0">
68 <size>
69 <width>60</width>
70 <height>20</height>
71 </size>
72 </property>
73 </spacer>
74 </item>
75 </layout>
76 </widget>
77 </item>
78 <item>
79 <widget class="QGroupBox" name="groupBox_2">
80 <property name="title">
81 <string>Gamecard</string>
82 </property>
83 <layout class="QGridLayout" name="gridLayout_2">
84 <item row="2" column="1">
85 <widget class="QLabel" name="label_3">
86 <property name="text">
87 <string>Path</string>
88 </property>
89 </widget>
90 </item>
91 <item row="2" column="2">
92 <widget class="QLineEdit" name="gamecard_path_edit"/>
93 </item>
94 <item row="0" column="1">
95 <widget class="QCheckBox" name="gamecard_inserted">
96 <property name="text">
97 <string>Inserted</string>
98 </property>
99 </widget>
100 </item>
101 <item row="1" column="1">
102 <widget class="QCheckBox" name="gamecard_current_game">
103 <property name="text">
104 <string>Current Game</string>
105 </property>
106 </widget>
107 </item>
108 <item row="2" column="3">
109 <widget class="QToolButton" name="gamecard_path_button">
110 <property name="text">
111 <string>...</string>
112 </property>
113 </widget>
114 </item>
115 </layout>
116 </widget>
117 </item>
118 <item>
119 <widget class="QGroupBox" name="groupBox_3">
120 <property name="title">
121 <string>Storage Sizes</string>
122 </property>
123 <layout class="QGridLayout" name="gridLayout_3">
124 <item row="3" column="0">
125 <widget class="QLabel" name="label_5">
126 <property name="text">
127 <string>SD Card</string>
128 </property>
129 </widget>
130 </item>
131 <item row="1" column="0">
132 <widget class="QLabel" name="label_4">
133 <property name="text">
134 <string>System NAND</string>
135 </property>
136 </widget>
137 </item>
138 <item row="1" column="1">
139 <widget class="QComboBox" name="sysnand_size">
140 <item>
141 <property name="text">
142 <string>2.5 GB</string>
143 </property>
144 </item>
145 </widget>
146 </item>
147 <item row="3" column="1">
148 <widget class="QComboBox" name="sdmc_size">
149 <property name="currentText">
150 <string>32 GB</string>
151 </property>
152 <item>
153 <property name="text">
154 <string>1 GB</string>
155 </property>
156 </item>
157 <item>
158 <property name="text">
159 <string>2 GB</string>
160 </property>
161 </item>
162 <item>
163 <property name="text">
164 <string>4 GB</string>
165 </property>
166 </item>
167 <item>
168 <property name="text">
169 <string>8 GB</string>
170 </property>
171 </item>
172 <item>
173 <property name="text">
174 <string>16 GB</string>
175 </property>
176 </item>
177 <item>
178 <property name="text">
179 <string>32 GB</string>
180 </property>
181 </item>
182 <item>
183 <property name="text">
184 <string>64 GB</string>
185 </property>
186 </item>
187 <item>
188 <property name="text">
189 <string>128 GB</string>
190 </property>
191 </item>
192 <item>
193 <property name="text">
194 <string>256 GB</string>
195 </property>
196 </item>
197 <item>
198 <property name="text">
199 <string>1 TB</string>
200 </property>
201 </item>
202 </widget>
203 </item>
204 <item row="2" column="1">
205 <widget class="QComboBox" name="usrnand_size">
206 <item>
207 <property name="text">
208 <string>26 GB</string>
209 </property>
210 </item>
211 </widget>
212 </item>
213 <item row="2" column="0">
214 <widget class="QLabel" name="label_6">
215 <property name="text">
216 <string>User NAND</string>
217 </property>
218 </widget>
219 </item>
220 <item row="0" column="0">
221 <widget class="QLabel" name="label_7">
222 <property name="text">
223 <string>NAND</string>
224 </property>
225 </widget>
226 </item>
227 <item row="0" column="1">
228 <widget class="QComboBox" name="nand_size">
229 <item>
230 <property name="text">
231 <string>29.1 GB</string>
232 </property>
233 </item>
234 </widget>
235 </item>
236 </layout>
237 </widget>
238 </item>
239 <item>
240 <widget class="QGroupBox" name="groupBox_4">
241 <property name="title">
242 <string>Patch Manager</string>
243 </property>
244 <layout class="QGridLayout" name="gridLayout_4">
245 <item row="1" column="2">
246 <widget class="QLineEdit" name="load_path_edit"/>
247 </item>
248 <item row="0" column="2">
249 <widget class="QLineEdit" name="dump_path_edit"/>
250 </item>
251 <item row="0" column="3">
252 <widget class="QToolButton" name="dump_path_button">
253 <property name="text">
254 <string>...</string>
255 </property>
256 </widget>
257 </item>
258 <item row="1" column="3">
259 <widget class="QToolButton" name="load_path_button">
260 <property name="text">
261 <string>...</string>
262 </property>
263 </widget>
264 </item>
265 <item row="2" column="0" colspan="4">
266 <layout class="QHBoxLayout" name="horizontalLayout">
267 <item>
268 <widget class="QCheckBox" name="dump_nso">
269 <property name="text">
270 <string>Dump Decompressed NSOs</string>
271 </property>
272 </widget>
273 </item>
274 <item>
275 <widget class="QCheckBox" name="dump_exefs">
276 <property name="text">
277 <string>Dump ExeFS</string>
278 </property>
279 </widget>
280 </item>
281 </layout>
282 </item>
283 <item row="1" column="0">
284 <widget class="QLabel" name="label_9">
285 <property name="text">
286 <string>Mod Load Root</string>
287 </property>
288 </widget>
289 </item>
290 <item row="0" column="0">
291 <widget class="QLabel" name="label_8">
292 <property name="text">
293 <string>Dump Root</string>
294 </property>
295 </widget>
296 </item>
297 <item row="0" column="1">
298 <spacer name="horizontalSpacer_2">
299 <property name="orientation">
300 <enum>Qt::Horizontal</enum>
301 </property>
302 <property name="sizeType">
303 <enum>QSizePolicy::Fixed</enum>
304 </property>
305 <property name="sizeHint" stdset="0">
306 <size>
307 <width>40</width>
308 <height>20</height>
309 </size>
310 </property>
311 </spacer>
312 </item>
313 </layout>
314 </widget>
315 </item>
316 <item>
317 <widget class="QGroupBox" name="groupBox_5">
318 <property name="title">
319 <string>Caching</string>
320 </property>
321 <layout class="QGridLayout" name="gridLayout_5">
322 <item row="0" column="0">
323 <widget class="QLabel" name="label_10">
324 <property name="text">
325 <string>Cache Directory</string>
326 </property>
327 </widget>
328 </item>
329 <item row="0" column="1">
330 <spacer name="horizontalSpacer_3">
331 <property name="orientation">
332 <enum>Qt::Horizontal</enum>
333 </property>
334 <property name="sizeType">
335 <enum>QSizePolicy::Fixed</enum>
336 </property>
337 <property name="sizeHint" stdset="0">
338 <size>
339 <width>40</width>
340 <height>20</height>
341 </size>
342 </property>
343 </spacer>
344 </item>
345 <item row="0" column="2">
346 <widget class="QLineEdit" name="cache_directory_edit"/>
347 </item>
348 <item row="0" column="3">
349 <widget class="QToolButton" name="cache_directory_button">
350 <property name="text">
351 <string>...</string>
352 </property>
353 </widget>
354 </item>
355 <item row="1" column="0" colspan="4">
356 <layout class="QHBoxLayout" name="horizontalLayout_2">
357 <item>
358 <widget class="QCheckBox" name="cache_game_list">
359 <property name="text">
360 <string>Cache Game List Metadata</string>
361 </property>
362 </widget>
363 </item>
364 <item>
365 <widget class="QPushButton" name="reset_game_list_cache">
366 <property name="text">
367 <string>Reset Metadata Cache</string>
368 </property>
369 </widget>
370 </item>
371 </layout>
372 </item>
373 </layout>
374 </widget>
375 </item>
376 </layout>
377 </item>
378 <item>
379 <spacer name="verticalSpacer">
380 <property name="orientation">
381 <enum>Qt::Vertical</enum>
382 </property>
383 <property name="sizeHint" stdset="0">
384 <size>
385 <width>20</width>
386 <height>40</height>
387 </size>
388 </property>
389 </spacer>
390 </item>
391 </layout>
392 </widget>
393 <resources/>
394 <connections/>
395</ui>
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 10bcd650e..b49446be9 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -2,6 +2,7 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <QSpinBox>
5#include "core/core.h" 6#include "core/core.h"
6#include "core/settings.h" 7#include "core/settings.h"
7#include "ui_configure_general.h" 8#include "ui_configure_general.h"
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 7613197f2..f2977719c 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -182,6 +182,8 @@ void ConfigureInput::UpdateUIEnabled() {
182 players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0); 182 players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0);
183 } 183 }
184 184
185 ui->handheld_connected->setChecked(ui->handheld_connected->isChecked() &&
186 !ui->use_docked_mode->isChecked());
185 ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked()); 187 ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked());
186 ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() && 188 ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() &&
187 !ui->use_docked_mode->isChecked()); 189 !ui->use_docked_mode->isChecked());
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 8304c6517..f147044d9 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -54,6 +54,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
54#include <QProgressDialog> 54#include <QProgressDialog>
55#include <QShortcut> 55#include <QShortcut>
56#include <QStatusBar> 56#include <QStatusBar>
57#include <QSysInfo>
57#include <QtConcurrent/QtConcurrent> 58#include <QtConcurrent/QtConcurrent>
58 59
59#include <fmt/format.h> 60#include <fmt/format.h>
@@ -66,6 +67,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
66#include "common/microprofile.h" 67#include "common/microprofile.h"
67#include "common/scm_rev.h" 68#include "common/scm_rev.h"
68#include "common/scope_exit.h" 69#include "common/scope_exit.h"
70#ifdef ARCHITECTURE_x86_64
71#include "common/x64/cpu_detect.h"
72#endif
69#include "common/telemetry.h" 73#include "common/telemetry.h"
70#include "core/core.h" 74#include "core/core.h"
71#include "core/crypto/key_manager.h" 75#include "core/crypto/key_manager.h"
@@ -205,6 +209,10 @@ GMainWindow::GMainWindow()
205 209
206 LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, 210 LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
207 Common::g_scm_desc); 211 Common::g_scm_desc);
212#ifdef ARCHITECTURE_x86_64
213 LOG_INFO(Frontend, "Host CPU: {}", Common::GetCPUCaps().cpu_string);
214#endif
215 LOG_INFO(Frontend, "Host OS: {}", QSysInfo::prettyProductName().toStdString());
208 UpdateWindowTitle(); 216 UpdateWindowTitle();
209 217
210 show(); 218 show();
@@ -213,7 +221,7 @@ GMainWindow::GMainWindow()
213 std::make_unique<FileSys::ContentProviderUnion>()); 221 std::make_unique<FileSys::ContentProviderUnion>());
214 Core::System::GetInstance().RegisterContentProvider( 222 Core::System::GetInstance().RegisterContentProvider(
215 FileSys::ContentProviderUnionSlot::FrontendManual, provider.get()); 223 FileSys::ContentProviderUnionSlot::FrontendManual, provider.get());
216 Service::FileSystem::CreateFactories(*vfs); 224 Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
217 225
218 // Gen keys if necessary 226 // Gen keys if necessary
219 OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); 227 OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
@@ -964,11 +972,11 @@ void GMainWindow::BootGame(const QString& filename) {
964 } 972 }
965 status_bar_update_timer.start(2000); 973 status_bar_update_timer.start(2000);
966 974
975 const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
976
967 std::string title_name; 977 std::string title_name;
968 const auto res = Core::System::GetInstance().GetGameName(title_name); 978 const auto res = Core::System::GetInstance().GetGameName(title_name);
969 if (res != Loader::ResultStatus::Success) { 979 if (res != Loader::ResultStatus::Success) {
970 const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
971
972 const auto [nacp, icon_file] = FileSys::PatchManager(title_id).GetControlMetadata(); 980 const auto [nacp, icon_file] = FileSys::PatchManager(title_id).GetControlMetadata();
973 if (nacp != nullptr) 981 if (nacp != nullptr)
974 title_name = nacp->GetApplicationName(); 982 title_name = nacp->GetApplicationName();
@@ -976,7 +984,7 @@ void GMainWindow::BootGame(const QString& filename) {
976 if (title_name.empty()) 984 if (title_name.empty())
977 title_name = FileUtil::GetFilename(filename.toStdString()); 985 title_name = FileUtil::GetFilename(filename.toStdString());
978 } 986 }
979 987 LOG_INFO(Frontend, "Booting game: {:016X} | {}", title_id, title_name);
980 UpdateWindowTitle(QString::fromStdString(title_name)); 988 UpdateWindowTitle(QString::fromStdString(title_name));
981 989
982 loading_screen->Prepare(Core::System::GetInstance().GetAppLoader()); 990 loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
@@ -1499,15 +1507,19 @@ void GMainWindow::OnMenuInstallToNAND() {
1499 failed(); 1507 failed();
1500 return; 1508 return;
1501 } 1509 }
1502 const auto res = 1510 const auto res = Core::System::GetInstance()
1503 Service::FileSystem::GetUserNANDContents()->InstallEntry(*nsp, false, qt_raw_copy); 1511 .GetFileSystemController()
1512 .GetUserNANDContents()
1513 ->InstallEntry(*nsp, false, qt_raw_copy);
1504 if (res == FileSys::InstallResult::Success) { 1514 if (res == FileSys::InstallResult::Success) {
1505 success(); 1515 success();
1506 } else { 1516 } else {
1507 if (res == FileSys::InstallResult::ErrorAlreadyExists) { 1517 if (res == FileSys::InstallResult::ErrorAlreadyExists) {
1508 if (overwrite()) { 1518 if (overwrite()) {
1509 const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( 1519 const auto res2 = Core::System::GetInstance()
1510 *nsp, true, qt_raw_copy); 1520 .GetFileSystemController()
1521 .GetUserNANDContents()
1522 ->InstallEntry(*nsp, true, qt_raw_copy);
1511 if (res2 == FileSys::InstallResult::Success) { 1523 if (res2 == FileSys::InstallResult::Success) {
1512 success(); 1524 success();
1513 } else { 1525 } else {
@@ -1561,19 +1573,28 @@ void GMainWindow::OnMenuInstallToNAND() {
1561 1573
1562 FileSys::InstallResult res; 1574 FileSys::InstallResult res;
1563 if (index >= static_cast<size_t>(FileSys::TitleType::Application)) { 1575 if (index >= static_cast<size_t>(FileSys::TitleType::Application)) {
1564 res = Service::FileSystem::GetUserNANDContents()->InstallEntry( 1576 res = Core::System::GetInstance()
1565 *nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); 1577 .GetFileSystemController()
1578 .GetUserNANDContents()
1579 ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false,
1580 qt_raw_copy);
1566 } else { 1581 } else {
1567 res = Service::FileSystem::GetSystemNANDContents()->InstallEntry( 1582 res = Core::System::GetInstance()
1568 *nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); 1583 .GetFileSystemController()
1584 .GetSystemNANDContents()
1585 ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false,
1586 qt_raw_copy);
1569 } 1587 }
1570 1588
1571 if (res == FileSys::InstallResult::Success) { 1589 if (res == FileSys::InstallResult::Success) {
1572 success(); 1590 success();
1573 } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { 1591 } else if (res == FileSys::InstallResult::ErrorAlreadyExists) {
1574 if (overwrite()) { 1592 if (overwrite()) {
1575 const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( 1593 const auto res2 = Core::System::GetInstance()
1576 *nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy); 1594 .GetFileSystemController()
1595 .GetUserNANDContents()
1596 ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index),
1597 true, qt_raw_copy);
1577 if (res2 == FileSys::InstallResult::Success) { 1598 if (res2 == FileSys::InstallResult::Success) {
1578 success(); 1599 success();
1579 } else { 1600 } else {
@@ -1603,7 +1624,7 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target)
1603 FileUtil::GetUserPath(target == EmulatedDirectoryTarget::SDMC ? FileUtil::UserPath::SDMCDir 1624 FileUtil::GetUserPath(target == EmulatedDirectoryTarget::SDMC ? FileUtil::UserPath::SDMCDir
1604 : FileUtil::UserPath::NANDDir, 1625 : FileUtil::UserPath::NANDDir,
1605 dir_path.toStdString()); 1626 dir_path.toStdString());
1606 Service::FileSystem::CreateFactories(*vfs); 1627 Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
1607 game_list->PopulateAsync(UISettings::values.game_dirs); 1628 game_list->PopulateAsync(UISettings::values.game_dirs);
1608 } 1629 }
1609} 1630}
@@ -1988,7 +2009,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
1988 2009
1989 const auto function = [this, &keys, &pdm] { 2010 const auto function = [this, &keys, &pdm] {
1990 keys.PopulateFromPartitionData(pdm); 2011 keys.PopulateFromPartitionData(pdm);
1991 Service::FileSystem::CreateFactories(*vfs); 2012 Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
1992 keys.DeriveETicket(pdm); 2013 keys.DeriveETicket(pdm);
1993 }; 2014 };
1994 2015
@@ -2033,7 +2054,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
2033 prog.close(); 2054 prog.close();
2034 } 2055 }
2035 2056
2036 Service::FileSystem::CreateFactories(*vfs); 2057 Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
2037 2058
2038 if (behavior == ReinitializeKeyBehavior::Warning) { 2059 if (behavior == ReinitializeKeyBehavior::Warning) {
2039 game_list->PopulateAsync(UISettings::values.game_dirs); 2060 game_list->PopulateAsync(UISettings::values.game_dirs);
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 067d58d80..d82438502 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -316,6 +316,29 @@ void Config::ReadValues() {
316 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, 316 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir,
317 sdl2_config->Get("Data Storage", "sdmc_directory", 317 sdl2_config->Get("Data Storage", "sdmc_directory",
318 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); 318 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
319 FileUtil::GetUserPath(FileUtil::UserPath::LoadDir,
320 sdl2_config->Get("Data Storage", "load_directory",
321 FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
322 FileUtil::GetUserPath(FileUtil::UserPath::DumpDir,
323 sdl2_config->Get("Data Storage", "dump_directory",
324 FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
325 FileUtil::GetUserPath(FileUtil::UserPath::CacheDir,
326 sdl2_config->Get("Data Storage", "cache_directory",
327 FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
328 Settings::values.gamecard_inserted =
329 sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false);
330 Settings::values.gamecard_current_game =
331 sdl2_config->GetBoolean("Data Storage", "gamecard_current_game", false);
332 Settings::values.gamecard_path = sdl2_config->Get("Data Storage", "gamecard_path", "");
333 Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>(sdl2_config->GetInteger(
334 "Data Storage", "nand_total_size", static_cast<long>(Settings::NANDTotalSize::S29_1GB)));
335 Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>(sdl2_config->GetInteger(
336 "Data Storage", "nand_user_size", static_cast<long>(Settings::NANDUserSize::S26GB)));
337 Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>(
338 sdl2_config->GetInteger("Data Storage", "nand_system_size",
339 static_cast<long>(Settings::NANDSystemSize::S2_5GB)));
340 Settings::values.sdmc_size = static_cast<Settings::SDMCSize>(sdl2_config->GetInteger(
341 "Data Storage", "sdmc_size", static_cast<long>(Settings::SDMCSize::S16GB)));
319 342
320 // System 343 // System
321 Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false); 344 Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
@@ -374,6 +397,8 @@ void Config::ReadValues() {
374 Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false); 397 Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false);
375 398
376 // Debugging 399 // Debugging
400 Settings::values.record_frame_times =
401 sdl2_config->GetBoolean("Debugging", "record_frame_times", false);
377 Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); 402 Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
378 Settings::values.gdbstub_port = 403 Settings::values.gdbstub_port =
379 static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); 404 static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 0cfc111a6..a6171c3ed 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -173,6 +173,20 @@ volume =
173# 1 (default): Yes, 0: No 173# 1 (default): Yes, 0: No
174use_virtual_sd = 174use_virtual_sd =
175 175
176# Whether or not to enable gamecard emulation
177# 1: Yes, 0 (default): No
178gamecard_inserted =
179
180# Whether or not the gamecard should be emulated as the current game
181# If 'gamecard_inserted' is 0 this setting is irrelevant
182# 1: Yes, 0 (default): No
183gamecard_current_game =
184
185# Path to an XCI file to use as the gamecard
186# If 'gamecard_inserted' is 0 this setting is irrelevant
187# If 'gamecard_current_game' is 1 this setting is irrelevant
188gamecard_path =
189
176[System] 190[System]
177# Whether the system is docked 191# Whether the system is docked
178# 1: Yes, 0 (default): No 192# 1: Yes, 0 (default): No
@@ -213,6 +227,8 @@ region_value =
213log_filter = *:Trace 227log_filter = *:Trace
214 228
215[Debugging] 229[Debugging]
230# Record frame time data, can be found in the log directory. Boolean value
231record_frame_times =
216# Port for listening to GDB connections. 232# Port for listening to GDB connections.
217use_gdbstub=false 233use_gdbstub=false
218gdbstub_port=24689 234gdbstub_port=24689
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 129d8ca73..bac05b959 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -184,7 +184,7 @@ int main(int argc, char** argv) {
184 Core::System& system{Core::System::GetInstance()}; 184 Core::System& system{Core::System::GetInstance()};
185 system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); 185 system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
186 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); 186 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
187 Service::FileSystem::CreateFactories(*system.GetFilesystem()); 187 system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
188 188
189 SCOPE_EXIT({ system.Shutdown(); }); 189 SCOPE_EXIT({ system.Shutdown(); });
190 190
diff --git a/src/yuzu_tester/yuzu.cpp b/src/yuzu_tester/yuzu.cpp
index 0ee97aa54..94ad50cb3 100644
--- a/src/yuzu_tester/yuzu.cpp
+++ b/src/yuzu_tester/yuzu.cpp
@@ -22,6 +22,7 @@
22#include "common/telemetry.h" 22#include "common/telemetry.h"
23#include "core/core.h" 23#include "core/core.h"
24#include "core/crypto/key_manager.h" 24#include "core/crypto/key_manager.h"
25#include "core/file_sys/registered_cache.h"
25#include "core/file_sys/vfs_real.h" 26#include "core/file_sys/vfs_real.h"
26#include "core/hle/service/filesystem/filesystem.h" 27#include "core/hle/service/filesystem/filesystem.h"
27#include "core/loader/loader.h" 28#include "core/loader/loader.h"
@@ -216,8 +217,9 @@ int main(int argc, char** argv) {
216 }; 217 };
217 218
218 Core::System& system{Core::System::GetInstance()}; 219 Core::System& system{Core::System::GetInstance()};
220 system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
219 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); 221 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
220 Service::FileSystem::CreateFactories(*system.GetFilesystem()); 222 system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
221 223
222 SCOPE_EXIT({ system.Shutdown(); }); 224 SCOPE_EXIT({ system.Shutdown(); });
223 225