◀ PLHVMCOURSESDOWNLOAD PDF ▾
CRONUS ZEN · SCRIPTING HANDBOOK · BUILT BY PLHVM
THE GPC
HANDBOOK

From zero to writing your own Cronus Zen scripts. Every concept explained, every function shown in real code, no programming experience required.

BUILT BY PLHVM
16 CHAPTERS · 30+ CODE EXAMPLES · PRACTICE DRILLS · CHEAT SHEET · GLOSSARY
SHARE FREELY
INDEXWhat's inside
01How the Zen workswhere your script actually runs
02SetupZen Studio, ports, slots
03Your first 30 minutesblank file → running script
04Anatomy of a scriptinit, main, combo
05The languagevariables, defines, operators, if/else
06Reading & writing inputsget_val, event_press, set_val
07State & patternstoggles, tap vs hold, modes, double-tap
08Combostimed sequences done right
09Functionsreusable code
10LED & rumble feedbacklet the player feel mode changes
11Saving settingsvalues that survive power-off
12A complete real scripteverything combined, fully annotated
13DebuggingDevice Monitor, TRACE, "it does nothing"
14Practice drills5 exercises, solutions included
15Cheat sheetone page, everything at a glance
16Glossaryevery term, plain English
How to use this guide: read chapters 1–5 in order — they build on each other. Type the examples in yourself instead of copy-pasting; your fingers learn the syntax faster than your eyes do. From chapter 6 on, treat it as a reference. Everything in GPC is built from three ideas: the main loop, variables, and combos. Once those click, you can read any script ever written.
01How the Zen works

The Cronus Zen sits physically between your controller and the console. Your script runs on the Zen itself — not on the console, not on a PC. Roughly every 10 milliseconds, this happens:

CONTROLLERraw inputs
ZEN — YOUR SCRIPTread · modify · add
CONSOLE / PCsees the result

Your script sees every button and stick value, can change any of them, and the console receives whatever your script decided. The console has no idea a script exists — it just sees a controller.

This also tells you what GPC can't do: it can't see the screen, read the game, or know what's happening in the match. Everything a script "knows" comes from the controller in your hands.

02Setup
  1. Download Zen Studio (free) from cronusmax.com and install it on a Windows PC.
  2. Connect the Zen to the PC using the PROG port (the side USB port, not the console one).
  3. Update firmware when Zen Studio asks. Do not skip this.
  4. Open the GPC IDE tab — this is your editor, compiler, and loader in one.

The Zen holds up to 8 script slots. You compile a script, flash it to a slot, then select the slot from the device. While testing you'll flash the same slot over and over — that's normal workflow.

BUTTON IN ZEN STUDIOWHAT IT DOES
Compilechecks your code and reports errors with line numbers
Build & Runcompiles and runs on the Zen immediately (best while testing)
Program Deviceflashes the script permanently into a slot
Device Monitorshows live input/output values — your best debugging friend
03Your first 30 minutes — do this once, follow along

Before any theory, get one script running end to end. This removes every "but does it actually work?" doubt and teaches you the workflow you'll use forever.

  1. Open Zen Studio → GPC IDE tab → File > New.
  2. Type this — all of it, don't paste:
// my first script — swap jump onto the bumper main { swap(XB1_A, XB1_RB); }
  1. Press Compile. Green output = success. Error? You typed something differently — check semicolons and braces.
  2. Press Build & Run. The script is now live on the Zen.
  3. Open the Device Monitor, press A on your controller — watch the RB output light up instead. That's your script working, visible with your own eyes.
  4. Now change XB1_RB to XB1_RS, recompile, Build & Run again. That edit-compile-run cycle is the whole workflow.
  5. Happy with it? Program Device flashes it into a slot permanently.

Total time: well under 30 minutes. Everything after this chapter is just learning more things to put between those braces.

04Anatomy of a script

Every GPC script has the same skeleton. Here it is with every section labeled:

// ── 1. HEADER ───────────────────────────────────────── // Comments start with two slashes. Use them a lot. define JUMP = XB1_A; // name a button once, reuse it define HOLD_TIME = 200; // name a number once, reuse it int counter; // variables live at the top int modeOn = 0; // they keep their value between loops // ── 2. INIT — runs ONCE when the script starts ──────── init { counter = 0; } // ── 3. MAIN — runs forever, ~every 10 ms ────────────── main { if (event_press(XB1_UP)) { combo_run(Example); } } // ── 4. COMBOS — timed sequences, started from main ──── combo Example { set_val(JUMP, 100); wait(60); }

main is a loop that never stops: read the controller, decide, repeat — about 100 times per second. That one fact explains most GPC rules: no long pauses in main (the game would freeze from your point of view), and anything you want "remembered" between passes must live in a variable.

GPC has no for/while loops. You don't need them — main is your loop. If you catch yourself wanting "do this until X", the answer is a variable plus an if, or a combo.

05The language

VARIABLES

One type: int — whole numbers, positive or negative. No decimals, no text. Declare at the top of the script, outside any section:

int shots; // starts at 0 int mode = 1; // starts at 1 int a, b, c; // several at once

DEFINES — NAMED CONSTANTS

A define gives a fixed value a name. Change it in one place, and the whole script updates. Every serious script starts with a block of defines:

define FIRE = PS4_R2; define ADS = PS4_L2; define TAP_MS = 180; // later, code reads like English: // if (get_val(FIRE) && get_val(ADS)) { ... }

OPERATORS

MATHMEANING
+   -add, subtract
*   /multiply, divide
%remainder (great for cycling modes)
=assign a value
COMPARE / LOGICMEANING
==   !=equal, not equal
<   >   <=   >=less / greater than
&&   ||AND, OR
!NOT (flips true/false)

The classic trap: = assigns, == compares. Writing if (mode = 1) when you meant if (mode == 1) is the oldest bug in programming.

IF / ELSE — DECISIONS

if (get_val(PS4_L2) && get_val(PS4_R2)) { // both held → do something } else if (get_val(PS4_L2)) { // only L2 held } else { // neither }

BUTTON IDENTIFIERS

Every input has a name. Buttons and triggers read 0–100; stick axes read -100 to 100 (negative = up/left). The Device Monitor shows you every identifier live — press a button and watch.

PLAYSTATIONXBOXWHATRANGE
PS4_CROSSXB1_Aface button0–100
PS4_R2 / PS4_L2XB1_RT / XB1_LTtriggers (analog)0–100
PS4_R1 / PS4_L1XB1_RB / XB1_LBbumpers0–100
PS4_LX / PS4_LYXB1_LX / XB1_LYleft stick axes-100–100
PS4_RX / PS4_RYXB1_RX / XB1_RYright stick axes-100–100
PS4_L3 / PS4_R3XB1_LS / XB1_RSstick clicks0–100
PS4_UP … PS4_RIGHTXB1_UP … XB1_RIGHTd-pad0–100

Scripts written with PS4_ names still work on Xbox and vice versa — the identifiers map to positions, not consoles. Pick one family and stay consistent.

06Reading & writing inputs

THE BIG FOUR

FUNCTIONANSWERS THE QUESTIONTRUE WHEN
get_val(btn)"is it held right now?"every loop while held
event_press(btn)"was it just pressed?"one single loop, on press
event_release(btn)"was it just let go?"one single loop, on release
get_ptime(btn)"how long has it been held?"returns milliseconds
main { if (get_val(XB1_A)) { // TRUE the whole time A is held } if (event_press(XB1_A)) { // TRUE only the moment A goes down } }

The #1 beginner bug is using get_val where you meant event_press. get_val fires ~100 times a second while held — a toggle flipped with get_val will flicker on and off madly. Toggles and counters want event_press.

WRITING WITH set_val

main { set_val(XB1_B, 100); // console sees B fully pressed set_val(XB1_A, 0); // console sees A released — // even if the player is holding it }

set_val lasts one loop. It is not a switch you flip once — main must keep setting it every pass. That's exactly why state lives in variables (next chapter).

BUILT-IN SHORTCUTS

swap(PS4_CROSS, PS4_CIRCLE); // swap two buttons block(PS4_TOUCH, 300); // suppress a button for N ms sensitivity(PS4_RX, NOT_USE, 120); // scale an axis (120 = +20%) deadzone(PS4_LX, PS4_LY, 15, 15); // kill stick drift near center

These do in one line what would take several with get_val/set_val. A whole "remap script" can be a single swap() in main.

07State & patterns — the four you'll use forever

PATTERN 1 — THE TOGGLE

Tap once = on, tap again = off. The variable is the memory; main re-applies the effect every loop while it's on:

int crouched = 0; main { if (event_press(PS4_R3)) { crouched = !crouched; // flip 0 ↔ 1 } if (crouched) { set_val(PS4_CIRCLE, 100); // held for you } }

PATTERN 2 — TAP VS HOLD

One button, two actions: tap = melee, hold = grenade. Decide on release, using how long it was down:

define TAP_MS = 200; main { if (event_release(XB1_B)) { if (get_ptime(XB1_B) < TAP_MS) { combo_run(QuickTap); // it was a tap } // held longer? do nothing — the normal // hold behavior already happened in game } } combo QuickTap { set_val(XB1_RS, 100); wait(60); }

PATTERN 3 — MODE CYCLING

Cycle through 3 profiles with one button. The % operator wraps the counter back to 0:

int mode = 0; // 0, 1, 2, then back to 0 main { // hold LT + press UP to switch mode if (get_val(XB1_LT) && event_press(XB1_UP)) { mode = (mode + 1) % 3; } if (mode == 0) { /* default behavior */ } if (mode == 1) { swap(XB1_A, XB1_RB); } if (mode == 2) { swap(XB1_A, XB1_RS); } }

Notice the guard: get_val(XB1_LT) && makes it a deliberate two-hand action, so nobody switches modes mid-fight by accident. Guarding your mode button is a mark of a well-made script.

PATTERN 4 — DOUBLE-TAP

Run something only when a button is pressed twice quickly. This needs a stopwatch: get_rtime() returns the milliseconds since the previous pass through main, so adding it up every loop gives you elapsed time:

define DT_WINDOW = 300; // max ms between the two taps int sinceTap = 1000; // start large: no recent tap main { sinceTap = sinceTap + get_rtime(); // tick the stopwatch if (event_press(PS4_CROSS)) { if (sinceTap < DT_WINDOW) { combo_run(DoubleTapAction); // second tap in time! } sinceTap = 0; // restart the stopwatch } } combo DoubleTapAction { set_val(PS4_L3, 100); wait(60); }

This stopwatch trick — timer = timer + get_rtime(); — is how GPC measures any gap between events. You'll reuse it constantly.

08Combos — timed sequences done right

A combo is the only place time is allowed to pass. Each wait(ms) holds the current outputs for that many milliseconds, then moves on:

main { if (event_press(PS4_UP)) { combo_run(PracticeString); } } combo PracticeString { set_val(PS4_SQUARE, 100); wait(60); // Square held 60 ms wait(80); // everything released 80 ms set_val(PS4_TRIANGLE, 100); wait(60); wait(80); set_val(PS4_CROSS, 100); wait(60); }

How to read it: a set_val inside a combo stays active until the next wait() finishes. A wait() with nothing set before it is a pause with everything released. That "empty wait" trick is how you put gaps between presses.

FUNCTIONWHAT IT DOES
combo_run(Name)starts a combo (if already running, does nothing)
combo_running(Name)TRUE while it runs — great for guards
combo_stop(Name)kills it immediately, outputs release
combo_restart(Name)jumps it back to the first line

Rules that save you pain:

  • Fire combos with event_press, not get_val — or holding the button restarts it forever.
  • A combo with no wait() at the end releases instantly — always end the final press with a wait.
  • Give the player an abort: if (event_press(PS4_CIRCLE)) combo_stop(PracticeString);
09Functions — write it once

When the same logic appears twice, move it into a function. Functions take inputs, can return a value, and make main read like a sentence:

function bothTriggers() { return get_val(XB1_LT) && get_val(XB1_RT); } function pressFor(btn, ms) { set_val(btn, 100); // note: lasts this loop only — } // timed presses still want a combo main { if (bothTriggers()) { // aiming AND firing } }

Keep functions small and named after what they answer or do: isAiming(), resetModes(). If you can't name it simply, it's doing too much.

10LED & rumble — let the player feel it

A script that changes modes silently feels broken. The Zen can flash the controller LED and fire the rumble motors — use a short buzz to confirm a toggle:

main { if (get_val(XB1_LT) && event_press(XB1_UP)) { mode = (mode + 1) % 3; combo_run(NotifyBuzz); // feel the switch } } combo NotifyBuzz { set_rumble(RUMBLE_A, 80); // strong motor at 80% wait(150); reset_rumble(); // always turn it off! }
  • set_rumble(RUMBLE_A, power) / RUMBLE_B — the two motors, power 0–100.
  • reset_rumble() — stop all rumble. Forgetting this = a controller that never stops buzzing.
  • set_led(LED_1..LED_4, state) — controller LEDs; which color each LED maps to depends on the console, so experiment with the Device Monitor connected.

A good convention: one buzz = mode 1, two buzzes = mode 2. Players learn it instantly.

11Saving settings — values that survive power-off

Normal variables reset every time the Zen restarts. Persistent variables (SPVAR_1 through SPVAR_16) are stored on the device itself — perfect for user-adjustable settings. This script lets the player tune stick sensitivity with the d-pad and remembers it forever:

int sens; init { // get_pvar(slot, min, max, default) sens = get_pvar(SPVAR_1, 50, 150, 100); } main { // hold LT: RIGHT = +10, LEFT = -10 if (get_val(XB1_LT) && event_press(XB1_RIGHT)) { sens = sens + 10; set_pvar(SPVAR_1, sens); // saved to the device } if (get_val(XB1_LT) && event_press(XB1_LEFT)) { sens = sens - 10; set_pvar(SPVAR_1, sens); } sensitivity(XB1_RX, NOT_USE, sens); sensitivity(XB1_RY, NOT_USE, sens); }
  • get_pvar(SPVAR_n, min, max, default) — read a saved value; the min/max clamp protects you from garbage, the default is used on first ever run.
  • set_pvar(SPVAR_n, value) — write it to device storage.

This is what separates a script from a product: users set it once and it stays set. Read the value in init, save on every change, clamp with sensible min/max.

12A complete real script — everything combined

This is a full quality-of-life script using every chapter so far: defines, a guarded toggle, tap-vs-hold, a combo, rumble feedback, and clean structure. It compiles as-is. Read it top to bottom — you now know every line:

// ════════════════════════════════════════════════════ // QOL PACK v1 — built by PLHVM // R3 tap = toggle crouch-hold // B tap = quick melee (hold B still works normally) // LT + UP = swap jump between A and RB // ════════════════════════════════════════════════════ define TAP_MS = 200; // max ms that counts as a "tap" define CROUCH = XB1_B; // what the game uses to crouch int crouchOn = 0; int altJump = 0; init { crouchOn = 0; altJump = 0; } main { // ── crouch toggle on R3 ────────────────────────── if (event_press(XB1_RS)) { crouchOn = !crouchOn; combo_run(Buzz); } if (crouchOn) { set_val(CROUCH, 100); } // ── tap B = quick melee ────────────────────────── if (event_release(XB1_B) && get_ptime(XB1_B) < TAP_MS) { combo_run(QuickMelee); } // ── guarded remap toggle: LT + UP ──────────────── if (get_val(XB1_LT) && event_press(XB1_UP)) { altJump = !altJump; combo_run(Buzz); } if (altJump) { swap(XB1_A, XB1_RB); } } combo QuickMelee { set_val(XB1_RS, 100); wait(60); } combo Buzz { set_rumble(RUMBLE_A, 70); wait(140); reset_rumble(); }

Why this is "well built" — the things to copy into your own scripts:

  • A header comment says what every control does. Future-you will thank present-you.
  • Timing numbers are defines, not magic numbers buried in code.
  • Mode switches are guarded (LT +) and confirmed (buzz).
  • Each feature is its own block with its own comment — easy to delete or borrow.
13Debugging — when it doesn't work

STEP 1 — IT WON'T COMPILE

ERROR SAYSIT USUALLY MEANS
syntax errormissing semicolon on the line above, or an unmatched { }
undeclared identifiertypo in a name, or the variable isn't declared at the top
already definedtwo variables/combos share one name

Fix the first error and recompile — one missing brace can produce twenty phantom errors below it.

STEP 2 — IT COMPILES BUT MISBEHAVES

Open the Device Monitor: inputs on one side, your script's outputs on the other. Most bugs are visible instantly. For anything you can't see — like a variable's value — send it to a TRACE:

main { set_val(TRACE_1, mode); // watch 'mode' live set_val(TRACE_2, crouchOn); // six slots: TRACE_1–6 }

TRACE values appear in the Device Monitor without affecting the game. It's GPC's version of print-debugging — when a toggle "doesn't work", tracing the variable usually shows it flipping twice per press (get_val vs event_press again).

STEP 3 — "IT DOES NOTHING AT ALL"

CHECKHOW
Is the right slot active?the Zen's display shows the slot number — cycle to the one you flashed
Did Build & Run actually succeed?re-compile; a failed build leaves the OLD script running, which is very confusing
Does your if ever fire?trace the condition: set_val(TRACE_1, get_val(XB1_LT));
Works in Monitor, not in game?the game's own settings remap buttons — check its control scheme matches what you assumed

GOLDEN RULES

  • Test in private / offline modes so a runaway combo costs nothing.
  • Change one thing at a time. Broke after an edit? The edit is the suspect.
  • Keep a working_v1.gpc copy before big changes. Version numbers beat regret.
14Practice drills — earn it

Reading makes it familiar; building makes it yours. Do these in order, without looking at the solutions until you're stuck for real. Every one uses only what's in this handbook.

#DRILLYOU'LL PRACTICE
1Swap Triangle and R1.swap, the workflow
2Auto-walk: while the left stick is pushed nearly fully forward, hold L3 for the player.axes, get_val, set_val
3Double-tap Cross runs a 2-press combo.the stopwatch pattern, combos
4Two modes on LT+UP — mode 1 buzzes once, mode 2 buzzes twice.mode cycling, feedback
5D-pad-adjustable sensitivity that survives a restart.persistent variables

SOLUTIONS — NO PEEKING UNTIL YOU'VE TRIED

// ── DRILL 1 ───────────────────────────────────────── main { swap(PS4_TRIANGLE, PS4_R1); } // ── DRILL 2 — stick Y is negative when pushed up ──── main { if (get_val(PS4_LY) < -85) { set_val(PS4_L3, 100); } } // ── DRILL 3 ───────────────────────────────────────── int t = 1000; main { t = t + get_rtime(); if (event_press(PS4_CROSS)) { if (t < 300) combo_run(Two); t = 0; } } combo Two { set_val(PS4_SQUARE, 100); wait(60); wait(80); set_val(PS4_SQUARE, 100); wait(60); } // ── DRILL 4 — combo per mode, guard the switch ────── int mode = 0; main { if (get_val(XB1_LT) && event_press(XB1_UP)) { mode = (mode + 1) % 2; if (mode == 0) combo_run(Buzz1); if (mode == 1) combo_run(Buzz2); } } combo Buzz1 { set_rumble(RUMBLE_A, 70); wait(120); reset_rumble(); } combo Buzz2 { set_rumble(RUMBLE_A, 70); wait(120); reset_rumble(); wait(120); set_rumble(RUMBLE_A, 70); wait(120); reset_rumble(); } // ── DRILL 5 — see chapter 11; that script IS the answer

Done all five? Congratulations — you've used every core mechanism in GPC. Your next project should be your own idea.

TRAPSThe classic mistakes — check here first
MISTAKEWHAT YOU SEEFIX
wait() inside maincompile error / frozen feelmove timed things into a combo
get_val for a toggletoggle flickers on/off while helduse event_press
= instead of ==if is always true, chaos== compares, = assigns
expecting set_val to stickpress lasts one instanthold state in a variable, set every loop
combo fired with get_valcombo restarts endlesslyfire with event_press
no wait after last set_valfinal press never registersend every combo with a wait
rumble never stopsangry handsreset_rumble() after the wait
sticks treated as 0–100up/left never detectedaxes run -100 to 100
Play fair — read before you publish anything. Remaps, toggles, accessibility aids, and offline/practice macros are what GPC is great for. Scripts that automate aiming or recoil in online competitive games are cheating — they break the terms of service of essentially every multiplayer title, and publishers actively detect Cronus-style devices (Rainbow Six Siege's Mousetrap flags them and permanently bans accounts). Selling or sharing such scripts puts every user's account at risk. Build things that make games more playable — not unfair.
15Cheat sheet — everything at a glance

SCRIPT SKELETON

define NAME = value; // constants int x; // variables init { } // once at start main { } // every ~10 ms combo Name { } // timed sequence function name(a) { return a; }

READ

get_val(b)0–100 while held
event_press(b)the press moment
event_release(b)the release moment
get_ptime(b)ms held so far
get_rtime()ms since last loop

WRITE

set_val(b, v)output v this loop
swap(a, b)exchange buttons
block(b, ms)suppress briefly
sensitivity(ax,NOT_USE,s)scale axis
deadzone(x,y,dx,dy)kill drift

COMBOS

combo_run(C)start
combo_stop(C)kill now
combo_running(C)is it live?
combo_restart(C)back to line 1
wait(ms)hold outputs (combo only)

FEEDBACK / DEBUG

set_rumble(RUMBLE_A, p)buzz 0–100
reset_rumble()stop buzzing
set_led(LED_n, s)controller LED
set_val(TRACE_1, x)watch x live

SAVED SETTINGS

get_pvar(SPVAR_n,min,max,def)read saved
set_pvar(SPVAR_n, v)save to device

THE FOUR PATTERNS

// TOGGLE if (event_press(BTN)) on = !on; if (on) set_val(TARGET, 100); // TAP vs HOLD if (event_release(BTN) && get_ptime(BTN) < 200) combo_run(Tap); // MODE CYCLE (guarded) if (get_val(GUARD) && event_press(BTN)) mode = (mode + 1) % 3; // DOUBLE-TAP (stopwatch) t = t + get_rtime(); if (event_press(BTN)) { if (t < 300) combo_run(DT); t = 0; }

REMEMBER

  • Buttons: 0–100. Stick axes: -100 to 100. Negative = up / left.
  • set_val lasts one loop. Variables are your memory.
  • wait() only in combos. main must always finish fast.
  • == compares, = assigns.
  • Fix the first compile error first. Trace variables you can't see.
16Glossary — every term in plain English
TERMMEANING
GPCthe scripting language Cronus devices run
compileturn your text into something the Zen can execute; catches mistakes first
flash / programcopy the compiled script into one of the Zen's 8 memory slots
slotone of 8 storage positions on the Zen; you pick which one is active
firmwarethe Zen's own internal software — keep it updated via Zen Studio
loop / passone run through main, roughly every 10 ms, ~100 times a second
ms (millisecond)1/1000 of a second; all GPC timing is in ms — a fast human tap is ~120–200 ms
identifierthe fixed name of an input, like PS4_CROSS or XB1_RT
analogan input with a range (trigger 0–100, stick -100 to 100), not just on/off
axisone direction of a stick: LX is left stick left/right, LY is up/down
deadzonethe small area near stick center that's ignored so drift doesn't register
comboa timed sequence of outputs that runs step by step, started from main
toggletap once = on, tap again = off; held in a variable
guardan extra held button required for an action, preventing accidents (LT + UP)
macro / QoLan automated convenience — quality-of-life, e.g. crouch toggle, auto-walk
Device MonitorZen Studio's live view of inputs and outputs; your main debugging tool
TRACEsix debug channels (TRACE_1–6) that show variable values in the Monitor
persistent variablea value saved on the device itself (SPVAR); survives power-off
NEXTWhere to go from here
  • The starter pack. This handbook ships with PLHVM-Starter.gpc — a clean, commented base script with a master switch, three example features, and a marked spot for your own — plus the five drill solutions as ready-to-open files. Open the starter in the GPC IDE, compile it, and make it yours.
  • Official examples inside Zen Studio (File > Open Examples) — every one now readable to you.
  • The Cronus community forums — the GPC Scripting section is full of tutorials and working scripts.
  • The ConsoleTuner GPC documentation — the full reference for every built-in function, including ones this guide skipped (OLED display drawing and more).
  • Read other people's scripts. Load one, predict what it does line by line, then verify with the Device Monitor. This is the single fastest way to level up.

Your path: chapter 3's first script → the drills in chapter 14 → your own QOL pack like chapter 12. At that point you're not learning GPC anymore. You're writing it.