Skip to content

Commit fc0843e

Browse files
author
xstage
committed
update 1.1.0
1 parent ba66c7b commit fc0843e

13 files changed

Lines changed: 300 additions & 93 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.editorconfig
2+
obj
3+
bin
4+
*.sln

FixRandomSpawn.csproj

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.282" />
12-
<Content Include="gamedata\**">
13-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
14-
</Content>
11+
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.303" />
1512
</ItemGroup>
1613

1714
</Project>

Plugin.cs

Lines changed: 0 additions & 74 deletions
This file was deleted.

Plugin/GameRules.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using CounterStrikeSharp.API;
2+
using CounterStrikeSharp.API.Core;
3+
using Microsoft.Extensions.Logging;
4+
5+
namespace FixRandomSpawn;
6+
7+
public sealed class GameRules
8+
{
9+
private Plugin plugin_ = null!;
10+
private CCSGameRules value_ = null!;
11+
12+
public void Init(bool hotReload, Plugin plugin)
13+
{
14+
plugin_ = plugin;
15+
16+
plugin.RegisterListener<Listeners.OnMapStart>(manName =>
17+
{
18+
Server.NextFrame(() =>
19+
{
20+
FindGameRules();
21+
});
22+
});
23+
if (hotReload) FindGameRules();
24+
}
25+
26+
public CCSGameRules Get() => value_;
27+
28+
public void FindGameRules()
29+
{
30+
try
31+
{
32+
value_ = Utilities.FindAllEntitiesByDesignerName<CCSGameRulesProxy>("cs_gamerules").First().GameRules!;
33+
}
34+
catch (Exception)
35+
{
36+
plugin_.Logger.LogError("Couldn't find `CCSGameRules`");
37+
}
38+
}
39+
}

Plugin/MemoryPatch.cs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System.Globalization;
2+
using System.Runtime.InteropServices;
3+
using CounterStrikeSharp.API.Core;
4+
using CounterStrikeSharp.API.Modules.Memory;
5+
6+
namespace FixRandomSpawn;
7+
8+
public unsafe partial class MemoryPatch
9+
{
10+
[LibraryImport("libc", EntryPoint = "mprotect")]
11+
private static partial int MProtect(nint address, int len, int protect);
12+
13+
[LibraryImport("kernel32.dll")]
14+
[return: MarshalAs(UnmanagedType.Bool)]
15+
private unsafe static partial bool VirtualProtect(nint address, int dwSize, int newProtect, int* oldProtect);
16+
17+
private readonly Dictionary<int, List<byte>> oldPattern_ = [];
18+
private readonly string modulePath_;
19+
private nint addr_;
20+
21+
public MemoryPatch(string? modulePath = null)
22+
{
23+
modulePath_ = modulePath ?? Addresses.ServerPath;
24+
}
25+
26+
public void Init(string signature)
27+
{
28+
addr_ = NativeAPI.FindSignature(modulePath_, signature);
29+
}
30+
31+
public void Apply(string patchSignature, int offset = 0)
32+
{
33+
if (string.IsNullOrEmpty(patchSignature) || oldPattern_.ContainsKey(offset)) return;
34+
35+
byte[] bytes = [.. patchSignature.Split(' ').Select(b => byte.Parse(b, NumberStyles.HexNumber))];
36+
byte* ptrAddr = GetPtr<byte>(offset);
37+
38+
SetMemAccess(ptrAddr, bytes.Length);
39+
40+
for (int i = 0; i < bytes.Length; i++)
41+
{
42+
oldPattern_[offset] = [];
43+
oldPattern_[offset].Add(ptrAddr[i]);
44+
45+
ptrAddr[i] = bytes[i];
46+
}
47+
}
48+
49+
public void Restore()
50+
{
51+
if (oldPattern_.Count == 0) return;
52+
53+
foreach (var (offset, pattern) in oldPattern_)
54+
{
55+
byte* ptrAddr = GetPtr<byte>(offset);
56+
57+
for (int i = 0; i < pattern.Count; i++)
58+
{
59+
ptrAddr[i] = pattern[i];
60+
}
61+
}
62+
}
63+
64+
private T* GetPtr<T>(int offset = 0) where T: unmanaged => (T*)(addr_ + offset);
65+
66+
private bool SetMemAccess<T>(T* ptrAddr, int size) where T: unmanaged
67+
{
68+
nint addr = (nint)ptrAddr;
69+
70+
if (addr == nint.Zero)
71+
throw new ArgumentNullException(nameof(ptrAddr));
72+
73+
const int PAGESIZE = 4096;
74+
75+
nint LALIGN(nint addr) => addr & ~(PAGESIZE-1);
76+
int LALDIF(nint addr) => (int)(addr % PAGESIZE);
77+
78+
int* oldProtect = stackalloc int[1];
79+
80+
return RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ?
81+
MProtect(LALIGN(addr), size + LALDIF(addr), 7) == 0 : VirtualProtect(addr, size, 0x40, oldProtect);
82+
}
83+
}

Plugin/Plugin.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using CounterStrikeSharp.API.Core;
2+
3+
namespace FixRandomSpawn;
4+
5+
public sealed partial class Plugin : BasePlugin
6+
{
7+
public override string ModuleName { get; } = "FixRandomSpawn";
8+
public override string ModuleVersion { get; } = "1.1.0";
9+
public override string ModuleAuthor { get; } = "xstage";
10+
11+
private readonly GameRules gameRules_ = new GameRules();
12+
private readonly MemoryPatch memoryPatch_ = new MemoryPatch();
13+
14+
public override void Load(bool hotReload)
15+
{
16+
gameRules_.Init(hotReload, this);
17+
18+
memoryPatch_.Init(GameData.GetSignature("EntSelectSpawnPoint"));
19+
memoryPatch_.Apply(GameData.GetSignature("EntSelectSpawnPoint_Patch1"), GameData.GetOffset("EntSelectSpawnPoint_Patch1"));
20+
}
21+
22+
public override void Unload(bool hotReload)
23+
{
24+
memoryPatch_.Restore();
25+
}
26+
}

Plugin/PluginConfig.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System.Text.Json.Serialization;
2+
using CounterStrikeSharp.API.Core;
3+
using CounterStrikeSharp.API.Core.Attributes.Registration;
4+
using CounterStrikeSharp.API.Modules.Commands;
5+
using CounterStrikeSharp.API.Modules.Extensions;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace FixRandomSpawn;
9+
10+
public class Warmup
11+
{
12+
[JsonPropertyName("enable")]
13+
public bool Enable { get; set; } = false;
14+
15+
[JsonPropertyName("buy_anywhere")]
16+
public bool BuyAnywhere { get; set; } = false;
17+
18+
[JsonPropertyName("alert_for_players")]
19+
public bool AlertForPlayers { get; set; } = false;
20+
}
21+
22+
public class PluginConfig : BasePluginConfig
23+
{
24+
[JsonPropertyName("warmup_mode")]
25+
public Warmup WarmupMode { get; set; } = new();
26+
}
27+
28+
public sealed partial class Plugin : IPluginConfig<PluginConfig>
29+
{
30+
public PluginConfig Config { get; set; } = new();
31+
32+
[ConsoleCommand("css_randomspawn_reload", $"Reload plugin FixRandomSpawn`")]
33+
public void OnConfigReload(CCSPlayerController? player, CommandInfo info)
34+
{
35+
try
36+
{
37+
Config.Reload();
38+
39+
info.ReplyToCommand($"[{ModuleName}] You have successfully reloaded the config.");
40+
}
41+
catch (Exception ex)
42+
{
43+
info.ReplyToCommand($"[{ModuleName}] An error occurred.");
44+
Logger.LogError("An error occurred while reloading the configuration: {error}", ex.Message);
45+
}
46+
}
47+
48+
public void OnConfigParsed(PluginConfig config)
49+
{
50+
if (config.Version < Config.Version)
51+
{
52+
Logger.LogError("Your plugin configuration version is outdated! (v. {old} -> v. {new})", config.Version, Config.Version);
53+
}
54+
55+
Config = config;
56+
}
57+
}

Plugin/PluginEvents.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using CounterStrikeSharp.API;
2+
using CounterStrikeSharp.API.Core;
3+
using CounterStrikeSharp.API.Core.Attributes.Registration;
4+
using CounterStrikeSharp.API.Core.Translations;
5+
using CounterStrikeSharp.API.Modules.Cvars;
6+
7+
namespace FixRandomSpawn;
8+
9+
public sealed partial class Plugin
10+
{
11+
[GameEventHandler]
12+
public HookResult OnRoundPrestart(EventRoundPrestart @event, GameEventInfo info)
13+
{
14+
if (!Config.WarmupMode.Enable)
15+
return HookResult.Continue;
16+
17+
int value = Convert.ToInt32(gameRules_.Get().WarmupPeriod);
18+
19+
ConVar[] convars = [
20+
ConVar.Find("mp_randomspawn")!,
21+
ConVar.Find("mp_buy_anywhere")!,
22+
];
23+
24+
if (convars[0].GetPrimitiveValue<int>() != 0 || value != 0)
25+
{
26+
convars[0]!.SetValue(value);
27+
}
28+
29+
if (Config.WarmupMode.BuyAnywhere && (convars[1].GetPrimitiveValue<int>() != 0 || value != 0))
30+
{
31+
Server.ExecuteCommand($"mp_buy_anywhere {value}");
32+
}
33+
34+
return HookResult.Continue;
35+
}
36+
37+
[GameEventHandler]
38+
public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info)
39+
{
40+
var player = @event.Userid;
41+
42+
if (player == null || @event.Oldteam != 0 || player.IsBot) return HookResult.Continue;
43+
44+
if (gameRules_.Get().WarmupPeriod && Config.WarmupMode.Enable && Config.WarmupMode.AlertForPlayers)
45+
{
46+
player.PrintToChat(Localizer.ForPlayer(player, "Plugin.AlertForPlayers"));
47+
}
48+
49+
return HookResult.Continue;
50+
}
51+
}

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,16 @@ Fixes ConVar `mp_randomspawn` for any game mode
1313
- Make a folder in /plugins named /FixRandomSpawn.
1414
- Put the plugin files in to the new folder.
1515
- Restart your server.
16+
17+
## Configuration
18+
`css_randomspawn_reload` - Reload configuration
19+
```json
20+
{
21+
"warmup_mode": {
22+
"enable": false, // Enable the plugin only for warmup time
23+
"buy_anywhere": false, // Allow purchase anywhere on the map
24+
"alert_for_players": false // Notification for players about the enabled random spawns
25+
},
26+
"ConfigVersion": 1
27+
}
28+
```

gamedata/FixRandomSpawn.json

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)