├── .gitignore
├── LICENSE
├── README.md
├── addons
└── sourcemod
│ ├── extensions
│ └── voicemanager.autoload
│ ├── gamedata
│ └── voicemanager.txt
│ └── scripting
│ ├── include
│ └── voicemanager.inc
│ └── voicemanager.sp
├── build.bat
├── build_ext.bat
├── build_ext.sh
├── build_ext_windows.bat
├── build_plugin.bat
├── build_plugin.sh
└── extension
├── AMBuildScript
├── AMBuilder
├── CDetour
├── detourhelpers.h
├── detours.cpp
└── detours.h
├── CRC.h
├── Dockerfile
├── Dockerfile.windows
├── PackageScript
├── asm
├── asm.c
└── asm.h
├── configure.py
├── defines.h
├── docker-compose.windows.yml
├── docker-compose.yml
├── extension.cpp
├── extension.h
├── include
├── celt
│ ├── arch.h
│ ├── celt.h
│ ├── ecintrin.h
│ ├── entcode.h
│ ├── entdec.h
│ ├── entenc.h
│ ├── mathops.h
│ └── os_support.h
├── opus-dev.lib
├── opus.dll
├── opus.lib
├── opus
│ └── opus.h
├── opus1.3.1.lib
├── opus_custom.h
├── opus_defines.h
├── opus_multistream.h
├── opus_projection.h
└── opus_types.h
├── inetmessage.h
├── libc_compat.cpp
├── scripts
├── build.bat
├── build.sh
└── setup.py
├── smsdk_config.h
├── smsdk_ext.h
├── voicemanager.cpp
├── voicemanager.h
├── voicemanagerclientstate.cpp
└── voicemanagerclientstate.h
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | obj-linux-x86_64
3 | ./extension/include
4 | .vscode
5 | spcomp
6 | spcomp.exe
7 | addons/sourcemod/plugins
8 | addons/sourcemod/extensions/voicemanager.ext*
9 | deploy_*
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Voice Manager
2 |
3 |
4 | A sourcemod plugin and extension that allows players to individually modify the voice volume of other players.
5 |
6 |
7 |
View Demo on YouTube
8 |
9 |
10 |
11 | [](https://youtu.be/5lFNonAkXDQ "Voice Manager Sourcemod Extension and Plugin Demo")
12 |
13 |
14 |
15 | ## How To Use
16 | A player can type the command `/vm` into chat to display a menu that allows them to set volume overrides for players in the server. Overrides can be set for invidual players or globally for all players (individual overrides will take precedence).
17 |
18 | 
19 |
20 | When a volume adjustment is made, all voice communications from that player will be adjusted accordingly.
21 |
22 | There are currently 5 volume levels that can be selected:
23 |
24 | 
25 |
26 | ## Requirements
27 |
28 | ### Supported Games*
29 | - Team Fortress 2
30 | - Open Fortress
31 |
32 | * Voice Manager would likely work with any game that supports the steam voice codec. I would be happy to add support for other games by request so long as testing assistance for said game is provided.
33 |
34 | ### Supported Platforms
35 | - Linux
36 | - Windows
37 |
38 | ### Sourcemod
39 | - Version 1.10+
40 |
41 | ### Supported Database Drivers
42 | - mysql
43 | - sqlite
44 |
45 | ### Supported Voice Codecs
46 | - steam (`sv_voicecodec steam`)
47 |
48 | ## Installation
49 | Download the [latest release](https://github.com/SouthernCrossGaming/voicemanager/releases/latest/download/voicemanager.zip), unzip and copy to your `addons` directory.
50 |
51 | Add a configuration for the voice manager database to your `addons/sourcemod/configs/databases.cfg` file. Note that voice manager will use the "default" configuration by default, but this can be configured via cvar, see below.
52 |
53 | ## Configuration
54 | `vm_enabled` - Enables or disables voice manager (0/1, default 1)
55 | `vm_database` - Database configuration to use from databases.cfg (default is "default")
56 | `vm_allow_self` - Allow players to override their own volume. This is recommended only for testing (0/1, default 0)
57 |
58 | ## Commands
59 | `/vm` | `/voicemanager` - Opens the Voice Manager menu
60 | `/vmclear` - Clears the player's overrides from the database
61 |
62 | ## Building
63 |
64 | ### Build Extension for Linux
65 |
66 | *Requires Docker (with docker compose) using Linux containers
67 | ```
68 | > .\build_ext.bat
69 | ```
70 | ```
71 | $ ./build_ext.sh
72 | ```
73 |
74 | ### Build Extension for Windows
75 |
76 | *Requires Windows environment with Docker (with docker compose) using Windows containers
77 | ```
78 | > .\build_ext_windows.bat
79 | ```
80 |
81 | ### Build Plugin
82 | *Requires spcomp (1.10 or higher) added to your path or to the base directory
83 | ```
84 | > .\build_plugin.bat
85 | ```
86 | ```
87 | $ ./build_plugin.sh
88 | ```
89 |
90 | ### Build All (Windows environment only)
91 | ```
92 | > .\build.bat
93 | ```
94 |
95 | ## VS Code Setup
96 | To setup C++ includes for VS Code, clone sourcemod, metamod, and the tf2 sdk. Include the following paths:
97 | - `./`
98 | - `./include`
99 | - ``
100 | - `\public`
101 | - `\public\extensions`
102 | - `\sourcepawn\include`
103 | - `\sourcepawn\third_party\amtl`
104 | - `\sourcepawn\third_party\amtl\amtl`
105 | - `\core`
106 | - `\core\sourcehook`
107 | - `\public`
108 | - `\public\tier0`
109 | - `\public\tier1`
110 |
111 | ### Troubleshooting
112 | - Problem: The VoiceManager plugin/extension did not load.
113 | - Potential Solution: Check for any errors on startup and ensure that an empty `voicemanager.autoload` file exists under the `addons/sourcemod/extension` directory.
114 |
115 |
116 |
117 | * Problem: When an adjustment is applied, there are no errors but the player does not have any voice output.
118 | * Potential Solution: Make sure the server is using the `steam` voice codec (`sv_voicecodec steam`)
119 |
120 | # Credits
121 | - [Fraeven](https://fraeven.dev) (Extension Code, Plugin Code, Testing)
122 | - [Rowedahelicon](https://rowdythecrux.dev) (Plugin Code, Debugging Assistance, Testing)
123 | - Many members of the SCG community (Testing)
124 |
125 | # AlliedModders Thread
126 | https://forums.alliedmods.net/showthread.php?t=344276
127 |
--------------------------------------------------------------------------------
/addons/sourcemod/extensions/voicemanager.autoload:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SouthernCrossGaming/voicemanager/789fa2ee045f2ed4f61412d60dd91d58c6c086b4/addons/sourcemod/extensions/voicemanager.autoload
--------------------------------------------------------------------------------
/addons/sourcemod/gamedata/voicemanager.txt:
--------------------------------------------------------------------------------
1 | "Games"
2 | {
3 | "tf"
4 | {
5 | "Signatures"
6 | {
7 | "SV_BroadcastVoiceData"
8 | {
9 | "library" "engine"
10 | "windows" "\x55\x8B\xEC\xA1\x2A\x2A\x2A\x2A\x83\xEC\x50\x83\x78\x30"
11 | "linux" "@_Z21SV_BroadcastVoiceDataP7IClientiPcx"
12 | }
13 | }
14 | }
15 |
16 | "open_fortress"
17 | {
18 | "Signatures"
19 | {
20 | "SV_BroadcastVoiceData"
21 | {
22 | "library" "engine"
23 | "windows" "\x55\x8B\xEC\xA1\x2A\x2A\x2A\x2A\x83\xEC\x50\x83\x78\x30"
24 | "linux" "@_Z21SV_BroadcastVoiceDataP7IClientiPcx"
25 | }
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/addons/sourcemod/scripting/include/voicemanager.inc:
--------------------------------------------------------------------------------
1 | /**
2 | * Loads an adjustment for a player into voicemanager
3 | *
4 | * @param adjuster The player invoking the volume adjustment
5 | * @param adjustedSteamId The steam id 64 (string) of the player being adjusted.
6 | * @param volume The volume setting they're being adjusted to
7 | */
8 | native bool LoadPlayerAdjustment(int caller, char adjustedSteamId[18], int volume);
9 |
10 | /**
11 | * Adjusts the volume of a player for a player
12 | *
13 | * @param caller The player invoking the volume adjustment
14 | * @param client The player being adjusted locally
15 | * @param volume The volume setting they're being adjusted to
16 | */
17 | native bool OnPlayerAdjustVolume(int caller, int client, int volume);
18 |
19 | native bool OnPlayerGlobalAdjust(int caller, int volume);
20 |
21 | native bool RefreshActiveOverrides();
22 |
23 | /**
24 | * Clears all overrides a client has set
25 | *
26 | * @param client The player invoking the clear request
27 | */
28 | native bool ClearClientOverrides(int client);
29 |
30 | /**
31 | * Checks to see if a client has another client muted
32 | *
33 | * @param client The player invoking the request
34 | * @param otherClient The player that is being checked for an override
35 | *
36 | * @return The value of override this client has set on the other client
37 | */
38 | native int GetClientOverride(int client, int otherClient);
--------------------------------------------------------------------------------
/addons/sourcemod/scripting/voicemanager.sp:
--------------------------------------------------------------------------------
1 | #pragma semicolon 1
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #define PLUGIN_VERSION "1.0.2"
9 | #define VOICE_MANAGER_PREFIX "{green}[VOICE MANAGER]{default}"
10 | #define TABLE_NAME "voicemanager"
11 | #define STEAM_ID_BUF_SIZE 18
12 |
13 | #pragma newdecls required
14 |
15 | int g_iSelection[MAXPLAYERS+1] = {0};
16 | int g_iCookieSelection[MAXPLAYERS+1] = {-1};
17 | char g_sVolumeLevels[4][2] = { "<<", "<", ">", ">>" };
18 |
19 | char g_sDriver[64];
20 |
21 | // Cvars
22 | ConVar g_Cvar_VoiceEnable;
23 | ConVar g_Cvar_Database;
24 | ConVar g_Cvar_AllowSelfOverride;
25 |
26 | // Cookies
27 | Handle g_Cookie_GlobalOverride;
28 |
29 | // Handles
30 | Handle g_hDatabase;
31 |
32 | public Extension __ext_voicemanager =
33 | {
34 | name = "VoiceManager",
35 | file = "voicemanager.ext",
36 | required = 1,
37 | }
38 |
39 | public Plugin myinfo =
40 | {
41 | name = "[TF2/OF] Voice Manager",
42 | author = "Fraeven (Extension/Plugin) + Rowedahelicon (Plugin)",
43 | description = "Plugin for Voice Manager Extension",
44 | version = PLUGIN_VERSION,
45 | url = "https://www.scg.wtf"
46 | };
47 |
48 | public void OnPluginStart()
49 | {
50 | g_Cvar_VoiceEnable = FindConVar("vm_enable");
51 | g_Cvar_Database = CreateConVar("vm_database", "default", "Database configuration to use from databases.cfg");
52 | g_Cvar_AllowSelfOverride = CreateConVar("vm_allow_self", "0", "Allow players to override their own volume (recommended only for testing)");
53 |
54 | RegConsoleCmd("sm_vm", CommandBaseMenu);
55 | RegConsoleCmd("sm_voicemanager", CommandBaseMenu);
56 | RegConsoleCmd("sm_vmclear", Command_ClearClientOverrides);
57 |
58 | HookConVarChange(g_Cvar_VoiceEnable, OnVoiceEnableChanged);
59 |
60 | g_Cookie_GlobalOverride = RegClientCookie("voicemanager_cookie", "VM Global Toggle", CookieAccess_Public);
61 |
62 | SQL_OpenConnection();
63 | }
64 |
65 | public void SQL_OpenConnection()
66 | {
67 | char database[64];
68 | g_Cvar_Database.GetString(database, sizeof(database));
69 |
70 | if (SQL_CheckConfig(database))
71 | {
72 | SQL_TConnect(T_InitDatabase, database);
73 | }
74 | else
75 | {
76 | SetFailState("Failed to load database config %s from databases.cfg", database);
77 | }
78 | }
79 |
80 | public void T_InitDatabase(Handle owner, Handle hndl, const char[] error, any data)
81 | {
82 | if (hndl != INVALID_HANDLE)
83 | {
84 | g_hDatabase = hndl;
85 | }
86 | else
87 | {
88 | SetFailState("DATABASE FAILURE: %s", error);
89 | }
90 |
91 | SQL_ReadDriver(g_hDatabase, g_sDriver, sizeof(g_sDriver));
92 |
93 | if (!StrEqual(g_sDriver, "sqlite") && !StrEqual(g_sDriver, "mysql"))
94 | {
95 | SetFailState("Unsupported database driver %s", g_sDriver);
96 | }
97 |
98 | // Add voicemanager table if it does not exist
99 | char szQuery[511];
100 | Format(szQuery, sizeof(szQuery), "CREATE TABLE IF NOT EXISTS `%s` (adjuster VARCHAR(64), adjusted VARCHAR(64), level TINYINT, PRIMARY KEY (adjuster, adjusted))", TABLE_NAME);
101 |
102 | SQL_TQuery(g_hDatabase, SQLErrorCheckCallback, szQuery);
103 |
104 | for (int client = 1; client <= MaxClients; client++)
105 | {
106 | if (IsValidClient(client))
107 | {
108 | OnClientPostAdminCheck(client);
109 | OnClientCookiesCached(client);
110 | }
111 | }
112 | }
113 |
114 | public void OnVoiceEnableChanged(ConVar convar, const char[] oldValue, const char[] newValue)
115 | {
116 | RefreshActiveOverrides();
117 | }
118 |
119 | public void OnClientPostAdminCheck(int client)
120 | {
121 | if (!g_Cvar_VoiceEnable.BoolValue)
122 | {
123 | return;
124 | }
125 |
126 | char szSteamID[STEAM_ID_BUF_SIZE];
127 | GetClientAuthId(client, AuthId_SteamID64, szSteamID, sizeof(szSteamID));
128 |
129 | // Load adjustments from database
130 | char szQueryBuffer[255];
131 | FormatEx(szQueryBuffer, sizeof(szQueryBuffer), "SELECT adjusted, level FROM `%s` WHERE adjuster = '%s'", TABLE_NAME, szSteamID);
132 | SQL_TQuery(g_hDatabase, T_LoadAdjustments, szQueryBuffer, client);
133 | }
134 |
135 | public void T_LoadAdjustments(Handle owner, Handle hndl, const char[] error, int client)
136 | {
137 | if (hndl == INVALID_HANDLE || strlen(error) > 1)
138 | {
139 | LogError("[VoiceManager] Failed to load adjustments: %s", error);
140 | return;
141 | }
142 |
143 | if (SQL_GetRowCount(hndl))
144 | {
145 | while (SQL_FetchRow(hndl))
146 | {
147 | // Fetch adjusted steam ids with levels from SQL
148 | char adjustedSteamId[STEAM_ID_BUF_SIZE];
149 | SQL_FetchString(hndl, 0, adjustedSteamId, sizeof(adjustedSteamId));
150 |
151 | int level = SQL_FetchInt(hndl, 1);
152 |
153 | LoadPlayerAdjustment(client, adjustedSteamId, level);
154 | }
155 | }
156 |
157 | RefreshActiveOverrides();
158 | }
159 |
160 | public void OnClientCookiesCached(int client)
161 | {
162 | char sCookieValue[12];
163 | GetClientCookie(client, g_Cookie_GlobalOverride, sCookieValue, sizeof(sCookieValue));
164 |
165 | // This is because cookies default to empty and otherwise we use 0 as our lowest volume setting
166 | if (sCookieValue[0] != '\0')
167 | {
168 | int cookieValue = StringToInt(sCookieValue);
169 | g_iCookieSelection[client] = cookieValue;
170 | OnPlayerGlobalAdjust(client, cookieValue);
171 | }
172 | else
173 | {
174 | g_iCookieSelection[client] = -1;
175 | }
176 | }
177 |
178 | public void OnClientDisconnect(int client)
179 | {
180 | if (g_Cvar_VoiceEnable.BoolValue)
181 | {
182 | RefreshActiveOverrides();
183 | }
184 | }
185 |
186 | // Menus
187 | public Action CommandBaseMenu(int client, int args)
188 | {
189 | if (!g_Cvar_VoiceEnable.BoolValue)
190 | {
191 | return Plugin_Handled;
192 | }
193 |
194 | char playerBuffer[32];
195 | char stringBuffer[32];
196 | int playersAdjusted = 0;
197 |
198 | for (int otherClient = 1; otherClient <= MaxClients; otherClient++)
199 | {
200 | if (IsValidClient(otherClient) && (g_Cvar_AllowSelfOverride.BoolValue || otherClient != client) && !IsFakeClient(otherClient) && GetClientOverride(client, otherClient) >= 0)
201 | {
202 | playersAdjusted++;
203 | }
204 | }
205 |
206 | Format(playerBuffer, sizeof(playerBuffer), "Player Adjustment (%i active)", playersAdjusted);
207 |
208 | if (g_iCookieSelection[client] >= 0)
209 | {
210 | Format(stringBuffer, sizeof(stringBuffer), "Global Adjustment (%s)", g_sVolumeLevels[g_iCookieSelection[client]]);
211 | }
212 | else
213 | {
214 | Format(stringBuffer, sizeof(stringBuffer), "Global Adjustment");
215 | }
216 |
217 | Menu menu = new Menu(BaseMenuHandler);
218 | menu.SetTitle("Voice Manager");
219 | menu.AddItem("players", playerBuffer);
220 | menu.AddItem("global", stringBuffer);
221 | menu.AddItem("clear", "Clear Player Adjustments");
222 | menu.ExitButton = true;
223 | menu.Display(client, 20);
224 |
225 | return Plugin_Handled;
226 | }
227 |
228 | public int BaseMenuHandler(Menu menu, MenuAction action, int client, int param2)
229 | {
230 | if (action == MenuAction_Select)
231 | {
232 | char info[32];
233 | bool found = menu.GetItem(param2, info, sizeof(info));
234 |
235 | if (found)
236 | {
237 | if (StrEqual(info, "players"))
238 | {
239 | Menu players = new Menu(VoiceMenuHandler);
240 | players.SetTitle("Player Voice Manager");
241 |
242 | char id[4];
243 | char name[32];
244 |
245 | for (int otherClient = 1; otherClient <= MaxClients; otherClient++)
246 | {
247 | if (IsValidClient(otherClient) && (g_Cvar_AllowSelfOverride.BoolValue || otherClient != client) && !IsFakeClient(otherClient))
248 | {
249 | int override = GetClientOverride(client, otherClient);
250 |
251 | if (override >= 0)
252 | {
253 | Format(name, sizeof(name), "%N (%s)", otherClient, g_sVolumeLevels[override]);
254 | }
255 | else
256 | {
257 | Format(name, sizeof(name), "%N", otherClient);
258 | }
259 | IntToString(otherClient, id, sizeof(id));
260 | players.AddItem(id, name);
261 | }
262 | }
263 |
264 | if (players.ItemCount == 0)
265 | {
266 | CPrintToChat(client, "%s There are no players to adjust.", VOICE_MANAGER_PREFIX);
267 | return 0;
268 | }
269 |
270 | players.ExitButton = true;
271 | players.ExitBackButton = true;
272 | players.Display(client, 20);
273 | }
274 | else if (StrEqual(info, "global"))
275 | {
276 | Menu global = new Menu(GlobalVoiceVolumeHandler);
277 |
278 | global.SetTitle("Adjust global volume level");
279 | global.AddItem("3", g_iCookieSelection[client] == 3 ? "Louder *" : "Louder");
280 | global.AddItem("2", g_iCookieSelection[client] == 2 ? "Loud *" : "Loud");
281 | global.AddItem("-1", g_iCookieSelection[client] == -1 ? "Normal *" : "Normal");
282 | global.AddItem("1", g_iCookieSelection[client] == 1 ? "Quiet *" : "Quiet");
283 | global.AddItem("0", g_iCookieSelection[client] == 0 ? "Quieter *" : "Quieter");
284 |
285 | global.ExitButton = true;
286 | global.ExitBackButton = true;
287 | global.Display(client, 20);
288 | }
289 | else if (StrEqual(info, "clear"))
290 | {
291 | Menu clear = new Menu(ClearMenuHandler);
292 |
293 | clear.SetTitle("Remove all player volume adjustments?");
294 | clear.AddItem("1", "Yes");
295 | clear.AddItem("0", "No");
296 |
297 | clear.ExitButton = true;
298 | clear.Display(client, 20);
299 | }
300 | }
301 | }
302 | else if (action == MenuAction_Cancel)
303 | {
304 | if (param2 == MenuCancel_ExitBack)
305 | {
306 | CommandBaseMenu(client, 0);
307 | }
308 | }
309 | else if (action == MenuAction_End)
310 | {
311 | delete menu;
312 | }
313 |
314 | return 0;
315 | }
316 |
317 | public Action Command_ClearClientOverrides(int client, int args)
318 | {
319 | if (!g_Cvar_VoiceEnable.BoolValue)
320 | {
321 | return Plugin_Handled;
322 | }
323 |
324 | char szSteamID[STEAM_ID_BUF_SIZE];
325 | GetClientAuthId(client, AuthId_SteamID64, szSteamID, sizeof(szSteamID));
326 |
327 | char szQuery[511];
328 | FormatEx(szQuery, sizeof(szQuery), "DELETE FROM `%s` WHERE adjuster = '%s'", TABLE_NAME, szSteamID);
329 | SQL_TQuery(g_hDatabase, SQLErrorCheckCallback, szQuery);
330 |
331 | ClearClientOverrides(client);
332 |
333 | CPrintToChat(client, "%s You have cleared all of your voice overrides!", VOICE_MANAGER_PREFIX);
334 |
335 | return Plugin_Handled;
336 |
337 | }
338 |
339 | public void OnClearClientOverrides(int client)
340 | {
341 | char szSteamID[STEAM_ID_BUF_SIZE];
342 | GetClientAuthId(client, AuthId_SteamID64, szSteamID, sizeof(szSteamID));
343 |
344 | char szQuery[511];
345 | FormatEx(szQuery, sizeof(szQuery), "DELETE FROM `%s` WHERE adjuster = '%s'", TABLE_NAME, szSteamID);
346 | SQL_TQuery(g_hDatabase, SQLErrorCheckCallback, szQuery);
347 |
348 | ClearClientOverrides(client);
349 |
350 | CPrintToChat(client, "%s You have cleared all of your voice overrides!", VOICE_MANAGER_PREFIX);
351 | }
352 |
353 | //Handlers
354 | public int VoiceMenuHandler(Menu menu, MenuAction action, int param1, int param2)
355 | {
356 | if (action == MenuAction_Select)
357 | {
358 | char info[32];
359 | bool found = menu.GetItem(param2, info, sizeof(info));
360 | if (found)
361 | {
362 | int otherClient = StringToInt(info);
363 | int override = GetClientOverride(param1, otherClient);
364 |
365 | g_iSelection[param1] = otherClient;
366 |
367 | Menu sub_menu = new Menu(VoiceVolumeHandler);
368 |
369 | sub_menu.SetTitle("Adjust %N's volume level", otherClient);
370 | sub_menu.AddItem("3", override == 3 ? "Louder *" : "Louder");
371 | sub_menu.AddItem("2", override == 2 ? "Loud *" : "Loud");
372 | sub_menu.AddItem("-1", override == -1 ? "Normal *" : "Normal");
373 | sub_menu.AddItem("1", override == 1 ? "Quiet *" : "Quiet");
374 | sub_menu.AddItem("0", override == 0 ? "Quieter *" : "Quieter");
375 |
376 | sub_menu.ExitButton = true;
377 | sub_menu.ExitBackButton = true;
378 | sub_menu.Display(param1, 20);
379 | }
380 | }
381 | else if (action == MenuAction_Cancel)
382 | {
383 | if (param2 == MenuCancel_ExitBack)
384 | {
385 | CommandBaseMenu(param1, 0);
386 | }
387 | }
388 | else if (action == MenuAction_End)
389 | {
390 | delete menu;
391 | }
392 |
393 | return 0;
394 | }
395 |
396 | public int ClearMenuHandler(Menu menu, MenuAction action, int client, int param2)
397 | {
398 | if (action == MenuAction_Select)
399 | {
400 | char info[32];
401 | menu.GetItem(param2, info, sizeof(info));
402 | int yes = StringToInt(info);
403 | if (yes)
404 | {
405 | OnClearClientOverrides(client);
406 | }
407 | else
408 | {
409 | CommandBaseMenu(client, 0);
410 | }
411 | }
412 | else if (action == MenuAction_End)
413 | {
414 | delete menu;
415 | }
416 |
417 | return 0;
418 | }
419 |
420 | public int VoiceVolumeHandler(Menu menu, MenuAction action, int client, int param2)
421 | {
422 | if (action == MenuAction_Select)
423 | {
424 | char info[32];
425 | char setting[32];
426 | bool found = menu.GetItem(param2, info, sizeof(info), _, setting, sizeof(setting));
427 | if (found)
428 | {
429 | int level = StringToInt(info);
430 | if (!OnPlayerAdjustVolume(client, g_iSelection[client], level))
431 | {
432 | CPrintToChat(client, "%s Something went wrong, please try again soon!", VOICE_MANAGER_PREFIX);
433 | }
434 | else
435 | {
436 | CPrintToChat(client, "%s %N's level is now set to %s.", VOICE_MANAGER_PREFIX, g_iSelection[client], setting);
437 | }
438 |
439 | char adjuster[STEAM_ID_BUF_SIZE], adjusted[STEAM_ID_BUF_SIZE];
440 | GetClientAuthId(client, AuthId_SteamID64, adjuster, sizeof(adjuster));
441 | GetClientAuthId(client, AuthId_SteamID64, adjusted, sizeof(adjusted));
442 |
443 | char szQuery[511];
444 | if (level == -1)
445 | {
446 | FormatEx(szQuery, sizeof(szQuery), "DELETE FROM `%s` WHERE adjuster = '%s' AND adjusted = '%s'", TABLE_NAME, adjuster, adjusted);
447 | SQL_TQuery(g_hDatabase, SQLErrorCheckCallback, szQuery);
448 | }
449 | else
450 | {
451 | char driver[64];
452 | SQL_ReadDriver(g_hDatabase, driver, sizeof(driver));
453 |
454 | if (StrEqual(driver, "sqlite"))
455 | {
456 | FormatEx(szQuery, sizeof(szQuery), "\
457 | INSERT INTO `%s` (adjuster, adjusted, level)\
458 | VALUES ('%s', '%s', %d)\
459 | ON CONFLICT(adjuster, adjusted) DO UPDATE SET level = %d",
460 | TABLE_NAME, adjuster, adjusted, level, level);
461 | }
462 | else
463 | {
464 | FormatEx(szQuery, sizeof(szQuery), "\
465 | INSERT INTO `%s` (adjuster, adjusted, level)\
466 | VALUES ('%s', '%s', %d)\
467 | ON DUPLICATE KEY UPDATE level = %d",
468 | TABLE_NAME, adjuster, adjusted, level, level);
469 | }
470 |
471 | SQL_TQuery(g_hDatabase, SQLErrorCheckCallback, szQuery);
472 | }
473 | }
474 | }
475 | else if (action == MenuAction_End)
476 | {
477 | delete menu;
478 | }
479 |
480 | return 0;
481 | }
482 |
483 | public void SQLErrorCheckCallback(Handle owner, Handle hndl, const char[] error, any data)
484 | {
485 | if (hndl == INVALID_HANDLE || strlen(error) > 1)
486 | {
487 | LogError("[VoiceManager] SQL Error: %s", error);
488 | }
489 | }
490 |
491 | public int GlobalVoiceVolumeHandler(Menu menu, MenuAction action, int client, int param2)
492 | {
493 | if (action == MenuAction_Select)
494 | {
495 | char info[32];
496 | char setting[32];
497 | bool found = menu.GetItem(param2, info, sizeof(info), _, setting, sizeof(setting));
498 | if (found)
499 | {
500 | int volume = StringToInt(info);
501 | if (!OnPlayerGlobalAdjust(client, volume))
502 | {
503 | CPrintToChat(client, "%s Something went wrong, please try again soon!", VOICE_MANAGER_PREFIX);
504 | }
505 | else
506 | {
507 | CPrintToChat(client, "%s Global voice volume is now set to %s.", VOICE_MANAGER_PREFIX, setting);
508 | SetClientCookie(client, g_Cookie_GlobalOverride, info);
509 | g_iCookieSelection[client] = volume;
510 | }
511 | }
512 | }
513 | else if (action == MenuAction_Cancel)
514 | {
515 | if (param2 == MenuCancel_ExitBack)
516 | {
517 | CommandBaseMenu(client, 0);
518 | }
519 | }
520 | else if (action == MenuAction_End)
521 | {
522 | delete menu;
523 | }
524 |
525 | return 0;
526 | }
527 |
528 | stock bool IsValidClient(int client)
529 | {
530 | if (!client || client > MaxClients || client < 1 || !IsClientInGame(client))
531 | {
532 | return false;
533 | }
534 |
535 | return true;
536 | }
--------------------------------------------------------------------------------
/build.bat:
--------------------------------------------------------------------------------
1 | "C:\Program Files\Docker\Docker\DockerCli.exe" -SwitchLinuxEngine
2 | CALL .\build_ext.bat
3 | "C:\Program Files\Docker\Docker\DockerCli.exe" -SwitchWindowsEngine
4 | CALL .\build_ext_windows.bat
5 |
6 | CALL .\build_plugin.bat
--------------------------------------------------------------------------------
/build_ext.bat:
--------------------------------------------------------------------------------
1 | DEL addons\sourcemod\extensions\voicemanager.ext.2.sdk2013.so
2 | DEL addons\sourcemod\extensions\voicemanager.ext.2.tf2.so
3 |
4 | cd extension
5 | rmdir build /s /q
6 | docker compose build
7 | docker compose run extension-build --remove-orphans
8 |
9 | echo f | XCOPY build\voicemanager.ext.2.sdk2013\voicemanager.ext.2.sdk2013.so ..\addons\sourcemod\extensions\voicemanager.ext.2.sdk2013.so /Y
10 | echo f | XCOPY build\voicemanager.ext.2.tf2\voicemanager.ext.2.tf2.so ..\addons\sourcemod\extensions\voicemanager.ext.2.tf2.so /Y
11 | cd ..
--------------------------------------------------------------------------------
/build_ext.sh:
--------------------------------------------------------------------------------
1 | rm -rf ./addons/sourcemod/extensions
2 |
3 | cd extension
4 | rm -rf ./build
5 | docker compose build
6 | docker compose run extension-build --remove-orphans
7 |
8 | mkdir -p ../addons/sourcemod/extensions
9 | cp build/voicemanager.ext.2.sdk2013/voicemanager.ext.2.sdk2013.so ../addons/sourcemod/extensions/voicemanager.ext.2.sdk2013.so
10 | cp build/voicemanager.ext.2.tf2/voicemanager.ext.2.tf2.so ../addons/sourcemod/extensions/voicemanager.ext.2.tf2.so
--------------------------------------------------------------------------------
/build_ext_windows.bat:
--------------------------------------------------------------------------------
1 | DEL addons\sourcemod\extensions\voicemanager.ext.2.sdk2013.dll
2 | DEL addons\sourcemod\extensions\voicemanager.ext.2.tf2.dll
3 |
4 | cd extension
5 | rmdir .\build /s /q
6 | docker compose -f docker-compose.windows.yml build
7 | docker compose -f docker-compose.windows.yml run extension-build-windows --remove-orphans
8 |
9 | echo f | XCOPY build\voicemanager.ext.2.sdk2013\voicemanager.ext.2.sdk2013.dll ..\addons\sourcemod\extensions\voicemanager.ext.2.sdk2013.dll /Y
10 | echo f | XCOPY build\voicemanager.ext.2.tf2\voicemanager.ext.2.tf2.dll ..\addons\sourcemod\extensions\voicemanager.ext.2.tf2.dll /Y
11 | cd ..
--------------------------------------------------------------------------------
/build_plugin.bat:
--------------------------------------------------------------------------------
1 | rmdir .\addons\sourcemod\plugins /s /q
2 | mkdir addons\sourcemod\plugins
3 |
4 | spcomp.exe addons\sourcemod\scripting\voicemanager.sp -iaddons\sourcemod\scripting\include -o addons\sourcemod\plugins\voicemanager.smx
--------------------------------------------------------------------------------
/build_plugin.sh:
--------------------------------------------------------------------------------
1 | rm -rf ./addons/sourcemod/plugins
2 | mkdir addons/sourcemod/plugins
3 |
4 | ./spcomp addons/sourcemod/scripting/voicemanager.sp -i/usr/include -iaddons/sourcemod/scripting/include -o addons/sourcemod/plugins/voicemanager.smx
--------------------------------------------------------------------------------
/extension/AMBuildScript:
--------------------------------------------------------------------------------
1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python:
2 | import os, sys
3 |
4 | # Simple extensions do not need to modify this file.
5 |
6 | class SDK(object):
7 | def __init__(self, sdk, ext, aDef, name, platform, dir):
8 | self.folder = 'hl2sdk-' + dir
9 | self.envvar = sdk
10 | self.ext = ext
11 | self.code = aDef
12 | self.define = name
13 | self.platform = platform
14 | self.name = dir
15 | self.path = None # Actual path
16 |
17 | WinOnly = ['windows']
18 | WinLinux = ['windows', 'linux']
19 | WinLinuxMac = ['windows', 'linux', 'mac']
20 |
21 | PossibleSDKs = {
22 | 'episode1': SDK('HL2SDK', '1.ep1', '1', 'EPISODEONE', WinLinux, 'episode1'),
23 | 'ep2': SDK('HL2SDKOB', '2.ep2', '3', 'ORANGEBOX', WinLinux, 'orangebox'),
24 | 'css': SDK('HL2SDKCSS', '2.css', '6', 'CSS', WinLinuxMac, 'css'),
25 | 'hl2dm': SDK('HL2SDKHL2DM', '2.hl2dm', '7', 'HL2DM', WinLinuxMac, 'hl2dm'),
26 | 'dods': SDK('HL2SDKDODS', '2.dods', '8', 'DODS', WinLinuxMac, 'dods'),
27 | 'sdk2013': SDK('HL2SDK2013', '2.sdk2013', '9', 'SDK2013', WinLinuxMac, 'sdk2013'),
28 | 'tf2': SDK('HL2SDKTF2', '2.tf2', '11', 'TF2', WinLinuxMac, 'tf2'),
29 | 'l4d': SDK('HL2SDKL4D', '2.l4d', '12', 'LEFT4DEAD', WinLinuxMac, 'l4d'),
30 | 'nucleardawn': SDK('HL2SDKND', '2.nd', '13', 'NUCLEARDAWN', WinLinuxMac, 'nucleardawn'),
31 | 'l4d2': SDK('HL2SDKL4D2', '2.l4d2', '15', 'LEFT4DEAD2', WinLinuxMac, 'l4d2'),
32 | 'darkm': SDK('HL2SDK-DARKM', '2.darkm', '2', 'DARKMESSIAH', WinOnly, 'darkm'),
33 | 'swarm': SDK('HL2SDK-SWARM', '2.swarm', '16', 'ALIENSWARM', WinOnly, 'swarm'),
34 | 'bgt': SDK('HL2SDK-BGT', '2.bgt', '4', 'BLOODYGOODTIME', WinOnly, 'bgt'),
35 | 'eye': SDK('HL2SDK-EYE', '2.eye', '5', 'EYE', WinOnly, 'eye'),
36 | 'csgo': SDK('HL2SDKCSGO', '2.csgo', '21', 'CSGO', WinLinuxMac, 'csgo'),
37 | 'portal2': SDK('HL2SDKPORTAL2', '2.portal2', '17', 'PORTAL2', [], 'portal2'),
38 | 'blade': SDK('HL2SDKBLADE', '2.blade', '18', 'BLADE', WinLinux, 'blade'),
39 | 'insurgency': SDK('HL2SDKINSURGENCY', '2.insurgency', '19', 'INSURGENCY', WinLinuxMac, 'insurgency'),
40 | 'contagion': SDK('HL2SDKCONTAGION', '2.contagion', '14', 'CONTAGION', WinOnly, 'contagion'),
41 | 'bms': SDK('HL2SDKBMS', '2.bms', '10', 'BMS', WinLinux, 'bms'),
42 | 'doi': SDK('HL2SDKDOI', '2.doi', '20', 'DOI', WinLinuxMac, 'doi'),
43 | }
44 |
45 | def ResolveEnvPath(env, folder):
46 | if env in os.environ:
47 | path = os.environ[env]
48 | if os.path.isdir(path):
49 | return path
50 | return None
51 |
52 | head = os.getcwd()
53 | oldhead = None
54 | while head != None and head != oldhead:
55 | path = os.path.join(head, folder)
56 | if os.path.isdir(path):
57 | return path
58 | oldhead = head
59 | head, tail = os.path.split(head)
60 |
61 | return None
62 |
63 | def Normalize(path):
64 | return os.path.abspath(os.path.normpath(path))
65 |
66 | class ExtensionConfig(object):
67 | def __init__(self):
68 | self.sdks = {}
69 | self.binaries = []
70 | self.extensions = []
71 | self.generated_headers = None
72 | self.mms_root = None
73 | self.sm_root = None
74 |
75 | @property
76 | def tag(self):
77 | if builder.options.debug == '1':
78 | return 'Debug'
79 | return 'Release'
80 |
81 | def detectSDKs(self):
82 | sdk_list = builder.options.sdks.split(',')
83 | use_all = sdk_list[0] == 'all'
84 | use_present = sdk_list[0] == 'present'
85 |
86 | for sdk_name in PossibleSDKs:
87 | sdk = PossibleSDKs[sdk_name]
88 | if builder.target_platform in sdk.platform:
89 | if builder.options.hl2sdk_root:
90 | sdk_path = os.path.join(builder.options.hl2sdk_root, sdk.folder)
91 | else:
92 | sdk_path = ResolveEnvPath(sdk.envvar, sdk.folder)
93 | if sdk_path is None or not os.path.isdir(sdk_path):
94 | if use_all or sdk_name in sdk_list:
95 | raise Exception('Could not find a valid path for {0}'.format(sdk.envvar))
96 | continue
97 | if use_all or use_present or sdk_name in sdk_list:
98 | sdk.path = Normalize(sdk_path)
99 | self.sdks[sdk_name] = sdk
100 |
101 | if len(self.sdks) < 1:
102 | raise Exception('At least one SDK must be available.')
103 |
104 | if builder.options.sm_path:
105 | self.sm_root = builder.options.sm_path
106 | else:
107 | self.sm_root = ResolveEnvPath('SOURCEMOD111', 'sourcemod-1.11')
108 | if not self.sm_root:
109 | self.sm_root = ResolveEnvPath('SOURCEMOD', 'sourcemod')
110 | if not self.sm_root:
111 | self.sm_root = ResolveEnvPath('SOURCEMOD_DEV', 'sourcemod-central')
112 |
113 | if not self.sm_root or not os.path.isdir(self.sm_root):
114 | raise Exception('Could not find a source copy of SourceMod')
115 | self.sm_root = Normalize(self.sm_root)
116 |
117 | if builder.options.mms_path:
118 | self.mms_root = builder.options.mms_path
119 | else:
120 | self.mms_root = ResolveEnvPath('MMSOURCE111', 'mmsource-1.11')
121 | if not self.mms_root:
122 | self.mms_root = ResolveEnvPath('MMSOURCE', 'metamod-source')
123 | if not self.mms_root:
124 | self.mms_root = ResolveEnvPath('MMSOURCE_DEV', 'mmsource-central')
125 |
126 | if not self.mms_root or not os.path.isdir(self.mms_root):
127 | raise Exception('Could not find a source copy of Metamod:Source')
128 | self.mms_root = Normalize(self.mms_root)
129 |
130 | def configure(self):
131 | cxx = builder.DetectCompilers()
132 |
133 | if cxx.like('gcc'):
134 | self.configure_gcc(cxx)
135 | elif cxx.vendor == 'msvc':
136 | self.configure_msvc(cxx)
137 |
138 | # Optimization
139 | if builder.options.opt == '1':
140 | cxx.defines += ['NDEBUG']
141 |
142 | # Debugging
143 | if builder.options.debug == '1':
144 | cxx.defines += ['DEBUG', '_DEBUG']
145 |
146 | # Platform-specifics
147 | if builder.target_platform == 'linux':
148 | self.configure_linux(cxx)
149 | elif builder.target_platform == 'mac':
150 | self.configure_mac(cxx)
151 | elif builder.target_platform == 'windows':
152 | self.configure_windows(cxx)
153 |
154 | # Finish up.
155 | cxx.includes += [
156 | os.path.join(self.sm_root, 'public')
157 | ]
158 |
159 | def configure_gcc(self, cxx):
160 | cxx.defines += [
161 | 'stricmp=strcasecmp',
162 | '_stricmp=strcasecmp',
163 | '_snprintf=snprintf',
164 | '_vsnprintf=vsnprintf',
165 | 'HAVE_STDINT_H',
166 | 'GNUC',
167 | ]
168 | cxx.cflags += [
169 | '-pipe',
170 | '-fno-strict-aliasing',
171 | '-Wall',
172 | '-Wno-unused',
173 | '-Wno-switch',
174 | '-Wno-array-bounds',
175 | '-msse',
176 | '-m32',
177 | '-fvisibility=hidden'
178 | ]
179 | cxx.cxxflags += [
180 | '-std=c++14',
181 | '-fno-exceptions',
182 | '-fno-threadsafe-statics',
183 | '-Wno-non-virtual-dtor',
184 | '-Wno-overloaded-virtual',
185 | '-fvisibility-inlines-hidden'
186 | ]
187 | cxx.linkflags += ['-m32']
188 |
189 | have_gcc = cxx.vendor == 'gcc'
190 | have_clang = cxx.vendor == 'clang'
191 | if cxx.version >= 'clang-3.6':
192 | cxx.cxxflags += ['-Wno-inconsistent-missing-override']
193 | if have_clang or (cxx.version >= 'gcc-4.6'):
194 | cxx.cflags += ['-Wno-narrowing']
195 | if have_clang or (cxx.version >= 'gcc-4.7'):
196 | cxx.cxxflags += ['-Wno-delete-non-virtual-dtor']
197 | if cxx.version >= 'gcc-4.8':
198 | cxx.cflags += ['-Wno-unused-result']
199 |
200 | if have_clang:
201 | cxx.cxxflags += ['-Wno-implicit-exception-spec-mismatch']
202 | if cxx.version >= 'apple-clang-5.1' or cxx.version >= 'clang-3.4':
203 | cxx.cxxflags += ['-Wno-deprecated-register']
204 | else:
205 | cxx.cxxflags += ['-Wno-deprecated']
206 | cxx.cflags += ['-Wno-sometimes-uninitialized']
207 |
208 | if have_gcc:
209 | cxx.cflags += ['-mfpmath=sse']
210 |
211 | if builder.options.opt == '1':
212 | cxx.cflags += ['-O3']
213 |
214 | def configure_msvc(self, cxx):
215 | if builder.options.debug == '1':
216 | cxx.cflags += ['/MTd']
217 | cxx.linkflags += ['/NODEFAULTLIB:libcmt']
218 | else:
219 | cxx.cflags += ['/MT']
220 | cxx.defines += [
221 | '_CRT_SECURE_NO_DEPRECATE',
222 | '_CRT_SECURE_NO_WARNINGS',
223 | '_CRT_NONSTDC_NO_DEPRECATE',
224 | '_ITERATOR_DEBUG_LEVEL=0',
225 | ]
226 | cxx.cflags += [
227 | '/W3',
228 | ]
229 | cxx.cxxflags += [
230 | '/std:c++17',
231 | '/EHsc',
232 | '/GR-',
233 | '/TP',
234 | ]
235 | cxx.linkflags += [
236 | '/MACHINE:X86',
237 | 'kernel32.lib',
238 | 'user32.lib',
239 | 'gdi32.lib',
240 | 'winspool.lib',
241 | 'comdlg32.lib',
242 | 'advapi32.lib',
243 | 'shell32.lib',
244 | 'ole32.lib',
245 | 'oleaut32.lib',
246 | 'uuid.lib',
247 | 'odbc32.lib',
248 | 'odbccp32.lib',
249 | 'legacy_stdio_definitions.lib',
250 | 'C:\\extension\\include\\opus1.3.1.lib'
251 | ]
252 |
253 | if builder.options.opt == '1':
254 | cxx.cflags += ['/Ox', '/Zo']
255 | cxx.linkflags += ['/OPT:ICF', '/OPT:REF']
256 |
257 | if builder.options.debug == '1':
258 | cxx.cflags += ['/Od', '/RTC1']
259 |
260 | # This needs to be after our optimization flags which could otherwise disable it.
261 | # Don't omit the frame pointer.
262 | cxx.cflags += ['/Oy-']
263 |
264 | def configure_linux(self, cxx):
265 | cxx.defines += ['_LINUX', 'POSIX']
266 | cxx.linkflags += ['-Wl,--exclude-libs,ALL', '/usr/local/lib/libopus.a']
267 | if cxx.vendor == 'gcc':
268 | cxx.linkflags += ['-static-libgcc']
269 | elif cxx.vendor == 'clang':
270 | cxx.linkflags += ['-lgcc_eh']
271 | cxx.linkflags += ['-lm']
272 |
273 | def configure_mac(self, cxx):
274 | cxx.defines += ['OSX', '_OSX', 'POSIX']
275 | cxx.cflags += ['-mmacosx-version-min=10.5']
276 | cxx.linkflags += [
277 | '-mmacosx-version-min=10.5',
278 | '-arch', 'i386',
279 | '-lstdc++',
280 | '-stdlib=libstdc++',
281 | ]
282 | cxx.cxxflags += ['-stdlib=libstdc++']
283 |
284 | def configure_windows(self, cxx):
285 | cxx.defines += ['WIN32', '_WINDOWS']
286 |
287 | def ConfigureForExtension(self, context, compiler):
288 | compiler.cxxincludes += [
289 | os.path.join(context.currentSourcePath),
290 | os.path.join(context.currentSourcePath, 'include'),
291 | os.path.join(context.currentSourcePath, 'sdk'),
292 | os.path.join(self.sm_root, 'public'),
293 | os.path.join(self.sm_root, 'public', 'extensions'),
294 | os.path.join(self.sm_root, 'sourcepawn', 'include'),
295 | os.path.join(self.sm_root, 'public', 'amtl', 'amtl'),
296 | os.path.join(self.sm_root, 'public', 'amtl'),
297 | ]
298 | return compiler
299 |
300 | def ConfigureForHL2(self, binary, sdk):
301 | compiler = binary.compiler
302 |
303 | if sdk.name == 'episode1':
304 | mms_path = os.path.join(self.mms_root, 'core-legacy')
305 | else:
306 | mms_path = os.path.join(self.mms_root, 'core')
307 |
308 | compiler.cxxincludes += [
309 | os.path.join(mms_path),
310 | os.path.join(mms_path, 'sourcehook'),
311 | ]
312 |
313 | defines = ['SE_' + PossibleSDKs[i].define + '=' + PossibleSDKs[i].code for i in PossibleSDKs]
314 | compiler.defines += defines
315 |
316 | paths = [
317 | ['public'],
318 | ['public', 'engine'],
319 | ['public', 'mathlib'],
320 | ['public', 'vstdlib'],
321 | ['public', 'tier0'],
322 | ['public', 'tier1']
323 | ]
324 | if sdk.name == 'episode1' or sdk.name == 'darkm':
325 | paths.append(['public', 'dlls'])
326 | paths.append(['game_shared'])
327 | else:
328 | paths.append(['public', 'game', 'server'])
329 | paths.append(['public', 'toolframework'])
330 | paths.append(['game', 'shared'])
331 | paths.append(['common'])
332 |
333 | compiler.defines += ['SOURCE_ENGINE=' + sdk.code]
334 |
335 | if sdk.name in ['sdk2013']:
336 | compiler.defines += ['REPLAY_ENABLED']
337 |
338 | if sdk.name in ['sdk2013', 'bms'] and compiler.like('gcc'):
339 | # The 2013 SDK already has these in public/tier0/basetypes.h
340 | compiler.defines.remove('stricmp=strcasecmp')
341 | compiler.defines.remove('_stricmp=strcasecmp')
342 | compiler.defines.remove('_snprintf=snprintf')
343 | compiler.defines.remove('_vsnprintf=vsnprintf')
344 |
345 | if compiler.like('msvc'):
346 | compiler.defines += ['COMPILER_MSVC', 'COMPILER_MSVC32']
347 | else:
348 | compiler.defines += ['COMPILER_GCC']
349 |
350 | # For everything after Swarm, this needs to be defined for entity networking
351 | # to work properly with sendprop value changes.
352 | if sdk.name in ['blade', 'insurgency', 'doi', 'csgo']:
353 | compiler.defines += ['NETWORK_VARS_ENABLED']
354 |
355 | if sdk.name in ['css', 'hl2dm', 'dods', 'sdk2013', 'bms', 'tf2', 'l4d', 'nucleardawn', 'l4d2']:
356 | if builder.target_platform in ['linux', 'mac']:
357 | compiler.defines += ['NO_HOOK_MALLOC', 'NO_MALLOC_OVERRIDE']
358 |
359 | if sdk.name == 'csgo' and builder.target_platform == 'linux':
360 | compiler.linkflags += ['-lstdc++']
361 |
362 | for path in paths:
363 | compiler.cxxincludes += [os.path.join(sdk.path, *path)]
364 |
365 | if builder.target_platform == 'linux':
366 | if sdk.name == 'episode1':
367 | lib_folder = os.path.join(sdk.path, 'linux_sdk')
368 | elif sdk.name in ['sdk2013', 'bms']:
369 | lib_folder = os.path.join(sdk.path, 'lib', 'public', 'linux32')
370 | else:
371 | lib_folder = os.path.join(sdk.path, 'lib', 'linux')
372 | elif builder.target_platform == 'mac':
373 | if sdk.name in ['sdk2013', 'bms']:
374 | lib_folder = os.path.join(sdk.path, 'lib', 'public', 'osx32')
375 | else:
376 | lib_folder = os.path.join(sdk.path, 'lib', 'mac')
377 |
378 | if builder.target_platform in ['linux', 'mac']:
379 | if sdk.name in ['sdk2013', 'bms']:
380 | compiler.postlink += [
381 | compiler.Dep(os.path.join(lib_folder, 'tier1.a')),
382 | compiler.Dep(os.path.join(lib_folder, 'mathlib.a'))
383 | ]
384 | else:
385 | compiler.postlink += [
386 | compiler.Dep(os.path.join(lib_folder, 'tier1_i486.a')),
387 | compiler.Dep(os.path.join(lib_folder, 'mathlib_i486.a'))
388 | ]
389 |
390 | if sdk.name in ['blade', 'insurgency', 'doi', 'csgo']:
391 | compiler.postlink += [compiler.Dep(os.path.join(lib_folder, 'interfaces_i486.a'))]
392 |
393 | dynamic_libs = []
394 | if builder.target_platform == 'linux':
395 | if sdk.name in ['css', 'hl2dm', 'dods', 'tf2', 'sdk2013', 'bms', 'nucleardawn', 'l4d2', 'insurgency', 'doi']:
396 | dynamic_libs = ['libtier0_srv.so', 'libvstdlib_srv.so']
397 | elif sdk.name in ['l4d', 'blade', 'insurgency', 'doi', 'csgo']:
398 | dynamic_libs = ['libtier0.so', 'libvstdlib.so']
399 | else:
400 | dynamic_libs = ['tier0_i486.so', 'vstdlib_i486.so']
401 | elif builder.target_platform == 'mac':
402 | compiler.linkflags.append('-liconv')
403 | dynamic_libs = ['libtier0.dylib', 'libvstdlib.dylib']
404 | elif builder.target_platform == 'windows':
405 | libs = ['tier0', 'tier1', 'vstdlib', 'mathlib']
406 | if sdk.name in ['swarm', 'blade', 'insurgency', 'doi', 'csgo']:
407 | libs.append('interfaces')
408 | for lib in libs:
409 | lib_path = os.path.join(sdk.path, 'lib', 'public', lib) + '.lib'
410 | compiler.linkflags.append(compiler.Dep(lib_path))
411 |
412 | for library in dynamic_libs:
413 | source_path = os.path.join(lib_folder, library)
414 | output_path = os.path.join(binary.localFolder, library)
415 |
416 | def make_linker(source_path, output_path):
417 | def link(context, binary):
418 | cmd_node, (output,) = context.AddSymlink(source_path, output_path)
419 | return output
420 | return link
421 |
422 | linker = make_linker(source_path, output_path)
423 | compiler.linkflags[0:0] = [compiler.Dep(library, linker)]
424 |
425 | return binary
426 |
427 | def HL2Library(self, context, name, sdk):
428 | binary = context.compiler.Library(name)
429 | self.ConfigureForExtension(context, binary.compiler)
430 | return self.ConfigureForHL2(binary, sdk)
431 |
432 | def HL2Project(self, context, name):
433 | project = context.compiler.LibraryProject(name)
434 | self.ConfigureForExtension(context, project.compiler)
435 | return project
436 |
437 | def HL2Config(self, project, name, sdk):
438 | binary = project.Configure(name, '{0} - {1}'.format(self.tag, sdk.name))
439 | return self.ConfigureForHL2(binary, sdk)
440 |
441 | Extension = ExtensionConfig()
442 | Extension.detectSDKs()
443 | Extension.configure()
444 |
445 | # Add additional buildscripts here
446 | BuildScripts = [
447 | 'AMBuilder',
448 | ]
449 |
450 | if builder.backend == 'amb2':
451 | BuildScripts += [
452 | 'PackageScript',
453 | ]
454 |
455 | builder.RunBuildScripts(BuildScripts, { 'Extension': Extension})
456 |
--------------------------------------------------------------------------------
/extension/AMBuilder:
--------------------------------------------------------------------------------
1 | # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python:
2 | import os, sys
3 |
4 | projectName = 'voicemanager'
5 |
6 | # smsdk_ext.cpp will be automatically added later
7 | sourceFiles = [
8 | 'extension.cpp',
9 | 'voicemanager.cpp',
10 | 'voicemanagerclientstate.cpp',
11 | 'CDetour/detours.cpp',
12 | 'asm/asm.c'
13 | ]
14 |
15 | ###############
16 | # Make sure to edit PackageScript, which copies your files to their appropriate locations
17 | # Simple extensions do not need to modify past this point.
18 |
19 | project = Extension.HL2Project(builder, projectName + '.ext')
20 |
21 | if os.path.isfile(os.path.join(builder.currentSourcePath, 'sdk', 'smsdk_ext.cpp')):
22 | # Use the copy included in the project
23 | project.sources += [os.path.join('sdk', 'smsdk_ext.cpp')]
24 | else:
25 | # Use the copy included with SM 1.6 and newer
26 | project.sources += [os.path.join(Extension.sm_root, 'public', 'smsdk_ext.cpp')]
27 |
28 | project.sources += sourceFiles
29 |
30 | for sdk_name in Extension.sdks:
31 | if sdk_name in ['sdk2013'] and builder.target_platform == 'linux':
32 | project.sources += ['libc_compat.cpp']
33 |
34 | sdk = Extension.sdks[sdk_name]
35 |
36 | binary = Extension.HL2Config(project, projectName + '.ext.' + sdk.ext, sdk)
37 |
38 | Extension.extensions = builder.Add(project)
39 |
--------------------------------------------------------------------------------
/extension/CDetour/detourhelpers.h:
--------------------------------------------------------------------------------
1 | /**
2 | * vim: set ts=4 :
3 | * =============================================================================
4 | * SourceMod
5 | * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved.
6 | * =============================================================================
7 | *
8 | * This program is free software; you can redistribute it and/or modify it under
9 | * the terms of the GNU General Public License, version 3.0, as published by the
10 | * Free Software Foundation.
11 | *
12 | * This program is distributed in the hope that it will be useful, but WITHOUT
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 | * details.
16 | *
17 | * You should have received a copy of the GNU General Public License along with
18 | * this program. If not, see .
19 | *
20 | * As a special exception, AlliedModders LLC gives you permission to link the
21 | * code of this program (as well as its derivative works) to "Half-Life 2," the
22 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
23 | * by the Valve Corporation. You must obey the GNU General Public License in
24 | * all respects for all other code used. Additionally, AlliedModders LLC grants
25 | * this exception to all derivative works. AlliedModders LLC defines further
26 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
27 | * or .
28 | *
29 | * Version: $Id: detourhelpers.h 248 2008-08-27 00:56:22Z pred $
30 | */
31 |
32 | #ifndef _INCLUDE_SOURCEMOD_DETOURHELPERS_H_
33 | #define _INCLUDE_SOURCEMOD_DETOURHELPERS_H_
34 |
35 | #if defined PLATFORM_POSIX
36 | #include
37 | #define PAGE_SIZE 4096
38 | #define ALIGN(ar) ((long)ar & ~(PAGE_SIZE-1))
39 | #define PAGE_EXECUTE_READWRITE PROT_READ|PROT_WRITE|PROT_EXEC
40 | #endif
41 |
42 | struct patch_t
43 | {
44 | patch_t()
45 | {
46 | patch[0] = 0;
47 | bytes = 0;
48 | }
49 | unsigned char patch[20];
50 | size_t bytes;
51 | };
52 |
53 | inline void ProtectMemory(void *addr, int length, int prot)
54 | {
55 | #if defined PLATFORM_POSIX
56 | void *addr2 = (void *)ALIGN(addr);
57 | mprotect(addr2, sysconf(_SC_PAGESIZE), prot);
58 | #elif defined PLATFORM_WINDOWS
59 | DWORD old_prot;
60 | VirtualProtect(addr, length, prot, &old_prot);
61 | #endif
62 | }
63 |
64 | inline void SetMemPatchable(void *address, size_t size)
65 | {
66 | ProtectMemory(address, (int)size, PAGE_EXECUTE_READWRITE);
67 | }
68 |
69 | inline void DoGatePatch(unsigned char *target, void *callback)
70 | {
71 | SetMemPatchable(target, 20);
72 |
73 | target[0] = 0xFF; /* JMP */
74 | target[1] = 0x25; /* MEM32 */
75 | *(void **)(&target[2]) = callback;
76 | }
77 |
78 | inline void ApplyPatch(void *address, int offset, const patch_t *patch, patch_t *restore)
79 | {
80 | ProtectMemory(address, 20, PAGE_EXECUTE_READWRITE);
81 |
82 | unsigned char *addr = (unsigned char *)address + offset;
83 | if (restore)
84 | {
85 | for (size_t i=0; ibytes; i++)
86 | {
87 | restore->patch[i] = addr[i];
88 | }
89 | restore->bytes = patch->bytes;
90 | }
91 |
92 | for (size_t i=0; ibytes; i++)
93 | {
94 | addr[i] = patch->patch[i];
95 | }
96 | }
97 |
98 | #endif //_INCLUDE_SOURCEMOD_DETOURHELPERS_H_
99 |
--------------------------------------------------------------------------------
/extension/CDetour/detours.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | * vim: set ts=4 :
3 | * =============================================================================
4 | * SourceMod
5 | * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
6 | * =============================================================================
7 | *
8 | * This program is free software; you can redistribute it and/or modify it under
9 | * the terms of the GNU General Public License, version 3.0, as published by the
10 | * Free Software Foundation.
11 | *
12 | * This program is distributed in the hope that it will be useful, but WITHOUT
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 | * details.
16 | *
17 | * You should have received a copy of the GNU General Public License along with
18 | * this program. If not, see .
19 | *
20 | * As a special exception, AlliedModders LLC gives you permission to link the
21 | * code of this program (as well as its derivative works) to "Half-Life 2," the
22 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
23 | * by the Valve Corporation. You must obey the GNU General Public License in
24 | * all respects for all other code used. Additionally, AlliedModders LLC grants
25 | * this exception to all derivative works. AlliedModders LLC defines further
26 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
27 | * or .
28 | *
29 | * Version: $Id: detours.cpp 248 2008-08-27 00:56:22Z pred $
30 | */
31 |
32 | #include "detours.h"
33 | #include
34 |
35 | ISourcePawnEngine *CDetourManager::spengine = NULL;
36 | IGameConfig *CDetourManager::gameconf = NULL;
37 |
38 | void CDetourManager::Init(ISourcePawnEngine *spengine, IGameConfig *gameconf)
39 | {
40 | CDetourManager::spengine = spengine;
41 | CDetourManager::gameconf = gameconf;
42 | }
43 |
44 | CDetour *CDetourManager::CreateDetour(void *callbackfunction, void **trampoline, const char *signame)
45 | {
46 | CDetour *detour = new CDetour(callbackfunction, trampoline, signame);
47 | if (detour)
48 | {
49 | if (!detour->Init(spengine, gameconf))
50 | {
51 | delete detour;
52 | return NULL;
53 | }
54 |
55 | return detour;
56 | }
57 |
58 | return NULL;
59 | }
60 |
61 | CDetour::CDetour(void *callbackfunction, void **trampoline, const char *signame)
62 | {
63 | enabled = false;
64 | detoured = false;
65 | detour_address = NULL;
66 | detour_trampoline = NULL;
67 | this->signame = signame;
68 | this->detour_callback = callbackfunction;
69 | spengine = NULL;
70 | gameconf = NULL;
71 | this->trampoline = trampoline;
72 | }
73 |
74 | bool CDetour::Init(ISourcePawnEngine *spengine, IGameConfig *gameconf)
75 | {
76 | this->spengine = spengine;
77 | this->gameconf = gameconf;
78 |
79 | if (!CreateDetour())
80 | {
81 | enabled = false;
82 | return enabled;
83 | }
84 |
85 | enabled = true;
86 |
87 | return enabled;
88 | }
89 |
90 | void CDetour::Destroy()
91 | {
92 | DeleteDetour();
93 | delete this;
94 | }
95 |
96 | bool CDetour::IsEnabled()
97 | {
98 | return enabled;
99 | }
100 |
101 | bool CDetour::CreateDetour()
102 | {
103 | if (!gameconf->GetMemSig(signame, &detour_address))
104 | {
105 | g_pSM->LogError(myself, "Could not locate %s - Disabling detour", signame);
106 | return false;
107 | }
108 |
109 | if (!detour_address)
110 | {
111 | g_pSM->LogError(myself, "Sigscan for %s failed - Disabling detour to prevent crashes", signame);
112 | return false;
113 | }
114 |
115 | detour_restore.bytes = copy_bytes((unsigned char *)detour_address, NULL, OP_JMP_SIZE+1);
116 |
117 | /* First, save restore bits */
118 | for (size_t i=0; iAllocatePageMemory(CodeSize);
147 | spengine->SetReadWrite(wr.outbase);
148 | wr.outptr = wr.outbase;
149 | detour_trampoline = wr.outbase;
150 | goto jit_rewind;
151 | }
152 |
153 | spengine->SetReadExecute(wr.outbase);
154 |
155 | *trampoline = detour_trampoline;
156 |
157 | return true;
158 | }
159 |
160 | void CDetour::DeleteDetour()
161 | {
162 | if (detoured)
163 | {
164 | DisableDetour();
165 | }
166 |
167 | if (detour_trampoline)
168 | {
169 | /* Free the allocated trampoline memory */
170 | spengine->FreePageMemory(detour_trampoline);
171 | detour_trampoline = NULL;
172 | }
173 | }
174 |
175 | void CDetour::EnableDetour()
176 | {
177 | if (!detoured)
178 | {
179 | DoGatePatch((unsigned char *)detour_address, &detour_callback);
180 | detoured = true;
181 | }
182 | }
183 |
184 | void CDetour::DisableDetour()
185 | {
186 | if (detoured)
187 | {
188 | /* Remove the patch */
189 | ApplyPatch(detour_address, 0, &detour_restore, NULL);
190 | detoured = false;
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/extension/CDetour/detours.h:
--------------------------------------------------------------------------------
1 | /**
2 | * vim: set ts=4 :
3 | * =============================================================================
4 | * SourceMod
5 | * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
6 | * =============================================================================
7 | *
8 | * This program is free software; you can redistribute it and/or modify it under
9 | * the terms of the GNU General Public License, version 3.0, as published by the
10 | * Free Software Foundation.
11 | *
12 | * This program is distributed in the hope that it will be useful, but WITHOUT
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 | * details.
16 | *
17 | * You should have received a copy of the GNU General Public License along with
18 | * this program. If not, see .
19 | *
20 | * As a special exception, AlliedModders LLC gives you permission to link the
21 | * code of this program (as well as its derivative works) to "Half-Life 2," the
22 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
23 | * by the Valve Corporation. You must obey the GNU General Public License in
24 | * all respects for all other code used. Additionally, AlliedModders LLC grants
25 | * this exception to all derivative works. AlliedModders LLC defines further
26 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
27 | * or .
28 | *
29 | * Version: $Id: detours.h 257 2008-09-23 03:12:13Z pred $
30 | */
31 |
32 | #ifndef _INCLUDE_SOURCEMOD_DETOURS_H_
33 | #define _INCLUDE_SOURCEMOD_DETOURS_H_
34 |
35 | #include "../extension.h"
36 | #include
37 | #include
38 | #include "detourhelpers.h"
39 |
40 | /**
41 | * CDetours class for SourceMod Extensions by pRED*
42 | * detourhelpers.h entirely stolen from CSS:DM and were written by BAILOPAN (I assume).
43 | * asm.h/c from devmaster.net (thanks cybermind) edited by pRED* to handle gcc -fPIC thunks correctly
44 | * Concept by Nephyrin Zey (http://www.doublezen.net/) and Windows Detour Library (http://research.microsoft.com/sn/detours/)
45 | * Member function pointer ideas by Don Clugston (http://www.codeproject.com/cpp/FastDelegate.asp)
46 | */
47 |
48 | #define DETOUR_MEMBER_CALL(name) (this->*name##_Actual)
49 | #define DETOUR_STATIC_CALL(name) (name##_Actual)
50 |
51 | #define DETOUR_DECL_STATIC0(name, ret) \
52 | ret (*name##_Actual)(void) = NULL; \
53 | ret name(void)
54 |
55 | #define DETOUR_DECL_STATIC1(name, ret, p1type, p1name) \
56 | ret (*name##_Actual)(p1type) = NULL; \
57 | ret name(p1type p1name)
58 |
59 | #define DETOUR_DECL_STATIC2(name, ret, p1type, p1name, p2type, p2name) \
60 | ret (*name##_Actual)(p1type, p2type) = NULL; \
61 | ret name(p1type p1name, p2type p2name)
62 |
63 |
64 | #define DETOUR_DECL_STATIC3(name, ret, p1type, p1name, p2type, p2name, p3type, p3name) \
65 | ret (*name##_Actual)(p1type, p2type, p3type) = NULL; \
66 | ret name(p1type p1name, p2type p2name, p3type p3name)
67 |
68 | #define DETOUR_DECL_STATIC4(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name) \
69 | ret (*name##_Actual)(p1type, p2type, p3type, p4type) = NULL; \
70 | ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name)
71 |
72 | #define DETOUR_DECL_STATIC5(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name) \
73 | ret (*name##_Actual)(p1type, p2type, p3type, p4type, p5type) = NULL; \
74 | ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name)
75 |
76 | #define DETOUR_DECL_STATIC6(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name) \
77 | ret (*name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type) = NULL; \
78 | ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name)
79 |
80 | #define DETOUR_DECL_STATIC7(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name) \
81 | ret (*name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type) = NULL; \
82 | ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name)
83 |
84 | #define DETOUR_DECL_STATIC0_fastcall(name, ret) \
85 | ret (__fastcall *name##_Actual)(void) = NULL; \
86 | ret __fastcall name(void)
87 |
88 | #define DETOUR_DECL_STATIC1_fastcall(name, ret, p1type, p1name) \
89 | ret (__fastcall *name##_Actual)(p1type) = NULL; \
90 | ret __fastcall name(p1type p1name)
91 |
92 | #define DETOUR_DECL_STATIC2_fastcall(name, ret, p1type, p1name, p2type, p2name) \
93 | ret (__fastcall *name##_Actual)(p1type, p2type) = NULL; \
94 | ret __fastcall name(p1type p1name, p2type p2name)
95 |
96 | #define DETOUR_DECL_STATIC3_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name) \
97 | ret (__fastcall *name##_Actual)(p1type, p2type, p3type) = NULL; \
98 | ret __fastcall name(p1type p1name, p2type p2name, p3type p3name)
99 |
100 | #define DETOUR_DECL_STATIC4_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name) \
101 | ret (__fastcall *name##_Actual)(p1type, p2type, p3type, p4type) = NULL; \
102 | ret __fastcall name(p1type p1name, p2type p2name, p3type p3name, p4type p4name)
103 |
104 | #define DETOUR_DECL_STATIC5_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name) \
105 | ret (__fastcall *name##_Actual)(p1type, p2type, p3type, p4type, p5type) = NULL; \
106 | ret __fastcall name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name)
107 |
108 | #define DETOUR_DECL_STATIC6_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name) \
109 | ret (__fastcall *name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type) = NULL; \
110 | ret __fastcall name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name)
111 |
112 | #define DETOUR_DECL_STATIC7_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name) \
113 | ret (__fastcall *name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type) = NULL; \
114 | ret __fastcall name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name)
115 |
116 | #define DETOUR_DECL_MEMBER0(name, ret) \
117 | class name##Class \
118 | { \
119 | public: \
120 | ret name(); \
121 | static ret (name##Class::* name##_Actual)(void); \
122 | }; \
123 | ret (name##Class::* name##Class::name##_Actual)(void) = NULL; \
124 | ret name##Class::name()
125 |
126 | #define DETOUR_DECL_MEMBER1(name, ret, p1type, p1name) \
127 | class name##Class \
128 | { \
129 | public: \
130 | ret name(p1type p1name); \
131 | static ret (name##Class::* name##_Actual)(p1type); \
132 | }; \
133 | ret (name##Class::* name##Class::name##_Actual)(p1type) = NULL; \
134 | ret name##Class::name(p1type p1name)
135 |
136 | #define DETOUR_DECL_MEMBER2(name, ret, p1type, p1name, p2type, p2name) \
137 | class name##Class \
138 | { \
139 | public: \
140 | ret name(p1type p1name, p2type p2name); \
141 | static ret (name##Class::* name##_Actual)(p1type, p2type); \
142 | }; \
143 | ret (name##Class::* name##Class::name##_Actual)(p1type, p2type) = NULL; \
144 | ret name##Class::name(p1type p1name, p2type p2name)
145 |
146 | #define DETOUR_DECL_MEMBER3(name, ret, p1type, p1name, p2type, p2name, p3type, p3name) \
147 | class name##Class \
148 | { \
149 | public: \
150 | ret name(p1type p1name, p2type p2name, p3type p3name); \
151 | static ret (name##Class::* name##_Actual)(p1type, p2type, p3type); \
152 | }; \
153 | ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type) = NULL; \
154 | ret name##Class::name(p1type p1name, p2type p2name, p3type p3name)
155 |
156 | #define DETOUR_DECL_MEMBER4(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name) \
157 | class name##Class \
158 | { \
159 | public: \
160 | ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name); \
161 | static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type); \
162 | }; \
163 | ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type) = NULL; \
164 | ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name)
165 |
166 | #define DETOUR_DECL_MEMBER5(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name) \
167 | class name##Class \
168 | { \
169 | public: \
170 | ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name); \
171 | static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type); \
172 | }; \
173 | ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type) = NULL; \
174 | ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name)
175 |
176 | #define DETOUR_DECL_MEMBER7(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name) \
177 | class name##Class \
178 | { \
179 | public: \
180 | ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name); \
181 | static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type); \
182 | }; \
183 | ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type) = NULL; \
184 | ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name)
185 |
186 | #define DETOUR_DECL_MEMBER8(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name, p8type, p8name) \
187 | class name##Class \
188 | { \
189 | public: \
190 | ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name); \
191 | static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type); \
192 | }; \
193 | ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type) = NULL; \
194 | ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name)
195 |
196 | #define DETOUR_DECL_MEMBER9(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name, p8type, p8name, p9type, p9name) \
197 | class name##Class \
198 | { \
199 | public: \
200 | ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name, p9type p9name); \
201 | static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type, p9type); \
202 | }; \
203 | ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type, p9type) = NULL; \
204 | ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name, p9type p9name)
205 |
206 | #define DETOUR_DECL_MEMBER10(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name, p8type, p8name, p9type, p9name, p10type, p10name) \
207 | class name##Class \
208 | { \
209 | public: \
210 | ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name, p9type p9name, p10type p10name); \
211 | static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type, p9type, p10type); \
212 | }; \
213 | ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type, p9type, p10type) = NULL; \
214 | ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name, p9type p9name, p10type p10name)
215 |
216 | #define DETOUR_DECL_MEMBER0_fastcall(name, ret) \
217 | class name##Class \
218 | { \
219 | public: \
220 | ret __fastcall name(); \
221 | static ret (__fastcall name##Class::* name##_Actual)(void); \
222 | }; \
223 | ret (__fastcall name##Class::* name##Class::name##_Actual)(void) = NULL; \
224 | ret __fastcall name##Class::name()
225 |
226 | #define DETOUR_DECL_MEMBER1_fastcall(name, ret, p1type, p1name) \
227 | class name##Class \
228 | { \
229 | public: \
230 | ret __fastcall name(p1type p1name); \
231 | static ret (__fastcall name##Class::* name##_Actual)(p1type); \
232 | }; \
233 | ret (__fastcall name##Class::* name##Class::name##_Actual)(p1type) = NULL; \
234 | ret __fastcall name##Class::name(p1type p1name)
235 |
236 | #define DETOUR_DECL_MEMBER2_fastcall(name, ret, p1type, p1name, p2type, p2name) \
237 | class name##Class \
238 | { \
239 | public: \
240 | ret __fastcall name(p1type p1name, p2type p2name); \
241 | static ret (__fastcall name##Class::* name##_Actual)(p1type, p2type); \
242 | }; \
243 | ret (__fastcall name##Class::* name##Class::name##_Actual)(p1type, p2type) = NULL; \
244 | ret __fastcall name##Class::name(p1type p1name, p2type p2name)
245 |
246 | #define DETOUR_DECL_MEMBER3_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name) \
247 | class name##Class \
248 | { \
249 | public: \
250 | ret __fastcall name(p1type p1name, p2type p2name, p3type p3name); \
251 | static ret (__fastcall name##Class::* name##_Actual)(p1type, p2type, p3type); \
252 | }; \
253 | ret (__fastcall name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type) = NULL; \
254 | ret __fastcall name##Class::name(p1type p1name, p2type p2name, p3type p3name)
255 |
256 | #define DETOUR_DECL_MEMBER4_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name) \
257 | class name##Class \
258 | { \
259 | public: \
260 | ret __fastcall name(p1type p1name, p2type p2name, p3type p3name, p4type p4name); \
261 | static ret (__fastcall name##Class::* name##_Actual)(p1type, p2type, p3type, p4type); \
262 | }; \
263 | ret (__fastcall name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type) = NULL; \
264 | ret __fastcall name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name)
265 |
266 | #define DETOUR_DECL_MEMBER5_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name) \
267 | class name##Class \
268 | { \
269 | public: \
270 | ret __fastcall name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name); \
271 | static ret (__fastcall name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type); \
272 | }; \
273 | ret (__fastcall name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type) = NULL; \
274 | ret __fastcall name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name)
275 |
276 | #define GET_MEMBER_CALLBACK(name) (void *)GetCodeAddress(&name##Class::name)
277 | #define GET_MEMBER_TRAMPOLINE(name) (void **)(&name##Class::name##_Actual)
278 |
279 | #define GET_STATIC_CALLBACK(name) (void *)&name
280 | #define GET_STATIC_TRAMPOLINE(name) (void **)&name##_Actual
281 |
282 | #define DETOUR_CREATE_MEMBER(name, gamedata) CDetourManager::CreateDetour(GET_MEMBER_CALLBACK(name), GET_MEMBER_TRAMPOLINE(name), gamedata);
283 | #define DETOUR_CREATE_STATIC(name, gamedata) CDetourManager::CreateDetour(GET_STATIC_CALLBACK(name), GET_STATIC_TRAMPOLINE(name), gamedata);
284 |
285 |
286 | class GenericClass {};
287 | typedef void (GenericClass::*VoidFunc)();
288 |
289 | inline void *GetCodeAddr(VoidFunc mfp)
290 | {
291 | return *(void **)&mfp;
292 | }
293 |
294 | /**
295 | * Converts a member function pointer to a void pointer.
296 | * This relies on the assumption that the code address lies at mfp+0
297 | * This is the case for both g++ and later MSVC versions on non virtual functions but may be different for other compilers
298 | * Based on research by Don Clugston : http://www.codeproject.com/cpp/FastDelegate.asp
299 | */
300 | #define GetCodeAddress(mfp) GetCodeAddr(reinterpret_cast(mfp))
301 |
302 | class CDetourManager;
303 |
304 | class CDetour
305 | {
306 | public:
307 |
308 | bool IsEnabled();
309 |
310 | /**
311 | * These would be somewhat self-explanatory I hope
312 | */
313 | void EnableDetour();
314 | void DisableDetour();
315 |
316 | void Destroy();
317 |
318 | friend class CDetourManager;
319 |
320 | protected:
321 | CDetour(void *callbackfunction, void **trampoline, const char *signame);
322 |
323 | bool Init(ISourcePawnEngine *spengine, IGameConfig *gameconf);
324 | private:
325 |
326 | /* These create/delete the allocated memory */
327 | bool CreateDetour();
328 | void DeleteDetour();
329 |
330 | bool enabled;
331 | bool detoured;
332 |
333 | patch_t detour_restore;
334 | /* Address of the detoured function */
335 | void *detour_address;
336 | /* Address of the allocated trampoline function */
337 | void *detour_trampoline;
338 | /* Address of the callback handler */
339 | void *detour_callback;
340 | /* The function pointer used to call our trampoline */
341 | void **trampoline;
342 |
343 | const char *signame;
344 | ISourcePawnEngine *spengine;
345 | IGameConfig *gameconf;
346 | };
347 |
348 | class CDetourManager
349 | {
350 | public:
351 |
352 | static void Init(ISourcePawnEngine *spengine, IGameConfig *gameconf);
353 |
354 | /**
355 | * Creates a new detour
356 | *
357 | * @param callbackfunction Void pointer to your detour callback function.
358 | * @param trampoline Address of the trampoline pointer
359 | * @param signame Section name containing a signature to fetch from the gamedata file.
360 | * @return A new CDetour pointer to control your detour.
361 | *
362 | * Example:
363 | *
364 | * CBaseServer::ConnectClient(netadr_s &, int, int, int, char const*, char const*, char const*, int)
365 | *
366 | * Define a new class with the required function and a member function pointer to the same type:
367 | *
368 | * class CBaseServerDetour
369 | * {
370 | * public:
371 | * bool ConnectClient(void *netaddr_s, int, int, int, char const*, char const*, char const*, int);
372 | * static bool (CBaseServerDetour::* ConnectClient_Actual)(void *netaddr_s, int, int, int, char const*, char const*, char const*, int);
373 | * }
374 | *
375 | * void *callbackfunc = GetCodeAddress(&CBaseServerDetour::ConnectClient);
376 | * void **trampoline = (void **)(&CBaseServerDetour::ConnectClient_Actual);
377 | *
378 | * Creation:
379 | * CDetourManager::CreateDetour(callbackfunc, trampoline, "ConnectClient");
380 | *
381 | * Usage:
382 | *
383 | * CBaseServerDetour::ConnectClient(void *netaddr_s, int, int, int, char const*, char const*, char const*, int)
384 | * {
385 | * //pre hook code
386 | * bool result = (this->*ConnectClient_Actual)(netaddr_s, rest of params);
387 | * //post hook code
388 | * return result;
389 | * }
390 | *
391 | * Note we changed the netadr_s reference into a void* to avoid needing to define the type
392 | */
393 | static CDetour *CreateDetour(void *callbackfunction, void **trampoline, const char *signame);
394 |
395 | friend class CBlocker;
396 | friend class CDetour;
397 |
398 | private:
399 | static ISourcePawnEngine *spengine;
400 | static IGameConfig *gameconf;
401 | };
402 |
403 | #define DECL_DETOUR(name) \
404 | CDetour *name##_Detour = nullptr;
405 |
406 | #define CREATE_DETOUR(name, signname, var) \
407 | name##_Detour = DETOUR_CREATE_MEMBER(name, signname); \
408 | if (name##_Detour != NULL) \
409 | { \
410 | name##_Detour->EnableDetour(); \
411 | var = true; \
412 | } else { \
413 | g_pSM->LogError(myself, "Failed to create " signname " detour, check error log.\n"); \
414 | var = false; \
415 | }
416 |
417 | #define CREATE_DETOUR_STATIC(name, signname, var) \
418 | name##_Detour = DETOUR_CREATE_STATIC(name, signname); \
419 | if (name##_Detour != NULL) \
420 | { \
421 | name##_Detour->EnableDetour(); \
422 | var = true; \
423 | } else { \
424 | g_pSM->LogError(myself, "Failed to create " signname " detour, check error log.\n"); \
425 | var = false; \
426 | }
427 |
428 | #define DESTROY_DETOUR(name) \
429 | if (name##_Detour != nullptr) \
430 | { \
431 | name##_Detour->Destroy(); \
432 | name##_Detour = nullptr; \
433 | }
434 |
435 | #endif // _INCLUDE_SOURCEMOD_DETOURS_H_
436 |
--------------------------------------------------------------------------------
/extension/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:20.04
2 |
3 | RUN apt update
4 | RUN dpkg --add-architecture i386
5 | RUN apt update && apt install -y git python3 python3-pip
6 |
7 | RUN mkdir /sdks
8 | RUN git clone https://github.com/alliedmodders/sourcemod --recurse-submodules -b 1.11-dev
9 | RUN git clone https://github.com/alliedmodders/metamod-source --recurse-submodules -b 1.11-dev
10 | RUN git clone https://github.com/alliedmodders/hl2sdk --recurse-submodules -b tf2 /sdks/hl2sdk-tf2
11 | RUN git clone https://github.com/alliedmodders/hl2sdk --recurse-submodules -b sdk2013 /sdks/hl2sdk-sdk2013
12 | RUN git clone https://github.com/alliedmodders/ambuild --recurse-submodules
13 |
14 | RUN apt install -y \
15 | g++-multilib \
16 | libtool \
17 | nasm \
18 | libiberty-dev:i386 \
19 | libelf-dev:i386 \
20 | libboost-dev:i386 \
21 | libbsd-dev:i386 \
22 | libunwind-dev:i386 \
23 | lib32stdc++-10-dev \
24 | lib32z1-dev \
25 | libc6-dev:i386 \
26 | linux-libc-dev:i386 \
27 | libopus-dev:i386
28 |
29 | RUN git clone https://github.com/xiph/opus.git -b v1.3.1 --depth 1
30 | RUN cd opus && ./autogen.sh && ./configure CFLAGS="-m32 -g -O2" LDFLAGS=-m32 && make && make install && cd ..
31 |
32 | RUN pip install ./ambuild
--------------------------------------------------------------------------------
/extension/Dockerfile.windows:
--------------------------------------------------------------------------------
1 | # escape=`
2 |
3 | FROM mcr.microsoft.com/windows/server:ltsc2022
4 |
5 | # Reset the shell.
6 | SHELL ["cmd", "/S", "/C"]
7 |
8 | # Download channel for fixed install.
9 | ADD https://aka.ms/vs/17/release/channel C:\TEMP\VisualStudio.chman
10 |
11 | # Download and install Build Tools for Visual Studio 2022 for native desktop workload.
12 | ADD https://aka.ms/vs/17/release/vs_buildtools.exe C:\TEMP\vs_buildtools.exe
13 | RUN C:\TEMP\vs_buildtools.exe --quiet --wait --norestart --nocache `
14 | --channelUri C:\TEMP\VisualStudio.chman `
15 | --installChannelUri C:\TEMP\VisualStudio.chman `
16 | --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended`
17 | --installPath C:\BuildTools
18 |
19 | RUN PowerShell Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
20 | RUN choco install -y `
21 | git `
22 | python
23 |
24 | RUN mkdir C:\sdks
25 | RUN git clone https://github.com/alliedmodders/sourcemod --recurse-submodules -b 1.11-dev
26 | RUN git clone https://github.com/alliedmodders/metamod-source --recurse-submodules -b 1.11-dev
27 | RUN git clone https://github.com/alliedmodders/hl2sdk --recurse-submodules -b tf2 C:\sdks\hl2sdk-tf2
28 | RUN git clone https://github.com/alliedmodders/hl2sdk --recurse-submodules -b sdk2013 C:\sdks\hl2sdk-sdk2013
29 | RUN git clone https://github.com/alliedmodders/ambuild --recurse-submodules
30 |
31 | COPY scripts\setup.py C:\ambuild\setup.py
32 | RUN pip install C:\ambuild
33 |
34 | ENTRYPOINT C:\BuildTools\Common7\Tools\VsDevCmd.bat && C:\extension\scripts\build.bat
--------------------------------------------------------------------------------
/extension/PackageScript:
--------------------------------------------------------------------------------
1 | # vim: set ts=8 sts=2 sw=2 tw=99 et ft=python:
2 | import os
3 |
4 | # This is where the files will be output to
5 | # package is the default
6 | builder.SetBuildFolder('package')
7 |
8 | # Add any folders you need to this list
9 | folder_list = [
10 | 'addons/sourcemod/extensions',
11 | #'addons/sourcemod/scripting/include',
12 | #'addons/sourcemod/gamedata',
13 | #'addons/sourcemod/configs',
14 | ]
15 |
16 | # Create the distribution folder hierarchy.
17 | folder_map = {}
18 | for folder in folder_list:
19 | norm_folder = os.path.normpath(folder)
20 | folder_map[folder] = builder.AddFolder(norm_folder)
21 |
22 | # Do all straight-up file copies from the source tree.
23 | def CopyFiles(src, dest, files):
24 | if not dest:
25 | dest = src
26 | dest_entry = folder_map[dest]
27 | for source_file in files:
28 | source_path = os.path.join(builder.sourcePath, src, source_file)
29 | builder.AddCopy(source_path, dest_entry)
30 |
31 | # Include files
32 | #CopyFiles('include', 'addons/sourcemod/scripting/include',
33 | # [ 'sample.inc', ]
34 | #)
35 |
36 | # GameData files
37 | #CopyFiles('gamedata', 'addons/sourcemod/gamedata',
38 | # [ 'myfile.txt',
39 | # 'file2.txt'
40 | # ]
41 | #)
42 |
43 | # Config Files
44 | #CopyFiles('configs', 'addons/sourcemod/configs',
45 | # [ 'configfile.cfg',
46 | # 'otherconfig.cfg,
47 | # ]
48 | #)
49 |
50 | # Copy binaries.
51 | for cxx_task in Extension.extensions:
52 | builder.AddCopy(cxx_task.binary, folder_map['addons/sourcemod/extensions'])
53 |
--------------------------------------------------------------------------------
/extension/asm/asm.c:
--------------------------------------------------------------------------------
1 | #include "asm.h"
2 |
3 | #ifndef WIN32
4 | #define _GNU_SOURCE
5 | #include
6 | #include
7 |
8 | #define REG_EAX 0
9 | #define REG_ECX 1
10 | #define REG_EDX 2
11 | #define REG_EBX 3
12 |
13 | #define IA32_MOV_REG_IMM 0xB8 // encoding is +r
14 | #endif
15 |
16 | extern void Msg( const char *, ... );
17 |
18 | /**
19 | * Checks if a call to a fpic thunk has just been written into dest.
20 | * If found replaces it with a direct mov that sets the required register to the value of pc.
21 | *
22 | * @param dest Destination buffer where a call opcode + addr (5 bytes) has just been written.
23 | * @param pc The program counter value that needs to be set (usually the next address from the source).
24 | * @noreturn
25 | */
26 | void check_thunks(unsigned char *dest, unsigned char *pc)
27 | {
28 | #if defined WIN32
29 | return;
30 | #else
31 | /* Step write address back 4 to the start of the function address */
32 | unsigned char *writeaddr = dest - 4;
33 | unsigned char *calloffset = *(unsigned char **)writeaddr;
34 | unsigned char *calladdr = (unsigned char *)(dest + (unsigned int)calloffset);
35 |
36 | /* Lookup name of function being called */
37 | if ((*calladdr == 0x8B) && (*(calladdr+2) == 0x24) && (*(calladdr+3) == 0xC3))
38 | {
39 | //a thunk maybe?
40 | char movByte = IA32_MOV_REG_IMM;
41 |
42 | /* Calculate the correct mov opcode */
43 | switch (*(calladdr+1))
44 | {
45 | case 0x04:
46 | {
47 | movByte += REG_EAX;
48 | break;
49 | }
50 | case 0x1C:
51 | {
52 | movByte += REG_EBX;
53 | break;
54 | }
55 | case 0x0C:
56 | {
57 | movByte += REG_ECX;
58 | break;
59 | }
60 | case 0x14:
61 | {
62 | movByte += REG_EDX;
63 | break;
64 | }
65 | default:
66 | {
67 | Msg("Unknown thunk: %c\n", *(calladdr+1));
68 | break;
69 | }
70 | }
71 |
72 | /* Move our write address back one to where the call opcode was */
73 | writeaddr--;
74 |
75 |
76 | /* Write our mov */
77 | *writeaddr = movByte;
78 | writeaddr++;
79 |
80 | /* Write the value - The provided program counter value */
81 | *(void **)writeaddr = (void *)pc;
82 | writeaddr += 4;
83 | }
84 |
85 | return;
86 | #endif
87 | }
88 |
89 | //if dest is NULL, returns minimum number of bytes needed to be copied
90 | //if dest is not NULL, it will copy the bytes to dest as well as fix CALLs and JMPs
91 | //http://www.devmaster.net/forums/showthread.php?t=2311
92 | int copy_bytes(unsigned char *func, unsigned char* dest, int required_len) {
93 | int bytecount = 0;
94 |
95 | while(bytecount < required_len && *func != 0xCC)
96 | {
97 | // prefixes F0h, F2h, F3h, 66h, 67h, D8h-DFh, 2Eh, 36h, 3Eh, 26h, 64h and 65h
98 | int operandSize = 4;
99 | int FPU = 0;
100 | int twoByte = 0;
101 | unsigned char opcode = 0x90;
102 | unsigned char modRM = 0xFF;
103 | while(*func == 0xF0 ||
104 | *func == 0xF2 ||
105 | *func == 0xF3 ||
106 | (*func & 0xFC) == 0x64 ||
107 | (*func & 0xF8) == 0xD8 ||
108 | (*func & 0x7E) == 0x62)
109 | {
110 | if(*func == 0x66)
111 | {
112 | operandSize = 2;
113 | }
114 | else if((*func & 0xF8) == 0xD8)
115 | {
116 | FPU = *func;
117 | if (dest)
118 | *dest++ = *func++;
119 | else
120 | func++;
121 | bytecount++;
122 | break;
123 | }
124 |
125 | if (dest)
126 | *dest++ = *func++;
127 | else
128 | func++;
129 | bytecount++;
130 | }
131 |
132 | // two-byte opcode byte
133 | if(*func == 0x0F)
134 | {
135 | twoByte = 1;
136 | if (dest)
137 | *dest++ = *func++;
138 | else
139 | func++;
140 | bytecount++;
141 | }
142 |
143 | // opcode byte
144 | opcode = *func++;
145 | if (dest) *dest++ = opcode;
146 | bytecount++;
147 |
148 | // mod R/M byte
149 | modRM = 0xFF;
150 | if(FPU)
151 | {
152 | if((opcode & 0xC0) != 0xC0)
153 | {
154 | modRM = opcode;
155 | }
156 | }
157 | else if(!twoByte)
158 | {
159 | if((opcode & 0xC4) == 0x00 ||
160 | ((opcode & 0xF4) == 0x60 && ((opcode & 0x0A) == 0x02 || (opcode & 0x09) == 0x09)) ||
161 | (opcode & 0xF0) == 0x80 ||
162 | ((opcode & 0xF8) == 0xC0 && (opcode & 0x0E) != 0x02) ||
163 | (opcode & 0xFC) == 0xD0 ||
164 | (opcode & 0xF6) == 0xF6)
165 | {
166 | modRM = *func++;
167 | if (dest) *dest++ = modRM;
168 | bytecount++;
169 | }
170 | }
171 | else
172 | {
173 | if(((opcode & 0xF0) == 0x00 && (opcode & 0x0F) >= 0x04 && (opcode & 0x0D) != 0x0D) ||
174 | (opcode & 0xF0) == 0x30 ||
175 | opcode == 0x77 ||
176 | (opcode & 0xF0) == 0x80 ||
177 | ((opcode & 0xF0) == 0xA0 && (opcode & 0x07) <= 0x02) ||
178 | (opcode & 0xF8) == 0xC8)
179 | {
180 | // No mod R/M byte
181 | }
182 | else
183 | {
184 | modRM = *func++;
185 | if (dest) *dest++ = modRM;
186 | bytecount++;
187 | }
188 | }
189 |
190 | // SIB
191 | if((modRM & 0x07) == 0x04 &&
192 | (modRM & 0xC0) != 0xC0)
193 | {
194 | if (dest)
195 | *dest++ = *func++; //SIB
196 | else
197 | func++;
198 | bytecount++;
199 | }
200 |
201 | // mod R/M displacement
202 |
203 | // Dword displacement, no base
204 | if((modRM & 0xC5) == 0x05) {
205 | if (dest) {
206 | *(unsigned int*)dest = *(unsigned int*)func;
207 | dest += 4;
208 | }
209 | func += 4;
210 | bytecount += 4;
211 | }
212 |
213 | // Byte displacement
214 | if((modRM & 0xC0) == 0x40) {
215 | if (dest)
216 | *dest++ = *func++;
217 | else
218 | func++;
219 | bytecount++;
220 | }
221 |
222 | // Dword displacement
223 | if((modRM & 0xC0) == 0x80) {
224 | if (dest) {
225 | *(unsigned int*)dest = *(unsigned int*)func;
226 | dest += 4;
227 | }
228 | func += 4;
229 | bytecount += 4;
230 | }
231 |
232 | // immediate
233 | if(FPU)
234 | {
235 | // Can't have immediate operand
236 | }
237 | else if(!twoByte)
238 | {
239 | if((opcode & 0xC7) == 0x04 ||
240 | (opcode & 0xFE) == 0x6A || // PUSH/POP/IMUL
241 | (opcode & 0xF0) == 0x70 || // Jcc
242 | opcode == 0x80 ||
243 | opcode == 0x83 ||
244 | (opcode & 0xFD) == 0xA0 || // MOV
245 | opcode == 0xA8 || // TEST
246 | (opcode & 0xF8) == 0xB0 || // MOV
247 | (opcode & 0xFE) == 0xC0 || // RCL
248 | opcode == 0xC6 || // MOV
249 | opcode == 0xCD || // INT
250 | (opcode & 0xFE) == 0xD4 || // AAD/AAM
251 | (opcode & 0xF8) == 0xE0 || // LOOP/JCXZ
252 | opcode == 0xEB ||
253 | (opcode == 0xF6 && (modRM & 0x30) == 0x00)) // TEST
254 | {
255 | if (dest)
256 | *dest++ = *func++;
257 | else
258 | func++;
259 | bytecount++;
260 | }
261 | else if((opcode & 0xF7) == 0xC2) // RET
262 | {
263 | if (dest) {
264 | *(unsigned short*)dest = *(unsigned short*)func;
265 | dest += 2;
266 | }
267 | func += 2;
268 | bytecount += 2;
269 | }
270 | else if((opcode & 0xFC) == 0x80 ||
271 | (opcode & 0xC7) == 0x05 ||
272 | (opcode & 0xF8) == 0xB8 ||
273 | (opcode & 0xFE) == 0xE8 || // CALL/Jcc
274 | (opcode & 0xFE) == 0x68 ||
275 | (opcode & 0xFC) == 0xA0 ||
276 | (opcode & 0xEE) == 0xA8 ||
277 | opcode == 0xC7 ||
278 | (opcode == 0xF7 && (modRM & 0x30) == 0x00))
279 | {
280 | if (dest) {
281 | //Fix CALL/JMP offset
282 | if ((opcode & 0xFE) == 0xE8) {
283 | if (operandSize == 4)
284 | {
285 | *(long*)dest = ((func + *(long*)func) - dest);
286 |
287 | //pRED* edit. func is the current address of the call address, +4 is the next instruction, so the value of $pc
288 | check_thunks(dest+4, func+4);
289 | }
290 | else
291 | *(short*)dest = ((func + *(short*)func) - dest);
292 |
293 | } else {
294 | if (operandSize == 4)
295 | *(unsigned long*)dest = *(unsigned long*)func;
296 | else
297 | *(unsigned short*)dest = *(unsigned short*)func;
298 | }
299 | dest += operandSize;
300 | }
301 | func += operandSize;
302 | bytecount += operandSize;
303 |
304 | }
305 | }
306 | else
307 | {
308 | if(opcode == 0xBA || // BT
309 | opcode == 0x0F || // 3DNow!
310 | (opcode & 0xFC) == 0x70 || // PSLLW
311 | (opcode & 0xF7) == 0xA4 || // SHLD
312 | opcode == 0xC2 ||
313 | opcode == 0xC4 ||
314 | opcode == 0xC5 ||
315 | opcode == 0xC6)
316 | {
317 | if (dest)
318 | *dest++ = *func++;
319 | else
320 | func++;
321 | }
322 | else if((opcode & 0xF0) == 0x80) // Jcc -i
323 | {
324 | if (dest) {
325 | if (operandSize == 4)
326 | *(unsigned long*)dest = *(unsigned long*)func;
327 | else
328 | *(unsigned short*)dest = *(unsigned short*)func;
329 |
330 | dest += operandSize;
331 | }
332 | func += operandSize;
333 | bytecount += operandSize;
334 | }
335 | }
336 | }
337 |
338 | return bytecount;
339 | }
340 |
341 | //insert a specific JMP instruction at the given location
342 | void inject_jmp(void* src, void* dest) {
343 | *(unsigned char*)src = OP_JMP;
344 | *(long*)((unsigned char*)src+1) = (long)((unsigned char*)dest - ((unsigned char*)src + OP_JMP_SIZE));
345 | }
346 |
347 | //fill a given block with NOPs
348 | void fill_nop(void* src, unsigned int len) {
349 | unsigned char* src2 = (unsigned char*)src;
350 | while (len) {
351 | *src2++ = OP_NOP;
352 | --len;
353 | }
354 | }
355 |
356 | void* eval_jump(void* src) {
357 | unsigned char* addr = (unsigned char*)src;
358 |
359 | if (!addr) return 0;
360 |
361 | //import table jump
362 | if (addr[0] == OP_PREFIX && addr[1] == OP_JMP_SEG) {
363 | addr += 2;
364 | addr = *(unsigned char**)addr;
365 | //TODO: if addr points into the IAT
366 | return *(void**)addr;
367 | }
368 |
369 | //8bit offset
370 | else if (addr[0] == OP_JMP_BYTE) {
371 | addr = &addr[OP_JMP_BYTE_SIZE] + *(char*)&addr[1];
372 | //mangled 32bit jump?
373 | if (addr[0] == OP_JMP) {
374 | addr = addr + *(int*)&addr[1];
375 | }
376 | return addr;
377 | }
378 | /*
379 | //32bit offset
380 | else if (addr[0] == OP_JMP) {
381 | addr = &addr[OP_JMP_SIZE] + *(int*)&addr[1];
382 | }
383 | */
384 |
385 | return addr;
386 | }
387 | /*
388 | from ms detours package
389 | static bool detour_is_imported(PBYTE pbCode, PBYTE pbAddress)
390 | {
391 | MEMORY_BASIC_INFORMATION mbi;
392 | VirtualQuery((PVOID)pbCode, &mbi, sizeof(mbi));
393 | __try {
394 | PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)mbi.AllocationBase;
395 | if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
396 | return false;
397 | }
398 |
399 | PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)pDosHeader +
400 | pDosHeader->e_lfanew);
401 | if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) {
402 | return false;
403 | }
404 |
405 | if (pbAddress >= ((PBYTE)pDosHeader +
406 | pNtHeader->OptionalHeader
407 | .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress) &&
408 | pbAddress < ((PBYTE)pDosHeader +
409 | pNtHeader->OptionalHeader
410 | .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress +
411 | pNtHeader->OptionalHeader
412 | .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size)) {
413 | return true;
414 | }
415 | return false;
416 | }
417 | __except(EXCEPTION_EXECUTE_HANDLER) {
418 | return false;
419 | }
420 | }
421 | */
422 |
--------------------------------------------------------------------------------
/extension/asm/asm.h:
--------------------------------------------------------------------------------
1 | #ifndef __ASM_H__
2 | #define __ASM_H__
3 |
4 | #define OP_JMP 0xE9
5 | #define OP_JMP_SIZE 5
6 |
7 | #define OP_NOP 0x90
8 | #define OP_NOP_SIZE 1
9 |
10 | #define OP_PREFIX 0xFF
11 | #define OP_JMP_SEG 0x25
12 |
13 | #define OP_JMP_BYTE 0xEB
14 | #define OP_JMP_BYTE_SIZE 2
15 |
16 | #ifdef __cplusplus
17 | extern "C" {
18 | #endif
19 |
20 | void check_thunks(unsigned char *dest, unsigned char *pc);
21 |
22 | //if dest is NULL, returns minimum number of bytes needed to be copied
23 | //if dest is not NULL, it will copy the bytes to dest as well as fix CALLs and JMPs
24 | //http://www.devmaster.net/forums/showthread.php?t=2311
25 | int copy_bytes(unsigned char *func, unsigned char* dest, int required_len);
26 |
27 | //insert a specific JMP instruction at the given location
28 | void inject_jmp(void* src, void* dest);
29 |
30 | //fill a given block with NOPs
31 | void fill_nop(void* src, unsigned int len);
32 |
33 | //evaluate a JMP at the target
34 | void* eval_jump(void* src);
35 |
36 | #ifdef __cplusplus
37 | }
38 | #endif
39 |
40 | #endif //__ASM_H__
41 |
--------------------------------------------------------------------------------
/extension/configure.py:
--------------------------------------------------------------------------------
1 | # vim: set sts=2 ts=8 sw=2 tw=99 et:
2 | import sys
3 | from ambuild2 import run
4 |
5 | # Simple extensions do not need to modify this file.
6 |
7 | builder = run.PrepareBuild(sourcePath = sys.path[0])
8 |
9 | builder.options.add_option('--hl2sdk-root', type=str, dest='hl2sdk_root', default=None,
10 | help='Root search folder for HL2SDKs')
11 | builder.options.add_option('--mms-path', type=str, dest='mms_path', default=None,
12 | help='Path to Metamod:Source')
13 | builder.options.add_option('--sm-path', type=str, dest='sm_path', default=None,
14 | help='Path to SourceMod')
15 | builder.options.add_option('--enable-debug', action='store_const', const='1', dest='debug',
16 | help='Enable debugging symbols')
17 | builder.options.add_option('--enable-optimize', action='store_const', const='1', dest='opt',
18 | help='Enable optimization')
19 | builder.options.add_option('-s', '--sdks', default='all', dest='sdks',
20 | help='Build against specified SDKs; valid args are "all", "present", or '
21 | 'comma-delimited list of engine names (default: %default)')
22 |
23 | builder.Configure()
24 |
--------------------------------------------------------------------------------
/extension/defines.h:
--------------------------------------------------------------------------------
1 |
2 | #define MAXPLAYERS 65
3 |
4 | #define LEVEL_QUIETER -3500
5 | #define LEVEL_QUIET -2500
6 | #define LEVEL_LOUD 1000
7 | #define LEVEL_LOUDER 2500
8 | #define MAX_LEVELS 4
9 |
10 | #define MAX_FRAMEBUFFER_SAMPLES 480
11 | #define STEAM_HEADER_SIZE 12
12 | #define CRC_SIZE 4
13 | #define CHANNELS 1
14 | #define APPLICATION OPUS_APPLICATION_AUDIO
15 | #define BITRATE 32000
16 | #define MAX_FRAME_SIZE 6 * 960
17 | #define MAX_PACKET_SIZE 4096
--------------------------------------------------------------------------------
/extension/docker-compose.windows.yml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 |
3 | services:
4 | extension-build-windows:
5 | build:
6 | context: .
7 | dockerfile: Dockerfile.windows
8 | image: extension-build-windows
9 | volumes:
10 | - .:C:\extension
11 | working_dir: C:\extension
12 |
--------------------------------------------------------------------------------
/extension/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 |
3 | services:
4 | extension-build:
5 | build:
6 | context: .
7 | dockerfile: Dockerfile
8 | image: extension-build
9 | volumes:
10 | - .:/extension
11 | working_dir: /extension
12 | entrypoint: /extension/scripts/build.sh
13 |
--------------------------------------------------------------------------------
/extension/extension.cpp:
--------------------------------------------------------------------------------
1 | #include "extension.h"
2 | #include "inetmessage.h"
3 |
4 | ConVar vm_enable("vm_enable", "1", FCVAR_NONE, "Enables voice manager");
5 | DECL_DETOUR(SV_BroadcastVoiceData);
6 |
7 | VoiceManagerExt g_VoiceManager; // Global singleton for extension's main interface
8 | IGameConfig* g_pGameConf = nullptr;
9 |
10 | class CGameClient;
11 | class CFrameSnapshot;
12 |
13 | SMEXT_LINK(&g_VoiceManager);
14 |
15 | IBinTools* g_pBinTools = nullptr;
16 | ISDKTools* g_pSDKTools = nullptr;
17 | IServer* g_pServer = nullptr;
18 |
19 | struct VoiceOverride
20 | {
21 | std::vector clients;
22 | };
23 |
24 | std::map> g_activeOverrides;
25 | std::map> m_userOverrides;
26 | std::map m_userGlobalOverrides;
27 |
28 | VoiceManagerClientState g_voiceManagerClientStates[MAXPLAYERS + 1];
29 |
30 | void SendVoiceDataMsg(int fromClientSlot, IClient* pToClient, uint8_t* data, int nBytes, int64 xuid)
31 | {
32 | SVC_VoiceData msg;
33 | msg.m_bProximity = false;
34 | msg.m_nLength = nBytes * 8;
35 | msg.m_xuid = xuid;
36 | msg.m_nFromClient = fromClientSlot;
37 | msg.m_DataOut = data;
38 | pToClient->SendNetMsg(msg);
39 | };
40 |
41 | DETOUR_DECL_STATIC4(SV_BroadcastVoiceData, void, IClient*, pClient, int, nBytes, uint8_t*, data, int64, xuid)
42 | {
43 | if (!vm_enable.GetBool())
44 | {
45 | DETOUR_STATIC_CALL(SV_BroadcastVoiceData)(pClient, nBytes, data, xuid);
46 | return;
47 | }
48 |
49 | int fromClientSlot = pClient->GetPlayerSlot();
50 | int fromClientIndex = pClient->GetPlayerSlot() + 1;
51 |
52 | // If there are no active overrides for this user, just broadcast the original data
53 | auto override = g_activeOverrides.find(fromClientIndex);
54 | if (override == g_activeOverrides.end())
55 | {
56 | DETOUR_STATIC_CALL(SV_BroadcastVoiceData)(pClient, nBytes, data, xuid);
57 | return;
58 | }
59 |
60 | // Populate a map of potential recipients.
61 | // The criteria for a recipient:
62 | // - The client is found
63 | // - The client is connected
64 | // - The client is not a bot (does not include replay/source tv)
65 | // - The client can hear the player (accounts for mutes, voice loopback)
66 | std::map recipientMap;
67 | for (int client = 1; client <= playerhelpers->GetMaxClients(); client++)
68 | {
69 | IGamePlayer* pGamePlayer = playerhelpers->GetGamePlayer(client);
70 | if (pGamePlayer == nullptr || !pGamePlayer->IsConnected() || pGamePlayer->IsFakeClient())
71 | {
72 | continue;
73 | }
74 |
75 | IClient* pToClient = g_pServer->GetClient(client - 1);
76 | if (pToClient == nullptr || !pToClient->IsHearingClient(fromClientSlot))
77 | {
78 | continue;
79 | }
80 |
81 | recipientMap.insert({ client, pToClient });
82 | }
83 |
84 | // Iterate over each volume level to determine if it has clients requesting it
85 | std::map overridingClients;
86 | for (int level = 0; level < MAX_LEVELS; level++)
87 | {
88 | // If the level has one or more clients requesting it, we need to re-encode the data at the specified level
89 | if (override->second[level].clients.size() > 0)
90 | {
91 | VoiceManager* vm = g_voiceManagerClientStates[override->first].GetVoiceManager(level);
92 |
93 | int nBytesOverride;
94 | uint8_t* newVoiceData = vm->OnBroadcastVoiceData(pClient, nBytes, data, &nBytesOverride);
95 |
96 | // Call CGameClient::SendNetMsg for each overriding client
97 | for (int client : override->second[level].clients)
98 | {
99 | // If the overriding client is not found, move on
100 | auto toClientPair = recipientMap.find(client);
101 | if (toClientPair == recipientMap.end())
102 | {
103 | continue;
104 | }
105 |
106 | SendVoiceDataMsg(fromClientSlot, toClientPair->second, newVoiceData, nBytesOverride, xuid);
107 |
108 | // Remove the recipient from the map as we've already sent them the voice message
109 | recipientMap.erase(client);
110 | }
111 |
112 | delete[] newVoiceData;
113 | }
114 | }
115 |
116 | for (const auto& [index, client] : recipientMap)
117 | {
118 | SendVoiceDataMsg(fromClientSlot, client, data, nBytes, xuid);
119 | }
120 | }
121 |
122 | uint64_t GetClientSteamId(int client)
123 | {
124 | auto player = playerhelpers->GetGamePlayer(client);
125 | if (player == nullptr || !player->IsConnected())
126 | {
127 | return -1;
128 | }
129 |
130 | return player->GetSteamId64();
131 | }
132 |
133 | std::map GetClientSteamIdMap()
134 | {
135 | auto steamIdsToSlots = std::map();
136 | for (int client = 1; client <= playerhelpers->GetMaxClients(); client++)
137 | {
138 | uint64_t steamId = GetClientSteamId(client);
139 | if (steamId > 0)
140 | {
141 | steamIdsToSlots.insert(std::pair(steamId, client));
142 | }
143 | }
144 |
145 | return steamIdsToSlots;
146 | }
147 |
148 | void AddOverride(std::map>* overrides, int adjuster, int adjusted, int level)
149 | {
150 | auto newActiveOverride = overrides->find(adjusted);
151 | if (newActiveOverride == overrides->end())
152 | {
153 | auto overrideLevels = std::map();
154 | for (int i = 0; i < MAX_LEVELS; i++)
155 | {
156 | VoiceOverride vo;
157 | vo.clients = std::vector();
158 |
159 | if (level == i)
160 | {
161 | vo.clients.push_back(adjuster);
162 | }
163 |
164 | overrideLevels.insert(std::pair{i, vo});
165 | }
166 |
167 | overrides->insert(std::pair>{adjusted, overrideLevels});
168 | }
169 | else
170 | {
171 | newActiveOverride->second.at(level).clients.push_back(adjuster);
172 | }
173 | }
174 |
175 | void RefreshActiveOverrides()
176 | {
177 | auto newActiveOverrides = std::map>();
178 | auto steamIdsToSlots = GetClientSteamIdMap();
179 |
180 | // Iterate over all active clients
181 | for (auto const& clientSteamIdPair : steamIdsToSlots)
182 | {
183 | int adjuster = clientSteamIdPair.second;
184 |
185 | for (auto const& otherClientSteamIdPair : steamIdsToSlots)
186 | {
187 | int adjusted = otherClientSteamIdPair.second;
188 |
189 | bool adjustmentMade = false;
190 | auto userOverride = m_userOverrides.find(clientSteamIdPair.first);
191 | if (userOverride != m_userOverrides.end())
192 | {
193 | auto overrides = userOverride->second;
194 | for (auto override : overrides)
195 | {
196 | if (override.steamId == otherClientSteamIdPair.first)
197 | {
198 | // smutils->LogMessage(myself, "Adding individual override. Adjuster: %d, Adjusted: %d, Level: %d", adjuster, adjusted, override.level);
199 | AddOverride(&newActiveOverrides, adjuster, adjusted, override.level);
200 | adjustmentMade = true;
201 | break;
202 | }
203 |
204 | if (adjustmentMade)
205 | {
206 | break;
207 | }
208 | }
209 | }
210 |
211 | if (adjustmentMade)
212 | {
213 | continue;
214 | }
215 |
216 | auto globalOverride = m_userGlobalOverrides.find(clientSteamIdPair.first);
217 | if (globalOverride != m_userGlobalOverrides.end())
218 | {
219 | // smutils->LogMessage(myself, "Adding global override. Adjuster: %d, Adjusted: %d, Level: %d", adjuster, adjusted, globalOverride->second);
220 | AddOverride(&newActiveOverrides, adjuster, adjusted, globalOverride->second);
221 | }
222 | }
223 | }
224 |
225 | g_activeOverrides = newActiveOverrides;
226 | }
227 |
228 | static cell_t OnGetClientOverride(IPluginContext* pContext, const cell_t* params)
229 | {
230 | if (!vm_enable.GetBool())
231 | {
232 | return false;
233 | }
234 |
235 | int adjuster = params[1];
236 | int adjusted = params[2];
237 |
238 | uint64_t adjusterSteamId = GetClientSteamId(adjuster);
239 |
240 | auto overridePair = m_userOverrides.find(adjusterSteamId);
241 | // User has no adjustments, return -1
242 | if (overridePair == m_userOverrides.end())
243 | {
244 | return -1;
245 | }
246 |
247 | uint64_t adjustedSteamId = GetClientSteamId(adjusted);
248 | for (auto override : overridePair->second)
249 | {
250 | if (override.steamId == adjustedSteamId)
251 | {
252 | return override.level;
253 | }
254 | }
255 |
256 | return -1;
257 | }
258 |
259 | static cell_t OnClearClientOverrides(IPluginContext* pContext, const cell_t* params)
260 | {
261 | if (!vm_enable.GetBool())
262 | {
263 | return false;
264 | }
265 |
266 | int client = params[1];
267 |
268 | uint64_t steamId = GetClientSteamId(client);
269 | if (m_userOverrides.find(steamId) != m_userOverrides.end())
270 | {
271 | m_userOverrides.erase(steamId);
272 | }
273 |
274 | RefreshActiveOverrides();
275 |
276 | return true;
277 | }
278 |
279 | static cell_t OnRefreshActiveOverrides(IPluginContext* pContext, const cell_t* params)
280 | {
281 | if (!vm_enable.GetBool())
282 | {
283 | return false;
284 | }
285 |
286 | RefreshActiveOverrides();
287 | return true;
288 | }
289 |
290 | static cell_t LoadPlayerAdjustment(IPluginContext* pContext, const cell_t* params)
291 | {
292 | if (!vm_enable.GetBool())
293 | {
294 | return false;
295 | }
296 |
297 | char* adjustedSteamIdString;
298 |
299 | int adjuster = params[1];
300 | pContext->LocalToString(params[2], &adjustedSteamIdString);
301 |
302 | int volume = params[3];
303 |
304 | // If the volume is invalid, return false
305 | if (volume < 0)
306 | {
307 | return false;
308 | }
309 |
310 | uint64_t adjusterSteamId = GetClientSteamId(adjuster);
311 | uint64_t adjustedSteamId = std::stoull(adjustedSteamIdString);
312 | if (adjusterSteamId <= 0 || adjustedSteamId <= 0)
313 | {
314 | return false;
315 | }
316 |
317 | auto playerAdjusted = m_userOverrides.find(adjusterSteamId);
318 |
319 | UserOverride uo;
320 | uo.steamId = adjustedSteamId;
321 | uo.level = volume;
322 |
323 | if (playerAdjusted != m_userOverrides.end())
324 | {
325 | bool existingFound = false;
326 | for (int i = 0; i < playerAdjusted->second.size(); i++)
327 | {
328 | if (playerAdjusted->second.at(i).steamId == adjustedSteamId)
329 | {
330 | playerAdjusted->second.at(i).level = uo.level;
331 | existingFound = true;
332 | break;
333 | }
334 | }
335 |
336 | // If we did not find an existing override, push it
337 | if (!existingFound)
338 | {
339 | playerAdjusted->second.push_back(uo);
340 | }
341 | }
342 | else
343 | {
344 | m_userOverrides.insert({ adjusterSteamId, std::vector{uo} });
345 | }
346 |
347 | return true;
348 | }
349 |
350 | static cell_t OnPlayerAdjustVolume(IPluginContext* pContext, const cell_t* params)
351 | {
352 | if (!vm_enable.GetBool())
353 | {
354 | return false;
355 | }
356 |
357 | int adjuster = params[1];
358 | int adjusted = params[2];
359 | int volume = params[3];
360 |
361 | uint64_t adjusterSteamId = GetClientSteamId(adjuster);
362 | uint64_t adjustedSteamId = GetClientSteamId(adjusted);
363 | if (adjusterSteamId <= 0 || adjustedSteamId <= 0)
364 | {
365 | return false;
366 | }
367 |
368 | auto playerAdjusted = m_userOverrides.find(adjusterSteamId);
369 |
370 | bool isReset = volume < 0;
371 | if (isReset)
372 | {
373 | // Do nothing, they have no overrides and they're setting a preference as normal
374 | if (playerAdjusted == m_userOverrides.end())
375 | {
376 | return true;
377 | }
378 |
379 | auto existingOverrides = playerAdjusted->second;
380 |
381 | for (int i = 0; i < existingOverrides.size(); i++)
382 | {
383 | if (existingOverrides.at(i).steamId == adjustedSteamId)
384 | {
385 | playerAdjusted->second.erase(playerAdjusted->second.begin() + i);
386 | break;
387 | }
388 | }
389 | }
390 | else
391 | {
392 | UserOverride uo;
393 | uo.steamId = adjustedSteamId;
394 | uo.level = volume;
395 |
396 | if (playerAdjusted != m_userOverrides.end())
397 | {
398 | bool existingFound = false;
399 | for (int i = 0; i < playerAdjusted->second.size(); i++)
400 | {
401 | if (playerAdjusted->second.at(i).steamId == adjustedSteamId)
402 | {
403 | playerAdjusted->second.at(i).level = uo.level;
404 | existingFound = true;
405 | break;
406 | }
407 | }
408 |
409 | // If we did not find an existing override, push it
410 | if (!existingFound)
411 | {
412 | playerAdjusted->second.push_back(uo);
413 | }
414 | }
415 | else
416 | {
417 | m_userOverrides.insert({ adjusterSteamId, std::vector{uo} });
418 | }
419 | }
420 |
421 | RefreshActiveOverrides();
422 |
423 | return true;
424 | }
425 |
426 | static cell_t OnPlayerGlobalAdjust(IPluginContext* pContext, const cell_t* params)
427 | {
428 | if (!vm_enable.GetBool())
429 | {
430 | return false;
431 | }
432 |
433 | int adjuster = params[1];
434 | int volume = params[2];
435 |
436 | uint64_t adjusterSteamId = GetClientSteamId(adjuster);
437 | if (adjusterSteamId <= 0 || adjusterSteamId <= 0)
438 | {
439 | return false;
440 | }
441 |
442 | auto globalOverride = m_userGlobalOverrides.find(adjusterSteamId);
443 |
444 | bool isReset = volume < 0;
445 | if (isReset)
446 | {
447 | // Do nothing, they have no overrides and they're setting a preference as normal
448 | if (globalOverride == m_userGlobalOverrides.end())
449 | {
450 | return true;
451 | }
452 |
453 | m_userGlobalOverrides.erase(adjusterSteamId);
454 | }
455 | else
456 | {
457 | if (globalOverride != m_userGlobalOverrides.end())
458 | {
459 | m_userGlobalOverrides[adjusterSteamId] = volume;
460 | }
461 | else
462 | {
463 | m_userGlobalOverrides.insert({ adjusterSteamId, volume });
464 | }
465 | }
466 |
467 | RefreshActiveOverrides();
468 |
469 | return true;
470 | }
471 |
472 | const sp_nativeinfo_t g_Natives[] =
473 | {
474 | {"LoadPlayerAdjustment", LoadPlayerAdjustment},
475 | {"OnPlayerAdjustVolume", OnPlayerAdjustVolume},
476 | {"RefreshActiveOverrides", OnRefreshActiveOverrides},
477 | {"ClearClientOverrides", OnClearClientOverrides},
478 | {"GetClientOverride", OnGetClientOverride},
479 | {"OnPlayerGlobalAdjust", OnPlayerGlobalAdjust},
480 | {nullptr, nullptr},
481 | };
482 |
483 | void VoiceManagerExt::SDK_OnAllLoaded()
484 | {
485 | SM_GET_LATE_IFACE(SDKTOOLS, g_pSDKTools);
486 | SM_GET_LATE_IFACE(BINTOOLS, g_pBinTools);
487 |
488 | g_pServer = g_pSDKTools->GetIServer();
489 | }
490 |
491 | bool VoiceManagerExt::SDK_OnLoad(char* error, size_t maxlength, bool late)
492 | {
493 | sharesys->AddDependency(myself, "sdktools.ext", true, true);
494 | sharesys->AddDependency(myself, "bintools.ext", true, true);
495 | plsys->AddPluginsListener(this);
496 | sharesys->AddNatives(myself, g_Natives);
497 |
498 | char conf_error[255];
499 | if (!gameconfs->LoadGameConfigFile("voicemanager", &g_pGameConf, conf_error, sizeof(conf_error)))
500 | {
501 | if (conf_error[0])
502 | {
503 | snprintf(error, maxlength, "Could not read config file voicemanager.txt: %s", conf_error);
504 | }
505 |
506 | return false;
507 | }
508 |
509 | CDetourManager::Init(smutils->GetScriptingEngine(), g_pGameConf);
510 |
511 | bool bDetoursInited = false;
512 | CREATE_DETOUR_STATIC(SV_BroadcastVoiceData, "SV_BroadcastVoiceData", bDetoursInited);
513 |
514 | for (int i = 1; i <= playerhelpers->GetMaxClients(); i++)
515 | {
516 | g_voiceManagerClientStates[i] = VoiceManagerClientState();
517 | }
518 |
519 | return true;
520 | }
521 |
522 | bool VoiceManagerExt::RegisterConCommandBase(ConCommandBase* pCommand)
523 | {
524 | META_REGCVAR(pCommand);
525 | return true;
526 | }
527 |
528 | bool VoiceManagerExt::SDK_OnMetamodLoad(ISmmAPI* ismm, char* error, size_t maxlen, bool late)
529 | {
530 | GET_V_IFACE_CURRENT(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION);
531 | ConVar_Register(0, this);
532 |
533 | return true;
534 | }
535 |
--------------------------------------------------------------------------------
/extension/extension.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #ifndef _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_
4 | #define _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_
5 |
6 | #include