commit 2e7b6b5ba57939d1e33ae1e06159ff5b982dff8a Author: David Anderson Date: Sun Jul 6 04:49:55 2008 +0000 jit refactoring branch --HG-- branch : refac-jit extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/branches/refac-jit%402369 diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 00000000..480f0b5b --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,36 @@ +We now use svn:keywords "Id" on all .c/.cpp/.h/.inc/.sp/ files. +Please make sure your client is configured properly. + +WINDOWS: + - Open your Application Data directory. + Windows XP/2000: C:\Documents and Settings\\Application Data + Windows Vista: C:\Users\\AppData\Roaming + + - Now go to the Subversion directory. + - Open the "config" file with a text editor. + +LINUX: + - Open ~/.subversion/config with your favorite text editor. + +Under [miscellany], uncomment this line: +# enable-auto-props = yes + +Under [auto-props], add these lines: +*.c = svn:keywords=Id +*.cpp = svn:keywords=Id +*.h = svn:keywords=Id +*.inc = svn:keywords=Id +*.sp = svn:keywords=Id + + +If you find a file with one of the above extensions that does not have the svn:keywords property... + +TORTOISE SVN: + - Right click on the file(s) that do not have the property. + - In the context menu that appears, select TortoiseSVN -> Properties. + - A properties window should appear. Click the Add button. + - Select "svn:keywords" from the "Property name" combo box and type "Id" in the "Property value" text area. + - Click OK on both windows and commit the change(s). + +CLI SVN CLIENT: + - Execute the following command: svn propset svn:keywords Id diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 00000000..7c48dee8 --- /dev/null +++ b/changelog.txt @@ -0,0 +1,75 @@ +SourceMod Changelog + +---------------------------- + +SourceMod 1.0.3 [2008-06-21] + +Changes: + + - Fixed SDKTools compatibility for latest TF2 update. + - Fixed amb1750: OnAutoConfigsBuffered() inserted before "exec server.cfg". + - Fixed a logic bug where OnConfigsExecuted() could be executed before "exec server.cfg" finished. + - Fixed a rare crash in the event manager that manifested on Zombie Panic! Source. + +---------------------------- + +SourceMod 1.0.2 [2008-05-31] + +Changes: + + - The admin menu is now user-modifiable (the "Dynamic Admin Menu"). + - Added a TF2 extension with Team Fortress functions. + - Added a RegEx extension with regular expression functions. + - Added functions to SDKTools for hooking entity outputs. + - Added preliminary support for the DoD:S Orange Box beta. + - Added a forward for map config plugins for preventing race conditions. + - Added a %b format specifier for binary printing. + - Added sm_dump_datamaps command (SDKTools) for enumerating datamap properties. + - Added sm_dump_admcache command for debugging the admin cache. + - Added amb1715 - TraceHull functions to SDKTools (complementing TraceRay). + - Added amb1694 - FindCharInString() function. + - Added amb1685 - GetTickInterval() function. + - Added amb1620 - ActivateEntity() function to SDKTools (for Orange Box particle system). + - Added amb1610 - StripQuotes() function. + - Added amb1558 - Compiler now has __BINARY_PATH__ and __BINARY_FILE__ macros. + - Fixed amb1686 - ReplaceString* with an empty search string crashed; it now throws an error. + - Fixed amb1684 - Blank passwords required an empty but set password. + - Fixed amb1595 - Extension load failures did not show a platform error message. + - Fixed amb1583 - MySQL string fetch from prepared queries returned corrupted data. + - Fixed amb1358 - Timeleft did not reset on TF2 restarts. + - Fixed cases where the JIT was too cautious in space optimizations. + - Fixed TF2/Cstrike extensions being loadable on incompatible games. + - Fixed various documentation inconsistencies and typos. + - Fixed internal bugs with file extension handling. + +Notes: + + There is a possible compatibility regression from amb1684. SetAdminPassword() + has been modified to remove any set password when given an empty string. Previously, + a blank password ("") would force an admin to use "setinfo" to set an empty password, + but this functionality was deemed unuseful and unintended. Blank passwords now + remove any set password. + +---------------------------- + +SourceMod 1.0.1 [2008-05-20] + +Changes: + + - Fixed SDKTools compatibility for latest TF2 update. + - Removed GivePlayerItem from TF2 (TF2 update broke functionality). + - Fixed amb1688: GivePlayerItem offset was wrong for DoD:S Linux. + - Fixed amb1657: Server console did not see admin version of sm_who. + - Fixed amb1648: Stack corruption from GetClientEyeAngles() on Windows. + - Fixed amb1646: NetFlow_Both did not work for client network statistics. + - Fixed amb1601: Vote FF menu reading from sv_alltalk cvar instead of mp_friendlyfire. + - Fixed amb1591: Fixed listen server crashes on mods like IOS:S which pre-add more than one bot. + - Fixed amb1586: GetTeamName() could crash the server if called on load. + - Fixed mapchooser's round counting for TF2. + - Fixed a bug where an RTE on plugin load would throw a message referring to the plugin as "-1". + - Symbols are no longer stripped on Linux. + - Minor SourceMod SDK fixes. + +Notes: + + The extension interface version has been bumped. Any extensions compiled against 1.0.1 will require 1.0.1 or higher to run. Extensions against 1.0.0 will continue to run normally. diff --git a/configs/admin_groups.cfg b/configs/admin_groups.cfg new file mode 100644 index 00000000..d464d27c --- /dev/null +++ b/configs/admin_groups.cfg @@ -0,0 +1,36 @@ +Groups +{ + /** + * Allowed properties for a group: + * + * "flags" - Flag string. + * "immunity" - Immunity level number, or a group name. + * If the group name is a number, prepend it with an + * '@' symbol similar to admins_simple.ini. Users + * will only inherit the level number if it's higher + * than their current value. + */ + "Default" + { + "immunity" "1" + } + + "Full Admins" + { + /** + * You can override commands and command groups here. + * Specify a command name or group and either "allow" or "deny" + * Examples: + * ":CSDM" "allow" + * "csdm_enable" "deny" + */ + Overrides + { + } + "flags" "abcdefghiz" + + /* Largish number for lots of in-between values. */ + "immunity" "99" + } +} + diff --git a/configs/admin_levels.cfg b/configs/admin_levels.cfg new file mode 100644 index 00000000..3c2c5769 --- /dev/null +++ b/configs/admin_levels.cfg @@ -0,0 +1,49 @@ +/** + * There is no reason to edit this file. Core uses this to map each named + * access type to a given ASCII character. The names are all pre-defined. + */ +Levels +{ + /** + * These are the default role flag mappings. + * You can assign new letters for custom purposes, however you should + * not change the default names, as SourceMod hardcodes these. + */ + Flags + { + "reservation" "a" //Reserved slots + "generic" "b" //Generic admin, required for admins + "kick" "c" //Kick other players + "ban" "d" //Banning other players + "unban" "e" //Removing bans + "slay" "f" //Slaying other players + "changemap" "g" //Changing the map + "cvars" "h" //Changing cvars + "config" "i" //Changing configs + "chat" "j" //Special chat privileges + "vote" "k" //Voting + "password" "l" //Password the server + "rcon" "m" //Remote console + "cheats" "n" //Change sv_cheats and related commands + + /** + * Custom flags can be used by plugins, but they can also be used to + * for you to expand on the previous groups, using Overrides. + */ + + "custom1" "o" + "custom2" "p" + "custom3" "q" + "custom4" "r" + "custom5" "s" + "custom6" "t" + + /** + * Root is a magic access flag that grants all permissions. + * This should only be given to trusted administrators. + * Root users can target anyone regardless of immunity, + * however, they themselves are not automatically immune. + */ + "root" "z" + } +} diff --git a/configs/admin_overrides.cfg b/configs/admin_overrides.cfg new file mode 100644 index 00000000..73404659 --- /dev/null +++ b/configs/admin_overrides.cfg @@ -0,0 +1,21 @@ +Overrides +{ + /** + * By default, commands are registered with three pieces of information: + * 1)Command Name (for example, "csdm_enable") + * 2)Command Group Name (for example, "CSDM") + * 3)Command Level (for example, "changemap") + * + * You can override the default flags assigned to individual commands or command groups in this way. + * To override a group, use the "@" character before the name. Example: + * Examples: + * "@CSDM" "b" // Override the CSDM group to 'b' flag + * "csdm_enable" "bgi" // Override the csdm_enable command to 'bgi' flags + * + * Note that for overrides, order is important. In the above example, csdm_enable overwrites + * any setting that csdm_enable previously had. + * + * You can make a command completely public by using an empty flag string. + */ +} + diff --git a/configs/adminmenu_cfgs.txt b/configs/adminmenu_cfgs.txt new file mode 100644 index 00000000..644c6d71 --- /dev/null +++ b/configs/adminmenu_cfgs.txt @@ -0,0 +1,10 @@ +/** + * List config files here (relative to moddir) to have them added to the exec config menu list + * Left side is the filename, right side is the text to be added to the menu + */ +Configs +{ + "cfg/server.cfg" "Standard Server Setup" + "cfg/sourcemod/sm_warmode_on.cfg" "War Mode On" + "cfg/sourcemod/sm_warmode_off.cfg" "War Mode Off" +} diff --git a/configs/adminmenu_custom.txt b/configs/adminmenu_custom.txt new file mode 100644 index 00000000..16f3e6fe --- /dev/null +++ b/configs/adminmenu_custom.txt @@ -0,0 +1,12 @@ +// Custom admin menu commands. +// For more information: +// +// http://wiki.alliedmods.net/Custom_Admin_Menu_%28SourceMod%29 +// +// Note: This file must be in Valve KeyValues format (no multiline comments) +// + +"Commands" +{ + +} diff --git a/configs/adminmenu_grouping.txt b/configs/adminmenu_grouping.txt new file mode 100644 index 00000000..84e6e29f --- /dev/null +++ b/configs/adminmenu_grouping.txt @@ -0,0 +1,20 @@ +/* Add group options to be added to 'group' or 'groupplayer' type submenus + * The left side is the name that will show in the menu, right is the command that will be fired + * + * For more information: http://wiki.alliedmods.net/Custom_Admin_Menu_%28SourceMod%29 + */ + +Groups +{ + "All" "@all" + "Bots" "@bots" + "Alive" "@alive" + "Dead" "@dead" + "Humans" "@humans" + "Current aim" "@aim" + + /* You can enable these if you are using Counter-Strike Source and running the cstrike extension */ +// "Terrorists" "@t" +// "Counter-Terrorists" "@ct" + +} \ No newline at end of file diff --git a/configs/adminmenu_sorting.txt b/configs/adminmenu_sorting.txt new file mode 100644 index 00000000..24a9dbee --- /dev/null +++ b/configs/adminmenu_sorting.txt @@ -0,0 +1,39 @@ +/** + * The default sorting is designed to look familiar to Mani's admin menu. + * You may re-order items here for your own menu. Any items not explicitly + * sorted will be sorted by their final translated phrases for each given client. + */ + +"Menu" +{ + "PlayerCommands" + { + "item" "sm_slay" + "item" "sm_slap" + "item" "sm_kick" + "item" "sm_ban" + "item" "sm_gag" + "item" "sm_burn" + "item" "sm_beacon" + "item" "sm_freeze" + "item" "sm_timebomb" + "item" "sm_firebomb" + "item" "sm_freezebomb" + } + + "ServerCommands" + { + "item" "sm_map" + "item" "sm_execcfg" + "item" "sm_reloadadmins" + } + + "VotingCommands" + { + "item" "sm_cancelvote" + "item" "sm_votemap" + "item" "sm_votekick" + "item" "sm_voteban" + } +} + diff --git a/configs/admins.cfg b/configs/admins.cfg new file mode 100644 index 00000000..ed1e481f --- /dev/null +++ b/configs/admins.cfg @@ -0,0 +1,39 @@ + +/** + * USE THIS SECTION TO DECLARE DETAILED ADMIN PROPERTIES. + * + * Each admin should have its own "Admin" section, followed by a name. + * The name does not have to be unique. + * + * Available properties: (Anything else is filtered as custom) + * "auth" - REQUIRED - Auth method to use. Built-in methods are: + * "steam" - Steam based authentication + * "name" - Name based authentication + * "ip" - IP based authentication + * Anything else is treated as custom. + * Note: Only one auth method is allowed per entry. + * + * "identity" - REQUIRED - Identification string, for example, a steamid or name. + * Note: Only one identity is allowed per entry. + * + * "password" - Optional password to require. + * "group" - Adds one group to the user's group table. + * "flags" - Adds one or more flags to the user's permissions. + * "immunity" - Sets the user's immunity level (0 = no immunity). + * Immunity can be any value. Admins with higher + * values cannot be targetted. See sm_immunity_mode + * to tweak the rules. Default value is 0. + * + * Example: + "BAILOPAN" + { + "auth" "steam" + "identity" "STEAM_0:1:16" + "flags" "abcdef" + } + * + */ +Admins +{ +} + diff --git a/configs/admins_simple.ini b/configs/admins_simple.ini new file mode 100644 index 00000000..9c88bbe6 --- /dev/null +++ b/configs/admins_simple.ini @@ -0,0 +1,46 @@ +// +// READ THIS CAREFULLY! SEE BOTTOM FOR EXAMPLES +// +// For each admin, you need three settings: +// "identity" "permissions" "password" +// +// For the Identity, you can use a SteamID or Name. To use an IP address, prepend a ! character. +// For the Permissions, you can use a flag string and an optional password. +// +// PERMISSIONS: +// Flag definitions are in "admin_levels.cfg" +// You can combine flags into a string like this: +// "abcdefgh" +// +// If you want to specify a group instead of a flag, use an @ symbol. Example: +// "@Full Admins" +// +// You can also specify immunity values. Two examples: +// "83:abcdefg" //Immunity is 83, flags are abcefgh +// "6:@Full Admins" //Immunity is 6, group is "Full Admins" +// +// Immunity values can be any number. An admin cannot target an admin with +// a higher access value (see sm_immunity_mode to tweak the rules). Default +// immunity value is 0 (no immunity). +// +// PASSWORDS: +// Passwords are generally not needed unless you have name-based authentication. +// In this case, admins must type this in their console: +// +// setinfo "KEY" "PASSWORD" +// +// Where KEY is the "PassInfoVar" setting in your core.cfg file, and "PASSWORD" +// is their password. With name based authentication, this must be done before +// changing names or connecting. Otherwise, SourceMod will automatically detect +// the password being set. +// +//////////////////////////////// +// Examples: (do not put // in front of real lines, as // means 'comment') +// +// "STEAM_0:1:16" "bce" //kick, ban, slay for this steam ID, no immunity +// "!127.0.0.1" "99:z" //all permissions for this ip, immunity value is 99 +// "BAILOPAN" "abc" "Gab3n" //name BAILOPAN, password "Gab3n": gets reservation, kick, ban +// +//////////////////////////////// + + diff --git a/configs/cfg/sm_warmode_off.cfg b/configs/cfg/sm_warmode_off.cfg new file mode 100644 index 00000000..278c925a --- /dev/null +++ b/configs/cfg/sm_warmode_off.cfg @@ -0,0 +1,4 @@ +//This file re-enables a server from "war mode" by unlocking plugin loading +//and refreshing the plugins list. +sm plugins load_unlock +sm plugins refresh diff --git a/configs/cfg/sm_warmode_on.cfg b/configs/cfg/sm_warmode_on.cfg new file mode 100644 index 00000000..ebc3c6f6 --- /dev/null +++ b/configs/cfg/sm_warmode_on.cfg @@ -0,0 +1,9 @@ +//This file unloads all plugins, re-loads a few "safe" ones, and then prevents +//any more plugins from being loaded. +sm plugins unload_all +sm plugins load basebans.smx +sm plugins load basecommands.smx +sm plugins load admin-flatfile.smx +sm plugins load adminhelp.smx +sm plugins load adminmenu.smx +sm plugins load_lock diff --git a/configs/cfg/sourcemod.cfg b/configs/cfg/sourcemod.cfg new file mode 100644 index 00000000..0c4f4dd7 --- /dev/null +++ b/configs/cfg/sourcemod.cfg @@ -0,0 +1,109 @@ +// SourceMod Configuration File +// This file is automatically executed by SourceMod every mapchange. + + +// Specifies how admin activity should be relayed to users. Add up the values +// below to get the functionality you want. +// 1: Show admin activity to non-admins anonymously. +// 2: If 1 is specified, admin names will be shown. +// 4: Show admin activity to admins anonymously. +// 8: If 4 is specified, admin names will be shown. +// 16: Always show admin names to root users. +// -- +// Default: 13 (1+4+8) +sm_show_activity 13 + +// Specifies whether menu sounds are enabled for menus created by SourceMod. +// Menu sounds can be further configured in addons/sourcemod/configs/core.cfg. +// -- +// Default: 1 +sm_menu_sounds 1 + +// Specifies how long of a delay, in seconds, should be used in between votes +// that are "public" or can be spammed. Whether or not this delay is obeyed +// is dependent on the menu/command. +// -- +// Default: 30 +sm_vote_delay 30 + +// Default datetime formatting rules when displaying to clients. +// For full options, see: http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html +// -- +// Default: %m/%d/%Y - %H:%M:%S +// 12 hour format: %m/%d/%Y - %I:%M:%S %p +sm_datetime_format "%m/%d/%Y - %H:%M:%S" + +// Sets how SourceMod should check immunity levels when administrators target +// each other. +// 0: Ignore immunity levels (except for specific group immunities). +// 1: Protect from admins of lower access only. +// 2: Protect from admins of equal to or lower access. +// 3: Same as 2, except admins with no immunity can affect each other. +// -- +// Default: 1 +sm_immunity_mode 1 + +// Sets how many seconds SourceMod should adjust time values for incorrect +// server clocks. This can be positive or negative and will affect every +// system time in SourceMod, including logging stamps. +// -- +// Default: 0 +sm_time_adjustment 0 + +// Specifies the amount of time that is allowed between chat messages. This +// includes the say and say_team commands. If a client sends a message faster +// than this time, they receive a flood token. When the client has accumulated +// 3 or more tokens, a warning message is shown instead of the chat message. +// -- +// Requires: antiflood.smx +// Default: 0.75 +sm_flood_time 0.75 + +// Specifies how the reserved slots plugin operates. Valid values are: +// 0 : Public slots are used in preference to reserved slots. Reserved slots are freed before public slots. +// 1 : If someone with reserve access joins into a reserved slot, the player with the highest latency and +// no reserved slot access (spectator players are selected first) is kicked to make room. Thus, the reserved +// slots always remains free. The only situation where the reserved slot(s) can become properly occupied is +// if the server is full with reserve slot access clients. +// -- +// Requires: reservedslots.smx +// Default: 0 +sm_reserve_type 0 + +// Specifies the number of reserved player slots. Users with the reservation +// admin flag set will be able to join the server when there are no public slots +// remaining. If someone does not have this flag, they will be kicked. +// (Public slots are defined as: maxplayers - number of reserved slots) +// -- +// Requires: reservedslots.smx +// Default: 0 +sm_reserved_slots 0 + +// Specifies whether or not reserved slots will be hidden (subtracted from max +// slot count). Valid values are 0 (visible) or 1 (hidden). +// -- +// Requires: reservedslots.smx +// Default: 0 +sm_hide_slots 0 + +// Specifies whether or not non-admins can send messages to admins using +// say_team @. Valid values are 0 (disabled) or 1 (enabled) +// -- +// Requires: basechat.smx +// Default: 1 +sm_chat_mode 1 + +// Specifies whether or not "timeleft" will automaticly be triggered every +// x seconds. Valid values are 0 (disabled) to 1800 seconds. +// -- +// Requires: basetriggers.smx +// Default: 0 +sm_timeleft_interval 0 + +// Specifies whether or not chat triggers are broadcast to the server or just +// the player who requested the info trigger. Valid values are 0 (Disabled) or +// 1 (Enabled) +// -- +// Requires: basetriggers.smx +// Default: 1 +sm_trigger_show 1 diff --git a/configs/core.cfg b/configs/core.cfg new file mode 100644 index 00000000..8969cdff --- /dev/null +++ b/configs/core.cfg @@ -0,0 +1,105 @@ +/** + * This file is used to set various options that are important to SourceMod's core. + * If this file is missing or an option in this file is missing, then the default values will be used. + */ +"Core" +{ + /** + * Relative path to SourceMod's base directory. This is relative to the game/mod directory. + * Only change this if you have installed SourceMod in a non-default location. + * + * The default value is "addons/sourcemod" + */ + "BasePath" "addons/sourcemod" + + /** + * This option determines if SourceMod logging is enabled. + * + * "on" - Logging is enabled (default) + * "off" - Logging is disabled + */ + "Logging" "on" + + /** + * This option determines how SourceMod logging should be handled. + * + * "daily" - New log file is created for each day (default) + * "map" - New log file is created for each map change + * "game" - Use game's log files + */ + "LogMode" "daily" + + /** + * Language that multilingual enabled plugins and extensions will use to print messages. + * Only languages listed in languages.cfg are valid. + * + * The default value is "en" + */ + "ServerLang" "en" + + /** + * String to use as the public chat trigger. Set an empty string to disable. + */ + "PublicChatTrigger" "!" + + /** + * String to use as the silent chat trigger. Set an empty string to disable. + */ + "SilentChatTrigger" "/" + + /** + * If a say command is a silent chat trigger, and is used by an admin, + * but it does not evaluate to an actual command, it will be displayed + * publicly. This setting allows you to suppress accidental typings. + * + * The default value is "no". A value of "yes" will supress. + */ + "SilentFailSuppress" "no" + + /** + * Password setinfo key that clients must set. You must change this in order for + * passwords to work, for security reasons. + */ + "PassInfoVar" "_password" + + /** + * Specifies the sound that gets played when an item is selected from a menu. + */ + "MenuItemSound" "buttons/button14.wav" + + /** + * Specifies the sound that gets played when an "Exit" button is selected + * from a menu. + */ + "MenuExitSound" "buttons/combine_button7.wav" + + /** + * Specifies the sound that gets played when an "Exit Back" button is selected + * from a menu. This is the special "Back" button that is intended to roll back + * to a previous menu. + */ + "MenuExitBackSound" "buttons/combine_button7.wav" + + /** + * Enables or disables whether SourceMod reads a client's cl_language cvar to set + * their language for server-side phrase translation. + * + * "on" - Translate using the client's language (default) + * "off" - Translate using default server's language + */ + "AllowClLanguageVar" "On" + + /** + * Enables or Disables SourceMod's automatic gamedata updating. + * + * The default value is "no". A value of "yes" will block the Auto Updater. + */ + "DisableAutoUpdate" "no" + + /** + * Enables or disables automatic restarting of the server after a successful update. + * + * The default value is "no". A value of "yes" will let the server automatically restart. + */ + "ForceRestartAfterUpdate" "no" +} diff --git a/configs/databases.cfg b/configs/databases.cfg new file mode 100644 index 00000000..072c27f7 --- /dev/null +++ b/configs/databases.cfg @@ -0,0 +1,32 @@ +"Databases" +{ + "driver_default" "mysql" + + "default" + { + "driver" "default" + "host" "localhost" + "database" "sourcemod" + "user" "root" + "pass" "" + //"timeout" "0" + //"port" "0" + } + + "storage-local" + { + "driver" "sqlite" + "database" "sourcemod-local" + } + + "clientprefs" + { + "driver" "sqlite" + "host" "localhost" + "database" "clientprefs-sqlite" + "user" "root" + "pass" "" + //"timeout" "0" + //"port" "0" + } +} diff --git a/configs/geoip/GeoIP.dat b/configs/geoip/GeoIP.dat new file mode 100644 index 00000000..e05bf098 Binary files /dev/null and b/configs/geoip/GeoIP.dat differ diff --git a/configs/languages.cfg b/configs/languages.cfg new file mode 100644 index 00000000..6bbce279 --- /dev/null +++ b/configs/languages.cfg @@ -0,0 +1,6 @@ +"Languages" +{ + "en" "English" + "es" "Español" +} + diff --git a/configs/maplists.cfg b/configs/maplists.cfg new file mode 100644 index 00000000..c51110dd --- /dev/null +++ b/configs/maplists.cfg @@ -0,0 +1,59 @@ +/** + * Use this file to configure map lists. + * + * Each section is a map list that plugins can use. For example, the Admin Menu + * requests an "admin menu" map list, and you can control which maps appear via + * this file. + * + * Each section must have a property that explains where to read the maps from. + * There are two properties: + * + * target - Redirect the request to another section. + * file - Read a file of map names, in mapcycle.txt format. + * + * There is one section by default, called "mapcyclefile" - it is mapped to the + * mapcycle.txt file, or whatever the contents of your mapcyclefile cvar is. + * + * If a plugin requests a map list file which doesn't exist, or is empty, SourceMod + * tries the "default" section, and then the "mapcyclefile" section. + */ +"MapLists" +{ + /** + * Default requests go right to the mapcyclefile. + */ + "default" + { + "target" "mapcyclefile" + } + + /* Admin menu, map menu */ + "sm_map menu" + { + "file" "addons/sourcemod/configs/adminmenu_maplist.ini" + } + + /* Admin menu, map voting menu */ + "sm_votemap menu" + { + "file" "addons/sourcemod/configs/adminmenu_maplist.ini" + } + + /* For the "randomcycle" plugin */ + "randomcycle" + { + "target" "default" + } + + /* For the "mapchooser" plugin */ + "mapchooser" + { + "target" "default" + } + + /* For the "rockthevote" plugin */ + "rockthevote" + { + "target" "default" + } +} diff --git a/configs/metamod/sourcemod.vdf b/configs/metamod/sourcemod.vdf new file mode 100644 index 00000000..c29686c4 --- /dev/null +++ b/configs/metamod/sourcemod.vdf @@ -0,0 +1,5 @@ +"Metamod Plugin" +{ + "alias" "sourcemod" + "file" "addons/sourcemod/bin/sourcemod_mm" +} diff --git a/configs/plugin_settings.cfg b/configs/plugin_settings.cfg new file mode 100644 index 00000000..cf46ae71 --- /dev/null +++ b/configs/plugin_settings.cfg @@ -0,0 +1,38 @@ +/** + * Each sub-section of "Plugins" should have a title which specifies a plugin filename. + * Filenames have a wildcard of *. Appending .smx is not required. + * If the filename has no explicit path, it will be patched to any sub-path in the plugins folder. + * + * Available properties for plugins are: + * "pause" - Whether or not the plugin should load paused - "yes" or "no" (default) + * "lifetime" - Lifetime of the plugin. Options: + * "mapsync" - Plugins should be reloaded on mapchange if changed (default) + * "global" - Plugin will never be unloaded or updated + * "blockload" - Plugin will always be blocked from loading. Implicit (automatic) loads + * produce no error, but explicit (manual) loads will show an error message. + * (Options are one of "yes" or "no") + * + * You can also have an "Options" section declaring options to pass onto the JIT: + * "debug" - Whether or not to load the plugin in debug mode + * "profile" - Bit flags for profiling level. Add flags together to reach a value. + * WARNING: Profiler is _ALPHA_ software! Use it at your own risk for + * development cycles only (not production setups). + * See the wiki article "SourceMod Profiler" for more information. + * 1 - Profile natives + * 2 - Profile callbacks + * 4 - Profile internal plugin function calls + */ + +"Plugins" +{ + "*" + { + "pause" "no" + "lifetime" "mapsync" + + "Options" + { + "debug" "no" + } + } +} diff --git a/configs/sql-init-scripts/mysql/clientprefs-mysql.sql b/configs/sql-init-scripts/mysql/clientprefs-mysql.sql new file mode 100644 index 00000000..35f78ea6 --- /dev/null +++ b/configs/sql-init-scripts/mysql/clientprefs-mysql.sql @@ -0,0 +1,17 @@ +CREATE TABLE sm_cookies +( + id INTEGER unsigned NOT NULL auto_increment, + name varchar(30) NOT NULL UNIQUE, + description varchar(255), + access INTEGER, + PRIMARY KEY (id) +); + +CREATE TABLE sm_cookie_cache +( + player varchar(65) NOT NULL, + cookie_id int(10) NOT NULL, + value varchar(100), + timestamp int NOT NULL, + PRIMARY KEY (player, cookie_id) +); diff --git a/configs/sql-init-scripts/mysql/create_admins.sql b/configs/sql-init-scripts/mysql/create_admins.sql new file mode 100644 index 00000000..0791aa34 --- /dev/null +++ b/configs/sql-init-scripts/mysql/create_admins.sql @@ -0,0 +1,56 @@ + +CREATE TABLE sm_admins ( + id int(10) unsigned NOT NULL auto_increment, + authtype enum('steam','name','ip') NOT NULL, + identity varchar(65) NOT NULL, + password varchar(65), + flags varchar(30) NOT NULL, + name varchar(65) NOT NULL, + immunity int(10) unsigned NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE sm_groups ( + id int(10) unsigned NOT NULL auto_increment, + flags varchar(30) NOT NULL, + name varchar(120) NOT NULL, + immunity_level int(1) unsigned NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE sm_group_immunity ( + group_id int(10) unsigned NOT NULL, + other_id int(10) unsigned NOT NULL, + PRIMARY KEY (group_id, other_id) +); + +CREATE TABLE sm_group_overrides ( + group_id int(10) unsigned NOT NULL, + type enum('command','group') NOT NULL, + name varchar(32) NOT NULL, + access enum('allow','deny') NOT NULL, + PRIMARY KEY (group_id, type, name) +); + +CREATE TABLE sm_overrides ( + type enum('command','group') NOT NULL, + name varchar(32) NOT NULL, + flags varchar(30) NOT NULL, + PRIMARY KEY (type,name) +); + +CREATE TABLE sm_admins_groups ( + admin_id int(10) unsigned NOT NULL, + group_id int(10) unsigned NOT NULL, + inherit_order int(10) NOT NULL, + PRIMARY KEY (admin_id, group_id) +); + +CREATE TABLE IF NOT EXISTS sm_config ( + cfg_key varchar(32) NOT NULL, + cfg_value varchar(255) NOT NULL, + PRIMARY KEY (cfg_key) +); + +INSERT INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '1.0.0.1409') ON DUPLICATE KEY UPDATE cfg_value = '1.0.0.1409'; + diff --git a/configs/sql-init-scripts/mysql/update_admins_r1409.sql b/configs/sql-init-scripts/mysql/update_admins_r1409.sql new file mode 100644 index 00000000..81c0992c --- /dev/null +++ b/configs/sql-init-scripts/mysql/update_admins_r1409.sql @@ -0,0 +1,15 @@ + +ALTER TABLE sm_admins ADD immunity INT UNSIGNED NOT NULL; + +ALTER TABLE sm_groups ADD immunity_level INT UNSIGNED NOT NULL; +UPDATE sm_groups SET immunity_level = 2 WHERE immunity = 'default'; +UPDATE sm_groups SET immunity_level = 1 WHERE immunity = 'global'; +ALTER TABLE sm_groups DROP immunity; + +CREATE TABLE sm_config ( + cfg_key varchar(32) NOT NULL, + cfg_value varchar(255) NOT NULL, + PRIMARY KEY (cfg_key) +); +INSERT INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '1.0.0.1409') ON DUPLICATE KEY UPDATE cfg_value = '1.0.0.1409'; + diff --git a/configs/sql-init-scripts/sqlite/admins-sqlite.sq3 b/configs/sql-init-scripts/sqlite/admins-sqlite.sq3 new file mode 100644 index 00000000..c68bc42e Binary files /dev/null and b/configs/sql-init-scripts/sqlite/admins-sqlite.sq3 differ diff --git a/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sq3 b/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sq3 new file mode 100644 index 00000000..04d30053 Binary files /dev/null and b/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sq3 differ diff --git a/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sql b/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sql new file mode 100644 index 00000000..d16622f2 --- /dev/null +++ b/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sql @@ -0,0 +1,16 @@ +CREATE TABLE sm_cookies +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name varchar(30) NOT NULL UNIQUE, + description varchar(255), + access INTEGER +); + +CREATE TABLE sm_cookie_cache +( + player varchar(65) NOT NULL, + cookie_id int(10) NOT NULL, + value varchar(100), + timestamp int, + PRIMARY KEY (player, cookie_id) +); \ No newline at end of file diff --git a/configs/sql-init-scripts/sqlite/create_admins.sql b/configs/sql-init-scripts/sqlite/create_admins.sql new file mode 100644 index 00000000..1dfa91d6 --- /dev/null +++ b/configs/sql-init-scripts/sqlite/create_admins.sql @@ -0,0 +1,54 @@ + +CREATE TABLE sm_admins ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + authtype varchar(16) NOT NULL CHECK(authtype IN ('steam', 'ip', 'name')), + identity varchar(65) NOT NULL, + password varchar(65), + flags varchar(30) NOT NULL, + name varchar(65) NOT NULL, + immunity INTEGER NOT NULL +); + +CREATE TABLE sm_groups ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + flags varchar(30) NOT NULL, + name varchar(120) NOT NULL, + immunity_level INTEGER NOT NULL +); + +CREATE TABLE sm_group_immunity ( + group_id INTEGER NOT NULL, + other_id INTEGER NOT NULL, + PRIMARY KEY (group_id, other_id) +); + +CREATE TABLE sm_group_overrides ( + group_id INTEGER NOT NULL, + type varchar(16) NOT NULL CHECK (type IN ('command', 'group')), + name varchar(32) NOT NULL, + access varchar(16) NOT NULL CHECK (access IN ('allow', 'deny')), + PRIMARY KEY (group_id, type, name) +); + +CREATE TABLE sm_overrides ( + type varchar(16) NOT NULL CHECK (type IN ('command', 'group')), + name varchar(32) NOT NULL, + flags varchar(30) NOT NULL, + PRIMARY KEY (type,name) +); + +CREATE TABLE sm_admins_groups ( + admin_id INTEGER NOT NULL, + group_id INTEGER NOT NULL, + inherit_order int(10) NOT NULL, + PRIMARY KEY (admin_id, group_id) +); + +CREATE TABLE IF NOT EXISTS sm_config ( + cfg_key varchar(32) NOT NULL, + cfg_value varchar(255) NOT NULL, + PRIMARY KEY (cfg_key) +); + +REPLACE INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '1.0.0.1409'); + diff --git a/configs/sql-init-scripts/sqlite/update_admins-r1409.sql b/configs/sql-init-scripts/sqlite/update_admins-r1409.sql new file mode 100644 index 00000000..d3a85ec8 --- /dev/null +++ b/configs/sql-init-scripts/sqlite/update_admins-r1409.sql @@ -0,0 +1,23 @@ + +ALTER TABLE sm_admins ADD immunity INTEGER DEFAULT 0 NOT NULL; + +CREATE TABLE _sm_groups_temp ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + flags varchar(30) NOT NULL, + name varchar(120) NOT NULL, + immunity_level INTEGER DEFAULT 0 NOT NULL +); +INSERT INTO _sm_groups_temp (id, flags, name) SELECT id, flags, name FROM sm_groups; +UPDATE _sm_groups_temp SET immunity_level = 2 WHERE id IN (SELECT g.id FROM sm_groups g WHERE g.immunity = 'global'); +UPDATE _sm_groups_temp SET immunity_level = 1 WHERE id IN (SELECT g.id FROM sm_groups g WHERE g.immunity = 'default'); +DROP TABLE sm_groups; +ALTER TABLE _sm_groups_temp RENAME TO sm_groups; + +CREATE TABLE IF NOT EXISTS sm_config ( + cfg_key varchar(32) NOT NULL, + cfg_value varchar(255) NOT NULL, + PRIMARY KEY (cfg_key) +); + +REPLACE INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '1.0.0.1409'); + diff --git a/core/ADTFactory.cpp b/core/ADTFactory.cpp new file mode 100644 index 00000000..e8fc2031 --- /dev/null +++ b/core/ADTFactory.cpp @@ -0,0 +1,96 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include "ADTFactory.h" +#include "sm_globals.h" +#include "ShareSys.h" + +ADTFactory g_AdtFactory; + +const char *ADTFactory::GetInterfaceName() +{ + return SMINTERFACE_ADTFACTORY_NAME; +} + +unsigned int ADTFactory::GetInterfaceVersion() +{ + return SMINTERFACE_ADTFACTORY_VERSION; +} + +void ADTFactory::OnSourceModAllInitialized() +{ + g_ShareSys.AddInterface(NULL, this); +} + +IBasicTrie *ADTFactory::CreateBasicTrie() +{ + return new BaseTrie(); +} + +BaseTrie::BaseTrie() +{ + m_pTrie = sm_trie_create(); +} + +BaseTrie::~BaseTrie() +{ + sm_trie_destroy(m_pTrie); +} + +bool BaseTrie::Insert(const char *key, void *value) +{ + return sm_trie_insert(m_pTrie, key, value); +} + +bool BaseTrie::Retrieve(const char *key, void **value) +{ + return sm_trie_retrieve(m_pTrie, key, value); +} + +bool BaseTrie::Delete(const char *key) +{ + return sm_trie_delete(m_pTrie, key); +} + +void BaseTrie::Clear() +{ + sm_trie_clear(m_pTrie); +} + +void BaseTrie::Destroy() +{ + delete this; +} + +bool BaseTrie::Replace(const char *key, void *value) +{ + return sm_trie_replace(m_pTrie, key, value); +} diff --git a/core/ADTFactory.h b/core/ADTFactory.h new file mode 100644 index 00000000..31bff041 --- /dev/null +++ b/core/ADTFactory.h @@ -0,0 +1,70 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_ADTFACTORY_H_ +#define _INCLUDE_SOURCEMOD_ADTFACTORY_H_ + +#include +#include "sm_globals.h" +#include "sm_trie.h" + +using namespace SourceMod; + +class BaseTrie : public IBasicTrie +{ +public: + BaseTrie(); + virtual ~BaseTrie(); + virtual bool Insert(const char *key, void *value); + virtual bool Replace(const char *key, void *value); + virtual bool Retrieve(const char *key, void **value); + virtual bool Delete(const char *key); + virtual void Clear(); + virtual void Destroy(); +private: + Trie *m_pTrie; +}; + +class ADTFactory : + public SMGlobalClass, + public IADTFactory +{ +public: //SMInterface + const char *GetInterfaceName(); + unsigned int GetInterfaceVersion(); +public: //SMGlobalClass + void OnSourceModAllInitialized(); +public: //IADTFactory + IBasicTrie *CreateBasicTrie(); +}; + +#endif //_INCLUDE_SOURCEMOD_ADTFACTORY_H_ + diff --git a/core/AdminCache.cpp b/core/AdminCache.cpp new file mode 100644 index 00000000..a03c1899 --- /dev/null +++ b/core/AdminCache.cpp @@ -0,0 +1,2081 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include +#include +#include +#include "AdminCache.h" +#include "ShareSys.h" +#include "ForwardSys.h" +#include "PlayerManager.h" +#include "ConCmdManager.h" +#include "Logger.h" +#include "sourcemod.h" +#include "sm_stringutil.h" +#include "sourcemm_api.h" +#include "sm_srvcmds.h" + +#define LEVEL_STATE_NONE 0 +#define LEVEL_STATE_LEVELS 1 +#define LEVEL_STATE_FLAGS 2 + +AdminCache g_Admins; +char g_ReverseFlags[26]; +AdminFlag g_FlagLetters[26]; +bool g_FlagSet[26]; + +ConVar sm_immunity_mode("sm_immunity_mode", "1", FCVAR_SPONLY, "Mode for deciding immunity protection"); + +/* Default flags */ +AdminFlag g_DefaultFlags[26] = +{ + Admin_Reservation, Admin_Generic, Admin_Kick, Admin_Ban, Admin_Unban, + Admin_Slay, Admin_Changemap, Admin_Convars, Admin_Config, Admin_Chat, + Admin_Vote, Admin_Password, Admin_RCON, Admin_Cheats, Admin_Custom1, + Admin_Custom2, Admin_Custom3, Admin_Custom4, Admin_Custom5, Admin_Custom6, + Admin_Generic, Admin_Generic, Admin_Generic, Admin_Generic, Admin_Generic, + Admin_Root +}; + + +class FlagReader : public ITextListener_SMC +{ +public: + void LoadLevels() + { + if (!Parse()) + { + memcpy(g_FlagLetters, g_DefaultFlags, sizeof(AdminFlag) * 26); + for (unsigned int i=0; i<20; i++) + { + g_FlagSet[i] = true; + } + g_FlagSet[25] = true; + } + } +private: + bool Parse() + { + SMCStates states; + SMCError error; + + m_bFileNameLogged = false; + g_SourceMod.BuildPath(Path_SM, m_File, sizeof(m_File), "configs/admin_levels.cfg"); + + if ((error = textparsers->ParseFile_SMC(m_File, this, &states)) + != SMCError_Okay) + { + const char *err_string = textparsers->GetSMCErrorString(error); + if (!err_string) + { + err_string = "Unknown error"; + } + ParseError(NULL, "Error %d (%s)", error, err_string); + return false; + } + + return true; + } + void ReadSMC_ParseStart() + { + m_LevelState = LEVEL_STATE_NONE; + m_IgnoreLevel = 0; + memset(g_FlagSet, 0, sizeof(g_FlagSet)); + } + SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name) + { + if (m_IgnoreLevel) + { + m_IgnoreLevel++; + return SMCResult_Continue; + } + + if (m_LevelState == LEVEL_STATE_NONE) + { + if (strcmp(name, "Levels") == 0) + { + m_LevelState = LEVEL_STATE_LEVELS; + } + else + { + m_IgnoreLevel++; + } + } else if (m_LevelState == LEVEL_STATE_LEVELS) { + if (strcmp(name, "Flags") == 0) + { + m_LevelState = LEVEL_STATE_FLAGS; + } + else + { + m_IgnoreLevel++; + } + } + else + { + m_IgnoreLevel++; + } + + return SMCResult_Continue; + } + SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value) + { + if (m_LevelState != LEVEL_STATE_FLAGS || m_IgnoreLevel) + { + return SMCResult_Continue; + } + + unsigned char c = (unsigned)value[0]; + + if (c < (unsigned)'a' || c > (unsigned)'z') + { + ParseError(states, "Flag \"%c\" is not a lower-case ASCII letter", c); + return SMCResult_Continue; + } + + c -= (unsigned)'a'; + + if (!g_Admins.FindFlag(key, &g_FlagLetters[c])) + { + ParseError(states, "Unrecognized admin level \"%s\"", key); + return SMCResult_Continue; + } + + g_FlagSet[c] = true; + + return SMCResult_Continue; + } + SMCResult ReadSMC_LeavingSection(const SMCStates *states) + { + if (m_IgnoreLevel) + { + m_IgnoreLevel--; + return SMCResult_Continue; + } + + if (m_LevelState == LEVEL_STATE_FLAGS) + { + m_LevelState = LEVEL_STATE_LEVELS; + return SMCResult_Halt; + } + else if (m_LevelState == LEVEL_STATE_LEVELS) + { + m_LevelState = LEVEL_STATE_NONE; + } + + return SMCResult_Continue; + } + void ParseError(const SMCStates *states, const char *message, ...) + { + va_list ap; + char buffer[256]; + + va_start(ap, message); + UTIL_FormatArgs(buffer, sizeof(buffer), message, ap); + va_end(ap); + + if (!m_bFileNameLogged) + { + g_Logger.LogError("[SM] Parse error(s) detected in file \"%s\":", m_File); + m_bFileNameLogged = true; + } + + g_Logger.LogError("[SM] (Line %d): %s", states ? states->line : 0, buffer); + } +private: + bool m_bFileNameLogged; + char m_File[PLATFORM_MAX_PATH]; + int m_LevelState; + int m_IgnoreLevel; +} s_FlagReader; + +AdminCache::AdminCache() +{ + m_pCmdOverrides = sm_trie_create(); + m_pCmdGrpOverrides = sm_trie_create(); + m_pStrings = new BaseStringTable(1024); + m_pMemory = m_pStrings->GetMemTable(); + m_FreeGroupList = m_FirstGroup = m_LastGroup = INVALID_GROUP_ID; + m_FreeUserList = m_FirstUser = m_LastUser = INVALID_ADMIN_ID; + m_pGroups = sm_trie_create(); + m_pCacheFwd = NULL; + m_FirstGroup = -1; + m_pAuthTables = sm_trie_create(); + m_InvalidatingAdmins = false; + m_destroying = false; + m_pLevelNames = sm_trie_create(); +} + +AdminCache::~AdminCache() +{ + m_destroying = true; + DumpAdminCache(AdminCache_Overrides, false); + DumpAdminCache(AdminCache_Groups, false); + + sm_trie_destroy(m_pCmdGrpOverrides); + sm_trie_destroy(m_pCmdOverrides); + + if (m_pGroups) + { + sm_trie_destroy(m_pGroups); + } + + List::iterator iter; + for (iter=m_AuthMethods.begin(); + iter!=m_AuthMethods.end(); + iter++) + { + sm_trie_destroy((*iter).table); + } + + sm_trie_destroy(m_pAuthTables); + + delete m_pStrings; + + sm_trie_destroy(m_pLevelNames); +} + +void AdminCache::OnSourceModStartup(bool late) +{ + RegisterAuthIdentType("steam"); + RegisterAuthIdentType("name"); + RegisterAuthIdentType("ip"); + + NameFlag("reservation", Admin_Reservation); + NameFlag("kick", Admin_Kick); + NameFlag("generic", Admin_Generic); + NameFlag("ban", Admin_Ban); + NameFlag("unban", Admin_Unban); + NameFlag("slay", Admin_Slay); + NameFlag("changemap", Admin_Changemap); + NameFlag("cvars", Admin_Convars); + NameFlag("config", Admin_Config); + NameFlag("chat", Admin_Chat); + NameFlag("vote", Admin_Vote); + NameFlag("password", Admin_Password); + NameFlag("rcon", Admin_RCON); + NameFlag("cheats", Admin_Cheats); + NameFlag("root", Admin_Root); + NameFlag("custom1", Admin_Custom1); + NameFlag("custom2", Admin_Custom2); + NameFlag("custom3", Admin_Custom3); + NameFlag("custom4", Admin_Custom4); + NameFlag("custom5", Admin_Custom5); + NameFlag("custom6", Admin_Custom6); +} + +void AdminCache::OnSourceModAllInitialized() +{ + m_pCacheFwd = g_Forwards.CreateForward("OnRebuildAdminCache", ET_Ignore, 1, NULL, Param_Cell); + g_ShareSys.AddInterface(NULL, this); +} + +void AdminCache::OnSourceModLevelChange(const char *mapName) +{ + int i; + AdminFlag flag; + + /* For now, we only read these once per level. */ + s_FlagReader.LoadLevels(); + + for (i = 0; i < 26; i++) + { + if (FindFlag('a' + i, &flag)) + { + g_ReverseFlags[flag] = 'a' + i; + } + else + { + g_ReverseFlags[flag] = '?'; + } + } +} + +void AdminCache::OnSourceModShutdown() +{ + g_Forwards.ReleaseForward(m_pCacheFwd); + m_pCacheFwd = NULL; +} + +void AdminCache::OnSourceModPluginsLoaded() +{ + DumpAdminCache(AdminCache_Overrides, true); + DumpAdminCache(AdminCache_Groups, true); +} + +void AdminCache::NameFlag(const char *str, AdminFlag flag) +{ + sm_trie_insert(m_pLevelNames, str, (void *)flag); +} + +bool AdminCache::FindFlag(const char *str, AdminFlag *pFlag) +{ + void *obj; + if (!sm_trie_retrieve(m_pLevelNames, str, &obj)) + { + return false; + } + + if (pFlag) + { + *pFlag = (AdminFlag)(int)obj; + } + + return true; +} + +void AdminCache::AddCommandOverride(const char *cmd, OverrideType type, FlagBits flags) +{ + Trie *pTrie = NULL; + if (type == Override_Command) + { + pTrie = m_pCmdOverrides; + } else if (type == Override_CommandGroup) { + pTrie = m_pCmdGrpOverrides; + } else { + return; + } + + sm_trie_insert(pTrie, cmd, (void *)(unsigned int)flags); + + g_ConCmds.UpdateAdminCmdFlags(cmd, type, flags, false); +} + +bool AdminCache::GetCommandOverride(const char *cmd, OverrideType type, FlagBits *pFlags) +{ + Trie *pTrie = NULL; + + if (type == Override_Command) + { + pTrie = m_pCmdOverrides; + } else if (type == Override_CommandGroup) { + pTrie = m_pCmdGrpOverrides; + } else { + return false; + } + + void *object; + if (sm_trie_retrieve(pTrie, cmd, &object)) + { + if (pFlags) + { + *pFlags = (FlagBits)object; + } + return true; + } + + return false; +} + +void AdminCache::UnsetCommandOverride(const char *cmd, OverrideType type) +{ + if (type == Override_Command) + { + return _UnsetCommandOverride(cmd); + } else if (type == Override_CommandGroup) { + return _UnsetCommandGroupOverride(cmd); + } +} + +void AdminCache::_UnsetCommandGroupOverride(const char *group) +{ + if (!m_pCmdGrpOverrides) + { + return; + } + + sm_trie_delete(m_pCmdGrpOverrides, group); + + g_ConCmds.UpdateAdminCmdFlags(group, Override_CommandGroup, 0, true); +} + +void AdminCache::_UnsetCommandOverride(const char *cmd) +{ + if (!m_pCmdOverrides) + { + return; + } + + sm_trie_delete(m_pCmdOverrides, cmd); + + g_ConCmds.UpdateAdminCmdFlags(cmd, Override_Command, 0, true); +} + +void AdminCache::DumpCommandOverrideCache(OverrideType type) +{ + if (type == Override_Command && m_pCmdOverrides) + { + sm_trie_clear(m_pCmdOverrides); + } else if (type == Override_CommandGroup && m_pCmdGrpOverrides) { + sm_trie_clear(m_pCmdGrpOverrides); + } +} + +AdminId AdminCache::CreateAdmin(const char *name) +{ + AdminId id; + AdminUser *pUser; + + if (m_FreeUserList != INVALID_ADMIN_ID) + { + pUser = (AdminUser *)m_pMemory->GetAddress(m_FreeUserList); + assert(pUser->magic == USR_MAGIC_UNSET); + id = m_FreeUserList; + m_FreeUserList = pUser->next_user; + } + else + { + id = m_pMemory->CreateMem(sizeof(AdminUser), (void **)&pUser); + pUser->grp_size = 0; + pUser->grp_table = -1; + } + + pUser->flags = 0; + pUser->eflags = 0; + pUser->grp_count = 0; + pUser->password = -1; + pUser->magic = USR_MAGIC_SET; + pUser->auth.identidx = -1; + pUser->auth.index = 0; + pUser->immunity_level = 0; + pUser->serialchange = 1; + + if (m_FirstUser == INVALID_ADMIN_ID) + { + m_FirstUser = id; + m_LastUser = id; + } + else + { + AdminUser *pPrev = (AdminUser *)m_pMemory->GetAddress(m_LastUser); + pPrev->next_user = id; + pUser->prev_user = m_LastUser; + m_LastUser = id; + } + + /* Since we always append to the tail, we should invalidate their next */ + pUser->next_user = -1; + + if (name && name[0] != '\0') + { + int nameidx = m_pStrings->AddString(name); + pUser = (AdminUser *)m_pMemory->GetAddress(id); + pUser->nameidx = nameidx; + } + else + { + pUser->nameidx = -1; + } + + return id; +} + +GroupId AdminCache::AddGroup(const char *group_name) +{ + if (sm_trie_retrieve(m_pGroups, group_name, NULL)) + { + return INVALID_GROUP_ID; + } + + GroupId id; + AdminGroup *pGroup; + if (m_FreeGroupList != INVALID_GROUP_ID) + { + pGroup = (AdminGroup *)m_pMemory->GetAddress(m_FreeGroupList); + assert(pGroup->magic == GRP_MAGIC_UNSET); + id = m_FreeGroupList; + m_FreeGroupList = pGroup->next_grp; + } else { + id = m_pMemory->CreateMem(sizeof(AdminGroup), (void **)&pGroup); + } + + pGroup->immunity_level = 0; + pGroup->immune_table = -1; + pGroup->magic = GRP_MAGIC_SET; + pGroup->next_grp = INVALID_GROUP_ID; + pGroup->pCmdGrpTable = NULL; + pGroup->pCmdTable = NULL; + pGroup->addflags = 0; + + if (m_FirstGroup == INVALID_GROUP_ID) + { + m_FirstGroup = id; + m_LastGroup = id; + pGroup->prev_grp = INVALID_GROUP_ID; + } else { + AdminGroup *pPrev = (AdminGroup *)m_pMemory->GetAddress(m_LastGroup); + assert(pPrev->magic == GRP_MAGIC_SET); + pPrev->next_grp = id; + pGroup->prev_grp = m_LastGroup; + m_LastGroup = id; + } + + int nameidx = m_pStrings->AddString(group_name); + pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + pGroup->nameidx = nameidx; + + sm_trie_insert(m_pGroups, group_name, (void *)id); + + return id; +} + +GroupId AdminCache::FindGroupByName(const char *group_name) +{ + void *object; + + if (!sm_trie_retrieve(m_pGroups, group_name, &object)) + { + return INVALID_GROUP_ID; + } + + GroupId id = (GroupId)object; + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return INVALID_GROUP_ID; + } + + return id; +} + +void AdminCache::SetGroupAddFlag(GroupId id, AdminFlag flag, bool enabled) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return; + } + + if (flag < Admin_Reservation || flag >= AdminFlags_TOTAL) + { + return; + } + + FlagBits bits = (1<<(FlagBits)flag); + + if (enabled) + { + pGroup->addflags |= bits; + } else { + pGroup->addflags &= ~bits; + } +} + +bool AdminCache::GetGroupAddFlag(GroupId id, AdminFlag flag) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return false; + } + + if (flag < Admin_Reservation || flag >= AdminFlags_TOTAL) + { + return false; + } + + FlagBits bit = 1<<(FlagBits)flag; + return ((pGroup->addflags & bit) == bit); +} + +FlagBits AdminCache::GetGroupAddFlags(GroupId id) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return 0; + } + + return pGroup->addflags; +} + +const char *AdminCache::GetGroupName(GroupId gid) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(gid); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return NULL; + } + + return m_pStrings->GetString(pGroup->nameidx); +} + +void AdminCache::SetGroupGenericImmunity(GroupId id, ImmunityType type, bool enabled) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return; + } + + unsigned int level = 0; + + if (enabled) + { + if (type == Immunity_Default) + { + level = 1; + } else if (type == Immunity_Global) { + level = 2; + } + if (level > pGroup->immunity_level) + { + pGroup->immunity_level = level; + } + } else { + pGroup->immunity_level = 0; + } +} + +bool AdminCache::GetGroupGenericImmunity(GroupId id, ImmunityType type) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return false; + } + + if (type == Immunity_Default && pGroup->immunity_level >= 1) + { + return true; + } else if (type == Immunity_Global && pGroup->immunity_level >= 2) { + return true; + } + + return false; +} + +void AdminCache::AddGroupImmunity(GroupId id, GroupId other_id) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(other_id); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return; + } + + pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return; + } + + /* We always need to resize the immunity table */ + int *table, tblidx; + if (pGroup->immune_table == -1) + { + tblidx = m_pMemory->CreateMem(sizeof(int) * 2, (void **)&table); + pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + table[0] = 0; + } else { + int *old_table = (int *)m_pMemory->GetAddress(pGroup->immune_table); + /* Break out if this group is already in the list */ + for (int i=0; iCreateMem(sizeof(int) * (old_table[0] + 2), (void **)&table); + /* Get the old address again in case of resize */ + pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + old_table = (int *)m_pMemory->GetAddress(pGroup->immune_table); + table[0] = old_table[0]; + for (unsigned int i=1; i<=(unsigned int)old_table[0]; i++) + { + table[i] = old_table[i]; + } + } + + /* Assign */ + pGroup->immune_table = tblidx; + + /* Add to the array */ + table[0]++; + table[table[0]] = other_id; +} + +unsigned int AdminCache::GetGroupImmunityCount(GroupId id) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return 0; + } + + if (pGroup->immune_table == -1) + { + return 0; + } + + int *table = (int *)m_pMemory->GetAddress(pGroup->immune_table); + + return table[0]; +} + +GroupId AdminCache::GetGroupImmunity(GroupId id, unsigned int number) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return INVALID_GROUP_ID; + } + + if (pGroup->immune_table == -1) + { + return INVALID_GROUP_ID; + } + + int *table = (int *)m_pMemory->GetAddress(pGroup->immune_table); + if (number >= (unsigned int)table[0]) + { + return INVALID_GROUP_ID; + } + + return table[1 + number]; +} + +void AdminCache::AddGroupCommandOverride(GroupId id, const char *name, OverrideType type, OverrideRule rule) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return; + } + + Trie *pTrie = NULL; + if (type == Override_Command) + { + if (pGroup->pCmdTable == NULL) + { + pGroup->pCmdTable = sm_trie_create(); + } + pTrie = pGroup->pCmdTable; + } else if (type == Override_CommandGroup) { + if (pGroup->pCmdGrpTable == NULL) + { + pGroup->pCmdGrpTable = sm_trie_create(); + } + pTrie = pGroup->pCmdGrpTable; + } else { + return; + } + + sm_trie_insert(pTrie, name, (void *)(int)rule); +} + +bool AdminCache::GetGroupCommandOverride(GroupId id, const char *name, OverrideType type, OverrideRule *pRule) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return false; + } + + Trie *pTrie = NULL; + if (type == Override_Command) + { + if (pGroup->pCmdTable == NULL) + { + return false; + } + pTrie = pGroup->pCmdTable; + } else if (type == Override_CommandGroup) { + if (pGroup->pCmdGrpTable == NULL) + { + return false; + } + pTrie = pGroup->pCmdGrpTable; + } else { + return false; + } + + void *object; + if (!sm_trie_retrieve(pTrie, name, &object)) + { + return false; + } + + if (pRule) + { + *pRule = (OverrideRule)(int)object; + } + + return true; +} + +Trie *AdminCache::GetMethodByIndex(unsigned int index) +{ + List::iterator iter; + for (iter=m_AuthMethods.begin(); + iter!=m_AuthMethods.end(); + iter++) + { + if (index-- == 0) + { + return (*iter).table; + } + } + + return NULL; +} + +bool AdminCache::InvalidateAdmin(AdminId id) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + AdminUser *pOther; + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return false; + } + + if (!m_InvalidatingAdmins && !m_destroying) + { + g_Players.ClearAdminId(id); + } + + /* Unlink from the dbl link list */ + if (id == m_FirstUser && id == m_LastUser) + { + m_FirstUser = INVALID_ADMIN_ID; + m_LastUser = INVALID_ADMIN_ID; + } else if (id == m_FirstUser) { + m_FirstUser = pUser->next_user; + pOther = (AdminUser *)m_pMemory->GetAddress(m_FirstUser); + pOther->prev_user = INVALID_ADMIN_ID; + } else if (id == m_LastUser) { + m_LastUser = pUser->prev_user; + pOther = (AdminUser *)m_pMemory->GetAddress(m_LastUser); + pOther->next_user = INVALID_ADMIN_ID; + } else { + pOther = (AdminUser *)m_pMemory->GetAddress(pUser->prev_user); + pOther->next_user = pUser->next_user; + pOther = (AdminUser *)m_pMemory->GetAddress(pUser->next_user); + pOther->prev_user = pUser->prev_user; + } + + /* Unlink from auth tables */ + if (pUser->auth.identidx != -1) + { + Trie *pTrie = GetMethodByIndex(pUser->auth.index); + if (pTrie) + { + sm_trie_delete(pTrie, m_pStrings->GetString(pUser->auth.identidx)); + } + } + + /* Clear table counts */ + pUser->grp_count = 0; + + /* Link into free list */ + pUser->magic = USR_MAGIC_UNSET; + pUser->next_user = m_FreeUserList; + m_FreeUserList = id; + + /* Unset serial change */ + pUser->serialchange = 0; + + return true; +} + + +void AdminCache::InvalidateGroup(GroupId id) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(id); + AdminGroup *pOther; + + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return; + } + + const char *str = m_pStrings->GetString(pGroup->nameidx); + sm_trie_delete(m_pGroups, str); + + /* Unlink from the live dbllink list */ + if (id == m_FirstGroup && id == m_LastGroup) + { + m_LastGroup = INVALID_GROUP_ID; + m_FirstGroup = INVALID_GROUP_ID; + } else if (id == m_FirstGroup) { + m_FirstGroup = pGroup->next_grp; + pOther = (AdminGroup *)m_pMemory->GetAddress(m_FirstGroup); + pOther->prev_grp = INVALID_GROUP_ID; + } else if (id == m_LastGroup) { + m_LastGroup = pGroup->prev_grp; + pOther = (AdminGroup *)m_pMemory->GetAddress(m_LastGroup); + pOther->next_grp = INVALID_GROUP_ID; + } else { + pOther = (AdminGroup *)m_pMemory->GetAddress(pGroup->prev_grp); + pOther->next_grp = pGroup->next_grp; + pOther = (AdminGroup *)m_pMemory->GetAddress(pGroup->next_grp); + pOther->prev_grp = pGroup->prev_grp; + } + + /* Free any used memory to be safe */ + if (pGroup->pCmdGrpTable) + { + sm_trie_destroy(pGroup->pCmdGrpTable); + pGroup->pCmdGrpTable = NULL; + } + if (pGroup->pCmdTable) + { + sm_trie_destroy(pGroup->pCmdTable); + pGroup->pCmdTable = NULL; + } + + /* Link into the free list */ + pGroup->magic = GRP_MAGIC_UNSET; + pGroup->next_grp = m_FreeGroupList; + m_FreeGroupList = id; + + int idx = m_FirstUser; + AdminUser *pUser; + int *table; + while (idx != INVALID_ADMIN_ID) + { + pUser = (AdminUser *)m_pMemory->GetAddress(idx); + if (pUser->grp_count > 0) + { + table = (int *)m_pMemory->GetAddress(pUser->grp_table); + for (unsigned int i=0; igrp_count; i++) + { + if (table[i] == id) + { + /* We have to remove this entry */ + for (unsigned int j=i+1; jgrp_count; j++) + { + /* Move everything down by one */ + table[j-1] = table[j]; + } + /* Decrease count */ + pUser->grp_count--; + /* Recalculate effective flags */ + pUser->eflags = pUser->flags; + for (unsigned int j=0; jgrp_count; j++) + { + pOther = (AdminGroup *)m_pMemory->GetAddress(table[j]); + pUser->eflags |= pOther->addflags; + } + /* Mark as changed */ + pUser->serialchange++; + /* Break now, duplicates aren't allowed */ + break; + } + } + } + idx = pUser->next_user; + } +} + +void AdminCache::InvalidateGroupCache() +{ + /* Nuke the free list */ + m_FreeGroupList = -1; + + /* Nuke reverse lookups */ + sm_trie_clear(m_pGroups); + + /* Free memory on groups */ + GroupId cur = m_FirstGroup; + AdminGroup *pGroup; + while (cur != INVALID_GROUP_ID) + { + pGroup = (AdminGroup *)m_pMemory->GetAddress(cur); + assert(pGroup->magic == GRP_MAGIC_SET); + if (pGroup->pCmdGrpTable) + { + sm_trie_destroy(pGroup->pCmdGrpTable); + } + if (pGroup->pCmdTable) + { + sm_trie_destroy(pGroup->pCmdTable); + } + cur = pGroup->next_grp; + } + + m_FirstGroup = INVALID_GROUP_ID; + m_LastGroup = INVALID_GROUP_ID; + + InvalidateAdminCache(false); + + /* Reset the memory table */ + m_pMemory->Reset(); +} + +void AdminCache::AddAdminListener(IAdminListener *pListener) +{ + m_hooks.push_back(pListener); +} + +void AdminCache::RemoveAdminListener(IAdminListener *pListener) +{ + m_hooks.remove(pListener); +} + +void AdminCache::RegisterAuthIdentType(const char *name) +{ + if (sm_trie_retrieve(m_pAuthTables, name, NULL)) + { + return; + } + + Trie *pAuth = sm_trie_create(); + + AuthMethod method; + method.name.assign(name); + method.table = pAuth; + + m_AuthMethods.push_back(method); + + sm_trie_insert(m_pAuthTables, name, pAuth); +} + +void AdminCache::InvalidateAdminCache(bool unlink_admins) +{ + m_InvalidatingAdmins = true; + if (!m_destroying) + { + g_Players.ClearAllAdmins(); + } + /* Wipe the identity cache first */ + List::iterator iter; + for (iter=m_AuthMethods.begin(); + iter!=m_AuthMethods.end(); + iter++) + { + sm_trie_clear((*iter).table); + } + + if (unlink_admins) + { + while (m_FirstUser != INVALID_ADMIN_ID) + { + InvalidateAdmin(m_FirstUser); + } + } else { + m_FirstUser = -1; + m_LastUser = -1; + m_FreeUserList = -1; + } + m_InvalidatingAdmins = false; +} + +void AdminCache::DumpAdminCache(AdminCachePart part, bool rebuild) +{ + List::iterator iter; + IAdminListener *pListener; + cell_t result; + + if (part == AdminCache_Overrides) + { + DumpCommandOverrideCache(Override_Command); + DumpCommandOverrideCache(Override_CommandGroup); + if (rebuild && !m_destroying) + { + for (iter=m_hooks.begin(); iter!=m_hooks.end(); iter++) + { + pListener = (*iter); + pListener->OnRebuildOverrideCache(); + } + m_pCacheFwd->PushCell(part); + m_pCacheFwd->Execute(&result); + } + } else if (part == AdminCache_Groups || part == AdminCache_Admins) { + if (part == AdminCache_Groups) + { + InvalidateGroupCache(); + if (rebuild && !m_destroying) + { + for (iter=m_hooks.begin(); iter!=m_hooks.end(); iter++) + { + pListener = (*iter); + pListener->OnRebuildGroupCache(); + } + m_pCacheFwd->PushCell(part); + m_pCacheFwd->Execute(&result); + } + } + InvalidateAdminCache(true); + if (rebuild && !m_destroying) + { + for (iter=m_hooks.begin(); iter!=m_hooks.end(); iter++) + { + pListener = (*iter); + pListener->OnRebuildAdminCache((part == AdminCache_Groups)); + } + m_pCacheFwd->PushCell(AdminCache_Admins); + m_pCacheFwd->Execute(&result); + g_Players.RecheckAnyAdmins(); + } + } +} + +const char *AdminCache::GetAdminName(AdminId id) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return NULL; + } + + return m_pStrings->GetString(pUser->nameidx); +} + +bool AdminCache::GetMethodIndex(const char *name, unsigned int *_index) +{ + List::iterator iter; + unsigned int index = 0; + for (iter=m_AuthMethods.begin(); + iter!=m_AuthMethods.end(); + iter++,index++) + { + if ((*iter).name.compare(name) == 0) + { + *_index = index; + return true; + } + } + + return false; +} + +bool AdminCache::BindAdminIdentity(AdminId id, const char *auth, const char *ident) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return false; + } + + Trie *pTable; + if (!sm_trie_retrieve(m_pAuthTables, auth, (void **)&pTable)) + { + return false; + } + + if (sm_trie_retrieve(pTable, ident, NULL)) + { + return false; + } + + int i_ident = m_pStrings->AddString(ident); + + pUser = (AdminUser *)m_pMemory->GetAddress(id); + pUser->auth.identidx = i_ident; + GetMethodIndex(auth, &pUser->auth.index); + + return sm_trie_insert(pTable, ident, (void **)id); +} + +AdminId AdminCache::FindAdminByIdentity(const char *auth, const char *identity) +{ + Trie *pTable; + if (!sm_trie_retrieve(m_pAuthTables, auth, (void **)&pTable)) + { + return INVALID_ADMIN_ID; + } + + void *object; + if (!sm_trie_retrieve(pTable, identity, &object)) + { + return INVALID_ADMIN_ID; + } + + return (AdminId)object; +} + +void AdminCache::SetAdminFlag(AdminId id, AdminFlag flag, bool enabled) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return; + } + + if (flag < Admin_Reservation + || flag >= AdminFlags_TOTAL) + { + return; + } + + FlagBits bits = (1<<(FlagBits)flag); + + if (enabled) + { + pUser->flags |= bits; + pUser->eflags |= bits; + } else { + pUser->flags &= ~bits; + pUser->eflags &= ~bits; + } + + pUser->serialchange++; +} + +bool AdminCache::GetAdminFlag(AdminId id, AdminFlag flag, AccessMode mode) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return false; + } + + if (flag < Admin_Reservation + || flag >= AdminFlags_TOTAL) + { + return false; + } + + FlagBits bit = (1<<(FlagBits)flag); + + if (mode == Access_Real) + { + return ((pUser->flags & bit) == bit); + } else if (mode == Access_Effective) { + bool has_bit = ((pUser->eflags & bit) == bit); + if (!has_bit && flag != Admin_Root && ((pUser->eflags & ADMFLAG_ROOT) == ADMFLAG_ROOT)) + { + has_bit = true; + } + return has_bit; + } + + return false; +} + +FlagBits AdminCache::GetAdminFlags(AdminId id, AccessMode mode) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return 0; + } + + if (mode == Access_Real) + { + return pUser->flags; + } else if (mode == Access_Effective) { + return pUser->eflags; + } + + return 0; +} + +void AdminCache::SetAdminFlags(AdminId id, AccessMode mode, FlagBits bits) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return; + } + + if (mode == Access_Real) + { + pUser->flags = bits; + pUser->eflags = bits; + } else if (mode == Access_Effective) { + pUser->eflags = bits; + } + + pUser->serialchange++; +} + +bool AdminCache::AdminInheritGroup(AdminId id, GroupId gid) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return false; + } + + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(gid); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return false; + } + + /* First check for duplicates */ + if (pUser->grp_count != 0) + { + int *temp_table = (int *)m_pMemory->GetAddress(pUser->grp_table); + for (unsigned int i=0; igrp_count; i++) + { + if (temp_table[i] == gid) + { + return false; + } + } + } + + int *table; + if (pUser->grp_count + 1 > pUser->grp_size) + { + int new_size = 0; + int tblidx; + + if (pUser->grp_size == 0) + { + new_size = 2; + } else { + new_size = pUser->grp_size * 2; + } + + /* Create and refresh pointers */ + tblidx = m_pMemory->CreateMem(new_size * sizeof(int), (void **)&table); + pUser = (AdminUser *)m_pMemory->GetAddress(id); + pGroup = (AdminGroup *)m_pMemory->GetAddress(gid); + + /* Copy old data if necessary */ + if (pUser->grp_table != -1) + { + int *old_table = (int *)m_pMemory->GetAddress(pUser->grp_table); + memcpy(table, old_table, sizeof(int) * pUser->grp_count); + } + pUser->grp_table = tblidx; + pUser->grp_size = new_size; + } else { + table = (int *)m_pMemory->GetAddress(pUser->grp_table); + } + + table[pUser->grp_count] = gid; + pUser->grp_count++; + + /* Compute new effective permissions */ + pUser->eflags |= pGroup->addflags; + + if (pGroup->immunity_level > pUser->immunity_level) + { + pUser->immunity_level = pGroup->immunity_level; + } + + pUser->serialchange++; + + return true; +} + +bool AdminCache::IsValidAdmin(AdminId id) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + return (pUser != NULL && pUser->magic == USR_MAGIC_SET); +} + +unsigned int AdminCache::GetAdminGroupCount(AdminId id) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return 0; + } + + return pUser->grp_count; +} + +GroupId AdminCache::GetAdminGroup(AdminId id, unsigned int index, const char **name) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET || index >= pUser->grp_count) + { + return INVALID_GROUP_ID; + } + + int *table = (int *)m_pMemory->GetAddress(pUser->grp_table); + + GroupId gid = table[index]; + + if (name) + { + *name = GetGroupName(gid); + } + + return gid; +} + +const char *AdminCache::GetAdminPassword(AdminId id) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return NULL; + } + + return m_pStrings->GetString(pUser->password); +} + +void AdminCache::SetAdminPassword(AdminId id, const char *password) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return; + } + + if (password[0] == '\0') + { + pUser->password = -1; + return; + } + + int i_password = m_pStrings->AddString(password); + pUser = (AdminUser *)m_pMemory->GetAddress(id); + pUser->password = i_password; +} + +unsigned int AdminCache::FlagBitsToBitArray(FlagBits bits, bool array[], unsigned int maxSize) +{ + unsigned int i; + for (i=0; iGetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return false; + } + + return ((pUser->eflags & bits) == bits); +} + +bool AdminCache::CanAdminTarget(AdminId id, AdminId target) +{ + /** + * Zeroth, if the targeting AdminId is INVALID_ADMIN_ID, targeting fails. + * First, if the targeted AdminId is INVALID_ADMIN_ID, targeting succeeds. + */ + + if (id == INVALID_ADMIN_ID) + { + return false; + } + + if (target == INVALID_ADMIN_ID) + { + return true; + } + + if (id == target) + { + return true; + } + + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return false; + } + + AdminUser *pTarget = (AdminUser *)m_pMemory->GetAddress(target); + if (!pTarget || pTarget->magic != USR_MAGIC_SET) + { + return false; + } + + /** + * Second, if the targeting admin is root, targeting succeeds. + */ + if (pUser->eflags & ADMFLAG_ROOT) + { + return true; + } + + /** Fourth, if the targeted admin is immune from targeting admin. */ + int mode = sm_immunity_mode.GetInt(); + switch (mode) + { + case 1: + { + if (pTarget->immunity_level > pUser->immunity_level) + { + return false; + } + break; + } + case 3: + { + /* If neither has any immunity, let this pass. */ + if (!pUser->immunity_level && !pTarget->immunity_level) + { + return true; + } + /* Don't break, go to the next case. */ + } + case 2: + { + if (pTarget->immunity_level >= pUser->immunity_level) + { + return false; + } + break; + } + } + + /** + * Fifth, if the targeted admin has specific immunity from the + * targeting admin via group immunities, targeting fails. + */ + //:TODO: speed this up... maybe with trie hacks. + //idea is to insert %d.%d in the trie after computing this and use it as a cache lookup. + //problem is the trie cannot delete prefixes, so we'd have a problem with invalidations. + if (pTarget->grp_count > 0 && pUser->grp_count > 0) + { + int *grp_table = (int *)m_pMemory->GetAddress(pTarget->grp_table); + int *src_table = (int *)m_pMemory->GetAddress(pUser->grp_table); + GroupId id, other; + unsigned int num; + for (unsigned int i=0; igrp_count; i++) + { + id = grp_table[i]; + num = GetGroupImmunityCount(id); + for (unsigned int j=0; jgrp_count; k++) + { + if (other == src_table[k]) + { + return false; + } + } + } + } + } + + return true; +} + +bool AdminCache::FindFlag(char c, AdminFlag *pAdmFlag) +{ + if (c < 'a' + || c > 'z' + || !g_FlagSet[(unsigned)c - (unsigned)'a']) + { + return false; + } + + if (pAdmFlag) + { + *pAdmFlag = g_FlagLetters[(unsigned)c - (unsigned)'a']; + } + + return true; +} + +bool AdminCache::FindFlagChar(AdminFlag flag, char *c) +{ + if (!g_FlagSet[flag]) + { + return false; + } + + if (c) + { + *c = g_ReverseFlags[flag]; + } + + return true; +} + +FlagBits AdminCache::ReadFlagString(const char *flags, const char **end) +{ + FlagBits bits = 0; + + while (flags && (*flags != '\0')) + { + AdminFlag flag; + if (!FindFlag(*flags, &flag)) + { + break; + } + bits |= FlagArrayToBits(&flag, 1); + flags++; + } + + if (end) + { + *end = flags; + } + + return bits; +} + +unsigned int AdminCache::GetAdminSerialChange(AdminId id) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return 0; + } + + return pUser->serialchange; +} + +bool AdminCache::CanAdminUseCommand(int client, const char *cmd) +{ + FlagBits bits; + OverrideType otype = Override_Command; + + if (cmd[0] == '@') + { + otype = Override_CommandGroup; + cmd++; + } + + if (!g_ConCmds.LookForCommandAdminFlags(cmd, &bits)) + { + if (!GetCommandOverride(cmd, otype, &bits)) + { + bits = 0; + } + } + + return g_ConCmds.CheckCommandAccess(client, cmd, bits); +} + +unsigned int AdminCache::SetGroupImmunityLevel(GroupId gid, unsigned int level) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(gid); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return 0; + } + + unsigned int old_level = pGroup->immunity_level; + + pGroup->immunity_level = level; + + return old_level; +} + +unsigned int AdminCache::GetGroupImmunityLevel(GroupId gid) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(gid); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return 0; + } + + return pGroup->immunity_level; +} + +unsigned int AdminCache::SetAdminImmunityLevel(AdminId id, unsigned int level) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return 0; + } + + unsigned int old_level = pUser->immunity_level; + + pUser->immunity_level = level; + + return old_level; +} + +unsigned int AdminCache::GetAdminImmunityLevel(AdminId id) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return 0; + } + + return pUser->immunity_level; +} + +bool AdminCache::CheckAccess(int client, const char *cmd, FlagBits flags, bool override_only) +{ + if (client == 0) + { + return true; + } + + /* Auto-detect a command if we can */ + FlagBits bits = flags; + bool found_command = false; + if (!override_only) + { + found_command = g_ConCmds.LookForCommandAdminFlags(cmd, &bits); + } + + if (!found_command) + { + GetCommandOverride(cmd, Override_Command, &bits); + } + + return g_ConCmds.CheckCommandAccess(client, cmd, bits) ? 1 : 0; +} + +void iterator_glob_basic_override(Trie *pTrie, const char *key, void **value, void *data) +{ + FILE *fp; + int flags; + char flagstr[64]; + + fp = (FILE *)data; + flags = (int)*value; + g_Admins.FillFlagString(flags, flagstr, sizeof(flagstr)); + + fprintf(fp, "\t\"%s\"\t\t\"%s\"\n", key, flagstr); +} + +void iterator_glob_grp_override(Trie *pTrie, const char *key, void **value, void *data) +{ + FILE *fp; + int flags; + char flagstr[64]; + + fp = (FILE *)data; + flags = (int)*value; + g_Admins.FillFlagString(flags, flagstr, sizeof(flagstr)); + + fprintf(fp, "\t\"@%s\"\t\t\"%s\"\n", key, flagstr); +} + +void iterator_group_basic_override(Trie *pTrie, const char *key, void **value, void *data) +{ + FILE *fp; + int flags; + char flagstr[64]; + + fp = (FILE *)data; + flags = (int)*value; + g_Admins.FillFlagString(flags, flagstr, sizeof(flagstr)); + + fprintf(fp, "\t\t\t\"%s\"\t\t\"%s\"\n", key, flagstr); +} + +void iterator_group_grp_override(Trie *pTrie, const char *key, void **value, void *data) +{ + FILE *fp; + int flags; + char flagstr[64]; + + fp = (FILE *)data; + flags = (int)*value; + g_Admins.FillFlagString(flags, flagstr, sizeof(flagstr)); + + fprintf(fp, "\t\t\t\"@%s\"\t\t\"%s\"\n", key, flagstr); +} + +void AdminCache::DumpCache(FILE *fp) +{ + int *itable; + AdminId aid; + GroupId gid; + char flagstr[64]; + unsigned int num; + AdminUser *pAdmin; + AdminGroup *pGroup; + char name_buffer[512]; + + fprintf(fp, "\"Groups\"\n{\n"); + + num = 0; + gid = m_FirstGroup; + while (gid != INVALID_GROUP_ID + && (pGroup = GetGroup(gid)) != NULL) + { + num++; + FillFlagString(pGroup->addflags, flagstr, sizeof(flagstr)); + + fprintf(fp, "\t/* num = %d, gid = 0x%X */\n", num, gid); + fprintf(fp, "\t\"%s\"\n\t{\n", GetString(pGroup->nameidx)); + fprintf(fp, "\t\t\"flags\"\t\t\t\"%s\"\n", flagstr); + fprintf(fp, "\t\t\"immunity\"\t\t\"%d\"\n", pGroup->immunity_level); + + if (pGroup->immune_table != -1 + && (itable = (int *)m_pMemory->GetAddress(pGroup->immune_table)) != NULL) + { + AdminGroup *pAltGroup; + const char *gname, *mod; + + for (int i = 1; i <= itable[0]; i++) + { + if ((pAltGroup = GetGroup(itable[i])) == NULL) + { + /* Assume the rest of the table is corrupt */ + break; + } + gname = GetString(pAltGroup->nameidx); + if (atoi(gname) != 0) + { + mod = "@"; + } + else + { + mod = ""; + } + fprintf(fp, "\t\t\"immunity\"\t\t\"%s%s\"\n", mod, gname); + } + } + + fprintf(fp, "\n\t\t\"Overrides\"\n\t\t{\n"); + if (pGroup->pCmdGrpTable != NULL) + { + sm_trie_bad_iterator(pGroup->pCmdGrpTable, + name_buffer, + sizeof(name_buffer), + iterator_group_grp_override, + fp); + } + if (pGroup->pCmdTable != NULL) + { + sm_trie_bad_iterator(pGroup->pCmdTable, + name_buffer, + sizeof(name_buffer), + iterator_group_basic_override, + fp); + } + fprintf(fp, "\t\t}\n"); + + fprintf(fp, "\t}\n"); + + if ((gid = pGroup->next_grp) != INVALID_GROUP_ID) + { + fprintf(fp, "\n"); + } + } + + fprintf(fp, "}\n\n"); + fprintf(fp, "\"Admins\"\n{\n"); + + num = 0; + aid = m_FirstUser; + while (aid != INVALID_ADMIN_ID + && (pAdmin = GetUser(aid)) != NULL) + { + num++; + FillFlagString(pAdmin->flags, flagstr, sizeof(flagstr)); + + fprintf(fp, "\t/* num = %d, aid = 0x%X, serialno = 0x%X*/\n", num, aid, pAdmin->serialchange); + + if (pAdmin->nameidx != -1) + { + fprintf(fp, "\t\"%s\"\n\t{\n", GetString(pAdmin->nameidx)); + } + else + { + fprintf(fp, "\t\"\"\n\t{\n"); + } + + if (pAdmin->auth.identidx != -1) + { + fprintf(fp, "\t\t\"auth\"\t\t\t\"%s\"\n", GetMethodName(pAdmin->auth.index)); + fprintf(fp, "\t\t\"identity\"\t\t\"%s\"\n", GetString(pAdmin->auth.identidx)); + } + if (pAdmin->password != -1) + { + fprintf(fp, "\t\t\"password\"\t\t\"%s\"\n", GetString(pAdmin->password)); + } + fprintf(fp, "\t\t\"flags\"\t\t\t\"%s\"\n", flagstr); + fprintf(fp, "\t\t\"immunity\"\t\t\"%d\"\n", pAdmin->immunity_level); + + if (pAdmin->grp_count != 0 + && pAdmin->grp_table != -1 + && (itable = (int *)m_pMemory->GetAddress(pAdmin->grp_table)) != NULL) + { + unsigned int i; + + for (i = 0; i < pAdmin->grp_count; i++) + { + if ((pGroup = GetGroup(itable[i])) == NULL) + { + /* Assume the rest of the table is corrupt */ + break; + } + fprintf(fp, "\t\t\"group\"\t\t\t\"%s\"\n", GetString(pGroup->nameidx)); + } + } + + fprintf(fp, "\t}\n"); + + if ((aid = pAdmin->next_user) != INVALID_ADMIN_ID) + { + fprintf(fp, "\n"); + } + } + + fprintf(fp, "}\n\n"); + + fprintf(fp, "\"Overrides\"\n{\n"); + if (m_pCmdGrpOverrides != NULL) + { + sm_trie_bad_iterator(m_pCmdGrpOverrides, + name_buffer, + sizeof(name_buffer), + iterator_glob_grp_override, + fp); + } + if (m_pCmdOverrides != NULL) + { + sm_trie_bad_iterator(m_pCmdOverrides, + name_buffer, + sizeof(name_buffer), + iterator_glob_basic_override, + fp); + } + fprintf(fp, "}\n"); +} + +AdminGroup *AdminCache::GetGroup(GroupId gid) +{ + AdminGroup *pGroup; + + pGroup = (AdminGroup *)m_pMemory->GetAddress(gid); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return NULL; + } + + return pGroup; +} + +AdminUser *AdminCache::GetUser(AdminId aid) +{ + AdminUser *pAdmin; + + pAdmin = (AdminUser *)m_pMemory->GetAddress(aid); + if (!pAdmin || pAdmin->magic != USR_MAGIC_SET) + { + return NULL; + } + + return pAdmin; +} + +const char *AdminCache::GetMethodName(unsigned int index) +{ + List::iterator iter; + for (iter=m_AuthMethods.begin(); + iter!=m_AuthMethods.end(); + iter++) + { + if (index-- == 0) + { + return (*iter).name.c_str(); + } + } + + return NULL; +} + +const char *AdminCache::GetString(int idx) +{ + return m_pStrings->GetString(idx); +} + +size_t AdminCache::FillFlagString(FlagBits bits, char *buffer, size_t maxlen) +{ + size_t pos; + unsigned int i, num_flags; + AdminFlag flags[AdminFlags_TOTAL]; + + num_flags = FlagBitsToArray(bits, flags, AdminFlags_TOTAL); + + pos = 0; + for (i = 0; pos < maxlen && i < num_flags; i++) + { + if (FindFlagChar(flags[i], &buffer[pos])) + { + pos++; + } + } + buffer[pos] = '\0'; + + return pos; +} + +CON_COMMAND(sm_dump_admcache, "Dumps the admin cache for debugging") +{ + FILE *fp; + char buffer[PLATFORM_MAX_PATH]; + + g_SourceMod.BuildPath(Path_SM, buffer, sizeof(buffer), "data/admin_cache_dump.txt"); + + if ((fp = fopen(buffer, "wt")) == NULL) + { + g_RootMenu.ConsolePrint("Could not open file for writing: %s", buffer); + return; + } + + g_Admins.DumpCache(fp); + + g_RootMenu.ConsolePrint("Admin cache dumped to: %s", buffer); + + fclose(fp); +} diff --git a/core/AdminCache.h b/core/AdminCache.h new file mode 100644 index 00000000..c7d21da1 --- /dev/null +++ b/core/AdminCache.h @@ -0,0 +1,208 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_ADMINCACHE_H_ +#define _INCLUDE_SOURCEMOD_ADMINCACHE_H_ + +#include "sm_memtable.h" +#include +#include +#include +#include +#include "sm_globals.h" +#include + +using namespace SourceHook; + +#define GRP_MAGIC_SET 0xDEADFADE +#define GRP_MAGIC_UNSET 0xFACEFACE +#define USR_MAGIC_SET 0xDEADFACE +#define USR_MAGIC_UNSET 0xFADEDEAD + +struct AdminGroup +{ + uint32_t magic; /* Magic flag, for memory validation (ugh) */ + unsigned int immunity_level; /* Immunity level */ + /* Immune from target table (-1 = nonexistent) + * [0] = number of entries + * [1...N] = immune targets + */ + int immune_table; + Trie *pCmdTable; /* Command override table (can be NULL) */ + Trie *pCmdGrpTable; /* Command group override table (can be NULL) */ + int next_grp; /* Next group in the chain */ + int prev_grp; /* Previous group in the chain */ + int nameidx; /* Name */ + FlagBits addflags; /* Additive flags */ +}; + +struct AuthMethod +{ + String name; + Trie *table; +}; + +struct UserAuth +{ + unsigned int index; /* Index into auth table */ + int identidx; /* Index into the string table */ +}; + +struct AdminUser +{ + uint32_t magic; /* Magic flag, for memory validation */ + FlagBits flags; /* Flags */ + FlagBits eflags; /* Effective flags */ + int nameidx; /* Name index */ + int password; /* Password index */ + unsigned int grp_count; /* Number of groups */ + unsigned int grp_size; /* Size of groups table */ + int grp_table; /* Group table itself */ + int next_user; /* Next user in the list */ + int prev_user; /* Previous user in the list */ + UserAuth auth; /* Auth method for this user */ + unsigned int immunity_level; /* Immunity level */ + unsigned int serialchange; /* Serial # for changes */ +}; + +class AdminCache : + public IAdminSystem, + public SMGlobalClass +{ +public: + AdminCache(); + ~AdminCache(); +public: //SMGlobalClass + void OnSourceModStartup(bool late); + void OnSourceModAllInitialized(); + void OnSourceModLevelChange(const char *mapName); + void OnSourceModShutdown(); + void OnSourceModPluginsLoaded(); +public: //IAdminSystem + /** Command cache stuff */ + void AddCommandOverride(const char *cmd, OverrideType type, FlagBits flags); + bool GetCommandOverride(const char *cmd, OverrideType type, FlagBits *flags); + void UnsetCommandOverride(const char *cmd, OverrideType type); + /** Group cache stuff */ + GroupId AddGroup(const char *group_name); + GroupId FindGroupByName(const char *group_name); + void SetGroupAddFlag(GroupId id, AdminFlag flag, bool enabled); + bool GetGroupAddFlag(GroupId id, AdminFlag flag); + FlagBits GetGroupAddFlags(GroupId id); + void SetGroupGenericImmunity(GroupId id, ImmunityType type, bool enabled); + bool GetGroupGenericImmunity(GroupId id, ImmunityType type); + void InvalidateGroup(GroupId id); + void AddGroupImmunity(GroupId id, GroupId other_id); + unsigned int GetGroupImmunityCount(GroupId id); + GroupId GetGroupImmunity(GroupId id, unsigned int number); + void AddGroupCommandOverride(GroupId id, const char *name, OverrideType type, OverrideRule rule); + bool GetGroupCommandOverride(GroupId id, const char *name, OverrideType type, OverrideRule *pRule); + void DumpAdminCache(AdminCachePart part, bool rebuild); + void AddAdminListener(IAdminListener *pListener); + void RemoveAdminListener(IAdminListener *pListener); + /** User stuff */ + void RegisterAuthIdentType(const char *name); + AdminId CreateAdmin(const char *name); + const char *GetAdminName(AdminId id); + bool BindAdminIdentity(AdminId id, const char *auth, const char *ident); + virtual void SetAdminFlag(AdminId id, AdminFlag flag, bool enabled); + bool GetAdminFlag(AdminId id, AdminFlag flag, AccessMode mode); + FlagBits GetAdminFlags(AdminId id, AccessMode mode); + bool AdminInheritGroup(AdminId id, GroupId gid); + unsigned int GetAdminGroupCount(AdminId id); + GroupId GetAdminGroup(AdminId id, unsigned int index, const char **name); + void SetAdminPassword(AdminId id, const char *password); + const char *GetAdminPassword(AdminId id); + AdminId FindAdminByIdentity(const char *auth, const char *identity); + bool InvalidateAdmin(AdminId id); + unsigned int FlagBitsToBitArray(FlagBits bits, bool array[], unsigned int maxSize); + FlagBits FlagBitArrayToBits(const bool array[], unsigned int maxSize); + FlagBits FlagArrayToBits(const AdminFlag array[], unsigned int numFlags); + unsigned int FlagBitsToArray(FlagBits bits, AdminFlag array[], unsigned int maxSize); + bool CheckAdminFlags(AdminId id, FlagBits bits); + bool CanAdminTarget(AdminId id, AdminId target); + void SetAdminFlags(AdminId id, AccessMode mode, FlagBits bits); + bool FindFlag(const char *str, AdminFlag *pFlag); + bool FindFlag(char c, AdminFlag *pAdmFlag); + FlagBits ReadFlagString(const char *flags, const char **end); + size_t FillFlagString(FlagBits bits, char *buffer, size_t maxlen); + unsigned int GetAdminSerialChange(AdminId id); + bool CanAdminUseCommand(int client, const char *cmd); + const char *GetGroupName(GroupId gid); + unsigned int SetGroupImmunityLevel(GroupId gid, unsigned int level); + unsigned int GetGroupImmunityLevel(GroupId gid); + unsigned int SetAdminImmunityLevel(AdminId id, unsigned int level); + unsigned int GetAdminImmunityLevel(AdminId id); + bool CheckAccess(int client, + const char *cmd, + FlagBits flags, + bool override_only); + bool FindFlagChar(AdminFlag flag, char *c); +public: + bool IsValidAdmin(AdminId id); + void DumpCache(FILE *fp); + AdminGroup *GetGroup(GroupId gid); + AdminUser *GetUser(AdminId id); + const char *GetString(int idx); +private: + void _UnsetCommandOverride(const char *cmd); + void _UnsetCommandGroupOverride(const char *group); + void InvalidateGroupCache(); + void InvalidateAdminCache(bool unlink_admins); + void DumpCommandOverrideCache(OverrideType type); + Trie *GetMethodByIndex(unsigned int index); + bool GetMethodIndex(const char *name, unsigned int *_index); + const char *GetMethodName(unsigned int index); + void NameFlag(const char *str, AdminFlag flag); +public: + BaseStringTable *m_pStrings; + BaseMemTable *m_pMemory; + Trie *m_pCmdOverrides; + Trie *m_pCmdGrpOverrides; + int m_FirstGroup; + int m_LastGroup; + int m_FreeGroupList; + Trie *m_pGroups; + List m_hooks; + List m_AuthMethods; + Trie *m_pAuthTables; + IForward *m_pCacheFwd; + int m_FirstUser; + int m_LastUser; + int m_FreeUserList; + bool m_InvalidatingAdmins; + bool m_destroying; + Trie *m_pLevelNames; +}; + +extern AdminCache g_Admins; + +#endif //_INCLUDE_SOURCEMOD_ADMINCACHE_H_ diff --git a/core/CDataPack.cpp b/core/CDataPack.cpp new file mode 100644 index 00000000..99d15694 --- /dev/null +++ b/core/CDataPack.cpp @@ -0,0 +1,259 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include +#include +#include "CDataPack.h" + +#define DATAPACK_INITIAL_SIZE 512 + +CDataPack::CDataPack() +{ + m_pBase = (char *)malloc(DATAPACK_INITIAL_SIZE); + m_capacity = DATAPACK_INITIAL_SIZE; + Initialize(); +} + +CDataPack::~CDataPack() +{ + free(m_pBase); +} + +void CDataPack::Initialize() +{ + m_curptr = m_pBase; + m_size = 0; +} + +void CDataPack::CheckSize(size_t typesize) +{ + if (m_curptr - m_pBase + typesize <= m_capacity) + { + return; + } + + size_t pos = m_curptr - m_pBase; + do + { + m_capacity *= 2; + m_pBase = (char *)realloc(m_pBase, m_capacity); + m_curptr = m_pBase + pos; + } while (m_curptr - m_pBase + typesize > m_capacity); +} + +void CDataPack::ResetSize() +{ + m_size = 0; +} + +size_t CDataPack::CreateMemory(size_t size, void **addr) +{ + CheckSize(sizeof(size_t) + size); + size_t pos = m_curptr - m_pBase; + + *(size_t *)m_curptr = size; + m_curptr += sizeof(size_t); + + if (addr) + { + *addr = m_curptr; + } + + m_curptr += size; + m_size += sizeof(size_t) + size; + + return pos; +} + +void CDataPack::PackCell(cell_t cell) +{ + CheckSize(sizeof(size_t) + sizeof(cell_t)); + + *(size_t *)m_curptr = sizeof(cell_t); + m_curptr += sizeof(size_t); + + *(cell_t *)m_curptr = cell; + m_curptr += sizeof(cell_t); + + m_size += sizeof(size_t) + sizeof(cell_t); +} + +void CDataPack::PackFloat(float val) +{ + CheckSize(sizeof(size_t) + sizeof(float)); + + *(size_t *)m_curptr = sizeof(float); + m_curptr += sizeof(size_t); + + *(float *)m_curptr = val; + m_curptr += sizeof(float); + + m_size += sizeof(size_t) + sizeof(float); +} + +void CDataPack::PackString(const char *string) +{ + size_t len = strlen(string); + size_t maxsize = sizeof(size_t) + len + 1; + CheckSize(maxsize); + + // Pack the string length first for buffer overrun checking. + *(size_t *)m_curptr = len; + m_curptr += sizeof(size_t); + + // Now pack the string. + memcpy(m_curptr, string, len); + m_curptr[len] = '\0'; + m_curptr += len + 1; + + m_size += maxsize; +} + +void CDataPack::Reset() const +{ + m_curptr = m_pBase; +} + +size_t CDataPack::GetPosition() const +{ + return static_cast(m_curptr - m_pBase); +} + +bool CDataPack::SetPosition(size_t pos) const +{ + if (pos > m_size-1) + { + return false; + } + m_curptr = m_pBase + pos; + + return true; +} + +cell_t CDataPack::ReadCell() const +{ + if (!IsReadable(sizeof(size_t) + sizeof(cell_t))) + { + return 0; + } + if (*reinterpret_cast(m_curptr) != sizeof(cell_t)) + { + return 0; + } + + m_curptr += sizeof(size_t); + + cell_t val = *reinterpret_cast(m_curptr); + m_curptr += sizeof(cell_t); + return val; +} + +float CDataPack::ReadFloat() const +{ + if (!IsReadable(sizeof(size_t) + sizeof(float))) + { + return 0; + } + if (*reinterpret_cast(m_curptr) != sizeof(float)) + { + return 0; + } + + m_curptr += sizeof(size_t); + + float val = *reinterpret_cast(m_curptr); + m_curptr += sizeof(float); + return val; +} + +bool CDataPack::IsReadable(size_t bytes) const +{ + return (bytes + (m_curptr - m_pBase) > m_size) ? false : true; +} + +const char *CDataPack::ReadString(size_t *len) const +{ + if (!IsReadable(sizeof(size_t))) + { + return NULL; + } + + size_t real_len = *(size_t *)m_curptr; + + m_curptr += sizeof(size_t); + char *str = (char *)m_curptr; + + if ((strlen(str) != real_len) || !(IsReadable(real_len+1))) + { + return NULL; + } + + if (len) + { + *len = real_len; + } + + m_curptr += real_len + 1; + + return str; +} + +void *CDataPack::GetMemory() const +{ + return m_curptr; +} + +void *CDataPack::ReadMemory(size_t *size) const +{ + if (!IsReadable(sizeof(size_t))) + { + return NULL; + } + + size_t bytecount = *(size_t *)m_curptr; + m_curptr += sizeof(size_t); + + if (!IsReadable(bytecount)) + { + return NULL; + } + + void *ptr = m_curptr; + + if (size) + { + *size = bytecount; + } + + m_curptr += bytecount; + + return ptr; +} diff --git a/core/CDataPack.h b/core/CDataPack.h new file mode 100644 index 00000000..83a53539 --- /dev/null +++ b/core/CDataPack.h @@ -0,0 +1,71 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_CDATAPACK_H_ +#define _INCLUDE_SOURCEMOD_CDATAPACK_H_ + +#include + +using namespace SourceMod; + +class CDataPack : public IDataPack +{ +public: + CDataPack(); + ~CDataPack(); +public: //IDataReader + void Reset() const; + size_t GetPosition() const; + bool SetPosition(size_t pos) const; + cell_t ReadCell() const; + float ReadFloat() const; + bool IsReadable(size_t bytes) const; + const char *ReadString(size_t *len) const; + void *GetMemory() const; + void *ReadMemory(size_t *size) const; +public: //IDataPack + void ResetSize(); + void PackCell(cell_t cell); + void PackFloat(float val); + void PackString(const char *string); + size_t CreateMemory(size_t size, void **addr); +public: + void Initialize(); +private: + void CheckSize(size_t sizetype); +private: + char *m_pBase; + mutable char *m_curptr; + size_t m_capacity; + size_t m_size; +}; + +#endif //_INCLUDE_SOURCEMOD_CDATAPACK_H_ diff --git a/core/CellArray.h b/core/CellArray.h new file mode 100644 index 00000000..c5ec78b0 --- /dev/null +++ b/core/CellArray.h @@ -0,0 +1,206 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include +#include + +extern HandleType_t htCellArray; + +class CellArray +{ +public: + CellArray(size_t blocksize) : m_Data(NULL), m_BlockSize(blocksize), m_AllocSize(0), m_Size(0) + { + } + + ~CellArray() + { + free(m_Data); + } + + size_t size() const + { + return m_Size; + } + + cell_t *push() + { + if (!GrowIfNeeded(1)) + { + return NULL; + } + cell_t *arr = &m_Data[m_Size * m_BlockSize]; + m_Size++; + return arr; + } + + cell_t *at(size_t b) const + { + return &m_Data[b * m_BlockSize]; + } + + size_t blocksize() const + { + return m_BlockSize; + } + + void clear() + { + m_Size = 0; + } + + bool swap(size_t item1, size_t item2) + { + /* Make sure there is extra space available */ + if (!GrowIfNeeded(1)) + { + return false; + } + + cell_t *pri = at(item1); + cell_t *alt = at(item2); + + /* Get our temporary array 1 after the limit */ + cell_t *temp = &m_Data[m_Size * m_BlockSize]; + + memcpy(temp, pri, sizeof(cell_t) * m_BlockSize); + memcpy(pri, alt, sizeof(cell_t) * m_BlockSize); + memcpy(alt, temp, sizeof(cell_t) * m_BlockSize); + + return true; + } + + void remove(size_t index) + { + /* If we're at the end, take the easy way out */ + if (index == m_Size - 1) + { + m_Size--; + return; + } + + /* Otherwise, it's time to move stuff! */ + size_t remaining_indexes = (m_Size - 1) - index; + cell_t *src = at(index + 1); + cell_t *dest = at(index); + memmove(dest, src, sizeof(cell_t) * m_BlockSize * remaining_indexes); + + m_Size--; + } + + cell_t *insert_at(size_t index) + { + /* Make sure it'll fit */ + if (!GrowIfNeeded(1)) + { + return NULL; + } + + /* move everything up */ + cell_t *src = at(index); + cell_t *dst = at(index + 1); + memmove(dst, src, sizeof(cell_t) * m_BlockSize * (m_Size-index)); + + m_Size++; + + return src; + } + + bool resize(size_t count) + { + if (count <= m_Size) + { + m_Size = count; + return true; + } + + if (!GrowIfNeeded(count - m_Size)) + { + return false; + } + + m_Size = count; + return true; + } + + CellArray *clone() + { + CellArray *array = new CellArray(m_BlockSize); + array->m_AllocSize = m_AllocSize; + array->m_Size = m_Size; + array->m_Data = (cell_t *)malloc(sizeof(cell_t) * m_BlockSize * m_AllocSize); + memcpy(array->m_Data, m_Data, sizeof(cell_t) * m_BlockSize * m_Size); + return array; + } + + cell_t *base() + { + return m_Data; + } + + size_t mem_usage() + { + return m_AllocSize * m_BlockSize * sizeof(cell_t); + } + +private: + bool GrowIfNeeded(size_t count) + { + /* Shortcut out if we can store this */ + if (m_Size + count <= m_AllocSize) + { + return true; + } + /* Set a base allocation size of 8 items */ + if (!m_AllocSize) + { + m_AllocSize = 8; + } + /* If it's not enough, keep doubling */ + while (m_Size + count > m_AllocSize) + { + m_AllocSize *= 2; + } + /* finally, allocate the new block */ + if (m_Data) + { + m_Data = (cell_t *)realloc(m_Data, sizeof(cell_t) * m_BlockSize * m_AllocSize); + } else { + m_Data = (cell_t *)malloc(sizeof(cell_t) * m_BlockSize * m_AllocSize); + } + return (m_Data != NULL); + } +private: + cell_t *m_Data; + size_t m_BlockSize; + size_t m_AllocSize; + size_t m_Size; +}; diff --git a/core/CellRecipientFilter.h b/core/CellRecipientFilter.h new file mode 100644 index 00000000..e7dcddee --- /dev/null +++ b/core/CellRecipientFilter.h @@ -0,0 +1,107 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_CELLRECIPIENTFILTER_H_ +#define _INCLUDE_SOURCEMOD_CELLRECIPIENTFILTER_H_ + +#include +#include + +class CellRecipientFilter : public IRecipientFilter +{ +public: + CellRecipientFilter() : m_IsReliable(false), m_IsInitMessage(false), m_Size(0) {} + ~CellRecipientFilter() {} +public: //IRecipientFilter + bool IsReliable() const; + bool IsInitMessage() const; + int GetRecipientCount() const; + int GetRecipientIndex(int slot) const; +public: + void Initialize(const cell_t *ptr, size_t count); + void SetToReliable(bool isreliable); + void SetToInit(bool isinitmsg); + void Reset(); +private: + cell_t m_Players[255]; + bool m_IsReliable; + bool m_IsInitMessage; + size_t m_Size; +}; + +inline void CellRecipientFilter::Reset() +{ + m_IsReliable = false; + m_IsInitMessage = false; + m_Size = 0; +} + +inline bool CellRecipientFilter::IsReliable() const +{ + return m_IsReliable; +} + +inline bool CellRecipientFilter::IsInitMessage() const +{ + return m_IsInitMessage; +} + +inline int CellRecipientFilter::GetRecipientCount() const +{ + return m_Size; +} + +inline int CellRecipientFilter::GetRecipientIndex(int slot) const +{ + if ((slot < 0) || (slot >= GetRecipientCount())) + { + return -1; + } + return static_cast(m_Players[slot]); +} + +inline void CellRecipientFilter::SetToInit(bool isinitmsg) +{ + m_IsInitMessage = isinitmsg; +} + +inline void CellRecipientFilter::SetToReliable(bool isreliable) +{ + m_IsReliable = isreliable; +} + +inline void CellRecipientFilter::Initialize(const cell_t *ptr, size_t count) +{ + memcpy(m_Players, ptr, count * sizeof(cell_t)); + m_Size = count; +} + +#endif //_INCLUDE_SOURCEMOD_CELLRECIPIENTFILTER_H_ diff --git a/core/ChatTriggers.cpp b/core/ChatTriggers.cpp new file mode 100644 index 00000000..d2491cc2 --- /dev/null +++ b/core/ChatTriggers.cpp @@ -0,0 +1,450 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include +#include "ChatTriggers.h" +#include "sm_stringutil.h" +#include "ConCmdManager.h" +#include "PlayerManager.h" +#include "Translator.h" +#include "HalfLife2.h" + +/* :HACKHACK: We can't SH_DECL here because ConCmdManager.cpp does. + * While the OB build only runs on MM:S 1.6.0+ (SH 5+), the older one + * can technically be compiled against any MM:S version after 1.4.2. + */ +#if defined ORANGEBOX_BUILD +extern bool __SourceHook_FHRemoveConCommandDispatch(void *, bool, class fastdelegate::FastDelegate1); +extern int __SourceHook_FHAddConCommandDispatch(void *, ISourceHook::AddHookMode, bool, class fastdelegate::FastDelegate1); +#else +extern bool __SourceHook_FHRemoveConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0); +#if SH_IMPL_VERSION >= 5 +extern int __SourceHook_FHAddConCommandDispatch(void *, ISourceHook::AddHookMode, bool, class fastdelegate::FastDelegate0); +#elif SH_IMPL_VERSION == 4 +extern int __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0); +#elif SH_IMPL_VERSION == 3 +extern bool __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0); +#endif //SH_IMPL_VERSION +#endif //ORANGEBOX_BUILD + +ChatTriggers g_ChatTriggers; +bool g_bSupressSilentFails = false; +CPhraseFile *g_pFloodPhrases = NULL; + +ChatTriggers::ChatTriggers() : m_pSayCmd(NULL), m_bWillProcessInPost(false), + m_bTriggerWasSilent(false), m_ReplyTo(SM_REPLY_CONSOLE) +{ + m_PubTrigger = sm_strdup("!"); + m_PrivTrigger = sm_strdup("/"); + m_PubTriggerSize = 1; + m_PrivTriggerSize = 1; + m_bIsChatTrigger = false; +} + +ChatTriggers::~ChatTriggers() +{ + delete [] m_PubTrigger; + m_PubTrigger = NULL; + delete [] m_PrivTrigger; + m_PrivTrigger = NULL; +} + +ConfigResult ChatTriggers::OnSourceModConfigChanged(const char *key, + const char *value, + ConfigSource source, + char *error, + size_t maxlength) +{ + if (strcmp(key, "PublicChatTrigger") == 0) + { + delete [] m_PubTrigger; + m_PubTrigger = sm_strdup(value); + m_PubTriggerSize = strlen(m_PubTrigger); + return ConfigResult_Accept; + } + else if (strcmp(key, "SilentChatTrigger") == 0) + { + delete [] m_PrivTrigger; + m_PrivTrigger = sm_strdup(value); + m_PrivTriggerSize = strlen(m_PrivTrigger); + return ConfigResult_Accept; + } + else if (strcmp(key, "SilentFailSuppress") == 0) + { + g_bSupressSilentFails = strcmp(value, "yes") == 0; + return ConfigResult_Accept; + } + + return ConfigResult_Ignore; +} + +void ChatTriggers::OnSourceModAllInitialized() +{ + m_pShouldFloodBlock = g_Forwards.CreateForward("OnClientFloodCheck", ET_Event, 1, NULL, Param_Cell); + m_pDidFloodBlock = g_Forwards.CreateForward("OnClientFloodResult", ET_Event, 2, NULL, Param_Cell, Param_Cell); +} + +void ChatTriggers::OnSourceModAllInitialized_Post() +{ + unsigned int file_id; + + file_id = g_Translator.FindOrAddPhraseFile("antiflood.phrases.txt"); + g_pFloodPhrases = g_Translator.GetFileByIndex(file_id); +} + +void ChatTriggers::OnSourceModGameInitialized() +{ + unsigned int total = 2; + ConCommandBase *pCmd = icvar->GetCommands(); + const char *name; + while (pCmd) + { + if (pCmd->IsCommand()) + { + name = pCmd->GetName(); + if (!m_pSayCmd && strcmp(name, "say") == 0) + { + m_pSayCmd = (ConCommand *)pCmd; + if (--total == 0) + { + break; + } + } else if (!m_pSayTeamCmd && strcmp(name, "say_team") == 0) { + m_pSayTeamCmd = (ConCommand *)pCmd; + if (--total == 0) + { + break; + } + } + } + pCmd = const_cast(pCmd->GetNext()); + } + + if (m_pSayCmd) + { + SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false); + SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true); + } + if (m_pSayTeamCmd) + { + SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Pre, false); + SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Post, true); + } +} + +void ChatTriggers::OnSourceModShutdown() +{ + if (m_pSayTeamCmd) + { + SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Post, true); + SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Pre, false); + } + if (m_pSayCmd) + { + SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true); + SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false); + } + + g_Forwards.ReleaseForward(m_pShouldFloodBlock); + g_Forwards.ReleaseForward(m_pDidFloodBlock); +} + +#if defined ORANGEBOX_BUILD +void ChatTriggers::OnSayCommand_Pre(const CCommand &command) +{ +#else +void ChatTriggers::OnSayCommand_Pre() +{ + CCommand command; +#endif + int client; + CPlayer *pPlayer; + + client = g_ConCmds.GetCommandClient(); + m_bIsChatTrigger = false; + m_bWasFloodedMessage = false; + + /* The server console cannot do this */ + if (client == 0 || (pPlayer = g_Players.GetPlayerByIndex(client)) == NULL) + { + RETURN_META(MRES_IGNORED); + } + + /* We guarantee the client is connected */ + if (!pPlayer->IsConnected()) + { + RETURN_META(MRES_IGNORED); + } + + const char *args = command.ArgS(); + + if (!args) + { + RETURN_META(MRES_IGNORED); + } + + /* Check if we need to block this message from being sent */ + if (ClientIsFlooding(client)) + { + char buffer[128]; + + if (!CoreTranslate( + buffer, + sizeof(buffer), + "%T", + 2, + NULL, + "Flooding the server", + &client)) + { + UTIL_Format(buffer, sizeof(buffer), "You are flooding the server!"); + } + + /* :TODO: we should probably kick people who spam too much. */ + + char fullbuffer[192]; + UTIL_Format(fullbuffer, sizeof(fullbuffer), "[SM] %s", buffer); + g_HL2.TextMsg(client, HUD_PRINTTALK, fullbuffer); + + m_bWasFloodedMessage = true; + + RETURN_META(MRES_SUPERCEDE); + } + + /* Handle quoted string sets */ + bool is_quoted = false; + if (args[0] == '"') + { + args++; + is_quoted = true; + } + + bool is_trigger = false; + bool is_silent = false; + + /* Check for either trigger */ + if (m_PubTriggerSize && strncmp(args, m_PubTrigger, m_PubTriggerSize) == 0) + { + is_trigger = true; + args = &args[m_PubTriggerSize]; + } + else if (m_PrivTriggerSize && strncmp(args, m_PrivTrigger, m_PrivTriggerSize) == 0) + { + is_trigger = true; + is_silent = true; + args = &args[m_PrivTriggerSize]; + } + + if (!is_trigger) + { + RETURN_META(MRES_IGNORED); + } + + /** + * Test if this is actually a command! + */ + if (!PreProcessTrigger(engine->PEntityOfEntIndex(client), args, is_quoted)) + { + CPlayer *pPlayer; + if (is_silent + && g_bSupressSilentFails + && client != 0 + && (pPlayer = g_Players.GetPlayerByIndex(client)) != NULL + && pPlayer->GetAdminId() != INVALID_ADMIN_ID) + { + RETURN_META(MRES_SUPERCEDE); + } + RETURN_META(MRES_IGNORED); + } + + m_bIsChatTrigger = true; + + /** + * We'll execute it in post. + */ + m_bWillProcessInPost = true; + m_bTriggerWasSilent = is_silent; + + /* If we're silent, block */ + if (is_silent) + { + RETURN_META(MRES_SUPERCEDE); + } + + /* Otherwise, let the command continue */ + RETURN_META(MRES_IGNORED); +} + +#if defined ORANGEBOX_BUILD +void ChatTriggers::OnSayCommand_Post(const CCommand &command) +#else +void ChatTriggers::OnSayCommand_Post() +#endif +{ + m_bIsChatTrigger = false; + m_bWasFloodedMessage = false; + if (m_bWillProcessInPost) + { + /* Reset this for re-entrancy */ + m_bWillProcessInPost = false; + + /* Execute the cached command */ + int client = g_ConCmds.GetCommandClient(); + unsigned int old = SetReplyTo(SM_REPLY_CHAT); + serverpluginhelpers->ClientCommand(engine->PEntityOfEntIndex(client), m_ToExecute); + SetReplyTo(old); + } +} + +bool ChatTriggers::PreProcessTrigger(edict_t *pEdict, const char *args, bool is_quoted) +{ + /* Extract a command. This is kind of sloppy. */ + char cmd_buf[64]; + size_t cmd_len = 0; + const char *inptr = args; + while (*inptr != '\0' + && !textparsers->IsWhitespace(inptr) + && *inptr != '"' + && cmd_len < sizeof(cmd_buf) - 1) + { + cmd_buf[cmd_len++] = *inptr++; + } + cmd_buf[cmd_len] = '\0'; + + if (cmd_len == 0) + { + return false; + } + + /* See if we have this registered */ + bool prepended = false; + if (!g_ConCmds.LookForSourceModCommand(cmd_buf)) + { + /* Check if we had an "sm_" prefix */ + if (strncmp(cmd_buf, "sm_", 3) == 0) + { + return false; + } + + /* Now, prepend. Don't worry about the buffers. This will + * work because the sizes are limited from earlier. + */ + char new_buf[80]; + strcpy(new_buf, "sm_"); + strncopy(&new_buf[3], cmd_buf, sizeof(new_buf)-3); + + /* Recheck */ + if (!g_ConCmds.LookForSourceModCommand(new_buf)) + { + return false; + } + + prepended = true; + } + + /* See if we need to do extra string manipulation */ + if (is_quoted || prepended) + { + size_t len; + + /* Check if we need to prepend sm_ */ + if (prepended) + { + len = UTIL_Format(m_ToExecute, sizeof(m_ToExecute), "sm_%s", args); + } else { + len = strncopy(m_ToExecute, args, sizeof(m_ToExecute)); + } + + /* Check if we need to strip a quote */ + if (is_quoted) + { + if (m_ToExecute[len-1] == '"') + { + m_ToExecute[--len] = '\0'; + } + } + } else { + strncopy(m_ToExecute, args, sizeof(m_ToExecute)); + } + + return true; +} + +unsigned int ChatTriggers::SetReplyTo(unsigned int reply) +{ + unsigned int old = m_ReplyTo; + + m_ReplyTo = reply; + + return old; +} + +unsigned int ChatTriggers::GetReplyTo() +{ + return m_ReplyTo; +} + +bool ChatTriggers::IsChatTrigger() +{ + return m_bIsChatTrigger; +} + +bool ChatTriggers::ClientIsFlooding(int client) +{ + bool is_flooding = false; + + if (m_pShouldFloodBlock->GetFunctionCount() != 0) + { + cell_t res = 0; + + m_pShouldFloodBlock->PushCell(client); + m_pShouldFloodBlock->Execute(&res); + + if (res != 0) + { + is_flooding = true; + } + } + + if (m_pDidFloodBlock->GetFunctionCount() != 0) + { + m_pDidFloodBlock->PushCell(client); + m_pDidFloodBlock->PushCell(is_flooding ? 1 : 0); + m_pDidFloodBlock->Execute(NULL); + } + + return is_flooding; +} + +bool ChatTriggers::WasFloodedMessage() +{ + return m_bWasFloodedMessage; +} diff --git a/core/ChatTriggers.h b/core/ChatTriggers.h new file mode 100644 index 00000000..c003f997 --- /dev/null +++ b/core/ChatTriggers.h @@ -0,0 +1,91 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_CHAT_TRIGGERS_H_ +#define _INCLUDE_SOURCEMOD_CHAT_TRIGGERS_H_ + +#include "sm_globals.h" +#include "sourcemm_api.h" +#include +#include +#include + +class ChatTriggers : public SMGlobalClass +{ +public: + ChatTriggers(); + ~ChatTriggers(); +public: //SMGlobalClass + void OnSourceModAllInitialized(); + void OnSourceModAllInitialized_Post(); + void OnSourceModGameInitialized(); + void OnSourceModShutdown(); + ConfigResult OnSourceModConfigChanged(const char *key, + const char *value, + ConfigSource source, + char *error, + size_t maxlength); +private: //ConCommand +#if defined ORANGEBOX_BUILD + void OnSayCommand_Pre(const CCommand &command); + void OnSayCommand_Post(const CCommand &command); +#else + void OnSayCommand_Pre(); + void OnSayCommand_Post(); +#endif +public: + unsigned int GetReplyTo(); + unsigned int SetReplyTo(unsigned int reply); + bool IsChatTrigger(); + bool WasFloodedMessage(); +private: + bool PreProcessTrigger(edict_t *pEdict, const char *args, bool is_quoted); + bool ClientIsFlooding(int client); +private: + ConCommand *m_pSayCmd; + ConCommand *m_pSayTeamCmd; + char *m_PubTrigger; + size_t m_PubTriggerSize; + char *m_PrivTrigger; + size_t m_PrivTriggerSize; + bool m_bWillProcessInPost; + bool m_bTriggerWasSilent; + bool m_bIsChatTrigger; + bool m_bWasFloodedMessage; + unsigned int m_ReplyTo; + char m_ToExecute[300]; + IForward *m_pShouldFloodBlock; + IForward *m_pDidFloodBlock; +}; + +extern ChatTriggers g_ChatTriggers; + +#endif //_INCLUDE_SOURCEMOD_CHAT_TRIGGERS_H_ diff --git a/core/ConCmdManager.cpp b/core/ConCmdManager.cpp new file mode 100644 index 00000000..562936a7 --- /dev/null +++ b/core/ConCmdManager.cpp @@ -0,0 +1,991 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include "ConCmdManager.h" +#include "sm_srvcmds.h" +#include "AdminCache.h" +#include "sm_stringutil.h" +#include "PlayerManager.h" +#include "Translator.h" +#include "HalfLife2.h" +#include "ChatTriggers.h" + +ConCmdManager g_ConCmds; + +#if defined ORANGEBOX_BUILD + SH_DECL_HOOK1_void(ConCommand, Dispatch, SH_NOATTRIB, false, const CCommand &); +#else + SH_DECL_HOOK0_void(ConCommand, Dispatch, SH_NOATTRIB, false); +#endif + +SH_DECL_HOOK1_void(IServerGameClients, SetCommandClient, SH_NOATTRIB, false, int); + +struct PlCmdInfo +{ + ConCmdInfo *pInfo; + CmdHook *pHook; + CmdType type; +}; +typedef List CmdList; + +void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info); + +ConCmdManager::ConCmdManager() : m_Strings(1024) +{ + m_pCmds = sm_trie_create(); + m_pCmdGrps = sm_trie_create(); + m_CmdClient = 0; +} + +ConCmdManager::~ConCmdManager() +{ + sm_trie_destroy(m_pCmds); + sm_trie_destroy(m_pCmdGrps); +} + +void ConCmdManager::OnSourceModAllInitialized() +{ + g_PluginSys.AddPluginsListener(this); + g_RootMenu.AddRootConsoleCommand("cmds", "List console commands", this); + SH_ADD_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &ConCmdManager::SetCommandClient, false); +} + +void ConCmdManager::OnSourceModShutdown() +{ + /* All commands should already be removed by the time we're done */ + SH_REMOVE_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &ConCmdManager::SetCommandClient, false); + g_RootMenu.RemoveRootConsoleCommand("cmds", this); +} + +void ConCmdManager::OnUnlinkConCommandBase(ConCommandBase *pBase, const char *name, bool is_read_safe) +{ + /* Whoa, first get its information struct */ + ConCmdInfo *pInfo; + + if (!sm_trie_retrieve(m_pCmds, name, (void **)&pInfo)) + { + return; + } + + RemoveConCmds(pInfo->srvhooks); + RemoveConCmds(pInfo->conhooks); + + RemoveConCmd(pInfo, name, is_read_safe, false); +} + +void ConCmdManager::RemoveConCmds(List &cmdlist) +{ + List::iterator iter = cmdlist.begin(); + + while (iter != cmdlist.end()) + { + CmdHook *pHook = (*iter); + IPluginContext *pContext = pHook->pf->GetParentContext(); + IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pContext->GetContext()); + CmdList *pList = NULL; + + //gaben + if (!pPlugin->GetProperty("CommandList", (void **)&pList, false) || !pList) + { + continue; + } + + CmdList::iterator p_iter = pList->begin(); + while (p_iter != pList->end()) + { + PlCmdInfo &cmd = (*p_iter); + if (cmd.pHook == pHook) + { + p_iter = pList->erase(p_iter); + } + else + { + p_iter++; + } + } + + delete pHook->pAdmin; + delete pHook; + + iter = cmdlist.erase(iter); + } +} + +void ConCmdManager::RemoveConCmds(List &cmdlist, IPluginContext *pContext) +{ + List::iterator iter = cmdlist.begin(); + CmdHook *pHook; + + while (iter != cmdlist.end()) + { + pHook = (*iter); + if (pHook->pf->GetParentContext() == pContext) + { + delete pHook->pAdmin; + delete pHook; + iter = cmdlist.erase(iter); + } + else + { + iter++; + } + } +} + +void ConCmdManager::OnPluginDestroyed(IPlugin *plugin) +{ + CmdList *pList; + List removed; + if (plugin->GetProperty("CommandList", (void **)&pList, true)) + { + IPluginContext *pContext = plugin->GetBaseContext(); + CmdList::iterator iter; + /* First pass! + * Only bother if there's an actual command list on this plugin... + */ + for (iter=pList->begin(); + iter!=pList->end(); + iter++) + { + PlCmdInfo &cmd = (*iter); + ConCmdInfo *pInfo = cmd.pInfo; + + /* Has this chain already been fully cleaned/removed? */ + if (removed.find(pInfo) != removed.end()) + { + continue; + } + + /* Remove any hooks from us on this command */ + RemoveConCmds(pInfo->conhooks, pContext); + RemoveConCmds(pInfo->srvhooks, pContext); + + /* See if there are still hooks */ + if (pInfo->srvhooks.size()) + { + continue; + } + if (pInfo->conhooks.size()) + { + continue; + } + + /* Remove the command, it should be safe now */ + RemoveConCmd(pInfo, pInfo->pCmd->GetName(), true, true); + removed.push_back(pInfo); + } + delete pList; + } +} +#if defined ORANGEBOX_BUILD +void CommandCallback(const CCommand &command) +{ +#else +void CommandCallback() +{ + CCommand command; +#endif + + g_HL2.PushCommandStack(&command); + + g_ConCmds.InternalDispatch(command); + + g_HL2.PopCommandStack(); +} + +void ConCmdManager::SetCommandClient(int client) +{ + m_CmdClient = client + 1; +} + +ResultType ConCmdManager::DispatchClientCommand(int client, const char *cmd, int args, ResultType type) +{ + ConCmdInfo *pInfo; + if (sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo)) + { + cell_t result = type; + cell_t tempres = result; + List::iterator iter; + CmdHook *pHook; + for (iter=pInfo->conhooks.begin(); + iter!=pInfo->conhooks.end(); + iter++) + { + pHook = (*iter); + if (!pHook->pf->IsRunnable()) + { + continue; + } + if (pHook->pAdmin && !CheckAccess(client, cmd, pHook->pAdmin)) + { + if (result < Pl_Handled) + { + result = Pl_Handled; + } + continue; + } + pHook->pf->PushCell(client); + pHook->pf->PushCell(args); + if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE) + { + if (tempres > result) + { + result = tempres; + } + if (result == Pl_Stop) + { + break; + } + } + } + type = (ResultType)result; + } + + return type; +} + +void ConCmdManager::InternalDispatch(const CCommand &command) +{ + int client = m_CmdClient; + + if (client) + { + CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); + if (!pPlayer || !pPlayer->IsConnected()) + { + return; + } + } + + /** + * Note: Console commands will EITHER go through IServerGameDLL::ClientCommand, + * OR this dispatch. They will NEVER go through both. + * -- + * Whether or not it goes through the callback is determined by FCVAR_GAMEDLL + */ + const char *cmd = g_HL2.CurrentCommandName(); + + ConCmdInfo *pInfo; + if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo)) + { + return; + } + + /* This is a hack to prevent say triggers from firing on messages that were + * blocked because of flooding. We won't remove this, but the hack will get + * "nicer" when we expose explicit say hooks. + */ + if (g_ChatTriggers.WasFloodedMessage()) + { + return; + } + + cell_t result = Pl_Continue; + int args = command.ArgC() - 1; + + List::iterator iter; + CmdHook *pHook; + + /* Execute server-only commands if viable */ + if (client == 0 && pInfo->srvhooks.size()) + { + cell_t tempres = result; + for (iter=pInfo->srvhooks.begin(); + iter!=pInfo->srvhooks.end(); + iter++) + { + pHook = (*iter); + if (!pHook->pf->IsRunnable()) + { + continue; + } + pHook->pf->PushCell(args); + if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE) + { + if (tempres > result) + { + result = tempres; + } + if (result == Pl_Stop) + { + break; + } + } + } + + /* Check if there's an early stop */ + if (result >= Pl_Stop) + { + if (!pInfo->sourceMod) + { + RETURN_META(MRES_SUPERCEDE); + } + return; + } + } + + /* Execute console commands */ + if (pInfo->conhooks.size()) + { + cell_t tempres = result; + for (iter=pInfo->conhooks.begin(); + iter!=pInfo->conhooks.end(); + iter++) + { + pHook = (*iter); + if (!pHook->pf->IsRunnable()) + { + continue; + } + if (client + && pHook->pAdmin + && !CheckAccess(client, cmd, pHook->pAdmin)) + { + if (result < Pl_Handled) + { + result = Pl_Handled; + } + continue; + } + + /* On a listen server, sometimes the server host's client index can be set as 0. + * So index 1 is passed to the command callback to correct this potential problem. + */ + if (!engine->IsDedicatedServer()) + { + client = g_Players.ListenClient(); + } + + pHook->pf->PushCell(client); + pHook->pf->PushCell(args); + + if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE) + { + if (tempres > result) + { + result = tempres; + } + if (result == Pl_Stop) + { + break; + } + } + } + } + + if (result >= Pl_Handled) + { + if (!pInfo->sourceMod) + { + RETURN_META(MRES_SUPERCEDE); + } + return; + } +} + +bool ConCmdManager::CheckCommandAccess(int client, const char *cmd, FlagBits cmdflags) +{ + if (cmdflags == 0 || client == 0) + { + return true; + } + + /* If running listen server, then client 1 is the server host and should have 'root' access */ + if (client == 1 && !engine->IsDedicatedServer()) + { + return true; + } + + CPlayer *player = g_Players.GetPlayerByIndex(client); + if (!player + || player->GetEdict() == NULL + || player->IsFakeClient()) + { + return false; + } + + AdminId adm = player->GetAdminId(); + if (adm != INVALID_ADMIN_ID) + { + FlagBits bits = g_Admins.GetAdminFlags(adm, Access_Effective); + + /* root knows all, WHOA */ + if ((bits & ADMFLAG_ROOT) == ADMFLAG_ROOT) + { + return true; + } + + /* Check for overrides + * :TODO: is it worth optimizing this? + */ + unsigned int groups = g_Admins.GetAdminGroupCount(adm); + GroupId gid; + OverrideRule rule; + bool override = false; + for (unsigned int i=0; ieflags)) + { + return true; + } + + edict_t *pEdict = engine->PEntityOfEntIndex(client); + + /* If we got here, the command failed... */ + char buffer[128]; + if (!CoreTranslate(buffer, sizeof(buffer), "%T", 2, NULL, "No Access", &client)) + { + UTIL_Format(buffer, sizeof(buffer), "You do not have access to this command"); + } + + unsigned int replyto = g_ChatTriggers.GetReplyTo(); + if (replyto == SM_REPLY_CONSOLE) + { + char fullbuffer[192]; + UTIL_Format(fullbuffer, sizeof(fullbuffer), "[SM] %s.\n", buffer); + engine->ClientPrintf(pEdict, fullbuffer); + } + else if (replyto == SM_REPLY_CHAT) + { + char fullbuffer[192]; + UTIL_Format(fullbuffer, sizeof(fullbuffer), "[SM] %s.", buffer); + g_HL2.TextMsg(client, HUD_PRINTTALK, fullbuffer); + } + + return false; +} + +bool ConCmdManager::AddConsoleCommand(IPluginFunction *pFunction, + const char *name, + const char *description, + int flags) +{ + ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags); + + if (!pInfo) + { + return false; + } + + CmdHook *pHook = new CmdHook(); + + pHook->pf = pFunction; + if (description && description[0]) + { + pHook->helptext.assign(description); + } + pInfo->conhooks.push_back(pHook); + + /* Add to the plugin */ + CmdList *pList; + IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext()); + if (!pPlugin->GetProperty("CommandList", (void **)&pList)) + { + pList = new CmdList(); + pPlugin->SetProperty("CommandList", pList); + } + PlCmdInfo info; + info.pInfo = pInfo; + info.type = Cmd_Console; + info.pHook = pHook; + AddToPlCmdList(pList, info); + + return true; +} + +bool ConCmdManager::AddAdminCommand(IPluginFunction *pFunction, + const char *name, + const char *group, + int adminflags, + const char *description, + int flags) +{ + ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags); + + if (!pInfo) + { + return false; + } + + CmdHook *pHook = new CmdHook(); + AdminCmdInfo *pAdmin = new AdminCmdInfo(); + + pHook->pf = pFunction; + if (description && description[0]) + { + pHook->helptext.assign(description); + } + pHook->pAdmin = pAdmin; + + void *object; + int grpid; + if (!sm_trie_retrieve(m_pCmdGrps, group, (void **)&object)) + { + grpid = m_Strings.AddString(group); + sm_trie_insert(m_pCmdGrps, group, (void *)grpid); + } + else + { + grpid = (int)object; + } + + pAdmin->cmdGrpId = grpid; + pAdmin->flags = adminflags; + + /* First get the command group override, if any */ + bool override = g_Admins.GetCommandOverride(group, + Override_CommandGroup, + &(pAdmin->eflags)); + + /* Next get the command override, if any */ + if (g_Admins.GetCommandOverride(name, + Override_Command, + &(pAdmin->eflags))) + { + override = true; + } + + /* Assign normal flags if there were no overrides */ + if (!override) + { + pAdmin->eflags = pAdmin->flags; + } + + /* Finally, add the hook */ + pInfo->conhooks.push_back(pHook); + pInfo->admin = *(pHook->pAdmin); + pInfo->is_admin_set = true; + + /* Now add to the plugin */ + CmdList *pList; + IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext()); + if (!pPlugin->GetProperty("CommandList", (void **)&pList)) + { + pList = new CmdList(); + pPlugin->SetProperty("CommandList", pList); + } + PlCmdInfo info; + info.pInfo = pInfo; + info.type = Cmd_Admin; + info.pHook = pHook; + AddToPlCmdList(pList, info); + + return true; +} + +bool ConCmdManager::AddServerCommand(IPluginFunction *pFunction, + const char *name, + const char *description, + int flags) + +{ + ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags); + + if (!pInfo) + { + return false; + } + + CmdHook *pHook = new CmdHook(); + + pHook->pf = pFunction; + if (description && description[0]) + { + pHook->helptext.assign(description); + } + + pInfo->srvhooks.push_back(pHook); + + /* Add to the plugin */ + CmdList *pList; + IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext()); + if (!pPlugin->GetProperty("CommandList", (void **)&pList)) + { + pList = new CmdList(); + pPlugin->SetProperty("CommandList", pList); + } + PlCmdInfo info; + info.pInfo = pInfo; + info.type = Cmd_Server; + info.pHook = pHook; + AddToPlCmdList(pList, info); + + return true; +} + +void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info) +{ + CmdList::iterator iter = pList->begin(); + bool inserted = false; + const char *orig = NULL; + + orig = info.pInfo->pCmd->GetName(); + + /* Insert this into the help list, SORTED alphabetically. */ + while (iter != pList->end()) + { + PlCmdInfo &obj = (*iter); + const char *cmd = obj.pInfo->pCmd->GetName(); + if (strcmp(orig, cmd) < 0) + { + pList->insert(iter, info); + inserted = true; + break; + } + iter++; + } + + if (!inserted) + { + pList->push_back(info); + } +} + +void ConCmdManager::AddToCmdList(ConCmdInfo *info) +{ + List::iterator iter = m_CmdList.begin(); + ConCmdInfo *pInfo; + bool inserted = false; + const char *orig = NULL; + + orig = info->pCmd->GetName(); + + /* Insert this into the help list, SORTED alphabetically. */ + while (iter != m_CmdList.end()) + { + const char *cmd = NULL; + pInfo = (*iter); + cmd = pInfo->pCmd->GetName(); + if (strcmp(orig, cmd) < 0) + { + m_CmdList.insert(iter, info); + inserted = true; + break; + } + iter++; + } + + if (!inserted) + { + m_CmdList.push_back(info); + } +} + +void ConCmdManager::UpdateAdminCmdFlags(const char *cmd, OverrideType type, FlagBits bits, bool remove) +{ + ConCmdInfo *pInfo; + + if (type == Override_Command) + { + if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo)) + { + return; + } + + List::iterator iter; + CmdHook *pHook; + + for (iter=pInfo->conhooks.begin(); iter!=pInfo->conhooks.end(); iter++) + { + pHook = (*iter); + if (pHook->pAdmin) + { + if (!remove) + { + pHook->pAdmin->eflags = bits; + } else { + pHook->pAdmin->eflags = pHook->pAdmin->flags; + } + pInfo->admin = *(pHook->pAdmin); + } + } + pInfo->is_admin_set = true; + } + else if (type == Override_CommandGroup) + { + void *object; + if (!sm_trie_retrieve(m_pCmdGrps, cmd, &object)) + { + return; + } + int grpid = (int)object; + + /* This is bad :( loop through all commands */ + List::iterator iter; + CmdHook *pHook; + for (iter=m_CmdList.begin(); iter!=m_CmdList.end(); iter++) + { + pInfo = (*iter); + for (List::iterator citer=pInfo->conhooks.begin(); + citer!=pInfo->conhooks.end(); + citer++) + { + pHook = (*citer); + if (pHook->pAdmin && pHook->pAdmin->cmdGrpId == grpid) + { + if (remove) + { + pHook->pAdmin->eflags = bits; + } else { + pHook->pAdmin->eflags = pHook->pAdmin->flags; + } + pInfo->admin = *(pHook->pAdmin); + } + } + } + pInfo->is_admin_set = true; + } +} + +void ConCmdManager::RemoveConCmd(ConCmdInfo *info, const char *name, bool is_read_safe, bool untrack) +{ + /* Remove from the trie */ + sm_trie_delete(m_pCmds, name); + + /* Remove console-specific information + * This should always be true as of right now + */ + if (info->pCmd) + { + if (info->sourceMod) + { + /* Unlink from SourceMM */ + g_SMAPI->UnregisterConCommandBase(g_PLAPI, info->pCmd); + /* Delete the command's memory */ + char *new_help = const_cast(info->pCmd->GetHelpText()); + char *new_name = const_cast(info->pCmd->GetName()); + delete [] new_help; + delete [] new_name; + delete info->pCmd; + } + else + { + if (is_read_safe) + { + /* Remove the external hook */ + SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, info->pCmd, CommandCallback, false); + } + if (untrack) + { + UntrackConCommandBase(info->pCmd, this); + } + } + } + + /* Remove from list */ + m_CmdList.remove(info); + + delete info; +} + +bool ConCmdManager::LookForSourceModCommand(const char *cmd) +{ + ConCmdInfo *pInfo; + + if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo)) + { + return false; + } + + return pInfo->sourceMod && (pInfo->conhooks.size() > 0); +} + +bool ConCmdManager::LookForCommandAdminFlags(const char *cmd, FlagBits *pFlags) +{ + ConCmdInfo *pInfo; + + if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo)) + { + return false; + } + + *pFlags = pInfo->admin.eflags; + + return pInfo->is_admin_set; +} + +ConCmdInfo *ConCmdManager::AddOrFindCommand(const char *name, const char *description, int flags) +{ + ConCmdInfo *pInfo; + if (!sm_trie_retrieve(m_pCmds, name, (void **)&pInfo)) + { + pInfo = new ConCmdInfo(); + /* Find the commandopan */ + ConCommandBase *pBase = icvar->GetCommands(); + ConCommand *pCmd = NULL; + while (pBase) + { + if (strcmp(pBase->GetName(), name) == 0) + { + /* Don't want to return convar with same name */ + if (!pBase->IsCommand()) + { + return NULL; + } + + pCmd = (ConCommand *)pBase; + break; + } + pBase = const_cast(pBase->GetNext()); + } + + if (!pCmd) + { + /* Note that we have to duplicate because the source might not be + * a static string, and these expect static memory. + */ + if (!description) + { + description = ""; + } + char *new_name = sm_strdup(name); + char *new_help = sm_strdup(description); + pCmd = new ConCommand(new_name, CommandCallback, new_help, flags); + pInfo->sourceMod = true; + } + else + { + TrackConCommandBase(pCmd, this); + SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, pCmd, CommandCallback, false); + } + + pInfo->pCmd = pCmd; + pInfo->is_admin_set = false; + + sm_trie_insert(m_pCmds, name, pInfo); + AddToCmdList(pInfo); + } + + return pInfo; +} + +void ConCmdManager::OnRootConsoleCommand(const char *cmdname, const CCommand &command) +{ + if (command.ArgC() >= 3) + { + const char *text = command.Arg(2); + + CPlugin *pPlugin = g_PluginSys.FindPluginByConsoleArg(text); + + if (!pPlugin) + { + g_RootMenu.ConsolePrint("[SM] Plugin \"%s\" was not found.", text); + return; + } + + const sm_plugininfo_t *plinfo = pPlugin->GetPublicInfo(); + const char *plname = IS_STR_FILLED(plinfo->name) ? plinfo->name : pPlugin->GetFilename(); + + CmdList *pList; + if (!pPlugin->GetProperty("CommandList", (void **)&pList)) + { + g_RootMenu.ConsolePrint("[SM] No commands found for: %s", plname); + return; + } + if (!pList->size()) + { + g_RootMenu.ConsolePrint("[SM] No commands found for: %s", plname); + return; + } + + CmdList::iterator iter; + const char *type = NULL; + const char *name; + const char *help; + g_RootMenu.ConsolePrint("[SM] Listing %d commands for: %s", pList->size(), plname); + g_RootMenu.ConsolePrint(" %-17.16s %-8.7s %s", "[Name]", "[Type]", "[Help]"); + for (iter=pList->begin(); + iter!=pList->end(); + iter++) + { + PlCmdInfo &cmd = (*iter); + if (cmd.type == Cmd_Server) + { + type = "server"; + } + else if (cmd.type == Cmd_Console) + { + type = "console"; + } + else if (cmd.type == Cmd_Admin) + { + type = "admin"; + } + name = cmd.pInfo->pCmd->GetName(); + if (cmd.pHook->helptext.size()) + { + help = cmd.pHook->helptext.c_str(); + } + else + { + help = cmd.pInfo->pCmd->GetHelpText(); + } + g_RootMenu.ConsolePrint(" %-17.16s %-12.11s %s", name, type, help); + } + + return; + } + + g_RootMenu.ConsolePrint("[SM] Usage: sm cmds "); +} diff --git a/core/ConCmdManager.h b/core/ConCmdManager.h new file mode 100644 index 00000000..8c25bb73 --- /dev/null +++ b/core/ConCmdManager.h @@ -0,0 +1,161 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_CONCMDMANAGER_H_ +#define _INCLUDE_SOURCEMOD_CONCMDMANAGER_H_ + +#include "sm_globals.h" +#include "sourcemm_api.h" +#include "ForwardSys.h" +#include "sm_trie.h" +#include "sm_memtable.h" +#include +#include +#include +#include +#include "concmd_cleaner.h" + +using namespace SourceHook; + +enum CmdType +{ + Cmd_Server, + Cmd_Console, + Cmd_Admin, +}; + +struct AdminCmdInfo +{ + AdminCmdInfo() + { + cmdGrpId = -1; + flags = 0; + eflags = 0; + } + int cmdGrpId; /* index into cmdgroup string table */ + FlagBits flags; /* default flags */ + FlagBits eflags; /* effective flags */ +}; + +struct CmdHook +{ + CmdHook() + { + pf = NULL; + pAdmin = NULL; + } + IPluginFunction *pf; /* function hook */ + String helptext; /* help text */ + AdminCmdInfo *pAdmin; /* admin requirements, if any */ +}; + +struct ConCmdInfo +{ + ConCmdInfo() + { + sourceMod = false; + pCmd = NULL; + } + bool sourceMod; /**< Determines whether or not concmd was created by a SourceMod plugin */ + ConCommand *pCmd; /**< Pointer to the command itself */ + List srvhooks; /**< Hooks as a server command */ + List conhooks; /**< Hooks as a console command */ + AdminCmdInfo admin; /**< Admin info, if any */ + bool is_admin_set; /**< Whether or not admin info is set */ +}; + +class ConCmdManager : + public SMGlobalClass, + public IRootConsoleCommand, + public IPluginsListener, + public IConCommandTracker +{ +#if defined ORANGEBOX_BUILD + friend void CommandCallback(const CCommand &command); +#else + friend void CommandCallback(); +#endif +public: + ConCmdManager(); + ~ConCmdManager(); +public: //SMGlobalClass + void OnSourceModAllInitialized(); + void OnSourceModShutdown(); +public: //IPluginsListener + void OnPluginDestroyed(IPlugin *plugin); +public: //IRootConsoleCommand + void OnRootConsoleCommand(const char *cmdname, const CCommand &command); +public: //IConCommandTracker + void OnUnlinkConCommandBase(ConCommandBase *pBase, const char *name, bool is_read_safe); +public: + bool AddServerCommand(IPluginFunction *pFunction, const char *name, const char *description, int flags); + bool AddConsoleCommand(IPluginFunction *pFunction, const char *name, const char *description, int flags); + bool AddAdminCommand(IPluginFunction *pFunction, + const char *name, + const char *group, + int adminflags, + const char *description, + int flags); + ResultType DispatchClientCommand(int client, const char *cmd, int args, ResultType type); + void UpdateAdminCmdFlags(const char *cmd, OverrideType type, FlagBits bits, bool remove); + bool LookForSourceModCommand(const char *cmd); + bool LookForCommandAdminFlags(const char *cmd, FlagBits *pFlags); + bool CheckCommandAccess(int client, const char *cmd, FlagBits flags); +private: + void InternalDispatch(const CCommand &command); + ResultType RunAdminCommand(ConCmdInfo *pInfo, int client, int args); + ConCmdInfo *AddOrFindCommand(const char *name, const char *description, int flags); + void SetCommandClient(int client); + void AddToCmdList(ConCmdInfo *info); + void RemoveConCmd(ConCmdInfo *info, const char *cmd, bool is_read_safe, bool untrack); + void RemoveConCmds(List &cmdlist); + void RemoveConCmds(List &cmdlist, IPluginContext *pContext); + bool CheckAccess(int client, const char *cmd, AdminCmdInfo *pAdmin); +public: + inline int GetCommandClient() + { + return m_CmdClient; + } + inline const List & GetCommandList() + { + return m_CmdList; + } +private: + Trie *m_pCmds; /* command lookup */ + Trie *m_pCmdGrps; /* command group lookup */ + List m_CmdList; /* command list */ + int m_CmdClient; /* current client */ + BaseStringTable m_Strings; /* string table */ +}; + +extern ConCmdManager g_ConCmds; + +#endif // _INCLUDE_SOURCEMOD_CONCMDMANAGER_H_ diff --git a/core/ConVarManager.cpp b/core/ConVarManager.cpp new file mode 100644 index 00000000..f8e27cee --- /dev/null +++ b/core/ConVarManager.cpp @@ -0,0 +1,707 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include "ConVarManager.h" +#include "HalfLife2.h" +#include "PluginSys.h" +#include "ForwardSys.h" +#include "HandleSys.h" +#include "sm_srvcmds.h" +#include "sm_stringutil.h" +#include +#include + +ConVarManager g_ConVarManager; + +#if !defined ORANGEBOX_BUILD +#define CallGlobalChangeCallbacks CallGlobalChangeCallback +#endif + +#if defined ORANGEBOX_BUILD +SH_DECL_HOOK3_void(ICvar, CallGlobalChangeCallbacks, SH_NOATTRIB, false, ConVar *, const char *, float); +#else +SH_DECL_HOOK2_void(ICvar, CallGlobalChangeCallbacks, SH_NOATTRIB, false, ConVar *, const char *); +#endif + +SH_DECL_HOOK5_void(IServerGameDLL, OnQueryCvarValueFinished, SH_NOATTRIB, 0, QueryCvarCookie_t, edict_t *, EQueryCvarValueStatus, const char *, const char *); +SH_DECL_HOOK5_void(IServerPluginCallbacks, OnQueryCvarValueFinished, SH_NOATTRIB, 0, QueryCvarCookie_t, edict_t *, EQueryCvarValueStatus, const char *, const char *); + +const ParamType CONVARCHANGE_PARAMS[] = {Param_Cell, Param_String, Param_String}; +typedef List ConVarList; +KTrie convar_cache; + +ConVarManager::ConVarManager() : m_ConVarType(0), m_bIsDLLQueryHooked(false), m_bIsVSPQueryHooked(false) +{ +} + +ConVarManager::~ConVarManager() +{ +} + +void ConVarManager::OnSourceModStartup(bool late) +{ + HandleAccess sec; + + /* Set up access rights for the 'ConVar' handle type */ + sec.access[HandleAccess_Read] = 0; + sec.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER; + sec.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER; + + /* Create the 'ConVar' handle type */ + m_ConVarType = g_HandleSys.CreateType("ConVar", this, 0, NULL, &sec, g_pCoreIdent, NULL); +} + +void ConVarManager::OnSourceModAllInitialized() +{ + /** + * Episode 2 has this function by default, but the older versions do not. + */ +#if !defined ORANGEBOX_BUILD + if (g_SMAPI->GetGameDLLVersion() >= 6) + { + SH_ADD_HOOK_MEMFUNC(IServerGameDLL, OnQueryCvarValueFinished, gamedll, this, &ConVarManager::OnQueryCvarValueFinished, false); + m_bIsDLLQueryHooked = true; + } +#endif + + SH_ADD_HOOK_STATICFUNC(ICvar, CallGlobalChangeCallbacks, icvar, OnConVarChanged, false); + + /* Add the 'convars' option to the 'sm' console command */ + g_RootMenu.AddRootConsoleCommand("cvars", "View convars created by a plugin", this); +} + +void ConVarManager::OnSourceModShutdown() +{ + List::iterator iter = m_ConVars.begin(); + HandleSecurity sec(NULL, g_pCoreIdent); + + /* Iterate list of ConVarInfo structures, remove every one of them */ + while (iter != m_ConVars.end()) + { + ConVarInfo *pInfo = (*iter); + + iter = m_ConVars.erase(iter); + + g_HandleSys.FreeHandle(pInfo->handle, &sec); + if (pInfo->pChangeForward != NULL) + { + g_Forwards.ReleaseForward(pInfo->pChangeForward); + } + if (pInfo->sourceMod) + { + /* If we created it, we won't be tracking it, therefore it is + * safe to remove everything in one go. + */ + META_UNREGCVAR(pInfo->pVar); + delete [] pInfo->pVar->GetName(); + delete [] pInfo->pVar->GetHelpText(); + delete [] pInfo->pVar->GetDefault(); + delete pInfo->pVar; + } + else + { + /* If we didn't create it, we might be tracking it. Also, + * it could be unreadable. + */ + UntrackConCommandBase(pInfo->pVar, this); + } + + /* It's not safe to read the name here, so we simply delete the + * the info struct and clear the lookup cache at the end. + */ + delete pInfo; + } + convar_cache.clear(); + + /* Unhook things */ + if (m_bIsDLLQueryHooked) + { + SH_REMOVE_HOOK_MEMFUNC(IServerGameDLL, OnQueryCvarValueFinished, gamedll, this, &ConVarManager::OnQueryCvarValueFinished, false); + m_bIsDLLQueryHooked = false; + } + else if (m_bIsVSPQueryHooked) + { + SH_REMOVE_HOOK_MEMFUNC(IServerPluginCallbacks, OnQueryCvarValueFinished, vsp_interface, this, &ConVarManager::OnQueryCvarValueFinished, false); + m_bIsVSPQueryHooked = false; + } + + SH_REMOVE_HOOK_STATICFUNC(ICvar, CallGlobalChangeCallbacks, icvar, OnConVarChanged, false); + + /* Remove the 'convars' option from the 'sm' console command */ + g_RootMenu.RemoveRootConsoleCommand("cvars", this); + + /* Remove the 'ConVar' handle type */ + g_HandleSys.RemoveType(m_ConVarType, g_pCoreIdent); +} + +/** + * Orange Box will never use this. + */ +void ConVarManager::OnSourceModVSPReceived() +{ + /** + * Don't bother if the DLL is already hooked. + */ + if (m_bIsDLLQueryHooked) + { + return; + } + + /* For later MM:S versions, use the updated API, since it's cleaner. */ +#if defined METAMOD_PLAPI_VERSION + int engine = g_SMAPI->GetSourceEngineBuild(); + if (engine == SOURCE_ENGINE_ORIGINAL || vsp_version < 2) + { + return; + } +#else + if (g_HL2.IsOriginalEngine() || vsp_version < 2) + { + return; + } +#endif + + SH_ADD_HOOK_MEMFUNC(IServerPluginCallbacks, OnQueryCvarValueFinished, vsp_interface, this, &ConVarManager::OnQueryCvarValueFinished, false); + m_bIsVSPQueryHooked = true; +} + +bool convar_cache_lookup(const char *name, ConVarInfo **pVar) +{ + ConVarInfo **pLookup = convar_cache.retrieve(name); + if (pLookup != NULL) + { + *pVar = *pLookup; + return true; + } + else + { + return false; + } +} + +void ConVarManager::OnUnlinkConCommandBase(ConCommandBase *pBase, const char *name, bool is_read_safe) +{ + /* Only check convars that have not been created by SourceMod's core */ + ConVarInfo *pInfo; + if (!convar_cache_lookup(name, &pInfo)) + { + return; + } + + HandleSecurity sec(NULL, g_pCoreIdent); + + /* Remove it from our cache */ + m_ConVars.remove(pInfo); + convar_cache.remove(name); + + /* Now make sure no plugins are referring to this pointer */ + IPluginIterator *pl_iter = g_PluginSys.GetPluginIterator(); + while (pl_iter->MorePlugins()) + { + IPlugin *pl = pl_iter->GetPlugin(); + + ConVarList *pConVarList; + if (pl->GetProperty("ConVarList", (void **)&pConVarList, true) + && pConVarList != NULL) + { + pConVarList->remove(pInfo->pVar); + } + + pl_iter->NextPlugin(); + } + + /* Free resources */ + g_HandleSys.FreeHandle(pInfo->handle, &sec); + delete pInfo; +} + +void ConVarManager::OnPluginUnloaded(IPlugin *plugin) +{ + ConVarList *pConVarList; + + /* If plugin has a convar list, free its memory */ + if (plugin->GetProperty("ConVarList", (void **)&pConVarList, true)) + { + delete pConVarList; + } +} + +void ConVarManager::OnHandleDestroy(HandleType_t type, void *object) +{ +} + +bool ConVarManager::GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize) +{ + *pSize = sizeof(ConVar) + sizeof(ConVarInfo); + return true; +} + +void ConVarManager::OnRootConsoleCommand(const char *cmdname, const CCommand &command) +{ + int argcount = command.ArgC(); + if (argcount >= 3) + { + /* Get plugin index that was passed */ + const char *arg = command.Arg(2); + + /* Get plugin object */ + CPlugin *plugin = g_PluginSys.FindPluginByConsoleArg(arg); + + if (!plugin) + { + g_RootMenu.ConsolePrint("[SM] Plugin \"%s\" was not found.", arg); + return; + } + + /* Get plugin name */ + const sm_plugininfo_t *plinfo = plugin->GetPublicInfo(); + const char *plname = IS_STR_FILLED(plinfo->name) ? plinfo->name : plugin->GetFilename(); + + ConVarList *pConVarList; + ConVarList::iterator iter; + + /* If no convar list... */ + if (!plugin->GetProperty("ConVarList", (void **)&pConVarList)) + { + g_RootMenu.ConsolePrint("[SM] No convars found for: %s", plname); + return; + } + + g_RootMenu.ConsolePrint("[SM] Listing %d convars for: %s", pConVarList->size(), plname); + g_RootMenu.ConsolePrint(" %-32.31s %s", "[Name]", "[Value]"); + + /* Iterate convar list and display each one */ + for (iter = pConVarList->begin(); iter != pConVarList->end(); iter++) + { + const ConVar *pConVar = (*iter); + g_RootMenu.ConsolePrint(" %-32.31s %s", pConVar->GetName(), pConVar->GetString()); + } + + return; + } + + /* Display usage of subcommand */ + g_RootMenu.ConsolePrint("[SM] Usage: sm convars "); +} + +Handle_t ConVarManager::CreateConVar(IPluginContext *pContext, const char *name, const char *defaultVal, const char *description, int flags, bool hasMin, float min, bool hasMax, float max) +{ + ConVar *pConVar = NULL; + ConVarInfo *pInfo = NULL; + Handle_t hndl = 0; + + /* Find out if the convar exists already */ + pConVar = icvar->FindVar(name); + + /* If the convar already exists... */ + if (pConVar) + { + /* Add convar to plugin's list */ + AddConVarToPluginList(pContext, pConVar); + + /* First find out if we already have a handle to it */ + if (convar_cache_lookup(name, &pInfo)) + { + return pInfo->handle; + } + else + { + /* Create and initialize ConVarInfo structure */ + pInfo = new ConVarInfo(); + pInfo->sourceMod = false; + pInfo->pChangeForward = NULL; + pInfo->pVar = pConVar; + + /* If we don't, then create a new handle from the convar */ + hndl = g_HandleSys.CreateHandle(m_ConVarType, pInfo, NULL, g_pCoreIdent, NULL); + if (hndl == BAD_HANDLE) + { + delete pInfo; + return BAD_HANDLE; + } + + pInfo->handle = hndl; + + /* Insert struct into caches */ + m_ConVars.push_back(pInfo); + convar_cache.insert(name, pInfo); + TrackConCommandBase(pConVar, this); + + return hndl; + } + } + + /* To prevent creating a convar that has the same name as a console command... ugh */ + ConCommandBase *pBase = icvar->GetCommands(); + + while (pBase) + { + if (pBase->IsCommand() && strcmp(pBase->GetName(), name) == 0) + { + return BAD_HANDLE; + } + + pBase = const_cast(pBase->GetNext()); + } + + /* Create and initialize ConVarInfo structure */ + pInfo = new ConVarInfo(); + pInfo->handle = hndl; + pInfo->sourceMod = true; + pInfo->pChangeForward = NULL; + + /* Create a handle from the new convar */ + hndl = g_HandleSys.CreateHandle(m_ConVarType, pInfo, NULL, g_pCoreIdent, NULL); + if (hndl == BAD_HANDLE) + { + delete pInfo; + return BAD_HANDLE; + } + + pInfo->handle = hndl; + + /* Since an existing convar (or concmd with the same name) was not found , now we can finally create it */ + pConVar = new ConVar(sm_strdup(name), sm_strdup(defaultVal), flags, sm_strdup(description), hasMin, min, hasMax, max); + pInfo->pVar = pConVar; + + /* Add convar to plugin's list */ + AddConVarToPluginList(pContext, pConVar); + + /* Insert struct into caches */ + m_ConVars.push_back(pInfo); + convar_cache.insert(name, pInfo); + + return hndl; +} + +Handle_t ConVarManager::FindConVar(const char *name) +{ + ConVar *pConVar = NULL; + ConVarInfo *pInfo; + Handle_t hndl; + + /* Search for convar */ + pConVar = icvar->FindVar(name); + + /* If it doesn't exist, then return an invalid handle */ + if (!pConVar) + { + return BAD_HANDLE; + } + + /* At this point, the convar exists. So, find out if we already have a handle */ + if (convar_cache_lookup(name, &pInfo)) + { + return pInfo->handle; + } + + /* Create and initialize ConVarInfo structure */ + pInfo = new ConVarInfo(); + pInfo->sourceMod = false; + pInfo->pChangeForward = NULL; + pInfo->pVar = pConVar; + + /* If we don't have a handle, then create a new one */ + hndl = g_HandleSys.CreateHandle(m_ConVarType, pInfo, NULL, g_pCoreIdent, NULL); + if (hndl == BAD_HANDLE) + { + delete pInfo; + return BAD_HANDLE; + } + + pInfo->handle = hndl; + + /* Insert struct into our caches */ + m_ConVars.push_back(pInfo); + convar_cache.insert(name, pInfo); + TrackConCommandBase(pConVar, this); + + return hndl; +} + +void ConVarManager::AddConVarChangeListener(const char *name, IConVarChangeListener *pListener) +{ + ConVarInfo *pInfo; + + if (FindConVar(name) == BAD_HANDLE) + { + return; + } + + /* Find the convar in the lookup trie */ + if (convar_cache_lookup(name, &pInfo)) + { + pInfo->changeListeners.push_back(pListener); + } +} + +void ConVarManager::RemoveConVarChangeListener(const char *name, IConVarChangeListener *pListener) +{ + ConVarInfo *pInfo; + + /* Find the convar in the lookup trie */ + if (convar_cache_lookup(name, &pInfo)) + { + pInfo->changeListeners.remove(pListener); + } +} + +void ConVarManager::HookConVarChange(ConVar *pConVar, IPluginFunction *pFunction) +{ + ConVarInfo *pInfo; + IChangeableForward *pForward; + + /* Find the convar in the lookup trie */ + if (convar_cache_lookup(pConVar->GetName(), &pInfo)) + { + /* Get the forward */ + pForward = pInfo->pChangeForward; + + /* If forward does not exist, create it */ + if (!pForward) + { + pForward = g_Forwards.CreateForwardEx(NULL, ET_Ignore, 3, CONVARCHANGE_PARAMS); + pInfo->pChangeForward = pForward; + } + + /* Add function to forward's list */ + pForward->AddFunction(pFunction); + } +} + +void ConVarManager::UnhookConVarChange(ConVar *pConVar, IPluginFunction *pFunction) +{ + ConVarInfo *pInfo; + IChangeableForward *pForward; + IPluginContext *pContext = pFunction->GetParentContext(); + + /* Find the convar in the lookup trie */ + if (convar_cache_lookup(pConVar->GetName(), &pInfo)) + { + /* Get the forward */ + pForward = pInfo->pChangeForward; + + /* If the forward doesn't exist, we can't unhook anything */ + if (!pForward) + { + pContext->ThrowNativeError("Convar \"%s\" has no active hook", pConVar->GetName()); + return; + } + + /* Remove the function from the forward's list */ + if (!pForward->RemoveFunction(pFunction)) + { + pContext->ThrowNativeError("Invalid hook callback specified for convar \"%s\"", pConVar->GetName()); + return; + } + + /* If the forward now has 0 functions in it... */ + if (pForward->GetFunctionCount() == 0) + { + /* Free this forward */ + g_Forwards.ReleaseForward(pForward); + pInfo->pChangeForward = NULL; + } + } +} + +QueryCvarCookie_t ConVarManager::QueryClientConVar(edict_t *pPlayer, const char *name, IPluginFunction *pCallback, Handle_t hndl) +{ + QueryCvarCookie_t cookie; + + /* Call StartQueryCvarValue() in either the IVEngineServer or IServerPluginHelpers depending on situation */ + if (m_bIsDLLQueryHooked) + { + cookie = engine->StartQueryCvarValue(pPlayer, name); + } + else if (m_bIsVSPQueryHooked) + { + cookie = serverpluginhelpers->StartQueryCvarValue(pPlayer, name); + } + else + { + return InvalidQueryCvarCookie; + } + + ConVarQuery query = {cookie, pCallback, hndl}; + m_ConVarQueries.push_back(query); + + return cookie; +} + +void ConVarManager::AddConVarToPluginList(IPluginContext *pContext, const ConVar *pConVar) +{ + ConVarList *pConVarList; + ConVarList::iterator iter; + bool inserted = false; + const char *orig = pConVar->GetName(); + + IPlugin *plugin = g_PluginSys.FindPluginByContext(pContext->GetContext()); + + /* Check plugin for an existing convar list */ + if (!plugin->GetProperty("ConVarList", (void **)&pConVarList)) + { + pConVarList = new ConVarList(); + plugin->SetProperty("ConVarList", pConVarList); + } + else if (pConVarList->find(pConVar) != pConVarList->end()) + { + /* If convar is already in list, then don't add it */ + return; + } + + /* Insert convar into list which is sorted alphabetically */ + for (iter = pConVarList->begin(); iter != pConVarList->end(); iter++) + { + if (strcmp(orig, (*iter)->GetName()) < 0) + { + pConVarList->insert(iter, pConVar); + inserted = true; + break; + } + } + + if (!inserted) + { + pConVarList->push_back(pConVar); + } +} + +#if defined ORANGEBOX_BUILD +void ConVarManager::OnConVarChanged(ConVar *pConVar, const char *oldValue, float flOldValue) +#else +void ConVarManager::OnConVarChanged(ConVar *pConVar, const char *oldValue) +#endif +{ + /* If the values are the same, exit early in order to not trigger callbacks */ + if (strcmp(pConVar->GetString(), oldValue) == 0) + { + return; + } + + ConVarInfo *pInfo; + + /* Find the convar in the lookup trie */ + if (!convar_cache_lookup(pConVar->GetName(), &pInfo)) + { + return; + } + + IChangeableForward *pForward = pInfo->pChangeForward; + + if (pInfo->changeListeners.size() != 0) + { + for (List::iterator i = pInfo->changeListeners.begin(); + i != pInfo->changeListeners.end(); + i++) + { +#if defined ORANGEBOX_BUILD + (*i)->OnConVarChanged(pConVar, oldValue, flOldValue); +#else + (*i)->OnConVarChanged(pConVar, oldValue, atof(oldValue)); +#endif + } + } + + if (pForward != NULL) + { + /* Now call forwards in plugins that have hooked this */ + pForward->PushCell(pInfo->handle); + pForward->PushString(oldValue); + pForward->PushString(pConVar->GetString()); + pForward->Execute(NULL); + } +} + +bool ConVarManager::IsQueryingSupported() +{ + return (m_bIsDLLQueryHooked || m_bIsVSPQueryHooked); +} + +void ConVarManager::OnQueryCvarValueFinished(QueryCvarCookie_t cookie, edict_t *pPlayer, EQueryCvarValueStatus result, const char *cvarName, const char *cvarValue) +{ + IPluginFunction *pCallback = NULL; + cell_t value = 0; + List::iterator iter; + + for (iter = m_ConVarQueries.begin(); iter != m_ConVarQueries.end(); iter++) + { + ConVarQuery &query = (*iter); + if (query.cookie == cookie) + { + pCallback = query.pCallback; + value = query.value; + break; + } + } + + if (pCallback) + { + cell_t ret; + + pCallback->PushCell(cookie); + pCallback->PushCell(engine->IndexOfEdict(pPlayer)); + pCallback->PushCell(result); + pCallback->PushString(cvarName); + + if (result == eQueryCvarValueStatus_ValueIntact) + { + pCallback->PushString(cvarValue); + } + else + { + pCallback->PushString("\0"); + } + + pCallback->PushCell(value); + pCallback->Execute(&ret); + + m_ConVarQueries.erase(iter); + } +} + +HandleError ConVarManager::ReadConVarHandle(Handle_t hndl, ConVar **pVar) +{ + ConVarInfo *pInfo; + HandleError error; + + if ((error = g_HandleSys.ReadHandle(hndl, m_ConVarType, NULL, (void **)&pInfo)) != HandleError_None) + { + return error; + } + + if (pVar) + { + *pVar = pInfo->pVar; + } + + return error; +} diff --git a/core/ConVarManager.h b/core/ConVarManager.h new file mode 100644 index 00000000..148f7c40 --- /dev/null +++ b/core/ConVarManager.h @@ -0,0 +1,165 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_CONVARMANAGER_H_ +#define _INCLUDE_SOURCEMOD_CONVARMANAGER_H_ + +#include "sm_globals.h" +#include "sourcemm_api.h" +#include +#include +#include +#include +#include +#include +#include "concmd_cleaner.h" + +using namespace SourceHook; + +class IConVarChangeListener +{ +public: + virtual void OnConVarChanged(ConVar *pConVar, const char *oldValue, float flOldValue) =0; +}; + +/** + * Holds SourceMod-specific information about a convar + */ +struct ConVarInfo +{ + Handle_t handle; /**< Handle to self */ + bool sourceMod; /**< Determines whether or not convar was created by a SourceMod plugin */ + IChangeableForward *pChangeForward; /**< Forward associated with convar */ + ConVar *pVar; /**< The actual convar */ + List changeListeners; +}; + +/** + * Holds information about a client convar query + */ +struct ConVarQuery +{ + QueryCvarCookie_t cookie; /**< Cookie that identifies query */ + IPluginFunction *pCallback; /**< Function that will be called when query is finished */ + cell_t value; /**< Optional value passed to query function */ +}; + +class ConVarManager : + public SMGlobalClass, + public IHandleTypeDispatch, + public IPluginsListener, + public IRootConsoleCommand, + public IConCommandTracker +{ +public: + ConVarManager(); + ~ConVarManager(); +public: // SMGlobalClass + void OnSourceModStartup(bool late); + void OnSourceModAllInitialized(); + void OnSourceModShutdown(); + void OnSourceModVSPReceived(); +public: // IHandleTypeDispatch + void OnHandleDestroy(HandleType_t type, void *object); + bool GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize); +public: // IPluginsListener + void OnPluginUnloaded(IPlugin *plugin); +public: //IRootConsoleCommand + void OnRootConsoleCommand(const char *cmdname, const CCommand &command); +public: //IConCommandTracker + void OnUnlinkConCommandBase(ConCommandBase *pBase, const char *name, bool is_read_safe); +public: + /** + * Create a convar and return a handle to it. + */ + Handle_t CreateConVar(IPluginContext *pContext, const char *name, const char *defaultVal, + const char *description, int flags, bool hasMin, float min, bool hasMax, float max); + + /** + * Searches for a convar and returns a handle to it + */ + Handle_t FindConVar(const char* name); + + /** + * Add a function to call when the specified convar changes. + */ + void HookConVarChange(ConVar *pConVar, IPluginFunction *pFunction); + + /** + * Remove a function from the forward that will be called when the specified convar changes. + */ + void UnhookConVarChange(ConVar *pConVar, IPluginFunction *pFunction); + + void AddConVarChangeListener(const char *name, IConVarChangeListener *pListener); + void RemoveConVarChangeListener(const char *name, IConVarChangeListener *pListener); + + /** + * Starts a query to find the value of a client convar. + */ + QueryCvarCookie_t QueryClientConVar(edict_t *pPlayer, const char *name, IPluginFunction *pCallback, + Handle_t hndl); + + bool IsQueryingSupported(); + + HandleError ReadConVarHandle(Handle_t hndl, ConVar **pVar); + +private: + /** + * Adds a convar to a plugin's list. + */ + static void AddConVarToPluginList(IPluginContext *pContext, const ConVar *pConVar); + + /** + * Static callback that Valve's ConVar object executes when the convar's value changes. + */ +#if defined ORANGEBOX_BUILD + static void OnConVarChanged(ConVar *pConVar, const char *oldValue, float flOldValue); +#else + static void OnConVarChanged(ConVar *pConVar, const char *oldValue); +#endif + + /** + * Callback for when StartQueryCvarValue() has finished. + */ + void OnQueryCvarValueFinished(QueryCvarCookie_t cookie, edict_t *pPlayer, EQueryCvarValueStatus result, + const char *cvarName, const char *cvarValue); +private: + HandleType_t m_ConVarType; + List m_ConVars; + List m_ConVarQueries; + bool m_bIsDLLQueryHooked; + bool m_bIsVSPQueryHooked; +}; + +extern ConVarManager g_ConVarManager; + +#endif // _INCLUDE_SOURCEMOD_CONVARMANAGER_H_ + diff --git a/core/CoreConfig.cpp b/core/CoreConfig.cpp new file mode 100644 index 00000000..2b0fc95c --- /dev/null +++ b/core/CoreConfig.cpp @@ -0,0 +1,524 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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 . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include +#include "CoreConfig.h" +#include "sourcemod.h" +#include "sourcemm_api.h" +#include "sm_srvcmds.h" +#include "sm_version.h" +#include "sm_stringutil.h" +#include "LibrarySys.h" +#include "Logger.h" +#include "PluginSys.h" +#include "ForwardSys.h" + +#ifdef PLATFORM_WINDOWS +ConVar sm_corecfgfile("sm_corecfgfile", "addons\\sourcemod\\configs\\core.cfg", 0, "SourceMod core configuration file"); +#elif defined PLATFORM_LINUX +ConVar sm_corecfgfile("sm_corecfgfile", "addons/sourcemod/configs/core.cfg", 0, "SourceMod core configuration file"); +#endif + +IForward *g_pOnServerCfg = NULL; +IForward *g_pOnConfigsExecuted = NULL; +IForward *g_pOnAutoConfigsBuffered = NULL; +CoreConfig g_CoreConfig; +bool g_bConfigsExecd = false; +bool g_bServerExecd = false; +bool g_bGotServerStart = false; +bool g_bGotTrigger = false; +ConCommand *g_pExecPtr = NULL; +ConVar *g_ServerCfgFile = NULL; + +void CheckAndFinalizeConfigs(); + +#if defined ORANGEBOX_BUILD +SH_DECL_EXTERN1_void(ConCommand, Dispatch, SH_NOATTRIB, false, const CCommand &); +void Hook_ExecDispatchPre(const CCommand &cmd) +#else +extern bool __SourceHook_FHAddConCommandDispatch(void *,bool,class fastdelegate::FastDelegate0); +extern bool __SourceHook_FHRemoveConCommandDispatch(void *,bool,class fastdelegate::FastDelegate0); +void Hook_ExecDispatchPre() +#endif +{ +#if !defined ORANGEBOX_BUILD + CCommand cmd; +#endif + + const char *arg = cmd.Arg(1); + + if (!g_bServerExecd + && arg != NULL + && strcmp(arg, g_ServerCfgFile->GetString()) == 0) + { + g_bGotTrigger = true; + } +} + +#if defined ORANGEBOX_BUILD +void Hook_ExecDispatchPost(const CCommand &cmd) +#else +void Hook_ExecDispatchPost() +#endif +{ + if (g_bGotTrigger) + { + g_bGotTrigger = false; + g_bServerExecd = true; + CheckAndFinalizeConfigs(); + } +} + +void CheckAndFinalizeConfigs() +{ + if ((g_bServerExecd || g_ServerCfgFile == NULL) + && g_bGotServerStart) + { + /* Order is important here. We need to buffer things before we send the command out. */ + g_pOnAutoConfigsBuffered->Execute(NULL); + engine->ServerCommand("sm internal 1\n"); + } +} + +void CoreConfig::OnSourceModAllInitialized() +{ + g_RootMenu.AddRootConsoleCommand("config", "Set core configuration options", this); + g_pOnServerCfg = g_Forwards.CreateForward("OnServerCfg", ET_Ignore, 0, NULL); + g_pOnConfigsExecuted = g_Forwards.CreateForward("OnConfigsExecuted", ET_Ignore, 0, NULL); + g_pOnAutoConfigsBuffered = g_Forwards.CreateForward("OnAutoConfigsBuffered", ET_Ignore, 0, NULL); +} + +void CoreConfig::OnSourceModShutdown() +{ + g_RootMenu.RemoveRootConsoleCommand("config", this); + g_Forwards.ReleaseForward(g_pOnServerCfg); + g_Forwards.ReleaseForward(g_pOnConfigsExecuted); + g_Forwards.ReleaseForward(g_pOnAutoConfigsBuffered); + + if (g_pExecPtr != NULL) + { + SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, g_pExecPtr, Hook_ExecDispatchPre, false); + SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, g_pExecPtr, Hook_ExecDispatchPost, true); + g_pExecPtr = NULL; + } +} + +void CoreConfig::OnSourceModLevelChange(const char *mapName) +{ + static bool already_checked = false; + + if (!already_checked) + { + g_ServerCfgFile = icvar->FindVar("servercfgfile"); + if (g_ServerCfgFile != NULL) + { + ConCommandBase *pBase = icvar->GetCommands(); + while (pBase != NULL) + { + if (pBase->IsCommand() && strcmp(pBase->GetName(), "exec") == 0) + { + break; + } + pBase = const_cast(pBase->GetNext()); + } + + g_pExecPtr = (ConCommand *)pBase; + if (g_pExecPtr != NULL) + { + SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, g_pExecPtr, Hook_ExecDispatchPre, false); + SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, g_pExecPtr, Hook_ExecDispatchPost, true); + } + else + { + g_ServerCfgFile = NULL; + } + } + already_checked = true; + } + + g_bConfigsExecd = false; + g_bServerExecd = false; + g_bGotServerStart = false; + g_bGotTrigger = false; +} + +void CoreConfig::OnRootConsoleCommand(const char *cmdname, const CCommand &command) +{ + int argcount = command.ArgC(); + if (argcount >= 4) + { + const char *option = command.Arg(2); + const char *value = command.Arg(3); + + char error[255]; + + ConfigResult res = SetConfigOption(option, value, ConfigSource_Console, error, sizeof(error)); + + if (res == ConfigResult_Reject) + { + g_RootMenu.ConsolePrint("[SM] Could not set config option \"%s\" to \"%s\" (%s)", option, value, error); + } else if (res == ConfigResult_Ignore) { + g_RootMenu.ConsolePrint("[SM] No such config option \"%s\" exists.", option); + } else { + g_RootMenu.ConsolePrint("Config option \"%s\" successfully set to \"%s.\"", option, value); + } + + return; + } + + g_RootMenu.ConsolePrint("[SM] Usage: sm config