cz_kelui docs

gorilla tag mod menu — unity quad + kelui software rendering, bnm reflection, xr input.

overview

cz_kelui is a gorilla tag (android/meta quest) mod menu injected as a native shared library. it hooks into the game via bnm (il2cpp reflection), renders a floating menu panel using kelui's widget api rasterized in software, and uploads the result to a unity texture on a world-space quad attached to the left hand.

the menu is toggleable mid-game. right hand acts as a pointer. right trigger clicks. mods are organized into tabs and run every frame or once on enable.

controls

left grip / left B
toggle menu open/close
right hand (pointing)
move cursor over menu panel
right trigger
click — toggle checkbox / press button
left thumbstick Y
scroll mod list

file structure

app/src/main/cpp/
  src/
    native-lib.cpp  — jni entry, unity panel, bnm hooks, audio
    gamebridge.cpp  — bnm reflection helpers (player fields)
    mods.cpp        — mod definitions live here
    ui.cpp          — menu layout, tabs, widget calls
    czkelrender.cpp  — software rasterizer for kelui vertices
  include/
    mods.hpp        — mod struct + gmods vector
    gamebridge.hpp  — player reflection api
    ui.hpp          — menu control api
    czkelrender.hpp  — rasterizer api
    font8x8.h       — embedded 8x8 bitmap font (128 chars)
    XRInput.hpp/.cpp — vr controller input wrapper
  extern/
    BNM-Android/     — il2cpp reflection library
    kelUI/ui/        — drawing.cpp, Widgets.cpp
    minimp3.h        — mp3 decoder for ui sounds
    czsounds.h       — embedded mp3 audio data
note — the only file you need to edit to add mods is src/mods.cpp. everything else (rendering, input, ui layout) is handled automatically.

adding a mod

open src/mods.cpp. inside registermods(), call either addtick() or addoneshot() with your mod definition. that's it.

signature
addtick("Category", "mod name", "tooltip text", []() {
    ...
});

addoneshot("Category", "mod name", "tooltip text", []() {
    ...
});
paramtypedescription
categoryconst char*tab name in the menu. use an existing one or create a new tab by using a new string.
nameconst char*label shown next to the checkbox.
descconst char*tooltip text shown on hover.
fnvoid(*)()lambda or function pointer. no parameters, no return value.

mod types

tick runs every frame

use addtick for mods that need to apply a continuous effect — overriding a field every frame, applying a force, moving the player, etc. the function is called every game frame while the checkbox is checked.

example — continuous force
addtick("Movement", "Anti Gravity", "push up every frame", []() {
    Rigidbody* rb = gamebridge::rb();
    if (!rb) return;
    rb->AddForce(Vector3(0, 15.0f, 0), ForceMode::Acceleration);
});

oneshot runs once on enable

use addoneshot for mods that set a value once — changing a field that persists (like max arm length or jump multiplier), teleporting, one-time game state changes. the function fires on the rising edge (checkbox off to on).

example — set a field once
addoneshot("Movement", "Max Jump", "set jump multiplier to 10", []() {
    gamebridge::setpf("jumpMultiplier", 10.0f);
    gamebridge::setpf("maxJumpSpeed", 999.0f);
});

categories

the category string you pass to addtick/addoneshot becomes a tab in the menu. the built-in tabs are:

tabwhat belongs here
"Movement"speed, fly, jump, locomotion mods
"Gravity"physics gravity changes
"Body"player body/collider mods (headless, freeze, slide)
"Hands"hand offset / arm length mods
"settings"reserved — menu settings tab

to add a new tab, just use a new category string. the tab appears automatically. tabs are ordered by first registration.

warning — if you add a new category that isn't listed in categories[] inside src/ui.cpp, its tab won't show. add the new name to that array and increment ncategories.
src/ui.cpp — adding a new tab
static const char* categories[] = {
    "Movement", "Gravity", "Body", "Hands", "Visual"
};
static constexpr int ncategories = 5;

examples

trigger-gated tick mod

src/mods.cpp
addtick("Movement", "Rocket Jump (R-Trigger)", "hold right trigger to launch upward", []() {
    if (XRInput::GetFloatFeature(Trigger, Right) < 0.5f) return;
    Rigidbody* rb = gamebridge::rb();
    if (rb) rb->AddForce(Vector3(0, 25.0f, 0), ForceMode::VelocityChange);
});

reading and restoring a field

static float origspeed = 0.0f;
static bool  saved     = false;

addtick("Movement", "Super Speed", "10x jump multiplier", []() {
    if (!saved) {
        origspeed = gamebridge::getpf("jumpMultiplier");
        saved = true;
    }
    gamebridge::setpf("jumpMultiplier", 10.0f);
});

addoneshot("Movement", "Restore Speed", "reset jump multiplier", []() {
    if (saved) { gamebridge::setpf("jumpMultiplier", origspeed); saved = false; }
});

button-gated movement

addtick("Movement", "Dash (R-Primary)", "press right A to dash", []() {
    if (!XRInput::GetBoolFeature(PrimaryButton, Right)) return;
    Transform* b = gamebridge::body();
    Transform* r = gamebridge::roottrans();
    if (!b || !r) return;
    r->SetPosition(r->GetPosition() + b->GetForward() * Time::GetDeltaTime() * 8.0f);
});

customizing colors

widget colors are defined as inline values in extern/kelUI/ui/Widgets.cpp. find the button(), checkbox(), and tabbutton() functions and change the Color{r, g, b, a} literals. values are 0.0 to 1.0 floats.

the menu background, border, and title bar colors are in src/ui.cpp inside buildframe():

src/ui.cpp — background + border
kel::Drawing::rect(0, 0, kmenuwidth, kmenuheight, {0.06f, 0.07f, 0.10f, 0.88f});
kel::Drawing::rect(0, 0, kmenuwidth, 1.5f, {0.28f, 0.48f, 0.90f, 0.55f});
src/ui.cpp — title bar
kel::Drawing::rect(0, 0, kmenuwidth, ktitleh, {0.12f, 0.30f, 0.72f, 0.98f});
kel::Drawing::text(kpadding, 8.0f, "gorilla tag mods", {0.95f, 0.96f, 1.0f, 1.0f});

color format is { red, green, blue, alpha } — all values between 0.0 and 1.0.

menu size

the framebuffer and panel dimensions are set at the top of two files. change them together to maintain correct aspect ratio.

src/native-lib.cpp
static constexpr int   kfbw    = 540;
static constexpr int   kfbh    = 360;
static constexpr float kpanelw = 0.40f;
static constexpr float kpanelh = 0.27f;
src/ui.cpp
static constexpr float kmenuwidth  = 540.0f;
static constexpr float kmenuheight = 360.0f;
tip — keep kpanelw / kpanelh == kfbw / kfbh (ratio 1.5) to avoid stretching. e.g. 480x320, 600x400, 720x480 all work.

menu layout

the menu is built entirely in src/ui.cpp. layout constants at the top:

constantdefaultdescription
ktitleh28height of the title bar in pixels
ktabh26height of each tab button
ktabw88width of each tab button
kpadding14left/right content padding

to change the title text, edit the string in drawtitlebar() inside ui.cpp.

gamebridge api

the gamebridge namespace wraps all bnm player reflection. use these inside mod lambdas.

functionreturnsdescription
gamebridge::player()void*raw pointer to the Player instance
gamebridge::setpf(name, val)voidset a float field on the player by name
gamebridge::getpf(name)floatget a float field from the player by name
gamebridge::setpb(name, val)voidset a bool field on the player
gamebridge::setpv3(name, val)voidset a Vector3 field on the player
gamebridge::head()Transform*head collider transform
gamebridge::body()Transform*body collider transform
gamebridge::rb()Rigidbody*player rigidbody
gamebridge::roottrans()Transform*GorillaPlayer root transform

common player field names

field nametypenotes
"jumpMultiplier"floatdefault ~1.1. higher = bigger swings
"maxJumpSpeed"floatdefault ~6.5. cap on launch velocity
"maxArmLength"floatdefault 1.5m. max grab reach
"defaultSlideFactor"floatsurface grip. 1.0 = full slide
"disableMovement"booltrue = tag-freeze the player
"rightHandOffset"Vector3hand position offset
"leftHandOffset"Vector3hand position offset

xrinput api

use these inside tick mod lambdas to read controller state:

callreturnsdescription
XRInput::GetBoolFeature(feat, ctrl)boolbutton is pressed
XRInput::GetFloatFeature(feat, ctrl)floatanalog value 0.0 to 1.0
XRInput::GetVector2Feature(feat, ctrl)Vector2thumbstick x/y
XRInput::SendHapticImpulse(ctrl, amp, dur)voidvibrate controller

controllers

Left
Right

bool features

PrimaryButton
SecondaryButton
GripButton
TriggerButton

float features

Trigger
Grip

vector2 features

Primary2DAxis
Secondary2DAxis

building

open the project in android studio. select the release build variant, then build apk.

build variants (android studio)
Build → Select Build Variant → release

the compiled library is output as cz_kelui.so. inject it into gorilla tag using your preferred android mod loader.

tip — the release build applies -O3 -ffast-math -funroll-loops and strips debug symbols. always test with debug first, release for final build.
bnm submodule — if extern/BNM-Android is empty after cloning, run git submodule update --init --recursive.