Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 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
11 changes: 11 additions & 0 deletions TShockAPI/Bouncer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,17 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA
return;
}

// TorchGod projectile is created only when a player is experiencing the torch god event.
// However, checking for happyFunTorchTime being true doesn't work, since due to a bug or oversight,
// clients don't sync it when it's set to true, so we check for unlockedBiomeTorches being false instead.
// The server will assume ownership of this projectile, despite it being hostile, so it is the only hostile projectile clients are allowed to create.
if (type == ProjectileID.TorchGod && !args.Player.TPlayer.unlockedBiomeTorches)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile super accepted from (torch god) {0}", args.Player.Name));
args.Handled = false;
return;
}
Comment on lines +1387 to +1396
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Potential exploit: hostile projectile bypass for non-event players

The condition !args.Player.TPlayer.unlockedBiomeTorches allows any player who has not yet unlocked biome torches to send TorchGod projectiles, regardless of whether they are currently in the event. Because this check is placed before the Main.projHostile[type] gate, it effectively lets a malicious client bypass the hostile-projectile filter simply by never completing the torch unlocking mechanic.

A griefer on a server with multiple users who haven't unlocked biome torches could spam TorchGod projectile packets at will. Even if the projectile itself does not deal direct player damage, it can still trigger server-side effects (tile modifications, audio/visual spam to other clients, etc.) that were intentionally gated behind the hostile-projectile check.

Consider adding an additional server-side guard — for example, a cooldown, a per-player projectile count limit, or logging/rate-limiting suspicious creation spikes — to mitigate abuse until a more authoritative event-state sync is available from the client.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially addressed in a95e88c.
It isn't possible to check if the player is in the torch god event, since the client doesn't send PlayerInfo when setting the flag to true, only when setting it to false.


/// If the projectile is a directional projectile, check if the player is holding their respected item to validate the projectile creation.
if (directionalProjectiles.ContainsKey(type))
{
Expand Down
23 changes: 22 additions & 1 deletion TShockAPI/Handlers/SendTileRectHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -591,14 +591,20 @@ private static bool IsRectPositionValid(TSPlayer player, TileRect rect)
/// <returns><see langword="true"/>, if the rect at a valid distance, otherwise <see langword="false"/>.</returns>
private static bool IsRectDistanceValid(TSPlayer player, TileRect rect)
{
int range = 32;

// Torches can be modified from far away through a water gun, or if the player is experiencing the torch god event.
if (IsRectATorch(player, rect))
range = 104; // Base value of 100 comes from Player.TorchAttack, bumped by 4 blocks to be safe.

for (int x = 0; x < rect.Width; x++)
{
for (int y = 0; y < rect.Height; y++)
{
int realX = rect.X + x;
int realY = rect.Y + y;

if (!player.IsInRange(realX, realY))
if (!player.IsInRange(realX, realY, range))
{
return false;
}
Expand All @@ -608,6 +614,21 @@ private static bool IsRectDistanceValid(TSPlayer player, TileRect rect)
return true;
}

/// <summary>
/// Checks whether the tile rect is modifying a torch.
/// </summary>
/// <param name="player">The player the operation originates from.</param>
/// <param name="rect">The tile rectangle of the operation.</param>
/// <returns><see langword="true"/>, if the rect is modifying a torch, otherwise <see langword="false"/>.</returns>
private static bool IsRectATorch(TSPlayer player, TileRect rect)
Comment thread
lost-werewolf marked this conversation as resolved.
Outdated
{
// Rect is definitely not a torch...
if (rect.Width != 1 || rect.Height != 1)
return false;

// Is the rect actually modifying a torch?
return rect[0, 0].Type == TileID.Torches && Main.tile[rect.X, rect.Y].type == TileID.Torches;
}
Comment on lines +628 to +630
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Extended range may not apply when relighting extinguished torches

The condition Main.tile[rect.X, rect.Y].type == TileID.Torches requires the current server-side tile to already be a torch. However, the PR description mentions that the player may need to "relight torches extinguished by the event."

If the Torch God event removes or replaces a torch tile (rather than only modifying its frame/paint), the server tile at that position would no longer have type TileID.Torches at the time the client sends the rect to relight it. In that case, IsRectATorch would return false, the range would stay at 32, and the relighting packet would be rejected — exactly the problem this PR aims to fix.

It's worth clarifying whether the vanilla Player.TorchAttack path only repaints/converts existing torch tiles (in which case the check is fine) or whether it can also replace/remove and later recreate them. If the latter, the server-side type guard should be relaxed or replaced with a check solely on rect[0, 0].Type.

Copy link
Copy Markdown
Contributor Author

@lost-werewolf lost-werewolf Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the torch is removed before the client relights it, it's not a problem. Player.RelightTorches doesn't send a TileSquare on a tile that's either NOT a torch or is NOT an unlit torch, so this shouldn't occur. Lit/unlit torches are the same tile, just with different framing.


/// <summary>
/// Checks whether the tile rect is a valid conversion spread (Clentaminator, Powders, etc.).
Expand Down
Loading