Compare commits

...

17 Commits

Author SHA1 Message Date
f3b4384f3d Android #21 2023-07-27 00:58:45 +00:00
a1c355051e Merge pull request #11128 from german77/discord
yuzu: Replace httplib with QtNetworkRequest
2023-07-26 11:04:57 -04:00
d3da1e6517 Merge pull request #10990 from comex/ubsan
Fixes and workarounds to make UBSan happier on macOS
2023-07-26 10:33:28 -04:00
6ffcc3d5d2 Merge pull request #11142 from german77/avoid_crash
yuzu: Avoid reading broken games
2023-07-26 10:33:14 -04:00
01b379fe20 Address feedback 2023-07-26 00:41:24 -04:00
4742627744 Merge pull request #11157 from lat9nq/nasm
cmake: Download nasm from our external repo
2023-07-25 13:35:02 -04:00
43920aa1a0 cmake: Download nasm from our external repo
This package download has intermittent failures due to host Internet
issues (presumably), so download it ourselves from our own hosting.
2023-07-25 15:47:44 -04:00
18000df5e9 Merge pull request #11095 from liamwhite/memory2
memory: cleanup
2023-07-24 13:47:11 -04:00
bb0edb53ae Merge pull request #11135 from liamwhite/getaddrinfo
core: implement GetGaiStringErrorRequest, IContextRegistrar
2023-07-24 13:47:02 -04:00
099295d7c6 Merge pull request #11136 from liamwhite/sp3shader
ssa_rewrite_pass: use proper maps
2023-07-24 13:46:53 -04:00
a0ed0e4e10 yuzu: Avoid reading broken games 2023-07-24 08:59:14 -06:00
dad0d076ee ssa_rewrite_pass: use proper maps 2023-07-23 11:38:01 -04:00
3e3294e1c2 core: implement GetGaiStringErrorRequest, IContextRegistrar 2023-07-22 23:29:45 -04:00
9f3f615e05 core: reduce TOCTTOU memory access 2023-07-22 11:19:29 -04:00
d144168442 memory: minimize dependency on process 2023-07-22 11:19:29 -04:00
c067046387 yuzu: Replace httplib with QtNetworkRequest 2023-07-21 16:53:06 -06:00
d7c532d889 Fixes and workarounds to make UBSan happier on macOS
There are still some other issues not addressed here, but it's a start.

Workarounds for false-positive reports:

- `RasterizerAccelerated`: Put a gigantic array behind a `unique_ptr`,
  because UBSan has a [hardcoded limit](https://stackoverflow.com/questions/64531383/c-runtime-error-using-fsanitize-undefined-object-has-a-possibly-invalid-vp)
  of how big it thinks objects can be, specifically when dealing with
  offset-to-top values used with multiple inheritance.  Hopefully this
  doesn't have a performance impact.

- `QueryCacheBase::QueryCacheBase`: Avoid an operation that UBSan thinks
  is UB even though it at least arguably isn't.  See the link in the
  comment for more information.

Fixes for correct reports:

- `PageTable`, `Memory`: Use `uintptr_t` values instead of pointers to
  avoid UB from pointer overflow (when pointer arithmetic wraps around
  the address space).

- `KScheduler::Reload`: `thread->GetOwnerProcess()` can be `nullptr`;
  avoid calling methods on it in this case.  (The existing code returns
  a garbage reference to a field, which is then passed into
  `LoadWatchpointArray`, and apparently it's never used, so it's
  harmless in practice but still triggers UBSan.)

- `KAutoObject::Close`: This function calls `this->Destroy()`, which
  overwrites the beginning of the object with junk (specifically a free
  list pointer).  Then it calls `this->UnregisterWithKernel()`.  UBSan
  complains about a type mismatch because the vtable has been
  overwritten, and I believe this is indeed UB.  `UnregisterWithKernel`
  also loads `m_kernel` from the 'freed' object, which seems to be
  technically safe (the overwriting doesn't extend as far as that
  field), but seems dubious.  Switch to a `static` method and load
  `m_kernel` in advance.
2023-07-15 12:00:28 -07:00
31 changed files with 318 additions and 165 deletions

View File

@ -136,6 +136,22 @@ if (YUZU_USE_BUNDLED_VCPKG)
endif()
endif()
if (MSVC)
set(VCPKG_DOWNLOADS_PATH ${PROJECT_SOURCE_DIR}/externals/vcpkg/downloads)
set(NASM_VERSION "2.16.01")
set(NASM_DESTINATION_PATH ${VCPKG_DOWNLOADS_PATH}/nasm-${NASM_VERSION}-win64.zip)
set(NASM_DOWNLOAD_URL "https://github.com/yuzu-emu/ext-windows-bin/raw/master/nasm/nasm-${NASM_VERSION}-win64.zip")
if (NOT EXISTS ${NASM_DESTINATION_PATH})
file(DOWNLOAD ${NASM_DOWNLOAD_URL} ${NASM_DESTINATION_PATH} SHOW_PROGRESS STATUS NASM_STATUS)
if (NOT NASM_STATUS EQUAL 0)
# Warn and not fail since vcpkg is supposed to download this package for us in the first place
message(WARNING "External nasm vcpkg package download from ${NASM_DOWNLOAD_URL} failed with status ${NASM_STATUS}")
endif()
endif()
endif()
if (YUZU_TESTS)
list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests")
endif()

View File

@ -1,3 +1,11 @@
| Pull Request | Commit | Title | Author | Merged? |
|----|----|----|----|----|
End of merge log. You can find the original README.md below the break.
-----
<!--
SPDX-FileCopyrightText: 2018 yuzu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later

View File

@ -51,7 +51,7 @@ struct PageTable {
class PageInfo {
public:
/// Returns the page pointer
[[nodiscard]] u8* Pointer() const noexcept {
[[nodiscard]] uintptr_t Pointer() const noexcept {
return ExtractPointer(raw.load(std::memory_order_relaxed));
}
@ -61,7 +61,7 @@ struct PageTable {
}
/// Returns the page pointer and attribute pair, extracted from the same atomic read
[[nodiscard]] std::pair<u8*, PageType> PointerType() const noexcept {
[[nodiscard]] std::pair<uintptr_t, PageType> PointerType() const noexcept {
const uintptr_t non_atomic_raw = raw.load(std::memory_order_relaxed);
return {ExtractPointer(non_atomic_raw), ExtractType(non_atomic_raw)};
}
@ -73,13 +73,13 @@ struct PageTable {
}
/// Write a page pointer and type pair atomically
void Store(u8* pointer, PageType type) noexcept {
raw.store(reinterpret_cast<uintptr_t>(pointer) | static_cast<uintptr_t>(type));
void Store(uintptr_t pointer, PageType type) noexcept {
raw.store(pointer | static_cast<uintptr_t>(type));
}
/// Unpack a pointer from a page info raw representation
[[nodiscard]] static u8* ExtractPointer(uintptr_t raw) noexcept {
return reinterpret_cast<u8*>(raw & (~uintptr_t{0} << ATTRIBUTE_BITS));
[[nodiscard]] static uintptr_t ExtractPointer(uintptr_t raw) noexcept {
return raw & (~uintptr_t{0} << ATTRIBUTE_BITS);
}
/// Unpack a page type from a page info raw representation

View File

@ -217,8 +217,8 @@ void ARM_Interface::Run() {
}
}
void ARM_Interface::LoadWatchpointArray(const WatchpointArray& wp) {
watchpoints = &wp;
void ARM_Interface::LoadWatchpointArray(const WatchpointArray* wp) {
watchpoints = wp;
}
const Kernel::DebugWatchpoint* ARM_Interface::MatchingWatchpoint(

View File

@ -186,7 +186,7 @@ public:
virtual void SaveContext(ThreadContext64& ctx) const = 0;
virtual void LoadContext(const ThreadContext32& ctx) = 0;
virtual void LoadContext(const ThreadContext64& ctx) = 0;
void LoadWatchpointArray(const WatchpointArray& wp);
void LoadWatchpointArray(const WatchpointArray* wp);
/// Clears the exclusive monitor's state.
virtual void ClearExclusiveState() = 0;

View File

@ -261,10 +261,8 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))};
if (system.ApplicationMemory().IsValidVirtualAddressRange(addr, size)) {
std::vector<u8> mem(size);
system.ApplicationMemory().ReadBlock(addr, mem.data(), size);
std::vector<u8> mem(size);
if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) {
SendReply(Common::HexToString(mem));
} else {
SendReply(GDB_STUB_REPLY_ERR);
@ -281,8 +279,7 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
const auto mem_substr{std::string_view(command).substr(mem_sep)};
const auto mem{Common::HexStringToVector(mem_substr, false)};
if (system.ApplicationMemory().IsValidVirtualAddressRange(addr, size)) {
system.ApplicationMemory().WriteBlock(addr, mem.data(), size);
if (system.ApplicationMemory().WriteBlock(addr, mem.data(), size)) {
system.InvalidateCpuInstructionCacheRange(addr, size);
SendReply(GDB_STUB_REPLY_OK);
} else {

View File

@ -15,8 +15,8 @@ void KAutoObject::RegisterWithKernel() {
m_kernel.RegisterKernelObject(this);
}
void KAutoObject::UnregisterWithKernel() {
m_kernel.UnregisterKernelObject(this);
void KAutoObject::UnregisterWithKernel(KernelCore& kernel, KAutoObject* self) {
kernel.UnregisterKernelObject(self);
}
} // namespace Kernel

View File

@ -159,14 +159,15 @@ public:
// If ref count hits zero, destroy the object.
if (cur_ref_count - 1 == 0) {
KernelCore& kernel = m_kernel;
this->Destroy();
this->UnregisterWithKernel();
KAutoObject::UnregisterWithKernel(kernel, this);
}
}
private:
void RegisterWithKernel();
void UnregisterWithKernel();
static void UnregisterWithKernel(KernelCore& kernel, KAutoObject* self);
protected:
KernelCore& m_kernel;

View File

@ -510,11 +510,12 @@ void KScheduler::Unload(KThread* thread) {
void KScheduler::Reload(KThread* thread) {
auto& cpu_core = m_kernel.System().ArmInterface(m_core_id);
auto* process = thread->GetOwnerProcess();
cpu_core.LoadContext(thread->GetContext32());
cpu_core.LoadContext(thread->GetContext64());
cpu_core.SetTlsAddress(GetInteger(thread->GetTlsAddress()));
cpu_core.SetTPIDR_EL0(thread->GetTpidrEl0());
cpu_core.LoadWatchpointArray(thread->GetOwnerProcess()->GetWatchpoints());
cpu_core.LoadWatchpointArray(process ? &process->GetWatchpoints() : nullptr);
cpu_core.ClearExclusiveState();
}

View File

@ -129,7 +129,7 @@ Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress
case ThreadType::User:
ASSERT(((owner == nullptr) ||
(owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask()));
ASSERT(((owner == nullptr) ||
ASSERT(((owner == nullptr) || (prio > Svc::LowestThreadPriority) ||
(owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask()));
break;
case ThreadType::Kernel:

View File

@ -8,6 +8,7 @@
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/svc.h"
#include "core/hle/kernel/svc_results.h"
namespace Kernel::Svc {
@ -49,14 +50,10 @@ Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles_ad
// Copy user handles.
if (num_handles > 0) {
// Ensure we can try to get the handles.
R_UNLESS(GetCurrentMemory(kernel).IsValidVirtualAddressRange(
handles_addr, static_cast<u64>(sizeof(Handle) * num_handles)),
ResultInvalidPointer);
// Get the handles.
GetCurrentMemory(kernel).ReadBlock(handles_addr, handles.data(),
sizeof(Handle) * num_handles);
R_UNLESS(GetCurrentMemory(kernel).ReadBlock(handles_addr, handles.data(),
sizeof(Handle) * num_handles),
ResultInvalidPointer);
// Convert the handles to objects.
R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(

View File

@ -7,6 +7,7 @@
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/svc.h"
#include "core/hle/kernel/svc_results.h"
namespace Kernel::Svc {
@ -64,14 +65,10 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha
// Copy user handles.
if (num_handles > 0) {
// Ensure we can try to get the handles.
R_UNLESS(GetCurrentMemory(kernel).IsValidVirtualAddressRange(
user_handles, static_cast<u64>(sizeof(Handle) * num_handles)),
ResultInvalidPointer);
// Get the handles.
GetCurrentMemory(kernel).ReadBlock(user_handles, handles.data(),
sizeof(Handle) * num_handles);
R_UNLESS(GetCurrentMemory(kernel).ReadBlock(user_handles, handles.data(),
sizeof(Handle) * num_handles),
ResultInvalidPointer);
// Convert the handles to objects.
R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(

View File

@ -496,8 +496,9 @@ public:
void LoadIdTokenCache(HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(0);
}
protected:

View File

@ -2,13 +2,48 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/glue/ectx.h"
#include "core/hle/service/ipc_helpers.h"
namespace Service::Glue {
// This is nn::err::context::IContextRegistrar
class IContextRegistrar : public ServiceFramework<IContextRegistrar> {
public:
IContextRegistrar(Core::System& system_) : ServiceFramework{system_, "IContextRegistrar"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IContextRegistrar::Complete, "Complete"},
};
// clang-format on
RegisterHandlers(functions);
}
~IContextRegistrar() override = default;
private:
void Complete(HLERequestContext& ctx) {
struct InputParameters {
u32 unk;
};
struct OutputParameters {
u32 unk;
};
IPC::RequestParser rp{ctx};
[[maybe_unused]] auto input = rp.PopRaw<InputParameters>();
[[maybe_unused]] auto value = ctx.ReadBuffer();
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(0);
}
};
ECTX_AW::ECTX_AW(Core::System& system_) : ServiceFramework{system_, "ectx:aw"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CreateContextRegistrar"},
{0, &ECTX_AW::CreateContextRegistrar, "CreateContextRegistrar"},
{1, nullptr, "CommitContext"},
};
// clang-format on
@ -18,4 +53,10 @@ ECTX_AW::ECTX_AW(Core::System& system_) : ServiceFramework{system_, "ectx:aw"} {
ECTX_AW::~ECTX_AW() = default;
void ECTX_AW::CreateContextRegistrar(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IContextRegistrar>(std::make_shared<IContextRegistrar>(system));
}
} // namespace Service::Glue

View File

@ -15,6 +15,9 @@ class ECTX_AW final : public ServiceFramework<ECTX_AW> {
public:
explicit ECTX_AW(Core::System& system_);
~ECTX_AW() override;
private:
void CreateContextRegistrar(HLERequestContext& ctx);
};
} // namespace Service::Glue

View File

@ -559,7 +559,7 @@ std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::span<con
const std::optional<FileDescriptor>& descriptor = file_descriptors[pollfd.fd];
if (!descriptor) {
LOG_ERROR(Service, "File descriptor handle={} is not allocated", pollfd.fd);
LOG_TRACE(Service, "File descriptor handle={} is not allocated", pollfd.fd);
pollfd.revents = PollEvents::Nval;
return {0, Errno::SUCCESS};
}

View File

@ -24,7 +24,7 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na
static const FunctionInfo functions[] = {
{5, nullptr, "GetSettingUrl"},
{10, nullptr, "GetSettingName"},
{11, nullptr, "GetEnvironmentIdentifier"},
{11, &NSD::GetEnvironmentIdentifier, "GetEnvironmentIdentifier"},
{12, nullptr, "GetDeviceId"},
{13, nullptr, "DeleteSettings"},
{14, nullptr, "ImportSettings"},
@ -103,6 +103,14 @@ void NSD::ResolveEx(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) {
const std::string environment_identifier = "lp1";
ctx.WriteBuffer(environment_identifier);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void NSD::GetApplicationServerEnvironmentType(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);

View File

@ -19,6 +19,7 @@ public:
private:
void Resolve(HLERequestContext& ctx);
void ResolveEx(HLERequestContext& ctx);
void GetEnvironmentIdentifier(HLERequestContext& ctx);
void GetApplicationServerEnvironmentType(HLERequestContext& ctx);
};

View File

@ -24,7 +24,7 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"
{2, &SFDNSRES::GetHostByNameRequest, "GetHostByNameRequest"},
{3, nullptr, "GetHostByAddrRequest"},
{4, nullptr, "GetHostStringErrorRequest"},
{5, nullptr, "GetGaiStringErrorRequest"},
{5, &SFDNSRES::GetGaiStringErrorRequest, "GetGaiStringErrorRequest"},
{6, &SFDNSRES::GetAddrInfoRequest, "GetAddrInfoRequest"},
{7, nullptr, "GetNameInfoRequest"},
{8, nullptr, "RequestCancelHandleRequest"},
@ -300,6 +300,20 @@ void SFDNSRES::GetAddrInfoRequest(HLERequestContext& ctx) {
});
}
void SFDNSRES::GetGaiStringErrorRequest(HLERequestContext& ctx) {
struct InputParameters {
GetAddrInfoError gai_errno;
};
IPC::RequestParser rp{ctx};
auto input = rp.PopRaw<InputParameters>();
const std::string result = Translate(input.gai_errno);
ctx.WriteBuffer(result);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void SFDNSRES::GetAddrInfoRequestWithOptions(HLERequestContext& ctx) {
// Additional options are ignored
auto [data_size, emu_gai_err] = GetAddrInfoRequestImpl(ctx);

View File

@ -18,6 +18,7 @@ public:
private:
void GetHostByNameRequest(HLERequestContext& ctx);
void GetGaiStringErrorRequest(HLERequestContext& ctx);
void GetHostByNameRequestWithOptions(HLERequestContext& ctx);
void GetAddrInfoRequest(HLERequestContext& ctx);
void GetAddrInfoRequestWithOptions(HLERequestContext& ctx);

View File

@ -81,6 +81,44 @@ GetAddrInfoError Translate(Network::GetAddrInfoError error) {
}
}
const char* Translate(GetAddrInfoError error) {
// https://android.googlesource.com/platform/bionic/+/085543106/libc/dns/net/getaddrinfo.c#254
switch (error) {
case GetAddrInfoError::SUCCESS:
return "Success";
case GetAddrInfoError::ADDRFAMILY:
return "Address family for hostname not supported";
case GetAddrInfoError::AGAIN:
return "Temporary failure in name resolution";
case GetAddrInfoError::BADFLAGS:
return "Invalid value for ai_flags";
case GetAddrInfoError::FAIL:
return "Non-recoverable failure in name resolution";
case GetAddrInfoError::FAMILY:
return "ai_family not supported";
case GetAddrInfoError::MEMORY:
return "Memory allocation failure";
case GetAddrInfoError::NODATA:
return "No address associated with hostname";
case GetAddrInfoError::NONAME:
return "hostname nor servname provided, or not known";
case GetAddrInfoError::SERVICE:
return "servname not supported for ai_socktype";
case GetAddrInfoError::SOCKTYPE:
return "ai_socktype not supported";
case GetAddrInfoError::SYSTEM:
return "System error returned in errno";
case GetAddrInfoError::BADHINTS:
return "Invalid value for hints";
case GetAddrInfoError::PROTOCOL:
return "Resolved protocol is unknown";
case GetAddrInfoError::OVERFLOW_:
return "Argument buffer overflow";
default:
return "Unknown error";
}
}
Network::Domain Translate(Domain domain) {
switch (domain) {
case Domain::Unspecified:

View File

@ -20,6 +20,9 @@ std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value);
/// Translate abstract getaddrinfo error to guest getaddrinfo error
GetAddrInfoError Translate(Network::GetAddrInfoError value);
/// Translate guest error to string
const char* Translate(GetAddrInfoError value);
/// Translate guest domain to abstract domain
Network::Domain Translate(Domain domain);

View File

@ -73,7 +73,7 @@ struct Memory::Impl {
return {};
}
return system.DeviceMemory().GetPointer<u8>(paddr) + vaddr;
return system.DeviceMemory().GetPointer<u8>(paddr + vaddr);
}
[[nodiscard]] u8* GetPointerFromDebugMemory(u64 vaddr) const {
@ -84,7 +84,7 @@ struct Memory::Impl {
return {};
}
return system.DeviceMemory().GetPointer<u8>(paddr) + vaddr;
return system.DeviceMemory().GetPointer<u8>(paddr + vaddr);
}
u8 Read8(const Common::ProcessAddress addr) {
@ -183,13 +183,13 @@ struct Memory::Impl {
return string;
}
void WalkBlock(const Kernel::KProcess& process, const Common::ProcessAddress addr,
const std::size_t size, auto on_unmapped, auto on_memory, auto on_rasterizer,
auto increment) {
const auto& page_table = process.GetPageTable().PageTableImpl();
bool WalkBlock(const Common::ProcessAddress addr, const std::size_t size, auto on_unmapped,
auto on_memory, auto on_rasterizer, auto increment) {
const auto& page_table = system.ApplicationProcess()->GetPageTable().PageTableImpl();
std::size_t remaining_size = size;
std::size_t page_index = addr >> YUZU_PAGEBITS;
std::size_t page_offset = addr & YUZU_PAGEMASK;
bool user_accessible = true;
while (remaining_size) {
const std::size_t copy_amount =
@ -200,11 +200,13 @@ struct Memory::Impl {
const auto [pointer, type] = page_table.pointers[page_index].PointerType();
switch (type) {
case Common::PageType::Unmapped: {
user_accessible = false;
on_unmapped(copy_amount, current_vaddr);
break;
}
case Common::PageType::Memory: {
u8* mem_ptr = pointer + page_offset + (page_index << YUZU_PAGEBITS);
u8* mem_ptr =
reinterpret_cast<u8*>(pointer + page_offset + (page_index << YUZU_PAGEBITS));
on_memory(copy_amount, mem_ptr);
break;
}
@ -227,13 +229,15 @@ struct Memory::Impl {
increment(copy_amount);
remaining_size -= copy_amount;
}
return user_accessible;
}
template <bool UNSAFE>
void ReadBlockImpl(const Kernel::KProcess& process, const Common::ProcessAddress src_addr,
void* dest_buffer, const std::size_t size) {
WalkBlock(
process, src_addr, size,
bool ReadBlockImpl(const Common::ProcessAddress src_addr, void* dest_buffer,
const std::size_t size) {
return WalkBlock(
src_addr, size,
[src_addr, size, &dest_buffer](const std::size_t copy_amount,
const Common::ProcessAddress current_vaddr) {
LOG_ERROR(HW_Memory,
@ -256,14 +260,14 @@ struct Memory::Impl {
});
}
void ReadBlock(const Common::ProcessAddress src_addr, void* dest_buffer,
bool ReadBlock(const Common::ProcessAddress src_addr, void* dest_buffer,
const std::size_t size) {
ReadBlockImpl<false>(*system.ApplicationProcess(), src_addr, dest_buffer, size);
return ReadBlockImpl<false>(src_addr, dest_buffer, size);
}
void ReadBlockUnsafe(const Common::ProcessAddress src_addr, void* dest_buffer,
bool ReadBlockUnsafe(const Common::ProcessAddress src_addr, void* dest_buffer,
const std::size_t size) {
ReadBlockImpl<true>(*system.ApplicationProcess(), src_addr, dest_buffer, size);
return ReadBlockImpl<true>(src_addr, dest_buffer, size);
}
const u8* GetSpan(const VAddr src_addr, const std::size_t size) const {
@ -283,10 +287,10 @@ struct Memory::Impl {
}
template <bool UNSAFE>
void WriteBlockImpl(const Kernel::KProcess& process, const Common::ProcessAddress dest_addr,
const void* src_buffer, const std::size_t size) {
WalkBlock(
process, dest_addr, size,
bool WriteBlockImpl(const Common::ProcessAddress dest_addr, const void* src_buffer,
const std::size_t size) {
return WalkBlock(
dest_addr, size,
[dest_addr, size](const std::size_t copy_amount,
const Common::ProcessAddress current_vaddr) {
LOG_ERROR(HW_Memory,
@ -308,20 +312,19 @@ struct Memory::Impl {
});
}
void WriteBlock(const Common::ProcessAddress dest_addr, const void* src_buffer,
bool WriteBlock(const Common::ProcessAddress dest_addr, const void* src_buffer,
const std::size_t size) {
WriteBlockImpl<false>(*system.ApplicationProcess(), dest_addr, src_buffer, size);
return WriteBlockImpl<false>(dest_addr, src_buffer, size);
}
void WriteBlockUnsafe(const Common::ProcessAddress dest_addr, const void* src_buffer,
bool WriteBlockUnsafe(const Common::ProcessAddress dest_addr, const void* src_buffer,
const std::size_t size) {
WriteBlockImpl<true>(*system.ApplicationProcess(), dest_addr, src_buffer, size);
return WriteBlockImpl<true>(dest_addr, src_buffer, size);
}
void ZeroBlock(const Kernel::KProcess& process, const Common::ProcessAddress dest_addr,
const std::size_t size) {
WalkBlock(
process, dest_addr, size,
bool ZeroBlock(const Common::ProcessAddress dest_addr, const std::size_t size) {
return WalkBlock(
dest_addr, size,
[dest_addr, size](const std::size_t copy_amount,
const Common::ProcessAddress current_vaddr) {
LOG_ERROR(HW_Memory,
@ -339,23 +342,23 @@ struct Memory::Impl {
[](const std::size_t copy_amount) {});
}
void CopyBlock(const Kernel::KProcess& process, Common::ProcessAddress dest_addr,
Common::ProcessAddress src_addr, const std::size_t size) {
WalkBlock(
process, dest_addr, size,
bool CopyBlock(Common::ProcessAddress dest_addr, Common::ProcessAddress src_addr,
const std::size_t size) {
return WalkBlock(
dest_addr, size,
[&](const std::size_t copy_amount, const Common::ProcessAddress current_vaddr) {
LOG_ERROR(HW_Memory,
"Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
GetInteger(current_vaddr), GetInteger(src_addr), size);
ZeroBlock(process, dest_addr, copy_amount);
ZeroBlock(dest_addr, copy_amount);
},
[&](const std::size_t copy_amount, const u8* const src_ptr) {
WriteBlockImpl<false>(process, dest_addr, src_ptr, copy_amount);
WriteBlockImpl<false>(dest_addr, src_ptr, copy_amount);
},
[&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount,
u8* const host_ptr) {
HandleRasterizerDownload(GetInteger(current_vaddr), copy_amount);
WriteBlockImpl<false>(process, dest_addr, host_ptr, copy_amount);
WriteBlockImpl<false>(dest_addr, host_ptr, copy_amount);
},
[&](const std::size_t copy_amount) {
dest_addr += copy_amount;
@ -364,13 +367,13 @@ struct Memory::Impl {
}
template <typename Callback>
Result PerformCacheOperation(const Kernel::KProcess& process, Common::ProcessAddress dest_addr,
std::size_t size, Callback&& cb) {
Result PerformCacheOperation(Common::ProcessAddress dest_addr, std::size_t size,
Callback&& cb) {
class InvalidMemoryException : public std::exception {};
try {
WalkBlock(
process, dest_addr, size,
dest_addr, size,
[&](const std::size_t block_size, const Common::ProcessAddress current_vaddr) {
LOG_ERROR(HW_Memory, "Unmapped cache maintenance @ {:#018X}",
GetInteger(current_vaddr));
@ -387,37 +390,34 @@ struct Memory::Impl {
return ResultSuccess;
}
Result InvalidateDataCache(const Kernel::KProcess& process, Common::ProcessAddress dest_addr,
std::size_t size) {
Result InvalidateDataCache(Common::ProcessAddress dest_addr, std::size_t size) {
auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr,
const std::size_t block_size) {
// dc ivac: Invalidate to point of coherency
// GPU flush -> CPU invalidate
HandleRasterizerDownload(GetInteger(current_vaddr), block_size);
};
return PerformCacheOperation(process, dest_addr, size, on_rasterizer);
return PerformCacheOperation(dest_addr, size, on_rasterizer);
}
Result StoreDataCache(const Kernel::KProcess& process, Common::ProcessAddress dest_addr,
std::size_t size) {
Result StoreDataCache(Common::ProcessAddress dest_addr, std::size_t size) {
auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr,
const std::size_t block_size) {
// dc cvac: Store to point of coherency
// CPU flush -> GPU invalidate
system.GPU().InvalidateRegion(GetInteger(current_vaddr), block_size);
};
return PerformCacheOperation(process, dest_addr, size, on_rasterizer);
return PerformCacheOperation(dest_addr, size, on_rasterizer);
}
Result FlushDataCache(const Kernel::KProcess& process, Common::ProcessAddress dest_addr,
std::size_t size) {
Result FlushDataCache(Common::ProcessAddress dest_addr, std::size_t size) {
auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr,
const std::size_t block_size) {
// dc civac: Store to point of coherency, and invalidate from cache
// CPU flush -> GPU invalidate
system.GPU().InvalidateRegion(GetInteger(current_vaddr), block_size);
};
return PerformCacheOperation(process, dest_addr, size, on_rasterizer);
return PerformCacheOperation(dest_addr, size, on_rasterizer);
}
void MarkRegionDebug(u64 vaddr, u64 size, bool debug) {
@ -448,7 +448,7 @@ struct Memory::Impl {
break;
case Common::PageType::Memory:
current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
nullptr, Common::PageType::DebugMemory);
0, Common::PageType::DebugMemory);
break;
default:
UNREACHABLE();
@ -466,7 +466,8 @@ struct Memory::Impl {
case Common::PageType::DebugMemory: {
u8* const pointer{GetPointerFromDebugMemory(vaddr & ~YUZU_PAGEMASK)};
current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
pointer - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory);
reinterpret_cast<uintptr_t>(pointer) - (vaddr & ~YUZU_PAGEMASK),
Common::PageType::Memory);
break;
}
default:
@ -506,7 +507,7 @@ struct Memory::Impl {
case Common::PageType::DebugMemory:
case Common::PageType::Memory:
current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
nullptr, Common::PageType::RasterizerCachedMemory);
0, Common::PageType::RasterizerCachedMemory);
break;
case Common::PageType::RasterizerCachedMemory:
// There can be more than one GPU region mapped per CPU region, so it's common
@ -534,10 +535,11 @@ struct Memory::Impl {
// pagetable after unmapping a VMA. In that case the underlying VMA will no
// longer exist, and we should just leave the pagetable entry blank.
current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
nullptr, Common::PageType::Unmapped);
0, Common::PageType::Unmapped);
} else {
current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
pointer - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory);
reinterpret_cast<uintptr_t>(pointer) - (vaddr & ~YUZU_PAGEMASK),
Common::PageType::Memory);
}
break;
}
@ -584,7 +586,7 @@ struct Memory::Impl {
"Mapping memory page without a pointer @ {:016x}", base * YUZU_PAGESIZE);
while (base != end) {
page_table.pointers[base].Store(nullptr, type);
page_table.pointers[base].Store(0, type);
page_table.backing_addr[base] = 0;
page_table.blocks[base] = 0;
base += 1;
@ -593,7 +595,8 @@ struct Memory::Impl {
auto orig_base = base;
while (base != end) {
auto host_ptr =
system.DeviceMemory().GetPointer<u8>(target) - (base << YUZU_PAGEBITS);
reinterpret_cast<uintptr_t>(system.DeviceMemory().GetPointer<u8>(target)) -
(base << YUZU_PAGEBITS);
auto backing = GetInteger(target) - (base << YUZU_PAGEBITS);
page_table.pointers[base].Store(host_ptr, type);
page_table.backing_addr[base] = backing;
@ -619,8 +622,8 @@ struct Memory::Impl {
// Avoid adding any extra logic to this fast-path block
const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Raw();
if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
return &pointer[vaddr];
if (const uintptr_t pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
return reinterpret_cast<u8*>(pointer + vaddr);
}
switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) {
case Common::PageType::Unmapped:
@ -814,7 +817,7 @@ bool Memory::IsValidVirtualAddress(const Common::ProcessAddress vaddr) const {
return false;
}
const auto [pointer, type] = page_table.pointers[page].PointerType();
return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory ||
return pointer != 0 || type == Common::PageType::RasterizerCachedMemory ||
type == Common::PageType::DebugMemory;
}
@ -899,14 +902,14 @@ std::string Memory::ReadCString(Common::ProcessAddress vaddr, std::size_t max_le
return impl->ReadCString(vaddr, max_length);
}
void Memory::ReadBlock(const Common::ProcessAddress src_addr, void* dest_buffer,
bool Memory::ReadBlock(const Common::ProcessAddress src_addr, void* dest_buffer,
const std::size_t size) {
impl->ReadBlock(src_addr, dest_buffer, size);
return impl->ReadBlock(src_addr, dest_buffer, size);
}
void Memory::ReadBlockUnsafe(const Common::ProcessAddress src_addr, void* dest_buffer,
bool Memory::ReadBlockUnsafe(const Common::ProcessAddress src_addr, void* dest_buffer,
const std::size_t size) {
impl->ReadBlockUnsafe(src_addr, dest_buffer, size);
return impl->ReadBlockUnsafe(src_addr, dest_buffer, size);
}
const u8* Memory::GetSpan(const VAddr src_addr, const std::size_t size) const {
@ -917,23 +920,23 @@ u8* Memory::GetSpan(const VAddr src_addr, const std::size_t size) {
return impl->GetSpan(src_addr, size);
}
void Memory::WriteBlock(const Common::ProcessAddress dest_addr, const void* src_buffer,
bool Memory::WriteBlock(const Common::ProcessAddress dest_addr, const void* src_buffer,
const std::size_t size) {
impl->WriteBlock(dest_addr, src_buffer, size);
return impl->WriteBlock(dest_addr, src_buffer, size);
}
void Memory::WriteBlockUnsafe(const Common::ProcessAddress dest_addr, const void* src_buffer,
bool Memory::WriteBlockUnsafe(const Common::ProcessAddress dest_addr, const void* src_buffer,
const std::size_t size) {
impl->WriteBlockUnsafe(dest_addr, src_buffer, size);
return impl->WriteBlockUnsafe(dest_addr, src_buffer, size);
}
void Memory::CopyBlock(Common::ProcessAddress dest_addr, Common::ProcessAddress src_addr,
bool Memory::CopyBlock(Common::ProcessAddress dest_addr, Common::ProcessAddress src_addr,
const std::size_t size) {
impl->CopyBlock(*system.ApplicationProcess(), dest_addr, src_addr, size);
return impl->CopyBlock(dest_addr, src_addr, size);
}
void Memory::ZeroBlock(Common::ProcessAddress dest_addr, const std::size_t size) {
impl->ZeroBlock(*system.ApplicationProcess(), dest_addr, size);
bool Memory::ZeroBlock(Common::ProcessAddress dest_addr, const std::size_t size) {
return impl->ZeroBlock(dest_addr, size);
}
void Memory::SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers) {
@ -941,15 +944,15 @@ void Memory::SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers
}
Result Memory::InvalidateDataCache(Common::ProcessAddress dest_addr, const std::size_t size) {
return impl->InvalidateDataCache(*system.ApplicationProcess(), dest_addr, size);
return impl->InvalidateDataCache(dest_addr, size);
}
Result Memory::StoreDataCache(Common::ProcessAddress dest_addr, const std::size_t size) {
return impl->StoreDataCache(*system.ApplicationProcess(), dest_addr, size);
return impl->StoreDataCache(dest_addr, size);
}
Result Memory::FlushDataCache(Common::ProcessAddress dest_addr, const std::size_t size) {
return impl->FlushDataCache(*system.ApplicationProcess(), dest_addr, size);
return impl->FlushDataCache(dest_addr, size);
}
void Memory::RasterizerMarkRegionCached(Common::ProcessAddress vaddr, u64 size, bool cached) {

View File

@ -24,7 +24,6 @@ class GPUDirtyMemoryManager;
} // namespace Core
namespace Kernel {
class PhysicalMemory;
class KProcess;
} // namespace Kernel
@ -330,7 +329,7 @@ public:
* @post The range [dest_buffer, size) contains the read bytes from the
* current process' address space.
*/
void ReadBlock(Common::ProcessAddress src_addr, void* dest_buffer, std::size_t size);
bool ReadBlock(Common::ProcessAddress src_addr, void* dest_buffer, std::size_t size);
/**
* Reads a contiguous block of bytes from the current process' address space.
@ -349,7 +348,7 @@ public:
* @post The range [dest_buffer, size) contains the read bytes from the
* current process' address space.
*/
void ReadBlockUnsafe(Common::ProcessAddress src_addr, void* dest_buffer, std::size_t size);
bool ReadBlockUnsafe(Common::ProcessAddress src_addr, void* dest_buffer, std::size_t size);
const u8* GetSpan(const VAddr src_addr, const std::size_t size) const;
u8* GetSpan(const VAddr src_addr, const std::size_t size);
@ -373,7 +372,7 @@ public:
* and will mark that region as invalidated to caches that the active
* graphics backend may be maintaining over the course of execution.
*/
void WriteBlock(Common::ProcessAddress dest_addr, const void* src_buffer, std::size_t size);
bool WriteBlock(Common::ProcessAddress dest_addr, const void* src_buffer, std::size_t size);
/**
* Writes a range of bytes into the current process' address space at the specified
@ -391,7 +390,7 @@ public:
* will be ignored and an error will be logged.
*
*/
void WriteBlockUnsafe(Common::ProcessAddress dest_addr, const void* src_buffer,
bool WriteBlockUnsafe(Common::ProcessAddress dest_addr, const void* src_buffer,
std::size_t size);
/**
@ -405,7 +404,7 @@ public:
* @post The range [dest_addr, size) within the process' address space contains the
* same data within the range [src_addr, size).
*/
void CopyBlock(Common::ProcessAddress dest_addr, Common::ProcessAddress src_addr,
bool CopyBlock(Common::ProcessAddress dest_addr, Common::ProcessAddress src_addr,
std::size_t size);
/**
@ -418,7 +417,7 @@ public:
* @post The range [dest_addr, size) within the process' address space contains the
* value 0.
*/
void ZeroBlock(Common::ProcessAddress dest_addr, std::size_t size);
bool ZeroBlock(Common::ProcessAddress dest_addr, std::size_t size);
/**
* Invalidates a range of bytes within the current process' address space at the specified

View File

@ -14,12 +14,12 @@
//
#include <deque>
#include <map>
#include <span>
#include <unordered_map>
#include <variant>
#include <vector>
#include <boost/container/flat_map.hpp>
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/opcodes.h"
#include "shader_recompiler/frontend/ir/pred.h"
@ -52,7 +52,7 @@ struct IndirectBranchVariable {
using Variant = std::variant<IR::Reg, IR::Pred, ZeroFlagTag, SignFlagTag, CarryFlagTag,
OverflowFlagTag, GotoVariable, IndirectBranchVariable>;
using ValueMap = boost::container::flat_map<IR::Block*, IR::Value>;
using ValueMap = std::unordered_map<IR::Block*, IR::Value>;
struct DefTable {
const IR::Value& Def(IR::Block* block, IR::Reg variable) {
@ -112,7 +112,7 @@ struct DefTable {
}
std::array<ValueMap, IR::NUM_USER_PREDS> preds;
boost::container::flat_map<u32, ValueMap> goto_vars;
std::unordered_map<u32, ValueMap> goto_vars;
ValueMap indirect_branch_var;
ValueMap zero_flag;
ValueMap sign_flag;
@ -295,8 +295,7 @@ private:
return same;
}
boost::container::flat_map<IR::Block*, boost::container::flat_map<Variant, IR::Inst*>>
incomplete_phis;
std::unordered_map<IR::Block*, std::map<Variant, IR::Inst*>> incomplete_phis;
DefTable current_def;
};

View File

@ -103,7 +103,9 @@ public:
explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_,
Core::Memory::Memory& cpu_memory_)
: rasterizer{rasterizer_},
cpu_memory{cpu_memory_}, streams{{CounterStream{static_cast<QueryCache&>(*this),
// Use reinterpret_cast instead of static_cast as workaround for
// UBSan bug (https://github.com/llvm/llvm-project/issues/59060)
cpu_memory{cpu_memory_}, streams{{CounterStream{reinterpret_cast<QueryCache&>(*this),
VideoCore::QueryType::SamplesPassed}}} {
(void)slot_async_jobs.insert(); // Null value
}

View File

@ -13,7 +13,8 @@ namespace VideoCore {
using namespace Core::Memory;
RasterizerAccelerated::RasterizerAccelerated(Memory& cpu_memory_) : cpu_memory{cpu_memory_} {}
RasterizerAccelerated::RasterizerAccelerated(Memory& cpu_memory_)
: cached_pages(std::make_unique<CachedPages>()), cpu_memory{cpu_memory_} {}
RasterizerAccelerated::~RasterizerAccelerated() = default;
@ -26,7 +27,7 @@ void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int del
std::atomic_thread_fence(std::memory_order_acquire);
const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE);
for (u64 page = addr >> YUZU_PAGEBITS; page != page_end; ++page) {
std::atomic_uint16_t& count = cached_pages.at(page >> 2).Count(page);
std::atomic_uint16_t& count = cached_pages->at(page >> 2).Count(page);
if (delta > 0) {
ASSERT_MSG(count.load(std::memory_order::relaxed) < UINT16_MAX, "Count may overflow!");

View File

@ -41,7 +41,8 @@ private:
};
static_assert(sizeof(CacheEntry) == 8, "CacheEntry should be 8 bytes!");
std::array<CacheEntry, 0x2000000> cached_pages;
using CachedPages = std::array<CacheEntry, 0x2000000>;
std::unique_ptr<CachedPages> cached_pages;
Core::Memory::Memory& cpu_memory;
};

View File

@ -3,9 +3,14 @@
#include <chrono>
#include <string>
#include <QEventLoop>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <discord_rpc.h>
#include <fmt/format.h>
#include <httplib.h>
#include "common/common_types.h"
#include "common/string_util.h"
#include "core/core.h"
@ -31,7 +36,7 @@ void DiscordImpl::Pause() {
Discord_ClearPresence();
}
static std::string GetGameString(const std::string& title) {
std::string DiscordImpl::GetGameString(const std::string& title) {
// Convert to lowercase
std::string icon_name = Common::ToLower(title);
@ -56,51 +61,56 @@ static std::string GetGameString(const std::string& title) {
return icon_name;
}
void DiscordImpl::Update() {
void DiscordImpl::UpdateGameStatus(bool use_default) {
const std::string default_text = "yuzu is an emulator for the Nintendo Switch";
const std::string default_image = "yuzu_logo";
const std::string url = use_default ? default_image : game_url;
s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
const std::string default_text = "yuzu is an emulator for the Nintendo Switch";
const std::string default_image = "yuzu_logo";
std::string game_cover_url = "https://yuzu-emu.org";
std::string title;
DiscordRichPresence presence{};
presence.largeImageKey = url.c_str();
presence.largeImageText = game_title.c_str();
presence.smallImageKey = default_image.c_str();
presence.smallImageText = default_text.c_str();
presence.state = game_title.c_str();
presence.details = "Currently in game";
presence.startTimestamp = start_time;
Discord_UpdatePresence(&presence);
}
void DiscordImpl::Update() {
const std::string default_text = "yuzu is an emulator for the Nintendo Switch";
const std::string default_image = "yuzu_logo";
if (system.IsPoweredOn()) {
system.GetAppLoader().ReadTitle(title);
system.GetAppLoader().ReadTitle(game_title);
// Used to format Icon URL for yuzu website game compatibility page
std::string icon_name = GetGameString(title);
std::string icon_name = GetGameString(game_title);
game_url = fmt::format("https://yuzu-emu.org/images/game/boxart/{}.png", icon_name);
// New Check for game cover
httplib::Client cli(game_cover_url);
cli.set_connection_timeout(std::chrono::seconds(3));
cli.set_read_timeout(std::chrono::seconds(3));
if (auto res = cli.Head(fmt::format("/images/game/boxart/{}.png", icon_name))) {
if (res->status == 200) {
game_cover_url += fmt::format("/images/game/boxart/{}.png", icon_name);
} else {
game_cover_url = "yuzu_logo";
}
} else {
game_cover_url = "yuzu_logo";
}
presence.largeImageKey = game_cover_url.c_str();
presence.largeImageText = title.c_str();
presence.smallImageKey = default_image.c_str();
presence.smallImageText = default_text.c_str();
presence.state = title.c_str();
presence.details = "Currently in game";
} else {
presence.largeImageKey = default_image.c_str();
presence.largeImageText = default_text.c_str();
presence.details = "Currently not in game";
QNetworkAccessManager manager;
QNetworkRequest request;
request.setUrl(QUrl(QString::fromStdString(game_url)));
request.setTransferTimeout(3000);
QNetworkReply* reply = manager.head(request);
QEventLoop request_event_loop;
QObject::connect(reply, &QNetworkReply::finished, &request_event_loop, &QEventLoop::quit);
request_event_loop.exec();
UpdateGameStatus(reply->error());
return;
}
s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
DiscordRichPresence presence{};
presence.largeImageKey = default_image.c_str();
presence.largeImageText = default_text.c_str();
presence.details = "Currently not in game";
presence.startTimestamp = start_time;
Discord_UpdatePresence(&presence);
}

View File

@ -19,6 +19,13 @@ public:
void Pause() override;
void Update() override;
private:
std::string GetGameString(const std::string& title);
void UpdateGameStatus(bool use_default);
std::string game_url{};
std::string game_title{};
Core::System& system;
};

View File

@ -265,7 +265,11 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
std::vector<u8> icon;
std::string name;
u64 program_id = 0;
loader->ReadProgramId(program_id);
const auto result = loader->ReadProgramId(program_id);
if (result != Loader::ResultStatus::Success) {
continue;
}
const PatchManager patch{program_id, system.GetFileSystemController(),
system.GetContentProvider()};