Remove migrated WowPresence source copy

This commit is contained in:
2026-09-01 16:20:30 +02:00
parent 6a8e01bb58
commit 804eecd53a
@@ -1,504 +0,0 @@
/*
* DiscordPresence.exe
* Invisible companion launcher for Modernization Tool.
*
* - is started automatically by DiscordPresence.dll after WoW is running
* - reads .modernization_tool\DiscordPresence\discord_wow_status.json
* - publishes Discord Rich Presence over the local Discord IPC named pipe
* - exits when WoW_Modernized.exe exits
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tlhelp32.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define STATUS_MAX_AGE 30
#define TICK_MS 2000
#define PIPE_COUNT 10
#define IPC_OPCODE_HANDSHAKE 0u
#define IPC_OPCODE_FRAME 1u
#define DEFAULT_DISCORD_APPLICATION_ID "1544072796098011176"
typedef struct {
char name[32];
char zone[80];
char guild[64];
char faction[24];
char class_name[32];
char race[32];
int level;
long long ts;
int ok;
int in_world;
} Status;
typedef struct {
HANDLE pipe;
char last_activity[768];
unsigned long nonce;
} DiscordRpc;
static int own_directory(char *out, size_t out_size) {
char path[MAX_PATH];
char *slash;
if (!GetModuleFileNameA(NULL, path, MAX_PATH)) return 0;
slash = strrchr(path, '\\');
if (!slash) slash = strrchr(path, '/');
if (!slash) return 0;
*slash = 0;
if (strlen(path) + 1 > out_size) return 0;
lstrcpynA(out, path, (int)out_size);
return 1;
}
static int support_directory(char *out, size_t out_size) {
char root[MAX_PATH], first[MAX_PATH];
if (!own_directory(root, sizeof(root))) return 0;
if (strlen(root) + strlen("\\.modernization_tool") + 1 >= sizeof(first)) return 0;
_snprintf(first, sizeof(first), "%s\\.modernization_tool", root);
first[sizeof(first) - 1] = 0;
CreateDirectoryA(first, NULL);
if (strlen(first) + strlen("\\DiscordPresence") + 1 >= out_size) return 0;
_snprintf(out, out_size, "%s\\DiscordPresence", first);
out[out_size - 1] = 0;
CreateDirectoryA(out, NULL);
return 1;
}
static int support_file(char *out, size_t out_size, const char *name) {
char dir[MAX_PATH];
if (!support_directory(dir, sizeof(dir))) return 0;
if (strlen(dir) + 1 + strlen(name) + 1 > out_size) return 0;
_snprintf(out, out_size, "%s\\%s", dir, name);
out[out_size - 1] = 0;
return 1;
}
static void log_line(const char *text) {
char path[MAX_PATH];
HANDLE file;
DWORD written;
SYSTEMTIME st;
char line[1200];
if (!support_file(path, sizeof(path), "DiscordPresence.log")) return;
file = CreateFileA(path, FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == INVALID_HANDLE_VALUE) return;
GetLocalTime(&st);
_snprintf(line, sizeof(line), "%04u-%02u-%02u %02u:%02u:%02u %s\r\n",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond,
text ? text : "");
line[sizeof(line) - 1] = 0;
WriteFile(file, line, (DWORD)strlen(line), &written, NULL);
CloseHandle(file);
}
static int read_text_file(const char *path, char *out, size_t out_size) {
HANDLE file;
DWORD got = 0;
if (!out || out_size < 2) return 0;
out[0] = 0;
file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == INVALID_HANDLE_VALUE) return 0;
if (!ReadFile(file, out, (DWORD)(out_size - 1), &got, NULL)) {
CloseHandle(file);
return 0;
}
CloseHandle(file);
out[got] = 0;
return 1;
}
static void trim_ascii(char *text) {
char *start, *end;
if (!text) return;
start = text;
while (*start == ' ' || *start == '\t' || *start == '\r' || *start == '\n') ++start;
if (start != text) memmove(text, start, strlen(start) + 1);
end = text + strlen(text);
while (end > text && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\r' || end[-1] == '\n'))
--end;
*end = 0;
}
static int valid_application_id(const char *text) {
size_t i, n;
if (!text) return 0;
n = strlen(text);
if (n < 15 || n > 24) return 0;
for (i = 0; i < n; ++i)
if (text[i] < '0' || text[i] > '9') return 0;
return 1;
}
static int load_application_id(char *out, size_t out_size) {
char path[MAX_PATH];
char override_id[64] = {0};
if (!out || out_size <= strlen(DEFAULT_DISCORD_APPLICATION_ID)) return 0;
lstrcpynA(out, DEFAULT_DISCORD_APPLICATION_ID, (int)out_size);
/* Optional advanced override. Normal users never need this file. */
if (support_file(path, sizeof(path), "discord_application_id") &&
read_text_file(path, override_id, sizeof(override_id))) {
trim_ascii(override_id);
if (valid_application_id(override_id)) {
lstrcpynA(out, override_id, (int)out_size);
} else if (override_id[0]) {
log_line("Ignoring invalid discord_application_id override; using built-in OctoWoW ID.");
}
}
return valid_application_id(out);
}
static DWORD parse_target_pid(const char *cmdline) {
const char *p;
char *end = NULL;
unsigned long value;
if (!cmdline) return 0;
p = strstr(cmdline, "--pid");
if (!p) return 0;
p += 5;
while (*p == ' ' || *p == '\t') ++p;
if (!*p) return 0;
value = strtoul(p, &end, 10);
if (end == p || value == 0 || value > 0xFFFFFFFFul) return 0;
return (DWORD)value;
}
static DWORD find_process(const char *exe_name) {
HANDLE snapshot;
PROCESSENTRY32 entry;
DWORD pid = 0;
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) return 0;
memset(&entry, 0, sizeof(entry));
entry.dwSize = sizeof(entry);
if (Process32First(snapshot, &entry)) {
do {
if (_stricmp(entry.szExeFile, exe_name) == 0) {
pid = entry.th32ProcessID;
break;
}
} while (Process32Next(snapshot, &entry));
}
CloseHandle(snapshot);
return pid;
}
static const char *find_key(const char *json, const char *key) {
static char pattern[80];
const char *p;
_snprintf(pattern, sizeof(pattern), "\"%s\":", key);
pattern[sizeof(pattern) - 1] = 0;
p = strstr(json, pattern);
return p ? p + strlen(pattern) : NULL;
}
static int json_bool(const char *json, const char *key, int *value) {
const char *p = find_key(json, key);
if (!p || !value) return 0;
while (*p == ' ' || *p == '\t') ++p;
if (strncmp(p, "true", 4) == 0) { *value = 1; return 1; }
if (strncmp(p, "false", 5) == 0) { *value = 0; return 1; }
return 0;
}
static int json_int64(const char *json, const char *key, long long *value) {
const char *p = find_key(json, key);
char *end = NULL;
long long v;
if (!p || !value) return 0;
v = _strtoi64(p, &end, 10);
if (end == p) return 0;
*value = v;
return 1;
}
static int json_int(const char *json, const char *key, int *value) {
long long v = 0;
if (!json_int64(json, key, &v)) return 0;
*value = (int)v;
return 1;
}
static int json_string(const char *json, const char *key, char *out, size_t out_size) {
const char *p = find_key(json, key);
size_t w = 0;
if (!p || !out || out_size < 2) return 0;
while (*p == ' ' || *p == '\t') ++p;
if (*p++ != '"') return 0;
while (*p && *p != '"' && w + 1 < out_size) {
if (*p == '\\') {
++p;
if (!*p) break;
if (*p == '"' || *p == '\\' || *p == '/') out[w++] = *p++;
else if (*p == 'n') { out[w++] = '\n'; ++p; }
else if (*p == 'r') { out[w++] = '\r'; ++p; }
else if (*p == 't') { out[w++] = '\t'; ++p; }
else return 0;
} else {
out[w++] = *p++;
}
}
out[w] = 0;
return *p == '"';
}
static int load_status(Status *status) {
char path[MAX_PATH], json[2048];
time_t now;
memset(status, 0, sizeof(*status));
if (!support_file(path, sizeof(path), "discord_wow_status.json")) return 0;
if (!read_text_file(path, json, sizeof(json))) return 0;
if (!json_bool(json, "ok", &status->ok) ||
!json_bool(json, "in_world", &status->in_world) ||
!json_int64(json, "ts", &status->ts))
return 0;
now = time(NULL);
if (!status->ok || !status->in_world ||
status->ts <= 0 || (long long)now - status->ts > STATUS_MAX_AGE ||
status->ts - (long long)now > 5)
return 0;
json_string(json, "name", status->name, sizeof(status->name));
json_string(json, "zone", status->zone, sizeof(status->zone));
json_string(json, "guild", status->guild, sizeof(status->guild));
json_string(json, "faction", status->faction, sizeof(status->faction));
json_string(json, "class", status->class_name, sizeof(status->class_name));
json_string(json, "race", status->race, sizeof(status->race));
json_int(json, "level", &status->level);
return status->name[0] || status->zone[0] || status->level > 0;
}
static int rpc_read_packet(HANDLE pipe, char *payload, size_t payload_size) {
uint32_t header[2];
DWORD got = 0, total = 0;
if (!ReadFile(pipe, header, sizeof(header), &got, NULL) || got != sizeof(header)) return 0;
if (header[1] >= payload_size) return 0;
while (total < header[1]) {
DWORD chunk = 0;
if (!ReadFile(pipe, payload + total, header[1] - total, &chunk, NULL) || chunk == 0)
return 0;
total += chunk;
}
payload[total] = 0;
return 1;
}
static int rpc_write_packet(HANDLE pipe, uint32_t opcode, const char *json) {
unsigned char buffer[2048];
uint32_t *header = (uint32_t *)buffer;
DWORD written = 0;
size_t len = strlen(json);
if (len + 8 > sizeof(buffer)) return 0;
header[0] = opcode;
header[1] = (uint32_t)len;
memcpy(buffer + 8, json, len);
return WriteFile(pipe, buffer, (DWORD)(len + 8), &written, NULL) &&
written == (DWORD)(len + 8);
}
static void rpc_close(DiscordRpc *rpc) {
if (rpc->pipe && rpc->pipe != INVALID_HANDLE_VALUE) CloseHandle(rpc->pipe);
rpc->pipe = INVALID_HANDLE_VALUE;
rpc->last_activity[0] = 0;
}
static int rpc_connect(DiscordRpc *rpc, const char *application_id) {
int i;
char pipe_name[64], handshake[160], response[2048];
rpc_close(rpc);
for (i = 0; i < PIPE_COUNT; ++i) {
_snprintf(pipe_name, sizeof(pipe_name), "\\\\.\\pipe\\discord-ipc-%d", i);
rpc->pipe = CreateFileA(pipe_name, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, 0, NULL);
if (rpc->pipe != INVALID_HANDLE_VALUE) break;
}
if (rpc->pipe == INVALID_HANDLE_VALUE) return 0;
_snprintf(handshake, sizeof(handshake), "{\"v\":1,\"client_id\":\"%s\"}", application_id);
if (!rpc_write_packet(rpc->pipe, IPC_OPCODE_HANDSHAKE, handshake) ||
!rpc_read_packet(rpc->pipe, response, sizeof(response)) ||
(!strstr(response, "\"READY\"") && !strstr(response, "\"evt\":\"READY\""))) {
rpc_close(rpc);
return 0;
}
log_line("Connected to Discord IPC.");
return 1;
}
static void title_case_faction(char *text) {
if (text && text[0] >= 'a' && text[0] <= 'z') text[0] = (char)(text[0] - 'a' + 'A');
}
static void format_activity(const Status *s, char *out, size_t out_size, long long session_start) {
char details[384] = {0};
char extra[192] = {0};
char faction[24] = {0};
if (s->faction[0]) {
lstrcpynA(faction, s->faction, sizeof(faction));
title_case_faction(faction);
}
if (s->name[0] && s->guild[0])
_snprintf(details, sizeof(details), "%s <%s>", s->name, s->guild);
else if (s->name[0])
_snprintf(details, sizeof(details), "%s", s->name);
else if (s->guild[0])
_snprintf(details, sizeof(details), "<%s>", s->guild);
if (s->level > 0 && s->class_name[0])
_snprintf(extra, sizeof(extra), "Lvl %d %s", s->level, s->class_name);
else if (s->level > 0)
_snprintf(extra, sizeof(extra), "Lvl %d", s->level);
else if (s->class_name[0])
_snprintf(extra, sizeof(extra), "%s", s->class_name);
if (s->race[0]) {
size_t used = strlen(extra);
if (used)
_snprintf(extra + used, sizeof(extra) - used, " \\u00b7 %s", s->race);
else
_snprintf(extra, sizeof(extra), "%s", s->race);
}
if (faction[0]) {
size_t used = strlen(extra);
if (used)
_snprintf(extra + used, sizeof(extra) - used, " \\u00b7 %s", faction);
else
_snprintf(extra, sizeof(extra), "%s", faction);
}
if (details[0] && extra[0]) {
size_t used = strlen(details);
_snprintf(details + used, sizeof(details) - used, " \\u00b7 %s", extra);
} else if (!details[0] && extra[0]) {
lstrcpynA(details, extra, sizeof(details));
}
_snprintf(out, out_size,
"{\"details\":\"%s\",\"state\":\"%s\",\"timestamps\":{\"start\":%lld}}",
details, s->zone, session_start);
out[out_size - 1] = 0;
}
static int rpc_set_activity(DiscordRpc *rpc, DWORD pid, const char *activity_json) {
char command[1400], response[2048];
if (!rpc || rpc->pipe == INVALID_HANDLE_VALUE) return 0;
if (strcmp(rpc->last_activity, activity_json) == 0) return 1;
++rpc->nonce;
_snprintf(command, sizeof(command),
"{\"cmd\":\"SET_ACTIVITY\",\"args\":{\"pid\":%lu,\"activity\":%s},"
"\"nonce\":\"%lu\"}",
(unsigned long)pid, activity_json, rpc->nonce);
command[sizeof(command) - 1] = 0;
if (!rpc_write_packet(rpc->pipe, IPC_OPCODE_FRAME, command) ||
!rpc_read_packet(rpc->pipe, response, sizeof(response))) {
rpc_close(rpc);
return 0;
}
if (strstr(response, "\"ERROR\"")) {
log_line(response);
return 0;
}
lstrcpynA(rpc->last_activity, activity_json, sizeof(rpc->last_activity));
return 1;
}
static void rpc_clear(DiscordRpc *rpc, DWORD pid) {
char command[256], response[1024];
if (!rpc || rpc->pipe == INVALID_HANDLE_VALUE) return;
++rpc->nonce;
_snprintf(command, sizeof(command),
"{\"cmd\":\"SET_ACTIVITY\",\"args\":{\"pid\":%lu,\"activity\":null},"
"\"nonce\":\"%lu\"}",
(unsigned long)pid, rpc->nonce);
rpc_write_packet(rpc->pipe, IPC_OPCODE_FRAME, command);
rpc_read_packet(rpc->pipe, response, sizeof(response));
}
int WINAPI WinMain(HINSTANCE instance, HINSTANCE prev, LPSTR cmdline, int show) {
char application_id[64] = {0};
DiscordRpc rpc;
HANDLE wow_process = NULL;
DWORD pid = 0;
DWORD requested_pid = 0;
int have_id;
int wait_loops = 0;
long long session_start = 0;
(void)instance; (void)prev; (void)show;
memset(&rpc, 0, sizeof(rpc));
rpc.pipe = INVALID_HANDLE_VALUE;
rpc.nonce = (unsigned long)GetTickCount();
have_id = load_application_id(application_id, sizeof(application_id));
if (!have_id) {
log_line("Built-in Discord Application ID is invalid; presence is disabled.");
}
/* Normal path: DiscordPresence.dll passes the exact WoW process id.
* The name scan remains only as a harmless manual/debug fallback. */
requested_pid = parse_target_pid(cmdline);
pid = requested_pid;
while (!pid && wait_loops++ < 25) {
pid = find_process("WoW_Modernized.exe");
if (!pid) Sleep(200);
}
if (!pid) {
log_line("No running WoW_Modernized.exe found; DiscordPresence exiting.");
return 0;
}
wow_process = OpenProcess(SYNCHRONIZE, FALSE, pid);
if (!wow_process) {
log_line("Could not open the target WoW process; DiscordPresence exiting.");
return 0;
}
log_line("WoW_Modernized.exe detected.");
/* Fixed for the lifetime of this WoW process. Text updates, character
* changes and loading states reuse the same Discord elapsed-time origin. */
session_start = (long long)time(NULL);
while (WaitForSingleObject(wow_process, 0) == WAIT_TIMEOUT) {
Status status;
char activity[768];
if (have_id && rpc.pipe == INVALID_HANDLE_VALUE)
rpc_connect(&rpc, application_id);
if (have_id && rpc.pipe != INVALID_HANDLE_VALUE) {
if (load_status(&status)) {
format_activity(&status, activity, sizeof(activity), session_start);
rpc_set_activity(&rpc, pid, activity);
} else {
/* Keep only the Discord application header while the game is
* loading, on character select, or when the snapshot is stale.
* This also prevents old character/zone text from lingering. */
_snprintf(activity, sizeof(activity),
"{\"timestamps\":{\"start\":%lld}}", session_start);
activity[sizeof(activity) - 1] = 0;
rpc_set_activity(&rpc, pid, activity);
}
}
Sleep(TICK_MS);
}
if (rpc.pipe != INVALID_HANDLE_VALUE) {
rpc_clear(&rpc, pid);
rpc_close(&rpc);
}
CloseHandle(wow_process);
log_line("WoW stopped; DiscordPresence exiting.");
return 0;
}