Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
202 commits
Select commit Hold shift + click to select a range
ad6d67c
Merge pull request #135 from actionquake/aqtion
darkshade9 Oct 30, 2023
8a4556c
Grab mouse when entering active state.
Nov 1, 2023
d79867f
Fix some issues with libavcodec cinematics.
Nov 1, 2023
9442417
Fix too early exit when one of the streams is shorter.
Nov 1, 2023
01971a0
Merge pull request #136 from skullernet/master
darkshade9 Nov 2, 2023
9b97f65
Changed branch name in workflow
darkshade9 Nov 2, 2023
3dee64a
Drop raw samples when resetting wrap counter.
Nov 3, 2023
a3a0138
Fix buffer underruns when in minimized state.
Nov 3, 2023
b98f35b
Wrap debug printf macros in do { } while (0).
Nov 4, 2023
1b4a15e
Replace libogg/libvorbis music playback with libavcodec.
Nov 4, 2023
9612988
Refuse downloading to ‘music’ or ‘video’ directories.
Nov 4, 2023
f0a36d0
Move casts in overflow checks.
Nov 5, 2023
3487f3f
Fix crash if MD5 skin path is too long.
Nov 5, 2023
f003fb0
Try different extensions for cinematics.
Nov 5, 2023
c8c0443
Initialize configstring mapping before serverdata.
Nov 5, 2023
c32121e
Account for terminating NUL for EntString lump.
Nov 9, 2023
03b7bda
Store edge/surfedge indices rather than pointers.
Nov 9, 2023
2cb4cc0
Fix dynamic lights on alpha faces.
Nov 12, 2023
ecce3b8
Add ‘autosave’ command.
Nov 12, 2023
a707d07
Bind white texture for flare occlusion test.
Nov 13, 2023
40e0023
Reduce number of BSP_RecursiveLightPoint() arguments.
Nov 14, 2023
a417338
Add struct for common frametime
res2k Nov 5, 2023
64aced4
Treat LOAD_LEVEL_START the same as level re-enter.
Nov 15, 2023
89dfa2f
Fixed merge conflits
darkshade9 Nov 15, 2023
46e51e5
Merge pull request #140 from actionquake/skullernet-master-1
darkshade9 Nov 15, 2023
1ead80c
Added ffmpeg deps
darkshade9 Nov 15, 2023
083a8b0
Missed a space
darkshade9 Nov 15, 2023
888fdd2
Merge pull request #141 from actionquake/support-ffmpeg-avcodec
darkshade9 Nov 15, 2023
92d2e23
Sync to aqtion alpha (#142)
darkshade9 Nov 15, 2023
99aef0b
Remove unused explosion type.
Nov 16, 2023
99a269a
Added handy message for badbyte, ListSounds prototype add
darkshade9 Nov 16, 2023
da2a8f1
Add monster muzzleflashes.
Nov 16, 2023
f6b72ef
Add more remaster muzzleflash definitions.
Nov 16, 2023
7425800
Add view weapon muzzleflashes.
Nov 17, 2023
6a93e69
Const-ify pointers to indices.
Nov 17, 2023
834262d
Reduce mesh drawing code duplication.
Nov 17, 2023
cf88706
Allow muzzleflashes to be disabled.
Nov 17, 2023
8416768
Allow custom footsteps to be disabled.
Nov 17, 2023
6ba7075
Enable lit alpha faces for N64 maps too.
Nov 18, 2023
6a9bcc0
Sync monster_flash_offset[] definitions with remaster.
Nov 18, 2023
408b0d8
Store MD5 texcoords in a separate array.
Nov 18, 2023
b3512e0
Replace qboolean with bool in hash map code.
Nov 19, 2023
876673b
List images that have glowmaps.
Nov 20, 2023
c9f74f7
List models that have MD5 replacements.
Nov 20, 2023
39c488d
Make Win32 pthread wrapper symbols static.
Nov 20, 2023
e567bd8
Add Meson 1.3.0 warning workaround.
Nov 20, 2023
31f26e0
Remove unused prototypes.
Nov 24, 2023
8583bba
Fix typo.
Nov 24, 2023
aff9aa8
Convert MSG_* flags to BIT() macro.
Nov 24, 2023
54809f7
Refuse downloading to ‘save’ directory.
Nov 25, 2023
4e25d99
Add Z_ReallocArray().
Nov 25, 2023
7cf1ed1
Discard raw texture data when stopping cinematic.
Nov 25, 2023
42cd25f
Fix decoding last frame in OGG_Load().
Nov 25, 2023
00019b8
Fix crash when resampling too long WAV file.
Nov 25, 2023
7577cee
Optimize sfx resampling for big-endian.
Nov 25, 2023
385d89c
Show MD5 feature if enabled.
Nov 25, 2023
b9c1b66
Limit OGG files to MAX_SFX_SAMPLES for symmetry with WAV.
Nov 25, 2023
c8e84a7
Fix ‘ogg info’ display.
Nov 27, 2023
f895a5f
Use entity radius for tracker shell and power splash effects.
Nov 27, 2023
a851181
Fix wrong axis.
Nov 29, 2023
55d2978
Allow more fine-grained ‘gl_showtris’ control.
Nov 29, 2023
3ab3394
Merge begin_/end_alias_mesh().
Nov 29, 2023
87d28b4
Improve drawing transparent gun.
Nov 29, 2023
5ccb2ac
Avoid depth func manipulation.
Nov 29, 2023
c2a1ef2
Simplify assert.
Dec 1, 2023
f3d9120
Fix UB if LM_MAX_LIGHTMAPS is exceeded.
Dec 2, 2023
ddee7ff
Make BSP face hashing always on.
Dec 2, 2023
22aefb0
Batch lightmap updates.
Dec 2, 2023
7c50405
Draw all lightmaps for debugging.
Dec 2, 2023
f59c8ed
Print lightmap extents if out of range.
Dec 2, 2023
8333905
Simplify checks for lightmap.
Dec 2, 2023
d50b52b
Don't spam about missing glowmaps with ‘developer 2’.
Dec 4, 2023
80636a5
Print BSPX lumps found with ‘developer 2’.
Dec 4, 2023
b3286ea
Use larger lightmaps for DECOUPLED_LM maps.
Dec 6, 2023
c528c92
Fit debug lightmaps on screen.
Dec 6, 2023
3cb974b
Count uploaded lightmap texels.
Dec 6, 2023
9ff231d
Update libcurl to 8.5.0.
Dec 7, 2023
d6fb3da
Simplify GL_RebuildLighting().
Dec 7, 2023
4001f13
Fix previous commit and clean up.
Dec 7, 2023
0f26547
Fix UB after GL_RebuildLighting().
Dec 8, 2023
df70ccf
Fix transparent surfaces with vertex lighting.
Dec 8, 2023
de93e69
Test ent->area.next to determine if entity is linked.
Dec 8, 2023
ba7f483
Fix UB in MenuList_PrevColumn().
Dec 9, 2023
7d251e5
Don't always scroll menu list when sorting.
Dec 9, 2023
6204d3f
Fix UB in touch functions when plane is NULL.
Dec 9, 2023
e020bdc
Support PRINT_TYPEWRITER and centerprint queue.
Dec 10, 2023
525ce14
Give more time to read typewritten string.
Dec 11, 2023
f60108f
Made String layout errors easier to debug
darkshade9 Dec 14, 2023
7148b8e
Lag scale graph scaling engage
darkshade9 Dec 14, 2023
528b27d
Updated meson.build to support c11 fallback
darkshade9 Dec 14, 2023
97204b6
Updated meson.build to support c11 fallback
darkshade9 Dec 14, 2023
02d534d
Reduce sizebuf_t size.
Dec 13, 2023
4f8e955
Avoid using size_t where int is sufficient.
Dec 13, 2023
ade77dd
Avoid having redundant fields in netchan_t.
Dec 13, 2023
fed8177
Avoid storing netchan methods in netchan_t.
Dec 13, 2023
b5ac669
Unify netchan message overflow check.
Dec 13, 2023
33a3baf
Reorder netchan_t fields to make it smaller.
Dec 13, 2023
4369511
Simplify packing mvd_unicast/multicast length.
Dec 14, 2023
5dafe2d
Avoid caching some sound indices.
Dec 14, 2023
35b109a
Refresh global precache indices after savegame load.
Dec 14, 2023
a3e8cb8
Allow SZ_Write() with NULL data if len == 0.
Dec 14, 2023
b8bc79d
Revert "Fix UB after GL_RebuildLighting()."
Dec 15, 2023
d7e4d98
Properly fix UB after GL_RebuildLighting().
Dec 15, 2023
5b29fa3
Merge SV_HullForEntity2() into SV_HullForEntity().
Dec 18, 2023
2cd1a76
Add q_cold attribute and mark Com_Error() as such.
Dec 18, 2023
d0db0cb
Replace some Com_LPrintf with specific macros.
Dec 18, 2023
319a0f4
Merge conflicts resolved
darkshade9 Dec 19, 2023
968a06b
Merge pull request #147 from actionquake/skullernet-master-2
darkshade9 Dec 19, 2023
ac68409
Fixing protocol issue
darkshade9 Dec 19, 2023
8cf3252
Merge pull request #148 from actionquake/bug/fix-protocol-problem
darkshade9 Dec 19, 2023
17d3300
Scope stuff?
darkshade9 Dec 21, 2023
dc750e1
Merge branch 'aqtion-alpha' of github.com:actionquake/q2pro into aqti…
darkshade9 Dec 21, 2023
6e8e108
Fixing Github huge screwup on not syncing parse.c correctly
darkshade9 Dec 22, 2023
07f1324
Merge branch 'aqtion' into aqtion-alpha
darkshade9 Dec 22, 2023
3a4313e
Merge pull request #146 from actionquake/scr_draw_lag_graph
darkshade9 Dec 22, 2023
b7a965f
Add SignExtend() function.
Dec 24, 2023
aec56b8
Fix more whitespace issues in game code.
Dec 24, 2023
322173f
Use initializer for aim vector.
Dec 24, 2023
466ff91
Precaching hitsounds
darkshade9 Jan 12, 2024
d1f7ff7
Removed unused sounds
darkshade9 Jan 12, 2024
342c88b
Removed unused sounds again
darkshade9 Jan 12, 2024
70f8971
Allows for multiple scopes
darkshade9 Jan 12, 2024
d7bfb73
Merge branch 'aqtion-alpha' of github.com:actionquake/q2pro into aqti…
darkshade9 Jan 14, 2024
b7f5d81
Enable zoom autosensitivity
darkshade9 Jan 16, 2024
d997b50
Got rid of some nonsense
darkshade9 Jan 16, 2024
7ffe61a
Got rid of some nonsense
darkshade9 Jan 16, 2024
2b9fcb0
Cleaning up some stuff + fix openal site link
darkshade9 Jan 16, 2024
d1a987e
More fixes
darkshade9 Jan 16, 2024
69c85fb
Merge pull request #152 from actionquake/fix/openal-and-stuff
darkshade9 Jan 16, 2024
79e8912
Merge pull request #150 from actionquake/feat/selectable_scopes
darkshade9 Jan 16, 2024
f34804a
Merge branch 'aqtion-alpha' of github.com:actionquake/q2pro into feat…
darkshade9 Jan 16, 2024
67bd7d8
Trying something a little different
darkshade9 Jan 16, 2024
6394dcf
Using a cvar rather than a struct entry
darkshade9 Jan 16, 2024
4ee13a8
Removed unused cvar, removed debug print
darkshade9 Jan 16, 2024
459678b
Misc game code simplifications.
Jan 14, 2024
2f271a3
Fix maps with 255 inline models and more.
Jan 14, 2024
4505802
Free edicts without classname or spawn function.
Jan 17, 2024
b7996d9
Use game.maxclients instead of maxclients->value.
Jan 17, 2024
b39cfd7
Use BIT_ULL() for all U_* defines.
Jan 21, 2024
337f9d3
Reduce size of some BoxEdicts() lists.
Jan 22, 2024
d487c85
Get rid of clamp() macro.
Jan 25, 2024
26b9feb
Add and use Q_atoi().
Jan 25, 2024
309b732
Allow client messagelevel up to 256.
Jan 28, 2024
45e658e
Check for zero JPEG/PNG image dimensions.
Jan 30, 2024
4a360a6
Make CL_ParseZPacket() length variables unsigned.
Jan 30, 2024
fcf63da
Use MAX_EDICTS instead of magic constants.
Jan 30, 2024
9ea8e6a
Properly check inuse flag in blocked functions.
Jan 30, 2024
8473ee5
Make HashMap_Reserve() capacity argument unsigned.
Jan 30, 2024
6699381
Fix use of uninitialized variable.
Jan 30, 2024
796df13
Change MAX_MSGLEN back to 32 KiB.
Jan 30, 2024
7fd2b4b
Scan client inventory up to game.num_items.
Jan 30, 2024
7fa6d69
Simplify sky drawing code.
Jan 30, 2024
f068cd7
Avoid signed integer overflow in SortPlayers().
Jan 31, 2024
5e4c5d1
Prefer libc versions of some string functions.
Jan 31, 2024
ce42912
Avoid using sizeof on pointer variable.
Feb 1, 2024
dc0a9b4
Properly check inuse flag in ai_run().
Feb 1, 2024
fca90a0
Debug print statements for sound caching
darkshade9 Feb 2, 2024
ce89c77
Simplify hash map lookup code.
Feb 1, 2024
a01f449
Clean up UI player models listing.
Feb 2, 2024
03707f4
Update zlib to 1.3.1.
Feb 3, 2024
14b2530
Update libpng to 1.6.42.
Feb 3, 2024
340a0e0
Update libjpeg-turbo to 3.0.2.
Feb 3, 2024
b0cc9f5
Update libcurl to 8.6.0.
Feb 3, 2024
a268fd4
Shorten code.
Feb 4, 2024
392c9bb
Update CI workflow.
Feb 4, 2024
d82d13a
Simplify code.
Feb 4, 2024
a952279
Fix message overflow check.
Feb 4, 2024
01f4496
Update comment.
Feb 4, 2024
49b9c7e
Add link to nightly builds.
Feb 5, 2024
ebce246
Merge conflicts addressed
darkshade9 Feb 6, 2024
135e260
Merge pull request #160 from actionquake/master_skuller_sync
darkshade9 Feb 6, 2024
e5516c4
Forgot to save file
darkshade9 Feb 6, 2024
bbea1a6
Merge pull request #161 from actionquake/master_skuller_sync
darkshade9 Feb 6, 2024
8d10713
Merge pull request #149 from actionquake/feat/hitsounds
darkshade9 Feb 27, 2024
b4cc577
Merge pull request #151 from actionquake/feat/autosens-zoom
darkshade9 Mar 19, 2024
89f8b1a
Fixed an issue in CL_PlayFootstepSfx where it always played a random …
ndit-dev Apr 20, 2024
d3a096d
Merge pull request #177 from ndit-dev/aqtion-alpha
darkshade9 Apr 29, 2024
accaeca
Set defines for referencing OpenAL header files for MacOS
darkshade9 Apr 29, 2024
94b45ce
Changed PKG_CONFIG_PATH for Darwin build
darkshade9 Apr 29, 2024
e54adfe
Revert AL import change as a test
darkshade9 Apr 29, 2024
7ea4701
Merge pull request #178 from actionquake/bug/fix-macos-openal-import
darkshade9 Apr 29, 2024
43ac843
Testing Mac ARM builds
darkshade9 Apr 29, 2024
9f5ec03
Changed tag
darkshade9 Apr 29, 2024
e86e942
macos-latest is now ARM64, trying macos-13 for Intel builds
darkshade9 Apr 29, 2024
6b90d2f
Reverted pkgconfig path for Intel homebrew
darkshade9 Apr 29, 2024
11528cc
Updated arm release steps
darkshade9 Apr 29, 2024
3cd3f79
Updated upload-artifact to v4
darkshade9 Apr 29, 2024
52d1562
Updated checkout and upload actions
darkshade9 Apr 29, 2024
8ebe6ae
Updated openal for Mac users
darkshade9 Apr 29, 2024
c2b7b08
Merge pull request #180 from actionquake/mac-arm-builds
darkshade9 Apr 29, 2024
3ac3a13
Merge branch 'aqtion' into aqtion-alpha
darkshade9 Sep 16, 2024
0cd6b36
Sync TNG to Q2Pro (#154)
darkshade9 Mar 4, 2026
fe874da
Merge branch 'aqtion' into aqtion-alpha
darkshade9 Mar 24, 2026
9d390db
Merge branch 'aqtion' of github.com:actionquake/q2pro into aqtion-alpha
darkshade9 Mar 24, 2026
841df7b
Add mm_carryover cvar for matchmode score carryover across maps (#309)
darkshade9 Mar 24, 2026
ff35bda
Merge branch 'aqtion-alpha' of github.com:actionquake/q2pro into aqti…
darkshade9 Mar 24, 2026
db022f6
Merge branch 'aqtion' of github.com:actionquake/q2pro into aqtion-alpha
darkshade9 Mar 24, 2026
c65618b
Merge aqtion-alpha into aqtion (fixes #316 FIX-04: mm_carryover and o…
darkshade9 May 23, 2026
d77798b
fix(#316) Tier 1: security-critical LRCON + autorecord hardening (#318)
darkshade9 May 24, 2026
f46d674
fix(#316) Tier 2: matchmode timeout/abandon/timeoutcount fixes (#319)
darkshade9 May 24, 2026
fa5157b
fix(#316) Tier 3: medium-impact fixes (teamnone, captain broadcast, R…
darkshade9 May 24, 2026
b4ab8cb
fix(#316) Tier 4: nits + quit_on_empty race fix + dead cvar cleanup (…
darkshade9 May 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions action/lrcon.cfg.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// LRCON Configuration Example
// Copy to lrcon.cfg and customize for your server
//
// LRCON provides limited remote console access allowing players to
// claim temporary admin rights and execute restricted server commands

[settings]
// Enable or disable LRCON on this server
enabled 1

// Quit the server when the last player leaves
// Set to 1 to enable, 0 to disable (default)
quit_on_empty 0

[allowed_cvars]
// List of cvars that can be queried and modified via lrcon
// One cvar per line - only whitelisted cvars can be changed
//
// WARNING: Do NOT whitelist sensitive cvars here. In particular, NEVER add:
// rcon_password - full server takeover
// password - lock all other players out
// sv_load_ent - bypass entity sandboxing
// sys_forcegamelib - load arbitrary game DLLs
// sys_* - system-level cvars
// Whitelist only gameplay-tuning cvars.
//
// Examples:
timelimit
fraglimit
teamdm
ctf
maxclients
hostname
dmflags
roundlimit
matchmode
teamplay
g_select_empty
sv_gravity
sv_fps
sv_antilag

[allowed_stuffcmds]
// Comma-delimited list of commands that may be sent to clients via
// `lrcon stuffcmd <id|all> <command>`.
//
// SECURITY: If this section is empty or missing, all stuffcmds are DENIED.
// Without an allowlist, a claimer can stuffcmd `disconnect`, `quit`,
// arbitrary `bind`s, or chain commands — effectively RCE on every client.
//
// Recommended baseline allowlist (uncomment to enable):
// disconnect, reconnect, say, say_team, record, stoprecord

[modes]
// Server configuration modes - allows players to switch configs quickly
// Format: mode_name|exec <filename>.cfg
//
// SECURITY: mode commands MUST be of the form `exec <filename>.cfg`.
// Filenames may only contain alphanumeric characters, '_', '-', '/', '.'.
// No '..' path traversal, no absolute paths, no command chaining.
// Malformed entries are rejected at startup with a warning.
//
// Examples:
//
// teamdm|exec cfg/teamdm.cfg
// ctf|exec cfg/ctf.cfg
// ffa|exec cfg/ffa.cfg
// duel|exec cfg/1v1.cfg
// instagib|exec cfg/instagib.cfg
// campmode|exec cfg/campmode.cfg
9 changes: 9 additions & 0 deletions doc/action.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,15 @@ The forfeit system provides two ways to end a match early: captain-initiated for

**Abandonment Forfeit**: When enabled, if all teams have zero players during a match in progress (at least one round played or any team has a score), the abandon timer begins counting down. If a player reconnects and joins a team before the timer expires, it resets. Warnings are printed at 30 seconds, 10 seconds, and a final 5-second countdown. If the timer expires, the match ends with no score awarded.

#### Score Carryover

In multi-map matches (e.g., best-of-two), team scores are normally reset when the map changes. Enabling `mm_carryover` preserves team scores from the first map into the second map, so the final scoreboard reflects the cumulative result across both maps.

- Server settings:
- `mm_carryover [0/1]` - Carry over team scores from map 1 to map 2 (default: 0). Requires matchmode to be enabled. Supports `use_3teams`.

After the second map concludes, the carryover is automatically cleared. Stat logging subtracts the carried-over scores so that per-map statistics remain accurate.

#### Timeout Settings

Timeouts allow teams to pause the match for a strategic break. The following cvars control timeout behavior:
Expand Down
11 changes: 11 additions & 0 deletions src/action/a_cmds.c
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,7 @@ void RemoveSpaces(char *s)
void Cmd_AutoRecord_f(edict_t * ent)
{
char rec_date[20], recstr[MAX_QPATH];
char *p;
time_t clock;

time( &clock );
Expand All @@ -1249,6 +1250,16 @@ void Cmd_AutoRecord_f(edict_t * ent)
Q_snprintf(recstr, sizeof(recstr), "%s-%s", rec_date, level.mapname);
}

/* Belt-and-suspenders: even though teamname intake sanitizes, scrub anything
* that could break out of the quoted stuffcmd arg (recstr also includes
* level.mapname which is engine-controlled but cheap to harden). */
for (p = recstr; *p; p++) {
if (*p == '"' || *p == '\\' || *p == '\n' || *p == '\r' ||
*p == ';' || *p == '$' || (unsigned char)*p < 0x20) {
*p = '_';
}
}

stuffcmd(ent, va("record \"%s\"\n", recstr));
}

Expand Down
4 changes: 0 additions & 4 deletions src/action/a_esp.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
// This is set to 1 if either atl or etv are 1
extern cvar_t *esp;

// Discrete game modes
extern cvar_t *atl;
extern cvar_t *etv;

#define IS_LEADER(ent) (teams[(ent)->client->resp.team].leader == (ent))
#define HAVE_LEADER(teamNum) (teams[(teamNum)].leader)
#define MAX_ESP_STRLEN 32
Expand Down
87 changes: 80 additions & 7 deletions src/action/a_game.c
Original file line number Diff line number Diff line change
Expand Up @@ -1659,13 +1659,23 @@ void ReadLrconConfig(void)
game.lrcon_config.quit_on_empty = 0;
game.lrcon_config.allowed_cvars_count = 0;
game.lrcon_config.modes_count = 0;
game.lrcon_config.allowed_stuffcmds_count = 0;

// Get config filename from cvar
// Get config filename from cvar.
// Validate value: must be a plain filename within the action/ directory.
// Without this, lrcon_config "../../../etc/crontab" would open arbitrary
// filesystem paths. Also replaces unbounded sprintf with Q_snprintf.
lrcon_config_cvar = gi.cvar("lrcon_config", "lrcon.cfg", 0);
if (lrcon_config_cvar->string && *(lrcon_config_cvar->string))
sprintf(cfgpath, "%s/%s", GAMEVERSION, lrcon_config_cvar->string);
else
sprintf(cfgpath, "%s/%s", GAMEVERSION, "lrcon.cfg");
{
const char *name = (lrcon_config_cvar->string && *lrcon_config_cvar->string)
? lrcon_config_cvar->string : "lrcon.cfg";
if (strstr(name, "..") || strchr(name, '/') || strchr(name, '\\') ||
strchr(name, ':')) {
gi.dprintf("LRCON: refusing lrcon_config '%s' — must be a plain filename within action/\n", name);
return;
}
Q_snprintf(cfgpath, sizeof(cfgpath), "%s/%s", GAMEVERSION, name);
}

// Try to open config file
config_file = fopen(cfgpath, "r");
Expand Down Expand Up @@ -1738,15 +1748,78 @@ void ReadLrconConfig(void)
game.lrcon_config.allowed_cvars[game.lrcon_config.allowed_cvars_count]);
game.lrcon_config.allowed_cvars_count++;
}
} else if (!strcmp(reading_section, "allowed_stuffcmds")) {
// Comma-delimited list of commands allowed via `lrcon stuffcmd`.
// Why: without an allowlist, a claimer can stuffcmd `disconnect`,
// `quit`, arbitrary `bind`s, or chain commands via ';' — effectively
// RCE on every connected client.
char *tok, *saveptr_buf = buf;
while ((tok = strtok(saveptr_buf, ", \t")) != NULL) {
saveptr_buf = NULL;
if (game.lrcon_config.allowed_stuffcmds_count >= MAX_LRCON_STUFFCMDS)
break;
if (!*tok)
continue;
Q_strncpyz(game.lrcon_config.allowed_stuffcmds[game.lrcon_config.allowed_stuffcmds_count],
tok, sizeof(game.lrcon_config.allowed_stuffcmds[0]));
gi.dprintf("LRCON: allowed stuffcmd %d = %s\n",
game.lrcon_config.allowed_stuffcmds_count,
game.lrcon_config.allowed_stuffcmds[game.lrcon_config.allowed_stuffcmds_count]);
game.lrcon_config.allowed_stuffcmds_count++;
}
} else if (!strcmp(reading_section, "modes")) {
// Format: name|command
// Format: name|exec <filename.cfg>
// Why: mode command is passed verbatim to AddCommandString.
// Without restriction, an operator (or compromised config) can
// embed arbitrary commands via ';'. Restrict to strict
// "exec <safe-filename>.cfg" form.
char *pipe = strchr(buf, '|');
if (pipe != NULL && game.lrcon_config.modes_count < MAX_LRCON_MODES) {
const char *cmd, *fname;
size_t flen;
qboolean valid = true;

*pipe = 0;
cmd = pipe + 1;

// Must begin with "exec "
if (Q_strncasecmp(cmd, "exec ", 5) != 0) {
gi.dprintf("LRCON: rejecting mode '%s' — command must start with 'exec '\n", buf);
valid = false;
}

if (valid) {
fname = cmd + 5;
while (*fname == ' ') fname++;
flen = strlen(fname);

// Filename rules: non-empty, ends in .cfg, no traversal,
// only Q_ispath() chars plus '/' and '.'
if (flen < 5 || strcmp(fname + flen - 4, ".cfg") != 0) {
gi.dprintf("LRCON: rejecting mode '%s' — filename must end in .cfg\n", buf);
valid = false;
} else if (strstr(fname, "..") || fname[0] == '/' || fname[0] == '\\') {
gi.dprintf("LRCON: rejecting mode '%s' — filename has traversal or absolute path\n", buf);
valid = false;
} else {
const char *p;
for (p = fname; *p; p++) {
if (!(Q_ispath(*p) || *p == '/' || *p == '.')) {
gi.dprintf("LRCON: rejecting mode '%s' — filename has disallowed char\n", buf);
valid = false;
break;
}
}
}
}

if (!valid)
continue;

Q_strncpyz(game.lrcon_config.modes[game.lrcon_config.modes_count].name,
buf, sizeof(game.lrcon_config.modes[0].name));
Q_strncpyz(game.lrcon_config.modes[game.lrcon_config.modes_count].command,
pipe + 1, sizeof(game.lrcon_config.modes[0].command));
cmd, sizeof(game.lrcon_config.modes[0].command));
gi.dprintf("LRCON: mode %d = %s -> %s\n",
game.lrcon_config.modes_count,
game.lrcon_config.modes[game.lrcon_config.modes_count].name,
Expand Down
49 changes: 47 additions & 2 deletions src/action/a_match.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ void SendScores(void)
// Stats: Reset roundNum
game.roundNum = 0;
// Stats end

// Clear carryover scores after map 2 is done.
// Keep carryover_active=true so ExitLevel() won't re-save scores.
// SpawnEntities() on map 3 will reset carryover_active when it finds no scores.
if (game.carryover_active)
{
int i;
for(i = TEAM1; i < TEAM_TOP; i++)
game.carryover_scores[i] = 0;
gi.dprintf("Matchmode carryover: cleared after map 2\n");
}
}

void Cmd_Sub_f(edict_t * ent)
Expand Down Expand Up @@ -410,6 +421,13 @@ qboolean CheckAbandon(void)
if (!matchmode->value || !use_forfeit->value)
return false;

/* Treat forfeit_abandon_time <= 0 as "abandonment detection disabled".
* Without this guard, abandonFrames becomes 0 in the registration path,
* the `!level.abandonFrames` branch fires every server tick, and the
* announcement spams the log forever. */
if (forfeit_abandon_time->value <= 0)
return false;

if (!team_game_going)
return false;

Expand Down Expand Up @@ -484,6 +502,22 @@ qboolean CheckAbandon(void)
return false;
}

/*
* Replace shell/stuffcmd-dangerous characters with '_' in-place.
* Why: team names are echoed into stuffcmd'd console commands (autorecord,
* etc.). An unescaped '"', ';', '\n', or '$' lets a captain inject commands
* into every other player's console.
*/
static void sanitize_command_arg(char *s)
{
for (; *s; s++) {
if (*s == '"' || *s == '\\' || *s == '\n' || *s == '\r' ||
*s == ';' || *s == '$' || (unsigned char)*s < 0x20) {
*s = '_';
}
}
}

void Cmd_Teamname_f(edict_t * ent)
{
int i, argc, teamNum;
Expand Down Expand Up @@ -544,6 +578,11 @@ void Cmd_Teamname_f(edict_t * ent)
temp[18] = 0;
}

if (!temp[0])
strcpy( temp, "noname" );

sanitize_command_arg(temp);

if (!temp[0])
strcpy( temp, "noname" );

Expand Down Expand Up @@ -645,7 +684,10 @@ void Cmd_Teamnone_f(edict_t *ent)
return;
}

if (gi.argc() < 1) {
/* gi.argc() always returns at least 1 (the command name itself), so the
* previous `< 1` guard was dead code — missing-arg silently fell through
* with playernum=0 from atoi(""). */
if (gi.argc() < 2) {
gi.cprintf(ent, PRINT_HIGH, "You need to provide a playernum for this command\nUse 'playerlist' to get a list of playernums\n");
return;
}
Expand Down Expand Up @@ -897,7 +939,10 @@ void Cmd_CallTimeout_f(edict_t * ent)
return;
}

if (level.matchTime >= timelimit->value * 60) {
/* Skip the last-round guard when timelimit is unlimited (0). Otherwise
* `matchTime >= 0` is always true and timeouts are blocked permanently
* on unlimited-time servers. */
if (timelimit->value > 0 && level.matchTime >= timelimit->value * 60) {
gi.cprintf(ent, PRINT_HIGH, "You cannot call for a timeout on the last round of the match\n");
return;
}
Expand Down
11 changes: 9 additions & 2 deletions src/action/g_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,7 @@ typedef struct precache_s {

#define MAX_LRCON_CVARS 32
#define MAX_LRCON_MODES 16
#define MAX_LRCON_STUFFCMDS 16

/* LRCON state - tracks current server claim */
typedef struct {
Expand All @@ -817,6 +818,8 @@ typedef struct {
char allowed_cvars[MAX_LRCON_CVARS][64]; /* Whitelisted cvar names */
int modes_count; /* Number of available modes */
lrcon_mode_t modes[MAX_LRCON_MODES]; /* Available server modes */
int allowed_stuffcmds_count; /* Number of allowlisted client stuffcmds */
char allowed_stuffcmds[MAX_LRCON_STUFFCMDS][32]; /* Allowlisted commands for `lrcon stuffcmd` */
} lrcon_config_t;

//
Expand Down Expand Up @@ -879,6 +882,10 @@ typedef struct

// LRCON configuration
lrcon_config_t lrcon_config;

// Matchmode carryover scores (persist across map changes)
int carryover_scores[TEAM_TOP];
qboolean carryover_active; // true if we're on map 2 with carried-over scores
}
game_locals_t;

Expand Down Expand Up @@ -972,6 +979,7 @@ typedef struct
int timeoutFrames;
float matchTime;
float emptyTime;
float quit_empty_time; // LRCON quit_on_empty: level.time when server first emptied, or -1 if not empty. Separate from emptyTime which empty_rotate uses as an accumulator.
int abandonFrames; // Countdown for abandon forfeit
int weapon_sound_framenum;
int pic_teamplay_timer_icon;
Expand Down Expand Up @@ -1392,8 +1400,6 @@ extern cvar_t *medkit_value;

// BEGIN AQ2 ETE
extern cvar_t *esp; // Enable or disable Espionage mode
extern cvar_t *atl; // Enable or disable Assassinate the Leader mode (do not set this manually)
extern cvar_t *etv; // Enable or disable Escort the VIP mode (do not set this manually)
extern cvar_t *esp_atl; // Prefer ATL mode even if ETV mode is available
extern cvar_t *esp_punish; // Enable or disable punishment for losing the around
extern cvar_t *esp_etv_halftime; // Enable or disable halftime in ETV mode
Expand Down Expand Up @@ -1451,6 +1457,7 @@ extern cvar_t *bots; // If bots are enabled and in the server

// 2026
extern cvar_t *use_buggy_ent_hitbox;
extern cvar_t *mm_carryover; // Carry over team scores across maps in matchmode

#ifdef AQTION_EXTENSION
extern int (*engine_Client_GetVersion)(edict_t *ent);
Expand Down
Loading
Loading