From 20620f2b473e1e959e6d9f8b9033607c1a478519 Mon Sep 17 00:00:00 2001 From: Patrikkk Date: Mon, 1 Jun 2020 11:54:59 +0200 Subject: [PATCH 1/6] PacketType handler delegate organization. We have the elements in GetDataHandlerDelegates dict in order by its packet type. --- TShockAPI/GetDataHandlers.cs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index b5c9bbb0..a5adc6d4 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -151,9 +151,9 @@ namespace TShockAPI { PacketTypes.CrystalInvasionStart, HandleOldOnesArmy }, { PacketTypes.PlayerHurtV2, HandlePlayerDamageV2 }, { PacketTypes.PlayerDeathV2, HandlePlayerKillMeV2 }, + { PacketTypes.SyncRevengeMarker, HandleSyncRevengeMarker }, { PacketTypes.FishOutNPC, HandleFishOutNPC }, - { PacketTypes.FoodPlatterTryPlacing, HandleFoodPlatterTryPlacing }, - { PacketTypes.SyncRevengeMarker, HandleSyncRevengeMarker } + { PacketTypes.FoodPlatterTryPlacing, HandleFoodPlatterTryPlacing } }; } @@ -3671,6 +3671,21 @@ namespace TShockAPI return false; } + private static bool HandleSyncRevengeMarker(GetDataHandlerArgs args) + { + int uniqueID = args.Data.ReadInt32(); + Vector2 location = args.Data.ReadVector2(); + int netId = args.Data.ReadInt32(); + float npcHpPercent = args.Data.ReadSingle(); + int npcTypeAgainstDiscouragement = args.Data.ReadInt32(); //tfw the argument is Type Against Discouragement + int npcAiStyleAgainstDiscouragement = args.Data.ReadInt32(); //see ^ + int coinsValue = args.Data.ReadInt32(); + float baseValue = args.Data.ReadSingle(); + bool spawnedFromStatus = args.Data.ReadBoolean(); + + return false; + } + private static bool HandleFishOutNPC(GetDataHandlerArgs args) { ushort tileX = args.Data.ReadUInt16(); @@ -3697,21 +3712,6 @@ namespace TShockAPI return false; } - private static bool HandleSyncRevengeMarker(GetDataHandlerArgs args) - { - int uniqueID = args.Data.ReadInt32(); - Vector2 location = args.Data.ReadVector2(); - int netId = args.Data.ReadInt32(); - float npcHpPercent = args.Data.ReadSingle(); - int npcTypeAgainstDiscouragement = args.Data.ReadInt32(); //tfw the argument is Type Against Discouragement - int npcAiStyleAgainstDiscouragement = args.Data.ReadInt32(); //see ^ - int coinsValue = args.Data.ReadInt32(); - float baseValue = args.Data.ReadSingle(); - bool spawnedFromStatus = args.Data.ReadBoolean(); - - return false; - } - public enum EditAction { KillTile = 0, From 26773f61a267f4b4f0f2d27cbcd94a7d4c13065b Mon Sep 17 00:00:00 2001 From: Patrikkk Date: Mon, 1 Jun 2020 12:07:57 +0200 Subject: [PATCH 2/6] Add LandGolfBallInCup event handler. --- TShockAPI/GetDataHandlers.cs | 65 ++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index a5adc6d4..fcb05489 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -152,6 +152,7 @@ namespace TShockAPI { PacketTypes.PlayerHurtV2, HandlePlayerDamageV2 }, { PacketTypes.PlayerDeathV2, HandlePlayerKillMeV2 }, { PacketTypes.SyncRevengeMarker, HandleSyncRevengeMarker }, + { PacketTypes.LandGolfBallInCup, HandleLandGolfBallInCup }, { PacketTypes.FishOutNPC, HandleFishOutNPC }, { PacketTypes.FoodPlatterTryPlacing, HandleFoodPlatterTryPlacing } }; @@ -1866,6 +1867,56 @@ namespace TShockAPI return args.Handled; } + /// + /// For use in a LandGolfBallInCup event. + /// + public class LandGolfBallInCupEventArgs : GetDataHandledEventArgs + { + /// + /// The player index in the packet, who puts the ball in the cup. + /// + public byte PlayerIndex { get; set; } + /// + /// The X tile position of where the ball lands in a cup. + /// + public ushort TileX { get; set; } + /// + /// The Y tile position of where the ball lands in a cup. + /// + public ushort TileY { get; set; } + /// + /// The amount of hits it took for the player to land the ball in the cup. + /// + public ushort Hits { get; set; } + /// + /// The type of the projectile that was landed in the cup. A golfball in legit cases. + /// + public ushort ProjectileType { get; set; } + } + + /// + /// Called when a player lands a golf ball in a cup. + /// + public static HandlerList LandGolfBallInCup = new HandlerList(); + private static bool OnLandGolfBallInCup(TSPlayer player, MemoryStream data, byte playerIndex, ushort tileX, ushort tileY, ushort hits, ushort projectileType ) + { + if (LandGolfBallInCup == null) + return false; + + var args = new LandGolfBallInCupEventArgs + { + Player = player, + Data = data, + PlayerIndex = playerIndex, + TileX = tileX, + TileY = tileY, + Hits = hits, + ProjectileType = projectileType + }; + LandGolfBallInCup.Invoke(null, args); + return args.Handled; + } + /// /// For use in a FishOutNPC event. /// @@ -3686,6 +3737,20 @@ namespace TShockAPI return false; } + private static bool HandleLandGolfBallInCup(GetDataHandlerArgs args) + { + byte playerIndex = args.Data.ReadInt8(); + ushort tileX = args.Data.ReadUInt16(); + ushort tileY = args.Data.ReadUInt16(); + ushort hits = args.Data.ReadUInt16(); + ushort projectileType = args.Data.ReadUInt16(); + + if (OnLandGolfBallInCup(args.Player, args.Data, playerIndex, tileX, tileY, hits, projectileType)) + return true; + + return false; + } + private static bool HandleFishOutNPC(GetDataHandlerArgs args) { ushort tileX = args.Data.ReadUInt16(); From 4944ee3144f14f92dc1b25527a62ab259c71c2bb Mon Sep 17 00:00:00 2001 From: Patrikkk Date: Mon, 1 Jun 2020 12:59:40 +0200 Subject: [PATCH 3/6] Implement Handler.LandGolfBalInCupHandler and handle packet exploits. Added multiple checks to prevent clients from sending the golfball packet directly, without having golf play actions. --- TShockAPI/Bouncer.cs | 4 + .../Handlers/LandGolfBallInCupHandler.cs | 115 ++++++++++++++++++ TShockAPI/TShockAPI.csproj | 1 + 3 files changed, 120 insertions(+) create mode 100644 TShockAPI/Handlers/LandGolfBallInCupHandler.cs diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 395bfddd..7cf7b87f 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -37,6 +37,7 @@ namespace TShockAPI internal sealed class Bouncer { internal Handlers.SendTileSquareHandler STSHandler { get; set; } + internal Handlers.LandGolfBallInCupHandler LandGolfBallInCupHandler { get; set; } /// Constructor call initializes Bouncer and related functionality. /// A new Bouncer. @@ -45,6 +46,9 @@ namespace TShockAPI STSHandler = new Handlers.SendTileSquareHandler(); GetDataHandlers.SendTileSquare += STSHandler.OnReceiveSendTileSquare; + LandGolfBallInCupHandler = new Handlers.LandGolfBallInCupHandler(); + GetDataHandlers.LandGolfBallInCup += LandGolfBallInCupHandler.OnLandGolfBallInCup; + // Setup hooks GetDataHandlers.GetSection += OnGetSection; GetDataHandlers.PlayerUpdate += OnPlayerUpdate; diff --git a/TShockAPI/Handlers/LandGolfBallInCupHandler.cs b/TShockAPI/Handlers/LandGolfBallInCupHandler.cs new file mode 100644 index 00000000..4ab7cbc9 --- /dev/null +++ b/TShockAPI/Handlers/LandGolfBallInCupHandler.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Terraria; +using Terraria.ID; + +namespace TShockAPI.Handlers +{ + /// + /// Handles client side exploits of LandGolfBallInCup packet. + /// + public class LandGolfBallInCupHandler + { + /// + /// List of golf ball projectile IDs. + /// + public static readonly List GolfBallProjectileIDs = new List() + { + ProjectileID.DirtGolfBall, + ProjectileID.GolfBallDyedBlack, + ProjectileID.GolfBallDyedBlue, + ProjectileID.GolfBallDyedBrown, + ProjectileID.GolfBallDyedCyan, + ProjectileID.GolfBallDyedGreen, + ProjectileID.GolfBallDyedLimeGreen, + ProjectileID.GolfBallDyedOrange, + ProjectileID.GolfBallDyedPink, + ProjectileID.GolfBallDyedPurple, + ProjectileID.GolfBallDyedRed, + ProjectileID.GolfBallDyedSkyBlue, + ProjectileID.GolfBallDyedTeal, + ProjectileID.GolfBallDyedViolet, + ProjectileID.GolfBallDyedYellow + }; + + /// + /// List of golf club item IDs + /// + public static readonly List GolfClubItemIDs = new List() + { + ItemID.GolfClubChlorophyteDriver, + ItemID.GolfClubDiamondWedge, + ItemID.GolfClubShroomitePutter, + ItemID.Fake_BambooChest, + ItemID.GolfClubTitaniumIron, + ItemID.GolfClubGoldWedge, + ItemID.GolfClubLeadPutter, + ItemID.GolfClubMythrilIron, + ItemID.GolfClubWoodDriver, + ItemID.GolfClubBronzeWedge, + ItemID.GolfClubRustyPutter, + ItemID.GolfClubStoneIron, + ItemID.GolfClubPearlwoodDriver, + ItemID.GolfClubIron, + ItemID.GolfClubDriver, + ItemID.GolfClubWedge, + ItemID.GolfClubPutter + }; + + /// + /// Invoked when a player lands a golf ball in a cup. + /// + /// + /// + public void OnLandGolfBallInCup(object sender, GetDataHandlers.LandGolfBallInCupEventArgs args) + { + if (args.PlayerIndex != args.Player.Index) + { + TShock.Log.ConsoleError($"LandGolfBallInCupHandler: Packet is spoofing to be player ID {args.PlayerIndex}! - From [{args.Player.Index}]{args.Player.Name}"); + args.Handled = true; + return; + } + + if (args.TileX > Main.maxTilesX || args.TileX < 0 + || args.TileY > Main.maxTilesY || args.TileY < 0) + { + TShock.Log.ConsoleError($"LandGolfBallInCupHandler: X and Y position is out of world bounds! - From {args.Player.Name}"); + args.Handled = true; + return; + } + + if (!Main.tile[args.TileX, args.TileY].active() && Main.tile[args.TileX, args.TileY].type != TileID.GolfHole) + { + TShock.Log.ConsoleError($"LandGolfBallInCupHandler: Tile at packet position X:{args.TileX} Y:{args.TileY} is not a golf hole! - From {args.Player.Name}"); + args.Handled = true; + return; + } + + if (!GolfBallProjectileIDs.Contains(args.ProjectileType)) + { + TShock.Log.ConsoleError($"LandGolfBallInCupHandler: Invalid golf ball projectile ID {args.ProjectileType}! - From {args.Player.Name}"); + args.Handled = true; + return; + } + + var usedGolfBall = args.Player.RecentlyCreatedProjectiles.Any(e => GolfBallProjectileIDs.Contains(e.Type)); + var usedGolfClub = args.Player.RecentlyCreatedProjectiles.Any(e => e.Type == ProjectileID.GolfClubHelper); + if (!usedGolfClub && !usedGolfBall) + { + TShock.Log.ConsoleError($"GolfPacketHandler: Player did not have create a golf club projectile the last 5 seconds! - From {args.Player.Name}"); + args.Handled = true; + return; + } + + if (!GolfClubItemIDs.Contains(args.Player.SelectedItem.type)) + { + TShock.Log.ConsoleError($"LandGolfBallInCupHandler: Item selected is not a golf club! - From {args.Player.Name}"); + args.Handled = true; + return; + } + } + } +} diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index ea6f4f48..fedc3f0b 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -88,6 +88,7 @@ + From 22abd3a2eb94201430dbe1111077db4986315927 Mon Sep 17 00:00:00 2001 From: Patrikkk Date: Mon, 1 Jun 2020 13:01:15 +0200 Subject: [PATCH 4/6] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ffffaa..92662b0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin * Fixed placement issues with Item Frames, Teleportation Pylons, etc. (@QuiCM) * Doors are good now for real probably (@QuiCM, @Hakusaro, @Olink) * Bump default max damage received cap to 42,000 to accommodate the Empress of Light's instant kill death amount. (@hakusaro, @moisterrific, @Irethia, @Ayrawei) +* Added LandGolfBallInCup event which is accessible for developers to work with, as well as LandGolfBallInCup handler to handle exploits where players could send direct packets to trigger and imitate golf ball cup landing anywhere in the game world. Added two public lists in Handlers.LandGolfBallInCupHandler: GolfBallProjectileIDs and GolfClubItemIDs. (@Patrikkk) ## TShock 4.4.0 (Pre-release 9) * Fixed pet licenses. (@Olink) From f674e7830886fcf5b47809b88dbb7aedf1dd3dec Mon Sep 17 00:00:00 2001 From: Patrikkk Date: Tue, 2 Jun 2020 11:13:02 +0200 Subject: [PATCH 5/6] LandGolfBallInCup - Use ConsoleDebug. Modify display message. --- TShockAPI/Handlers/LandGolfBallInCupHandler.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/TShockAPI/Handlers/LandGolfBallInCupHandler.cs b/TShockAPI/Handlers/LandGolfBallInCupHandler.cs index 4ab7cbc9..7d246976 100644 --- a/TShockAPI/Handlers/LandGolfBallInCupHandler.cs +++ b/TShockAPI/Handlers/LandGolfBallInCupHandler.cs @@ -68,7 +68,7 @@ namespace TShockAPI.Handlers { if (args.PlayerIndex != args.Player.Index) { - TShock.Log.ConsoleError($"LandGolfBallInCupHandler: Packet is spoofing to be player ID {args.PlayerIndex}! - From [{args.Player.Index}]{args.Player.Name}"); + TShock.Log.ConsoleDebug($"LandGolfBallInCupHandler: Packet rejected for ID spoofing. Expected {args.PlayerIndex} , received {args.PlayerIndex} from {args.Player.Name}."); args.Handled = true; return; } @@ -76,21 +76,21 @@ namespace TShockAPI.Handlers if (args.TileX > Main.maxTilesX || args.TileX < 0 || args.TileY > Main.maxTilesY || args.TileY < 0) { - TShock.Log.ConsoleError($"LandGolfBallInCupHandler: X and Y position is out of world bounds! - From {args.Player.Name}"); + TShock.Log.ConsoleDebug($"LandGolfBallInCupHandler: X and Y position is out of world bounds! - From {args.Player.Name}"); args.Handled = true; return; } if (!Main.tile[args.TileX, args.TileY].active() && Main.tile[args.TileX, args.TileY].type != TileID.GolfHole) { - TShock.Log.ConsoleError($"LandGolfBallInCupHandler: Tile at packet position X:{args.TileX} Y:{args.TileY} is not a golf hole! - From {args.Player.Name}"); + TShock.Log.ConsoleDebug($"LandGolfBallInCupHandler: Tile at packet position X:{args.TileX} Y:{args.TileY} is not a golf hole! - From {args.Player.Name}"); args.Handled = true; return; } if (!GolfBallProjectileIDs.Contains(args.ProjectileType)) { - TShock.Log.ConsoleError($"LandGolfBallInCupHandler: Invalid golf ball projectile ID {args.ProjectileType}! - From {args.Player.Name}"); + TShock.Log.ConsoleDebug($"LandGolfBallInCupHandler: Invalid golf ball projectile ID {args.ProjectileType}! - From {args.Player.Name}"); args.Handled = true; return; } @@ -99,14 +99,14 @@ namespace TShockAPI.Handlers var usedGolfClub = args.Player.RecentlyCreatedProjectiles.Any(e => e.Type == ProjectileID.GolfClubHelper); if (!usedGolfClub && !usedGolfBall) { - TShock.Log.ConsoleError($"GolfPacketHandler: Player did not have create a golf club projectile the last 5 seconds! - From {args.Player.Name}"); + TShock.Log.ConsoleDebug($"GolfPacketHandler: Player did not have create a golf club projectile the last 5 seconds! - From {args.Player.Name}"); args.Handled = true; return; } if (!GolfClubItemIDs.Contains(args.Player.SelectedItem.type)) { - TShock.Log.ConsoleError($"LandGolfBallInCupHandler: Item selected is not a golf club! - From {args.Player.Name}"); + TShock.Log.ConsoleDebug($"LandGolfBallInCupHandler: Item selected is not a golf club! - From {args.Player.Name}"); args.Handled = true; return; } From 0a1f608a3ae6020c435b15eb2dfcafba0b655757 Mon Sep 17 00:00:00 2001 From: Patrikkk Date: Tue, 2 Jun 2020 11:59:24 +0200 Subject: [PATCH 6/6] Fix conflict resolve merge. Make project buildable. Had a duplicate HandleSyncRevengeMarker method. --- TShockAPI/Bouncer.cs | 2 +- TShockAPI/GetDataHandlers.cs | 15 --------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index bfe270e0..f6de4c80 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -52,7 +52,7 @@ namespace TShockAPI GetDataHandlers.ReadNetModule += NetModuleHandler.OnReceive; EmojiHandler = new Handlers.EmojiHandler(); - GetDataHandlers.Emoji += EmojiHandler.OnReceiveEmoji; + GetDataHandlers.Emoji += EmojiHandler.OnReceive; LandGolfBallInCupHandler = new Handlers.LandGolfBallInCupHandler(); GetDataHandlers.LandGolfBallInCup += LandGolfBallInCupHandler.OnLandGolfBallInCup; diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 6a420ecd..cf13e396 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -3669,21 +3669,6 @@ namespace TShockAPI return false; } - private static bool HandleSyncRevengeMarker(GetDataHandlerArgs args) - { - int uniqueID = args.Data.ReadInt32(); - Vector2 location = args.Data.ReadVector2(); - int netId = args.Data.ReadInt32(); - float npcHpPercent = args.Data.ReadSingle(); - int npcTypeAgainstDiscouragement = args.Data.ReadInt32(); //tfw the argument is Type Against Discouragement - int npcAiStyleAgainstDiscouragement = args.Data.ReadInt32(); //see ^ - int coinsValue = args.Data.ReadInt32(); - float baseValue = args.Data.ReadSingle(); - bool spawnedFromStatus = args.Data.ReadBoolean(); - - return false; - } - private static bool HandleLandGolfBallInCup(GetDataHandlerArgs args) { byte playerIndex = args.Data.ReadInt8();