diff --git a/AMBuilder b/AMBuilder
index 033d025c..b79020ae 100644
--- a/AMBuilder
+++ b/AMBuilder
@@ -70,6 +70,8 @@ for sdk_target in MMSPlugin.sdk_targets:
'src/buttonwatch.cpp',
'src/topdefender.cpp',
'src/idlemanager.cpp',
+ 'src/cvarwhitelist.cpp',
+ 'src/cfgparser.cpp',
'src/mapmigrations.cpp',
'sdk/entity2/entitysystem.cpp',
'sdk/entity2/entityidentity.cpp',
diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj
index d82eb31b..9b0b1648 100644
--- a/CS2Fixes.vcxproj
+++ b/CS2Fixes.vcxproj
@@ -213,6 +213,8 @@
+
+
@@ -291,6 +293,8 @@
+
+
diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters
index 23ee92a5..ecb355ea 100644
--- a/CS2Fixes.vcxproj.filters
+++ b/CS2Fixes.vcxproj.filters
@@ -179,6 +179,12 @@
Source Files
+
+ Source Files
+
+
+ Source Files
+
Source Files\utils
@@ -394,6 +400,12 @@
Header Files
+
+ Header Files
+
+
+ Header Files
+
Header Files\cs2_sdk\entity
diff --git a/PackageScript b/PackageScript
index 6e96f562..b68cbc84 100644
--- a/PackageScript
+++ b/PackageScript
@@ -115,7 +115,9 @@ for task in MMSPlugin.binaries:
builder.AddCopy(os.path.join('configs', 'admins.jsonc.example'), configs_folder)
builder.AddCopy(os.path.join('configs', 'discordbots.jsonc.example'), configs_folder)
builder.AddCopy(os.path.join('configs', 'maplist.jsonc.example'), configs_folder)
+ builder.AddCopy(os.path.join('configs', 'cvar_whitelist.jsonc.example'), configs_folder)
builder.AddCopy(os.path.join('cfg', MMSPlugin.metadata['name'], 'cs2fixes.cfg'), cfg_folder)
+ builder.AddCopy(os.path.join('cfg', MMSPlugin.metadata['name'], 'server.cfg.example'), cfg_folder)
builder.AddCopy(os.path.join('cfg', MMSPlugin.metadata['name'], 'maps', 'de_somemap.cfg'), mapcfg_folder)
builder.AddCopy(os.path.join('configs', 'zr', 'playerclass.jsonc.example'), zr_folder)
builder.AddCopy(os.path.join('configs', 'zr', 'weapons.cfg.example'), zr_folder)
diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg
index e7cdac87..3efc1a4a 100644
--- a/cfg/cs2fixes/cs2fixes.cfg
+++ b/cfg/cs2fixes/cs2fixes.cfg
@@ -24,6 +24,7 @@ cs2f_prevent_using_players 0 // Whether to prevent +use from hitting players (
cs2f_map_steamids_enable 0 // Whether to make Steam ID's available to maps
cs2f_fix_game_bans 0 // Whether to fix CS2 game bans spreading to all new joining players
cs2f_free_armor 0 // Whether kevlar (1+) and/or helmet (2) are given automatically
+cs2f_cvarwhitelist_enable 0 // Whether to enable the custom ConVar whitelist for maps
cs2f_block_particle_msgs 0 // Whether to block CUserMsg_ParticleManager messages to fix lag/crashes, experimental
cs2f_disable_setmodel 0 // Whether to disable SetModel usage from maps (custom input, cs_script function)
cs2f_allow_duck_spam 0 // Whether to allow duck spamming by removing the duck slowdown, clients will only partially predict [0 = disabled, 1 = both teams, 2 = T only, 3 = CT only]
diff --git a/cfg/cs2fixes/server.cfg.example b/cfg/cs2fixes/server.cfg.example
new file mode 100644
index 00000000..927a578f
--- /dev/null
+++ b/cfg/cs2fixes/server.cfg.example
@@ -0,0 +1,3 @@
+// This config is executed every map start like cfg/server.cfg, but fixes a bug for properly bypassing the ConVar whitelist
+// Like exec, use exec_custom to execute another cfg through this custom cfg parser:
+// exec_custom gamemode_casual_custom
diff --git a/configs/cvar_whitelist.jsonc.example b/configs/cvar_whitelist.jsonc.example
new file mode 100644
index 00000000..e2efb939
--- /dev/null
+++ b/configs/cvar_whitelist.jsonc.example
@@ -0,0 +1,563 @@
+{
+ // Since maps can change cvars that are enabled here, make sure they all get reset to desired values on map change by server config(s)!
+
+ /* Map specific overrides can also be set up as so:
+ "ze_my_first_ze_map":
+ {
+ "sv_airaccelerate": false,
+ "sv_noclipspeed": true
+ },
+ "ze_last_man_standing_p":
+ {
+ "zr_infect_spawn_mz_ratio": true
+ },
+ */
+
+ // Default whitelist, from workshop_cvar_whitelist.txt
+ // We disable several of these in our default config, because they don't make sense to be whitelisted, explanations are commented
+ "ammo_grenade_limit_default": true,
+ "ammo_grenade_limit_flashbang": true,
+ "ammo_grenade_limit_total": true,
+ "ammo_item_limit_healthshot": true,
+ "annotation_auto_load": true,
+ "annotation_load": true,
+ "bot_add": false, // Not suitable for Zombie Escape servers, bots usually not supported outside of debug
+ "bot_add_ct": false, // Not suitable for Zombie Escape servers, bots usually not supported outside of debug
+ "bot_add_t": false, // Not suitable for Zombie Escape servers, bots usually not supported outside of debug
+ "bot_allow_grenades": true,
+ "bot_allow_machine_guns": true,
+ "bot_allow_pistols": true,
+ "bot_allow_rifles": true,
+ "bot_allow_rogues": true,
+ "bot_allow_shotguns": true,
+ "bot_allow_snipers": true,
+ "bot_allow_sub_machine_guns": true,
+ "bot_auto_vacate": true,
+ "bot_autodifficulty_threshold_high": true,
+ "bot_autodifficulty_threshold_low": true,
+ "bot_chatter": true,
+ "bot_controllable": true,
+ "bot_coop_idle_max_vision_distance": true,
+ "bot_crouch": true,
+ "bot_defer_to_human_goals": true,
+ "bot_defer_to_human_items": true,
+ "bot_difficulty": true,
+ "bot_dont_shoot": true,
+ "bot_eco_limit": true,
+ "bot_flipout": true,
+ "bot_force_duck": true,
+ "bot_freeze": true,
+ "bot_ignore_enemies": true,
+ "bot_ignore_players": true,
+ "bot_join_after_player": true,
+ "bot_join_delay": true,
+ "bot_join_in_warmup": true,
+ "bot_join_team": true,
+ "bot_kick": true,
+ "bot_kill": true,
+ "bot_knives_only": true,
+ "bot_max_visible_smoke_length": true,
+ "bot_max_vision_distance_override": true,
+ "bot_mimic": true,
+ "bot_prefix": true,
+ "bot_quota": false, // Not suitable for Zombie Escape servers, bots usually not supported outside of debug
+ "bot_quota_mode": true,
+ "bot_stop": true,
+ "bot_walk": true,
+ "bot_zombie": true,
+ "buddha": true,
+ "buddha_reset_hp": true,
+ "callvote": true,
+ "cash_player_bomb_defused": true,
+ "cash_player_bomb_planted": true,
+ "cash_player_damage_hostage": true,
+ "cash_player_get_killed": true,
+ "cash_player_interact_with_hostage": true,
+ "cash_player_killed_enemy_default": true,
+ "cash_player_killed_enemy_factor": true,
+ "cash_player_killed_hostage": true,
+ "cash_player_killed_teammate": true,
+ "cash_player_rescued_hostage": true,
+ "cash_player_respawn_amount": true,
+ "cash_team_bonus_shorthanded": true,
+ "cash_team_elimination_bomb_map": true,
+ "cash_team_elimination_hostage_map_ct": true,
+ "cash_team_elimination_hostage_map_t": true,
+ "cash_team_hostage_alive": true,
+ "cash_team_hostage_interaction": true,
+ "cash_team_loser_bonus": true,
+ "cash_team_loser_bonus_consecutive_rounds": true,
+ "cash_team_per_dead_enemy": true,
+ "cash_team_planted_bomb_but_defused": true,
+ "cash_team_rescued_hostage": true,
+ "cash_team_terrorist_win_bomb": true,
+ "cash_team_win_by_defusing_bomb": true,
+ "cash_team_win_by_hostage_rescue": true,
+ "cash_team_win_by_time_running_out_bomb": true,
+ "cash_team_win_by_time_running_out_hostage": true,
+ "cl_crosshair_drawoutline": true,
+ "cl_crosshair_dynamic_maxdist_splitratio": true,
+ "cl_crosshair_dynamic_splitalpha_innermod": true,
+ "cl_crosshair_dynamic_splitalpha_outermod": true,
+ "cl_crosshair_dynamic_splitdist": true,
+ "cl_crosshair_friendly_warning": true,
+ "cl_crosshair_outlinethickness": true,
+ "cl_crosshair_recoil": true,
+ "cl_crosshair_sniper_show_normal_inaccuracy": true,
+ "cl_crosshair_sniper_width": true,
+ "cl_crosshair_t": true,
+ "cl_crosshairalpha": true,
+ "cl_crosshaircolor": true,
+ "cl_crosshaircolor_b": true,
+ "cl_crosshaircolor_g": true,
+ "cl_crosshaircolor_r": true,
+ "cl_crosshairdot": true,
+ "cl_crosshairgap": true,
+ "cl_crosshairgap_useweaponvalue": true,
+ "cl_crosshairsize": true,
+ "cl_crosshairstyle": true,
+ "cl_crosshairthickness": true,
+ "cl_crosshairusealpha": true,
+ "cl_draw_only_deathnotices": true,
+ "cl_drawhud": true,
+ "cl_drawhud_force_deathnotices": true,
+ "cl_frametime_summary_report_detailed": true,
+ "cl_hud_color": true,
+ "cl_hud_radar_scale": true,
+ "cl_hud_telemetry_frametime_show": true,
+ "cl_lock_camera": true,
+ "cl_player_ragdolls_collide": true,
+ "cl_prefer_lefthanded": true,
+ "cl_radar_always_centered": true,
+ "cl_radar_icon_scale_min": true,
+ "cl_radar_rotate": true,
+ "cl_radar_scale": true,
+ "cl_radar_square_with_scoreboard": true,
+ "cl_showfps": true,
+ "cl_teamcounter_playercount_instead_of_avatars": true,
+ "cl_teamid_overhead_colors_show": true,
+ "contributionscore_assist": true,
+ "contributionscore_assist_reqs": true,
+ "contributionscore_bomb_defuse_major": true,
+ "contributionscore_bomb_defuse_minor": true,
+ "contributionscore_bomb_exploded": true,
+ "contributionscore_bomb_planted": true,
+ "contributionscore_cash_bundle": true,
+ "contributionscore_crate_break": true,
+ "contributionscore_hostage_kill": true,
+ "contributionscore_hostage_rescue_major": true,
+ "contributionscore_hostage_rescue_minor": true,
+ "contributionscore_kill": true,
+ "contributionscore_kill_factor": true,
+ "contributionscore_kill_reqs": true,
+ "contributionscore_objective_kill": true,
+ "contributionscore_participation": true,
+ "contributionscore_suicide": true,
+ "contributionscore_team_kill": true,
+ "crosshair": true,
+ "custom_bot_difficulty": true,
+ "disconnect": true,
+ "dsp_volume": true,
+ "echo": true,
+ "ent_create": true,
+ "ent_fire": true,
+ "ent_fire_output": true,
+ "ent_setname": true,
+ "exec": true,
+ "execifexists": true,
+ "ff_damage_bullet_penetration": true,
+ "ff_damage_reduction_bullets": true,
+ "ff_damage_reduction_grenade": true,
+ "ff_damage_reduction_grenade_self": true,
+ "ff_damage_reduction_other": true,
+ "fps_max": true,
+ "game_alias": false, // Server command, should not be controlled by maps
+ "game_mode": false, // Server setting, should not be controlled by maps
+ "game_type": false, // Server setting, should not be controlled by maps
+ "give": true,
+ "god": true,
+ "healthshot_allow_use_at_full": true,
+ "healthshot_health": true,
+ "healthshot_healthboost_damage_multiplier": true,
+ "healthshot_healthboost_speed_multiplier": true,
+ "healthshot_healthboost_time": true,
+ "hinttext_displaytime": true,
+ "host_timescale": true,
+ "host_writeconfig_with_prompt": true,
+ "hostage_debug": true,
+ "hostname": false, // Server setting, should not be controlled by maps
+ "hud_scaling": true,
+ "hud_showtargetid": true,
+ "inferno_child_spawn_max_depth": true,
+ "inferno_max_flames": true,
+ "inferno_max_range": true,
+ "log_color": false, // Server command, should not be controlled by maps
+ "log_flags": false, // Server command, should not be controlled by maps
+ "log_verbosity": false, // Server command, should not be controlled by maps
+ "map_workshop": false, // Server command, should not be executed by maps
+ "molotov_throw_detonate_time": true,
+ "mp_afterroundmoney": true,
+ "mp_allowspectators": true,
+ "mp_anyone_can_pickup_c4": true,
+ "mp_autokick": false, // Server setting, should not be controlled by maps
+ "mp_autoteambalance": false, // Not suitable for Zombie Escape servers
+ "mp_bot_ai_bt": true,
+ "mp_bot_ai_bt_clear_cache": true,
+ "mp_buy_allow_grenades": true,
+ "mp_buy_allow_guns": true,
+ "mp_buy_anywhere": true,
+ "mp_buy_during_immunity": true,
+ "mp_buytime": true,
+ "mp_c4_cannot_be_defused": true,
+ "mp_c4timer": true,
+ "mp_consecutive_loss_max": true,
+ "mp_coop_force_join_t": true,
+ "mp_coopmission_bot_difficulty_offset": true,
+ "mp_ct_default_grenades": true,
+ "mp_ct_default_melee": true,
+ "mp_ct_default_primary": true,
+ "mp_ct_default_secondary": true,
+ "mp_damage_headshot_only": true,
+ "mp_damage_scale_ct_body": true,
+ "mp_damage_scale_ct_head": true,
+ "mp_damage_scale_t_body": true,
+ "mp_damage_scale_t_head": true,
+ "mp_damage_vampiric_amount": true,
+ "mp_death_drop_c4": true,
+ "mp_death_drop_defuser": true,
+ "mp_death_drop_grenade": true,
+ "mp_death_drop_gun": true,
+ "mp_death_drop_healthshot": true,
+ "mp_deathcam_skippable": true,
+ "mp_default_team_winner_no_objective": true,
+ "mp_defuser_allocation": true,
+ "mp_disconnect_kills_bots": false, // Server setting, should not be controlled by maps
+ "mp_disconnect_kills_players": false, // Server setting, should not be controlled by maps
+ "mp_display_kill_assists": true,
+ "mp_dm_bonus_percent": true,
+ "mp_dm_bonusweapon_dogtags": true,
+ "mp_dm_dogtag_score": true,
+ "mp_dm_kill_base_score": true,
+ "mp_dm_teammode": true,
+ "mp_dm_teammode_bonus_score": true,
+ "mp_dm_teammode_dogtag_score": true,
+ "mp_dm_teammode_kill_score": true,
+ "mp_dogtag_despawn_on_killer_death": true,
+ "mp_dogtag_despawn_time": true,
+ "mp_dogtag_pickup_rule": true,
+ "mp_drop_grenade_enable": true,
+ "mp_drop_knife_enable": true,
+ "mp_economy_reset_rounds": true,
+ "mp_endmatch_votenextleveltime": false, // Server setting, should not be controlled by maps
+ "mp_endmatch_votenextmap": false, // Server setting, should not be controlled by maps
+ "mp_endmatch_votenextmap_keepcurrent": false, // Server setting, should not be controlled by maps
+ "mp_equipment_reset_rounds": true,
+ "mp_force_pick_time": false, // Server setting, should not be controlled by maps
+ "mp_forcecamera": true,
+ "mp_forcerespawn": true,
+ "mp_fraglimit": false, // Server setting, should not be controlled by maps
+ "mp_free_armor": false, // Breaks buy menu rebuys, cs2f_free_armor doesn't
+ "mp_freezetime": true,
+ "mp_friendlyfire": true,
+ "mp_give_player_c4": true,
+ "mp_global_damage_per_second": true,
+ "mp_guardian_ai_bt_difficulty_adjust_wave_interval": true,
+ "mp_guardian_ai_bt_difficulty_cap_beginning_round": true,
+ "mp_guardian_ai_bt_difficulty_initial_value": true,
+ "mp_guardian_ai_bt_difficulty_max_next_level_bots": true,
+ "mp_guardian_bomb_plant_custom_x_mark_location": true,
+ "mp_guardian_bot_money_per_wave": true,
+ "mp_guardian_player_dist_max": true,
+ "mp_guardian_player_dist_min": true,
+ "mp_guardian_special_kills_needed": true,
+ "mp_guardian_special_weapon_needed": true,
+ "mp_guardian_target_site": true,
+ "mp_halftime": false, // Not suitable for Zombie Escape servers
+ "mp_halftime_pausetimer": true,
+ "mp_hostagepenalty": true,
+ "mp_hostages_max": true,
+ "mp_hostages_rescuetime": true,
+ "mp_hostages_spawn_force_positions": true,
+ "mp_hostages_spawn_force_positions_xyz": true,
+ "mp_hostages_spawn_same_every_round": true,
+ "mp_humanteam": false, // Not suitable for Zombie Escape servers
+ "mp_ignore_round_win_conditions": false, // Not suitable for Zombie Escape servers
+ "mp_items_prohibited": true,
+ "mp_join_grace_time": false, // Not suitable for Zombie Escape servers
+ "mp_limitteams": false, // Not suitable for Zombie Escape servers
+ "mp_match_can_clinch": false, // Server setting, should not be controlled by maps
+ "mp_match_end_changelevel": false, // Server setting, should not be controlled by maps
+ "mp_match_end_restart": false, // Server setting, should not be controlled by maps
+ "mp_match_restart_delay": false, // Server setting, should not be controlled by maps
+ "mp_max_armor": true,
+ "mp_maxmoney": true,
+ "mp_maxrounds": false, // Server setting, should not be controlled by maps
+ "mp_only_cts_rescue_hostages": true,
+ "mp_plant_c4_anywhere": true,
+ "mp_playercashawards": false, // Not suitable for Zombie Escape servers
+ "mp_promoted_item_enabled": true,
+ "mp_randomspawn": true,
+ "mp_randomspawn_dist": true,
+ "mp_randomspawn_los": true,
+ "mp_respawn_immunitytime": true,
+ "mp_respawn_on_death_ct": false, // Not suitable for Zombie Escape servers
+ "mp_respawn_on_death_t": false, // Not suitable for Zombie Escape servers
+ "mp_respawnwavetime_ct": true,
+ "mp_respawnwavetime_t": true,
+ "mp_restartgame": false, // Also resets timeleft, which should not be controlled by maps, just use a nuke or an info_map_parameters>FireWinCondition>10 input (won't add score to either team)
+ "mp_retake_ct_loadout_bonus_card": true,
+ "mp_retake_ct_loadout_bonus_card_availability": true,
+ "mp_retake_ct_loadout_default_pistol_round": true,
+ "mp_retake_ct_loadout_enemy_card": true,
+ "mp_retake_ct_loadout_full_buy_round": true,
+ "mp_retake_ct_loadout_light_buy_round": true,
+ "mp_retake_ct_loadout_upgraded_pistol_round": true,
+ "mp_retake_max_consecutive_rounds_same_target_site": true,
+ "mp_retake_t_loadout_bonus_card": true,
+ "mp_retake_t_loadout_bonus_card_availability": true,
+ "mp_retake_t_loadout_default_pistol_round": true,
+ "mp_retake_t_loadout_enemy_card": true,
+ "mp_retake_t_loadout_full_buy_round": true,
+ "mp_retake_t_loadout_light_buy_round": true,
+ "mp_retake_t_loadout_upgraded_pistol_round": true,
+ "mp_round_restart_delay": true,
+ "mp_roundtime": true,
+ "mp_roundtime_defuse": true,
+ "mp_roundtime_deployment": true,
+ "mp_roundtime_hostage": true,
+ "mp_solid_enemies": true,
+ "mp_solid_teammates": true,
+ "mp_spawnprotectiontime": false, // Server setting, should not be controlled by maps
+ "mp_spectators_max": true,
+ "mp_starting_losses": true,
+ "mp_startmoney": true,
+ "mp_suicide_penalty": true,
+ "mp_t_default_grenades": true,
+ "mp_t_default_melee": true,
+ "mp_t_default_primary": true,
+ "mp_t_default_secondary": true,
+ "mp_tagging_scale": false, // Server setting, should not be controlled by maps
+ "mp_taser_recharge_time": true,
+ "mp_team_intro_time": false, // Server setting, should not be controlled by maps
+ "mp_teamcashawards": true,
+ "mp_teammatchstat_holdtime": true,
+ "mp_teammates_are_enemies": true,
+ "mp_teamname_1": true,
+ "mp_teamname_2": true,
+ "mp_technical_timeout_duration_s": true,
+ "mp_technical_timeout_per_team": true,
+ "mp_timelimit": false, // Server setting, should not be controlled by maps
+ "mp_use_respawn_waves": false, // Not suitable for Zombie Escape servers
+ "mp_warmup_end": true,
+ "mp_warmup_items_drop_policy": true,
+ "mp_warmup_items_nocost": true,
+ "mp_warmup_items_nocount_policy": true,
+ "mp_warmup_offline_enabled": true,
+ "mp_warmup_online_enabled": true,
+ "mp_warmup_pausetimer": true,
+ "mp_warmuptime": true,
+ "mp_warmuptime_all_players_connected": true,
+ "mp_warmuptime_match_cancelled": true,
+ "mp_weapon_self_inflict_amount": true,
+ "mp_weapons_allow_heavy": true,
+ "mp_weapons_allow_heavyassaultsuit": true,
+ "mp_weapons_allow_map_placed": true,
+ "mp_weapons_allow_pistols": true,
+ "mp_weapons_allow_rifles": true,
+ "mp_weapons_allow_smgs": true,
+ "mp_weapons_allow_typecount": true,
+ "mp_weapons_allow_zeus": true,
+ "mp_weapons_max_gun_purchases_per_weapon_per_match": true,
+ "mp_win_panel_display_time": true,
+ "noclip": true,
+ "player_ping_token_cooldown": true,
+ "r_csgo_render_decals": true,
+ "r_csgo_render_decals_on_translucent": true,
+ "r_decals_overide_fadestarttime_params": true,
+ "r_drawviewmodel": true,
+ "r_fullscreen_gamma": true,
+ "regenerate_weapon_skins": true,
+ "restart": true,
+ "safezonex": true,
+ "safezoney": true,
+ "say": true,
+ "say_team": true,
+ "setang": true,
+ "setpos": true,
+ "slot1": true,
+ "slot2": true,
+ "slot3": true,
+ "slot4": true,
+ "slot5": true,
+ "slot6": true,
+ "slot7": true,
+ "slot8": true,
+ "slot9": true,
+ "snd_deathcamera_volume": true,
+ "snd_gamevolume": true,
+ "snd_headphone_eq": true,
+ "snd_mapobjective_volume": true,
+ "snd_menumusic_volume": true,
+ "snd_musicvolume": true,
+ "snd_mvp_volume": true,
+ "snd_roundaction_volume": true,
+ "snd_roundend_volume": true,
+ "snd_roundstart_volume": true,
+ "snd_spatialize_lerp": true,
+ "snd_steamaudio_enable_perspective_correction": true,
+ "snd_tensecondwarning_volume": true,
+ "spawn_group_load": true,
+ "spawn_group_unload": true,
+ "speaker_config": true,
+ "spec_freeze_deathanim_time": true,
+ "spec_freeze_time": true,
+ "spec_freeze_time_lock": true,
+ "spec_freeze_traveltime": true,
+ "spec_replay_bot": true,
+ "spec_replay_enable": false, // Server setting, should not be controlled by maps
+ "spec_replay_leadup_time": true,
+ "subclass_create": true,
+ "sv_accelerate": true,
+ "sv_accelerate_use_weapon_speed": true,
+ "sv_airaccelerate": true,
+ "sv_allow_annotations_access_level": true,
+ "sv_allow_votes": false, // Server setting, should not be controlled by maps
+ "sv_alltalk": false, // Server setting, should not be controlled by maps
+ "sv_alternateticks": true,
+ "sv_auto_adjust_bot_difficulty": true,
+ "sv_auto_full_alltalk_during_warmup_half_end": true,
+ "sv_autobunnyhopping": true,
+ "sv_autobuyammo": true,
+ "sv_autoexec_mapname_cfg": true,
+ "sv_bot_buy_decoy_weight": true,
+ "sv_bot_buy_flash_weight": true,
+ "sv_bot_buy_grenade_chance": true,
+ "sv_bot_buy_hegrenade_weight": true,
+ "sv_bot_buy_molotov_weight": true,
+ "sv_bot_buy_smoke_weight": true,
+ "sv_bots_get_easier_each_win": true,
+ "sv_bounce": true,
+ "sv_buy_status_override": true,
+ "sv_cheats": false, // Server setting, should not be controlled by maps
+ "sv_deadtalk": false, // Server setting, should not be controlled by maps
+ "sv_disable_immunity_alpha": true,
+ "sv_disable_radar": true,
+ "sv_disable_teamselect_menu": false, // Server setting, should not be controlled by maps
+ "sv_disconnected_players_cleanup_delay": false, // Server setting, should not be controlled by maps
+ "sv_enablebunnyhopping": true,
+ "sv_extract_ammo_from_dropped_weapons": true,
+ "sv_falldamage_scale": true,
+ "sv_falldamage_to_below_player_multiplier": true,
+ "sv_falldamage_to_below_player_ratio": true,
+ "sv_friction": true,
+ "sv_game_mode_flags": false, // Server setting, should not be controlled by maps
+ "sv_gameinstructor_disable": true,
+ "sv_gameinstructor_enable": true,
+ "sv_give_item": true,
+ "sv_gravity": true,
+ "sv_grenade_trajectory_prac_pipreview": true,
+ "sv_grenade_trajectory_prac_trailtime": true,
+ "sv_grenade_trajectory_time_spectator": true,
+ "sv_guardian_extra_equipment_ct": true,
+ "sv_guardian_extra_equipment_t": true,
+ "sv_guardian_refresh_ammo_for_items_on_waves": true,
+ "sv_guardian_spawn_health_ct": true,
+ "sv_guardian_spawn_health_t": true,
+ "sv_health_approach_enabled": true,
+ "sv_health_approach_speed": true,
+ "sv_hegrenade_damage_multiplier": true,
+ "sv_hegrenade_radius_multiplier": true,
+ "sv_hide_roundtime_until_seconds": true,
+ "sv_highlight_distance": true,
+ "sv_highlight_duration": true,
+ "sv_human_autojoin_team": false, // Server setting, should not be controlled by maps
+ "sv_ignoregrenaderadio": true,
+ "sv_infinite_ammo": true,
+ "sv_jump_impulse": true,
+ "sv_jump_spam_penalty_time": true,
+ "sv_kick_ban_duration": false, // Server setting, should not be controlled by maps
+ "sv_ladder_scale_speed": true,
+ "sv_max_deathmatch_respawns_per_tick": true,
+ "sv_maxspeed": true,
+ "sv_maxvelocity": true,
+ "sv_minimum_desired_chicken_count": true,
+ "sv_outofammo_indicator": true,
+ "sv_pure": false, // Server command, should not be controlled by maps
+ "sv_pure_kick_clients": false, // Server setting, should not be controlled by maps
+ "sv_radio_throttle_window": false, // Server setting, should not be controlled by maps
+ "sv_regeneration_force_on": true,
+ "sv_regeneration_wait_time": true,
+ "sv_show_team_equipment_force_on": true,
+ "sv_showbullethits": true,
+ "sv_showhitregistration": true,
+ "sv_showimpacts": true,
+ "sv_showimpacts_penetration": true,
+ "sv_skirmish_id": true,
+ "sv_staminajumpcost": true,
+ "sv_staminalandcost": true,
+ "sv_staminamax": true,
+ "sv_staminarecoveryrate": true,
+ "sv_steamauth_enforce": false, // Server setting, should not be controlled by maps
+ "sv_stopspeed": true,
+ "sv_talk_enemy_dead": false, // Server setting, should not be controlled by maps
+ "sv_talk_enemy_living": false, // Server setting, should not be controlled by maps
+ "sv_teamid_overhead_maxdist": false, // Server setting, should not be controlled by maps
+ "sv_teamid_overhead_maxdist_spec": false, // Server setting, should not be controlled by maps
+ "sv_versus_screen_scene_id": true,
+ "sv_vote_to_changelevel_before_match_point": false, // Server setting, should not be controlled by maps
+ "sv_vote_to_changelevel_rndmin": false, // Server setting, should not be controlled by maps
+ "sv_warmup_to_freezetime_delay": true,
+ "sv_wateraccelerate": true,
+ "sv_waterfriction": true,
+ "tv_delay": false, // Server setting, should not be controlled by maps
+ "tv_delay1": false, // Server setting, should not be controlled by maps
+ "viewmodel_fov": true,
+ "viewmodel_offset_x": true,
+ "viewmodel_offset_y": true,
+ "viewmodel_offset_z": true,
+ "viewmodel_presetpos": true,
+ "weapon_accuracy_nospread": true,
+ "weapon_air_spread_scale": true,
+ "weapon_auto_cleanup_time": true,
+ "weapon_max_before_cleanup": true,
+ "weapon_recoil_scale": true,
+ "weapon_reticle_knife_show": true,
+ "weapon_sound_falloff_multiplier": true,
+ "sv_legacy_jump": false, // Server setting, should not be controlled by maps
+ "radio": true,
+ "radio1": true,
+ "radio2": true,
+ "radio3": true,
+ "player_ping": true,
+ "mp_teamlogo_1": true,
+ "mp_teamlogo_2": true,
+ "noclip_fixup": true,
+ "fov_cs_debug": true,
+ "sv_show_teammate_death_notification": true,
+ "sv_standable_normal": true,
+ "sv_walkable_normal": true,
+ "+jump": true,
+ "-jump": true,
+ "+duck": true,
+ "-duck": true,
+ "+forward": true,
+ "-forward": true,
+ "+back": true,
+ "-back": true,
+ "+left": true,
+ "-left": true,
+ "+right": true,
+ "-right": true,
+ "+sprint": true,
+ "-sprint": true,
+
+ // Custom stuff
+ "endround": true,
+ "cs2f_use_old_push": true,
+ "cs2f_infinite_reserve_ammo": true,
+ "cs2f_free_armor": true,
+ // Often over-abused but still have some legitimate uses, recommend either setting up map specific overrides or just globally enabling these
+ "zr_infect_spawn_time_min": false,
+ "zr_infect_spawn_time_max": false,
+ "zr_infect_spawn_mz_ratio": false
+}
\ No newline at end of file
diff --git a/gamedata/cs2fixes.jsonc b/gamedata/cs2fixes.jsonc
index f3515276..982ff121 100644
--- a/gamedata/cs2fixes.jsonc
+++ b/gamedata/cs2fixes.jsonc
@@ -395,6 +395,13 @@
"library": "server",
"windows": "48 8B C4 48 89 58 ? 57 48 81 EC ? ? ? ? 0F 29 70 ? 48 8B FA 0F 29 78 ? 48 8B D9",
"linux": "55 48 89 E5 41 57 41 56 41 55 41 54 49 89 F4 53 48 89 FB 48 81 EC ? ? ? ? 66 0F 1F 44 00"
+ },
+ // Only called in an if statement by function with "DISALLOWED WORKSHOP CONVAR: %s" string
+ "IsCommandWhitelisted":
+ {
+ "library": "server",
+ "windows": "48 83 EC ? 4C 8B C2 48 8D 0D ? ? ? ? 48 8D 54 24 ? FF",
+ "linux": "55 48 89 F2 48 8D 35 ? ? ? ? 48 89 E5 48 83 EC"
}
},
"Offsets":
diff --git a/src/cfgparser.cpp b/src/cfgparser.cpp
new file mode 100644
index 00000000..adfe3adf
--- /dev/null
+++ b/src/cfgparser.cpp
@@ -0,0 +1,80 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2026 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#include "cfgparser.h"
+#include "commands.h"
+
+#include
+
+CCfgParser* g_pCfgParser = nullptr;
+
+CON_COMMAND_F(exec_custom, " - Execute a cfg through the custom cfg parser", FCVAR_SPONLY | FCVAR_LINKED_CONCOMMAND)
+{
+ if (args.ArgC() < 2)
+ {
+ Message("Usage: exec_custom \n");
+ return;
+ }
+
+ g_pCfgParser->ParseCfg(args[1]);
+}
+
+void CCfgParser::ApplyGameSettings(const char* pszMapName)
+{
+ // Run plugin cfg
+ g_pCfgParser->ParseCfg("cs2fixes/cs2fixes");
+
+ // Run custom server cfg
+ g_pCfgParser->ParseCfg("cs2fixes/server");
+
+ if (!V_strcmp(pszMapName, ""))
+ return;
+
+ // Run map cfg (if present)
+ // We call ParseCfg indirectly through exec_custom, so any commands within the map cfg will be added to the command buffer after nested executes in previous configs
+ char cmd[MAX_PATH];
+ V_snprintf(cmd, sizeof(cmd), "exec_custom cs2fixes/maps/%s", pszMapName);
+ g_pEngineServer2->ServerCommand(cmd);
+}
+
+void CCfgParser::ParseCfg(const char* pszCfgPath)
+{
+ char szPath[MAX_PATH];
+ V_snprintf(szPath, sizeof(szPath), "%s/csgo/cfg/%s.cfg", Plat_GetGameDirectory(), pszCfgPath);
+ std::ifstream cfgFile(szPath);
+
+ if (!cfgFile.is_open())
+ {
+ Message("Unable to open & execute custom cfg file \"%s\"\n", pszCfgPath);
+ return;
+ }
+
+ Message("Executing custom cfg file \"%s\"\n", pszCfgPath);
+
+ std::string strCommand;
+
+ while (std::getline(cfgFile, strCommand))
+ {
+ if (!strCommand.empty() && strCommand.back() == '\r')
+ strCommand.pop_back();
+
+ if (!strCommand.empty())
+ g_pEngineServer2->ServerCommand(strCommand.c_str());
+ }
+}
diff --git a/src/cfgparser.h b/src/cfgparser.h
new file mode 100644
index 00000000..78433b7d
--- /dev/null
+++ b/src/cfgparser.h
@@ -0,0 +1,29 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2026 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+class CCfgParser
+{
+public:
+ void ApplyGameSettings(const char* pszMapName);
+ void ParseCfg(const char* pszCfgPath);
+};
+
+extern CCfgParser* g_pCfgParser;
diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp
index 4b3ff5a6..6017f9eb 100644
--- a/src/cs2fixes.cpp
+++ b/src/cs2fixes.cpp
@@ -22,10 +22,12 @@
#include "adminsystem.h"
#include "appframework/IAppSystem.h"
+#include "cfgparser.h"
#include "commands.h"
#include "common.h"
#include "cs_gameevents.pb.h"
#include "ctimer.h"
+#include "cvarwhitelist.h"
#include "detours.h"
#include "discord.h"
#include "entities.h"
@@ -355,6 +357,8 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool
g_pIdleSystem = new CIdleSystem();
g_pPanoramaVoteHandler = new CPanoramaVoteHandler();
g_pEWHandler = new CEWHandler();
+ g_pConvarWhitelist = new CConVarWhitelist();
+ g_pCfgParser = new CCfgParser();
g_pMapMigrations = new CMapMigrations();
RegisterWeaponCommands();
@@ -378,7 +382,7 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool
});
// run our cfg
- g_pEngineServer2->ServerCommand("exec cs2fixes/cs2fixes");
+ g_pCfgParser->ParseCfg("cs2fixes/cs2fixes");
srand(time(0));
@@ -504,6 +508,12 @@ bool CS2Fixes::Unload(char* error, size_t maxlen)
delete g_pEWHandler;
}
+ if (g_pConvarWhitelist)
+ delete g_pConvarWhitelist;
+
+ if (g_pCfgParser)
+ delete g_pCfgParser;
+
if (g_pMapMigrations)
delete g_pMapMigrations;
@@ -1031,8 +1041,22 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount
void CS2Fixes::Hook_ApplyGameSettings(KeyValues* pKV)
{
- g_pMapVoteSystem->ApplyGameSettings(pKV);
- g_pMapMigrations->ApplyGameSettings(pKV);
+ const char* pszMapName;
+ uint64 iWorkshopId;
+
+ if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("levelname"))
+ pszMapName = pKV->FindKey("launchoptions")->GetString("levelname");
+ else
+ pszMapName = "";
+
+ if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("customgamemode"))
+ iWorkshopId = pKV->FindKey("launchoptions")->GetUint64("customgamemode");
+ else
+ iWorkshopId = 0;
+
+ g_pCfgParser->ApplyGameSettings(pszMapName);
+ g_pMapVoteSystem->ApplyGameSettings(pszMapName, iWorkshopId);
+ g_pMapMigrations->ApplyGameSettings(iWorkshopId);
}
void CS2Fixes::Hook_CreateWorkshopMapGroup(const char* name, const CUtlStringList& mapList)
@@ -1321,14 +1345,6 @@ void CS2Fixes::OnLevelInit(char const* pMapName,
{
Message("OnLevelInit(%s)\n", pMapName);
- // run our cfg
- g_pEngineServer2->ServerCommand("exec cs2fixes/cs2fixes");
-
- // Run map cfg (if present)
- char cmd[MAX_PATH];
- V_snprintf(cmd, sizeof(cmd), "exec cs2fixes/maps/%s", pMapName);
- g_pEngineServer2->ServerCommand(cmd);
-
// Only patch BotNavIgnore while a map is loaded, else adding bots will crash
if (V_strcmp(pMapName, "error"))
g_CommonPatches[1].PerformPatch(g_GameConfig);
diff --git a/src/cvarwhitelist.cpp b/src/cvarwhitelist.cpp
new file mode 100644
index 00000000..3fe5a958
--- /dev/null
+++ b/src/cvarwhitelist.cpp
@@ -0,0 +1,122 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2026 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#include "cvarwhitelist.h"
+#include "commands.h"
+#include "utils.h"
+#undef snprintf
+#include "vendor/nlohmann/json.hpp"
+#include
+
+CConVarWhitelist* g_pConvarWhitelist = nullptr;
+
+CConVar g_cvarConVarWhitelistEnable("cs2f_cvarwhitelist_enable", FCVAR_NONE, "Whether to enable the custom ConVar whitelist for maps", false);
+
+CON_COMMAND_CHAT_FLAGS(cvarwhitelist_reload, "- Reload the ConVar whitelist config file", ADMFLAG_ROOT)
+{
+ if (!g_cvarConVarWhitelistEnable.Get())
+ return;
+
+ if (g_pConvarWhitelist->LoadConfig())
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "ConVar whitelist config reloaded!");
+ else
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Failed to reload ConVar whitelist config!");
+}
+
+bool CConVarWhitelist::LoadConfig()
+{
+ m_bConfigLoaded = false;
+ m_vecGlobalWhitelist.clear();
+ m_mapOverrides.clear();
+
+ const char* pszCvarWhitelistPath = "addons/cs2fixes/configs/cvar_whitelist.jsonc";
+ char szPath[MAX_PATH];
+ V_snprintf(szPath, sizeof(szPath), "%s%s%s", Plat_GetGameDirectory(), "/csgo/", pszCvarWhitelistPath);
+ std::ifstream cvarWhitelistFile(szPath);
+
+ if (!cvarWhitelistFile.is_open())
+ {
+ Panic("Failed to open %s, ConVar whitelist not loaded!\n", pszCvarWhitelistPath);
+ return false;
+ }
+
+ json jsonWhitelist = json::parse(cvarWhitelistFile, nullptr, false, true);
+
+ if (jsonWhitelist.is_discarded() || !jsonWhitelist.is_object())
+ {
+ Panic("Failed parsing JSON from %s, ConVar whitelist not loaded!\n", pszCvarWhitelistPath);
+ return false;
+ }
+
+ for (auto& [strKey, jsonValue] : jsonWhitelist.items())
+ {
+ if (jsonValue.is_boolean())
+ {
+ m_vecGlobalWhitelist.push_back({StringToLower(strKey), jsonValue.get()});
+ }
+ else if (jsonValue.is_object())
+ {
+ std::vector vecWhitelist;
+
+ for (auto& [strConvar, jsonInnerValue] : jsonValue.items())
+ {
+ if (!jsonInnerValue.is_boolean())
+ {
+ Panic("Found invalid value in %s, ConVar whitelist not loaded!\n", pszCvarWhitelistPath);
+ return false;
+ }
+
+ vecWhitelist.push_back({StringToLower(strConvar), jsonInnerValue.get()});
+ }
+
+ m_mapOverrides[strKey] = vecWhitelist;
+ }
+ else
+ {
+ Panic("Found invalid value in %s, ConVar whitelist not loaded!\n", pszCvarWhitelistPath);
+ return false;
+ }
+ }
+
+ m_bConfigLoaded = true;
+ return true;
+}
+
+bool CConVarWhitelist::IsWhitelisted(std::string strName)
+{
+ auto globalIt = std::find_if(m_vecGlobalWhitelist.begin(), m_vecGlobalWhitelist.end(), [strName](const auto& wv) {
+ return wv.strConVar == StringToLower(strName);
+ });
+
+ // Cannot load map overrides, only check global whitelist
+ if (!GetGlobals())
+ return globalIt != m_vecGlobalWhitelist.end() && globalIt->bEnabled;
+
+ auto mapVector = m_mapOverrides[GetGlobals()->mapname.ToCStr()];
+ auto mapIt = std::find_if(mapVector.begin(), mapVector.end(), [strName](const auto& wv) {
+ return wv.strConVar == StringToLower(strName);
+ });
+
+ // If a map override is present, it takes priority
+ if (mapIt != mapVector.end())
+ return mapIt->bEnabled;
+
+ // Or fall back to global whitelist
+ return globalIt != m_vecGlobalWhitelist.end() && globalIt->bEnabled;
+}
\ No newline at end of file
diff --git a/src/cvarwhitelist.h b/src/cvarwhitelist.h
new file mode 100644
index 00000000..ecb77a70
--- /dev/null
+++ b/src/cvarwhitelist.h
@@ -0,0 +1,54 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2026 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#include "convar.h"
+#undef snprintf
+#include "vendor/nlohmann/json_fwd.hpp"
+
+using json = nlohmann::json;
+
+extern CConVar g_cvarConVarWhitelistEnable;
+
+struct WhitelistValue
+{
+ std::string strConVar;
+ bool bEnabled;
+};
+
+class CConVarWhitelist
+{
+public:
+ CConVarWhitelist()
+ {
+ LoadConfig();
+ }
+
+ bool LoadConfig();
+ bool IsWhitelisted(std::string strName);
+ bool IsConfigLoaded() { return m_bConfigLoaded; };
+
+private:
+ bool m_bConfigLoaded = false;
+ std::vector m_vecGlobalWhitelist;
+ std::map> m_mapOverrides;
+};
+
+extern CConVarWhitelist* g_pConvarWhitelist;
\ No newline at end of file
diff --git a/src/detours.cpp b/src/detours.cpp
index 3d21b9fc..eea5ac10 100644
--- a/src/detours.cpp
+++ b/src/detours.cpp
@@ -29,6 +29,7 @@
#include "common.h"
#include "ctimer.h"
#include "customio.h"
+#include "cvarwhitelist.h"
#include "detours.h"
#include "entities.h"
#include "entity/cbasemodelentity.h"
@@ -92,6 +93,7 @@ DECLARE_DETOUR(CBaseModelEntity_SetModel, Detour_CBaseModelEntity_SetModel);
DECLARE_DETOUR(CCSGameRules_GoToIntermission, Detour_CCSGameRules_GoToIntermission);
DECLARE_DETOUR(SetBeamOrigin, Detour_SetBeamOrigin);
DECLARE_DETOUR(SetBeamEndPos, Detour_SetBeamEndPos);
+DECLARE_DETOUR(IsCommandWhitelisted, Detour_IsCommandWhitelisted);
CConVar g_cvarBlockMolotovSelfDmg("cs2f_block_molotov_self_dmg", FCVAR_NONE, "Whether to block self-damage from molotovs", false);
CConVar g_cvarBlockAllDamage("cs2f_block_all_dmg", FCVAR_NONE, "Whether to block all damage to players", false);
@@ -892,6 +894,14 @@ void FASTCALL Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecPosition)
pThis->m_vecEndPos = *(VectorWS*)(pVecPosition);
}
+bool FASTCALL Detour_IsCommandWhitelisted(void* pAddonManager, const char* pszCommandName)
+{
+ if (!g_cvarConVarWhitelistEnable.Get() || !g_pConvarWhitelist->IsConfigLoaded())
+ return IsCommandWhitelisted(pAddonManager, pszCommandName);
+
+ return g_pConvarWhitelist->IsWhitelisted(pszCommandName);
+}
+
bool InitDetours(CGameConfig* gameConfig)
{
bool success = true;
diff --git a/src/detours.h b/src/detours.h
index b48e7b05..19f4cdc7 100644
--- a/src/detours.h
+++ b/src/detours.h
@@ -118,4 +118,5 @@ void FASTCALL Detour_CS_Script_SetModel(uint64_t unk1);
void FASTCALL Detour_CBaseModelEntity_SetModel(CBaseModelEntity* pModel, const char* pszModel);
void FASTCALL Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAbortedMatch);
void FASTCALL Detour_SetBeamOrigin(CBeam* pThis, const Vector* pVecWorldPosition);
-void FASTCALL Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecWorldPosition);
\ No newline at end of file
+void FASTCALL Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecWorldPosition);
+bool FASTCALL Detour_IsCommandWhitelisted(void* pAddonManager, const char* pszCommandName);
\ No newline at end of file
diff --git a/src/map_votes.cpp b/src/map_votes.cpp
index 148b159a..6005b524 100644
--- a/src/map_votes.cpp
+++ b/src/map_votes.cpp
@@ -1136,24 +1136,11 @@ void CMapVoteSystem::ClearInvalidNominations()
}
}
-void CMapVoteSystem::ApplyGameSettings(KeyValues* pKV)
+void CMapVoteSystem::ApplyGameSettings(const char* pszMapName, uint64 iWorkshopId)
{
if (!g_cvarVoteManagerEnable.Get())
return;
- const char* pszMapName;
- uint64 iWorkshopId;
-
- if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("levelname"))
- pszMapName = pKV->FindKey("launchoptions")->GetString("levelname");
- else
- pszMapName = "";
-
- if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("customgamemode"))
- iWorkshopId = pKV->FindKey("launchoptions")->GetUint64("customgamemode");
- else
- iWorkshopId = 0;
-
auto pair = GetMapInfoByIdentifiers(pszMapName, iWorkshopId);
if (pair.first != -1)
diff --git a/src/map_votes.h b/src/map_votes.h
index 136bfbdf..178f65cc 100644
--- a/src/map_votes.h
+++ b/src/map_votes.h
@@ -210,7 +210,7 @@ class CMapVoteSystem
std::shared_ptr GetForcedNextMap() { return m_pForcedNextMap; }
void SetForcedNextMap(std::shared_ptr pForcedNextMap) { m_pForcedNextMap = pForcedNextMap; }
std::unordered_map GetNominatedMaps();
- void ApplyGameSettings(KeyValues* pKV);
+ void ApplyGameSettings(const char* pszMapName, uint64 iWorkshopId);
void OnLevelShutdown();
std::vector> GetMapCooldowns() { return m_vecCooldowns; }
std::string ConvertFloatToString(float fValue, int precision);
diff --git a/src/mapmigrations.cpp b/src/mapmigrations.cpp
index 5de64a0e..03db6b16 100644
--- a/src/mapmigrations.cpp
+++ b/src/mapmigrations.cpp
@@ -33,15 +33,13 @@ const time_t g_time20260420 = 1776725888;
CConVar g_cvarMapMigrations20260121("cs2f_mapmigrations_20260121", FCVAR_NONE, "Current mode for 2026-01-21 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-01-21 & disabled if updated after]", 2);
CConVar g_cvarMapMigrations20260420("cs2f_mapmigrations_20260420", FCVAR_NONE, "Current mode for 2026-04-20 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-04-20 & disabled if updated after]", 2);
-void CMapMigrations::ApplyGameSettings(KeyValues* pKV)
+void CMapMigrations::ApplyGameSettings(uint64 iWorkshopId)
{
m_timeMapUpdated = std::numeric_limits::max();
// Don't run on default maps
- if (!pKV->FindKey("launchoptions") || !pKV->FindKey("launchoptions")->FindKey("customgamemode"))
- return;
-
- CMapMigrationWorkshopDetailsQuery::Create(pKV->FindKey("launchoptions")->GetUint64("customgamemode"));
+ if (iWorkshopId != 0)
+ CMapMigrationWorkshopDetailsQuery::Create(iWorkshopId);
}
void CMapMigrations::OnRoundPrestart()
diff --git a/src/mapmigrations.h b/src/mapmigrations.h
index c19fde12..6d4cca13 100644
--- a/src/mapmigrations.h
+++ b/src/mapmigrations.h
@@ -52,7 +52,7 @@ class CMapMigrationWorkshopDetailsQuery : public std::enable_shared_from_this