summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/core/core.cpp25
-rw-r--r--src/core/core.h7
-rw-r--r--src/core/file_sys/content_archive.cpp17
-rw-r--r--src/core/hle/service/am/am.cpp8
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_image.cpp2
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_image.cpp35
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp3
-rw-r--r--src/video_core/dma_pusher.cpp34
-rw-r--r--src/video_core/dma_pusher.h5
-rw-r--r--src/video_core/engines/engine_interface.h8
-rw-r--r--src/video_core/engines/engine_upload.h8
-rw-r--r--src/video_core/engines/kepler_compute.cpp20
-rw-r--r--src/video_core/engines/kepler_compute.h17
-rw-r--r--src/video_core/engines/maxwell_3d.cpp6
-rw-r--r--src/video_core/engines/puller.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp11
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp13
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp14
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp1
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h5
-rw-r--r--src/yuzu/main.cpp134
-rw-r--r--src/yuzu/main.h3
23 files changed, 308 insertions, 86 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6068c7a1f..a9f68a8f2 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -105,6 +105,8 @@ if (MSVC)
105 set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) 105 set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
106else() 106else()
107 add_compile_options( 107 add_compile_options(
108 -fwrapv
109
108 -Werror=all 110 -Werror=all
109 -Werror=extra 111 -Werror=extra
110 -Werror=missing-declarations 112 -Werror=missing-declarations
@@ -129,7 +131,6 @@ else()
129 131
130 if (ARCHITECTURE_x86_64) 132 if (ARCHITECTURE_x86_64)
131 add_compile_options("-mcx16") 133 add_compile_options("-mcx16")
132 add_compile_options("-fwrapv")
133 endif() 134 endif()
134 135
135 if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) 136 if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 2f67e60a9..e95ae80da 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -273,7 +273,8 @@ struct System::Impl {
273 time_manager.Initialize(); 273 time_manager.Initialize();
274 274
275 is_powered_on = true; 275 is_powered_on = true;
276 exit_lock = false; 276 exit_locked = false;
277 exit_requested = false;
277 278
278 microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0); 279 microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0);
279 microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1); 280 microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1);
@@ -398,7 +399,8 @@ struct System::Impl {
398 } 399 }
399 400
400 is_powered_on = false; 401 is_powered_on = false;
401 exit_lock = false; 402 exit_locked = false;
403 exit_requested = false;
402 404
403 if (gpu_core != nullptr) { 405 if (gpu_core != nullptr) {
404 gpu_core->NotifyShutdown(); 406 gpu_core->NotifyShutdown();
@@ -507,7 +509,8 @@ struct System::Impl {
507 509
508 CpuManager cpu_manager; 510 CpuManager cpu_manager;
509 std::atomic_bool is_powered_on{}; 511 std::atomic_bool is_powered_on{};
510 bool exit_lock = false; 512 bool exit_locked = false;
513 bool exit_requested = false;
511 514
512 bool nvdec_active{}; 515 bool nvdec_active{};
513 516
@@ -943,12 +946,20 @@ const Service::Time::TimeManager& System::GetTimeManager() const {
943 return impl->time_manager; 946 return impl->time_manager;
944} 947}
945 948
946void System::SetExitLock(bool locked) { 949void System::SetExitLocked(bool locked) {
947 impl->exit_lock = locked; 950 impl->exit_locked = locked;
948} 951}
949 952
950bool System::GetExitLock() const { 953bool System::GetExitLocked() const {
951 return impl->exit_lock; 954 return impl->exit_locked;
955}
956
957void System::SetExitRequested(bool requested) {
958 impl->exit_requested = requested;
959}
960
961bool System::GetExitRequested() const {
962 return impl->exit_requested;
952} 963}
953 964
954void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) { 965void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) {
diff --git a/src/core/core.h b/src/core/core.h
index c70ea1965..a9ff9315e 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -412,8 +412,11 @@ public:
412 /// Gets an immutable reference to the Room Network. 412 /// Gets an immutable reference to the Room Network.
413 [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; 413 [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
414 414
415 void SetExitLock(bool locked); 415 void SetExitLocked(bool locked);
416 [[nodiscard]] bool GetExitLock() const; 416 bool GetExitLocked() const;
417
418 void SetExitRequested(bool requested);
419 bool GetExitRequested() const;
417 420
418 void SetApplicationProcessBuildID(const CurrentBuildProcessID& id); 421 void SetApplicationProcessBuildID(const CurrentBuildProcessID& id);
419 [[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const; 422 [[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const;
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 44e6852fe..7d2f0abb8 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -22,6 +22,10 @@
22 22
23namespace FileSys { 23namespace FileSys {
24 24
25static u8 MasterKeyIdForKeyGeneration(u8 key_generation) {
26 return std::max<u8>(key_generation, 1) - 1;
27}
28
25NCA::NCA(VirtualFile file_, const NCA* base_nca) 29NCA::NCA(VirtualFile file_, const NCA* base_nca)
26 : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { 30 : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
27 if (file == nullptr) { 31 if (file == nullptr) {
@@ -41,12 +45,17 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca)
41 return; 45 return;
42 } 46 }
43 47
48 // Ensure we have the proper key area keys to continue.
49 const u8 master_key_id = MasterKeyIdForKeyGeneration(reader->GetKeyGeneration());
50 if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, reader->GetKeyIndex())) {
51 status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
52 return;
53 }
54
44 RightsId rights_id{}; 55 RightsId rights_id{};
45 reader->GetRightsId(rights_id.data(), rights_id.size()); 56 reader->GetRightsId(rights_id.data(), rights_id.size());
46 if (rights_id != RightsId{}) { 57 if (rights_id != RightsId{}) {
47 // External decryption key required; provide it here. 58 // External decryption key required; provide it here.
48 const auto key_generation = std::max<s32>(reader->GetKeyGeneration(), 1) - 1;
49
50 u128 rights_id_u128; 59 u128 rights_id_u128;
51 std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id)); 60 std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id));
52 61
@@ -57,12 +66,12 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca)
57 return; 66 return;
58 } 67 }
59 68
60 if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, key_generation)) { 69 if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
61 status = Loader::ResultStatus::ErrorMissingTitlekek; 70 status = Loader::ResultStatus::ErrorMissingTitlekek;
62 return; 71 return;
63 } 72 }
64 73
65 auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, key_generation); 74 auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id);
66 Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB); 75 Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB);
67 cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), 76 cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(),
68 Core::Crypto::Op::Decrypt); 77 Core::Crypto::Op::Decrypt);
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index da33f0e44..e92f400de 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -341,7 +341,7 @@ void ISelfController::Exit(HLERequestContext& ctx) {
341void ISelfController::LockExit(HLERequestContext& ctx) { 341void ISelfController::LockExit(HLERequestContext& ctx) {
342 LOG_DEBUG(Service_AM, "called"); 342 LOG_DEBUG(Service_AM, "called");
343 343
344 system.SetExitLock(true); 344 system.SetExitLocked(true);
345 345
346 IPC::ResponseBuilder rb{ctx, 2}; 346 IPC::ResponseBuilder rb{ctx, 2};
347 rb.Push(ResultSuccess); 347 rb.Push(ResultSuccess);
@@ -350,10 +350,14 @@ void ISelfController::LockExit(HLERequestContext& ctx) {
350void ISelfController::UnlockExit(HLERequestContext& ctx) { 350void ISelfController::UnlockExit(HLERequestContext& ctx) {
351 LOG_DEBUG(Service_AM, "called"); 351 LOG_DEBUG(Service_AM, "called");
352 352
353 system.SetExitLock(false); 353 system.SetExitLocked(false);
354 354
355 IPC::ResponseBuilder rb{ctx, 2}; 355 IPC::ResponseBuilder rb{ctx, 2};
356 rb.Push(ResultSuccess); 356 rb.Push(ResultSuccess);
357
358 if (system.GetExitRequested()) {
359 system.Exit();
360 }
357} 361}
358 362
359void ISelfController::EnterFatalSection(HLERequestContext& ctx) { 363void ISelfController::EnterFatalSection(HLERequestContext& ctx) {
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
index 3ad668a47..d9872ecc2 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
@@ -558,7 +558,7 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index,
558 if (multi_component) { 558 if (multi_component) {
559 if (info.num_derivates >= 3) { 559 if (info.num_derivates >= 3) {
560 const auto offset_vec{ctx.var_alloc.Consume(offset)}; 560 const auto offset_vec{ctx.var_alloc.Consume(offset)};
561 ctx.Add("{}=textureGrad({},{},vec3({}.xz, {}.x),vec3({}.yz, {}.y));", texel, texture, 561 ctx.Add("{}=textureGrad({},{},vec3({}.xz, {}.x),vec3({}.yw, {}.y));", texel, texture,
562 coords, derivatives_vec, offset_vec, derivatives_vec, offset_vec); 562 coords, derivatives_vec, offset_vec, derivatives_vec, offset_vec);
563 return; 563 return;
564 } 564 }
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
index 7d901c04b..34240b36f 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
@@ -91,6 +91,34 @@ public:
91 } 91 }
92 } 92 }
93 93
94 explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivates_1, Id derivates_2,
95 Id offset, Id lod_clamp) {
96 if (!Sirit::ValidId(derivates_1) || !Sirit::ValidId(derivates_2)) {
97 throw LogicError("Derivates must be present");
98 }
99 boost::container::static_vector<Id, 3> deriv_1_accum{
100 ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 0),
101 ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 2),
102 ctx.OpCompositeExtract(ctx.F32[1], derivates_2, 0),
103 };
104 boost::container::static_vector<Id, 3> deriv_2_accum{
105 ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 1),
106 ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 3),
107 ctx.OpCompositeExtract(ctx.F32[1], derivates_2, 1),
108 };
109 const Id derivates_id1{ctx.OpCompositeConstruct(
110 ctx.F32[3], std::span{deriv_1_accum.data(), deriv_1_accum.size()})};
111 const Id derivates_id2{ctx.OpCompositeConstruct(
112 ctx.F32[3], std::span{deriv_2_accum.data(), deriv_2_accum.size()})};
113 Add(spv::ImageOperandsMask::Grad, derivates_id1, derivates_id2);
114 if (Sirit::ValidId(offset)) {
115 Add(spv::ImageOperandsMask::Offset, offset);
116 }
117 if (has_lod_clamp) {
118 Add(spv::ImageOperandsMask::MinLod, lod_clamp);
119 }
120 }
121
94 std::span<const Id> Span() const noexcept { 122 std::span<const Id> Span() const noexcept {
95 return std::span{operands.data(), operands.size()}; 123 return std::span{operands.data(), operands.size()};
96 } 124 }
@@ -524,8 +552,11 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I
524Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, 552Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
525 Id derivates, Id offset, Id lod_clamp) { 553 Id derivates, Id offset, Id lod_clamp) {
526 const auto info{inst->Flags<IR::TextureInstInfo>()}; 554 const auto info{inst->Flags<IR::TextureInstInfo>()};
527 const ImageOperands operands(ctx, info.has_lod_clamp != 0, derivates, info.num_derivates, 555 const auto operands =
528 offset, lod_clamp); 556 info.num_derivates == 3
557 ? ImageOperands(ctx, info.has_lod_clamp != 0, derivates, offset, {}, lod_clamp)
558 : ImageOperands(ctx, info.has_lod_clamp != 0, derivates, info.num_derivates, offset,
559 lod_clamp);
529 return Emit(&EmitContext::OpImageSparseSampleExplicitLod, 560 return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
530 &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], 561 &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
531 Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); 562 Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
index 753c62098..e593132e6 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
@@ -161,7 +161,8 @@ enum class SpecialRegister : u64 {
161 LOG_WARNING(Shader, "(STUBBED) SR_AFFINITY"); 161 LOG_WARNING(Shader, "(STUBBED) SR_AFFINITY");
162 return ir.Imm32(0); // This is the default value hardware returns. 162 return ir.Imm32(0); // This is the default value hardware returns.
163 default: 163 default:
164 throw NotImplementedException("S2R special register {}", special_register); 164 LOG_CRITICAL(Shader, "(STUBBED) Special register {}", special_register);
165 return ir.Imm32(0); // This is the default value hardware returns.
165 } 166 }
166} 167}
167} // Anonymous namespace 168} // Anonymous namespace
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp
index 9f1b340a9..58ce0d8c2 100644
--- a/src/video_core/dma_pusher.cpp
+++ b/src/video_core/dma_pusher.cpp
@@ -14,6 +14,7 @@
14namespace Tegra { 14namespace Tegra {
15 15
16constexpr u32 MacroRegistersStart = 0xE00; 16constexpr u32 MacroRegistersStart = 0xE00;
17constexpr u32 ComputeInline = 0x6D;
17 18
18DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_, 19DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
19 Control::ChannelState& channel_state_) 20 Control::ChannelState& channel_state_)
@@ -83,12 +84,35 @@ bool DmaPusher::Step() {
83 dma_state.dma_get, command_list_header.size * sizeof(u32)); 84 dma_state.dma_get, command_list_header.size * sizeof(u32));
84 } 85 }
85 } 86 }
86 Core::Memory::GpuGuestMemory<Tegra::CommandHeader, 87 const auto safe_process = [&] {
87 Core::Memory::GuestMemoryFlags::UnsafeRead> 88 Core::Memory::GpuGuestMemory<Tegra::CommandHeader,
88 headers(memory_manager, dma_state.dma_get, command_list_header.size, &command_headers); 89 Core::Memory::GuestMemoryFlags::SafeRead>
89 ProcessCommands(headers); 90 headers(memory_manager, dma_state.dma_get, command_list_header.size,
91 &command_headers);
92 ProcessCommands(headers);
93 };
94 const auto unsafe_process = [&] {
95 Core::Memory::GpuGuestMemory<Tegra::CommandHeader,
96 Core::Memory::GuestMemoryFlags::UnsafeRead>
97 headers(memory_manager, dma_state.dma_get, command_list_header.size,
98 &command_headers);
99 ProcessCommands(headers);
100 };
101 if (Settings::IsGPULevelHigh()) {
102 if (dma_state.method >= MacroRegistersStart) {
103 unsafe_process();
104 return true;
105 }
106 if (subchannel_type[dma_state.subchannel] == Engines::EngineTypes::KeplerCompute &&
107 dma_state.method == ComputeInline) {
108 unsafe_process();
109 return true;
110 }
111 safe_process();
112 return true;
113 }
114 unsafe_process();
90 } 115 }
91
92 return true; 116 return true;
93} 117}
94 118
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h
index 8a2784cdc..c9fab2d90 100644
--- a/src/video_core/dma_pusher.h
+++ b/src/video_core/dma_pusher.h
@@ -130,8 +130,10 @@ public:
130 130
131 void DispatchCalls(); 131 void DispatchCalls();
132 132
133 void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id) { 133 void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id,
134 Engines::EngineTypes engine_type) {
134 subchannels[subchannel_id] = engine; 135 subchannels[subchannel_id] = engine;
136 subchannel_type[subchannel_id] = engine_type;
135 } 137 }
136 138
137 void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); 139 void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
@@ -170,6 +172,7 @@ private:
170 const bool ib_enable{true}; ///< IB mode enabled 172 const bool ib_enable{true}; ///< IB mode enabled
171 173
172 std::array<Engines::EngineInterface*, max_subchannels> subchannels{}; 174 std::array<Engines::EngineInterface*, max_subchannels> subchannels{};
175 std::array<Engines::EngineTypes, max_subchannels> subchannel_type;
173 176
174 GPU& gpu; 177 GPU& gpu;
175 Core::System& system; 178 Core::System& system;
diff --git a/src/video_core/engines/engine_interface.h b/src/video_core/engines/engine_interface.h
index 392322358..54631ee6c 100644
--- a/src/video_core/engines/engine_interface.h
+++ b/src/video_core/engines/engine_interface.h
@@ -11,6 +11,14 @@
11 11
12namespace Tegra::Engines { 12namespace Tegra::Engines {
13 13
14enum class EngineTypes : u32 {
15 KeplerCompute,
16 Maxwell3D,
17 Fermi2D,
18 MaxwellDMA,
19 KeplerMemory,
20};
21
14class EngineInterface { 22class EngineInterface {
15public: 23public:
16 virtual ~EngineInterface() = default; 24 virtual ~EngineInterface() = default;
diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h
index 7242d2529..21bf8aeb4 100644
--- a/src/video_core/engines/engine_upload.h
+++ b/src/video_core/engines/engine_upload.h
@@ -69,6 +69,14 @@ public:
69 /// Binds a rasterizer to this engine. 69 /// Binds a rasterizer to this engine.
70 void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); 70 void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
71 71
72 GPUVAddr ExecTargetAddress() const {
73 return regs.dest.Address();
74 }
75
76 u32 GetUploadSize() const {
77 return copy_size;
78 }
79
72private: 80private:
73 void ProcessData(std::span<const u8> read_buffer); 81 void ProcessData(std::span<const u8> read_buffer);
74 82
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index a38d9528a..cd61ab222 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -43,16 +43,33 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal
43 43
44 switch (method) { 44 switch (method) {
45 case KEPLER_COMPUTE_REG_INDEX(exec_upload): { 45 case KEPLER_COMPUTE_REG_INDEX(exec_upload): {
46 UploadInfo info{.upload_address = upload_address,
47 .exec_address = upload_state.ExecTargetAddress(),
48 .copy_size = upload_state.GetUploadSize()};
49 uploads.push_back(info);
46 upload_state.ProcessExec(regs.exec_upload.linear != 0); 50 upload_state.ProcessExec(regs.exec_upload.linear != 0);
47 break; 51 break;
48 } 52 }
49 case KEPLER_COMPUTE_REG_INDEX(data_upload): { 53 case KEPLER_COMPUTE_REG_INDEX(data_upload): {
54 upload_address = current_dma_segment;
50 upload_state.ProcessData(method_argument, is_last_call); 55 upload_state.ProcessData(method_argument, is_last_call);
51 break; 56 break;
52 } 57 }
53 case KEPLER_COMPUTE_REG_INDEX(launch): 58 case KEPLER_COMPUTE_REG_INDEX(launch): {
59 const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address();
60
61 for (auto& data : uploads) {
62 const GPUVAddr offset = data.exec_address - launch_desc_loc;
63 if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x) &&
64 memory_manager.IsMemoryDirty(data.upload_address, data.copy_size)) {
65 indirect_compute = {data.upload_address};
66 }
67 }
68 uploads.clear();
54 ProcessLaunch(); 69 ProcessLaunch();
70 indirect_compute = std::nullopt;
55 break; 71 break;
72 }
56 default: 73 default:
57 break; 74 break;
58 } 75 }
@@ -62,6 +79,7 @@ void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amoun
62 u32 methods_pending) { 79 u32 methods_pending) {
63 switch (method) { 80 switch (method) {
64 case KEPLER_COMPUTE_REG_INDEX(data_upload): 81 case KEPLER_COMPUTE_REG_INDEX(data_upload):
82 upload_address = current_dma_segment;
65 upload_state.ProcessData(base_start, amount); 83 upload_state.ProcessData(base_start, amount);
66 return; 84 return;
67 default: 85 default:
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index 2092e685f..735e05fb4 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -5,6 +5,7 @@
5 5
6#include <array> 6#include <array>
7#include <cstddef> 7#include <cstddef>
8#include <optional>
8#include <vector> 9#include <vector>
9#include "common/bit_field.h" 10#include "common/bit_field.h"
10#include "common/common_funcs.h" 11#include "common/common_funcs.h"
@@ -36,6 +37,9 @@ namespace Tegra::Engines {
36#define KEPLER_COMPUTE_REG_INDEX(field_name) \ 37#define KEPLER_COMPUTE_REG_INDEX(field_name) \
37 (offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32)) 38 (offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32))
38 39
40#define LAUNCH_REG_INDEX(field_name) \
41 (offsetof(Tegra::Engines::KeplerCompute::LaunchParams, field_name) / sizeof(u32))
42
39class KeplerCompute final : public EngineInterface { 43class KeplerCompute final : public EngineInterface {
40public: 44public:
41 explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager); 45 explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager);
@@ -201,6 +205,10 @@ public:
201 void CallMultiMethod(u32 method, const u32* base_start, u32 amount, 205 void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
202 u32 methods_pending) override; 206 u32 methods_pending) override;
203 207
208 std::optional<GPUVAddr> GetIndirectComputeAddress() const {
209 return indirect_compute;
210 }
211
204private: 212private:
205 void ProcessLaunch(); 213 void ProcessLaunch();
206 214
@@ -216,6 +224,15 @@ private:
216 MemoryManager& memory_manager; 224 MemoryManager& memory_manager;
217 VideoCore::RasterizerInterface* rasterizer = nullptr; 225 VideoCore::RasterizerInterface* rasterizer = nullptr;
218 Upload::State upload_state; 226 Upload::State upload_state;
227 GPUVAddr upload_address;
228
229 struct UploadInfo {
230 GPUVAddr upload_address;
231 GPUVAddr exec_address;
232 u32 copy_size;
233 };
234 std::vector<UploadInfo> uploads;
235 std::optional<GPUVAddr> indirect_compute{};
219}; 236};
220 237
221#define ASSERT_REG_POSITION(field_name, position) \ 238#define ASSERT_REG_POSITION(field_name, position) \
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index c3696096d..06e349e43 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -257,6 +257,7 @@ u32 Maxwell3D::GetMaxCurrentVertices() {
257 const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); 257 const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
258 num_vertices = std::max( 258 num_vertices = std::max(
259 num_vertices, address_size / std::max(attribute.SizeInBytes(), array.stride.Value())); 259 num_vertices, address_size / std::max(attribute.SizeInBytes(), array.stride.Value()));
260 break;
260 } 261 }
261 return num_vertices; 262 return num_vertices;
262} 263}
@@ -269,10 +270,13 @@ size_t Maxwell3D::EstimateIndexBufferSize() {
269 std::numeric_limits<u32>::max()}; 270 std::numeric_limits<u32>::max()};
270 const size_t byte_size = regs.index_buffer.FormatSizeInBytes(); 271 const size_t byte_size = regs.index_buffer.FormatSizeInBytes();
271 const size_t log2_byte_size = Common::Log2Ceil64(byte_size); 272 const size_t log2_byte_size = Common::Log2Ceil64(byte_size);
273 const size_t cap{GetMaxCurrentVertices() * 3 * byte_size};
274 const size_t lower_cap =
275 std::min<size_t>(static_cast<size_t>(end_address - start_address), cap);
272 return std::min<size_t>( 276 return std::min<size_t>(
273 memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) / 277 memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) /
274 byte_size, 278 byte_size,
275 static_cast<size_t>(end_address - start_address)); 279 lower_cap);
276} 280}
277 281
278u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) { 282u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) {
diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp
index 7718a09b3..6de2543b7 100644
--- a/src/video_core/engines/puller.cpp
+++ b/src/video_core/engines/puller.cpp
@@ -34,19 +34,24 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) {
34 bound_engines[method_call.subchannel] = engine_id; 34 bound_engines[method_call.subchannel] = engine_id;
35 switch (engine_id) { 35 switch (engine_id) {
36 case EngineID::FERMI_TWOD_A: 36 case EngineID::FERMI_TWOD_A:
37 dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel); 37 dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel,
38 EngineTypes::Fermi2D);
38 break; 39 break;
39 case EngineID::MAXWELL_B: 40 case EngineID::MAXWELL_B:
40 dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel); 41 dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel,
42 EngineTypes::Maxwell3D);
41 break; 43 break;
42 case EngineID::KEPLER_COMPUTE_B: 44 case EngineID::KEPLER_COMPUTE_B:
43 dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel); 45 dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel,
46 EngineTypes::KeplerCompute);
44 break; 47 break;
45 case EngineID::MAXWELL_DMA_COPY_A: 48 case EngineID::MAXWELL_DMA_COPY_A:
46 dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel); 49 dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel,
50 EngineTypes::MaxwellDMA);
47 break; 51 break;
48 case EngineID::KEPLER_INLINE_TO_MEMORY_B: 52 case EngineID::KEPLER_INLINE_TO_MEMORY_B:
49 dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel); 53 dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel,
54 EngineTypes::KeplerMemory);
50 break; 55 break;
51 default: 56 default:
52 UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); 57 UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 1ba31be88..dd03efecd 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -380,6 +380,17 @@ void RasterizerOpenGL::DispatchCompute() {
380 pipeline->SetEngine(kepler_compute, gpu_memory); 380 pipeline->SetEngine(kepler_compute, gpu_memory);
381 pipeline->Configure(); 381 pipeline->Configure();
382 const auto& qmd{kepler_compute->launch_description}; 382 const auto& qmd{kepler_compute->launch_description};
383 auto indirect_address = kepler_compute->GetIndirectComputeAddress();
384 if (indirect_address) {
385 // DispatchIndirect
386 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
387 const auto post_op = VideoCommon::ObtainBufferOperation::DiscardWrite;
388 const auto [buffer, offset] =
389 buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op);
390 glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, buffer->Handle());
391 glDispatchComputeIndirect(static_cast<GLintptr>(offset));
392 return;
393 }
383 glDispatchCompute(qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z); 394 glDispatchCompute(qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z);
384 ++num_queued_commands; 395 ++num_queued_commands;
385 has_written_global_memory |= pipeline->WritesGlobalMemory(); 396 has_written_global_memory |= pipeline->WritesGlobalMemory();
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index fe432dfe1..4f83a88e1 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -665,6 +665,19 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
665 std::move(modules), infos); 665 std::move(modules), infos);
666 666
667} catch (const Shader::Exception& exception) { 667} catch (const Shader::Exception& exception) {
668 auto hash = key.Hash();
669 size_t env_index{0};
670 for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
671 if (key.unique_hashes[index] == 0) {
672 continue;
673 }
674 Shader::Environment& env{*envs[env_index]};
675 ++env_index;
676
677 const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))};
678 Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0);
679 env.Dump(hash, key.unique_hashes[index]);
680 }
668 LOG_ERROR(Render_Vulkan, "{}", exception.what()); 681 LOG_ERROR(Render_Vulkan, "{}", exception.what());
669 return nullptr; 682 return nullptr;
670} 683}
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 032f694bc..01e76a82c 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -463,6 +463,20 @@ void RasterizerVulkan::DispatchCompute() {
463 pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache); 463 pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache);
464 464
465 const auto& qmd{kepler_compute->launch_description}; 465 const auto& qmd{kepler_compute->launch_description};
466 auto indirect_address = kepler_compute->GetIndirectComputeAddress();
467 if (indirect_address) {
468 // DispatchIndirect
469 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
470 const auto post_op = VideoCommon::ObtainBufferOperation::DiscardWrite;
471 const auto [buffer, offset] =
472 buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op);
473 scheduler.RequestOutsideRenderPassOperationContext();
474 scheduler.Record([indirect_buffer = buffer->Handle(),
475 indirect_offset = offset](vk::CommandBuffer cmdbuf) {
476 cmdbuf.DispatchIndirect(indirect_buffer, indirect_offset);
477 });
478 return;
479 }
466 const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z}; 480 const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z};
467 scheduler.RequestOutsideRenderPassOperationContext(); 481 scheduler.RequestOutsideRenderPassOperationContext();
468 scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); 482 scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); });
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index 78e5a248f..c3f388d89 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -92,6 +92,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
92 X(vkCmdCopyImage); 92 X(vkCmdCopyImage);
93 X(vkCmdCopyImageToBuffer); 93 X(vkCmdCopyImageToBuffer);
94 X(vkCmdDispatch); 94 X(vkCmdDispatch);
95 X(vkCmdDispatchIndirect);
95 X(vkCmdDraw); 96 X(vkCmdDraw);
96 X(vkCmdDrawIndexed); 97 X(vkCmdDrawIndexed);
97 X(vkCmdDrawIndirect); 98 X(vkCmdDrawIndirect);
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index c226a2a29..049fa8038 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -203,6 +203,7 @@ struct DeviceDispatch : InstanceDispatch {
203 PFN_vkCmdCopyImage vkCmdCopyImage{}; 203 PFN_vkCmdCopyImage vkCmdCopyImage{};
204 PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; 204 PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{};
205 PFN_vkCmdDispatch vkCmdDispatch{}; 205 PFN_vkCmdDispatch vkCmdDispatch{};
206 PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{};
206 PFN_vkCmdDraw vkCmdDraw{}; 207 PFN_vkCmdDraw vkCmdDraw{};
207 PFN_vkCmdDrawIndexed vkCmdDrawIndexed{}; 208 PFN_vkCmdDrawIndexed vkCmdDrawIndexed{};
208 PFN_vkCmdDrawIndirect vkCmdDrawIndirect{}; 209 PFN_vkCmdDrawIndirect vkCmdDrawIndirect{};
@@ -1209,6 +1210,10 @@ public:
1209 dld->vkCmdDispatch(handle, x, y, z); 1210 dld->vkCmdDispatch(handle, x, y, z);
1210 } 1211 }
1211 1212
1213 void DispatchIndirect(VkBuffer indirect_buffer, VkDeviceSize offset) const noexcept {
1214 dld->vkCmdDispatchIndirect(handle, indirect_buffer, offset);
1215 }
1216
1212 void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, 1217 void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
1213 VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers, 1218 VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers,
1214 Span<VkBufferMemoryBarrier> buffer_barriers, 1219 Span<VkBufferMemoryBarrier> buffer_barriers,
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 33c9fd0af..f2e6c03f0 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -2010,8 +2010,16 @@ bool GMainWindow::OnShutdownBegin() {
2010 2010
2011 emit EmulationStopping(); 2011 emit EmulationStopping();
2012 2012
2013 int shutdown_time = 1000;
2014
2015 if (system->DebuggerEnabled()) {
2016 shutdown_time = 0;
2017 } else if (system->GetExitLocked()) {
2018 shutdown_time = 5000;
2019 }
2020
2013 shutdown_timer.setSingleShot(true); 2021 shutdown_timer.setSingleShot(true);
2014 shutdown_timer.start(system->DebuggerEnabled() ? 0 : 5000); 2022 shutdown_timer.start(shutdown_time);
2015 connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired); 2023 connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired);
2016 connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped); 2024 connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped);
2017 2025
@@ -2573,50 +2581,48 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
2573 return; 2581 return;
2574 } 2582 }
2575 2583
2576 FileSys::VirtualFile base_romfs; 2584 FileSys::VirtualFile packed_update_raw{};
2577 if (loader->ReadRomFS(base_romfs) != Loader::ResultStatus::Success) { 2585 loader->ReadUpdateRaw(packed_update_raw);
2578 failed();
2579 return;
2580 }
2581 2586
2582 const auto& installed = system->GetContentProvider(); 2587 const auto& installed = system->GetContentProvider();
2583 const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id);
2584 2588
2585 if (!romfs_title_id) { 2589 u64 title_id{};
2590 u8 raw_type{};
2591 if (!SelectRomFSDumpTarget(installed, program_id, &title_id, &raw_type)) {
2586 failed(); 2592 failed();
2587 return; 2593 return;
2588 } 2594 }
2589 2595
2590 const auto type = *romfs_title_id == program_id ? FileSys::ContentRecordType::Program 2596 const auto type = static_cast<FileSys::ContentRecordType>(raw_type);
2591 : FileSys::ContentRecordType::Data; 2597 const auto base_nca = installed.GetEntry(title_id, type);
2592 const auto base_nca = installed.GetEntry(*romfs_title_id, type);
2593 if (!base_nca) { 2598 if (!base_nca) {
2594 failed(); 2599 failed();
2595 return; 2600 return;
2596 } 2601 }
2597 2602
2603 const FileSys::NCA update_nca{packed_update_raw, nullptr};
2604 if (type != FileSys::ContentRecordType::Program ||
2605 update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS ||
2606 update_nca.GetTitleId() != FileSys::GetUpdateTitleID(title_id)) {
2607 packed_update_raw = {};
2608 }
2609
2610 const auto base_romfs = base_nca->GetRomFS();
2611 if (!base_romfs) {
2612 failed();
2613 return;
2614 }
2615
2598 const auto dump_dir = 2616 const auto dump_dir =
2599 target == DumpRomFSTarget::Normal 2617 target == DumpRomFSTarget::Normal
2600 ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) 2618 ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)
2601 : Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents"; 2619 : Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents";
2602 const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id); 2620 const auto romfs_dir = fmt::format("{:016X}/romfs", title_id);
2603 2621
2604 const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); 2622 const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir);
2605 2623
2606 FileSys::VirtualFile romfs; 2624 const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), installed};
2607 2625 auto romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, packed_update_raw, false);
2608 if (*romfs_title_id == program_id) {
2609 const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), installed};
2610 romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, nullptr, false);
2611 } else {
2612 romfs = installed.GetEntry(*romfs_title_id, type)->GetRomFS();
2613 }
2614
2615 const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
2616 if (extracted == nullptr) {
2617 failed();
2618 return;
2619 }
2620 2626
2621 const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite); 2627 const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite);
2622 2628
@@ -2640,6 +2646,12 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
2640 return; 2646 return;
2641 } 2647 }
2642 2648
2649 const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
2650 if (extracted == nullptr) {
2651 failed();
2652 return;
2653 }
2654
2643 const auto full = res == selections.constFirst(); 2655 const auto full = res == selections.constFirst();
2644 const auto entry_size = CalculateRomFSEntrySize(extracted, full); 2656 const auto entry_size = CalculateRomFSEntrySize(extracted, full);
2645 2657
@@ -3261,7 +3273,7 @@ void GMainWindow::OnPauseContinueGame() {
3261} 3273}
3262 3274
3263void GMainWindow::OnStopGame() { 3275void GMainWindow::OnStopGame() {
3264 if (system->GetExitLock() && !ConfirmForceLockedExit()) { 3276 if (system->GetExitLocked() && !ConfirmForceLockedExit()) {
3265 return; 3277 return;
3266 } 3278 }
3267 3279
@@ -4350,28 +4362,41 @@ bool GMainWindow::CheckSystemArchiveDecryption() {
4350 return mii_nca->GetRomFS().get() != nullptr; 4362 return mii_nca->GetRomFS().get() != nullptr;
4351} 4363}
4352 4364
4353std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, 4365bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
4354 u64 program_id) { 4366 u64* selected_title_id, u8* selected_content_record_type) {
4355 const auto dlc_entries = 4367 using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>;
4356 installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); 4368 boost::container::flat_map<u64, ContentInfo> available_title_ids;
4357 std::vector<FileSys::ContentProviderEntry> dlc_match; 4369
4358 dlc_match.reserve(dlc_entries.size()); 4370 const auto RetrieveEntries = [&](FileSys::TitleType title_type,
4359 std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), 4371 FileSys::ContentRecordType record_type) {
4360 [&program_id, &installed](const FileSys::ContentProviderEntry& entry) { 4372 const auto entries = installed.ListEntriesFilter(title_type, record_type);
4361 return FileSys::GetBaseTitleID(entry.title_id) == program_id && 4373 for (const auto& entry : entries) {
4362 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; 4374 if (FileSys::GetBaseTitleID(entry.title_id) == program_id &&
4363 }); 4375 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) {
4364 4376 available_title_ids[entry.title_id] = {title_type, record_type};
4365 std::vector<u64> romfs_tids; 4377 }
4366 romfs_tids.push_back(program_id); 4378 }
4367 for (const auto& entry : dlc_match) { 4379 };
4368 romfs_tids.push_back(entry.title_id); 4380
4369 } 4381 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
4370 4382 RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
4371 if (romfs_tids.size() > 1) { 4383
4372 QStringList list{QStringLiteral("Base")}; 4384 if (available_title_ids.empty()) {
4373 for (std::size_t i = 1; i < romfs_tids.size(); ++i) { 4385 return false;
4374 list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF)); 4386 }
4387
4388 size_t title_index = 0;
4389
4390 if (available_title_ids.size() > 1) {
4391 QStringList list;
4392 for (auto& [title_id, content_info] : available_title_ids) {
4393 const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id));
4394 if (content_info.first == FileSys::TitleType::Application) {
4395 list.push_back(QStringLiteral("Application [%1]").arg(hex_title_id));
4396 } else {
4397 list.push_back(
4398 QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id));
4399 }
4375 } 4400 }
4376 4401
4377 bool ok; 4402 bool ok;
@@ -4379,13 +4404,16 @@ std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProv
4379 this, tr("Select RomFS Dump Target"), 4404 this, tr("Select RomFS Dump Target"),
4380 tr("Please select which RomFS you would like to dump."), list, 0, false, &ok); 4405 tr("Please select which RomFS you would like to dump."), list, 0, false, &ok);
4381 if (!ok) { 4406 if (!ok) {
4382 return {}; 4407 return false;
4383 } 4408 }
4384 4409
4385 return romfs_tids[list.indexOf(res)]; 4410 title_index = list.indexOf(res);
4386 } 4411 }
4387 4412
4388 return program_id; 4413 const auto selected_info = available_title_ids.nth(title_index);
4414 *selected_title_id = selected_info->first;
4415 *selected_content_record_type = static_cast<u8>(selected_info->second.second);
4416 return true;
4389} 4417}
4390 4418
4391bool GMainWindow::ConfirmClose() { 4419bool GMainWindow::ConfirmClose() {
@@ -4515,6 +4543,8 @@ void GMainWindow::RequestGameExit() {
4515 auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); 4543 auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
4516 bool has_signalled = false; 4544 bool has_signalled = false;
4517 4545
4546 system->SetExitRequested(true);
4547
4518 if (applet_oe != nullptr) { 4548 if (applet_oe != nullptr) {
4519 applet_oe->GetMessageQueue()->RequestExit(); 4549 applet_oe->GetMessageQueue()->RequestExit();
4520 has_signalled = true; 4550 has_signalled = true;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 1b7055122..668dbc3b1 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -375,7 +375,8 @@ private:
375 void RemoveAllTransferableShaderCaches(u64 program_id); 375 void RemoveAllTransferableShaderCaches(u64 program_id);
376 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); 376 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
377 void RemoveCacheStorage(u64 program_id); 377 void RemoveCacheStorage(u64 program_id);
378 std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); 378 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
379 u64* selected_title_id, u8* selected_content_record_type);
379 InstallResult InstallNSPXCI(const QString& filename); 380 InstallResult InstallNSPXCI(const QString& filename);
380 InstallResult InstallNCA(const QString& filename); 381 InstallResult InstallNCA(const QString& filename);
381 void MigrateConfigFiles(); 382 void MigrateConfigFiles();