#include #include #include #pragma semicolon 1 #pragma newdecls required #define PLUGIN_VERSION "1.0" public Plugin myinfo = { name = "CS:GO Fixes", author = "xen", description = "Fix some CS:GO entity issues", version = PLUGIN_VERSION, url = "" } Handle g_hSetParent; // Entity solid types enum { SOLID_NONE = 0, // no solid model SOLID_BSP = 1, // a BSP tree SOLID_BBOX = 2, // an AABB SOLID_OBB = 3, // an OBB (not implemented yet) SOLID_OBB_YAW = 4, // an OBB, constrained so that it can only yaw SOLID_CUSTOM = 5, // Always call into the entity for tests SOLID_VPHYSICS = 6, // solid vphysics object, get vcollide from the model and collide with that SOLID_LAST, }; // Entity collision groups enum { COLLISION_GROUP_NONE = 0, // 0 COLLISION_GROUP_DEBRIS, // 1 - Collides with nothing but world and static stuff COLLISION_GROUP_DEBRIS_TRIGGER, // 2 - Same as debris, but hits triggers COLLISION_GROUP_INTERACTIVE_DEBRIS, // 3 - Collides with everything except other interactive debris or debris COLLISION_GROUP_INTERACTIVE, // 4 - Collides with everything except interactive debris or debris COLLISION_GROUP_PLAYER, // 5 COLLISION_GROUP_BREAKABLE_GLASS, // 6 COLLISION_GROUP_VEHICLE, // 7 COLLISION_GROUP_PLAYER_MOVEMENT, // 8 - For HL2, same as Collision_Group_Player, for // TF2, this filters out other players and CBaseObjects COLLISION_GROUP_NPC, // 9 - Generic NPC group COLLISION_GROUP_IN_VEHICLE, // 10 - for any entity inside a vehicle COLLISION_GROUP_WEAPON, // 11 - for any weapons that need collision detection COLLISION_GROUP_VEHICLE_CLIP, // 12 - vehicle clip brush to restrict vehicle movement COLLISION_GROUP_PROJECTILE, // 13 - Projectiles! COLLISION_GROUP_DOOR_BLOCKER, // 14 - Blocks entities not permitted to get near moving doors COLLISION_GROUP_PASSABLE_DOOR, // 15 - Doors that the player shouldn't collide with COLLISION_GROUP_DISSOLVING, // 16 - Things that are dissolving are in this group COLLISION_GROUP_PUSHAWAY, // 17 - Nonsolid on client and server, pushaway in player code COLLISION_GROUP_NPC_ACTOR, // 18 - Used so NPCs in scripts ignore the player. COLLISION_GROUP_NPC_SCRIPTED, // 19 - USed for NPCs in scripts that should not collide with each other LAST_SHARED_COLLISION_GROUP }; // Physbox spawnflags enum { SF_PHYSBOX_ASLEEP = 0x01000, SF_PHYSBOX_IGNOREUSE = 0x02000, SF_PHYSBOX_DEBRIS = 0x04000, SF_PHYSBOX_MOTIONDISABLED = 0x08000, SF_PHYSBOX_USEPREFERRED = 0x10000, SF_PHYSBOX_ENABLE_ON_PHYSCANNON = 0x20000, SF_PHYSBOX_NO_ROTORWASH_PUSH = 0x40000, // The rotorwash doesn't push these SF_PHYSBOX_ENABLE_PICKUP_OUTPUT = 0x80000, SF_PHYSBOX_ALWAYS_PICK_UP = 0x100000, // Physcannon can always pick this up, no matter what mass or constraints may apply. SF_PHYSBOX_NEVER_PICK_UP = 0x200000, // Physcannon will never be able to pick this up. SF_PHYSBOX_NEVER_PUNT = 0x400000, // Physcannon will never be able to punt this object. SF_PHYSBOX_PREVENT_PLAYER_TOUCH_ENABLE = 0x800000 // If set, the player will not cause the object to enable its motion when bumped into }; enum struct Patch { char sPatchName[64]; // Signature name to lookup in gamedata bool bNOP; // Whether it's a full-on NOP patch Address iPatchAddress; // Patch address to be later filled in from gamedata char aPatch[128]; // The patch itself char aPatchRestore[128]; // Buffer to store the original bytes int iPatchSize; // Length of the patch } Patch g_Patches[] = { {"GameUILag"}, // Prevent game_ui from setting FL_ONTRAIN flag which disables prediction //{"SpeedModFL"} // Prevent player_speedmod from disabling flashlight }; #define NOP 0x90 public void OnPluginStart() { Handle hGameConf = LoadGameConfigFile("CSGOFixes.games"); if(!hGameConf) { SetFailState("Can't find CSGOFixes.games.txt gamedata."); return; } int offset = GameConfGetOffset(hGameConf, "CBaseEntity::SetParent"); if (offset == -1) SetFailState("Failed to find CBaseEntity::SetParent offset"); // DHooks. g_hSetParent = DHookCreate(offset, HookType_Entity, ReturnType_Void, ThisPointer_CBaseEntity, Hook_SetParent); DHookAddParam(g_hSetParent, HookParamType_CBaseEntity); DHookAddParam(g_hSetParent, HookParamType_Int); for (int i = 0; i < sizeof(g_Patches); i++) { char sPatchName[128], sPatchOffset[128], sPatchSize[128], sPatch[128]; strcopy(sPatchName, 128, g_Patches[i].sPatchName); strcopy(sPatchOffset, 128, g_Patches[i].sPatchName); strcopy(sPatchSize, 128, g_Patches[i].sPatchName); strcopy(sPatch, 128, g_Patches[i].sPatchName); Address iAddr = GameConfGetAddress(hGameConf, sPatchName); if(iAddr == Address_Null) { CloseHandle(hGameConf); PrintToServer("Can't find %s address.", sPatchName); return; } StrCat(sPatchOffset, 128, "_Offset"); // Get the offset from the start of the signature to the start of our patch area. int iOffset = GameConfGetOffset(hGameConf, sPatchOffset); if(iOffset == -1) { CloseHandle(hGameConf); PrintToServer("Can't find Offset for %s in gamedata.", sPatchName); return; } // Move right in front of the instructions we want to NOP. iAddr += view_as
(iOffset); g_Patches[i].iPatchAddress = iAddr; StrCat(sPatchSize, 128, "_PatchSize"); // Get how many bytes we want to NOP. int iPatchSize = GameConfGetOffset(hGameConf, sPatchSize); if(iPatchSize == -1) { CloseHandle(hGameConf); PrintToServer("Can't find PatchBytes for %s in gamedata.", sPatchName); return; } g_Patches[i].iPatchSize = iPatchSize; StrCat(sPatch, 128, "_Patch"); // Assume it's a NOP patch unless we get an actual patch array if(GameConfGetKeyValue(hGameConf, sPatch, g_Patches[i].aPatch, 128)) { PrintToServer("%s isn't a NOP patch.", sPatchName); g_Patches[i].bNOP = true; } else { PrintToServer("%s is a NOP patch.", sPatchName); g_Patches[i].bNOP = false; } } CloseHandle(hGameConf); ApplyPatches(); } public void OnPluginEnd() { RevertPatches(); } public void ApplyPatches() { for (int i = 0; i < sizeof(g_Patches); i++) { Address iAddr = g_Patches[i].iPatchAddress; for(int j = 0; j < g_Patches[i].iPatchSize; j++) { // Save the current instructions, so we can restore them on unload. g_Patches[i].aPatchRestore[j] = LoadFromAddress(iAddr, NumberType_Int8); if (g_Patches[i].bNOP) StoreToAddress(iAddr, NOP, NumberType_Int8); else StoreToAddress(iAddr, g_Patches[i].aPatch[j], NumberType_Int8); iAddr++; } } } public void RevertPatches() { for (int i = 0; i < sizeof(g_Patches); i++) { Address iAddr = g_Patches[i].iPatchAddress; // Restore the original instructions only if we actually patched them if (iAddr != Address_Null) { for(int j = 0; j < g_Patches[i].iPatchSize; j++) { StoreToAddress(iAddr, g_Patches[i].aPatchRestore[j], NumberType_Int8); iAddr++; } } } } public void OnEntityCreated(int iEntity, const char[] sClassname) { if(StrEqual(sClassname, "func_physbox_multiplayer", false)) { RequestFrame(SetDebrisCollisionGroup, iEntity); DHookEntity(g_hSetParent, false, iEntity); } } public MRESReturn Hook_SetParent(int iEntity, Handle hReturn, Handle hParams) { RequestFrame(SetDebrisCollisionGroup, iEntity); return MRES_Ignored; } public void SetDebrisCollisionGroup(int iEntity) { if (IsValidEntity(iEntity)) { // Set collisiongroup to WEAPON to replicate CS:S behavior when parented char parent[64]; if(GetEntPropString(iEntity, Prop_Data, "m_iParent", parent, sizeof(parent))) SetEntProp(iEntity, Prop_Data, "m_CollisionGroup", COLLISION_GROUP_WEAPON); } }