Skip to main content
Minimal C ABI for engines that cannot call Rust directly. Build as cdylib or staticlib, include include/arcane_sdk.h, then init at launch — passing nothing but an error buffer. The client is a process-wide singleton — you init once and read from getters, with no handle to carry through C# or Blueprint. Error codes match Rust SdkError::code() — use Errors when debugging.
The header ships in the crate (include/arcane_sdk.h) and is attached to each GitHub Release. Regenerate after FFI changes (cbindgen 0.29.4):

Return conventions

Two conventions, by function kind. Both are in the header as named constants. Actions — arcane_sdk_init, arcane_sdk_refresh, arcane_sdk_set_graphics, arcane_sdk_achievement_unlock, arcane_sdk_lobby_invite, arcane_sdk_lobby_leave, arcane_sdk_lobby_close: Getters — the char *buf functions:

Actions

arcane_sdk_init

Checks ownership and builds the singleton. Call once at launch. You pass no id: Arcane Powered sets the game id of your title in ARCANE_GAME_ID and the signed-in account in ARCANE_USER_ID on the game process, and the SDK reads both. Set them yourself to run your build outside the launcher — see Local development. It takes no string argument, so it never returns 1: the result is 0 or 2. On 2, err_buf holds code: message — hint (context…). Parse up to the first : for the stable code — missing_game_id is what you get when nothing set ARCANE_GAME_ID. Errors: every code in the Errors table except not_initialized.

arcane_sdk_refresh

Re-runs the check against Arcane desktop and updates the singleton. Returns 1 if arcane_sdk_init has not succeeded. On failure the client keeps its previous state.

arcane_sdk_shutdown

Ends the play session — reporting the final playtime with a 2-second timeout — then drops the client. Call it when the game exits. It is also what you want on an editor play-mode reload, where the library stays loaded across runs.

arcane_sdk_frame

Counts one rendered frame, for FPS sampling. Call it once per frame from the render loop: outside a sampling window it is a relaxed atomic load, inside one it adds a relaxed increment. Does nothing before arcane_sdk_init succeeds, and cannot fail.

arcane_sdk_set_graphics

Records the current display settings — for example "2560x1440" and "high" — attached to the FPS samples that follow. Empty strings clear them. Returns 1 on a null / non-UTF-8 argument or before init. Takes a short lock, so never call it per frame.

arcane_sdk_achievement_unlock

Unlocks an achievement for the signed-in player. key is the key from the Arcane portal (NUL-terminated UTF-8). Idempotent — call it every time the condition holds; an already-unlocked or queued (desktop app offline) answer returns 0. One synchronous loopback call, so never call it from the render loop. Returns 1 on a null / non-UTF-8 key or before init, 2 on an SDK error written to err_buf — invalid_argument for a malformed key (keys are lowercase; raised before any call), unknown_achievement when the title does not define it. See Achievements.

arcane_sdk_achievement_is_unlocked

Reads the cache the last arcane_sdk_achievements_json filled — memory only, no call. Returns 1 (unlocked), 0 (locked), -1 (not initialised), -2 (null / non-UTF-8 key), or -4 when the list has never been loaded or did not carry that key.

arcane_sdk_lobby_invite, arcane_sdk_lobby_leave, arcane_sdk_lobby_close

Invite one friend to a lobby, leave one, or close one you host. Each is one synchronous loopback call — never from the render loop. Returns 1 on a null / non-UTF-8 id or before init, 2 on an SDK error written to err_buf: not_friends, lobby_not_found, lobby_closed, invalid_argument for a malformed id (raised before any call). close ends the lobby for everyone; its members get a lobby_closed event. There is no host migration.

Getters

arcane_sdk_ownership returns ARCANE_OWNERSHIP_OWNED (0) or ARCANE_OWNERSHIP_DRM_DISABLED (1). Both mean launch the game. arcane_sdk_last_error_json writes the last failure as JSON — {"code","message","hint","retryable","context"} — for engines that want to render full detail. It returns -4 when nothing has failed since the last success. arcane_sdk_session_json writes the play session state:
tracking is "active", "pending" or "disabled"; lobby_events is "off", "active" or "unavailable" — see Lobbies. arcane_sdk_achievements_json is the C form of the Rust list(): it makes one synchronous loopback call, writes every achievement of the title, and fills the cache arcane_sdk_achievement_is_unlocked reads. Call it on a loading or achievements screen, never per frame.
unlocked_at is a Unix timestamp, or null while locked. A -4 here means the call failed — read arcane_sdk_last_error_json for the code. arcane_sdk_friends_json is the C form of the Rust friends().list(): one synchronous loopback call returning this player’s friends with their presence. Call it when a friends menu opens or on a timer of your own, never per frame.
in_game is true for a friend playing this title right now — the one named in ARCANE_GAME_ID. stale is true when the desktop app answered from its cache because it is offline; the list is still usable. A -4 means the call failed — read arcane_sdk_last_error_json for the code.

Lobbies

arcane_sdk_lobby_create, arcane_sdk_lobby_join_code, arcane_sdk_lobby_join and arcane_sdk_lobby_get each make one synchronous loopback call and write the same lobby object — arcane_sdk_lobby_get reads one without joining or leaving anything:
visibility is ARCANE_LOBBY_FRIENDS (0), ARCANE_LOBBY_CODE (1) or ARCANE_LOBBY_FRIENDS_AND_CODE (2). payload_b64 is your connection blob already base64-encoded, at most 4096 raw bytes — a null pointer means no payload. Payloads come back base64 too; Arcane never reads them. join_code is null for a friends-only lobby and for a member who is not the host. A -2 means a bad argument — a payload that is not base64, an unknown visibility, a null or non-UTF-8 id. A -4 means the call failed: read arcane_sdk_last_error_json for lobby_not_found, lobby_full, lobby_closed, not_friends, or invalid_argument when the join code or payload was rejected before any call.
arcane_sdk_launch_join_code reads the launcher’s stashed code on the first call and caches it for the process; -4 when there is none, when the desktop app predates the route, or in offline-only mode.
arcane_sdk_lobby_events_json reads memory only — the arcane-session thread does the polling, armed by the first lobby call — and drains the queue only once the JSON is safely in your buffer. A -3 leaves every event queued, so retry with a bigger buffer and lose nothing. Event objects carry type first, then lobby_id, then the fields of that type: invite has join_code, from_user_id, pseudo; member_joined has user_id, pseudo, payload; member_left has user_id; lobby_closed has nothing more. A {"type":"resync"} event has no lobby_id at all: Arcane dropped events before this client fetched them, so re-read the lobbies you are in with arcane_sdk_lobby_get. Buffer sizing: user ids and device hashes fit comfortably in 64 bytes; allow 256 for the session JSON, 512 for err_buf, 1024 for the error JSON, 8 for a join code, and size the achievement, friends and lobby JSON to your title (a few hundred bytes per entry, plus your payloads at 4/3 their raw size once base64-encoded). A short buffer is never a silent truncation — you get -3 and an empty string.

Example

Safety notes

  • Pointers must be valid for the duration of the call
  • err_buf may be null or err_len == 0 if you do not need the message
  • Getter buffers must point to at least len writable bytes
  • Do not free strings owned by the SDK; only your own buffers are written into
  • All functions are safe to call from any thread; the singleton is behind a lock
  • arcane_sdk_frame takes only a read lock and no allocation — it is the one function meant for the render loop