summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/hle/kernel/process.cpp4
-rw-r--r--src/core/hle/kernel/process.h3
-rw-r--r--src/core/hle/kernel/svc.cpp2
-rw-r--r--src/core/hle/kernel/vm_manager.cpp6
-rw-r--r--src/core/hle/kernel/vm_manager.h3
-rw-r--r--src/core/hle/service/ldr/ldr.cpp397
-rw-r--r--src/core/hle/service/ns/pl_u.cpp8
-rw-r--r--src/core/hle/service/time/interface.cpp3
-rw-r--r--src/core/hle/service/time/time.cpp15
-rw-r--r--src/core/hle/service/time/time.h1
-rw-r--r--src/core/hle/service/vi/vi.cpp22
-rw-r--r--src/core/loader/nro.cpp7
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp7
-rw-r--r--src/video_core/textures/decoders.cpp15
-rw-r--r--src/video_core/textures/decoders.h7
-rw-r--r--src/yuzu/configuration/configure_gamelist.cpp14
-rw-r--r--src/yuzu/configuration/configure_gamelist.h2
-rw-r--r--src/yuzu/configuration/configure_general.cpp3
-rw-r--r--src/yuzu/main.cpp8
-rw-r--r--src/yuzu/ui_settings.h2
-rw-r--r--src/yuzu_cmd/config.cpp2
22 files changed, 496 insertions, 37 deletions
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index f06b6bb55..a257c3726 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -252,8 +252,8 @@ ResultCode Process::HeapFree(VAddr target, u32 size) {
252 return vm_manager.HeapFree(target, size); 252 return vm_manager.HeapFree(target, size);
253} 253}
254 254
255ResultCode Process::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size) { 255ResultCode Process::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state) {
256 return vm_manager.MirrorMemory(dst_addr, src_addr, size); 256 return vm_manager.MirrorMemory(dst_addr, src_addr, size, state);
257} 257}
258 258
259ResultCode Process::UnmapMemory(VAddr dst_addr, VAddr /*src_addr*/, u64 size) { 259ResultCode Process::UnmapMemory(VAddr dst_addr, VAddr /*src_addr*/, u64 size) {
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index cf48787ce..230e395ff 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -259,7 +259,8 @@ public:
259 ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms); 259 ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);
260 ResultCode HeapFree(VAddr target, u32 size); 260 ResultCode HeapFree(VAddr target, u32 size);
261 261
262 ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size); 262 ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size,
263 MemoryState state = MemoryState::Mapped);
263 264
264 ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size); 265 ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size);
265 266
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 2e7c9d094..75dbfc31d 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -1181,7 +1181,7 @@ static ResultCode CloseHandle(Handle handle) {
1181 1181
1182/// Reset an event 1182/// Reset an event
1183static ResultCode ResetSignal(Handle handle) { 1183static ResultCode ResetSignal(Handle handle) {
1184 LOG_WARNING(Kernel_SVC, "(STUBBED) called handle 0x{:08X}", handle); 1184 LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle);
1185 1185
1186 const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); 1186 const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
1187 auto event = handle_table.Get<Event>(handle); 1187 auto event = handle_table.Get<Event>(handle);
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index ec7fd6150..100f8f6bf 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -298,7 +298,7 @@ ResultCode VMManager::HeapFree(VAddr target, u64 size) {
298 return RESULT_SUCCESS; 298 return RESULT_SUCCESS;
299} 299}
300 300
301ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size) { 301ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state) {
302 const auto vma = FindVMA(src_addr); 302 const auto vma = FindVMA(src_addr);
303 303
304 ASSERT_MSG(vma != vma_map.end(), "Invalid memory address"); 304 ASSERT_MSG(vma != vma_map.end(), "Invalid memory address");
@@ -312,8 +312,8 @@ ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
312 const std::shared_ptr<std::vector<u8>>& backing_block = vma->second.backing_block; 312 const std::shared_ptr<std::vector<u8>>& backing_block = vma->second.backing_block;
313 const std::size_t backing_block_offset = vma->second.offset + vma_offset; 313 const std::size_t backing_block_offset = vma->second.offset + vma_offset;
314 314
315 CASCADE_RESULT(auto new_vma, MapMemoryBlock(dst_addr, backing_block, backing_block_offset, size, 315 CASCADE_RESULT(auto new_vma,
316 MemoryState::Mapped)); 316 MapMemoryBlock(dst_addr, backing_block, backing_block_offset, size, state));
317 // Protect mirror with permissions from old region 317 // Protect mirror with permissions from old region
318 Reprotect(new_vma, vma->second.permissions); 318 Reprotect(new_vma, vma->second.permissions);
319 // Remove permissions from old region 319 // Remove permissions from old region
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 248cc46dc..d522404fe 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -189,7 +189,8 @@ public:
189 ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms); 189 ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);
190 ResultCode HeapFree(VAddr target, u64 size); 190 ResultCode HeapFree(VAddr target, u64 size);
191 191
192 ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size); 192 ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size,
193 MemoryState state = MemoryState::Mapped);
193 194
194 /** 195 /**
195 * Scans all VMAs and updates the page table range of any that use the given vector as backing 196 * Scans all VMAs and updates the page table range of any that use the given vector as backing
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index d607d985e..b43f1f054 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -4,7 +4,10 @@
4 4
5#include <memory> 5#include <memory>
6#include <fmt/format.h> 6#include <fmt/format.h>
7#include <mbedtls/sha256.h>
7 8
9#include "common/alignment.h"
10#include "common/hex_util.h"
8#include "core/hle/ipc_helpers.h" 11#include "core/hle/ipc_helpers.h"
9#include "core/hle/kernel/process.h" 12#include "core/hle/kernel/process.h"
10#include "core/hle/service/ldr/ldr.h" 13#include "core/hle/service/ldr/ldr.h"
@@ -13,6 +16,38 @@
13 16
14namespace Service::LDR { 17namespace Service::LDR {
15 18
19namespace ErrCodes {
20enum {
21 InvalidMemoryState = 51,
22 InvalidNRO = 52,
23 InvalidNRR = 53,
24 MissingNRRHash = 54,
25 MaximumNRO = 55,
26 MaximumNRR = 56,
27 AlreadyLoaded = 57,
28 InvalidAlignment = 81,
29 InvalidSize = 82,
30 InvalidNROAddress = 84,
31 InvalidNRRAddress = 85,
32 NotInitialized = 87,
33};
34}
35
36constexpr ResultCode ERROR_INVALID_MEMORY_STATE(ErrorModule::Loader, ErrCodes::InvalidMemoryState);
37constexpr ResultCode ERROR_INVALID_NRO(ErrorModule::Loader, ErrCodes::InvalidNRO);
38constexpr ResultCode ERROR_INVALID_NRR(ErrorModule::Loader, ErrCodes::InvalidNRR);
39constexpr ResultCode ERROR_MISSING_NRR_HASH(ErrorModule::Loader, ErrCodes::MissingNRRHash);
40constexpr ResultCode ERROR_MAXIMUM_NRO(ErrorModule::Loader, ErrCodes::MaximumNRO);
41constexpr ResultCode ERROR_MAXIMUM_NRR(ErrorModule::Loader, ErrCodes::MaximumNRR);
42constexpr ResultCode ERROR_ALREADY_LOADED(ErrorModule::Loader, ErrCodes::AlreadyLoaded);
43constexpr ResultCode ERROR_INVALID_ALIGNMENT(ErrorModule::Loader, ErrCodes::InvalidAlignment);
44constexpr ResultCode ERROR_INVALID_SIZE(ErrorModule::Loader, ErrCodes::InvalidSize);
45constexpr ResultCode ERROR_INVALID_NRO_ADDRESS(ErrorModule::Loader, ErrCodes::InvalidNROAddress);
46constexpr ResultCode ERROR_INVALID_NRR_ADDRESS(ErrorModule::Loader, ErrCodes::InvalidNRRAddress);
47constexpr ResultCode ERROR_NOT_INITIALIZED(ErrorModule::Loader, ErrCodes::NotInitialized);
48
49constexpr u64 MAXIMUM_LOADED_RO = 0x40;
50
16class DebugMonitor final : public ServiceFramework<DebugMonitor> { 51class DebugMonitor final : public ServiceFramework<DebugMonitor> {
17public: 52public:
18 explicit DebugMonitor() : ServiceFramework{"ldr:dmnt"} { 53 explicit DebugMonitor() : ServiceFramework{"ldr:dmnt"} {
@@ -64,9 +99,9 @@ public:
64 // clang-format off 99 // clang-format off
65 static const FunctionInfo functions[] = { 100 static const FunctionInfo functions[] = {
66 {0, &RelocatableObject::LoadNro, "LoadNro"}, 101 {0, &RelocatableObject::LoadNro, "LoadNro"},
67 {1, nullptr, "UnloadNro"}, 102 {1, &RelocatableObject::UnloadNro, "UnloadNro"},
68 {2, &RelocatableObject::LoadNrr, "LoadNrr"}, 103 {2, &RelocatableObject::LoadNrr, "LoadNrr"},
69 {3, nullptr, "UnloadNrr"}, 104 {3, &RelocatableObject::UnloadNrr, "UnloadNrr"},
70 {4, &RelocatableObject::Initialize, "Initialize"}, 105 {4, &RelocatableObject::Initialize, "Initialize"},
71 }; 106 };
72 // clang-format on 107 // clang-format on
@@ -75,9 +110,123 @@ public:
75 } 110 }
76 111
77 void LoadNrr(Kernel::HLERequestContext& ctx) { 112 void LoadNrr(Kernel::HLERequestContext& ctx) {
113 IPC::RequestParser rp{ctx};
114 rp.Skip(2, false);
115 const VAddr nrr_addr{rp.Pop<VAddr>()};
116 const u64 nrr_size{rp.Pop<u64>()};
117
118 if (!initialized) {
119 LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
120 IPC::ResponseBuilder rb{ctx, 2};
121 rb.Push(ERROR_NOT_INITIALIZED);
122 return;
123 }
124
125 if (nrr.size() >= MAXIMUM_LOADED_RO) {
126 LOG_ERROR(Service_LDR, "Loading new NRR would exceed the maximum number of loaded NRRs "
127 "(0x40)! Failing...");
128 IPC::ResponseBuilder rb{ctx, 2};
129 rb.Push(ERROR_MAXIMUM_NRR);
130 return;
131 }
132
133 // NRR Address does not fall on 0x1000 byte boundary
134 if (!Common::Is4KBAligned(nrr_addr)) {
135 LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", nrr_addr);
136 IPC::ResponseBuilder rb{ctx, 2};
137 rb.Push(ERROR_INVALID_ALIGNMENT);
138 return;
139 }
140
141 // NRR Size is zero or causes overflow
142 if (nrr_addr + nrr_size <= nrr_addr || nrr_size == 0 || !Common::Is4KBAligned(nrr_size)) {
143 LOG_ERROR(Service_LDR, "NRR Size is invalid! (nrr_address={:016X}, nrr_size={:016X})",
144 nrr_addr, nrr_size);
145 IPC::ResponseBuilder rb{ctx, 2};
146 rb.Push(ERROR_INVALID_SIZE);
147 return;
148 }
149 // Read NRR data from memory
150 std::vector<u8> nrr_data(nrr_size);
151 Memory::ReadBlock(nrr_addr, nrr_data.data(), nrr_size);
152 NRRHeader header;
153 std::memcpy(&header, nrr_data.data(), sizeof(NRRHeader));
154
155 if (header.magic != Common::MakeMagic('N', 'R', 'R', '0')) {
156 LOG_ERROR(Service_LDR, "NRR did not have magic 'NRR0' (actual {:08X})!", header.magic);
157 IPC::ResponseBuilder rb{ctx, 2};
158 rb.Push(ERROR_INVALID_NRR);
159 return;
160 }
161
162 if (header.size != nrr_size) {
163 LOG_ERROR(Service_LDR,
164 "NRR header reported size did not match LoadNrr parameter size! "
165 "(header_size={:016X}, loadnrr_size={:016X})",
166 header.size, nrr_size);
167 IPC::ResponseBuilder rb{ctx, 2};
168 rb.Push(ERROR_INVALID_SIZE);
169 return;
170 }
171
172 if (Core::CurrentProcess()->GetTitleID() != header.title_id) {
173 LOG_ERROR(Service_LDR,
174 "Attempting to load NRR with title ID other than current process. (actual "
175 "{:016X})!",
176 header.title_id);
177 IPC::ResponseBuilder rb{ctx, 2};
178 rb.Push(ERROR_INVALID_NRR);
179 return;
180 }
181
182 std::vector<SHA256Hash> hashes;
183
184 // Copy all hashes in the NRR (specified by hash count/hash offset) into vector.
185 for (std::size_t i = header.hash_offset;
186 i < (header.hash_offset + (header.hash_count * sizeof(SHA256Hash))); i += 8) {
187 SHA256Hash hash;
188 std::memcpy(hash.data(), nrr_data.data() + i, sizeof(SHA256Hash));
189 hashes.emplace_back(hash);
190 }
191
192 nrr.insert_or_assign(nrr_addr, std::move(hashes));
193
194 IPC::ResponseBuilder rb{ctx, 2};
195 rb.Push(RESULT_SUCCESS);
196 }
197
198 void UnloadNrr(Kernel::HLERequestContext& ctx) {
199 if (!initialized) {
200 LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
201 IPC::ResponseBuilder rb{ctx, 2};
202 rb.Push(ERROR_NOT_INITIALIZED);
203 return;
204 }
205
206 IPC::RequestParser rp{ctx};
207 rp.Skip(2, false);
208 const auto nrr_addr{rp.Pop<VAddr>()};
209
210 if (!Common::Is4KBAligned(nrr_addr)) {
211 LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", nrr_addr);
212 IPC::ResponseBuilder rb{ctx, 2};
213 rb.Push(ERROR_INVALID_ALIGNMENT);
214 return;
215 }
216
217 const auto iter = nrr.find(nrr_addr);
218 if (iter == nrr.end()) {
219 LOG_ERROR(Service_LDR,
220 "Attempting to unload NRR which has not been loaded! (addr={:016X})",
221 nrr_addr);
222 IPC::ResponseBuilder rb{ctx, 2};
223 rb.Push(ERROR_INVALID_NRR_ADDRESS);
224 return;
225 }
226
227 nrr.erase(iter);
78 IPC::ResponseBuilder rb{ctx, 2}; 228 IPC::ResponseBuilder rb{ctx, 2};
79 rb.Push(RESULT_SUCCESS); 229 rb.Push(RESULT_SUCCESS);
80 LOG_WARNING(Service_LDR, "(STUBBED) called");
81 } 230 }
82 231
83 void LoadNro(Kernel::HLERequestContext& ctx) { 232 void LoadNro(Kernel::HLERequestContext& ctx) {
@@ -88,33 +237,253 @@ public:
88 const VAddr bss_addr{rp.Pop<VAddr>()}; 237 const VAddr bss_addr{rp.Pop<VAddr>()};
89 const u64 bss_size{rp.Pop<u64>()}; 238 const u64 bss_size{rp.Pop<u64>()};
90 239
240 if (!initialized) {
241 LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
242 IPC::ResponseBuilder rb{ctx, 2};
243 rb.Push(ERROR_NOT_INITIALIZED);
244 return;
245 }
246
247 if (nro.size() >= MAXIMUM_LOADED_RO) {
248 LOG_ERROR(Service_LDR, "Loading new NRO would exceed the maximum number of loaded NROs "
249 "(0x40)! Failing...");
250 IPC::ResponseBuilder rb{ctx, 2};
251 rb.Push(ERROR_MAXIMUM_NRO);
252 return;
253 }
254
255 // NRO Address does not fall on 0x1000 byte boundary
256 if (!Common::Is4KBAligned(nro_addr)) {
257 LOG_ERROR(Service_LDR, "NRO Address has invalid alignment (actual {:016X})!", nro_addr);
258 IPC::ResponseBuilder rb{ctx, 2};
259 rb.Push(ERROR_INVALID_ALIGNMENT);
260 return;
261 }
262
263 // NRO Size or BSS Size is zero or causes overflow
264 const auto nro_size_valid =
265 nro_size != 0 && nro_addr + nro_size > nro_addr && Common::Is4KBAligned(nro_size);
266 const auto bss_size_valid =
267 nro_size + bss_size >= nro_size && (bss_size == 0 || bss_addr + bss_size > bss_addr);
268
269 if (!nro_size_valid || !bss_size_valid) {
270 LOG_ERROR(Service_LDR,
271 "NRO Size or BSS Size is invalid! (nro_address={:016X}, nro_size={:016X}, "
272 "bss_address={:016X}, bss_size={:016X})",
273 nro_addr, nro_size, bss_addr, bss_size);
274 IPC::ResponseBuilder rb{ctx, 2};
275 rb.Push(ERROR_INVALID_SIZE);
276 return;
277 }
278
91 // Read NRO data from memory 279 // Read NRO data from memory
92 std::vector<u8> nro_data(nro_size); 280 std::vector<u8> nro_data(nro_size);
93 Memory::ReadBlock(nro_addr, nro_data.data(), nro_size); 281 Memory::ReadBlock(nro_addr, nro_data.data(), nro_size);
94 282
283 SHA256Hash hash{};
284 mbedtls_sha256(nro_data.data(), nro_data.size(), hash.data(), 0);
285
286 // NRO Hash is already loaded
287 if (std::any_of(nro.begin(), nro.end(), [&hash](const std::pair<VAddr, NROInfo>& info) {
288 return info.second.hash == hash;
289 })) {
290 LOG_ERROR(Service_LDR, "NRO is already loaded!");
291 IPC::ResponseBuilder rb{ctx, 2};
292 rb.Push(ERROR_ALREADY_LOADED);
293 return;
294 }
295
296 // NRO Hash is not in any loaded NRR
297 if (!IsValidNROHash(hash)) {
298 LOG_ERROR(Service_LDR,
299 "NRO hash is not present in any currently loaded NRRs (hash={})!",
300 Common::HexArrayToString(hash));
301 IPC::ResponseBuilder rb{ctx, 2};
302 rb.Push(ERROR_MISSING_NRR_HASH);
303 return;
304 }
305
306 NROHeader header;
307 std::memcpy(&header, nro_data.data(), sizeof(NROHeader));
308
309 if (!IsValidNRO(header, nro_size, bss_size)) {
310 LOG_ERROR(Service_LDR, "NRO was invalid!");
311 IPC::ResponseBuilder rb{ctx, 2};
312 rb.Push(ERROR_INVALID_NRO);
313 return;
314 }
315
95 // Load NRO as new executable module 316 // Load NRO as new executable module
96 const VAddr addr{*Core::CurrentProcess()->VMManager().FindFreeRegion(nro_size + bss_size)}; 317 auto* process = Core::CurrentProcess();
97 Loader::AppLoader_NRO::LoadNro(nro_data, fmt::format("nro-{:08x}", addr), addr); 318 auto& vm_manager = process->VMManager();
319 auto map_address = vm_manager.FindFreeRegion(nro_size + bss_size);
320
321 if (!map_address.Succeeded() ||
322 *map_address + nro_size + bss_size > vm_manager.GetAddressSpaceEndAddress()) {
323
324 LOG_ERROR(Service_LDR,
325 "General error while allocation memory or no available memory to allocate!");
326 IPC::ResponseBuilder rb{ctx, 2};
327 rb.Push(ERROR_INVALID_MEMORY_STATE);
328 return;
329 }
330
331 ASSERT(process->MirrorMemory(*map_address, nro_addr, nro_size,
332 Kernel::MemoryState::ModuleCodeStatic) == RESULT_SUCCESS);
333 ASSERT(process->UnmapMemory(nro_addr, 0, nro_size) == RESULT_SUCCESS);
334
335 if (bss_size > 0) {
336 ASSERT(process->MirrorMemory(*map_address + nro_size, bss_addr, bss_size,
337 Kernel::MemoryState::ModuleCodeStatic) == RESULT_SUCCESS);
338 ASSERT(process->UnmapMemory(bss_addr, 0, bss_size) == RESULT_SUCCESS);
339 }
340
341 vm_manager.ReprotectRange(*map_address, header.text_size,
342 Kernel::VMAPermission::ReadExecute);
343 vm_manager.ReprotectRange(*map_address + header.ro_offset, header.ro_size,
344 Kernel::VMAPermission::Read);
345 vm_manager.ReprotectRange(*map_address + header.rw_offset, header.rw_size,
346 Kernel::VMAPermission::ReadWrite);
98 347
99 // TODO(bunnei): This is an incomplete implementation. It was tested with Super Mario Party. 348 Core::System::GetInstance().ArmInterface(0).ClearInstructionCache();
100 // It is currently missing: 349 Core::System::GetInstance().ArmInterface(1).ClearInstructionCache();
101 // - Signature checks with LoadNRR 350 Core::System::GetInstance().ArmInterface(2).ClearInstructionCache();
102 // - Checking if a module has already been loaded 351 Core::System::GetInstance().ArmInterface(3).ClearInstructionCache();
103 // - Using/validating BSS, etc. params (these are used from NRO header instead) 352
104 // - Error checking 353 nro.insert_or_assign(*map_address, NROInfo{hash, nro_size + bss_size});
105 // - ...Probably other things
106 354
107 IPC::ResponseBuilder rb{ctx, 4}; 355 IPC::ResponseBuilder rb{ctx, 4};
108 rb.Push(RESULT_SUCCESS); 356 rb.Push(RESULT_SUCCESS);
109 rb.Push(addr); 357 rb.Push(*map_address);
110 LOG_WARNING(Service_LDR, "(STUBBED) called"); 358 }
359
360 void UnloadNro(Kernel::HLERequestContext& ctx) {
361 IPC::RequestParser rp{ctx};
362 rp.Skip(2, false);
363 const VAddr mapped_addr{rp.PopRaw<VAddr>()};
364 const VAddr heap_addr{rp.PopRaw<VAddr>()};
365
366 if (!initialized) {
367 LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
368 IPC::ResponseBuilder rb{ctx, 2};
369 rb.Push(ERROR_NOT_INITIALIZED);
370 return;
371 }
372
373 if (!Common::Is4KBAligned(mapped_addr) || !Common::Is4KBAligned(heap_addr)) {
374 LOG_ERROR(Service_LDR,
375 "NRO/BSS Address has invalid alignment (actual nro_addr={:016X}, "
376 "bss_addr={:016X})!",
377 mapped_addr, heap_addr);
378 IPC::ResponseBuilder rb{ctx, 2};
379 rb.Push(ERROR_INVALID_ALIGNMENT);
380 return;
381 }
382
383 const auto iter = nro.find(mapped_addr);
384 if (iter == nro.end()) {
385 LOG_ERROR(Service_LDR,
386 "The NRO attempting to unmap was not mapped or has an invalid address "
387 "(actual {:016X})!",
388 mapped_addr);
389 IPC::ResponseBuilder rb{ctx, 2};
390 rb.Push(ERROR_INVALID_NRO_ADDRESS);
391 return;
392 }
393
394 auto* process = Core::CurrentProcess();
395 auto& vm_manager = process->VMManager();
396 const auto& nro_size = iter->second.size;
397
398 ASSERT(process->MirrorMemory(heap_addr, mapped_addr, nro_size,
399 Kernel::MemoryState::ModuleCodeStatic) == RESULT_SUCCESS);
400 ASSERT(process->UnmapMemory(mapped_addr, 0, nro_size) == RESULT_SUCCESS);
401
402 Core::System::GetInstance().ArmInterface(0).ClearInstructionCache();
403 Core::System::GetInstance().ArmInterface(1).ClearInstructionCache();
404 Core::System::GetInstance().ArmInterface(2).ClearInstructionCache();
405 Core::System::GetInstance().ArmInterface(3).ClearInstructionCache();
406
407 nro.erase(iter);
408 IPC::ResponseBuilder rb{ctx, 2};
409 rb.Push(RESULT_SUCCESS);
111 } 410 }
112 411
113 void Initialize(Kernel::HLERequestContext& ctx) { 412 void Initialize(Kernel::HLERequestContext& ctx) {
413 initialized = true;
414
114 IPC::ResponseBuilder rb{ctx, 2}; 415 IPC::ResponseBuilder rb{ctx, 2};
115 rb.Push(RESULT_SUCCESS); 416 rb.Push(RESULT_SUCCESS);
116 LOG_WARNING(Service_LDR, "(STUBBED) called"); 417 LOG_WARNING(Service_LDR, "(STUBBED) called");
117 } 418 }
419
420private:
421 using SHA256Hash = std::array<u8, 0x20>;
422
423 struct NROHeader {
424 u32_le entrypoint_insn;
425 u32_le mod_offset;
426 INSERT_PADDING_WORDS(2);
427 u32_le magic;
428 INSERT_PADDING_WORDS(1);
429 u32_le nro_size;
430 INSERT_PADDING_WORDS(1);
431 u32_le text_offset;
432 u32_le text_size;
433 u32_le ro_offset;
434 u32_le ro_size;
435 u32_le rw_offset;
436 u32_le rw_size;
437 u32_le bss_size;
438 INSERT_PADDING_WORDS(1);
439 std::array<u8, 0x20> build_id;
440 INSERT_PADDING_BYTES(0x20);
441 };
442 static_assert(sizeof(NROHeader) == 0x80, "NROHeader has invalid size.");
443
444 struct NRRHeader {
445 u32_le magic;
446 INSERT_PADDING_BYTES(0x1C);
447 u64_le title_id_mask;
448 u64_le title_id_pattern;
449 std::array<u8, 0x100> modulus;
450 std::array<u8, 0x100> signature_1;
451 std::array<u8, 0x100> signature_2;
452 u64_le title_id;
453 u32_le size;
454 INSERT_PADDING_BYTES(4);
455 u32_le hash_offset;
456 u32_le hash_count;
457 INSERT_PADDING_BYTES(8);
458 };
459 static_assert(sizeof(NRRHeader) == 0x350, "NRRHeader has incorrect size.");
460
461 struct NROInfo {
462 SHA256Hash hash;
463 u64 size;
464 };
465
466 bool initialized = false;
467
468 std::map<VAddr, NROInfo> nro;
469 std::map<VAddr, std::vector<SHA256Hash>> nrr;
470
471 bool IsValidNROHash(const SHA256Hash& hash) {
472 return std::any_of(
473 nrr.begin(), nrr.end(), [&hash](const std::pair<VAddr, std::vector<SHA256Hash>>& p) {
474 return std::find(p.second.begin(), p.second.end(), hash) != p.second.end();
475 });
476 }
477
478 static bool IsValidNRO(const NROHeader& header, u64 nro_size, u64 bss_size) {
479 return header.magic == Common::MakeMagic('N', 'R', 'O', '0') &&
480 header.nro_size == nro_size && header.bss_size == bss_size &&
481 header.ro_offset == header.text_offset + header.text_size &&
482 header.rw_offset == header.ro_offset + header.ro_size &&
483 nro_size == header.rw_offset + header.rw_size &&
484 Common::Is4KBAligned(header.text_size) && Common::Is4KBAligned(header.ro_size) &&
485 Common::Is4KBAligned(header.rw_size);
486 }
118}; 487};
119 488
120void InstallInterfaces(SM::ServiceManager& sm) { 489void InstallInterfaces(SM::ServiceManager& sm) {
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
index 44accecb7..1066bf505 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -351,6 +351,14 @@ void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) {
351 font_sizes.push_back(region.size); 351 font_sizes.push_back(region.size);
352 } 352 }
353 353
354 // Resize buffers if game requests smaller size output.
355 font_codes.resize(
356 std::min<std::size_t>(font_codes.size(), ctx.GetWriteBufferSize(0) / sizeof(u32)));
357 font_offsets.resize(
358 std::min<std::size_t>(font_offsets.size(), ctx.GetWriteBufferSize(1) / sizeof(u32)));
359 font_sizes.resize(
360 std::min<std::size_t>(font_sizes.size(), ctx.GetWriteBufferSize(2) / sizeof(u32)));
361
354 ctx.WriteBuffer(font_codes, 0); 362 ctx.WriteBuffer(font_codes, 0);
355 ctx.WriteBuffer(font_offsets, 1); 363 ctx.WriteBuffer(font_offsets, 1);
356 ctx.WriteBuffer(font_sizes, 2); 364 ctx.WriteBuffer(font_sizes, 2);
diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp
index e3cbd7004..b3a196f65 100644
--- a/src/core/hle/service/time/interface.cpp
+++ b/src/core/hle/service/time/interface.cpp
@@ -23,7 +23,8 @@ Time::Time(std::shared_ptr<Module> time, const char* name)
23 {300, nullptr, "CalculateMonotonicSystemClockBaseTimePoint"}, 23 {300, nullptr, "CalculateMonotonicSystemClockBaseTimePoint"},
24 {400, &Time::GetClockSnapshot, "GetClockSnapshot"}, 24 {400, &Time::GetClockSnapshot, "GetClockSnapshot"},
25 {401, nullptr, "GetClockSnapshotFromSystemClockContext"}, 25 {401, nullptr, "GetClockSnapshotFromSystemClockContext"},
26 {500, nullptr, "CalculateStandardUserSystemClockDifferenceByUser"}, 26 {500, &Time::CalculateStandardUserSystemClockDifferenceByUser,
27 "CalculateStandardUserSystemClockDifferenceByUser"},
27 {501, nullptr, "CalculateSpanBetween"}, 28 {501, nullptr, "CalculateSpanBetween"},
28 }; 29 };
29 RegisterHandlers(functions); 30 RegisterHandlers(functions);
diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp
index 85e7b1195..e561a0c52 100644
--- a/src/core/hle/service/time/time.cpp
+++ b/src/core/hle/service/time/time.cpp
@@ -299,6 +299,21 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
299 ctx.WriteBuffer(&clock_snapshot, sizeof(ClockSnapshot)); 299 ctx.WriteBuffer(&clock_snapshot, sizeof(ClockSnapshot));
300} 300}
301 301
302void Module::Interface::CalculateStandardUserSystemClockDifferenceByUser(
303 Kernel::HLERequestContext& ctx) {
304 LOG_DEBUG(Service_Time, "called");
305
306 IPC::RequestParser rp{ctx};
307 const auto snapshot_a = rp.PopRaw<ClockSnapshot>();
308 const auto snapshot_b = rp.PopRaw<ClockSnapshot>();
309 const u64 difference =
310 snapshot_b.user_clock_context.offset - snapshot_a.user_clock_context.offset;
311
312 IPC::ResponseBuilder rb{ctx, 4};
313 rb.Push(RESULT_SUCCESS);
314 rb.PushRaw<u64>(difference);
315}
316
302Module::Interface::Interface(std::shared_ptr<Module> time, const char* name) 317Module::Interface::Interface(std::shared_ptr<Module> time, const char* name)
303 : ServiceFramework(name), time(std::move(time)) {} 318 : ServiceFramework(name), time(std::move(time)) {}
304 319
diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h
index 77871ae07..ea43fbea7 100644
--- a/src/core/hle/service/time/time.h
+++ b/src/core/hle/service/time/time.h
@@ -84,6 +84,7 @@ public:
84 void GetTimeZoneService(Kernel::HLERequestContext& ctx); 84 void GetTimeZoneService(Kernel::HLERequestContext& ctx);
85 void GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx); 85 void GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx);
86 void GetClockSnapshot(Kernel::HLERequestContext& ctx); 86 void GetClockSnapshot(Kernel::HLERequestContext& ctx);
87 void CalculateStandardUserSystemClockDifferenceByUser(Kernel::HLERequestContext& ctx);
87 88
88 protected: 89 protected:
89 std::shared_ptr<Module> time; 90 std::shared_ptr<Module> time;
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index d764b2406..d25fdb1fe 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -237,6 +237,22 @@ private:
237 Data data{}; 237 Data data{};
238}; 238};
239 239
240/// Represents a parcel containing one int '0' as its data
241/// Used by DetachBuffer and Disconnect
242class IGBPEmptyResponseParcel : public Parcel {
243protected:
244 void SerializeData() override {
245 Write(data);
246 }
247
248private:
249 struct Data {
250 u32_le unk_0;
251 };
252
253 Data data{};
254};
255
240class IGBPSetPreallocatedBufferRequestParcel : public Parcel { 256class IGBPSetPreallocatedBufferRequestParcel : public Parcel {
241public: 257public:
242 explicit IGBPSetPreallocatedBufferRequestParcel(std::vector<u8> buffer) 258 explicit IGBPSetPreallocatedBufferRequestParcel(std::vector<u8> buffer)
@@ -554,6 +570,12 @@ private:
554 ctx.WriteBuffer(response.Serialize()); 570 ctx.WriteBuffer(response.Serialize());
555 } else if (transaction == TransactionId::CancelBuffer) { 571 } else if (transaction == TransactionId::CancelBuffer) {
556 LOG_CRITICAL(Service_VI, "(STUBBED) called, transaction=CancelBuffer"); 572 LOG_CRITICAL(Service_VI, "(STUBBED) called, transaction=CancelBuffer");
573 } else if (transaction == TransactionId::Disconnect ||
574 transaction == TransactionId::DetachBuffer) {
575 const auto buffer = ctx.ReadBuffer();
576
577 IGBPEmptyResponseParcel response{};
578 ctx.WriteBuffer(response.Serialize());
557 } else { 579 } else {
558 ASSERT_MSG(false, "Unimplemented"); 580 ASSERT_MSG(false, "Unimplemented");
559 } 581 }
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index c8e491fec..fbbd6b0de 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -170,17 +170,20 @@ static constexpr u32 PageAlignSize(u32 size) {
170 arg_data.size()); 170 arg_data.size());
171 } 171 }
172 172
173 // Read MOD header
174 ModHeader mod_header{};
175 // Default .bss to NRO header bss size if MOD0 section doesn't exist 173 // Default .bss to NRO header bss size if MOD0 section doesn't exist
176 u32 bss_size{PageAlignSize(nro_header.bss_size)}; 174 u32 bss_size{PageAlignSize(nro_header.bss_size)};
175
176 // Read MOD header
177 ModHeader mod_header{};
177 std::memcpy(&mod_header, program_image.data() + nro_header.module_header_offset, 178 std::memcpy(&mod_header, program_image.data() + nro_header.module_header_offset,
178 sizeof(ModHeader)); 179 sizeof(ModHeader));
180
179 const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')}; 181 const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')};
180 if (has_mod_header) { 182 if (has_mod_header) {
181 // Resize program image to include .bss section and page align each section 183 // Resize program image to include .bss section and page align each section
182 bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset); 184 bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
183 } 185 }
186
184 codeset.DataSegment().size += bss_size; 187 codeset.DataSegment().size += bss_size;
185 program_image.resize(static_cast<u32>(program_image.size()) + bss_size); 188 program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
186 189
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 8c92ceb21..ae6aaee4c 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -107,8 +107,6 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo
107 107
108 ASSERT_MSG(has_ARB_separate_shader_objects, "has_ARB_separate_shader_objects is unsupported"); 108 ASSERT_MSG(has_ARB_separate_shader_objects, "has_ARB_separate_shader_objects is unsupported");
109 OpenGLState::ApplyDefaultState(); 109 OpenGLState::ApplyDefaultState();
110 // Clipping plane 0 is always enabled for PICA fixed clip plane z <= 0
111 state.clip_distance[0] = true;
112 110
113 // Create render framebuffer 111 // Create render framebuffer
114 framebuffer.Create(); 112 framebuffer.Create();
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index b44ecfa1c..9ca82c06c 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -381,11 +381,8 @@ void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 d
381 const u32 tile_size_y{GetDefaultBlockHeight(format)}; 381 const u32 tile_size_y{GetDefaultBlockHeight(format)};
382 382
383 if (morton_to_gl) { 383 if (morton_to_gl) {
384 const std::vector<u8> data = 384 Tegra::Texture::UnswizzleTexture(gl_buffer, addr, tile_size_x, tile_size_y, bytes_per_pixel,
385 Tegra::Texture::UnswizzleTexture(addr, tile_size_x, tile_size_y, bytes_per_pixel, 385 stride, height, depth, block_height, block_depth);
386 stride, height, depth, block_height, block_depth);
387 const std::size_t size_to_copy{std::min(gl_buffer_size, data.size())};
388 memcpy(gl_buffer, data.data(), size_to_copy);
389 } else { 386 } else {
390 Tegra::Texture::CopySwizzledData((stride + tile_size_x - 1) / tile_size_x, 387 Tegra::Texture::CopySwizzledData((stride + tile_size_x - 1) / tile_size_x,
391 (height + tile_size_y - 1) / tile_size_y, depth, 388 (height + tile_size_y - 1) / tile_size_y, depth,
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index 19f30b1b5..c9160b467 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -229,14 +229,21 @@ u32 BytesPerPixel(TextureFormat format) {
229 } 229 }
230} 230}
231 231
232void UnswizzleTexture(u8* const unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y,
233 u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height,
234 u32 block_depth) {
235 CopySwizzledData((width + tile_size_x - 1) / tile_size_x,
236 (height + tile_size_y - 1) / tile_size_y, depth, bytes_per_pixel,
237 bytes_per_pixel, Memory::GetPointer(address), unswizzled_data, true,
238 block_height, block_depth);
239}
240
232std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y, 241std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y,
233 u32 bytes_per_pixel, u32 width, u32 height, u32 depth, 242 u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
234 u32 block_height, u32 block_depth) { 243 u32 block_height, u32 block_depth) {
235 std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel); 244 std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel);
236 CopySwizzledData((width + tile_size_x - 1) / tile_size_x, 245 UnswizzleTexture(unswizzled_data.data(), address, tile_size_x, tile_size_y, bytes_per_pixel,
237 (height + tile_size_y - 1) / tile_size_y, depth, bytes_per_pixel, 246 width, height, depth, block_height, block_depth);
238 bytes_per_pixel, Memory::GetPointer(address), unswizzled_data.data(), true,
239 block_height, block_depth);
240 return unswizzled_data; 247 return unswizzled_data;
241} 248}
242 249
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
index ba065510b..f4ef7c73e 100644
--- a/src/video_core/textures/decoders.h
+++ b/src/video_core/textures/decoders.h
@@ -19,6 +19,13 @@ inline std::size_t GetGOBSize() {
19/** 19/**
20 * Unswizzles a swizzled texture without changing its format. 20 * Unswizzles a swizzled texture without changing its format.
21 */ 21 */
22void UnswizzleTexture(u8* unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y,
23 u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
24 u32 block_height = TICEntry::DefaultBlockHeight,
25 u32 block_depth = TICEntry::DefaultBlockHeight);
26/**
27 * Unswizzles a swizzled texture without changing its format.
28 */
22std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y, 29std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y,
23 u32 bytes_per_pixel, u32 width, u32 height, u32 depth, 30 u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
24 u32 block_height = TICEntry::DefaultBlockHeight, 31 u32 block_height = TICEntry::DefaultBlockHeight,
diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp
index 639d5df0f..ae8cac243 100644
--- a/src/yuzu/configuration/configure_gamelist.cpp
+++ b/src/yuzu/configuration/configure_gamelist.cpp
@@ -36,6 +36,16 @@ ConfigureGameList::ConfigureGameList(QWidget* parent)
36 InitializeRowComboBoxes(); 36 InitializeRowComboBoxes();
37 37
38 this->setConfiguration(); 38 this->setConfiguration();
39
40 // Force game list reload if any of the relevant settings are changed.
41 connect(ui->show_unknown, &QCheckBox::stateChanged, this,
42 &ConfigureGameList::RequestGameListUpdate);
43 connect(ui->icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
44 &ConfigureGameList::RequestGameListUpdate);
45 connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
46 &ConfigureGameList::RequestGameListUpdate);
47 connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
48 &ConfigureGameList::RequestGameListUpdate);
39} 49}
40 50
41ConfigureGameList::~ConfigureGameList() = default; 51ConfigureGameList::~ConfigureGameList() = default;
@@ -49,6 +59,10 @@ void ConfigureGameList::applyConfiguration() {
49 Settings::Apply(); 59 Settings::Apply();
50} 60}
51 61
62void ConfigureGameList::RequestGameListUpdate() {
63 UISettings::values.is_game_list_reload_pending.exchange(true);
64}
65
52void ConfigureGameList::setConfiguration() { 66void ConfigureGameList::setConfiguration() {
53 ui->show_unknown->setChecked(UISettings::values.show_unknown); 67 ui->show_unknown->setChecked(UISettings::values.show_unknown);
54 ui->show_add_ons->setChecked(UISettings::values.show_add_ons); 68 ui->show_add_ons->setChecked(UISettings::values.show_add_ons);
diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_gamelist.h
index ff7406c60..bbf7e25f1 100644
--- a/src/yuzu/configuration/configure_gamelist.h
+++ b/src/yuzu/configuration/configure_gamelist.h
@@ -21,6 +21,8 @@ public:
21 void applyConfiguration(); 21 void applyConfiguration();
22 22
23private: 23private:
24 void RequestGameListUpdate();
25
24 void setConfiguration(); 26 void setConfiguration();
25 27
26 void changeEvent(QEvent*) override; 28 void changeEvent(QEvent*) override;
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 314f51203..c22742007 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -23,6 +23,9 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
23 23
24 this->setConfiguration(); 24 this->setConfiguration();
25 25
26 connect(ui->toggle_deepscan, &QCheckBox::stateChanged, this,
27 [] { UISettings::values.is_game_list_reload_pending.exchange(true); });
28
26 ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); 29 ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
27} 30}
28 31
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 999086e7f..4b969119c 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1341,7 +1341,13 @@ void GMainWindow::OnConfigure() {
1341 UpdateUITheme(); 1341 UpdateUITheme();
1342 if (UISettings::values.enable_discord_presence != old_discord_presence) 1342 if (UISettings::values.enable_discord_presence != old_discord_presence)
1343 SetDiscordEnabled(UISettings::values.enable_discord_presence); 1343 SetDiscordEnabled(UISettings::values.enable_discord_presence);
1344 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 1344
1345 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
1346 if (reload) {
1347 game_list->PopulateAsync(UISettings::values.gamedir,
1348 UISettings::values.gamedir_deepscan);
1349 }
1350
1345 config->Save(); 1351 config->Save();
1346 } 1352 }
1347} 1353}
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index 32a0d813c..e80aebc0a 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -5,6 +5,7 @@
5#pragma once 5#pragma once
6 6
7#include <array> 7#include <array>
8#include <atomic>
8#include <vector> 9#include <vector>
9#include <QByteArray> 10#include <QByteArray>
10#include <QString> 11#include <QString>
@@ -63,6 +64,7 @@ struct Values {
63 uint32_t icon_size; 64 uint32_t icon_size;
64 uint8_t row_1_text_id; 65 uint8_t row_1_text_id;
65 uint8_t row_2_text_id; 66 uint8_t row_2_text_id;
67 std::atomic_bool is_game_list_reload_pending{false};
66}; 68};
67 69
68extern Values values; 70extern Values values;
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 9cc409fd5..96f1ef636 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -139,6 +139,8 @@ void Config::ReadValues() {
139 Settings::values.rng_seed = std::nullopt; 139 Settings::values.rng_seed = std::nullopt;
140 } 140 }
141 141
142 Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1);
143
142 // Miscellaneous 144 // Miscellaneous
143 Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); 145 Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");
144 Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false); 146 Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false);