diff --git a/.gitignore b/.gitignore index 85ccd4a..8ac77ed 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ /Release p2sm.vcxproj.user /.idea +p2sm.sln.DotSettings.user +copy.bat +/Build diff --git a/LICENSE_P2MM b/LICENSE_P2MM new file mode 100644 index 0000000..be101c7 --- /dev/null +++ b/LICENSE_P2MM @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Portal 2: Multiplayer Mod Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE b/LICENSE_P2SM++ similarity index 100% rename from LICENSE rename to LICENSE_P2SM++ diff --git a/README.md b/README.md index 6c451f4..52125c9 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,56 @@ # ***P2SourceModPlusPlus*** (Formerly ***WorkshopStopper9000***) -## ***A Source Engine Plugin for Portal 2 Source Mods that fixes and adds some QoL features for them!*** +## ***A Source Engine Plugin for Portal 2 SourceMods that fixes and adds some QoL features for them!*** + +> [!NOTE] +> I originally planned `WorkshopStopper9000` to fix the workshop downloading issue, but I hope to add more features and fixes, so now it has become `P2SourceModPlusPlus`. I plan to implement some good features the community will use until the fated day Portal 2: Community Edition (P2:CE) is released. This plugin will act as a temporary solution for mod makers. Once P2:CE's public beta and/or release occurs, I will probably not work on this anymore, as most of the stuff here is fixed over there. + +> [!WARNING] +> This plugin was designed in mind for SourceMods, meaning mods loaded through the `sourcemods` folder in Steam. This is not for Portal 2 mods on Steam's store, which can be installed through Steam. +> +> While this plugin should be ok for a Steam Portal 2 mod, note that: +> +> 1. I do not know if Valve will allow such modifications on the Steam platform, especially if it involves one of their games. +> 2. Compatibility will not be 100%, so expect bugs or other issues. +> 3. If you are using this plugin with your mod, I am not responsible if anything bad happens to your mod's page on Steam. +> +> Ideally, if you wish to have some of these features but want to be able to have your mod on Steam, wait for P2:CE to release rather than using this. +> +> I will, in the future, try to add custom elements involving interactions with Steam's API, like achievements, but for now, I don't expect this to work correctly with Steam mods. Plus, in the end, P2:CE will do this properly in the future. + +### Features Implemented/Potentially Plan to Implement: + +* Stopped workshop maps from being downloaded by the game because the base game's workshop folder isn't on path for SourceMods. Because of this, it tries to get ***ALL*** of them at once, which causes the game to be unstable and occasionally crash. +* Fixed human NPC movement so they don't jitter when they move. `cl_localnetworkbackdoor` is automatically set to 0 without manual adjustment. +* Made `r_screenoverlay` ConVar be enabled by default and not need cheats so `env_screenoverlay` entities can work correctly without `sv_cheats` being needed. +* Added a ConVar to enable instant respawns for multiplayer. Skips those moments in the third-person death cam before respawn. +* Fixed linked portal doors causing crashes on multiplayer when players enter them. +* Increased the max VScript VM runtime so that VScript operations like `for` and `while` loops run without problems. This helps give more leeway in doing things in VScript without the engine complaining and shutting down the rest of the script, spewing errors in the console. While it depends on how the script was programmed and what it is doing, I occasionally notice this, and this fix helps eliminate it. +* Fixed the player flashlight impulse ConCommand, so it no longer requires cheats to be enabled. All that is needed is a key bound to `impulse 100`. However, the flashlight doesn't work perfectly with portals, snapping to odd positions when moving through and unable to shine through portals. +* **(NOT IMPLEMENTED)** Add a ConVar to disable multiplayer death icons. +* **(NOT IMPLEMENTED)** Allow more than one env_projectedtexture to be on at once. However, the engine limit of eight is still enforced! That can not be fixed. +* **(NOT IMPLEMENTED)** Option to turn on or off puzzle maker functionality. Most mods don't use the puzzle maker, but it still loads in. The plugin will try to disable as much of it as possible so the console is not filled with any puzzle-maker-related errors, messages, or other additional bloat. +* **(NOT IMPLEMENTED)** Custom Discord RPC for Portal 2 SourceMods. +* **(NOT IMPLEMENTED)** Additional achievement support and features. Like custom achievements that span multiple maps and have more parts than Door Prize and Smash TV. +* **(NOT IMPLEMENTED)** As informed by MarvinG, `env_sun` disappears when over player crosshair. I have not been able to confirm this and have never noticed it in general. +* **(NOT IMPLEMENTED)** When in water, the player is pushed to the world's origin. +* **(NOT IMPLEMENTED)** Fix SourceMods only getting a small 2 MB of memory for material system render context allocation, increasing it to 6 MB. This could happen if this gets ported to SAR or P2SRM. +* **(NOT IMPLEMENTED)** Linux support. +* **(NOT IMPLEMENTED)** Implement some VScript functions and hooks to capture specific events, turn ConVars on or off, and fix and add new VScript functions. -> I originally planned `WorkshopStopper9000` to simply fix the workshop downloading issue, but I hope to add a bit more features and fixes so now it has become `P2SourceModPlusPlus`. Hopefully I can implement some good features that will be used by the community until the fated day Portal 2: Community Edition is released. Basically this is gonna be a temporarily solution for mod makers. - -### Features Implemented/Plan to Implement: +### Download and Install Instructions: -* Stopped workshop maps from being downloaded by the game because the workshop folder isn't on path for Source Mods. Because of this it tries to get ***ALL*** of them at once, which caused the game to be unstable and on occasion crash. -* **(NOT IMPLEMENTED)** Fix human NPC movement so they don't jitter when they move. -* **(NOT IMPLEMENTED)** Option to turn on or off Puzzlemaker functionality. Most mods don't use the puzzlemaker, but it still loads in. The plugin will try to disable as much of it as possible so console is not filled with any puzzlemaker related errors or messages or any other additional bloat. -* **(NOT IMPLEMENTED)** Custom Discord RPC for Portal 2 Source Mods. -* **(NOT IMPLEMENTED)** Additional achievement support and features. Like custom achievements that span multiple maps that have more parts than Door Prize and Smash TV. -* **(NOT IMPLEMENTED)** Use of player flashlight without needing cheats enabled. -* **(NOT IMPLEMENTED)** By default have env_screenoverlay entities not need the `r_screenoverlay` ConVar be enabled. +Download the latest `addons.zip` under the `Releases` tab on GitHub and extract its folder into the base directory of your SourceMod. Portal 2 should automatically start the plugin up with the game. You should be able to see log messages from the plugin in the developer console if it is loaded. That's it. Please create an Issue post on the plugin's repository if there are any issues. I'll get to it within the next 20 years. (Hopefully not). -### Download and Install Instructions: +> [!IMPORTANT] +> If Portal 2: Multiplayer Mod is running along with P2SM++, P2SM++'s hooks, configs, and settings will take priority over Portal 2: Multiplayer Mod's. If P2SM++ patches the same things that P2:MM does, P2:MM ignores patching as P2SM++ has already done it. Note: P2:MM assumes that it is being loaded second based on how it is loaded normally with the launcher and with a `tempcontent` folder. -Download the latest `addons.zip` under the `Releases` tab on GitHub and extract its folder into the base directory of your Source Mod. Portal 2 should automatically start the plugin up with the game and thus stop the game from downloading workshop maps. You should be able to see a message in the developer console from the plugin if it loaded. That's it. This took about 20 minutes to make, so please create an Issue post on the plugin's repository if there are any issues. I'll get to it within the next 20 years. +**As of writing, 11/10/2024, the plugin is only compatible with Windows. The signature scanner it uses still needs to be compatible with Linux, plus overall, the way the P2:MM plugin is structured (this plugin is built off of some of the P2:MM plugin code as a base), it's impossible to compile for Linux anyway. Literally 1984, I know, but I don't have enough time or experience to figure it out by myself. Linux users should still be able to get away with using Proton or similar to be able to use the plugin's Windows `.dll`. Hopefully, one day, this can be fixed...** -**As of writing, 11/10/2024, the plugin is only compatible with Windows. The signature scanner it uses still needs to be compatible with Linux, plus overall, the way the P2:MM plugin is structured (this plugin is built off of some of P2:MM plugin code as a base), it's impossible to compile for Linux anyway. Literally 1984, I know, but I don't have enough time or experience to figure it out by myself. Linux users should still be able to get away with using Proton or similar to be able to use the plugin's Windows `.dll`. Hopefully, one day, this can be fixed...** +**Update 3/24/2025: I plan to move this over to a forked version of SAR using its code base as a base for this plugin. Its structure is much better, it has more engine functionality accessible in the plugin, and it compiles for Linux. However, do not expect this switch to happen anytime soon. Thank you for being so patient.** *** -Please credit either Orsell/OrsellGaming, Nanoman2525, and NULLderef, or the `Portal 2: Multiplayer Mod Team` if you use this plugin or its code in any way with your Source Mod. -You must also include this repository and P2:MM's license file with the plugin. +Please credit Orsell/OrsellGaming, Nanoman2525, NULLderef, and/or the `Portal 2: Multiplayer Mod Team` if you use this plugin or its code in any way with your SourceMod. +You'll also need to include this repository and P2:MM's license file with the plugin. Both licenses will be included with the `addon.zip` file you download to get the plugin. *** - -```c++ -MIT License - -Copyright (c) 2024 Portal 2: Multiplayer Mod Team - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` diff --git a/globals.cpp b/globals.cpp new file mode 100644 index 0000000..a8dc314 --- /dev/null +++ b/globals.cpp @@ -0,0 +1,245 @@ +//===========================================================================// +// +// Author: Orsell +// Purpose: Global functions & variables used repeatedly throughout the plugin +// +//===========================================================================// +#include "globals.hpp" + +#include "sdk.hpp" +#include "p2sm.hpp" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//--------------------------------------------------------------------------------- +// Interfaces from the engine. +//--------------------------------------------------------------------------------- +IVEngineServer* engineServer = nullptr; +IVEngineClient* engineClient = nullptr; +CGlobalVars* g_pGlobals = nullptr; +IPlayerInfoManager* g_pPlayerInfoManager = nullptr; +// IScriptVM* g_pScriptVM; +//IServerTools* g_pServerTools = nullptr; +// IGameEventManager2* g_pGameEventManager_; +//IServerPluginHelpers* g_pPluginHelpers = nullptr; +// IFileSystem* g_pFileSystem; + +static ConVar p2sm_developer("p2sm_developer", "0", FCVAR_HIDDEN, "Enable for developer messages."); + +//--------------------------------------------------------------------------------- +// Purpose: Logging for the plugin by adding a prefix and line break. +// Max character limit of 1024 characters. +// level: 0 = Msg/DevMsg, 1 = Warning/DevWarning, 2 = Error WILL STOP ENGINE! +//--------------------------------------------------------------------------------- +void Log(const LogLevel level, const bool dev, const char* pMsgFormat, ...) +{ + if (dev && !p2sm_developer.GetBool() && level != ERRORR) return; // Stop developer messages when p2mm_developer isn't enabled. + + // Take our log message and format any arguments it has into the message. + va_list argPtr; + char szFormattedText[1024] = { 0 }; + va_start(argPtr, pMsgFormat); + V_vsnprintf(szFormattedText, sizeof(szFormattedText), pMsgFormat, argPtr); + va_end(argPtr); + + // Add a header to the log message. + char completeMsg[1024] = { 0 }; + V_snprintf(completeMsg, sizeof(completeMsg), "(P2SourceMod++ PLUGIN): %s\n", szFormattedText); + + switch (level) + { + case (INFO): + ConColorMsg(P2SMPLUSPLUS_PLUGIN_CONSOLE_COLOR, completeMsg); + return; + case (WARNING): + Warning(completeMsg); + return; + case (ERRORR): + Warning("(P2SourceMod++ PLUGIN):\n!!!ERROR ERROR ERROR!!!:\nA FATAL ERROR OCCURED WITH THE ENGINE:\n%s", completeMsg); + Error(completeMsg); + return; + default: + Warning("(P2SourceMod++ PLUGIN): Log level set outside of INFO-ERRORR, \"%i\". Defaulting to level INFO.\n", level); + ConColorMsg(P2SMPLUSPLUS_PLUGIN_CONSOLE_COLOR, completeMsg); + } +} + +//--------------------------------------------------------------------------------- +// Purpose: Get the player's entity index by their userid. +//--------------------------------------------------------------------------------- +int UserIDToPlayerIndex(const int userid) +{ + for (int i = 1; i <= MAX_PLAYERS; i++) + { + const edict_t* pEdict = nullptr; + if (i >= 0 && i < g_pGlobals->maxEntities) + pEdict = (g_pGlobals->pEdicts + i); + + if (engineServer->GetPlayerUserId(pEdict) == userid) + return i; + } + return 0; // Return 0 if the index can't be found +} + +//--------------------------------------------------------------------------------- +// Purpose: Get player username by their entity index. +//--------------------------------------------------------------------------------- +const char* GetPlayerName(const int playerIndex) +{ + if (playerIndex <= 0 || playerIndex > MAX_PLAYERS) + { + Log(WARNING, true, "Invalid index passed to GetPlayerName: %i! Returning ""!", playerIndex); + return ""; + } + + player_info_t playerInfo; + if (!engineServer->GetPlayerInfo(playerIndex, &playerInfo)) + { + Log(WARNING, true, R"(Couldn't retrieve playerInfo of player index in GetPlayerName: %i! Returning ""!)", playerIndex); + return ""; + } + + return playerInfo.name; +} + +//--------------------------------------------------------------------------------- +// Purpose: Get the account ID component of player SteamID by the player's entity index. +//--------------------------------------------------------------------------------- +int GetSteamID(const int playerIndex) +{ + edict_t* pEdict = nullptr; + if (playerIndex >= 0 && playerIndex < MAX_PLAYERS) + pEdict = (g_pGlobals->pEdicts + playerIndex); + + if (!pEdict) + return -1; + + player_info_t playerInfo; + if (!engineServer->GetPlayerInfo(playerIndex, &playerInfo)) + return -1; + + const CSteamID* pSteamID = engineServer->GetClientSteamID(pEdict); + if (!pSteamID || pSteamID->GetAccountID() == 0) + return -1; + + return pSteamID->GetAccountID(); +} + +//--------------------------------------------------------------------------------- +// Purpose: Self-explanatory. +//--------------------------------------------------------------------------------- +int GetConVarInt(const char* cvName) +{ + const ConVar* pVar = g_pCVar->FindVar(cvName); + if (!pVar) + { + Log(WARNING, false, R"(Could not find ConVar: "%s"! Returning ""!)", cvName); + return -1; + } + + return pVar->GetInt(); +} + +//--------------------------------------------------------------------------------- +// Purpose: Self-explanatory. +//--------------------------------------------------------------------------------- +const char* GetConVarString(const char* cvName) +{ + const ConVar* pVar = g_pCVar->FindVar(cvName); + if (!pVar) + { + Log(WARNING, false, R"(Could not find ConVar: "%s"! Returning ""!)", cvName); + return ""; + } + + return pVar->GetString(); +} + +//--------------------------------------------------------------------------------- +// Purpose: Self-explanatory. +//--------------------------------------------------------------------------------- +void SetConVarInt(const char* cvName, const int newValue) +{ + ConVar* pVar = g_pCVar->FindVar(cvName); + if (!pVar) + { + Log(WARNING, false, "Could not set ConVar: \"%s\"!", cvName); + return; + } + pVar->SetValue(newValue); +} + +//--------------------------------------------------------------------------------- +// Purpose: Self-explanatory. +//--------------------------------------------------------------------------------- +void SetConVarString(const char* cvName, const char* newValue) +{ + ConVar* pVar = g_pCVar->FindVar(cvName); + if (!pVar) + { + Log(WARNING, false, R"(Could not set ConVar: "%s"!)", cvName); + return; + } + pVar->SetValue(newValue); + return; +} + +//--------------------------------------------------------------------------------- +// Purpose: Returns true if player is a bot. +//--------------------------------------------------------------------------------- +bool IsBot(const int playerIndex) +{ + player_info_t playerInfo; + if (!engineServer->GetPlayerInfo(playerIndex, &playerInfo)) + { + Log(WARNING, true, R"(Couldn't retrieve player info of player index "%i" in IsBot!)", playerIndex); + return false; + } + + return playerInfo.fakeplayer; +} + +//--------------------------------------------------------------------------------- +// Purpose: Get the number of bots in the server. +//--------------------------------------------------------------------------------- +int GetBotCount() +{ + int b = 0; + FOR_ALL_PLAYERS(i) + { + if (IsBot(i)) + b++; + } + return b; +} + +//--------------------------------------------------------------------------------- +// Purpose: Get the current player count on the server. +//--------------------------------------------------------------------------------- +int CURPLAYERCOUNT() +{ + int playerCount = 0; + for (int i = 1; i <= MAX_PLAYERS; i++) + { + if (UTIL_PlayerByIndex(i)) + playerCount++; + } + return playerCount; +} + +//--------------------------------------------------------------------------------- +// Purpose: Entity index to script handle. +//--------------------------------------------------------------------------------- +HSCRIPT INDEXHANDLE(const int iEdictNum) +{ + edict_t* pEdict = INDEXENT(iEdictNum); + if (!pEdict->GetUnknown()) + return nullptr; + + CBaseEntity* pBaseEntity = pEdict->GetUnknown()->GetBaseEntity(); + if (!pBaseEntity) + return nullptr; + + return CBaseEntity__GetScriptInstance(pBaseEntity); +} \ No newline at end of file diff --git a/globals.hpp b/globals.hpp new file mode 100644 index 0000000..fca8d74 --- /dev/null +++ b/globals.hpp @@ -0,0 +1,238 @@ +//===========================================================================// +// +// Author: Orsell +// Purpose: Global functions & variables used repeatedly throughout the plugin +// +//===========================================================================// + +#pragma once + +#include "eiface.h" +#include "cdll_int.h" +#include "vscript/ivscript.h" +#include "engine/iserverplugin.h" +#include "game/server/iplayerinfo.h" +#include "public/steam/steamclientpublic.h" +#include "irecipientfilter.h" + +#include "scanner.hpp" + +#include + +// Forward references for class types unable to be accessed directly. +class CBasePlayer; +class CPortal_Player; +class CBaseEntity; +class CWorkshopManager; +class CEnvProjectedTexture; + +// Color macros for console printing. +#define P2SMPLUSPLUS_PLUGIN_VERSION "1.1.0" // Update this when a new version of the plugin is released. +#define P2SMPLUSPLUS_PLUGIN_CONSOLE_COLOR Color(100, 192, 252, 255) // Light Blue + +#define CUR_MAPFILE_NAME STRING(g_pGlobals->mapname) +#define MAX_PLAYERS g_pGlobals->maxClients + +// Used for autocomplete console commands. +#define COMMAND_COMPLETION_MAXITEMS 64 +#define COMMAND_COMPLETION_ITEM_LENGTH 64 + +// A macro to iterate through all ConVars and ConCommand in the game. +// Thanks to Nanoman2525 for this. +#define FOR_ALL_CONSOLE_COMMANDS(pCommandVarName) \ + ConCommandBase* m_pConCommandList = *reinterpret_cast((uintptr_t)g_pCVar + 0x30); /* CCvar::m_pConCommandList */ \ + for (ConCommandBase* (pCommandVarName) = m_pConCommandList; \ + pCommandVarName; (pCommandVarName) = *reinterpret_cast(reinterpret_cast(pCommandVarName) + 0x04)) /* ConCommandBase::m_pNext (private variable) */ + +// Macro to iterate through all players on the server. +#define FOR_ALL_PLAYERS(i) \ + for (int (i) = 1; (i) <= CURPLAYERCOUNT(); (i)++) + +// Log levels for all log functions. +typedef enum LogLevels : std::uint8_t +{ + INFO = 0, + WARNING, + ERRORR // Have to use ERRORR because of macro by the Windows API. Yeah I know, I hate it too. +} LogLevel; + + +// Player team enum. +enum : std::uint8_t +{ + TEAM_SINGLEPLAYER = 0, + TEAM_SPECTATOR, + TEAM_RED, + TEAM_BLUE +}; + +// ClientPrint msg_dest macros. +enum : std::uint8_t +{ + HUD_PRINTNOTIFY = 1, // Works same as HUD_PRINTCONSOLE + HUD_PRINTCONSOLE, + HUD_PRINTTALK, + HUD_PRINTCENTER +}; + +//--------------------------------------------------------------------------------- +// Public variables. +//--------------------------------------------------------------------------------- +// Windows API Window Handle +static HWND hWnd; + +//--------------------------------------------------------------------------------- +// Interfaces from the engine. +//--------------------------------------------------------------------------------- +extern IVEngineServer* engineServer; +extern IVEngineClient* engineClient; +extern CGlobalVars* g_pGlobals; +extern IPlayerInfoManager* g_pPlayerInfoManager; +// extern IScriptVM* g_pScriptVM; +// extern IServerTools* g_pServerTools; +// extern IGameEventManager2* g_pGameEventManager_; +// extern IServerPluginHelpers* g_pPluginHelpers; +// extern IFileSystem* g_pFileSystem; + +//--------------------------------------------------------------------------------- +// UTIL functions. +//--------------------------------------------------------------------------------- +int UserIDToPlayerIndex(int userid); +const char* GetPlayerName(int playerIndex); +int GetSteamID(int playerIndex); +int GetConVarInt(const char* cvName); +const char* GetConVarString(const char* cvName); +void SetConVarInt(const char* cvName, int newValue); +void SetConVarString(const char* cvName, const char* newValue); +bool IsBot(int playerIndex); +int GetBotCount(); +int CURPLAYERCOUNT(); +HSCRIPT INDEXHANDLE(int iEdictNum); + +// Logging function. +void Log(LogLevel level, bool dev, const char* pMsgFormat, ...); + +//--------------------------------------------------------------------------------- +// Player recipient filter. +//--------------------------------------------------------------------------------- +class CPlayerFilter : public IRecipientFilter +{ +public: + CPlayerFilter(): recipients{} { recipientCount = 0; } + ~CPlayerFilter() override = default; + + bool IsReliable() const override { return false; } + bool IsInitMessage() const override { return false; } + + int GetRecipientCount() const override { return recipientCount; } + + int GetRecipientIndex(const int slot) const override + { + return (slot < 0 || slot >= recipientCount) ? -1 : recipients[slot]; + } + + void AddPlayer(const int playerIndex) + { + if (recipientCount < 256) + { + recipients[recipientCount] = playerIndex; + recipientCount++; + } + } + +private: + int recipients[256]; + int recipientCount; +}; + +// If String Equals String helper function. Taken from utils.h. +inline bool FStrEq(const char* sz1, const char* sz2) +{ + return (V_stricmp(sz1, sz2) == 0); +} + +// If String Has Substring helper function. Taken from utils.h. +inline bool FSubStr(const char* sz1, const char* search) +{ + return (V_strstr(sz1, search)); +} + +//--------------------------------------------------------------------------------- +// Purpose: Entity edict to entity index. Taken from utils.h. +//--------------------------------------------------------------------------------- +inline int EDICTINDEX(const edict_t* pEdict) +{ + if (!pEdict) + return 0; + const int edictIndex = pEdict - g_pGlobals->pEdicts; + Assert(edictIndex < MAX_EDICTS && edictIndex >= 0); + return edictIndex; +} + +//--------------------------------------------------------------------------------- +// Purpose: Entity to entity index. +//--------------------------------------------------------------------------------- +inline int ENTINDEX(CBaseEntity* pEnt) +{ + static auto ENTINDEX_ = reinterpret_cast(Memory::Scanner::Scan(SERVERDLL, "55 8B EC 8B 45 ?? 85 C0 74 ?? 8B 40 ?? 85 C0 74 ?? 8B 0D")); + return ENTINDEX_(pEnt); +} + +//--------------------------------------------------------------------------------- +// Purpose: Entity index to entity edict. Taken from utils.h. +//--------------------------------------------------------------------------------- +inline edict_t* INDEXENT(const int iEdictNum) +{ + Assert(iEdictNum >= 0 && iEdictNum < MAX_EDICTS); + if (g_pGlobals->pEdicts) + { + edict_t* pEdict = g_pGlobals->pEdicts + iEdictNum; + if (pEdict->IsFree()) + return nullptr; + return pEdict; + } + return nullptr; +} + +//--------------------------------------------------------------------------------- +// Purpose: Returns the current game directory. Ex. portal2 +//--------------------------------------------------------------------------------- +inline const char* GetGameMainDir() +{ + return CommandLine()->ParmValue("-game", CommandLine()->ParmValue("-defaultgamedir", "portal2")); +} + +//--------------------------------------------------------------------------------- +// Purpose: Returns the current root game directory. Ex. Portal 2 +//--------------------------------------------------------------------------------- +inline const char* GetGameRootDir() +{ + static char baseDir[MAX_PATH] = { 0 }; + const std::string fullGameDirectoryPath = engineClient->GetGameDirectory(); + + // Find last two backslashes + const size_t firstSlash = fullGameDirectoryPath.find_last_of('\\'); + const size_t secondSlash = fullGameDirectoryPath.find_last_of('\\', firstSlash - 1); + const std::string tempBaseDir = fullGameDirectoryPath.substr(secondSlash + 1, firstSlash - secondSlash - 1); + V_strcpy(baseDir, tempBaseDir.c_str()); // Copy to static buffer + + return baseDir; +} + +//--------------------------------------------------------------------------------- +// Purpose: Returns true if a game session is running. +//--------------------------------------------------------------------------------- +inline bool IsGameActive() +{ + const bool m_activeGame = **Memory::Scanner::Scan(ENGINEDLL, "C6 05 ?? ?? ?? ?? ?? C6 05 ?? ?? ?? ?? ?? 0F B6 96", 2); + return m_activeGame; +} + +//--------------------------------------------------------------------------------- +// Purpose: Returns true if a game session is shutting down or has been shutdown. +//--------------------------------------------------------------------------------- +inline bool IsGameShutdown() +{ + const bool bIsGameShuttingDown = reinterpret_cast(Memory::Scanner::Scan(ENGINEDLL, "B8 05 00 00 00 39 05"))(); + return bIsGameShuttingDown; +} diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 35793de..0000000 --- a/main.cpp +++ /dev/null @@ -1,183 +0,0 @@ -//===========================================================================// -// -// Authors: Orsell & Nanoman2525 & NULLderef -// Purpose: P2SourceModPlusPlus plugin -// -//===========================================================================// - -#include "main.hpp" -#include "scanner.hpp" - -#include "cdll_int.h" -#include "minhook/include/MinHook.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -void Log(int level, bool dev, const char* pMsgFormat, ...); -static ConVar p2sm_developer("p2sm_developer", "0", FCVAR_HIDDEN, "Enable for developer messages."); - -//--------------------------------------------------------------------------------- -// The plugin is a static singleton that is exported as an interface -//--------------------------------------------------------------------------------- -static CP2SMPlusPlusPlugin g_P2SMPlusPlusPlugin; -EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CP2SMPlusPlusPlugin, IServerPluginCallbacks, INTERFACEVERSION_ISERVERPLUGINCALLBACKS, g_P2SMPlusPlusPlugin); - -//--------------------------------------------------------------------------------- -// Purpose: Logging for the plugin by adding a prefix and line break. -// Max character limit of 1024 characters. -// level: 0 = Msg/DevMsg, 1 = Warning/DevWarning, 2 = Error WILL STOP ENGINE! -//--------------------------------------------------------------------------------- -void Log(int level, bool dev, const char* pMsgFormat, ...) -{ - if (dev && !p2sm_developer.GetBool() && level != 2) return; // Stop developer messages when p2mm_developer isn't enabled. - - // Take our log message and format any arguments it has into the message. - va_list argPtr; - char szFormattedText[1024] = { 0 }; - va_start(argPtr, pMsgFormat); - V_vsnprintf(szFormattedText, sizeof(szFormattedText), pMsgFormat, argPtr); - va_end(argPtr); - - // Add a header to the log message. - char completeMsg[1024] = { 0 }; - V_snprintf(completeMsg, sizeof(completeMsg), "(P2SourceModPlusPlus PLUGIN): %s\n", szFormattedText); - - switch (level) - { - case 0: - ConColorMsg(P2SMPLUSPLUS_PLUGIN_CONSOLE_COLOR, completeMsg); - return; - case 1: - Warning(completeMsg); - return; - case 2: - Warning("(P2SourceModPlusPlus PLUGIN):\n!!!ERROR ERROR ERROR!!!:\nA FATAL ERROR OCCURED WITH THE ENGINE:\n%s", completeMsg); - Error(completeMsg); - return; - default: - Warning("(P2SourceModPlusPlus PLUGIN): Log level set outside of 0-1, \"%i\". Defaulting to level 0.\n", level); - ConColorMsg(P2SMPLUSPLUS_PLUGIN_CONSOLE_COLOR, completeMsg); - } -} - -//--------------------------------------------------------------------------------- -// Purpose: constructor -//--------------------------------------------------------------------------------- -CP2SMPlusPlusPlugin::CP2SMPlusPlusPlugin() -{ - this->m_bPluginLoaded = false; - this->m_bNoUnload = false; // If we fail to load, we don't want to run anything on Unload() to get what the error was. -} - -//--------------------------------------------------------------------------------- -// Purpose: Description of plugin outputted when the "plugin_print" console command is executed. -//--------------------------------------------------------------------------------- -const char* CP2SMPlusPlusPlugin::GetPluginDescription(void) -{ - return "P2SourceModPlusPlus Plugin | Plugin Version: " P2SMPLUSPLUS_PLUGIN_VERSION; -} - -//--------------------------------------------------------------------------------- -// Purpose: Stop the UGC manager from automatically download workshop maps. -//--------------------------------------------------------------------------------- -class CUGCFileRequestManager; -static void (__fastcall* CUGCFileRequestManager__Update_orig)(CUGCFileRequestManager* thisPtr); -static void __fastcall CUGCFileRequestManager__Update_hook(CUGCFileRequestManager* thisPtr) { } // Simply do nothing so that nothing gets updated and therefore nothing gets downloaded. - -//--------------------------------------------------------------------------------- -// Purpose: Called when the plugin is loaded, initialization process. -// Loads the interfaces we need from the engine and applies our patches. -//--------------------------------------------------------------------------------- -bool CP2SMPlusPlusPlugin::Load(CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory) -{ - if (m_bPluginLoaded) - { - Log(1, false, "Already loaded!"); - m_bNoUnload = true; - return false; - } - - Log(0, false, "Loading plugin..."); - - Log(0, true, "Connecting tier libraries..."); - ConnectTier1Libraries(&interfaceFactory, 1); - ConnectTier2Libraries(&interfaceFactory, 1); - ConVar_Register(0); - - // big ol' try catch because game has a TerminateProcess handler for exceptions... - // why this wasn't here is mystifying, - 10/2024 NULLderef - try { - // MinHook initialization and hooking - Log(0, true, "Initializing MinHook and hooking functions..."); - MH_Initialize(); - - // STOP THEM WORKSHOP DOWNLOADS: MinHook Edition -#if _WIN32 - MH_CreateHook( - Memory::Scanner::Scan(CLIENTDLL, "55 8B EC 81 EC 48 01 00 00 57"), - &CUGCFileRequestManager__Update_hook, reinterpret_cast(&CUGCFileRequestManager__Update_orig) - ); -#else // Linux Hooking. Due to the way this plugin is structured, it's currently not possible to compile this for Linux. Literally 1984 I know, but I don't have enough time or experience to figure it out by myself. -#endif // _WIN32 - - MH_EnableHook(MH_ALL_HOOKS); - - Log(0, false, "Loaded plugin! Hooray! :D"); - m_bPluginLoaded = true; - } catch (const std::exception& ex) { - Log(0, false, "Failed to load plugin! :( Exception: \"%s\"", ex.what()); - this->m_bNoUnload = true; - return false; - } - - return true; -} - -//--------------------------------------------------------------------------------- -// Purpose: Called when the plugin is turning off/unloading. -//--------------------------------------------------------------------------------- -void CP2SMPlusPlusPlugin::Unload(void) -{ - // If the plugin errors for some reason, prevent it from unloading. - if (m_bNoUnload) - { - m_bNoUnload = false; - return; - } - - Log(0, false, "Unloading Plugin..."); - - Log(0, true, "Disconnecting hooked functions and un-initializing MinHook..."); - MH_DisableHook(MH_ALL_HOOKS); - MH_Uninitialize(); - - m_bPluginLoaded = false; - Log(0, false, "Plugin unloaded! Goodbye!"); -} - -//--------------------------------------------------------------------------------- -// Purpose: Unused callbacks -//--------------------------------------------------------------------------------- -#pragma region UNUSED_CALLBACKS -void CP2SMPlusPlusPlugin::SetCommandClient(int index) {} -void CP2SMPlusPlusPlugin::ServerActivate(edict_t* pEdictList, int edictCount, int clientMax) {} -void CP2SMPlusPlusPlugin::LevelInit(char const* pMapName) {} -PLUGIN_RESULT CP2SMPlusPlusPlugin::ClientCommand(edict_t* pEntity, const CCommand& args) { return PLUGIN_CONTINUE; } -void CP2SMPlusPlusPlugin::ClientActive(edict_t* pEntity) {} -void CP2SMPlusPlusPlugin::GameFrame(bool simulating) {} -void CP2SMPlusPlusPlugin::LevelShutdown(void) {} -void CP2SMPlusPlusPlugin::Pause(void) {} -void CP2SMPlusPlusPlugin::UnPause(void) {} -void CP2SMPlusPlusPlugin::ClientDisconnect(edict_t* pEntity) {} -void CP2SMPlusPlusPlugin::ClientFullyConnect(edict_t* pEntity) {} -void CP2SMPlusPlusPlugin::ClientPutInServer(edict_t* pEntity, char const* playerName) {} -void CP2SMPlusPlusPlugin::ClientSettingsChanged(edict_t* pEdict) {} -PLUGIN_RESULT CP2SMPlusPlusPlugin::ClientConnect(bool* bAllowConnect, edict_t* pEntity, const char* pszName, const char* pszAddress, char* reject, int maxRejectLen) { return PLUGIN_CONTINUE; } -PLUGIN_RESULT CP2SMPlusPlusPlugin::NetworkIDValidated(const char* pszUserName, const char* pszNetworkID) { return PLUGIN_CONTINUE; } -void CP2SMPlusPlusPlugin::OnQueryCvarValueFinished(QueryCvarCookie_t iCookie, edict_t* pPlayerEntity, EQueryCvarValueStatus eStatus, const char* pCvarName, const char* pCvarValue) {} -void CP2SMPlusPlusPlugin::OnEdictAllocated(edict_t* edict) {} -void CP2SMPlusPlusPlugin::OnEdictFreed(const edict_t* edict) {} -bool CP2SMPlusPlusPlugin::BNetworkCryptKeyCheckRequired(uint32 unFromIP, uint16 usFromPort, uint32 unAccountIdProvidedByClient, bool bClientWantsToUseCryptKey) { return false; } -bool CP2SMPlusPlusPlugin::BNetworkCryptKeyValidate(uint32 unFromIP, uint16 usFromPort, uint32 unAccountIdProvidedByClient, int nEncryptionKeyIndexFromClient, int numEncryptedBytesFromClient, byte* pbEncryptedBufferFromClient, byte* pbPlainTextKeyForNetchan) { return true; } -#pragma endregion \ No newline at end of file diff --git a/p2sm.cpp b/p2sm.cpp new file mode 100644 index 0000000..22ada86 --- /dev/null +++ b/p2sm.cpp @@ -0,0 +1,321 @@ +//===========================================================================// +// +// Authors: Orsell & Nanoman2525 & NULLderef +// Purpose: P2SourceMod++ Plugin +// +//===========================================================================// + +#include "p2sm.hpp" +#include "globals.hpp" +#include "sdk.hpp" +#include "scanner.hpp" // Memory scanner + +#include "cdll_int.h" // Client interfacing +#include "eiface.h" // Server interfacing +#include "minhook/include/MinHook.h" // MinHook + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//--------------------------------------------------------------------------------- +// The plugin is a static singleton that is exported as an interface +//--------------------------------------------------------------------------------- +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CP2SMPlusPlusPlugin, IServerPluginCallbacks, INTERFACEVERSION_ISERVERPLUGINCALLBACKS, g_P2SMPlusPlusPlugin); + +//--------------------------------------------------------------------------------- +// Purpose: constructor +//--------------------------------------------------------------------------------- +CP2SMPlusPlusPlugin::CP2SMPlusPlusPlugin() +{ + hWnd = nullptr; + this->m_bPluginLoaded = false; + this->m_bNoUnload = false; // If we fail to load, we don't want to run anything on Unload() to get what the error was. +} + +//--------------------------------------------------------------------------------- +// Purpose: Description of plugin outputted when the "plugin_print" console command is executed. +//--------------------------------------------------------------------------------- +const char* CP2SMPlusPlusPlugin::GetPluginDescription(void) +{ + return "P2SourceModPlusPlus Plugin | Plugin Version: " P2SMPLUSPLUS_PLUGIN_VERSION; +} + +//--------------------------------------------------------------------------------- +// Purpose: Called when the plugin is loaded, initialization process. +// Loads the interfaces we need from the engine and applies our patches. +//--------------------------------------------------------------------------------- +bool CP2SMPlusPlusPlugin::Load(CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory) +{ + if (m_bPluginLoaded) + { + Log(WARNING, false, "Already loaded!"); + m_bNoUnload = true; + return false; + } + + Log(INFO, false, + "\n"\ + R"( /$$$$$$$ /$$$$$$ /$$$$$$ /$$ /$$ )""\n"\ + R"(| $$__ $$ /$$__ $$ /$$__ $$| $$$ /$$$ /$$ /$$ )""\n"\ + R"(| $$ \ $$|__/ \ $$| $$ \__/| $$$$ /$$$$ | $$ | $$ )""\n"\ + R"(| $$$$$$$/ /$$$$$$/| $$$$$$ | $$ $$/$$ $$ /$$$$$$$$ /$$$$$$$$)""\n"\ + R"(| $$____/ /$$____/ \____ $$| $$ $$$| $$|__ $$__/|__ $$__/)""\n"\ + R"(| $$ | $$ /$$ \ $$| $$\ $ | $$ | $$ | $$ )""\n"\ + R"(| $$ | $$$$$$$$| $$$$$$/| $$ \/ | $$ |__/ |__/ )""\n"\ + R"(|__/ |________/ \______/ |__/ |__/ )""\n"\ + "(========================== VERSION: %s ==========================)", P2SMPLUSPLUS_PLUGIN_VERSION + ); + + Log(INFO, false, "Loading plugin..."); + + Log(INFO, true, "Grabbing game window handle..."); + hWnd = FindWindow("Valve001", nullptr); + if (!hWnd) + Log(WARNING, false, "Failed to find game window!"); + + // big ol' try catch because game has a TerminateProcess handler for exceptions... + // why this wasn't here is mystifying, - 10/2024 NULLderef + try { + + Log(INFO, true, "Executing game patches..."); +#if _WIN32 + // When a player, both client or host, goes through a linked_portal_door entity in multiplayer, the host will crash. This fixes that. + Log(INFO, true, "Fixing linked portal doors for multiplayer..."); + Memory::ReplacePattern("server", "0F B6 87 04 05 00 00 8B 16", "EB 14 87 04 05 00 00 8B 16"); + + // Increase runtime max from 0.03 to 0.05. + // Helps add some more leeway to some things we do in VScript without the engine complaining and shutting down the rest of the script. + Log(INFO, true, "Patching max runtime for VScript..."); + Memory::ReplacePattern("vscript", "00 00 00 E0 51 B8 9E 3F", "9a 99 99 99 99 99 a9 3f"); +#else // Linux Hooking. Due to the way this plugin is structured, it's currently not possible to compile this for Linux. Literally 1984 I know, but I don't have enough time or experience to figure it out by myself. One day. +#endif + +#if _WIN32 + // MinHook initialization and hooking. + Log(INFO, true, "Initializing MinHook and hooking functions..."); + MH_Initialize(); + + // Hook the death think function so players can be spawned immediate when the p2sm_instantrespawn ConVar is on. + Log(INFO, true, "Hooking CPortal_Player::PlayerDeathThink..."); + MH_CreateHook( + Memory::Scanner::Scan(SERVERDLL, "53 8B DC 83 EC 08 83 E4 F0 83 C4 04 55 8B 6B ?? 89 6C 24 ?? 8B EC A1 ?? ?? ?? ?? F3 0F 10 40 ?? F3 0F 58 05 ?? ?? ?? ?? 83 EC 28 56 57 6A 00 51 8B F1 F3 0F 11 04 24 E8 ?? ?? ?? ?? 6A 03"), + &CPortal_Player__PlayerDeathThink_hook, reinterpret_cast(&CPortal_Player__PlayerDeathThink_orig) + ); + + // Hook flashlight functions. + Log(INFO, true, "Hooking CPortal_Player::FlashlightTurnOn..."); + MH_CreateHook( + Memory::Scanner::Scan(SERVERDLL, "A1 ?? ?? ?? ?? 8B 50 ?? 83 7A ?? ?? 75"), + &CPortal_Player__FlashlightTurnOn_hook, reinterpret_cast(&CPortal_Player__FlashlightTurnOn_orig) + ); + Log(INFO, true, "Hooking CPortal_Player::FlashlightTurnOff..."); + MH_CreateHook( + Memory::Scanner::Scan(SERVERDLL, "A1 ?? ?? ?? ?? 8B 50 ?? 83 7A ?? ?? 74 ?? 8B 81"), + &CPortal_Player__FlashlightTurnOff_hook, reinterpret_cast(&CPortal_Player__FlashlightTurnOff_orig) + ); + + // Stop workshop map downloads by not returning false on the download request. + Log(INFO, true, "Hooking CWorkshopManager::CreateFileDownloadRequest..."); + MH_CreateHook( + Memory::Scanner::Scan(CLIENTDLL, "55 8B EC 8B 45 ?? 8B 55 ?? 50 8B 45 ?? 52 8B 55 ?? 50 8B 45 ?? 52 8B 55 ?? 50 8B 45"), + &CWorkshopManager__CreateFileDownloadRequest_hook, reinterpret_cast(&CWorkshopManager__CreateFileDownloadRequest_orig) + ); + + // Stop env_projectedtexture entities from getting disabled when more than one is active. + //! Engine limit still exists though with a max of eight env_projectedtextures. + //Log(INFO, true, "Hooking CEnvProjectedTexture::EnforceSingleProjectionRules..."); + //! Currently crashes, hook is incorrect most likely even though I checked sig is correct and parameters match up with what is in code. + // MH_CreateHook( + // Memory::Scanner::Scan(SERVERDLL, "55 8B EC 83 EC 14 53 56 57 68 B8 6F 5D 10"), + // &CEnvProjectedTexture__EnforceSingleProjectionRules_hook, reinterpret_cast(&CEnvProjectedTexture__EnforceSingleProjectionRules_orig) + // ); +#else // Linux Hooking. Due to the way this plugin is structured, it's currently not possible to compile this for Linux. Literally 1984 I know, but I don't have enough time or experience to figure it out by myself. One day. +#endif // _WIN32 + + MH_EnableHook(MH_ALL_HOOKS); + } catch (const std::exception& ex) { + Log(INFO, false, "Failed to load plugin! :( Exception: \"%s\"", ex.what()); + //this->m_bNoUnload = true; + return false; + } + + Log(INFO, true, "Connecting tier libraries..."); + MathLib_Init(2.2f, 2.2f, 0.0f, 2.0f); + ConnectTier1Libraries(&interfaceFactory, 1); + ConnectTier2Libraries(&interfaceFactory, 1); + + Log(INFO, true, "Registering plugin ConVars and ConCommands..."); + ConVar_Register(0); + + // Make sure that all the interfaces needed are loaded and usable. + Log(INFO, true, "Loading interfaces..."); + Log(INFO, true, "Loading engineServer..."); + engineServer = static_cast(interfaceFactory(INTERFACEVERSION_VENGINESERVER, 0)); + if (!engineServer) + { + assert(0 && "Unable to load engineServer!"); + Log(WARNING, false, "Unable to load engineServer!"); + this->m_bNoUnload = true; + return false; + } + + Log(INFO, true, "Loading engineClient..."); + engineClient = static_cast(interfaceFactory(VENGINE_CLIENT_INTERFACE_VERSION, 0)); + if (!engineClient) + { + assert(0 && "Unable to load engineClient!"); + Log(WARNING, false, "Unable to load engineClient!"); + this->m_bNoUnload = true; + return false; + } + + Log(INFO, true, "Loading g_pPlayerInfoManager..."); + g_pPlayerInfoManager = static_cast(gameServerFactory(INTERFACEVERSION_PLAYERINFOMANAGER, 0)); + if (!g_pPlayerInfoManager) + { + assert(0 && "Unable to load g_pPlayerInfoManager!"); + Log(WARNING, false, "Unable to load g_pPlayerInfoManager!"); + this->m_bNoUnload = true; + return false; + } + + Log(INFO, true, "Loading g_pGlobals..."); + g_pGlobals = g_pPlayerInfoManager->GetGlobalVars(); + if (!g_pGlobals) + { + assert(0 && "Unable to load g_pGlobals!"); + Log(WARNING, false, "Unable to load g_pGlobals!"); + this->m_bNoUnload = true; + return false; + } + + Log(INFO, true, "Updating/Fixing game ConVars and ConCommands..."); + + // cl_localnetworkbackdoor is causing NPCs to not move correctly thanks to Valve networking "optimizations". + // Turn it off, nothing else should turn it back automatically while in game. + Log(INFO, true, "cl_localnetworkbackdoor..."); + if (ConVar* lnbCVar = g_pCVar->FindVar("cl_localnetworkbackdoor")) + lnbCVar->SetValue(0); + + // Remove the cheat flag on r_drawscreenoverlay and enable it by default to allow maps to easily display screen overlays. + Log(INFO, true, "r_drawscreenoverlay..."); + if (ConVar* screenCVar = g_pCVar->FindVar("r_drawscreenoverlay")) + { + screenCVar->RemoveFlags(FCVAR_CHEAT); + screenCVar->SetValue(1); + } + + // Make switching between players in splitscreen when testing easier by removing + // the need for cheats to change the current player under control. + Log(INFO, true, "in_forceuser..."); + if (ConVar* ifuCVar = g_pCVar->FindVar("in_forceuser")) + ifuCVar->RemoveFlags(FCVAR_CHEAT); + + Log(INFO, false, "Loaded plugin! Yay! :D"); + m_bPluginLoaded = true; + + return true; +} + +//--------------------------------------------------------------------------------- +// Purpose: Called when the plugin is turning off/unloading. +//--------------------------------------------------------------------------------- +void CP2SMPlusPlusPlugin::Unload(void) +{ + // If the plugin errors for some reason, prevent it from unloading. + if (m_bNoUnload) + { + m_bNoUnload = false; + MessageBox(hWnd, "P2SM++ ran into a error when starting!\nPlease check the console for more info!", "P2SM++ Startup Error", MB_OK | MB_ICONERROR); + return; + } + + Log(INFO, false, "Unloading Plugin..."); + + try + { +#if _WIN32 + Log(INFO, true, "Un-patching game patches..."); + + Log(INFO, true, "Un-patching linked portal doors..."); + Memory::ReplacePattern("server", "EB 14 87 04 05 00 00 8B 16", "0F B6 87 04 05 00 00 8B 16"); + + Log(INFO, true, "Un-patching max runtime for VScript..."); + Memory::ReplacePattern("vscript", "00 00 00 00 00 00 E0 3F", "00 00 00 E0 51 B8 9E 3F"); + + Log(INFO, true, "Disconnecting hooked functions and un-initializing MinHook..."); + MH_DisableHook(MH_ALL_HOOKS); + MH_Uninitialize(); +#else // Linux Hooking. Due to the way this plugin is structured, it's currently not possible to compile this for Linux. Literally 1984 I know, but I don't have enough time or experience to figure it out by myself. One day. +#endif + } + catch (const std::exception& ex) + { + assert(0 && "Failed to fully unload!"); + Log(INFO, false, R"(Encountered error when unload plugin! :( Exception: "%s")", ex.what()); + Log(ERRORR, false, "P2:MM failed to unload!\nGame has to be shutdown as possibly some other patches/hooks are still connected which can cause issues!"); + } + + // Turn every ConVar/ConCommand back to normal. + Log(INFO, true, "Reverting changed ConVars and ConCommands..."); + + // NPC's will move jankly again. + Log(INFO, true, "cl_localnetworkbackdoor..."); + if (ConVar* lnbCVar = g_pCVar->FindVar("cl_localnetworkbackdoor")) + lnbCVar->SetValue(1); + + // Cheats flag readded, disabled. + Log(INFO, true, "r_drawscreenoverlay..."); + if (ConVar* screenCVar = g_pCVar->FindVar("r_drawscreenoverlay")) + { + screenCVar->AddFlags(FCVAR_CHEAT); + screenCVar->SetValue(0); + } + + // Need cheats to switch again. + Log(INFO, true, "in_forceuser..."); + if (ConVar* ifuCVar = g_pCVar->FindVar("in_forceuser")) + ifuCVar->AddFlags(FCVAR_CHEAT); + + Log(INFO, true, "Unregistering ConVars and ConCommands..."); + ConVar_Unregister(); + + Log(INFO, true, "Disconnecting tier libraries..."); + DisconnectTier2Libraries(); + DisconnectTier1Libraries(); + + m_bPluginLoaded = false; + Log(INFO, false, "Plugin unloaded! Goodbye!"); +} + +void CP2SMPlusPlusPlugin::ClientFullyConnect(edict_t* pEntity) +{ + // Make sure the r_drawscreenoverlay ConVar is enabled for connecting clients. + engineServer->ClientCommand(pEntity, "r_drawscreenoverlay 1"); + Log(WARNING, true, "r_drawscreenoverlay set!"); +} + +//--------------------------------------------------------------------------------- +// Purpose: Unused callbacks +//--------------------------------------------------------------------------------- +#pragma region UNUSED_CALLBACKS +void CP2SMPlusPlusPlugin::SetCommandClient(int index) {} +void CP2SMPlusPlusPlugin::ServerActivate(edict_t* pEdictList, int edictCount, int clientMax) {} +void CP2SMPlusPlusPlugin::LevelInit(char const* pMapName) {} +PLUGIN_RESULT CP2SMPlusPlusPlugin::ClientCommand(edict_t* pEntity, const CCommand& args) { return PLUGIN_CONTINUE; } +void CP2SMPlusPlusPlugin::ClientActive(edict_t* pEntity) {} +void CP2SMPlusPlusPlugin::GameFrame(bool simulating) {} +void CP2SMPlusPlusPlugin::LevelShutdown(void) {} +void CP2SMPlusPlusPlugin::Pause(void) {} +void CP2SMPlusPlusPlugin::UnPause(void) {} +void CP2SMPlusPlusPlugin::ClientDisconnect(edict_t* pEntity) {} +void CP2SMPlusPlusPlugin::ClientPutInServer(edict_t* pEntity, char const* playerName) {} +void CP2SMPlusPlusPlugin::ClientSettingsChanged(edict_t* pEdict) {} +PLUGIN_RESULT CP2SMPlusPlusPlugin::ClientConnect(bool* bAllowConnect, edict_t* pEntity, const char* pszName, const char* pszAddress, char* reject, int maxRejectLen) { return PLUGIN_CONTINUE; } +PLUGIN_RESULT CP2SMPlusPlusPlugin::NetworkIDValidated(const char* pszUserName, const char* pszNetworkID) { return PLUGIN_CONTINUE; } +void CP2SMPlusPlusPlugin::OnQueryCvarValueFinished(QueryCvarCookie_t iCookie, edict_t* pPlayerEntity, EQueryCvarValueStatus eStatus, const char* pCvarName, const char* pCvarValue) {} +void CP2SMPlusPlusPlugin::OnEdictAllocated(edict_t* edict) {} +void CP2SMPlusPlusPlugin::OnEdictFreed(const edict_t* edict) {} +bool CP2SMPlusPlusPlugin::BNetworkCryptKeyCheckRequired(uint32 unFromIP, uint16 usFromPort, uint32 unAccountIdProvidedByClient, bool bClientWantsToUseCryptKey) { return false; } +bool CP2SMPlusPlusPlugin::BNetworkCryptKeyValidate(uint32 unFromIP, uint16 usFromPort, uint32 unAccountIdProvidedByClient, int nEncryptionKeyIndexFromClient, int numEncryptedBytesFromClient, byte* pbEncryptedBufferFromClient, byte* pbPlainTextKeyForNetchan) { return true; } +#pragma endregion \ No newline at end of file diff --git a/main.hpp b/p2sm.hpp similarity index 93% rename from main.hpp rename to p2sm.hpp index ccb2145..f09f501 100644 --- a/main.hpp +++ b/p2sm.hpp @@ -7,9 +7,6 @@ #pragma once -#define P2SMPLUSPLUS_PLUGIN_VERSION "1.0.0" // Update this when a new version of the plugin is released. -#define P2SMPLUSPLUS_PLUGIN_CONSOLE_COLOR Color(100, 192, 252, 255) // Light Blue - #include "engine/iserverplugin.h" //--------------------------------------------------------------------------------- @@ -52,3 +49,5 @@ class CP2SMPlusPlusPlugin : public IServerPluginCallbacks bool m_bPluginLoaded; bool m_bNoUnload; }; + +static CP2SMPlusPlusPlugin g_P2SMPlusPlusPlugin; \ No newline at end of file diff --git a/p2sm.sln b/p2sm.sln index 1ba512a..a485493 100644 --- a/p2sm.sln +++ b/p2sm.sln @@ -3,13 +3,16 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.11.35327.3 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "p2sm", "p2sm.vcxproj", "{531796FA-476D-47F1-84FA-CD3613D68536}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "P2SourceModPlusPlus", "p2sm.vcxproj", "{531796FA-476D-47F1-84FA-CD3613D68536}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 Release|Win32 = Release|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {531796FA-476D-47F1-84FA-CD3613D68536}.Debug|Win32.ActiveCfg = Debug|Win32 + {531796FA-476D-47F1-84FA-CD3613D68536}.Debug|Win32.Build.0 = Debug|Win32 {531796FA-476D-47F1-84FA-CD3613D68536}.Release|Win32.ActiveCfg = Release|Win32 {531796FA-476D-47F1-84FA-CD3613D68536}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection diff --git a/p2sm.vcxproj b/p2sm.vcxproj index 30f3202..76fe51a 100644 --- a/p2sm.vcxproj +++ b/p2sm.vcxproj @@ -1,6 +1,10 @@  + + Debug + Win32 + Release Win32 @@ -19,28 +23,52 @@ MultiByte v143 + + DynamicLibrary + true + MultiByte + v143 + + + + - .\Release\.\ + .\Build\.\ + + + .\Build\.\ - .\Release\.\ + .\Build\.\ + + + .\Build\.\ p2sm + + p2sm + .dll + + .dll + *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.xml;*.metagen;*.bi + + *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.xml;*.metagen;*.bi + Level4 @@ -83,13 +111,59 @@ + + + Level4 + MinSpace + ;public;public\tier0;public\tier1 + true + VPC;RAD_TELEMETRY_DISABLED;_HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_ALLOW_RUNTIME_LIBRARY_MISMATCH;_ALLOW_ITERATOR_DEBUG_LEVEL_MISMATCH;_ALLOW_MSC_VER_MISMATCH;%(PreprocessorDefinitions);COMPILER_MSVC32;COMPILER_MSVC;_DLL_EXT=.dll;DLLNAME=p2sm;BINK_VIDEO;AVI_VIDEO;WMV_VIDEO;DEV_BUILD;FRAME_POINTER_OMISSION_DISABLED;_MBCS;_EXTERNAL_DLL_EXT=.dll;VPCGAMECAPS=HL2MP;SOURCE1=1;VPCGAME=hl2mp + false + false + Default + MultiThreadedDebugDLL + true + NotSet + Fast + true + + + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + CompileAsCpp + ;4316 + true + Prompt + false + EditAndContinue + false + stdcpplatest + Size + + + DebugFastLink + lib\public;minhook\lib;%(AdditionalLibraryDirectories) + legacy_stdio_definitions.lib;vstdlib.lib;tier0.lib;tier1.lib;tier2.lib;mathlib.lib;interfaces.lib;libMinHook-x86-v141-mtd.lib + libc;libcd;libcmt;libcpmt;libcpmt1 + + + IF EXIST ./copy.bat copy.bat + + + + - + + + - + + + diff --git a/scanner.cpp b/scanner.cpp index adf5574..15f1f28 100644 --- a/scanner.cpp +++ b/scanner.cpp @@ -4,6 +4,7 @@ // Purpose: Portal 2: Multiplayer Mod server plugin memory scanner // //===========================================================================// + #include "scanner.hpp" #ifdef _WIN32 @@ -17,11 +18,11 @@ #include #include +#include "globals.hpp" + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" -extern void Log(int level, bool dev, const char* pMsgFormat, ...); - namespace Memory { #ifndef _WIN32 inline void __cpuidex(int cpuid[4], int function, int subleaf) { @@ -405,10 +406,10 @@ namespace Memory { void* addr = Memory::Scanner::Scan(Memory::Modules::Get(target_module), patternBytes); if (!addr) { - Log(1, false, "Failed to replace pattern! Turn on p2sm_developer for more info..."); - Log(1, true, "Target Module: %s", target_module.c_str()); - Log(1, true, "Pattern Bytes To Find: %s", patternBytes.c_str()); - Log(1, true, "Bytes To Replace Pattern Bytes With: %s", replace_with.c_str()); + Log(WARNING, false, "Failed to replace pattern! Turn on p2sm_developer for more info..."); + Log(WARNING, true, "Target Module: %s", target_module.c_str()); + Log(WARNING, true, "Pattern Bytes To Find: %s", patternBytes.c_str()); + Log(WARNING, true, "Bytes To Replace Pattern Bytes With: %s", replace_with.c_str()); return; } diff --git a/scanner.hpp b/scanner.hpp index b3a956d..112aaff 100644 --- a/scanner.hpp +++ b/scanner.hpp @@ -18,7 +18,6 @@ #include #include #include -#include #include namespace Memory { diff --git a/sdk.cpp b/sdk.cpp new file mode 100644 index 0000000..3d1f678 --- /dev/null +++ b/sdk.cpp @@ -0,0 +1,249 @@ +//===========================================================================// +// +// Author: Orsell +// Purpose: Interfaced functions and hooks from the Portal 2 engine for the plugin to use. +// +//===========================================================================// + +#include "sdk.hpp" + +#include "scanner.hpp" +#include "p2sm.hpp" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static ConVar p2sm_instantrespawn("p2sm_instantrespawn", "0", FCVAR_NONE, "Whether respawning should be instant or not in multiplayer."); + +//--------------------------------------------------------------------------------- +// Purpose: For hooking onto the function that is called before a player respawns to skip the delay +// that is usual there and instead force a instant respawn of the player. +//--------------------------------------------------------------------------------- +void (__fastcall* CPortal_Player__PlayerDeathThink_orig)(CPortal_Player* thisPtr); +void __fastcall CPortal_Player__PlayerDeathThink_hook(CPortal_Player* thisPtr) +{ + if (p2sm_instantrespawn.GetBool()) + { + CPortal_Player__RespawnPlayer(ENTINDEX(reinterpret_cast(thisPtr))); + return; + } + CPortal_Player__PlayerDeathThink_orig(thisPtr); +} + +//--------------------------------------------------------------------------------- +// Purpose: Change out the original Flashlight turn and off with our versions. +// TODO: Have flashlight on and off sound work again. +//--------------------------------------------------------------------------------- +bool (__fastcall* CPortal_Player__FlashlightTurnOn_orig)(CPortal_Player* thisPtr, void* edx, bool playSound); +bool __fastcall CPortal_Player__FlashlightTurnOn_hook(CPortal_Player* thisPtr, void* edx, bool playSound) +{ + const int playerIndex = ENTINDEX(reinterpret_cast(thisPtr)); + if (playerIndex <= 0 || playerIndex > MAX_PLAYERS) + { + Log(WARNING, false, "CPortal_Player::FlashlightTurnOn was called with a invalid player!"); + return false; + } + + CPortal_Player__SetFlashlightState(playerIndex, true); + // engineServer->EmitAmbientSound(playerIndex) + return true; +} + +void (__fastcall* CPortal_Player__FlashlightTurnOff_orig)(CPortal_Player* thisPtr, void* edx, bool playSound); +void __fastcall CPortal_Player__FlashlightTurnOff_hook(CPortal_Player* thisPtr, void* edx, bool playSound) +{ + const int playerIndex = ENTINDEX(reinterpret_cast(thisPtr)); + if (playerIndex <= 0 || playerIndex > MAX_PLAYERS) + { + Log(WARNING, false, "CPortal_Player::FlashlightTurnOff was called with a invalid player!"); + return; + } + + CPortal_Player__SetFlashlightState(playerIndex, false); + // engineServer->EmitAmbientSound(playerIndex) + return; +} + +//--------------------------------------------------------------------------------- +// Purpose: Stop the UGC manager from automatically download workshop maps. +// Simply do nothing so that nothing gets updated and therefore nothing gets downloaded. +// !WARNING! This makes the game extremely unstable if the plugin is unloaded while the game is running. +//--------------------------------------------------------------------------------- +bool (__fastcall* CWorkshopManager__CreateFileDownloadRequest_orig)(CWorkshopManager* thisPtr, void* edx, + uint64 hFileHandle, + uint64 fileID, + const char *lpszDirectory, + const char *lpszFilename, + uint32 unPriority, + uint32 unTimeLastUpdated, + bool bForceUpdate); +bool __fastcall CWorkshopManager__CreateFileDownloadRequest_hook(CWorkshopManager* thisPtr, void* edx, + uint64 hFileHandle, + uint64 fileID, + const char *lpszDirectory, + const char *lpszFilename, + uint32 unPriority, + uint32 unTimeLastUpdated, + bool bForceUpdate) +{ + return false; +} + +//--------------------------------------------------------------------------------- +// Purpose: Stop engine from disabling any other env_projectedtexture entities when one turns on. +// ! Engine limit still exists though with a max of eight env_projectedtextures. +//--------------------------------------------------------------------------------- +// void (__fastcall* CEnvProjectedTexture__EnforceSingleProjectionRules_orig)(CEnvProjectedTexture* thisPtr, void* edx, bool bWarnOnEnforcement); +// void __fastcall CEnvProjectedTexture__EnforceSingleProjectionRules_hook(CEnvProjectedTexture* thisPtr, void* edx, bool bWarnOnEnforcement) { } + +/// Interfaced UTIL Functions \\\ + +//--------------------------------------------------------------------------------- +// Purpose: Get the player's base class with its entity index. Thanks to Nanoman2525 for this. +//--------------------------------------------------------------------------------- +CBasePlayer* UTIL_PlayerByIndex(const int playerIndex) +{ +#ifdef _WIN32 + static auto PlayerByIndex_ = reinterpret_cast(Memory::Scanner::Scan(SERVERDLL, "55 8B EC 8B 4D 08 33 C0 85 C9 7E 30")); + return PlayerByIndex_(playerIndex); +#else // Linux support TODO + return nullptr; +#endif +} + +//--------------------------------------------------------------------------------- +// Purpose: Show on screen message to players. msg_dest are definSed macros in globals.hpp. +//--------------------------------------------------------------------------------- +void UTIL_ClientPrint(CBasePlayer* player, const int msg_dest, const char* msg_name, const char* param1, const char* param2, const char* param3, const char* param4) +{ + static auto ClientPrint_ = reinterpret_cast(Memory::Scanner::Scan(SERVERDLL, "55 8B EC 83 EC 20 56 8B 75 08 85 F6 74 4C")); + ClientPrint_(player, msg_dest, msg_name, param1, param2, param3, param4); +} + +//--------------------------------------------------------------------------------- +// Purpose: Show on text on screen just like game_text does. +//--------------------------------------------------------------------------------- +void UTIL_HudMessage(CBasePlayer* pPlayer, const HudMessageParams& textparms, const char* pMessage) +{ + static auto HudMessage_ = reinterpret_cast(Memory::Scanner::Scan(SERVERDLL, "55 8B EC 83 EC 20 8D 4D ?? E8 ?? ?? ?? ?? 8B 45 ?? 8D 4D ?? 85 C0 74 ?? 50 E8 ?? ?? ?? ?? EB ?? E8 ?? ?? ?? ?? 56")); + HudMessage_(pPlayer, textparms, pMessage); +} + +//--------------------------------------------------------------------------------- +// Purpose: Get the CBasePlayer of the player who executed the ConVar or ConCommand. +//--------------------------------------------------------------------------------- +CBasePlayer* UTIL_GetCommandClient() +{ + static auto GetCommandClient_ = reinterpret_cast(Memory::Scanner::Scan(SERVERDLL, "A1 ?? ?? ?? ?? 40 85 C0")); + return GetCommandClient_(); +} + +//--------------------------------------------------------------------------------- +// Purpose: Get the client index of the player who executed the ConVar or ConCommand. This is not the player's entity index which is 1 more. +//--------------------------------------------------------------------------------- +int UTIL_GetCommandClientIndex() +{ + static auto GetCommandClientIndex_ = reinterpret_cast(Memory::Scanner::Scan(SERVERDLL, "A1 ?? ?? ?? ?? 40 C3")); + return GetCommandClientIndex_(); +} + +/// CBaseEntity Class Functions \\\ + +//--------------------------------------------------------------------------------- +// Purpose: Self-explanatory. Thanks to Nanoman2525 for this. +//--------------------------------------------------------------------------------- +void CBaseEntity__RemoveEntity(CBaseEntity* pEntity) +{ + static auto RemoveEntity_ = reinterpret_cast(Memory::Scanner::Scan(SERVERDLL, "55 8B EC 57 8B 7D 08 85 FF 74 72")); + RemoveEntity_((reinterpret_cast(pEntity)->GetNetworkable())); +} + +//--------------------------------------------------------------------------------- +// Purpose: Get team number for the supplied CBasePlayer. +//--------------------------------------------------------------------------------- +int CBaseEntity__GetTeamNumber(CBasePlayer* pPlayer) +{ + static auto GetTeamNumber_ = reinterpret_cast(Memory::Scanner::Scan(SERVERDLL, "8B 81 F4 02 00 00 C3")); + return GetTeamNumber_(reinterpret_cast(pPlayer)); +} + +//--------------------------------------------------------------------------------- +// Purpose: Get the script scope of a entity. Thanks to Nullderef/Vista for this. +//--------------------------------------------------------------------------------- +HSCRIPT CBaseEntity__GetScriptScope(CBaseEntity* entity) +{ + if (!entity) + return nullptr; + + return *reinterpret_cast(reinterpret_cast(entity) + 0x33c); +} + +//--------------------------------------------------------------------------------- +// Purpose: Get the script instance of a entity. Thanks to Nullderef/Vista for this. +//--------------------------------------------------------------------------------- +HSCRIPT CBaseEntity__GetScriptInstance(CBaseEntity* entity) +{ + static auto GetScriptInstance_ = reinterpret_cast(Memory::Scanner::Scan(SERVERDLL, "55 8B EC 51 56 8B F1 83 BE 50")); + if (!GetScriptInstance_) + { + Log(WARNING, false, "Could not get script instance for entity!"); + return nullptr; + } + + return GetScriptInstance_(entity); +} + + +/// CBasePlayer Class Functions \\\ + +void CBasePlayer__ShowViewPortPanel(const int playerIndex, const char* name, const bool bShow, KeyValues* data) +{ + CBasePlayer* pPlayer = UTIL_PlayerByIndex(playerIndex); + if (!pPlayer) + { + Log(WARNING, false, "Couldn't get player to display view port panel to! playerIndex: %i", playerIndex); + return; + } + static auto ShowViewPortPanel_ = reinterpret_cast(Memory::Scanner::Scan(SERVERDLL, "55 8B EC 83 EC 20 53 56 8B F1 57 8D 4D ?? E8 ?? ?? ?? ?? 56")); + ShowViewPortPanel_(pPlayer, name, bShow, data); +} + + +/// CPortal_Player Class Functions \\\ + +//--------------------------------------------------------------------------------- +// Purpose: Respawn the a player by their entity index. +//--------------------------------------------------------------------------------- +void CPortal_Player__RespawnPlayer(const int playerIndex) +{ + CBasePlayer* pPlayer = UTIL_PlayerByIndex(playerIndex); + if (!pPlayer) + { + Log(WARNING, false, "Couldn't get player to respawn! playerIndex: %i", playerIndex); + return; + } + + static auto RespawnPlayer_ = reinterpret_cast(Memory::Scanner::Scan(SERVERDLL, "0F 57 C0 56 8B F1 57 8D 8E")); + RespawnPlayer_(reinterpret_cast(pPlayer)); +} + +//--------------------------------------------------------------------------------- +// Purpose: Set the flashlight for a player on or off. Thanks to Nanoman2525 for this. +// Not a function in the CPortal_Player class, just more grouping it with the +// class. This does the same thing as the FlashlightTurnOn and FlashlightTurnOff +// functions in CPortal_Player but done in one function. +//--------------------------------------------------------------------------------- +void CPortal_Player__SetFlashlightState(const int playerIndex, const bool enable) +{ + CBasePlayer* pPlayer = UTIL_PlayerByIndex(playerIndex); + if (!pPlayer) + { + Log(WARNING, true, "Couldn't get player to set flashlight state! playerIndex: %i enable: %i", playerIndex, !!enable); + return; + } + + if (enable) + reinterpret_cast(Memory::Scanner::Scan(SERVERDLL, "55 8B EC 53 8B D9 8B 83 A8"))(reinterpret_cast(pPlayer), EF_DIMLIGHT); + else + reinterpret_cast(Memory::Scanner::Scan(SERVERDLL, "55 8B EC 53 56 8B 75 08 8B D9 8B 83"))(reinterpret_cast(pPlayer), EF_DIMLIGHT); +} \ No newline at end of file diff --git a/sdk.hpp b/sdk.hpp new file mode 100644 index 0000000..4fd070f --- /dev/null +++ b/sdk.hpp @@ -0,0 +1,83 @@ +//===========================================================================// +// +// Author: Orsell +// Purpose: Interfaced functions and hooks from the Portal 2 engine for the plugin to use. +// +//===========================================================================// + +#pragma once + +#include "globals.hpp" + +// UTIL_HudMessage message parameters struct. Based on the one from util.h. +// See Valve Developer Community for game_text to see which field does what: +// https://developer.valvesoftware.com/wiki/Game_text +typedef struct +{ + float x, y; + int effect; + byte r1, g1, b1, a1; + byte r2, g2, b2, a2; + float fadeinTime, fadeoutTime, holdTime; + float fxTime; + int channel; +} HudMessageParams; + +//--------------------------------------------------------------------------------- +// Hooked game functions. +//--------------------------------------------------------------------------------- + +// Respawn Hooks. +extern void (__fastcall* CPortal_Player__PlayerDeathThink_orig)(CPortal_Player* thisPtr); +void __fastcall CPortal_Player__PlayerDeathThink_hook(CPortal_Player* thisPtr); + +// Flashlight Hooks. +extern bool (__fastcall* CPortal_Player__FlashlightTurnOn_orig)(CPortal_Player* thisPtr, void* edx, bool playSound); +bool __fastcall CPortal_Player__FlashlightTurnOn_hook(CPortal_Player* thisPtr, void* edx, bool playSound); +extern void (__fastcall* CPortal_Player__FlashlightTurnOff_orig)(CPortal_Player* thisPtr, void* edx, bool playSound); +void __fastcall CPortal_Player__FlashlightTurnOff_hook(CPortal_Player* thisPtr, void* edx, bool playSound); + +// Workshop download stopping hooks. +extern bool (__fastcall* CWorkshopManager__CreateFileDownloadRequest_orig)(CWorkshopManager* thisPtr, void* edx, + uint64 hFileHandle, + uint64 fileID, + const char *lpszDirectory, + const char *lpszFilename, + uint32 unPriority, + uint32 unTimeLastUpdated, + bool bForceUpdate); +bool __fastcall CWorkshopManager__CreateFileDownloadRequest_hook(CWorkshopManager* thisPtr, void* edx, + uint64 hFileHandle, + uint64 fileID, + const char *lpszDirectory, + const char *lpszFilename, + uint32 unPriority, + uint32 unTimeLastUpdated, + bool bForceUpdate); + +// env_projectedtexture enforcement function hook. +// extern void (__fastcall* CEnvProjectedTexture__EnforceSingleProjectionRules_orig)(CEnvProjectedTexture* thisPtr, void* edx, bool bWarnOnEnforcement); +// void __fastcall CEnvProjectedTexture__EnforceSingleProjectionRules_hook(CEnvProjectedTexture* thisPtr, void* edx, bool bWarnOnEnforcement); + +//--------------------------------------------------------------------------------- +// Interfaced game functions. +//--------------------------------------------------------------------------------- +// UTIL functions +CBasePlayer* UTIL_PlayerByIndex(int playerIndex); +void UTIL_ClientPrint(CBasePlayer* player, int msg_dest, const char* msg_name, const char* param1 = nullptr, const char* param2 = nullptr, const char* param3 = nullptr, const char* param4 = nullptr); +void UTIL_HudMessage(CBasePlayer* pPlayer, const HudMessageParams& textparms, const char* pMessage); +CBasePlayer* UTIL_GetCommandClient(); +int UTIL_GetCommandClientIndex(); + +// CBaseEntity functions +void CBaseEntity__RemoveEntity(CBaseEntity* pEntity); +int CBaseEntity__GetTeamNumber(CBasePlayer* pPlayer); +HSCRIPT CBaseEntity__GetScriptScope(CBaseEntity* entity); +HSCRIPT CBaseEntity__GetScriptInstance(CBaseEntity* entity); + +// CBasePlayer functions +void CBasePlayer__ShowViewPortPanel(int playerIndex, const char* name, bool bShow = true, KeyValues* data = nullptr); + +// CPortal_Player functions +void CPortal_Player__RespawnPlayer(int playerIndex); +void CPortal_Player__SetFlashlightState(int playerIndex, bool enable); \ No newline at end of file