Skip to content

Commit 0490ce3

Browse files
author
Matthew Bate
committed
Updates to Chat, Prevent Punt Spamming
1 parent 1cf3a0f commit 0490ce3

File tree

15 files changed

+385
-126
lines changed

15 files changed

+385
-126
lines changed

BHD-ServerManager/Classes/GameManagement/ServerMemory.cs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,9 +1288,7 @@ public static void WriteMemorySendChatMessage(int MsgLocation, string Msg)
12881288
int revert_colorbuffer = 0;
12891289
byte[] revert_colorcode = Functions.ToByteArray("6A 01".Replace(" ", ""));
12901290
WriteProcessMemory((int)processHandle, 0x00462ABA, revert_colorcode, revert_colorcode.Length, ref revert_colorbuffer);
1291-
1292-
1293-
1291+
12941292
}
12951293
// Function: WriteMemoryChatCountDownKiller
12961294
public static void WriteMemoryChatCountDownKiller(int ChatLogAddr)
@@ -1417,6 +1415,8 @@ public static void WriteMemoryTogglePlayerGodMode(int playerSlot, int health)
14171415
byte[] setDamageBy = BitConverter.GetBytes(0);
14181416
int setDamageByWrite = 0;
14191417

1418+
AppDebug.Log("WriteMemoryTogglePlayerGodMode", $"Player Health Data - Base: 0x{playerNewLocationAddress:X8}, Object: 0x{playerObjectLocation:X8}, Damage Addr: 0x{(playerObjectLocation + 0x138):X8}, Health Addr: 0x{(playerObjectLocation + 0xE2):X8}");
1419+
14201420
WriteProcessMemory((int)processHandle, playerObjectLocation + 0x138, setDamageBy, setDamageBy.Length, ref setDamageByWrite);
14211421
WriteProcessMemory((int)processHandle, playerObjectLocation + 0xE2, setPlayerHealth, setPlayerHealth.Length, ref setPlayerHealthWrite);
14221422

@@ -2214,6 +2214,64 @@ public static void ScoreMap()
22142214

22152215
}
22162216

2217+
// Function: ReadMemoryPlayerLeaningStatus
2218+
// Returns: 0 = upright, 2 = left leaning, 4 = right leaning
2219+
public static int ReadMemoryPlayerLeaningStatus(int playerSlot)
2220+
{
2221+
int buffer = 0;
2222+
byte[] PointerAddr9 = new byte[4];
2223+
var Pointer = baseAddr + 0x005ED600;
2224+
2225+
// read the playerlist memory
2226+
ReadProcessMemory((int)processHandle, Pointer, PointerAddr9, PointerAddr9.Length, ref buffer);
2227+
var playerlistStartingLocationPointer = BitConverter.ToInt32(PointerAddr9, 0) + 0x28;
2228+
byte[] playerListStartingLocationByteArray = new byte[4];
2229+
int playerListStartingLocationBuffer = 0;
2230+
ReadProcessMemory((int)processHandle, playerlistStartingLocationPointer, playerListStartingLocationByteArray, playerListStartingLocationByteArray.Length, ref playerListStartingLocationBuffer);
2231+
2232+
int playerlistStartingLocation = BitConverter.ToInt32(playerListStartingLocationByteArray, 0);
2233+
2234+
// Calculate the player's address
2235+
int playerNewLocationAddress = playerlistStartingLocation + (playerSlot - 1) * 0xAF33C;
2236+
2237+
byte[] playerObjectLocationBytes = new byte[4];
2238+
int playerObjectLocationRead = 0;
2239+
ReadProcessMemory((int)processHandle, playerNewLocationAddress + 0x11C, playerObjectLocationBytes, playerObjectLocationBytes.Length, ref playerObjectLocationRead);
2240+
int playerObjectLocation = BitConverter.ToInt32(playerObjectLocationBytes, 0);
2241+
2242+
// Read prone/rolling status (2 bytes)
2243+
byte[] proneStatusBytes = new byte[2];
2244+
int proneStatusRead = 0;
2245+
ReadProcessMemory((int)processHandle, playerObjectLocation + 0x164, proneStatusBytes, proneStatusBytes.Length, ref proneStatusRead);
2246+
short proneStatus = BitConverter.ToInt16(proneStatusBytes, 0);
2247+
2248+
// Check if player is rolling (95 or 96) - ignore leaning status when rolling
2249+
if (proneStatus == 95 || proneStatus == 96)
2250+
{
2251+
AppDebug.Log("ReadMemoryPlayerLeaningStatus", $"Slot {playerSlot} - Rolling detected (prone: {proneStatus}), ignoring lean");
2252+
return 0; // Return 0 (upright) when rolling
2253+
}
2254+
2255+
// Read leaning status (2 bytes)
2256+
byte[] leaningStatusBytes = new byte[2];
2257+
int leaningStatusRead = 0;
2258+
ReadProcessMemory((int)processHandle, playerObjectLocation + 0x102, leaningStatusBytes, leaningStatusBytes.Length, ref leaningStatusRead);
2259+
short leaningStatus = BitConverter.ToInt16(leaningStatusBytes, 0);
2260+
2261+
// Normalize the status (remove the +8 for moving)
2262+
// 0 = upright, 2 = left lean, 4 = right lean
2263+
// 8 = moving upright, 10 = moving left lean, 12 = moving right lean
2264+
int normalizedStatus = leaningStatus % 8;
2265+
2266+
AppDebug.Log("ReadMemoryPlayerLeaningStatus",
2267+
$"Slot {playerSlot} - Object: 0x{playerObjectLocation:X8}, " +
2268+
$"Lean Addr: 0x{(playerObjectLocation + 0x102):X8}, " +
2269+
$"Prone Addr: 0x{(playerObjectLocation + 0x164):X8}, " +
2270+
$"Raw: {leaningStatus}, Normalized: {normalizedStatus}");
2271+
2272+
return normalizedStatus;
2273+
}
2274+
22172275
}
22182276

22192277
}

BHD-ServerManager/Classes/InstanceManagers/theInstanceManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public static void InitializeTickers()
114114
{
115115
// TODO: Remove the need for the thisServer variable in the ticker methods.
116116
CommonCore.Ticker?.Start("ServerManager", 500, () => tickerServerManager.runTicker());
117-
CommonCore.Ticker?.Start("ChatManager", 500, () => tickerChatManagement.runTicker());
117+
CommonCore.Ticker?.Start("ChatManager", 100, () => tickerChatManagement.runTicker());
118118
CommonCore.Ticker?.Start("PlayerManager", 1000, () => tickerPlayerManagement.runTicker());
119119
CommonCore.Ticker?.Start("BanManager", 1000, () => tickerBanManagement.runTicker());
120120

BHD-ServerManager/Classes/Services/ProxyDetection/ProxyCheckManager.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,54 @@ public static int ReloadCacheFromDatabase(bool replaceExisting = true)
8585

8686
return addedCount + updatedCount;
8787
}
88+
89+
/// <summary>
90+
/// Check if an IP address is internal/private.
91+
/// </summary>
92+
private static bool IsInternalIP(IPAddress ipAddress)
93+
{
94+
if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
95+
{
96+
// Check for IPv6 loopback (::1) and link-local (fe80::/10)
97+
if (IPAddress.IsLoopback(ipAddress))
98+
return true;
99+
100+
byte[] bytes = ipAddress.GetAddressBytes();
101+
// Link-local: fe80::/10
102+
if (bytes[0] == 0xfe && (bytes[1] & 0xc0) == 0x80)
103+
return true;
104+
105+
// Unique local: fc00::/7
106+
if ((bytes[0] & 0xfe) == 0xfc)
107+
return true;
108+
109+
return false;
110+
}
111+
112+
// IPv4 checks
113+
if (IPAddress.IsLoopback(ipAddress))
114+
return true;
115+
116+
byte[] addressBytes = ipAddress.GetAddressBytes();
88117

118+
// 10.0.0.0/8
119+
if (addressBytes[0] == 10)
120+
return true;
121+
122+
// 172.16.0.0/12
123+
if (addressBytes[0] == 172 && addressBytes[1] >= 16 && addressBytes[1] <= 31)
124+
return true;
125+
126+
// 192.168.0.0/16
127+
if (addressBytes[0] == 192 && addressBytes[1] == 168)
128+
return true;
129+
130+
// 169.254.0.0/16 (link-local)
131+
if (addressBytes[0] == 169 && addressBytes[1] == 254)
132+
return true;
133+
134+
return false;
135+
}
89136
/// <summary>
90137
/// Check an IP address for proxy/VPN/Tor. Uses in-memory cache first, then database, then API.
91138
/// </summary>
@@ -102,6 +149,21 @@ public static async Task<ProxyCheckResult> CheckIPAsync(IPAddress ipAddress)
102149
if (ipAddress == null)
103150
throw new ArgumentNullException(nameof(ipAddress));
104151

152+
// Step 0: Check if IP is internal/private - skip proxy check
153+
if (IsInternalIP(ipAddress))
154+
{
155+
AppDebug.Log("ProxyCheckManager", $"Internal IP detected: {ipAddress} - Skipping proxy check");
156+
return new ProxyCheckResult
157+
{
158+
Success = true,
159+
IsProxy = false,
160+
IsVpn = false,
161+
IsTor = false,
162+
RiskScore = 0,
163+
ErrorMessage = "Internal IP Proxy Check Skipped"
164+
};
165+
}
166+
105167
// Step 1: Check in-memory cache first
106168
var cachedRecord = GetFromMemoryCache(ipAddress);
107169
if (cachedRecord != null && !IsCacheExpired(cachedRecord))

BHD-ServerManager/Classes/Tickers/tickerBanManagement.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,11 @@ public static void CheckAndPuntBannedPlayers()
426426
// Punt the player if they're banned
427427
if (shouldPunt)
428428
{
429+
if (player.PlayerPing <= 0)
430+
{
431+
return; // Player not in game yet. Needless to spam the "punt".
432+
}
433+
429434
ServerMemory.WriteMemorySendConsoleCommand("punt " + slotNum);
430435
AppDebug.Log("tickerBanManagement", $"Punting player '{player.PlayerName}' (Slot {slotNum}). Reason: {puntReason}");
431436
}

BHD-ServerManager/Classes/Tickers/tickerChatManagement.cs

Lines changed: 54 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
using BHD_ServerManager.Classes.GameManagement;
2-
using BHD_ServerManager.Forms;
1+
using BHD_ServerManager;
32
using BHD_ServerManager.Classes.CoreObjects;
3+
using BHD_ServerManager.Classes.GameManagement;
44
using BHD_ServerManager.Classes.InstanceManagers;
55
using BHD_ServerManager.Classes.Instances;
66
using BHD_ServerManager.Classes.SupportClasses;
7+
using BHD_ServerManager.Forms;
78

89
namespace BHD_ServerManager.Classes.Tickers
910
{
@@ -19,60 +20,42 @@ public static class tickerChatManagement
1920
// For deduplication of chat messages
2021
private static string? _lastProcessedPlayerName = null;
2122
private static string? _lastProcessedMessageText = null;
22-
23-
// Helper for UI thread safety
23+
private static DateTime _lastProcessedMessageTime = DateTime.MinValue;
2424

2525
public static void runTicker()
2626
{
27-
// Always ensure UI thread safety for UI-bound operations
28-
if (thisServer.InvokeRequired)
29-
{
30-
try
31-
{
32-
thisServer.BeginInvoke(new Action(runTicker));
33-
}
34-
catch (Exception ex)
35-
{
36-
AppDebug.Log("tickerChatManagement", $"Error invoking runTicker: {ex.Message}");
37-
}
38-
return;
39-
}
40-
4127
lock (tickerLock)
4228
{
43-
4429
// Only process chat when server is online or in start delay
4530
if (thisInstance.instanceStatus != InstanceStatus.ONLINE &&
4631
thisInstance.instanceStatus != InstanceStatus.STARTDELAY)
4732
{
4833
return;
4934
}
5035

51-
// Ensure the chat tab is initialized before proceeding
52-
thisServer.ChatTab.ChatTickerHook();
53-
54-
5536
if (ServerMemory.ReadMemoryIsProcessAttached())
5637
{
5738
// Recover auto message counter before processing auto messages
5839
RecoverAutoMessageCounter();
5940

60-
// Process auto messages (non-blocking)
41+
// Process auto messages (non-blocking to avoid ticker delays)
6142
Task.Run(ProcessAutoMessages);
6243

63-
// Process latest chat message and update UI (non-blocking)
64-
Task.Run(ProcessChatMessages);
44+
// Process latest chat message immediately (no Task.Run to prevent missing messages)
45+
ProcessChatMessages();
6546
}
6647
else
6748
{
68-
AppDebug.Log("tickerChatManagement", "Server process is not attached. Ticker Skipping.");
49+
AppDebug.Log("tickerChatManagement", "Server process is not attached. Ticker skipping.");
6950
}
7051
}
7152
}
7253

7354
public static void ProcessChatMessages()
7455
{
56+
// Read memory immediately on ticker thread to avoid missing messages
7557
var latestMessage = ServerMemory.ReadMemoryLastChatMessage();
58+
7659
if (latestMessage == null || latestMessage.Length < 3)
7760
return;
7861

@@ -92,13 +75,30 @@ public static void ProcessChatMessages()
9275

9376
string playerName = lastMessage.Substring(0, msgStart).Trim();
9477
string playerMessage = lastMessage.Substring(msgStart + 1).Trim();
95-
96-
// Prevent duplicate messages based on last processed message
97-
if (_lastProcessedPlayerName == playerName && _lastProcessedMessageText == playerMessage)
98-
return;
99-
78+
79+
AppDebug.Log("ProcessChatMessages", $"Last Message: {latestMessage[0]} - {latestMessage[1]} - {latestMessage[2]}");
80+
10081
lock (chatLog)
10182
{
83+
// Primary check: Compare against the most recent chat log entry
84+
if (chatLog.Count > 0)
85+
{
86+
var lastEntry = chatLog[^1];
87+
if (lastEntry.PlayerName == playerName &&
88+
lastEntry.MessageText == playerMessage)
89+
{
90+
return;
91+
}
92+
}
93+
94+
// Secondary check: Use static fields with 1-second window
95+
// This allows multi-part messages (sent 1+ seconds apart) to both be logged
96+
if (_lastProcessedPlayerName == playerName &&
97+
_lastProcessedMessageText == playerMessage)
98+
{
99+
return;
100+
}
101+
102102
// Message type mapping
103103
int msgType = msgTypeBytes switch
104104
{
@@ -112,8 +112,9 @@ public static void ProcessChatMessages()
112112
if (msgType == 0 && int.TryParse(latestMessage[0], out int chatLogAddr))
113113
ServerMemory.WriteMemoryChatCountDownKiller(chatLogAddr);
114114

115-
// Find PlayerTeam number
115+
// Find player team number
116116
int teamNum = 3;
117+
117118
foreach (var player in thisInstance.playerList.Values)
118119
{
119120
if (player.PlayerName == playerName)
@@ -123,6 +124,7 @@ public static void ProcessChatMessages()
123124
}
124125
}
125126

127+
// Create chat log entry
126128
ChatLogObject newChatLog = new ChatLogObject
127129
{
128130
PlayerName = playerName,
@@ -133,14 +135,24 @@ public static void ProcessChatMessages()
133135
};
134136

135137
chatLog.Add(newChatLog);
136-
137138
chatInstanceManager.SaveChatLogEntry(newChatLog);
138139

139-
// Update last processed message for deduplication
140+
// Update deduplication tracking
140141
_lastProcessedPlayerName = playerName;
141142
_lastProcessedMessageText = playerMessage;
143+
_lastProcessedMessageTime = DateTime.Now;
142144

143145
AppDebug.Log("tickerChatManagement", $"Chat Message: {playerName} ({teamNum}) - {playerMessage} (Type: {msgType})");
146+
147+
// Marshal UI updates to UI thread AFTER processing
148+
if (thisServer.InvokeRequired)
149+
{
150+
thisServer.BeginInvoke(() => thisServer.ChatTab.ChatTickerHook());
151+
}
152+
else
153+
{
154+
thisServer.ChatTab.ChatTickerHook();
155+
}
144156
}
145157
}
146158

@@ -222,6 +234,12 @@ public static void RecoverAutoMessageCounter()
222234
_autoMessageRecoveryDone = true;
223235
}
224236

225-
237+
public static void ResetDeduplication()
238+
{
239+
_lastProcessedPlayerName = null;
240+
_lastProcessedMessageText = null;
241+
_lastProcessedMessageTime = DateTime.MinValue;
242+
_autoMessageRecoveryDone = false;
243+
}
226244
}
227245
}

0 commit comments

Comments
 (0)