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 baseshare/dir, anchored at the executable (<exe>/share,<exe>/../share, or CWDshare). 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 primitiveslibjass— Warcraft III JASS VM fromgames/warcraft-3/jass/libsheet— Warcraft III SLK/profile parser fromgames/warcraft-3/sheet/librenderer— generic renderer sources fromrenderer/plus the selected game's renderer hookslibui— selected-game UI library; for Warcraft III this isgames/warcraft-3/ui/libgame— selected-game server-side game logic; for Warcraft III this isgames/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 rendererstdoutortext— 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:
Equivalent explicit command:
build/bin/openwarcraft3 \
-data data/Warcraft\ III \
+r_module stdout \
+menu_main \
+com_frame_limit 1
Important flags:
+r_module stdoutselects the text renderer.- Menu-only checks do not enter the LAN browser, connect, or host a lobby, so UDP sockets are never opened.
+menu_mainchooses the UI starting command.+com_frame_limit 1exits 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
- Client Architecture — client main loop, input, and scene rendering
- Network Architecture — loopback/UDP transport and connection handshake
- UI System Architecture — menu screens, FDF layout, in-game HUD