summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
88 files changed, 4422 insertions, 1129 deletions
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