Repository navigation
Conversation
…IPC pipe Shows the player's state in their Discord profile, e.g. "Lv 380 / ML 120 Blade Knight" / "In Blood Castle 5 · Party 3/5", with "Logging in" and "Selecting a character" outside the world. - Talks to the Discord app over `discord-ipc-N` (named pipe on Windows, Unix socket elsewhere, including the Flatpak and Snap locations) with the bundled json.hpp; no proprietary SDK. - A worker thread connects, handshakes, publishes and reconnects; the frame loop only reads the game state once a second and hands over the newest activity. Updates are paced to what Discord accepts (one per 4 s, newest wins). Nothing blocks or breaks when Discord isn't running. - Application id and image keys come from config.ini [Discord]; none is built in. Without an application id the presence stays off. - Compile switch ENABLE_DISCORD (default ON, forced OFF on Android/iOS); without it RichPresence.h compiles to a no-op. Part of #697. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
The options window gets a Discord row (On / Hide details / Off) below Windowed mode; it applies at once and is stored as [Discord] Presence. Builds without ENABLE_DISCORD keep the window as it was. docs/discord.md describes what is shown, the setting, and how a server operator sets up the Discord application and its images. Closes #697. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
sven-n
left a comment
There was a problem hiding this comment.
Part of a review pass over the whole Discord integration — this PR plus #707, #708 and #709 here, and the seven server PRs in MUnique/OpenMU.
This is the strongest code of the eleven PRs. The IPC layer in particular is written like someone who assumed the thing on the other end of the pipe might be hostile, which is the right assumption for a local socket that any process can bind:
DecodeHeaderrejects unknown opcodes and caps the payload at 64 KiB, and the comment inIpcFrame.hnames exactly the attack it's preventing ("trusting it would let it make the client allocate whatever it claims"). That's the one bug I went looking for first — a length-prefixed read from an untrusted peer with no cap — and it isn't there.ReadExacthandles partial reads, cancellation and deadlines separately, and a torn header closes the connection rather than being retried into a desynchronised stream.OpenEndpointchecks the path againstsizeof(sun_path)before thememcpy, setsFD_CLOEXEC, and usesSO_NOSIGPIPE/MSG_NOSIGNALso a dead Discord can't kill the client with a signal.- The
recvreturning 0 case is treated as the peer's shutdown rather than as a short read, with a comment saying so.
The threading is also clean: m_desired/m_desiredVersion are written under the lock from the caller, m_sentVersion is touched only by the worker, and the destructor sets m_stopping under the lock before joining. I couldn't find a race.
Two comments inline, both minor — one on fixed buffers, one on the default presence mode, which is a product decision rather than a defect.
Generated by Claude Code
- The presence texts are formatted into strings which grow with the translation (Integration::Discord::FormatWide) instead of fixed 128 character buffers, which cut longer translations silently. - HideDetails is the default: level and class of the character only go to Discord when the player chooses On. The docs say so, and that the character's name is never shown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
main replaced the options window with the RmlUi COptionWindow, so the Discord presence setting moves there: a dropdown (id 8) after the theme row in both themes, hidden in builds without the presence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Summary
The player's state now shows in their Discord profile, for example:
Outside the world it shows "Logging in" or "Selecting a character". The client talks to the local Discord app over its IPC pipe or socket (
discord-ipc-N), using the bundledjson.hpp. No proprietary SDK is shipped.How it works
PresenceClient): connects, does the handshake, sendsSET_ACTIVITY, answers pings, and reconnects every 15 s while Discord is down. It sends at most one update per 4 s, because Discord throttles faster updates. Changes that arrive in between are merged and only the newest is sent. Every wait has a time limit and can be cancelled, so shutdown and the Off setting take effect at once.RichPresence::Update): reads the game state once a second (scene, level and master level, class, map, party, events), builds the activity and passes it to the worker only when it changed. It never waits on Discord. Map changes, level-ups, party changes and entering or leaving events are all picked up by this once-a-second read. The control socket reads map changes the same way, instead of hooking packet handlers.config.ini [Discord]holdsPresence(On/HideDetails/Off),ApplicationId,LargeImageKeyandSmallImageKey. No application id is built in. Without one the presence stays off, because each server owns its own Discord application.ENABLE_DISCORD, following theENABLE_CONTROL_SOCKETpattern. It defaults to ON and is forced OFF on Android and iOS. When it is OFF,Integration/Discord/*.cppis not compiled,RichPresence.hcompiles to a no-op, and the options window keeps its old layout.XDG_RUNTIME_DIR,TMPDIR,TMP,TEMPor/tmp; on Linux the Flatpak and Snap locations are searched too.Game.en.resxandGame.de.resx. Other locales fall back to English.docs/discord.md, covering what is shown, the setting, and the server-operator setup. Build-option table and options-window doc updated.Code lives in
src/source/Integration/Discord/(namespaceIntegration::Discord), one class per file, so C2–C6 have a home.Tests:
tests/network/test_discord_presence.cpp, 16 cases:SET_ACTIVITYpayloads, including clearing the presence and invalid UTF-8"evt": nullreplies (this caught a crash on the worker thread)Verification
linux-x64Debug build: fullctestrun, 437/437 passed.MU_ENABLE_DISCORDundefined, the options window and Winmain compile cleanly. TheENABLE_DISCORD=OFFsource filter was checked to drop all 7.cppfiles.IpcConnection.cpp,_WIN32branch) has never been compiled or run. Please try it on Windows with an application id set.Related issues
Closes #697 (part of #696)
Checklist
docs/CODING_RULES.md.docs/build/README.md).🤖 Generated with Claude Code
https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj