Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions addons/sourcemod/gamedata/l4d_pellet_spread_customizer.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"Games"
{
"#default"
{
"Functions"
{
"CTerrorGun::FireBullet"
{
"signature" "CTerrorGun::FireBullet"
"callconv" "thiscall"
"return" "void"
"this" "entity"
}

"SharedRandomFloat"
{
"signature" "SharedRandomFloat"
"callconv" "cdecl"
"return" "float"
"arguments"
{
"name"
{
"type" "charptr"
}
"min"
{
"type" "float"
}
"max"
{
"type" "float"
}
"seed"
{
"type" "int"
}
}
}
}
}

"left4dead"
{
"MemPatches"
{
"center_pellet"
{
"signature" "CTerrorPlayer::FireBullet"
"linux"
{
"offset" "44Ch"
"verify" "\xB8\x01"
"patch" "\xB8\x00"
}
"windows"
{
"offset" "262h"
"verify" "\xB1\x01"
"patch" "\xB1\x00"
}
}
}

"Signatures"
{
"CTerrorGun::FireBullet"
{
"library" "server"
"linux" "@_ZN10CTerrorGun10FireBulletEv"
"windows" "\x83\xEC\x40\x53\x56\x8B\xF1\xE8"
/* 83 EC 40 53 56 8B F1 E8 */
}

/* @A1m`:
* How to find in windows:
* Can be found by string "CTerrorPlayer::FireBullet Spread" or "CTerrorPlayer::FireBullet SpreadDir"
*/
"CTerrorPlayer::FireBullet"
{
"library" "server"
"linux" "@_ZN13CTerrorPlayer10FireBulletE6VectorRK6QAngle10CSWeaponIDi"
"windows" "\x81\xEC\x90\x00\x00\x00\x8B\x84\x24\xA4\x00\x00\x00"
}

"SharedRandomFloat"
{
"library" "server"
"linux" "@_Z17SharedRandomFloatPKcffi"
"windows" "\x83\xEC\x08\xA1\x2A\x2A\x2A\x2A\x53\x56\x57\x8B\x7C\x24\x24\x8D\x4C\x24\x24\x51\x89\x7C\x24\x14\x89\x44\x24\x10\xE8\x2A\x2A\x2A\x2A\x6A\x04\x8D\x54\x24\x14\x52\x8D\x44\x24\x30\x50\xE8\x2A\x2A\x2A\x2A\x6A\x04\x8D\x4C\x24\x24\x51\x8D\x54\x24\x3C\x52\xE8\x2A\x2A\x2A\x2A\x8B\x74\x24\x34\x56\xE8\x2A\x2A\x2A\x2A\x50\x8D\x44\x24\x48\x56\x50\xE8\x2A\x2A\x2A\x2A\x8D\x4C\x24\x50\x51\xE8\x2A\x2A\x2A\x2A\x8B\x15\x2A\x2A\x2A\x2A\x8B\x5C\x24\x54\x83\xC4\x30\x83\x7A\x30\x00\x74\x2A\x57\x53\x56\xB0\xFF\x68\x2A\x2A\x2A\x2A\x68\x2A\x2A\x2A\x2A\x88\x44\x24\x38\x88\x44\x24\x39\x88\x44\x24\x3B\x8D\x44\x24\x38\x68\x2A\x2A\x2A\x2A\x50\xC6\x44\x24\x42\x00\xFF\x15\x2A\x2A\x2A\x2A\x83\xC4\x1C\x53\xB9\x2A\x2A\x2A\x2A\xFF\x15\x2A\x2A\x2A\x2A\xD9\x44\x24\x20"
// 83 EC 08 A1 ? ? ? ? 53 56 57 8B 7C 24 24 8D 4C 24 24 51 89 7C 24 14 89 44 24 10 E8 ? ? ? ? 6A 04 8D 54 24 14 52 8D 44 24 30 50 E8 ? ? ? ? 6A 04 8D 4C 24 24 51 8D 54 24 3C 52 E8 ? ? ? ? 8B 74 24 34 56 E8 ? ? ? ? 50 8D 44 24 48 56 50 E8 ? ? ? ? 8D 4C 24 50 51 E8 ? ? ? ? 8B 15 ? ? ? ? 8B 5C 24 54 83 C4 30 83 7A 30 00 74 ? 57 53 56 B0 FF 68 ? ? ? ? 68 ? ? ? ? 88 44 24 38 88 44 24 39 88 44 24 3B 8D 44 24 38 68 ? ? ? ? 50 C6 44 24 42 00 FF 15 ? ? ? ? 83 C4 1C 53 B9 ? ? ? ? FF 15 ? ? ? ? D9 44 24 20
}
}
}

"left4dead2"
{
"MemPatches"
{
"center_pellet"
{
"signature" "CTerrorPlayer::FireBullet"
"linux"
{
"offset" "3FAh"
"verify" "\xB8\x01"
"patch" "\xB8\x00"
}
"windows"
{
"offset" "2EBh"
"verify" "\xB1\x01"
"patch" "\xB1\x00"
}
}
}

"Signatures"
{
"CTerrorGun::FireBullet"
{
"library" "server"
"linux" "@_ZN10CTerrorGun10FireBulletEv"
"windows" "\x55\x8B\xEC\x83\xEC\x34\x53\x56\x8B\xF1\xE8"
/* 55 8B EC 83 EC 34 53 56 8B F1 E8 */
}

/* @A1m`:
* How to find in windows:
* Can be found by string "CTerrorPlayer::FireBullet Spread" or "CTerrorPlayer::FireBullet SpreadDir"
*/
"CTerrorPlayer::FireBullet"
{
"library" "server"
"linux" "@_ZN13CTerrorPlayer10FireBulletE6VectorRK6QAngle10CSWeaponIDi"
"windows" "\x55\x8b\xec\x81\xec\x2a\x2a\x2a\x2a\x8b\x45\x18\x57\x8b\xf9"
}

"SharedRandomFloat"
{
"library" "server"
"linux" "@_Z17SharedRandomFloatPKcffi"
"windows" "\x55\x8B\xEC\x83\xEC\x08\xA1\x2A\x2A\x2A\x2A\x53\x56\x57\x8B\x7D\x14\x8D\x4D\x14\x51\x89\x7D\xF8\x89\x45\xFC\xE8\x2A\x2A\x2A\x2A\x6A\x04\x8D\x55\xFC\x52\x8D\x45\x14\x50\xE8\x2A\x2A\x2A\x2A\x6A\x04\x8D\x4D\xF8\x51\x8D\x55\x14\x52\xE8\x2A\x2A\x2A\x2A\x8B\x75\x08\x56\xE8\x2A\x2A\x2A\x2A\x50\x8D\x45\x14\x56\x50\xE8\x2A\x2A\x2A\x2A\x8D\x4D\x14\x51\xE8\x2A\x2A\x2A\x2A\x8B\x15\x2A\x2A\x2A\x2A\x8B\x5D\x14\x83\xC4\x30\x83\x7A\x30\x00\x74\x2A\x57\x53\x56\x68\x2A\x2A\x2A\x2A\x68\x2A\x2A\x2A\x2A\x8D\x45\x14\x68\x2A\x2A\x2A\x2A\x50\xC7\x45\x14\xFF\xFF\x00\xFF\xFF\x15\x2A\x2A\x2A\x2A\x83\xC4\x1C\x53\xB9\x2A\x2A\x2A\x2A\xFF\x15\x2A\x2A\x2A\x2A\xD9\x45\x10"
// 55 8B EC 83 EC 08 A1 ? ? ? ? 53 56 57 8B 7D 14 8D 4D 14 51 89 7D F8 89 45 FC E8 ? ? ? ? 6A 04 8D 55 FC 52 8D 45 14 50 E8 ? ? ? ? 6A 04 8D 4D F8 51 8D 55 14 52 E8 ? ? ? ? 8B 75 08 56 E8 ? ? ? ? 50 8D 45 14 56 50 E8 ? ? ? ? 8D 4D 14 51 E8 ? ? ? ? 8B 15 ? ? ? ? 8B 5D 14 83 C4 30 83 7A 30 00 74 ? 57 53 56 68 ? ? ? ? 68 ? ? ? ? 8D 45 14 68 ? ? ? ? 50 C7 45 14 FF FF 00 FF FF 15 ? ? ? ? 83 C4 1C 53 B9 ? ? ? ? FF 15 ? ? ? ? D9 45 10
}
}
}
}
197 changes: 197 additions & 0 deletions addons/sourcemod/scripting/archive/l4d2_static_shotgun_spread.sp
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#include <sourcescramble>

#define BULLET_MAX_SIZE 3
#define GAMEDATA_FILE "code_patcher"
#define KEY_SGSPREAD "sgspread"

// Original code & Notes: https://github.com/Jahze/l4d2_plugins/tree/master/spread_patch
// Static Shotgun Spread leverages code_patcher (code_patcher.txt gamedata)
// to replace RNG in pellet spread with static factors.
// This plugin allows you to adjust the spread characteristics, by live patching operands in the custom ASM.

// You can use the visualise_impacts.smx plugin to test the resulting spread.
// It will render small purple boxes where the server-side pellets land.

enum
{
eWindows = 0,
eLinux,
/* eMac, joke:) */

ePlatform_Size
};

int
g_ePlatform = eLinux;

static const int
g_iBulletOffsets[ePlatform_Size][BULLET_MAX_SIZE] = {
{ 0xf, 0x21, 0x30 }, // Windows
{ 0x11, 0x22, 0x2f } // Linux
},
g_iDegreeOffsets[ePlatform_Size] = {
0x3f, // Windows
0x43 // Linux
},
g_iFactorOffset[ePlatform_Size] = {
0x36, // Windows
0x34 // Linux
},
g_iCenterPelletOffset[ePlatform_Size] = {
-0x36, // Windows
-0x1c // Linux
};

Address
g_pBullets[BULLET_MAX_SIZE] = {Address_Null, ...},
g_pDegree = Address_Null,
g_pFactor = Address_Null,
g_pCenterPellet = Address_Null;

MemoryPatch
g_hPatchSgSpread = null;

ConVar
g_hCvarRing1Bullets = null,
g_hCvarRing1Factor = null,
g_hCvarCenterPellet = null;

public Plugin myinfo =
{
name = "L4D2 Static Shotgun Spread",
author = "Jahze, Visor, A1m`, Rena",
version = "1.6.5",
description = "Changes the values in the sgspread patch",
url = "https://github.com/SirPlease/L4D2-Competitive-Rework"
};

public void OnPluginStart()
{
InitGameData();

g_hCvarRing1Bullets = CreateConVar("sgspread_ring1_bullets", "3", "Number of bullets for the first ring, the remaining bullets will be in the second ring.");
g_hCvarRing1Factor = CreateConVar("sgspread_ring1_factor", "2", "Determines how far or closer the bullets will be from the center for the first ring.");
g_hCvarCenterPellet = CreateConVar("sgspread_center_pellet", "1", "Center pellet: 0 - off, 1 - on.", _, true, 0.0, true, 1.0);

InitPlugin();
}

void InitGameData()
{
Handle hConf = LoadGameConfigFile(GAMEDATA_FILE);
if (hConf == null) {
SetFailState("Missing gamedata \"" ... GAMEDATA_FILE ... "\"");
}

g_ePlatform = GameConfGetOffset(hConf, "OS");
if (g_ePlatform == -1) {
SetFailState("Failed to retrieve offset \"OS\"");
}

g_hPatchSgSpread = MemoryPatch.CreateFromConf(hConf, KEY_SGSPREAD);
if (g_hPatchSgSpread == null || !g_hPatchSgSpread.Validate()) {
SetFailState("Failed to validate MemoryPatch \"" ... KEY_SGSPREAD ... "\"");
}

Address pFinalAddr = g_hPatchSgSpread.Address + view_as<Address>(g_iCenterPelletOffset[g_ePlatform]);
int iCurrentValue = LoadFromAddress(pFinalAddr, NumberType_Int8);
if (iCurrentValue != 1) {
SetFailState("Center pellet offset is uncorrect! CheckByte: %x", iCurrentValue);
}

if (!g_hPatchSgSpread.Enable()) {
SetFailState("Failed to enable MemoryPatch \"" ... KEY_SGSPREAD ... "\"");
}

delete hConf;
}

void InitPlugin()
{
for (int i = 0; i < BULLET_MAX_SIZE; i++) {
g_pBullets[i] = g_hPatchSgSpread.Address + view_as<Address>(g_iBulletOffsets[g_ePlatform][i]);
}

g_pDegree = g_hPatchSgSpread.Address + view_as<Address>(g_iDegreeOffsets[g_ePlatform]);
g_pFactor = g_hPatchSgSpread.Address + view_as<Address>(g_iFactorOffset[g_ePlatform]);
g_pCenterPellet = g_hPatchSgSpread.Address + view_as<Address>(g_iCenterPelletOffset[g_ePlatform]);

HotPatchBullets(g_hCvarRing1Bullets.IntValue);
HotPatchFactor(g_hCvarRing1Factor.IntValue);
HotPatchCenterPellet(g_hCvarCenterPellet.BoolValue);

g_hCvarRing1Bullets.AddChangeHook(OnRing1BulletsChange);
g_hCvarRing1Factor.AddChangeHook(OnRing1FactorChange);
g_hCvarCenterPellet.AddChangeHook(OnCenterPelletChange);
}

public void OnPluginEnd()
{
if (g_pCenterPellet != Address_Null) {
int iCurrentValue = LoadFromAddress(g_pCenterPellet, NumberType_Int8);

if (iCurrentValue != 1) {
StoreToAddress(g_pCenterPellet, 1, NumberType_Int8);
}
}
}

void OnRing1BulletsChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue)
{
HotPatchBullets(hConVar.IntValue);
}

void OnRing1FactorChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue)
{
HotPatchFactor(hConVar.IntValue);
}

void OnCenterPelletChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue)
{
HotPatchCenterPellet(hConVar.BoolValue);
}

void HotPatchBullets(int iBullets)
{
PatchBullets(iBullets, g_hCvarCenterPellet.BoolValue);

float fBullets = float(iBullets);
if (g_ePlatform == eLinux) {
fBullets *= 2.0;
}

StoreToAddress(g_pDegree, view_as<int>(360.0 / fBullets), NumberType_Int32);
}

void HotPatchFactor(int iFactor)
{
// Asm patch on windows need the float type !
int iSetFactor = (g_ePlatform == eWindows) ? view_as<int>(float(iFactor)) : iFactor;
StoreToAddress(g_pFactor, iSetFactor, NumberType_Int32);
}

void HotPatchCenterPellet(bool bNewValue)
{
bool iCurrentValue = LoadFromAddress(g_pCenterPellet, NumberType_Int8);
if (iCurrentValue == bNewValue) {
return;
}

StoreToAddress(g_pCenterPellet, view_as<int>(bNewValue), NumberType_Int8);

PatchBullets(g_hCvarRing1Bullets.IntValue, bNewValue);
}

void PatchBullets(int iBullets, bool bCenterPellet)
{
int iOffset1 = iBullets + (bCenterPellet ? 1 : 0);
int iOffset2 = iBullets + (bCenterPellet ? 2 : 1);

StoreToAddress(g_pBullets[0], iOffset1, NumberType_Int8);
StoreToAddress(g_pBullets[1], iOffset1, NumberType_Int8);
StoreToAddress(g_pBullets[2], iOffset2, NumberType_Int8);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#if defined _l4d_pellet_spread_customizer_included_
#endinput
#endif
#define _l4d_pellet_spread_customizer_included_

/**
* @brief Called whenever a weapon is about to fire bullets.
* @remarks Determine whether the first bullet should be affected by ScatterYaw/Pitch.
* @remarks Bullet that's affected by ScatterYaw/Pitch is called pellet hereinafter.
* @note Called in pre-detour of function "CTerrorGun::FireBullet".
* @note Has no effect if either ScatterYaw/Pitch is not configured for this weapon.
*
* @param weapon Weapon entity index
*
* @return Plugin_Handled to force the first bullet acting as a pellet,
* Plugin_Continue otherwise.
*/
forward Action L4D_OnPelletFirstBullet(int weapon);

/**
* @brief Called whenever generating spread values for a pellet.
* @remarks Spread -> how far from the center, SpreadDir -> circular angle.
* @remarks By default the actual output for spread is ranged in [-x, x], for angle it is [0, 180].
* @note Negative values are accepted, for the opposite direction.
* @note "nMaxPellets" adds 1 if "L4D_OnPelletFirstBullet" returns Plugin_Handled.
* @note Called in post-detour of function "SharedRandomFloat", the same frame later "L4D_OnPelletFirstBullet".
*
* @param weapon Weapon entity index
* @param spread/angle Spread value
* @param nPellet Current pellet number (starting from 1)
* @param nMaxPellets Max pellet number
*
* @return Plugin_Changed to change to the exact value,
* Plugin_Continue to leave it randomly generated.
*/
forward Action L4D_OnPelletSpread(int weapon, float &spread, int nPellet, int nMaxPellets);
forward Action L4D_OnPelletSpreadDir(int weapon, float &angle, int nPellet, int nMaxPellets);



public SharedPlugin __pl_l4d_pellet_spread_customizer =
{
name = "l4d_pellet_spread_customizer",
file = "l4d_pellet_spread_customizer.smx",
#if defined REQUIRE_PLUGIN
required = 1,
#else
required = 0,
#endif
};
Loading
Loading