From ca38d64632b3609f19c0a7fe6912ead8a206aee6 Mon Sep 17 00:00:00 2001 From: Chris <2648373+QuiCM@users.noreply.github.com> Date: Fri, 13 Nov 2020 17:46:00 +1030 Subject: [PATCH] The great refactoring! Squares to Rectangles --- TShockAPI/Bouncer.cs | 6 +- TShockAPI/ConfigFile.cs | 7 + TShockAPI/GetDataHandlers.cs | 65 +++++--- TShockAPI/Handlers/SendTileSquareHandler.cs | 175 ++++++++++---------- 4 files changed, 142 insertions(+), 111 deletions(-) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index b79d59c5..5dd9d3ce 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -36,7 +36,7 @@ namespace TShockAPI /// Bouncer is the TShock anti-hack and anti-cheat system. internal sealed class Bouncer { - internal Handlers.SendTileSquareHandler STSHandler { get; set; } + internal Handlers.SendTileRectHandler STSHandler { get; set; } internal Handlers.NetModules.NetModulePacketHandler NetModuleHandler { get; set; } internal Handlers.EmojiHandler EmojiHandler { get; set; } internal Handlers.DisplayDollItemSyncHandler DisplayDollItemSyncHandler { get; set; } @@ -48,8 +48,8 @@ namespace TShockAPI /// A new Bouncer. internal Bouncer() { - STSHandler = new Handlers.SendTileSquareHandler(); - GetDataHandlers.SendTileSquare += STSHandler.OnReceive; + STSHandler = new Handlers.SendTileRectHandler(); + GetDataHandlers.SendTileRect += STSHandler.OnReceive; NetModuleHandler = new Handlers.NetModules.NetModulePacketHandler(); GetDataHandlers.ReadNetModule += NetModuleHandler.OnReceive; diff --git a/TShockAPI/ConfigFile.cs b/TShockAPI/ConfigFile.cs index b65c5402..1a969669 100644 --- a/TShockAPI/ConfigFile.cs +++ b/TShockAPI/ConfigFile.cs @@ -466,6 +466,13 @@ namespace TShockAPI [Description("Whether or not to kick users when they surpass the HealOther threshold.")] public bool KickOnHealOtherThresholdBroken = false; + /// Disables a player if this number of tiles is present in a Tile Rectangle packet + [Description("Disables a player if this number of tiles is present in a Tile Rectangle packet")] + public int TileRectangleSizeThreshold = 50; + + /// Whether or not to kick users when they surpass the TileRectangleSize threshold. + [Description("Whether or not to kick users when they surpass the TileRectangleSize threshold.")] + public bool KickOnTileRectangleSizeThresholdBroken = false; #endregion diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 7f2c5c41..efef37c6 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -105,7 +105,7 @@ namespace TShockAPI { PacketTypes.PlayerHp, HandlePlayerHp }, { PacketTypes.Tile, HandleTile }, { PacketTypes.DoorUse, HandleDoorUse }, - { PacketTypes.TileSendSquare, HandleSendTileSquare }, + { PacketTypes.TileSendSquare, HandleSendTileRect }, { PacketTypes.ItemDrop, HandleItemDrop }, { PacketTypes.ItemOwner, HandleItemOwner }, { PacketTypes.ProjectileNew, HandleProjectileNew }, @@ -493,44 +493,57 @@ namespace TShockAPI } /// - /// For use in a SendTileSquare event + /// For use in a SendTileRect event /// - public class SendTileSquareEventArgs : GetDataHandledEventArgs + public class SendTileRectEventArgs : GetDataHandledEventArgs { /// - /// Size of the area + /// X position of the rectangle /// - public short Size { get; set; } + public short TileX { get; set; } /// - /// A corner of the section + /// Y position of the rect /// - public int TileX { get; set; } + public short TileY { get; set; } /// - /// A corner of the section + /// Width of the rectangle /// - public int TileY { get; set; } + public byte Width { get; set; } + + /// + /// Length of the rectangle + /// + public byte Length { get; set; } + + /// + /// Change type involved in the rectangle + /// + public TileChangeType ChangeType { get; set; } } + /// /// When the player sends a tile square /// - public static HandlerList SendTileSquare = new HandlerList(); - private static bool OnSendTileSquare(TSPlayer player, MemoryStream data, short size, int tilex, int tiley) + public static HandlerList SendTileRect = new HandlerList(); + private static bool OnSendTileRect(TSPlayer player, MemoryStream data, short tilex, short tiley, byte width, byte length, TileChangeType changeType = TileChangeType.None) { - if (SendTileSquare == null) + if (SendTileRect == null) return false; - var args = new SendTileSquareEventArgs + var args = new SendTileRectEventArgs { Player = player, Data = data, - Size = size, TileX = tilex, TileY = tiley, + Width = width, + Length = length, + ChangeType = changeType }; - SendTileSquare.Invoke(null, args); + SendTileRect.Invoke(null, args); return args.Handled; } @@ -2618,23 +2631,25 @@ namespace TShockAPI return false; } - private static bool HandleSendTileSquare(GetDataHandlerArgs args) + private static bool HandleSendTileRect(GetDataHandlerArgs args) { var player = args.Player; - var size = args.Data.ReadInt16(); - var changeType = TileChangeType.None; - - bool hasChangeType = ((size & 0x7FFF) & 0x8000) != 0; - if (hasChangeType) - { - changeType = (TileChangeType)args.Data.ReadInt8(); - } var tileX = args.Data.ReadInt16(); var tileY = args.Data.ReadInt16(); + var width = (byte)args.Data.ReadByte(); + var length = (byte)args.Data.ReadByte(); + + var changeByte = (byte)args.Data.ReadByte(); + var changeType = TileChangeType.None; + if (Enum.IsDefined(typeof(TileChangeType), changeByte)) + { + changeType = (TileChangeType)changeByte; + } + var data = args.Data; - if (OnSendTileSquare(player, data, size, tileX, tileY)) + if (OnSendTileRect(player, data, tileX, tileY, width, length, changeType)) return true; return false; diff --git a/TShockAPI/Handlers/SendTileSquareHandler.cs b/TShockAPI/Handlers/SendTileSquareHandler.cs index c7449087..83832fac 100644 --- a/TShockAPI/Handlers/SendTileSquareHandler.cs +++ b/TShockAPI/Handlers/SendTileSquareHandler.cs @@ -12,9 +12,9 @@ using TShockAPI.Net; namespace TShockAPI.Handlers { /// - /// Provides processors for handling Tile Square packets + /// Provides processors for handling Tile Rect packets /// - public class SendTileSquareHandler : IPacketHandler + public class SendTileRectHandler : IPacketHandler { /// /// Maps grass-type blocks to flowers that can be grown on them with flower boots @@ -53,11 +53,11 @@ namespace TShockAPI.Handlers }; /// - /// Invoked when a SendTileSquare packet is received + /// Invoked when a SendTileRect packet is received /// /// /// - public void OnReceive(object sender, GetDataHandlers.SendTileSquareEventArgs args) + public void OnReceive(object sender, GetDataHandlers.SendTileRectEventArgs args) { // By default, we'll handle everything args.Handled = true; @@ -67,41 +67,42 @@ namespace TShockAPI.Handlers return; } - bool[,] processed = new bool[args.Size, args.Size]; - NetTile[,] tiles = ReadNetTilesFromStream(args.Data, args.Size); + bool[,] processed = new bool[args.Width, args.Length]; + NetTile[,] tiles = ReadNetTilesFromStream(args.Data, args.Width, args.Length); - Debug.VisualiseTileSetDiff(args.TileX, args.TileY, args.Size, args.Size, tiles); + Debug.VisualiseTileSetDiff(args.TileX, args.TileY, args.Width, args.Length, tiles); - IterateTileSquare(tiles, processed, args); + IterateTileRect(tiles, processed, args); - // Uncommenting this function will send the same tile square 10 blocks above you for visualisation. This will modify your world and overwrite existing blocks. + // Uncommenting this function will send the same tile rect 10 blocks above you for visualisation. This will modify your world and overwrite existing blocks. // Use in test worlds only. - //Debug.DisplayTileSetInGame(tileX, tileY - 10, size, size, tiles, args.Player); + Debug.DisplayTileSetInGame(args.TileX, (short)(args.TileY - 10), args.Width, args.Length, tiles, args.Player); // If we are handling this event then we have updated the server's Main.tile state the way we want it. // At this point we should send our state back to the client so they remain in sync with the server if (args.Handled == true) { - args.Player.SendTileSquare(args.TileX, args.TileY, args.Size); - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare reimplemented from carbonara from {0}", args.Player.Name); + args.Player.SendTileRect(args.TileX, args.TileY, args.Width, args.Length); + TShock.Log.ConsoleDebug("Bouncer / SendTileRect reimplemented from carbonara from {0}", args.Player.Name); } } /// - /// Iterates over each tile in the tile square and performs processing on individual tiles or multi-tile Tile Objects + /// Iterates over each tile in the tile rectangle and performs processing on individual tiles or multi-tile Tile Objects /// /// /// /// - internal void IterateTileSquare(NetTile[,] tiles, bool[,] processed, GetDataHandlers.SendTileSquareEventArgs args) + internal void IterateTileRect(NetTile[,] tiles, bool[,] processed, GetDataHandlers.SendTileRectEventArgs args) { - short size = args.Size; int tileX = args.TileX; int tileY = args.TileY; + byte width = args.Width; + byte length = args.Length; - for (int x = 0; x < size; x++) + for (int x = 0; x < width; x++) { - for (int y = 0; y < size; y++) + for (int y = 0; y < length; y++) { // Do not process already processed tiles if (processed[x, y]) @@ -137,8 +138,8 @@ namespace TShockAPI.Handlers { data = TileObjectData._data[newTile.Type]; NetTile[,] newTiles; - int width = data.Width; - int height = data.Height; + int objWidth = data.Width; + int objHeight = data.Height; int offsetY = 0; if (newTile.Type == TileID.TrapdoorClosed) @@ -146,40 +147,40 @@ namespace TShockAPI.Handlers // Trapdoors can modify a 2x3 space. When it closes it will have leftover tiles either on top or bottom. // If we don't update these tiles, the trapdoor gets confused and disappears. // So we capture all 6 possible tiles and offset ourselves 1 tile above the closed trapdoor to capture the entire 2x3 area - width = 2; - height = 3; + objWidth = 2; + objHeight = 3; offsetY = -1; } - // Ensure the tile object fits inside the square before processing it - if (!DoesTileObjectFitInTileSquare(x, y, width, height, size, offsetY, processed)) + // Ensure the tile object fits inside the rect before processing it + if (!DoesTileObjectFitInTileRect(x, y, objWidth, objHeight, width, length, offsetY, processed)) { continue; } - newTiles = new NetTile[width, height]; + newTiles = new NetTile[objWidth, objHeight]; - for (int i = 0; i < width; i++) + for (int i = 0; i < objWidth; i++) { - for (int j = 0; j < height; j++) + for (int j = 0; j < objHeight; j++) { newTiles[i, j] = tiles[x + i, y + j + offsetY]; processed[x + i, y + j + offsetY] = true; } } - ProcessTileObject(newTile.Type, realX, realY + offsetY, width, height, newTiles, args); + ProcessTileObject(newTile.Type, realX, realY + offsetY, objWidth, objHeight, newTiles, args); continue; } // If the new tile does not have an associated tile object, process it as an individual tile - ProcessSingleTile(realX, realY, newTile, size, args); + ProcessSingleTile(realX, realY, newTile, width, length, args); processed[x, y] = true; } } } /// - /// Processes a tile object consisting of multiple tiles from the tile square packet + /// Processes a tile object consisting of multiple tiles from the tile rect packet /// /// The tile type the object is comprised of /// 2D array of NetTile containing the new tiles properties @@ -187,20 +188,20 @@ namespace TShockAPI.Handlers /// Y position at the top left of the object /// Width of the tile object /// Height of the tile object - /// SendTileSquareEventArgs containing event information - internal void ProcessTileObject(int tileType, int realX, int realY, int width, int height, NetTile[,] newTiles, GetDataHandlers.SendTileSquareEventArgs args) + /// SendTileRectEventArgs containing event information + internal void ProcessTileObject(int tileType, int realX, int realY, int width, int height, NetTile[,] newTiles, GetDataHandlers.SendTileRectEventArgs args) { // As long as the player has permission to build, we should allow a tile object to be placed // More in depth checks should take place in handlers for the Place Object (79), Update Tile Entity (86), and Place Tile Entity (87) packets if (!args.Player.HasBuildPermissionForTileObject(realX, realY, width, height)) { - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from no permission for tile object from {0}", args.Player.Name); + TShock.Log.ConsoleDebug("Bouncer / SendTileRect rejected from no permission for tile object from {0}", args.Player.Name); return; } if (TShock.TileBans.TileIsBanned((short)tileType)) { - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected for banned tile"); + TShock.Log.ConsoleDebug("Bouncer / SendTileRect rejected for banned tile"); return; } @@ -215,18 +216,19 @@ namespace TShockAPI.Handlers } /// - /// Processes a single tile from the tile square packet + /// Processes a single tile from the tile rect packet /// /// X position at the top left of the object /// Y position at the top left of the object /// The NetTile containing new tile properties - /// The size of the tile square being received - /// SendTileSquareEventArgs containing event information - internal void ProcessSingleTile(int realX, int realY, NetTile newTile, int squareSize, GetDataHandlers.SendTileSquareEventArgs args) + /// The width of the rectangle being processed + /// The length of the rectangle being processed + /// SendTileRectEventArgs containing event information + internal void ProcessSingleTile(int realX, int realY, NetTile newTile, byte rectWidth, byte rectLength, GetDataHandlers.SendTileRectEventArgs args) { - // Some boots allow growing flowers on grass. This process sends a 1x1 tile square to grow the flowers - // The square size must be 1 and the player must have an accessory that allows growing flowers in order for this square to be valid - if (squareSize == 1 && args.Player.Accessories.Any(a => a != null && FlowerBootItems.Contains(a.type))) + // Some boots allow growing flowers on grass. This process sends a 1x1 tile rect to grow the flowers + // The rect size must be 1 and the player must have an accessory that allows growing flowers in order for this rect to be valid + if (rectWidth == 1 && rectLength == 1 && args.Player.Accessories.Any(a => a != null && FlowerBootItems.Contains(a.type))) { ProcessFlowerBoots(realX, realY, newTile, args); return; @@ -250,25 +252,25 @@ namespace TShockAPI.Handlers } /// - /// Applies changes to a tile if a tile square for flower-growing boots is valid + /// Applies changes to a tile if a tile rect for flower-growing boots is valid /// - /// The tile x position of the tile square packet - this is where the flowers are intending to grow - /// The tile y position of the tile square packet - this is where the flowers are intending to grow + /// The tile x position of the tile rect packet - this is where the flowers are intending to grow + /// The tile y position of the tile rect packet - this is where the flowers are intending to grow /// The NetTile containing information about the flowers that are being grown - /// SendTileSquareEventArgs containing event information - internal void ProcessFlowerBoots(int realX, int realY, NetTile newTile, GetDataHandlers.SendTileSquareEventArgs args) + /// SendTileRectEventArgs containing event information + internal void ProcessFlowerBoots(int realX, int realY, NetTile newTile, GetDataHandlers.SendTileRectEventArgs args) { - // We need to get the tile below the tile square to determine what grass types are allowed + // We need to get the tile below the tile rect to determine what grass types are allowed if (!WorldGen.InWorld(realX, realY + 1)) { - // If the tile below the tile square isn't valid, we return here and don't update the server tile state + // If the tile below the tile rect isn't valid, we return here and don't update the server tile state return; } ITile tile = Main.tile[realX, realY + 1]; if (!GrassToPlantMap.TryGetValue(tile.type, out List plantTiles) && !plantTiles.Contains(newTile.Type)) { - // If the tile below the tile square isn't a valid plant tile (eg grass) then we don't update the server tile state + // If the tile below the tile rect isn't a valid plant tile (eg grass) then we don't update the server tile state return; } @@ -305,13 +307,13 @@ namespace TShockAPI.Handlers WallID.Sets.Conversion.NewWall4[tile.wall] && WallID.Sets.Conversion.NewWall4[newTile.Wall] ) { - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare processing a conversion update - [{0}|{1}] -> [{2}|{3}]", tile.type, tile.wall, newTile.Type, newTile.Wall); + TShock.Log.ConsoleDebug("Bouncer / SendTileRect processing a conversion update - [{0}|{1}] -> [{2}|{3}]", tile.type, tile.wall, newTile.Type, newTile.Wall); UpdateServerTileState(tile, newTile); } } /// - /// Updates a single tile's world state with a change from the tile square packet + /// Updates a single tile's world state with a change from the tile rect packet /// /// The tile to update /// The NetTile containing the change @@ -370,7 +372,7 @@ namespace TShockAPI.Handlers tile.slope(slope); - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare updated a tile from type {0} to {1}", tile.type, newTile.Type); + TShock.Log.ConsoleDebug("Bouncer / SendTileRect updated a tile from type {0} to {1}", tile.type, newTile.Type); } /// @@ -396,14 +398,15 @@ namespace TShockAPI.Handlers /// Reads a set of NetTiles from a memory stream /// /// - /// + /// + /// /// - static NetTile[,] ReadNetTilesFromStream(System.IO.MemoryStream stream, short size) + static NetTile[,] ReadNetTilesFromStream(System.IO.MemoryStream stream, byte width, byte length) { - NetTile[,] tiles = new NetTile[size, size]; - for (int x = 0; x < size; x++) + NetTile[,] tiles = new NetTile[width, length]; + for (int x = 0; x < width; x++) { - for (int y = 0; y < size; y++) + for (int y = 0; y < length; y++) { tiles[x, y] = new NetTile(stream); } @@ -413,37 +416,42 @@ namespace TShockAPI.Handlers } /// - /// Determines whether or not the tile square should be immediately accepted or rejected + /// Determines whether or not the tile rect should be immediately accepted or rejected /// /// /// - static bool ShouldSkipProcessing(GetDataHandlers.SendTileSquareEventArgs args) + static bool ShouldSkipProcessing(GetDataHandlers.SendTileRectEventArgs args) { if (args.Player.HasPermission(Permissions.allowclientsideworldedit)) { - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare accepted clientside world edit from {0}", args.Player.Name); + TShock.Log.ConsoleDebug("Bouncer / SendTileRect accepted clientside world edit from {0}", args.Player.Name); args.Handled = false; return true; } - // 7x7 is the largest vanilla-sized tile square (used for lamp posts). Anything larger than this should not be sent by the vanilla game and should be rejected - if (args.Size > 7) + var rectSize = args.Width * args.Length; + if (rectSize > TShock.Config.TileRectangleSizeThreshold) { - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from non-vanilla tilemod from {0}", args.Player.Name); + TShock.Log.ConsoleDebug("Bouncer / SendTileRect rejected from non-vanilla tilemod from {0}", args.Player.Name); + if (TShock.Config.KickOnTileRectangleSizeThresholdBroken) + { + args.Player.Kick("Unexpected tile threshold reached"); + } + return true; } if (args.Player.IsBouncerThrottled()) { - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from throttle from {0}", args.Player.Name); - args.Player.SendTileSquare(args.TileX, args.TileY, args.Size); + TShock.Log.ConsoleDebug("Bouncer / SendTileRect rejected from throttle from {0}", args.Player.Name); + args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width); return true; } if (args.Player.IsBeingDisabled()) { - TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from being disabled from {0}", args.Player.Name); - args.Player.SendTileSquare(args.TileX, args.TileY, args.Size); + TShock.Log.ConsoleDebug("Bouncer / SendTileRect rejected from being disabled from {0}", args.Player.Name); + args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width); return true; } @@ -451,37 +459,38 @@ namespace TShockAPI.Handlers } /// - /// Checks if a tile object fits inside the dimensions of a tile square + /// Checks if a tile object fits inside the dimensions of a tile rectangle /// /// /// /// /// - /// + /// + /// /// /// /// - static bool DoesTileObjectFitInTileSquare(int x, int y, int width, int height, int size, int offsetY, bool[,] processed) + static bool DoesTileObjectFitInTileRect(int x, int y, int width, int height, short rectWidth, short rectLength, int offsetY, bool[,] processed) { - // If the starting y position of this tile object is at (x, 0) and the y offset is negative, we'll be accessing tiles outside the square + // If the starting y position of this tile object is at (x, 0) and the y offset is negative, we'll be accessing tiles outside the rect if (y + offsetY < 0) { - TShock.Log.ConsoleDebug("Bouncer / SendTileSquareHandler - rejected tile object because object dimensions fall outside the tile square (negative y value)"); + TShock.Log.ConsoleDebug("Bouncer / SendTileRectHandler - rejected tile object because object dimensions fall outside the tile rect (negative y value)"); return false; } - if (x + width > size || y + height + offsetY > size) + if (x + width > rectWidth || y + height + offsetY > rectLength) { // This is ugly, but we want to mark all these tiles as processed so that we're not hitting this check multiple times for one dodgy tile object - for (int i = x; i < size; i++) + for (int i = x; i < rectWidth; i++) { - for (int j = Math.Max(0, y + offsetY); j < size; j++) // This is also ugly. Using Math.Max to make sure y + offsetY >= 0 + for (int j = Math.Max(0, y + offsetY); j < rectLength; j++) // This is also ugly. Using Math.Max to make sure y + offsetY >= 0 { processed[i, j] = true; } } - TShock.Log.ConsoleDebug("Bouncer / SendTileSquareHandler - rejected tile object because object dimensions fall outside the tile square (excessive size)"); + TShock.Log.ConsoleDebug("Bouncer / SendTileRectHandler - rejected tile object because object dimensions fall outside the tile rect (excessive size)"); return false; } @@ -493,8 +502,8 @@ namespace TShockAPI.Handlers /// /// Displays the difference in IDs between existing tiles and a set of NetTiles to the console /// - /// X position at the top left of the square - /// Y position at the top left of the square + /// X position at the top left of the rect + /// Y position at the top left of the rect /// Width of the NetTile set /// Height of the NetTile set /// New tiles to be visualised @@ -527,15 +536,15 @@ namespace TShockAPI.Handlers } /// - /// Sends a tile square at the given (tileX, tileY) coordinate, using the given set of NetTiles information to update the tile square + /// Sends a tile rect at the given (tileX, tileY) coordinate, using the given set of NetTiles information to update the tile rect /// - /// X position at the top left of the square - /// Y position at the top left of the square + /// X position at the top left of the rect + /// Y position at the top left of the rect /// Width of the NetTile set /// Height of the NetTile set - /// New tiles to place in the square + /// New tiles to place in the rect /// Player to send the debug display to - public static void DisplayTileSetInGame(int tileX, int tileY, int width, int height, NetTile[,] newTiles, TSPlayer player) + public static void DisplayTileSetInGame(short tileX, short tileY, byte width, byte height, NetTile[,] newTiles, TSPlayer player) { for (int x = 0; x < width; x++) { @@ -547,7 +556,7 @@ namespace TShockAPI.Handlers UpdateServerTileState(Main.tile[tileX + x, tileY + height], new NetTile { Active = true, Type = 0 }); } - player.SendTileSquare(tileX, tileY, Math.Max(width, height) + 1); + player.SendTileRect(tileX, tileY, width, height); } } }