mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-10 09:11:52 -04:00
Merge pull request #109 from DarthMDev/discord-rich-presence
feat: add basic Discord rich presence support
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
// Host implementation of Dolphin's /dev/dolphin Discord contract. The guest
|
||||
// sends only strings and big-endian integer fields; the IPC wire protocol is
|
||||
// owned here so translated game code never needs host SDK headers.
|
||||
namespace DiscordPresence {
|
||||
|
||||
struct Activity {
|
||||
std::string details;
|
||||
std::string state;
|
||||
std::string largeImageKey;
|
||||
std::string largeImageText;
|
||||
std::string smallImageKey;
|
||||
std::string smallImageText;
|
||||
int64_t startTimestamp = 0;
|
||||
int64_t endTimestamp = 0;
|
||||
uint32_t partySize = 0;
|
||||
uint32_t partyMax = 0;
|
||||
};
|
||||
|
||||
void Initialize(const std::string& basicClientId, const std::string& basicTitle);
|
||||
void SetClient(const std::string& clientId);
|
||||
void SetActivity(Activity activity);
|
||||
void Reset();
|
||||
void Shutdown();
|
||||
|
||||
} // namespace DiscordPresence
|
||||
@@ -53,6 +53,11 @@ struct RuntimeUserConfig {
|
||||
std::optional<bool> audioMixWorker;
|
||||
std::optional<bool> attenuateMusicWhenMediaPlays;
|
||||
std::optional<bool> networkEnabled;
|
||||
std::optional<bool> discordPresenceEnabled;
|
||||
// The application ID of the WiiCompiled Discord application. This is only
|
||||
// used by the base product; Retro Rewind supplies its own ID through the
|
||||
// standard Dolphin /dev/dolphin interface.
|
||||
std::optional<std::string> discordClientId;
|
||||
std::optional<std::string> nandRoot;
|
||||
std::optional<std::string> dvdRoot;
|
||||
// The one canonical Retro Rewind installation, owned and updated by the frontend. Setup records
|
||||
@@ -286,6 +291,12 @@ inline void EnsureConfigFile() {
|
||||
"mix_worker = true\n\n"
|
||||
"[network]\n"
|
||||
"enabled = true\n\n"
|
||||
"[discord]\n"
|
||||
"# Rich Presence talks only to a locally-running Discord client.\n"
|
||||
"# Retro Rewind supplies its official app ID automatically. Set this\n"
|
||||
"# to WiiCompiled's Discord application ID for basic base-game presence.\n"
|
||||
"enabled = true\n"
|
||||
"# client_id = \"123456789012345678\"\n\n"
|
||||
"[paths]\n"
|
||||
"# dvd_root = \"D:\\\\MarioKartWii\\\\DATA\"\n"
|
||||
"# nand_root = \"D:\\\\WiiNand\"\n"
|
||||
@@ -418,6 +429,8 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
|
||||
config.attenuateMusicWhenMediaPlays =
|
||||
FindConfigValue<bool>(document, "audio", "attenuate_music_when_media_plays");
|
||||
config.networkEnabled = FindConfigValue<bool>(document, "network", "enabled");
|
||||
config.discordPresenceEnabled = FindConfigValue<bool>(document, "discord", "enabled");
|
||||
config.discordClientId = FindConfigValue<std::string>(document, "discord", "client_id");
|
||||
|
||||
config.nandRoot = FindConfigValue<std::string>(document, "paths", "nand_root");
|
||||
config.dvdRoot = FindConfigValue<std::string>(document, "paths", "dvd_root");
|
||||
@@ -815,6 +828,14 @@ inline std::string RetroRewindRoot(std::string fallback = "") {
|
||||
return Get().retroRewindRoot.value_or(std::move(fallback));
|
||||
}
|
||||
|
||||
inline bool DiscordPresenceEnabled(bool fallback = true) {
|
||||
return Get().discordPresenceEnabled.value_or(fallback);
|
||||
}
|
||||
|
||||
inline std::string DiscordClientId(std::string fallback = "1543984562369990706") {
|
||||
return Get().discordClientId.value_or(std::move(fallback));
|
||||
}
|
||||
|
||||
inline const std::vector<std::string>& OverlayRoots() {
|
||||
return Get().overlayRoots;
|
||||
}
|
||||
@@ -871,6 +892,9 @@ inline void LogLoadedConfig() {
|
||||
if (config.networkEnabled) {
|
||||
std::cout << " network_enabled=" << (*config.networkEnabled ? "true" : "false");
|
||||
}
|
||||
if (config.discordPresenceEnabled) {
|
||||
std::cout << " discord_enabled=" << (*config.discordPresenceEnabled ? "true" : "false");
|
||||
}
|
||||
if (config.nandRoot) {
|
||||
std::cout << " nand_root=" << *config.nandRoot;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,466 @@
|
||||
#include "discord_presence.h"
|
||||
|
||||
#include "runtime_log.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace DiscordPresence {
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kHandshakeOpcode = 0;
|
||||
constexpr uint32_t kFrameOpcode = 1;
|
||||
constexpr size_t kMaxClientIdLength = 32;
|
||||
constexpr auto kConnectionRetryCooldown = std::chrono::seconds(1);
|
||||
|
||||
bool IsClientId(std::string_view value) {
|
||||
return !value.empty() && value.size() <= kMaxClientIdLength &&
|
||||
std::all_of(value.begin(), value.end(), [](unsigned char ch) { return std::isdigit(ch) != 0; });
|
||||
}
|
||||
|
||||
std::string EscapeJson(std::string_view value) {
|
||||
std::string escaped;
|
||||
escaped.reserve(value.size());
|
||||
for (const unsigned char ch : value) {
|
||||
switch (ch) {
|
||||
case '\\': escaped += "\\\\"; break;
|
||||
case '\"': escaped += "\\\""; break;
|
||||
case '\b': escaped += "\\b"; break;
|
||||
case '\f': escaped += "\\f"; break;
|
||||
case '\n': escaped += "\\n"; break;
|
||||
case '\r': escaped += "\\r"; break;
|
||||
case '\t': escaped += "\\t"; break;
|
||||
default:
|
||||
if (ch < 0x20) {
|
||||
static constexpr char kHex[] = "0123456789abcdef";
|
||||
escaped += "\\u00";
|
||||
escaped += kHex[ch >> 4];
|
||||
escaped += kHex[ch & 0x0f];
|
||||
} else {
|
||||
escaped += static_cast<char>(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
void AppendJsonString(std::ostringstream& output, bool& hasValue, std::string_view key, std::string_view value) {
|
||||
if (value.empty()) {
|
||||
return;
|
||||
}
|
||||
if (hasValue) {
|
||||
output << ',';
|
||||
}
|
||||
output << '\"' << key << "\":\"" << EscapeJson(value) << '\"';
|
||||
hasValue = true;
|
||||
}
|
||||
|
||||
std::string BuildActivityPayload(const Activity& activity) {
|
||||
std::ostringstream json;
|
||||
json << "{\"cmd\":\"SET_ACTIVITY\",\"nonce\":\"wiicompiled\",\"args\":{\"pid\":";
|
||||
#if defined(_WIN32)
|
||||
json << static_cast<unsigned long>(::GetCurrentProcessId());
|
||||
#else
|
||||
json << static_cast<long>(::getpid());
|
||||
#endif
|
||||
json << ",\"activity\":{";
|
||||
bool hasActivityField = false;
|
||||
AppendJsonString(json, hasActivityField, "details", activity.details);
|
||||
AppendJsonString(json, hasActivityField, "state", activity.state);
|
||||
const auto appendSection = [&](std::string_view name, const auto& append) {
|
||||
if (hasActivityField) {
|
||||
json << ',';
|
||||
}
|
||||
json << '\"' << name << "\":{";
|
||||
append();
|
||||
json << '}';
|
||||
hasActivityField = true;
|
||||
};
|
||||
const bool hasAssets = !activity.largeImageKey.empty() || !activity.largeImageText.empty() ||
|
||||
!activity.smallImageKey.empty() || !activity.smallImageText.empty();
|
||||
if (hasAssets) {
|
||||
appendSection("assets", [&] {
|
||||
bool hasAsset = false;
|
||||
AppendJsonString(json, hasAsset, "large_image", activity.largeImageKey);
|
||||
AppendJsonString(json, hasAsset, "large_text", activity.largeImageText);
|
||||
AppendJsonString(json, hasAsset, "small_image", activity.smallImageKey);
|
||||
AppendJsonString(json, hasAsset, "small_text", activity.smallImageText);
|
||||
});
|
||||
}
|
||||
if (activity.startTimestamp > 0 || activity.endTimestamp > 0) {
|
||||
appendSection("timestamps", [&] {
|
||||
if (activity.startTimestamp > 0) {
|
||||
json << "\"start\":" << activity.startTimestamp;
|
||||
}
|
||||
if (activity.endTimestamp > 0) {
|
||||
if (activity.startTimestamp > 0) {
|
||||
json << ',';
|
||||
}
|
||||
json << "\"end\":" << activity.endTimestamp;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (activity.partySize > 0 && activity.partyMax > 0) {
|
||||
appendSection("party", [&] {
|
||||
json << "\"size\":[" << activity.partySize << ',' << activity.partyMax << ']';
|
||||
});
|
||||
}
|
||||
if (hasActivityField) {
|
||||
json << ',';
|
||||
}
|
||||
json << "\"instance\":false}}}";
|
||||
return json.str();
|
||||
}
|
||||
|
||||
class Client {
|
||||
public:
|
||||
void Initialize(const std::string& clientId, const std::string& title) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
basicClientId_ = IsClientId(clientId) ? clientId : std::string{};
|
||||
basicActivity_ = {};
|
||||
basicActivity_.details = title;
|
||||
basicActivity_.startTimestamp = UnixSeconds();
|
||||
customClient_ = false;
|
||||
activity_ = basicActivity_;
|
||||
ReconnectAndSendLocked();
|
||||
}
|
||||
|
||||
void SetClient(const std::string& clientId) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (!IsClientId(clientId)) {
|
||||
RT_LOG(RT_TAG_RUNTIME) << "Ignoring invalid Discord client ID from /dev/dolphin" << std::endl;
|
||||
return;
|
||||
}
|
||||
if (clientId_ == clientId && connected_) {
|
||||
return;
|
||||
}
|
||||
customClient_ = true;
|
||||
clientId_ = clientId;
|
||||
CloseLocked();
|
||||
ReconnectAndSendLocked();
|
||||
}
|
||||
|
||||
void SetActivity(Activity activity) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (!customClient_) {
|
||||
return;
|
||||
}
|
||||
activity_ = std::move(activity);
|
||||
SendActivityLocked();
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
customClient_ = false;
|
||||
clientId_.clear();
|
||||
activity_ = basicActivity_;
|
||||
CloseLocked();
|
||||
ReconnectAndSendLocked();
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
CloseLocked();
|
||||
}
|
||||
|
||||
private:
|
||||
static int64_t UnixSeconds() {
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
std::string ActiveClientIdLocked() const {
|
||||
return customClient_ ? clientId_ : basicClientId_;
|
||||
}
|
||||
|
||||
bool WriteFrameLocked(uint32_t opcode, std::string_view payload) {
|
||||
std::array<uint8_t, 8> header{};
|
||||
const uint32_t size = static_cast<uint32_t>(payload.size());
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
header[i] = static_cast<uint8_t>(opcode >> (i * 8));
|
||||
header[4 + i] = static_cast<uint8_t>(size >> (i * 8));
|
||||
}
|
||||
return WriteAllLocked(header.data(), header.size()) &&
|
||||
WriteAllLocked(reinterpret_cast<const uint8_t*>(payload.data()), payload.size());
|
||||
}
|
||||
|
||||
void ReconnectAndSendLocked() {
|
||||
const std::string clientId = ActiveClientIdLocked();
|
||||
if (clientId.empty() || !ConnectLocked()) {
|
||||
return;
|
||||
}
|
||||
const std::string handshake = "{\"v\":1,\"client_id\":\"" + clientId + "\"}";
|
||||
if (!WriteFrameLocked(kHandshakeOpcode, handshake)) {
|
||||
RecordFailedConnectionLocked();
|
||||
CloseLocked();
|
||||
return;
|
||||
}
|
||||
if (!ReadReadyLocked()) {
|
||||
RecordFailedConnectionLocked();
|
||||
CloseLocked();
|
||||
return;
|
||||
}
|
||||
SendActivityLocked();
|
||||
}
|
||||
|
||||
void SendActivityLocked() {
|
||||
if (!connected_) {
|
||||
ReconnectAndSendLocked();
|
||||
return;
|
||||
}
|
||||
if (ActiveClientIdLocked().empty()) {
|
||||
return;
|
||||
}
|
||||
if (!WriteFrameLocked(kFrameOpcode, BuildActivityPayload(activity_))) {
|
||||
RecordFailedConnectionLocked();
|
||||
CloseLocked();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConnectionRetryAllowedLocked() const {
|
||||
return !lastFailedConnectionAttempt_ ||
|
||||
std::chrono::steady_clock::now() - *lastFailedConnectionAttempt_ >= kConnectionRetryCooldown;
|
||||
}
|
||||
|
||||
void RecordFailedConnectionLocked() {
|
||||
lastFailedConnectionAttempt_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
bool ConnectLocked() {
|
||||
if (connected_) {
|
||||
return true;
|
||||
}
|
||||
if (!ConnectionRetryAllowedLocked()) {
|
||||
return false;
|
||||
}
|
||||
for (unsigned int index = 0; index < 10; ++index) {
|
||||
const std::string name = "\\\\.\\pipe\\discord-ipc-" + std::to_string(index);
|
||||
handle_ = ::CreateFileA(name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (handle_ != INVALID_HANDLE_VALUE) {
|
||||
DWORD mode = PIPE_READMODE_BYTE;
|
||||
::SetNamedPipeHandleState(handle_, &mode, nullptr, nullptr);
|
||||
connected_ = true;
|
||||
lastFailedConnectionAttempt_.reset();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
RecordFailedConnectionLocked();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WriteAllLocked(const uint8_t* data, size_t size) {
|
||||
while (size != 0) {
|
||||
DWORD written = 0;
|
||||
if (!::WriteFile(handle_, data, static_cast<DWORD>(size), &written, nullptr) || written == 0) {
|
||||
return false;
|
||||
}
|
||||
data += written;
|
||||
size -= written;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadAllLocked(uint8_t* data, size_t size) {
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(1);
|
||||
while (size != 0) {
|
||||
if (std::chrono::steady_clock::now() >= deadline) {
|
||||
return false;
|
||||
}
|
||||
DWORD available = 0;
|
||||
if (!::PeekNamedPipe(handle_, nullptr, 0, nullptr, &available, nullptr)) {
|
||||
return false;
|
||||
}
|
||||
if (available == 0) {
|
||||
if (std::chrono::steady_clock::now() >= deadline) {
|
||||
return false;
|
||||
}
|
||||
::Sleep(10);
|
||||
continue;
|
||||
}
|
||||
DWORD read = 0;
|
||||
const DWORD requested = static_cast<DWORD>(std::min<size_t>(size, available));
|
||||
if (!::ReadFile(handle_, data, requested, &read, nullptr) || read == 0) {
|
||||
return false;
|
||||
}
|
||||
data += read;
|
||||
size -= read;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CloseLocked() {
|
||||
if (handle_ != INVALID_HANDLE_VALUE) {
|
||||
::CloseHandle(handle_);
|
||||
handle_ = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
connected_ = false;
|
||||
}
|
||||
|
||||
HANDLE handle_ = INVALID_HANDLE_VALUE;
|
||||
#else
|
||||
bool ConnectLocked() {
|
||||
if (connected_) {
|
||||
return true;
|
||||
}
|
||||
if (!ConnectionRetryAllowedLocked()) {
|
||||
return false;
|
||||
}
|
||||
std::array<std::string, 5> roots{};
|
||||
size_t rootCount = 0;
|
||||
if (const char* runtimeDir = std::getenv("XDG_RUNTIME_DIR"); runtimeDir && *runtimeDir) {
|
||||
roots[rootCount++] = runtimeDir;
|
||||
}
|
||||
if (const char* tempDir = std::getenv("TMPDIR"); tempDir && *tempDir) {
|
||||
roots[rootCount++] = tempDir;
|
||||
}
|
||||
if (const char* tempDir = std::getenv("TMP"); tempDir && *tempDir && rootCount < roots.size()) {
|
||||
roots[rootCount++] = tempDir;
|
||||
}
|
||||
if (const char* tempDir = std::getenv("TEMP"); tempDir && *tempDir && rootCount < roots.size()) {
|
||||
roots[rootCount++] = tempDir;
|
||||
}
|
||||
roots[rootCount++] = "/tmp";
|
||||
for (size_t rootIndex = 0; rootIndex < rootCount; ++rootIndex) {
|
||||
for (unsigned int index = 0; index < 10; ++index) {
|
||||
const std::string path = roots[rootIndex] + "/discord-ipc-" + std::to_string(index);
|
||||
if (path.size() >= sizeof(sockaddr_un::sun_path)) {
|
||||
continue;
|
||||
}
|
||||
const int socketFd = ::socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (socketFd < 0) {
|
||||
continue;
|
||||
}
|
||||
sockaddr_un address{};
|
||||
address.sun_family = AF_UNIX;
|
||||
std::memcpy(address.sun_path, path.c_str(), path.size() + 1);
|
||||
if (::connect(socketFd, reinterpret_cast<const sockaddr*>(&address), sizeof(address)) == 0) {
|
||||
timeval timeout{};
|
||||
timeout.tv_sec = 1;
|
||||
::setsockopt(socketFd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
fd_ = socketFd;
|
||||
connected_ = true;
|
||||
lastFailedConnectionAttempt_.reset();
|
||||
return true;
|
||||
}
|
||||
::close(socketFd);
|
||||
}
|
||||
}
|
||||
RecordFailedConnectionLocked();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WriteAllLocked(const uint8_t* data, size_t size) {
|
||||
while (size != 0) {
|
||||
const ssize_t written = ::send(fd_, data, size, MSG_NOSIGNAL);
|
||||
if (written <= 0) {
|
||||
return false;
|
||||
}
|
||||
data += written;
|
||||
size -= static_cast<size_t>(written);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadAllLocked(uint8_t* data, size_t size) {
|
||||
while (size != 0) {
|
||||
const ssize_t read = ::recv(fd_, data, size, 0);
|
||||
if (read <= 0) {
|
||||
return false;
|
||||
}
|
||||
data += read;
|
||||
size -= static_cast<size_t>(read);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CloseLocked() {
|
||||
if (fd_ >= 0) {
|
||||
::close(fd_);
|
||||
fd_ = -1;
|
||||
}
|
||||
connected_ = false;
|
||||
}
|
||||
|
||||
int fd_ = -1;
|
||||
#endif
|
||||
|
||||
bool ReadReadyLocked() {
|
||||
std::array<uint8_t, 8> header{};
|
||||
if (!ReadAllLocked(header.data(), header.size())) {
|
||||
return false;
|
||||
}
|
||||
uint32_t opcode = 0;
|
||||
uint32_t size = 0;
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
opcode |= static_cast<uint32_t>(header[i]) << (i * 8);
|
||||
size |= static_cast<uint32_t>(header[4 + i]) << (i * 8);
|
||||
}
|
||||
if (opcode != kFrameOpcode || size > 1024 * 1024) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> payload(size);
|
||||
if (!ReadAllLocked(payload.data(), payload.size())) {
|
||||
return false;
|
||||
}
|
||||
const std::string_view json(reinterpret_cast<const char*>(payload.data()), payload.size());
|
||||
return json.find("\"evt\":\"READY\"") != std::string_view::npos;
|
||||
}
|
||||
|
||||
std::mutex mutex_;
|
||||
bool connected_ = false;
|
||||
bool customClient_ = false;
|
||||
std::optional<std::chrono::steady_clock::time_point> lastFailedConnectionAttempt_;
|
||||
std::string basicClientId_;
|
||||
std::string clientId_;
|
||||
Activity basicActivity_;
|
||||
Activity activity_;
|
||||
};
|
||||
|
||||
Client g_client;
|
||||
|
||||
} // namespace
|
||||
|
||||
void Initialize(const std::string& basicClientId, const std::string& basicTitle) {
|
||||
g_client.Initialize(basicClientId, basicTitle);
|
||||
}
|
||||
|
||||
void SetClient(const std::string& clientId) {
|
||||
g_client.SetClient(clientId);
|
||||
}
|
||||
|
||||
void SetActivity(Activity activity) {
|
||||
g_client.SetActivity(std::move(activity));
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
g_client.Reset();
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
g_client.Shutdown();
|
||||
}
|
||||
|
||||
} // namespace DiscordPresence
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "nand_internal.h"
|
||||
|
||||
#include "discord_presence.h"
|
||||
#include "runtime_log.h"
|
||||
|
||||
extern "C" void OSSleepThread_HLE_801aa9b8(CpuContext* ctx);
|
||||
@@ -201,9 +202,69 @@ static int32_t HandleDolphinIoctlv(uint32_t cmd, uint32_t numIn, uint32_t numOut
|
||||
}
|
||||
|
||||
case DOLPHIN_IOCTL_SET_SPEED_LIMIT:
|
||||
case DOLPHIN_IOCTL_DISCORD_SET_CLIENT:
|
||||
case DOLPHIN_IOCTL_DISCORD_SET_PRESENCE:
|
||||
return ISFS_OK;
|
||||
|
||||
case DOLPHIN_IOCTL_DISCORD_SET_CLIENT: {
|
||||
if (numIn != 1 || numOut != 0 || vectorPtr == 0) {
|
||||
return ISFS_EINVAL;
|
||||
}
|
||||
const IosVector client = ReadIosVector(vectorPtr, 0);
|
||||
if (!IsValidGuestRange(client.address, client.size)) {
|
||||
return ISFS_EINVAL;
|
||||
}
|
||||
if (RuntimeConfigFile::DiscordPresenceEnabled()) {
|
||||
DiscordPresence::SetClient(ReadGuestCString(client.address, client.size));
|
||||
}
|
||||
return ISFS_OK;
|
||||
}
|
||||
|
||||
case DOLPHIN_IOCTL_DISCORD_SET_PRESENCE: {
|
||||
if (numIn != 10 || numOut != 0 || vectorPtr == 0) {
|
||||
return ISFS_EINVAL;
|
||||
}
|
||||
std::array<IosVector, 10> values{};
|
||||
for (uint32_t index = 0; index < values.size(); ++index) {
|
||||
values[index] = ReadIosVector(vectorPtr, index);
|
||||
if (!IsValidGuestRange(values[index].address, values[index].size)) {
|
||||
return ISFS_EINVAL;
|
||||
}
|
||||
}
|
||||
if (RuntimeConfigFile::DiscordPresenceEnabled()) {
|
||||
DiscordPresence::Activity activity;
|
||||
activity.details = ReadGuestCString(values[0].address, values[0].size);
|
||||
activity.state = ReadGuestCString(values[1].address, values[1].size);
|
||||
activity.largeImageKey = ReadGuestCString(values[2].address, values[2].size);
|
||||
activity.largeImageText = ReadGuestCString(values[3].address, values[3].size);
|
||||
activity.smallImageKey = ReadGuestCString(values[4].address, values[4].size);
|
||||
activity.smallImageText = ReadGuestCString(values[5].address, values[5].size);
|
||||
if (values[6].size >= 8 && Memory::Contains(values[6].address, 8)) {
|
||||
activity.startTimestamp = static_cast<int64_t>(
|
||||
(static_cast<uint64_t>(Memory::Read32(values[6].address)) << 32) |
|
||||
Memory::Read32(values[6].address + 4));
|
||||
}
|
||||
if (values[7].size >= 8 && Memory::Contains(values[7].address, 8)) {
|
||||
activity.endTimestamp = static_cast<int64_t>(
|
||||
(static_cast<uint64_t>(Memory::Read32(values[7].address)) << 32) |
|
||||
Memory::Read32(values[7].address + 4));
|
||||
}
|
||||
if (values[8].size >= 4) {
|
||||
activity.partySize = Memory::Read32(values[8].address);
|
||||
}
|
||||
if (values[9].size >= 4) {
|
||||
activity.partyMax = Memory::Read32(values[9].address);
|
||||
}
|
||||
DiscordPresence::SetActivity(std::move(activity));
|
||||
}
|
||||
return ISFS_OK;
|
||||
}
|
||||
|
||||
case DOLPHIN_IOCTL_DISCORD_RESET:
|
||||
if (numIn != 0 || numOut != 0) {
|
||||
return ISFS_EINVAL;
|
||||
}
|
||||
if (RuntimeConfigFile::DiscordPresenceEnabled()) {
|
||||
DiscordPresence::Reset();
|
||||
}
|
||||
return ISFS_OK;
|
||||
|
||||
case DOLPHIN_IOCTL_GET_SYSTEM_TIME: {
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "system_bridge.h"
|
||||
#include "ppc_runtime.h"
|
||||
#include "aurora_events.h"
|
||||
#include "discord_presence.h"
|
||||
#include "fiber_manager.h"
|
||||
#include "hle_stubs.h"
|
||||
#include "runtime_config.h"
|
||||
@@ -1315,6 +1316,9 @@ int RuntimeMain(int argc, char** argv) {
|
||||
throw std::invalid_argument("The game runtime does not accept command-line options; use Config.toml through the installed host.");
|
||||
}
|
||||
RuntimeConfigFile::LogLoadedConfig();
|
||||
if (RuntimeConfigFile::DiscordPresenceEnabled()) {
|
||||
DiscordPresence::Initialize(RuntimeConfigFile::DiscordClientId(), "Mario Kart Wii");
|
||||
}
|
||||
SystemBridge::Initialize();
|
||||
TranslatedFunctionRegistry::Finalize();
|
||||
|
||||
@@ -1441,6 +1445,7 @@ int RuntimeMain(int argc, char** argv) {
|
||||
Fiber::GuestFiberManager::Shutdown();
|
||||
WindowPlacementPersistence::Flush(true);
|
||||
aurora_shutdown();
|
||||
DiscordPresence::Shutdown();
|
||||
SetRuntimeExitCodeImpl(0);
|
||||
ShutdownProcessTranscript();
|
||||
return 0;
|
||||
@@ -1458,6 +1463,7 @@ int RuntimeMain(int argc, char** argv) {
|
||||
Fiber::GuestFiberManager::Shutdown();
|
||||
WindowPlacementPersistence::Flush(true);
|
||||
aurora_shutdown();
|
||||
DiscordPresence::Shutdown();
|
||||
ShutdownProcessTranscript();
|
||||
return 1;
|
||||
} catch (const std::exception& ex) {
|
||||
@@ -1469,6 +1475,7 @@ int RuntimeMain(int argc, char** argv) {
|
||||
Fiber::GuestFiberManager::Shutdown();
|
||||
WindowPlacementPersistence::Flush(true);
|
||||
aurora_shutdown();
|
||||
DiscordPresence::Shutdown();
|
||||
ShutdownProcessTranscript();
|
||||
return 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user