Skip to content

Runtime Modules and Cvars

OpenWarcraft3 uses Quake-style runtime configuration: small cvars select subsystems, choose startup modes, and make diagnostics reproducible from the command line.

Project-private compile-time macros, generated binding helpers, environment toggles, and namespaced constants use the BZ_ prefix. Keep new project-prefixed names on that prefix instead of adding another project namespace.

Cvar System Internals

The cvar_t Struct

// common/common.h
typedef struct cvar_s {
    struct cvar_s *next;  // linked list
    LPCSTR name;
    LPSTR string;          // string value (heap-allocated)
    FLOAT value;           // float conversion
    int integer;           // integer conversion
    DWORD flags;           // bitmask: CVAR_ARCHIVE
    bool modified;         // set true when value changes
} cvar_t;

Cvars are stored in a singly-linked list. Cvar_Get(name, value, flags) finds or creates a cvar by name, adding any new flags to existing cvars. Cvar_Set(name, value) sets the value and marks modified = true. If the old value matches the new one, it's a no-op.

Flags

There is one flag: CVAR_ARCHIVE (bit 0). A cvar with CVAR_ARCHIVE is written to the generated config file by writeconfig and restored on next launch. Session-only cvars like map and connect omit this flag.

Console Integration

When you type a bare cvar name in the console (e.g., scr_showfps 0), Cvar_Command() auto-dispatches: prints the current value with one arg, sets it with two or more. Console commands:

Command Effect
set <name> <value> Set a cvar without changing flags
seta <name> <value> Set a cvar and add CVAR_ARCHIVE
cvarlist Print all cvars — * prefix marks archived ones
writeconfig [path] Write all CVAR_ARCHIVE cvars to a config file, defaults to the value of cvar config
exec <path> Load and execute a config file

writeconfig Output

// Generated by openwarcraft3, do not modify
seta data "data/Warcraft III"
seta name "Player"
seta r_module "renderer"
seta ui_module "ui"
seta g_module "game"
seta scr_showfps "1"
...

Session-only cvars (map, connect) are explicitly skipped. When com_frame_limit > 0, the engine exits without writing config, so one-shot diagnostic runs don't pollute saved settings.

Config File Loading

Load Order

Config files are split by ownership: read-only defaults ship with the game, writable user settings live in a per-user home directory. The paths are resolved at startup (Sys_ResolveShareDirectory / Sys_ResolveHomeDirectory in common/main.c):

  • fs_basepath — read-only base share/ dir, anchored at the executable (<exe>/share, <exe>/../share, or CWD share). Engine-wide assets (fonts/) live at its top level.
  • fs_homepath — writable ~/.<game>/ on Unix, %APPDATA%\<game>\ on Windows, adopted only if creatable and writable; empty otherwise.

The load order in Com_Init() is:

Step File Purpose
1 Programmatic Cvar_Get() in Cvar_Init() In-code defaults
2 -config CLI arg Override config path
3 Early + args (+set, +<cvar>) Command-line overrides
4 <base>/<game>/config.cfg Shipped game defaults (key bindings)
5 Cvar config (default ~/.<game>/config.cfg) Generated user config — created by writeconfig
6 ~/.<game>/autoexec.cfg Optional local overrides
7 -data, -connect, -tft, -roc CLI args Data-dir / expansion settings
8 Remaining + args (+set, +<cvar>), consumed Final command-line overrides

When $HOME is absent or read-only (portable/read-only deploy), fs_homepath is empty and steps 5–6 degrade to <base>/<game>/config.cfg and <base>/<game>/autoexec.cfg, so a share/ tree copied beside the executable still works.

After step 6, map and connect cvars are explicitly cleared, then re-populated from command-line arguments in steps 7–8.

Config File Execution

Cvar_LoadConfig(path) tries FS_ReadFileIntoString first (MPQ/loose filesystem), then falls back to raw fopen for local files. It queues the text; startup calls Cbuf_Execute() after each config load before consuming the resulting cvars.

Command-Line Arguments

- (dash) Prefix — set cvars immediately

Argument Effect
-data <folder> Sets data cvar (game asset directory)
-connect <host[:port]> Sets connect cvar (remote server address)
-config <path> Sets config cvar (generated config path)
-vid_modes Sets session cvar vid_modes to "1"; logs SDL display modes during renderer startup
-tft Sets fs_expansion to "1" (mount TFT MPQs)
-roc Sets fs_expansion to "0" (ROnly, no expansion MPQs)

+ (plus) Prefix — queue commands

The + prefix is for command-line only. It tells Cbuf_AddEarlyCommands / Cbuf_AddLateCommands to queue the argument as a command for startup execution:

Form Behavior
+set <name> <value> Cvar_Set(name, value) immediately
+<cvar> [<value>] If <cvar> exists, sets it to <value> (or "1" if no value)
+<command> [<args>...] Queued via Cbuf_AddText — executed after module init

Cursor Ownership

SDL owns the native platform cursor on macOS, Linux, Windows, and other supported video backends. WoW changes that native cursor with SDL_CreateSystemCursor and SDL_SetCursor for hover context. The renderer does not duplicate platform cursor APIs or draw a generic software fallback.

r_cursor 0 keeps the SDL cursor and is the default. r_cursor 1 explicitly replaces it with a game-authored cursor when the active game renderer provides one; Warcraft III renders UI\\Cursor\\HumanCursor.mdx. If that asset cannot load, the client leaves the SDL cursor visible.

Early commands (+set, +<cvar>) are processed during Com_Init(), before module registration. Late commands (+map, +menu_main, etc.) are processed after CL_Init() when all command handlers are registered.

In code, always use the bare command name: "map ...", not "+map ...".

Standard Invocations

# Listen server + local client (loopback, no real socket)
openwarcraft3 -data "Warcraft III" +map "Maps\Campaign\Human02.w3m"

# Remote client
openwarcraft3 -data "Warcraft III" -connect 192.168.1.10:27910

# Client menu only
openwarcraft3 -data "Warcraft III"

# Mount expansion MPQs
openwarcraft3 -data "Warcraft III" -tft +map "Maps\FrozenThrone\Campaign\NightElfX01.w3m"

# One-frame UI diagnostic (text output, no window)
openwarcraft3 -data "Warcraft III" +r_module stdout +menu_main +com_frame_limit 1

Core Cvars

All cvars registered in Cvar_Init():

cvar Default Flags Description
config ~/.<game>/config.cfg (resolved) CVAR_ARCHIVE Generated config path
fs_basepath resolved share dir 0 Read-only engine/share data directory
fs_homepath ~/.<game>/ (empty if unavailable) 0 Writable per-user directory
data "" CVAR_ARCHIVE Game asset directory (contains MPQs)
fs_expansion "0" 0 Mount expansion archives (-tft sets to "1")
map "" 0 Internal MPQ map path for listen-server mode
connect "" 0 Remote server address
cl_debug_entities "0" 0 Client entity debug logging
sv_debug_entities "0" 0 Server entity debug logging
r_debug_entities "0" 0 Renderer entity debug logging
r_module "renderer" CVAR_ARCHIVE Renderer backend (renderer for GL, stdout/text for diagnostics)
ui_module "ui" CVAR_ARCHIVE UI module name (placeholder for dynamic loading)
g_module "game" CVAR_ARCHIVE Game module name (placeholder for dynamic loading)
ui_game_setup_map "" 0 Pre-selected map for game setup screen
game_port "27910" CVAR_ARCHIVE UDP port
name "Player" CVAR_ARCHIVE Player name
sv_hostname "OpenWarcraft3" CVAR_ARCHIVE Server hostname
com_frame_limit "0" 0 Exit after N frames; 0 means run forever
scr_showfps "1" CVAR_ARCHIVE Show FPS counter
skip_cutscene "0" 0 Skip cutscenes
vid_mode "0" CVAR_ARCHIVE Resolution-table index; mode 0 is 640x480
r_model_detail "2" CVAR_ARCHIVE Model detail level
r_anim_quality "2" CVAR_ARCHIVE Animation quality
r_texture_quality "2" CVAR_ARCHIVE Texture quality
r_particles "2" CVAR_ARCHIVE Particle quality
r_lights "2" CVAR_ARCHIVE Light quality
r_unit_shadows "1" CVAR_ARCHIVE Unit shadow rendering
r_occlusion "1" CVAR_ARCHIVE Occlusion culling
r_norefresh "0" 0 Skip all screen rendering while input, client networking, snapshots, and the server continue
r_stats "0" 0 Print renderer stats, or client/server loop rate while r_norefresh=1
ui_chat_support "0" CVAR_ARCHIVE Chat UI support
s_provider "1" CVAR_ARCHIVE Sound provider

Module Boundary

The runtime libraries are built into build/lib/:

  • libshared — math and shared primitives
  • libjass — Warcraft III JASS VM from games/warcraft-3/jass/
  • libsheet — Warcraft III SLK/profile parser from games/warcraft-3/sheet/
  • librenderer — generic renderer sources from renderer/ plus the selected game's renderer hooks
  • libui — selected-game UI library; for Warcraft III this is games/warcraft-3/ui/
  • libgame — selected-game server-side game logic; for Warcraft III this is games/warcraft-3/game/

Game-owned sources live under games/<game>/:

Game Game logic Renderer hooks UI Other game-owned sources
Warcraft III games/warcraft-3/game/ games/warcraft-3/renderer/ games/warcraft-3/ui/ jass/, sheet/, tests/
World of Warcraft games/world-of-warcraft/game/ games/world-of-warcraft/renderer/ games/world-of-warcraft/ui/ none today
StarCraft II games/starcraft-2/game/ games/starcraft-2/renderer/ uses the default UI library today none today

The project follows the Quake 2/Quake 3 module style: modules communicate through import/export function tables, not by reaching directly into each other's internals. The renderer exposes R_GetAPI; the UI exposes UI_GetAPI; the game module has a server/game API boundary.

The renderer has one extra internal boundary: engine code in renderer/ calls the R_Game* functions declared in renderer/r_game.h. Those hooks are implemented by the selected games/<game>/renderer/ tree. This keeps common renderer code from switching on game-specific model formats such as MDX, M2, or M3.

The cvars r_module, ui_module, and g_module are the runtime names for those modules. At present, r_module selects between:

  • renderer — the normal OpenGL renderer
  • stdout or text — the diagnostic text renderer

ui_module and g_module currently document the configured module names and keep the config shape ready for fully dynamic library selection.

Stdout Renderer

The stdout renderer implements the renderer API without opening an SDL/OpenGL window. It records renderer calls as text, which makes UI layout and draw order inspectable in scripts and CI.

Recommended command:

make run-ui-text

Equivalent explicit command:

build/bin/openwarcraft3 \
  -data data/Warcraft\ III \
  +r_module stdout \
  +menu_main \
  +com_frame_limit 1

Important flags:

  • +r_module stdout selects the text renderer.
  • Menu-only checks do not enter the LAN browser, connect, or host a lobby, so UDP sockets are never opened.
  • +menu_main chooses the UI starting command.
  • +com_frame_limit 1 exits after one frame.

One-frame runs with com_frame_limit > 0 do not write the generated config file, so diagnostics do not change the next normal launch.

Example output:

renderer_init backend="stdout" window={w:1024,h:768}
begin_frame index=1
draw_portrait model="UI\\Glues\\MainMenu\\MainMenu3d\\MainMenu3d.mdl" anim="Stand" viewport={x:0.000000,y:0.000000,w:1.000000,h:1.000000}
draw_sprite model="UI\\Glues\\MainMenu\\WarCraftIIILogo\\WarCraftIIILogo.mdl" anim="Stand" x=0.130000 y=0.080000
draw_image texture=39 name="UI\\Widgets\\Glues\\GlueScreen-Button1-Border.blp" screen={x:0.529000,y:0.110625,w:0.256000,h:0.064000} uv={x:0.000000,y:0.000000,w:1.000000,h:1.000000}
draw_text font_size=13 font="Fonts\\FRIZQT__.TTF" rect={x:0.594000,y:0.127125,w:0.179000,h:0.031000} text="|CffffffffS|Ringle Player"
end_frame index=1
renderer_shutdown backend="stdout"

Use Cases

Use the stdout renderer when investigating UI issues that are hard to see from screenshots:

  • missing frames or wrong command startup
  • button/backdrop placement and sizes
  • texture and model load paths
  • UVs, tiling, rotation, colors, and blend modes
  • translated strings and Warcraft color codes
  • layout regressions in automated checks

Screenshots are still useful for final visual review, but stdout rendering gives a fast first answer to "what did the UI ask the renderer to draw?"

See Also