效果如下
https://www.bilibili.com/video/BV16A9jYWEZC
首先:这段代码似乎对于绿色社区没有什么用处
其次:这段代码基于Windows平台且我也不清楚是否有更好的办法实现
简述实现:
我们PreHook -> __int64 __fastcall sub_18074E130(__int64 a1, __int64 a2) 然后修改内存


我们为这个jz指令生成签名 然后通过NativeAPI.FindSignature获取到签名的内存地址
我们通过对该内存的实际读取 发现这个jz使用的是近跳转(即机器码:0F 84)
然后我们对这条jz指令的 前一字节读取 判断有没有67属性
如果没有67属性相对地址为32位(即4字节,这时给jzOffsetSize赋值6),如有相对地址为16位(即2字节赋值4)
(jzOffsetSize指的是指令长度+偏移长度)
然后解除jz指令开始到偏移量结尾区域的内存保护 并将相对地址存入offsetBytes
接下来我们来到sub_18074E130函数的hook部分
因为我们使用prehook 所以我们在这时判断玩家种子设定 然后更改内存
如果玩家种子想要与服务器同步 我们便执行跳转
也就是将0F 84改写为90 E9。
其实也可以改回0F 84,但那样跳转是否执行会受sv_usercmd_custom_random_seed设定值的影响
90代表NOP指令 E9代表无条件近跳转
如果玩家种子不想要与服务器同步 我们便不执行跳转
于是我们将jz内存地址向后的jzOffsetSize都填入90指令(即NOP)
代码如下:
using System.Runtime.InteropServices;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
namespace FNS_Limit {
public class FNS_Limit : BasePlugin {
public override string ModuleName => "FNS Limit";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "Ambr0se";
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);
private MemoryFunctionWithReturn<nint, nint, nint> unknowFunction1 = new("48 89 5C 24 ? 57 48 81 EC ? ? ? ? 48 8B DA E8");
private nint jzAddress;
private int jzOffsetSize = 6;
private List offsetBytes = new List();
private Dictionary<ulong, bool> seedSync = new Dictionary<ulong, bool>();
private bool currentMemoryState = true;
private bool memoryProtectionDisabled = false;
public override void Load(bool hotReload) {
jzAddress = NativeAPI.FindSignature(Addresses.ServerPath, "0F 84 ? ? ? ? FF 15 ? ? ? ? 0F 57 C9 BA");
Console.WriteLine($"跳转指令内存地址:{jzAddress}");
if (!DisableMemoryProtection()) {
Console.WriteLine("无法解除内存保护");
return;
}
unsafe {
byte prefix = *(byte*)(jzAddress - 1);
jzOffsetSize = prefix == 0x67 ? 4 : 6;
for (int i = 0; i < jzOffsetSize; i++) {
if (i > 1) {
offsetBytes.Add(*(byte*)(jzAddress + i));
}
}
}
unknowFunction1.Hook((hook) => {
CCSPlayer_MovementServices? playerMovement = new CCSPlayer_MovementServices(hook.GetParam(0));
CCSPlayerController? player = playerMovement.Pawn?.Value.Controller.Value?.As();
if (player == null || !player.IsValid)
return HookResult.Continue;
if (!seedSync.TryGetValue(player.SteamID, out bool needSync)) {
seedSync[player.SteamID] = true;
needSync = true;
}
// 只有状态变化时才修改内存
if (needSync != currentMemoryState) {
ModifyMemory(needSync);
}
return HookResult.Continue;
}, HookMode.Pre);
}
[ConsoleCommand("seed")]
public void SetSeedSync(CCSPlayerController player, CommandInfo command) {
if (!player.IsValid)
return;
bool newState = !seedSync.GetValueOrDefault(player.SteamID, true);
seedSync[player.SteamID] = newState;
player.PrintToChat($"已设置{player.PlayerName}种子 {(newState ? $"{ChatColors.Green}同步" : $"{ChatColors.LightRed}不同步")}");
}
private bool DisableMemoryProtection() {
if (memoryProtectionDisabled)
return true;
uint oldProtect;
if (!VirtualProtect(new IntPtr(jzAddress), (uint)jzOffsetSize, 0x40, out oldProtect)) {
Console.WriteLine($"虚拟保护解除失败:{Marshal.GetLastWin32Error()}");
return false;
}
memoryProtectionDisabled = true;
return true;
}
private void ModifyMemory(bool jmp) {
unsafe {
for (int i = 0; i < jzOffsetSize; i++) {
byte* ptr = (byte*)(jzAddress + i);
if (!jmp) {
*ptr = 0x90; // NOP
} else {
if (i == 0)
*ptr = 0x90;
else if (i == 1)
*ptr = 0xE9;
else
*ptr = offsetBytes[i - 2];
}
}
}
currentMemoryState = jmp;
// Console.WriteLine($"[FNS Limit] 内存状态已切换至:{(jmp ? "JMP" : "NOP")}");
}
}
}