31 Commits

Author SHA1 Message Date
Dusk-92 09d3d7f34b release v1.3 2026-09-02 15:54:32 +02:00
Dusk-92 86860a7fad Prepare WowPresence v1.3 release notes 2026-09-02 15:50:52 +02:00
Dusk-92 9dfe3679d7 Prepare WowPresence v1.3 workflow 2026-09-02 15:50:37 +02:00
Dusk-92 08e096a8b6 Document Discord privacy flag presets 2026-09-02 15:26:51 +02:00
Dusk-92 10e25cc25f Omit empty Discord activity fields 2026-09-02 15:20:04 +02:00
Dusk-92 d4c4f0eef3 Publish Discord details test prerelease 2026-09-02 15:07:51 +02:00
Dusk-92 c4d0d5fee6 Default test broadcast mask to all details 2026-09-02 15:07:29 +02:00
Dusk-92 f8886e15aa Accept any shared character detail 2026-09-02 15:07:20 +02:00
Dusk-92 35a94d6b49 Add independent Race broadcast flag 2026-09-02 15:07:07 +02:00
Dusk-92 9f50681798 Clarify Discord Application ID setup wording 2026-09-02 09:28:30 +02:00
Dusk-92 699b158fdf Move OctoWoW Application ID below example 2026-09-02 09:27:32 +02:00
Dusk-92 05376fabd2 Link Modernization Tool in README 2026-09-02 09:16:18 +02:00
Dusk-92 6f11db08d7 Clarify release asset in README 2026-09-02 09:13:36 +02:00
Dusk-92 ee71b4364c release v1.2 2026-09-02 09:10:52 +02:00
Dusk-92 f2623c33fd Use curated v1.2 release notes 2026-09-02 09:10:43 +02:00
Dusk-92 a63a7b5a48 release v1.2 2026-09-02 09:09:47 +02:00
Dusk-92 a4e0b7afcf Update v1.2 release notes 2026-09-02 09:09:38 +02:00
Dusk-92 05dc4094a0 Publish only the WowPresence ZIP release asset 2026-09-02 09:06:14 +02:00
Dusk-92 e27c7334bc release v1.2 2026-09-02 09:03:41 +02:00
Dusk-92 fdb3126d1f Improve WoW process detection logging 2026-09-02 09:00:56 +02:00
Dusk-92 4576605dce Document OctoWoW Discord Application ID 2026-09-01 20:10:15 +02:00
Dusk-92 e393a7999b release v1.2 2026-09-01 16:51:22 +02:00
Dusk-92 8048e804f6 Prepare WowPresence v1.2 release 2026-09-01 16:51:05 +02:00
Dusk-92 3c76de9bdb Document ClassicAPI technical reference 2026-09-01 16:50:55 +02:00
Dusk-92 0994fe4e08 Document AreaTable zone resolution 2026-09-01 16:50:43 +02:00
Dusk-92 f261c3be29 Resolve zones through AreaTable 2026-09-01 16:50:09 +02:00
Dusk-92 750fe8a2e5 release v1.1 2026-09-01 16:18:30 +02:00
Dusk-92 aa9412d7c2 Prepare WowPresence v1.1 release 2026-09-01 16:18:19 +02:00
Dusk-92 d0aff5e95a Document Modernization Tool data path 2026-09-01 16:14:15 +02:00
Dusk-92 2a10763dac Support Modernization Tool data directory 2026-09-01 16:13:59 +02:00
Dusk-92 9449080788 Support Modernization Tool data directory 2026-09-01 16:13:53 +02:00
7 changed files with 323 additions and 102 deletions
+5 -9
View File
@@ -72,20 +72,18 @@ jobs:
dist/WowPresence.zip
if-no-files-found: error
- name: Create GitHub Release v1.0
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.event.head_commit.message == 'release v1.0' }}
- name: Create GitHub Release v1.3
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.event.head_commit.message == 'release v1.3' }}
uses: softprops/action-gh-release@v3
with:
tag_name: v1.0
tag_name: v1.3
target_commitish: ${{ github.sha }}
files: |
dist/WowPresence.dll
dist/WowPresence.exe
dist/WowPresence.zip
fail_on_unmatched_files: true
name: "WowPresence v1.0"
name: "WowPresence v1.3"
body_path: RELEASE_NOTES.md
generate_release_notes: true
generate_release_notes: false
make_latest: true
- name: Create GitHub Release
@@ -93,8 +91,6 @@ jobs:
uses: softprops/action-gh-release@v3
with:
files: |
dist/WowPresence.dll
dist/WowPresence.exe
dist/WowPresence.zip
fail_on_unmatched_files: true
name: "WowPresence ${{ github.ref_name }}"
+90 -12
View File
@@ -44,11 +44,18 @@ After extraction, the relevant files should look like this:
The runtime JSON and log files are created automatically inside the same `WowPresence` folder.
When WowPresence is installed through the **[Modernization Tool](https://github.com/Dusk-92/Modernization-Tool)**, the same binaries automatically use:
```text
<WoW folder>\.modernization_tool\WowPresence\
```
instead. No separate build is required.
## 🛰️ Discord Application ID
WowPresence intentionally does **not** include a server-specific Discord Application ID.
To configure your own Discord Application ID:
To create yours:
1. Open the **Discord Developer Portal**: https://discord.com/developers/applications
2. Create a **New Application**.
@@ -67,6 +74,14 @@ Example:
123456789012345678
```
For **OctoWoW**, you can use the preconfigured Discord Application ID:
```text
1544072796098011176
```
This is the same Application ID automatically configured by **Modernization Tool**.
No Discord Client Secret, bot token or bot account is used or required.
If `discord_application_id` is missing or invalid, Rich Presence remains disabled and the reason is written to `WowPresence\WowPresence.log`.
@@ -76,7 +91,7 @@ If `discord_application_id` is missing or invalid, Rich Presence remains disable
- Character name and guild display.
- Level and class display.
- Race and faction display.
- Current zone display.
- Current zone display resolved from the client AreaTable instead of heuristic text-address scanning.
- Stable elapsed-session timer across character changes.
- Automatic companion startup.
- Exact WoW process tracking.
@@ -111,16 +126,21 @@ Other modified 1.12.1 clients should be considered **untested until verified**.
WoW
└─ WowPresence.dll
├─ read-only character sampling
├─ Area ID → AreaTable zone resolution
├─ WowPresence\discord_wow_status.json
└─ starts WowPresence.exe
└─ Discord local IPC
```
Zone names are resolved from the player's current AreaTable ID, matching the client's own zone data and avoiding internal map tokens such as `H32D` or `HLVA`.
The DLL does not communicate directly with Discord.
The companion executable does not read or write WoW memory.
Runtime files are kept together in:
Runtime files are kept together in one of these locations:
**Standalone installation:**
```text
<WoW folder>\WowPresence\
@@ -130,29 +150,87 @@ Runtime files are kept together in:
└─ WowPresence.log
```
## 🔒 Privacy flags
**Installed through Modernization Tool:**
The included file:
```text
<WoW folder>\.modernization_tool\WowPresence\
├─ discord_application_id
├─ discord_broadcast_flags
├─ discord_wow_status.json
└─ WowPresence.log
```
If the Modernization Tool managed folder exists, WowPresence automatically prefers it. Otherwise it uses the standalone `WowPresence` folder.
## 🔒 Discord Rich Presence privacy
WowPresence lets you choose exactly which character details are shown on Discord.
For a standalone installation, edit:
```text
WowPresence\discord_broadcast_flags
```
controls which character details are published to Discord.
When WowPresence is installed through **Modernization Tool**, the equivalent file is stored in:
```text
.modernization_tool\WowPresence\discord_broadcast_flags
```
Modernization Tool provides checkboxes for these options, so manual bitmask editing is normally only needed for standalone WowPresence installations.
### Available fields
Each field has its own value:
| Value | Field |
| ---: | --- |
| 1 | Name |
| 1 | Character Name |
| 2 | Guild |
| 4 | Faction |
| 8 | Class |
| 16 | Level |
| 32 | Zone |
| 63 | All fields |
| 64 | Race |
The release package uses `63` by default.
To display multiple fields, **add their values together** and put the result in `discord_broadcast_flags`.
If the file is deleted, WowPresence also defaults to all supported fields.
### Examples
| Display | Value |
| --- | ---: |
| Nothing | 0 |
| Character Name only | 1 |
| Faction + Class | 12 |
| Race only | 64 |
| Race + Faction + Class | 76 |
| Character Name + Level + Zone | 49 |
| All character details | 127 |
Example — to display **Race + Faction + Class**:
```text
Race = 64
Faction = 4
Class = 8
64 + 4 + 8 = 76
```
Then set:
```text
76
```
inside `discord_broadcast_flags`.
The default configuration uses `127`, which enables all supported character details.
WowPresence reads this setting while the game is running, so changes should normally appear on Discord automatically within a few seconds.
If the file is missing or contains an invalid value, WowPresence falls back to all supported fields.
## 🛡️ Administrator privileges
@@ -181,7 +259,7 @@ GitHub Actions builds:
The ZIP includes the ready-to-edit `WowPresence` configuration folder.
Tags matching `v*` can publish these files as a GitHub Release.
Tags matching `v*` publish `WowPresence.zip` as the GitHub Release asset.
## 📜 Project identity & licensing
+18 -21
View File
@@ -1,25 +1,22 @@
# WowPresence v1.0
# WowPresence v1.3
First standalone release of WowPresence.
## What's New
- Added independent privacy control for:
- Character Name
- Guild
- Race
- Faction
- Class
- Level
- Zone
- Added a dedicated Race flag.
- Full detail mask is now `127`.
- Fixed Discord Rich Presence combinations that previously failed when Name, Zone or Level were not shared.
- Race is no longer always shown and can now be disabled independently.
- Improved handling of empty Discord Rich Presence fields.
- Updated README with standalone privacy configuration examples.
## Included
- WowPresence.dll
- WowPresence.exe
- WowPresence.zip ready-to-install package
## Highlights
- Native Discord Rich Presence for WoW 1.12.1 build 5875 compatible clients
- Character name, guild, level, class, race, faction and zone display
- Stable elapsed-session timer across character changes
- Read-only game-memory sampling
- Separate native Discord IPC companion
- Configurable Discord Application ID through `WowPresence\discord_application_id`
- Privacy flags through `WowPresence\discord_broadcast_flags`
- Standalone `WowPresence\` runtime folder
- Automatic companion startup and exact WoW process tracking
## Installation
Extract `WowPresence.zip` into the WoW folder, edit `WowPresence\discord_application_id`, add `WowPresence.dll` to VanillaFixes `dlls.txt`, then launch WoW normally.
- `WowPresence.zip` — ready-to-install package containing `WowPresence.dll`, `WowPresence.exe`, and the WowPresence configuration folder.
+9
View File
@@ -16,6 +16,15 @@ The current WowPresence implementation is maintained separately and has been ind
At the historical IchaLaunch revision reviewed during development, no explicit LICENSE, LICENSE.md, or COPYING file was present. This notice therefore records provenance and credit; it should not be interpreted as granting rights on behalf of the IchaLaunch author.
## ClassicAPI technical reference
AreaTable and current-player-area client layout details were cross-checked against:
- Project: ClassicAPI
- Repository: https://github.com/brues-code/ClassicAPI
ClassicAPI is used as a technical/reference source for documented WoW 1.12.1 client structures. WowPresence does not require, load, or call ClassicAPI.
## World of Warcraft client compatibility data
WowPresence uses client-layout information and memory offsets associated with World of Warcraft 1.12.1 build 5875. Such compatibility information has been documented in multiple community projects over the years.
+1 -1
View File
@@ -1 +1 @@
63
127
+149 -49
View File
@@ -3,16 +3,18 @@
* Read-only WoW 1.12.1 (build 5875) status sampler for WowPresence.
*
* Runtime contract:
* <game>\WowPresence\discord_wow_status.json
* <game>\WowPresence\discord_broadcast_flags
* standalone: <game>\WowPresence\...
* Modernization Tool: <game>\.modernization_tool\WowPresence\...
*
* Discord IPC is intentionally out of process. After the first sample, this
* DLL starts WowPresence.exe from its worker thread and passes the exact
* WoW process id. No process launch is performed from DllMain.
*
* This implementation is organized independently around a small memory-view
* layer and a snapshot serializer. Client addresses are the community-known
* WoW 1.12.1 / build 5875 values used for compatibility with the target client.
* layer and a snapshot serializer. Zone names are resolved from the player's
* current AreaTable ID instead of guessing among raw text addresses. Client
* addresses are community-documented WoW 1.12.1 / build 5875 compatibility
* values used by the target client.
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
@@ -24,6 +26,16 @@
#define CLIENT_IMAGE_BASE 0x00400000u
#define CLIENT_OBJECT_MANAGER_VA 0x00B41414u
#define CLIENT_IN_WORLD_VA 0x00B4B424u
#define CLIENT_PLAYER_AREA_ID_VA 0x00B4E314u
#define AREATABLE_RECORDS_VA 0x00C0E048u
#define AREATABLE_COUNT_VA 0x00C0E04Cu
#define LOCALE_INDEX_VA 0x00C0E080u
#define AREATABLE_PARENT_ID_OFF 0x08u
#define AREATABLE_NAMES_OFF 0x2Cu
#define AREATABLE_LOCALE_SLOTS 9u
#define AREATABLE_MAX_RECORDS 65535u
#define OM_FIRST_OBJECT_OFF 0xACu
#define OM_LOCAL_GUID_OFF 0xC0u
#define OBJECT_NEXT_OFF 0x3Cu
@@ -63,17 +75,13 @@
#define SHARE_CLASS 8u
#define SHARE_LEVEL 16u
#define SHARE_ZONE 32u
#define SHARE_ALL 63u
#define SHARE_RACE 64u
#define SHARE_ALL 127u
static const uintptr_t kPlayerNameLocations[] = {
0x00C27FC8u, 0x00C27D88u, 0x00C27FD8u, 0
};
static const uintptr_t kZoneNameLocations[] = {
0x00B4B404u, 0x00B4B424u, 0x00CE06D0u,
0x00CE06F8u, 0x00B4B3C8u, 0
};
typedef struct MemoryView {
HANDLE process;
uintptr_t module_base;
@@ -233,45 +241,114 @@ static int valid_display_text(const char *text, size_t max_length) {
return 1;
}
static int looks_like_internal_zone_code(const char *text) {
size_t i, length;
int has_letter = 0;
int has_digit = 0;
if (!text) return 0;
length = strlen(text);
/* Some 1.12-compatible clients expose compact internal area tokens
* (for example "H32D") at one of the candidate zone addresses. These
* are identifiers, not user-facing zone names. Keep the rule narrow:
* short mixed letter/digit strings with no separators are rejected,
* while legitimate names such as "Area 52" remain valid. */
if (length < 2u || length > 8u) return 0;
for (i = 0u; i < length; ++i) {
char ch = text[i];
if (ascii_letter(ch)) {
has_letter = 1;
} else if (ch >= '0' && ch <= '9') {
has_digit = 1;
} else {
return 0;
}
}
return has_letter && has_digit;
}
static int valid_zone_name(const char *text) {
if (!valid_display_text(text, ZONE_LIMIT)) return 0;
return !looks_like_internal_zone_code(text);
}
static int valid_guild_name(const char *text) {
if (!valid_display_text(text, GUILD_LIMIT)) return 0;
return _stricmp(text, "none") != 0;
}
static int valid_area_name(const char *text) {
size_t i, length;
int has_letter = 0;
if (!text) return 0;
length = strlen(text);
if (length < 2u || length > ZONE_LIMIT) return 0;
for (i = 0u; i < length; ++i) {
if (ascii_letter(text[i])) has_letter = 1;
}
return has_letter;
}
static int client_u32(const MemoryView *view, uintptr_t vanilla_va, uint32_t *value) {
uintptr_t address = client_address(view, vanilla_va);
return address && memory_u32(view, address, value);
}
static int area_record(const MemoryView *view, uint32_t area_id, uint32_t *record) {
uint32_t count = 0, records = 0, candidate = 0;
uintptr_t slot;
if (!view || !record || !area_id) return 0;
if (!client_u32(view, AREATABLE_COUNT_VA, &count) ||
!count || count > AREATABLE_MAX_RECORDS || area_id > count) {
return 0;
}
if (!client_u32(view, AREATABLE_RECORDS_VA, &records) ||
!user_address((uintptr_t)records)) {
return 0;
}
slot = (uintptr_t)records + (uintptr_t)area_id * sizeof(uint32_t);
if (!memory_u32(view, slot, &candidate) ||
!user_address((uintptr_t)candidate)) {
return 0;
}
*record = candidate;
return 1;
}
static int area_name_from_record(const MemoryView *view, uint32_t record,
char *output, size_t output_size) {
uint32_t locale = 0;
unsigned pass;
if (!view || !user_address((uintptr_t)record) || !output || output_size < 2u)
return 0;
output[0] = 0;
if (!client_u32(view, LOCALE_INDEX_VA, &locale) ||
locale >= AREATABLE_LOCALE_SLOTS) {
locale = 0;
}
/* Try the active client locale first. If that string is unavailable or
* not representable by the current ASCII JSON path, fall back to another
* populated locale slot rather than publishing an internal map token. */
for (pass = 0u; pass < AREATABLE_LOCALE_SLOTS; ++pass) {
unsigned index = pass == 0u ? (unsigned)locale : pass - 1u;
uint32_t name_ptr = 0;
uintptr_t pointer_slot;
if (pass > 0u && index >= locale) ++index;
if (index >= AREATABLE_LOCALE_SLOTS) continue;
pointer_slot = (uintptr_t)record + AREATABLE_NAMES_OFF +
(uintptr_t)index * sizeof(uint32_t);
if (!memory_u32(view, pointer_slot, &name_ptr) ||
!user_address((uintptr_t)name_ptr)) {
continue;
}
if (memory_ascii(view, (uintptr_t)name_ptr, output, output_size, ZONE_LIMIT) &&
valid_area_name(output)) {
return 1;
}
output[0] = 0;
}
return 0;
}
static int read_player_zone(const MemoryView *view, char *output, size_t output_size) {
uint32_t raw_area = 0, area_id, record = 0, parent_id = 0, parent_record = 0;
if (!view || !output || output_size < 2u) return 0;
output[0] = 0;
if (!client_u32(view, CLIENT_PLAYER_AREA_ID_VA, &raw_area)) return 0;
area_id = raw_area & 0xFFFFu;
if (!area_id || !area_record(view, area_id, &record)) return 0;
/* GetRealZoneText resolves sub-areas to their enclosing AreaTable zone.
* Mirror that behavior when a valid parent row is available. */
if (memory_u32(view, (uintptr_t)record + AREATABLE_PARENT_ID_OFF, &parent_id) &&
parent_id && area_record(view, parent_id, &parent_record)) {
record = parent_record;
}
return area_name_from_record(view, record, output, output_size);
}
static int client_is_in_world(const MemoryView *view) {
uint32_t value = 0;
return client_u32(view, CLIENT_IN_WORLD_VA, &value) && (value & 0xFFu) != 0u;
}
static int text_from_location(const MemoryView *view, uintptr_t vanilla_va,
char *output, size_t output_size, size_t max_chars,
int (*validator)(const char *)) {
@@ -510,9 +587,8 @@ static void collect_character_snapshot(CharacterSnapshot *snapshot) {
have_name = first_text_match(&view, kPlayerNameLocations, snapshot->name,
sizeof(snapshot->name), NAME_LIMIT, valid_player_name);
have_zone = first_text_match(&view, kZoneNameLocations, snapshot->zone,
sizeof(snapshot->zone), ZONE_LIMIT, valid_zone_name);
snapshot->in_world = have_name && have_zone;
have_zone = read_player_zone(&view, snapshot->zone, sizeof(snapshot->zone));
snapshot->in_world = client_is_in_world(&view) && have_name && have_zone;
now = GetTickCount();
if (!snapshot->in_world) {
@@ -541,11 +617,34 @@ static int game_folder(char *output, size_t output_size) {
return 1;
}
static int directory_exists(const char *path) {
DWORD attrs;
if (!path || !path[0]) return 0;
attrs = GetFileAttributesA(path);
return attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY);
}
static int support_folder(char *output, size_t output_size) {
char root[MAX_PATH];
char root[MAX_PATH], managed[MAX_PATH];
int count;
if (!game_folder(root, sizeof(root))) return 0;
/* Modernization Tool creates this directory before WoW starts. Prefer it
* when present so the same binaries can use the tool-managed location. */
count = _snprintf(
managed,
sizeof(managed),
"%s\\.modernization_tool\\WowPresence",
root
);
managed[sizeof(managed) - 1] = 0;
if (count >= 0 && (size_t)count < sizeof(managed) && directory_exists(managed)) {
if (strlen(managed) + 1u > output_size) return 0;
lstrcpynA(output, managed, (int)output_size);
return 1;
}
/* Standalone installs keep their data in <game>\\WowPresence. */
count = _snprintf(output, output_size, "%s\\WowPresence", root);
if (output_size) output[output_size - 1] = 0;
if (count < 0 || (size_t)count >= output_size) return 0;
@@ -653,7 +752,8 @@ static void publish_character_snapshot(void) {
collect_character_snapshot(&snapshot);
mask = load_share_mask();
race_text = snapshot.in_world ? label_for_id(kRaceLabels, snapshot.race_id) : "";
race_text = (snapshot.in_world && (mask & SHARE_RACE))
? label_for_id(kRaceLabels, snapshot.race_id) : "";
class_text = (snapshot.in_world && (mask & SHARE_CLASS))
? label_for_id(kClassLabels, snapshot.class_id) : "";
faction_text = (snapshot.in_world && (mask & SHARE_FACTION))
+51 -10
View File
@@ -3,9 +3,9 @@
* Invisible companion process for WowPresence.
*
* - is started automatically by WowPresence.dll after WoW is running
* - reads WowPresence\discord_wow_status.json
* - reads the standalone WowPresence folder or the Modernization Tool managed folder
* - publishes Discord Rich Presence over the local Discord IPC named pipe
* - exits when WoW_Modernized.exe exits
* - exits when the target WoW process exits
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
@@ -54,9 +54,30 @@ static int own_directory(char *out, size_t out_size) {
return 1;
}
static int directory_exists(const char *path) {
DWORD attrs;
if (!path || !path[0]) return 0;
attrs = GetFileAttributesA(path);
return attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY);
}
static int support_directory(char *out, size_t out_size) {
char root[MAX_PATH];
char root[MAX_PATH], managed[MAX_PATH];
if (!own_directory(root, sizeof(root))) return 0;
_snprintf(
managed,
sizeof(managed),
"%s\\.modernization_tool\\WowPresence",
root
);
managed[sizeof(managed) - 1] = 0;
if (directory_exists(managed)) {
if (strlen(managed) + 1 >= out_size) return 0;
lstrcpynA(out, managed, (int)out_size);
return 1;
}
if (strlen(root) + strlen("\\WowPresence") + 1 >= out_size) return 0;
_snprintf(out, out_size, "%s\\WowPresence", root);
out[out_size - 1] = 0;
@@ -140,7 +161,7 @@ static int load_application_id(char *out, size_t out_size) {
if (!support_file(path, sizeof(path), "discord_application_id") ||
!read_text_file(path, configured_id, sizeof(configured_id))) {
log_line("discord_application_id is missing. Add your Discord Application ID to WowPresence\\discord_application_id.");
log_line("discord_application_id is missing. Add your Discord Application ID to the active WowPresence data folder.");
return 0;
}
@@ -272,7 +293,9 @@ static int load_status(Status *status) {
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;
return status->name[0] || status->zone[0] || status->level > 0 ||
status->guild[0] || status->faction[0] || status->class_name[0] ||
status->race[0];
}
static int rpc_read_packet(HANDLE pipe, char *payload, size_t payload_size) {
@@ -381,9 +404,26 @@ static void format_activity(const Status *s, char *out, size_t out_size, long lo
lstrcpynA(details, extra, sizeof(details));
}
_snprintf(out, out_size,
"{\"details\":\"%s\",\"state\":\"%s\",\"timestamps\":{\"start\":%lld}}",
details, s->zone, session_start);
/* Discord rejects empty string values for Rich Presence text fields.
* Omit a field entirely when the user did not choose anything that maps
* to it, so Race/Class/Faction/Guild-only and Zone-only combinations work. */
if (details[0] && s->zone[0]) {
_snprintf(out, out_size,
"{\"details\":\"%s\",\"state\":\"%s\",\"timestamps\":{\"start\":%lld}}",
details, s->zone, session_start);
} else if (details[0]) {
_snprintf(out, out_size,
"{\"details\":\"%s\",\"timestamps\":{\"start\":%lld}}",
details, session_start);
} else if (s->zone[0]) {
_snprintf(out, out_size,
"{\"state\":\"%s\",\"timestamps\":{\"start\":%lld}}",
s->zone, session_start);
} else {
_snprintf(out, out_size,
"{\"timestamps\":{\"start\":%lld}}",
session_start);
}
out[out_size - 1] = 0;
}
@@ -449,10 +489,11 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE prev, LPSTR cmdline, int show)
while (!pid && wait_loops++ < 25) {
pid = find_process("WoW_Modernized.exe");
if (!pid) pid = find_process("WoW.exe");
if (!pid) Sleep(200);
}
if (!pid) {
log_line("No running WoW_Modernized.exe found; WowPresence exiting.");
log_line("No running WoW process found; WowPresence exiting.");
return 0;
}
@@ -462,7 +503,7 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE prev, LPSTR cmdline, int show)
return 0;
}
log_line("WoW_Modernized.exe detected.");
log_line("WoW process 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);