darpcd.exe
The daemon is the normal starting point for dashboards, scripts, and tools that work with one or more game clients. This chapter covers running and configuring it. Use Web API for HTTP routes and Live events for the streaming interface.
Status: Automatic client discovery, the identity registry, daemon-managed load, unload, and launch, current client state, routed movement commands, REST, and Server-Sent Events are implemented.
darpcd.exe is a 64-bit x86-64 Windows daemon that makes injected clients easy
to use from local applications.
Its current responsibilities are to:
- Discover supported game client windows and their deterministic daRPC pipes.
- Track uninjected processes as loader candidates.
- Connect and reconnect to available
darpc.dllinstances. - Invoke the configured
loader.exefor explicit lifecycle operations. - Optionally load the configured DLL once into each uninjected client.
- Aggregate client identity, connection health, snapshots, and ordered state updates from each connected client.
- Route bounded commands through each client’s existing pipe session.
- Expose default-loopback REST and Server-Sent Events APIs, an OpenAPI document, and Swagger UI, with an explicit IPv4 bind for trusted networks.
Additional game actions can build on this boundary later. REST provides bounded requests and responses for those actions, while Server-Sent Events provide the live update stream. The daemon retains observations but is not the authority for client memory or local state.
Command-line reference
darpcd.exe [--pid <pid> ...] [--port <port> | --listen <ipv4[:port]>]
[--auto-load] [--managed]
[--loader-path <path>] [--dll-path <path>] [--maps-path <path>]
darpcd.exe --print-openapi
| Flag | Meaning |
|---|---|
--pid <pid> | Retain a specific process as a controlled target. Repeat the flag for multiple clients. Normal window discovery remains active. |
--port <port> | Listen on this TCP port instead of 2626. The listener remains bound to 127.0.0.1. |
--listen <ipv4[:port]> | Bind to an explicit IPv4 interface and optional port. An omitted port defaults to 2626. This flag cannot be combined with --port. |
--auto-load | Use the configured loader and DLL once for each discovered, supported client that is not already loaded. |
--managed | Treat standard input as a parent-owned lifetime pipe and shut down normally when it reaches end-of-file. |
--loader-path <path> | Use this loader.exe for managed load, unload, launch, and automatic loading. The default is loader.exe beside the daemon. |
--dll-path <path> | Use this darpc.dll for managed load, launch, and automatic loading. The default is darpc.dll beside the daemon. |
--maps-path <path> | Override the automatically discovered local client Maps directory used by GET /maps/{map_id}/download. The path must name an existing directory. |
--print-openapi | Print the OpenAPI 3.1 document as JSON and exit. This standalone flag cannot be combined with server flags. |
Paths containing spaces must be quoted. Repeating a single-value flag or using an unknown flag is an error. The daemon reports startup failures on standard error and exits nonzero.
Start normal discovery and serve the API on the default loopback address:
darpcd.exe
Allow a host or another virtual machine on a trusted network to reach the API:
darpcd.exe --listen 0.0.0.0:2626
0.0.0.0 binds every IPv4 interface. Prefer the VM’s specific IPv4 address
when practical, and restrict the port with Windows Firewall. The API has no
authentication or Transport Layer Security (TLS), so every host that can reach
the listener can read state and submit actions.
Automatically load uninjected supported clients and select explicit runtime files:
darpcd.exe --auto-load --loader-path "C:\daRPC\loader.exe" --dll-path "C:\daRPC\darpc.dll"
Run the daemon as a child owned by another program:
darpcd.exe --managed --auto-load
The parent must start the child with piped standard input and retain the sole
write end. The daemon ignores bytes written to the pipe; they are not commands.
Closing the write end, including when the parent exits unexpectedly, produces
end-of-file and requests a normal daemon shutdown. The daemon stops discovery
and new HTTP connections, closes active Server-Sent Events streams, gives
other in-flight HTTP requests a bounded drain period, and exits successfully.
It does not stop Darkages.exe, unload darpc.dll, or detach clients.
Without --managed, the daemon never reads standard input. --managed cannot
be combined with --print-openapi, and there is no HTTP shutdown route. A
parent that launches additional child processes must ensure they do not inherit
another copy of the lifetime pipe’s write end, because any remaining writer
delays end-of-file.
Override the local client’s automatically discovered map directory:
darpcd.exe --maps-path "C:\Dark Ages\Maps"
Without the flag, the daemon adopts the Maps directory beside the first
discovered Darkages.exe, including a client discovered after daemon startup.
Until a client or override supplies a directory, map downloads return 404.
The daemon fails at startup if an explicit override is missing or is not a
directory.
Export the same OpenAPI document that the running daemon serves at
/openapi.json:
darpcd.exe --print-openapi > openapi.json
Discovery and registry
Start the daemon without a PID to discover clients from their verified
Darkages top-level window class:
darpcd.exe
darpcd.exe --port 3626
darpcd.exe --auto-load
Repeat --pid <pid> to retain additional controlled targets or processes that
do not expose the normal game window. Explicit and discovered targets use the
same independent connection workers:
darpcd.exe --pid 3780 --pid 6648
The daemon’s client roster owns the membership of explicit, discovered, and recently launched targets together with their registry records and connection workers. Membership is tracked independently from worker availability. A worker startup failure therefore remains retryable while the target is desired, and the target can still be removed cleanly after it disappears. Removal stops the worker before deleting the public registry record, and daemon shutdown signals every remaining worker.
Each worker retries a missing or busy pipe, performs the shared controller
handshake, requests a fresh snapshot, long-polls bounded state-event batches,
and sends a periodic Ping to detect a broken connection.
An accepted release connection must report the supported x86 architecture,
executable fingerprint, and client version. Registry identity combines the PID, raw
process creation time, and DLL instance ID. A reused PID or reloaded DLL
therefore replaces the prior record instead of inheriting it.
Each worker also owns a bounded command receiver. HTTP requests carry the expected process and DLL identity, and the worker rejects a request after a replacement or disconnect. It processes at most one routed command between normal event polls, assigns protocol request IDs on the owning session, and never opens a competing pipe connection. A full worker queue affects only that client.
An incompatible peer remains visible as a target status but is not accepted as a client and is never reinjected automatically. A discovered target is removed after its game window disappears. An explicit PID remains configured until the daemon exits.
--auto-load applies the configured loader and DLL to every not_loaded
target once per tracked process. This includes targets present at daemon startup
and targets discovered later. Connecting, connected, busy, initializing, and
incompatible targets are not injected. Each target is handled independently,
so one validation or loader failure does not stop discovery or other clients.
Automatic loading records the attempt before starting the loader. It therefore
does not retry on every discovery pass, and an explicit unload remains unloaded
for the rest of that tracked process lifetime. Removing and rediscovering the
process, or restarting the daemon with --auto-load, makes it eligible again.
Managed lifecycle
The loader and DLL paths default to loader.exe and darpc.dll beside
darpcd.exe. Override those server-side paths when the artifacts live
elsewhere:
darpcd.exe --loader-path <loader.exe> --dll-path <darpc.dll>
These paths are also used by --auto-load; the flag never accepts a different
DLL or bypasses normal loader validation.
Each launch request supplies the full executable path for the intended
installation. The daemon assumes no client base directory; loader.exe uses
the executable’s parent directory as the launched process working directory.
The HTTP API can load the configured DLL into a discovered PID, unload it, or
launch the requested executable suspended and initialize the DLL before the
client resumes. loader.exe repeats architecture, DLL, and executable
validation for every operation. A window match or request path is only a
candidate signal.
Launch requests expose the client executable path and only the supported startup choices: allow multiple clients, skip the intro, skip the notice sequence, suppress terminal exchange alerts, and optionally select a server endpoint. The API never accepts arbitrary process arguments or request-selected loader and DLL paths.
The current console output reports transitions such as:
HTTP API listening on http://127.0.0.1:2626
client pid=3780 status=connecting
client pid=3780 status=not_loaded
client pid=3780 status=initializing
client pid=3780 status=connected creation_time=... instance=... protocol=1.3 ...
client pid=3780 status=disconnected instance=... reason="..."
client pid=3780 status=busy
client pid=3780 status=incompatible instance=... reason="..."
client pid=3780 status=removed
After the handshake, each worker requests a fresh snapshot and stores it with that client’s identity and connection metadata. Reconnecting after a daemon restart therefore reconstructs daemon state without reinjecting the DLL. The snapshot carries the event boundary it already represents. Consecutive absolute updates reduce into the retained state and appear in REST without another memory walk. A reported overflow or sequence or revision gap causes an immediate fresh snapshot.
The registry validates and reduces each observation once before REST or
Server-Sent Events can publish it. A rejected batch does not partially update
the retained state and none of its events reach subscribers. The last valid
snapshot remains available internally for recovery comparisons, but public
state routes return 503 Service Unavailable until the worker obtains and
commits a fresh snapshot.
Active spell effects are retained as a focused collection resource. Ordered add, remove, and relative-duration changes update that resource and the per-client event stream from the same event boundary.
Web interface
The HTTP server binds to 127.0.0.1:2626 by default. --port <port> overrides
the port while retaining the loopback boundary. --listen <ipv4[:port]>
explicitly selects another IPv4 interface for trusted VM or local-network use.
The generated OpenAPI document is served at /openapi.json, and the vendored
Swagger UI is served at /docs. HTTP models remain separate from registry and
binary protocol types.
Each connected client also exposes
GET /clients/{client}/events. The daemon subscribes before reading the
current registry snapshot, emits a stream.ready boundary, and then emits only
later state changes for that exact process and DLL identity. The internal
broadcast channel holds 4,096 events. A lagging subscriber receives
stream.resync_required and closes; it cannot block the game hook, client
worker, or another subscriber.
The same web boundary can submit, query, and cancel the no-op diagnostic command. A separate bounded daemon router wakes the registry loop, which sends the request to the matching per-client worker. The returned status includes the DLL instance ID, client tick timing, execution duration, and game main-thread ID.
Each connected worker also samples the existing tick-hook health counter once
per second. Three consecutive samples below 60 ticks per second produce one
tick_rate_degraded daemon log entry. The next healthy sample produces one
tick_rate_recovered entry, avoiding continuous warnings during one incident.
For DLL component 1.5.2 and later, the worker also queries hook timing once per
second. Disabled responses are silent. When an over-budget counter advances,
the daemon writes one hook_budget_exceeded entry with the client PID, stage,
budget, delta, total, maximum, and last duration. HTTP callers can query,
enable, disable, or reset the same counters without reconnecting the client.
See the Web API chapter for routes, request models, responses, and failure behavior.
Failure isolation
A daemon restart must not end a game session. The pipe closes when the daemon
stops, darpc.dll immediately returns to listening, and a replacement daemon
can reconnect without reinjection. One worker failure changes only that
target’s status and cannot terminate another worker or the daemon.
Lifecycle work runs outside the asynchronous HTTP executor. Connections, requests, DLL event storage, and daemon stream fanout are bounded so one slow API consumer or game client cannot starve the others.