From 8f596968e1d16a320e8fd155f74e2e310766b93b Mon Sep 17 00:00:00 2001 From: ACaiCat <13110818005@qq.com> Date: Tue, 22 Jul 2025 12:59:46 +0800 Subject: [PATCH 1/7] feat(Bouncer): add portal validation to block portal exploit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: LaoSparrow Co-authored-by: RuyouSunshine <221790696+RuyouSunshine@users.noreply.github.com> Co-authored-by: 肝帝熙恩 <111548550+thexn@users.noreply.github.com> --- TShockAPI/Bouncer.cs | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 488abe61..1f8dac7e 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -1308,6 +1308,56 @@ namespace TShockAPI return; } + // If the created projectile is a Portal Gun Bolt but the player isn't holding a Portal Gun, + // reject the projectile as this indicates potential cheating. + if (type == ProjectileID.PortalGunBolt) + { + if (args.Player.SelectedItem.type != ItemID.PortalGun) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from portal gun bolt from {0} (not holding portal gun)", args.Player.Name)); + args.Player.RemoveProjectile(ident, owner); + args.Handled = true; + } + } + + // Portal Gun Gate projectiles must meet several validation criteria: + // 1. The angle must be within valid discrete directions (45 degree increments) + // 2. Must have an active PortalGunBolt projectile associated + // 3. The bolt position must match the expected position + if (type == ProjectileID.PortalGunGate) + { + // Validate the gate angle is one of 8 possible cardinal directions (every 45 degrees) + var wrappedAngle = MathHelper.WrapAngle(ai[0]); + var discreteDirection = (int)Math.Round(wrappedAngle / (MathF.PI / 4f)); + if (discreteDirection is < -3 or > 4) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from portal gate from {0} (invalid angle: {1})", args.Player.Name, discreteDirection)); + args.Player.RemoveProjectile(ident, owner); + args.Handled = true; + return; + } + + // Validate we found an active bolt projectile + var boltProjectileData = args.Player.RecentlyCreatedProjectiles.FirstOrDefault(p => Main.projectile[p.Index].type == ProjectileID.PortalGunBolt); + if (boltProjectileData.Type == 0 || boltProjectileData.Killed) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from portal gate from {0} (invalid angle: {1})", args.Player.Name, discreteDirection)); + args.Player.RemoveProjectile(ident, owner); + args.Handled = true; + return; + } + + // Validate the bolt position matches where the gate is being placed + var boltProjectile = Main.projectile[boltProjectileData.Index]; + if (boltProjectile.position.Equals(args.Position)) + { + TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from portal gate from {0} (position mismatch)", args.Player.Name)); + args.Player.RemoveProjectile(ident, owner); + args.Handled = true; + return; + } + } + if (!TShock.Config.Settings.IgnoreProjUpdate && !args.Player.HasPermission(Permissions.ignoreprojectiledetection)) { if (type == ProjectileID.BlowupSmokeMoonlord From 539d8194b41d01e406a0348326b5542cb490b119 Mon Sep 17 00:00:00 2001 From: ACaiCat <13110818005@qq.com> Date: Tue, 22 Jul 2025 14:26:14 +0800 Subject: [PATCH 2/7] fix(Bouncer): remove portal bolt position validation --- TShockAPI/Bouncer.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 1f8dac7e..c48494e5 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -1323,7 +1323,6 @@ namespace TShockAPI // Portal Gun Gate projectiles must meet several validation criteria: // 1. The angle must be within valid discrete directions (45 degree increments) // 2. Must have an active PortalGunBolt projectile associated - // 3. The bolt position must match the expected position if (type == ProjectileID.PortalGunGate) { // Validate the gate angle is one of 8 possible cardinal directions (every 45 degrees) @@ -1347,15 +1346,7 @@ namespace TShockAPI return; } - // Validate the bolt position matches where the gate is being placed - var boltProjectile = Main.projectile[boltProjectileData.Index]; - if (boltProjectile.position.Equals(args.Position)) - { - TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from portal gate from {0} (position mismatch)", args.Player.Name)); - args.Player.RemoveProjectile(ident, owner); - args.Handled = true; - return; - } + boltProjectileData.Killed = true; } if (!TShock.Config.Settings.IgnoreProjUpdate && !args.Player.HasPermission(Permissions.ignoreprojectiledetection)) From 9de7ce6957ab27a72e422874757671fc9a3f3ae3 Mon Sep 17 00:00:00 2001 From: ACaiCat <13110818005@qq.com> Date: Tue, 22 Jul 2025 19:03:44 +0800 Subject: [PATCH 3/7] fix(Bouncer): remove holding a Portal Gun check * Portal Gun Station also shoot Portal Gun Bolt --- TShockAPI/Bouncer.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index c48494e5..85a1c637 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -1308,18 +1308,6 @@ namespace TShockAPI return; } - // If the created projectile is a Portal Gun Bolt but the player isn't holding a Portal Gun, - // reject the projectile as this indicates potential cheating. - if (type == ProjectileID.PortalGunBolt) - { - if (args.Player.SelectedItem.type != ItemID.PortalGun) - { - TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from portal gun bolt from {0} (not holding portal gun)", args.Player.Name)); - args.Player.RemoveProjectile(ident, owner); - args.Handled = true; - } - } - // Portal Gun Gate projectiles must meet several validation criteria: // 1. The angle must be within valid discrete directions (45 degree increments) // 2. Must have an active PortalGunBolt projectile associated From 2ac52cb1460be3fd327a3040c70919b978786183 Mon Sep 17 00:00:00 2001 From: ACaiCat <13110818005@qq.com> Date: Tue, 22 Jul 2025 19:07:25 +0800 Subject: [PATCH 4/7] fix(Bouncer): correct debug message of portal gun bolt validation --- TShockAPI/Bouncer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 85a1c637..bf37110d 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -1328,7 +1328,7 @@ namespace TShockAPI var boltProjectileData = args.Player.RecentlyCreatedProjectiles.FirstOrDefault(p => Main.projectile[p.Index].type == ProjectileID.PortalGunBolt); if (boltProjectileData.Type == 0 || boltProjectileData.Killed) { - TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from portal gate from {0} (invalid angle: {1})", args.Player.Name, discreteDirection)); + TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from portal gate from {0} (missing active Portal Gun bolt)", args.Player.Name, discreteDirection)); args.Player.RemoveProjectile(ident, owner); args.Handled = true; return; From 7c41775d6db3e073b48f9c52886a42c423780357 Mon Sep 17 00:00:00 2001 From: Cai <13110818005@qq.com> Date: Wed, 17 Sep 2025 21:36:30 +0800 Subject: [PATCH 5/7] chore: silent kick on invalid client connection --- TShockAPI/TShock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 99153deb..d3b01b6d 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -1456,7 +1456,7 @@ namespace TShockAPI if (!tsplr.FinishedHandshake) { - tsplr.Kick(GetString("Your client didn't send the right connection information."), true); + tsplr.Kick(GetString("Your client didn't send the right connection information."), true, true); args.Handled = true; return; } From 0cc0f7733a288d2b83217e8ac741945115d9a87e Mon Sep 17 00:00:00 2001 From: Cai <13110818005@qq.com> Date: Fri, 26 Sep 2025 23:28:00 +0800 Subject: [PATCH 6/7] fix: allow evil grass to grow during world generation even when `Allow...Creep` is false --- TShockAPI/TShock.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 99153deb..538e5ba4 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -1263,6 +1263,11 @@ namespace TShockAPI /// True if allowed, otherwise false private bool OnCreep(int tileType) { + if (WorldGen.generatingWorld) + { + return true; + } + if (!Config.Settings.AllowCrimsonCreep && (tileType == TileID.Dirt || tileType == TileID.CrimsonGrass || TileID.Sets.Crimson[tileType])) { From e6f3013a764ae19faa28cd3581aebc509e2c1bb1 Mon Sep 17 00:00:00 2001 From: Cai <13110818005@qq.com> Date: Sat, 17 Jan 2026 20:41:09 +0800 Subject: [PATCH 7/7] fix: ignore `respawnTimer` when players are spawning into the world * Players who die in single-player mode without respawning should not be marked as Dead on the SSC server. * In my tests, only the SSC server receives a non-zero `respawnTimer`. Therefore, this may be a client-side bug. closed: #3151 --- TShockAPI/GetDataHandlers.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 6ed0f8de..a21fe55f 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -2734,8 +2734,11 @@ namespace TShockAPI if (OnPlayerSpawn(args.Player, args.Data, player, spawnX, spawnY, respawnTimer, numberOfDeathsPVE, numberOfDeathsPVP, context)) return true; - - args.Player.Dead = respawnTimer > 0; + + if (!Main.ServerSideCharacter || context != PlayerSpawnContext.SpawningIntoWorld) + { + args.Player.Dead = respawnTimer > 0; + } if (Main.ServerSideCharacter) {