← Mods

CairnDevTools

dev

v0.1.1

Live C# console wired into the running game.

A localhost HTTP console that compiles and evals C# against the live game (Roslyn), plus scene inspection and per-instance logging. For writing mods, not playing them.

Built-in verbs: eval, belay, find, inspect, ropes, renderers, gameplay, survival, edelweiss, input, emit, help.

Features

  • HTTP console (ports 14200–14209)
  • Live C# eval — sync and async (Game.WaitFor)
  • Scene inspection: find, inspect, renderers, ropes, belay graph
  • Per-instance error log (multi-instance safe)
  • Synthetic gamepad input
  • Survival freeze and infinite edelweiss for repro loops

Requires


Installing

Extract the zip into your Cairn folder, next to Cairn.exe — the mod lands in Mods/ and its compiler libraries in UserLibs/. The bare DLL alone won't run.

HTTP Console

One instance per port, probed from 14200 up. The chosen port is logged at startup. Both GET and POST are supported. Each instance also mirrors MelonLogger errors and warnings to a per-PID log at MelonLoader/CairnDevTools/<timestamp>_pid<pid>_errors.log — safe with multiple instances open.

GET /cmd?q= [args]

Run a console verb synchronously. Returns plain text. URL-encode spaces in args.

POST /cmd?q=eval

Body is raw C# — no URL-encoding. Preferred for multi-line scripts. The q query param defaults to eval when omitted.

GET /events?since=&name=&timeout=

Long-poll for edge events emitted by game patches or the emit verb. Blocks until the next event past since (default: now), optionally filtered by name, or until timeout ms (default 60000). Returns one-line JSON {seq,name,payload,ts} or {timeout:true,seq,oldest}.

Example
# run a verb
curl "http://127.0.0.1:14200/cmd?q=help"

# eval multi-line C#
curl -s -X POST "http://127.0.0.1:14200/cmd?q=eval" --data-binary @script.cs

# long-poll for events
curl "http://127.0.0.1:14200/events?since=0&name=revive-resolved"

Eval

Roslyn-compiled C# against the live game. All loaded assemblies are referenced. Import namespaces are pre-added: System, System.Linq, System.Collections.Generic, UnityEngine, Il2Cpp, Il2CppTheGameBakers.Cairn.Netplay, CairnDevTools — so LINQ and co-op types resolve unqualified. End with return <expr> to get a value back.

Log(message)

Alias for MelonLogger.Msg, available in every script body.

message string Text to log.
Bag → Dictionary

Persists between evals. Store objects in one script, read them in another.

Example
// inline — returns the value
return Il2Cpp.PawnManager.MCSpawned;

// persist across evals
Bag["pawn"] = UnityEngine.Object.FindObjectOfType<Il2Cpp.ClimbingV2PawnController>();

// retrieve later
var pawn = (Il2Cpp.ClimbingV2PawnController)Bag["pawn"];

Async Eval

Scripts can await Game.WaitFor — the POST blocks and returns only when the script settles. Level-triggered: if the predicate is already true it completes on the next frame, so no edge is ever missed. Timeout faults with a TimeoutException carrying the message. A single blocking POST is capped at ~28 s by the overall script deadline, so drive long sequences across multiple calls rather than one very long await.

Game.WaitFor(predicate, timeoutMs?) → Task

Await the first frame on which predicate is true.

predicate Func<bool> Polled each frame on the main thread.
timeoutMs opt int Default 30000.
Game.WaitFor(sample, done, timeoutMs?) → Task

Sample a value each frame; complete when done(value) is true. Returns the value.

sample Func<T> Reads game state each frame.
done Func<T, bool> Predicate over the sample.
timeoutMs opt int Default 30000.
Game.Delay(ms) → Task

Frame-based delay. Advances in lockstep with the game (paused when the pump is).

ms int Wall-clock ms to wait.
Game.NextFrame() → Task

Yield to the next frame.

Game.WaitForMenu(timeoutMs?) → Task

Await the MainMenu being present.

Game.WaitForGameplay(timeoutMs?) → Task

Await the climber pawn being spawned.

Game.WaitForFullyLoaded(timeoutMs?) → Task

Await loadingState == GameStarted — stricter than pawn-spawned.

Game.WaitForScene(name, timeoutMs?) → Task

Await the active scene having a given name.

name string e.g. <code>CommonBaseScene</code>.
Game.WaitForEagleEye(timeoutMs?) → Task

Await the eagle-eye warp view being open.

Game.EnterGameplay(timeoutMs?) → Task

The canonical "get me into a playable game": wait for menu → continue → pawn-spawn → fully-loaded. Idempotent: returns immediately if already in gameplay.

Game.Continue() → string

Continue the most-recent save from the main menu (fires the gameplay verb inline).

Game.Do(verb, args) → string

Dispatch any registered console verb by name from inside a script.

verb string Registered verb name.
args string[] Argument strings.
Example
// drive the game to a state in one blocking POST
return await Game.EnterGameplay();

// wait for a specific object to appear
var ui = await Game.WaitFor(
    () => UnityEngine.Object.FindObjectOfType<Il2Cpp.EagleEyeUI>(true),
    ui => ui != null);

// frame-based timing
await Game.Delay(500);
await Game.NextFrame();

// dispatch a registered verb from inside a script
var result = Game.Do("survival", "on");

Inspection

Read-only scene queries. All run on the main thread and return plain text.

belay

Complete connection graph for the local climber's securing rope: harness attach, rope identity/length/bounds, every holder, every part with endpoint pins, LineRenderer truth, climbot state, pitons, and the causal fall-distance scalars.

find

Search all scene Transforms (including inactive) by name. Returns up to 30 matches with hierarchy paths and positions.

inspect

Full component list for one GameObject: position, rotation, scale, per-component extras (rope length, renderer bounds), and child list.

ropes

Census of every LogicalRope in the scene: hierarchy path, active/init/visible state, length, holder count, bounds.

renderers [minSpanMeters]

Active renderers with bounds over the span threshold (default 300 m). Returns up to 25 entries.

minSpanMeters opt float Default 300.
Example
curl "http://127.0.0.1:14200/cmd?q=belay"
curl "http://127.0.0.1:14200/cmd?q=find Quickdraw"
curl "http://127.0.0.1:14200/cmd?q=inspect Aava/bn_Head"
curl "http://127.0.0.1:14200/cmd?q=ropes"
curl "http://127.0.0.1:14200/cmd?q=renderers 10"

Other Verbs

Utility commands registered at startup.

help

List every registered verb. The default when no q is given.

gameplay

Continue the most-recent save from the main menu (MainMenu → CommonBaseScene, ~12 s). Only works when the MainMenu is in the scene.

survival [on|off]

Freeze hunger, thirst, and cold so the climber doesn't starve mid-investigation. Blocks the drain at the source (ClimberData.Set), not just the ratio getters. Toggles when called with no argument.

edelweiss [on|off]

Make the resurrection item non-consumable so it can revive repeatedly. Useful for repro loops that drive the death→revive cycle many times. Toggles when called with no argument.

input [...]

Hold a synthetic gamepad control value. Drives the real Unity InputSystem so InputAction callbacks fire (hold-to-switch, chords). Controls: lt, rt, lb, rb, lsx, lsy.

control string lt | rt | lb | rb | lsx | lsy
value float 0..1
input clear

Release all synthetic input and hand control back to the physical device.

input state

Report current synthetic input values.

emit [payload]

Push a named event onto the EventBus from outside the process. Wakes any /events long-poll watching that name.

Example
curl "http://127.0.0.1:14200/cmd?q=gameplay"
curl "http://127.0.0.1:14200/cmd?q=survival on"
curl "http://127.0.0.1:14200/cmd?q=edelweiss on"
curl "http://127.0.0.1:14200/cmd?q=input lt 1 rt 1"
curl "http://127.0.0.1:14200/cmd?q=input clear"
curl "http://127.0.0.1:14200/cmd?q=emit revive-resolved ok"