diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 2e3a3910..0c7beb84 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,2 +1,2 @@
# These are supported funding model platforms
-github: [bartico6, Stealownz, QuiCM, hakusaro]
+github: [DeathCradle, hakusaro, Stealownz, QuiCM]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 46cd481e..0aa888dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,9 +12,20 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
* Do not forget to sign every line you change with your name. (@hakusaro)
* If there is no section called "Upcoming changes" below this line, please add one with `## Upcoming changes` as the first line, and then a bulleted item directly after with the first change.
-## Upcoming changes
+## TShock 4.5.10
+* Changed the server behavior when `SIGINT` is received. When `SIGINT` is trapped, the server will attempt to shut down safely. When it is trapped a second time in a session, it will immediately exit. (`SIGINT` is typically triggered via CTRL + C.) This means that it is possible to corrupt your world if you force shutdown at the wrong time (e.g., while the world is saving), but hopefully you expect this to happen if you hit CTRL + C twice in a session and you read the warning. (@hakusaro, @Onusai)
+
+## TShock 4.5.9
+* Added the ability to change a `TSPlayer`'s PVP mode. (@AgaSpace)
+* Added protocol level support for Terraria 1.4.3.2. (@DeathCradle, @Patrikkk)
+
+## TShock 4.5.8
* Removed `TShockAPI/DB/DBTools.cs`. This appears to have been dead code and not used by anything. (@hakusaro, @DeathCradle)
* Fixed the `/firework` command not sending fireworks when specified without a firework color. The firework command now correctly sends red fireworks to a target if a color is not specified. (@hakusaro, @Kojirremer)
+* Fixed bad XML of TShock code documentation. (@Arthri)
+* Fixed Bouncer exploits allowing for invalid tiles' placement. These tiles(specifically torches) caused clients to crash. The fixed exploits are listed below. (@Arthri, @QuiCM)
+ - [Biome Torch Correction](https://github.com/Pryaxis/TShock/blob/3ba1e7419d63535eeb8b5634ec668448499f71df/TShockAPI/Bouncer.cs#L310). Previously, it used unrelated values to validate biome torches, and unintentionally passed on invalid tiles. It's now fixed to use the correct values and block invalid torches. As well as a new right booster track correction/check, to allow right booster tracks to be placed. Right booster track is an extraneous place style because it depends on the direction the player is facing. The new check takes that into consideration so that the place style isn't considered mismatched and rejected.
+ - [Max Place Styles](https://github.com/Pryaxis/TShock/blob/3ba1e7419d63535eeb8b5634ec668448499f71df/TShockAPI/Bouncer.cs#L385). Previously, it rejects only if both `MaxPlaceStyles` and `ExtraneousPlaceStyles` contains an entry for a tile, and unintentionally passed on invalid tiles. `ExtraneousPlaceStyles` only contains special max placeStyles, not all placeables unlike `MaxPlaceStyles`. It's now corrected to take from `ExtraneousPlaceStyles` first, then fallback to `MaxPlaceStyles` if there's no entry for that tile, and then finally -1 if there's no entry in either.
## TShock 4.5.7
* Fixed the `/respawn` command to permit respawning players from the console. (@hakusaro, @Kojirremer)
diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs
index c4985d84..ae794096 100644
--- a/TShockAPI/Bouncer.cs
+++ b/TShockAPI/Bouncer.cs
@@ -44,6 +44,20 @@ namespace TShockAPI
internal Handlers.LandGolfBallInCupHandler LandGolfBallInCupHandler { get; private set; }
internal Handlers.SyncTilePickingHandler SyncTilePickingHandler { get; private set; }
+ ///
+ /// Represents a place style corrector.
+ ///
+ /// The player placing the tile.
+ /// The requested place style to be placed.
+ /// The actual place style that should be placed, based of the player's held item.
+ /// The correct place style in the current context.
+ internal delegate int PlaceStyleCorrector(Player player, int requestedPlaceStyle, int actualItemPlaceStyle);
+
+ ///
+ /// Represents a dictionary of s, the key is the tile ID and the value is the corrector.
+ ///
+ internal Dictionary PlaceStyleCorrectors = new Dictionary();
+
/// Constructor call initializes Bouncer and related functionality.
/// A new Bouncer.
internal Bouncer()
@@ -100,6 +114,122 @@ namespace TShockAPI
GetDataHandlers.KillMe += OnKillMe;
GetDataHandlers.FishOutNPC += OnFishOutNPC;
GetDataHandlers.FoodPlatterTryPlacing += OnFoodPlatterTryPlacing;
+
+
+ // The following section is based off Player.PlaceThing_Tiles_PlaceIt and Player.PlaceThing_Tiles_PlaceIt_GetLegacyTileStyle.
+ // Multi-block tiles are intentionally ignored because they don't pass through OnTileEdit.
+ PlaceStyleCorrectors.Add(TileID.Torches,
+ (player, requestedPlaceStyle, actualItemPlaceStyle) =>
+ {
+ // If the client is attempting to place a default torch, we need to check that the torch they are attempting to place is valid.
+ // The place styles may mismatch if the player is placing a biome torch.
+ // Biome torches can only be placed if the player has unlocked them (Torch God's Favor)
+ // Therefore, the following conditions need to be true:
+ // - The client's selected item will create a default torch(this should be true if this handler is running)
+ // - The client's selected item's place style will be that of a default torch
+ // - The client has unlocked biome torches
+ if (actualItemPlaceStyle == TorchID.Torch && player.unlockedBiomeTorches)
+ {
+ // The server isn't notified when the player turns on biome torches.
+ // So on the client it can be on, while on the server it's off.
+ // BiomeTorchPlaceStyle returns placeStyle as-is if biome torches is off.
+ // Because of the uncertainty, we:
+ // 1. Ensure that UsingBiomeTorches is on, so we can get the correct
+ // value from BiomeTorchPlaceStyle.
+ // 2. Check if the torch is either 0 or the biome torch since we aren't
+ // sure if the player has biome torches on
+ var usingBiomeTorches = player.UsingBiomeTorches;
+ player.UsingBiomeTorches = true;
+ // BiomeTorchPlaceStyle returns the place style of the player's current biome's biome torch
+ var biomeTorchPlaceStyle = player.BiomeTorchPlaceStyle(actualItemPlaceStyle);
+ // Reset UsingBiomeTorches value
+ player.UsingBiomeTorches = usingBiomeTorches;
+
+ return biomeTorchPlaceStyle;
+ }
+ else
+ {
+ // If the player isn't holding the default torch, then biome torches don't apply and return item place style.
+ // Or, they are holding the default torch but haven't unlocked biome torches yet, so return item place style.
+ return actualItemPlaceStyle;
+ }
+ });
+ PlaceStyleCorrectors.Add(TileID.Presents,
+ (player, requestedPlaceStyle, actualItemPlaceStyle) =>
+ {
+ // RNG only generates placeStyles less than 7, so permit only <7
+ // Note: there's an 8th present(blue, golden stripes) that's unplaceable.
+ // https://terraria.fandom.com/wiki/Presents, last present of the 8 displayed
+ if (requestedPlaceStyle < 7)
+ {
+ return requestedPlaceStyle;
+ }
+ else
+ {
+ // Return 0 for now, but ideally 0-7 should be returned.
+ return 0;
+ }
+ });
+ PlaceStyleCorrectors.Add(TileID.Explosives,
+ (player, requestedPlaceStyle, actualItemPlaceStyle) =>
+ {
+ // RNG only generates placeStyles less than 2, so permit only <2
+ if (requestedPlaceStyle < 2)
+ {
+ return requestedPlaceStyle;
+ }
+ else
+ {
+ // Return 0 for now, but ideally 0-1 should be returned.
+ return 0;
+ }
+ });
+ PlaceStyleCorrectors.Add(TileID.Crystals,
+ (player, requestedPlaceStyle, actualItemPlaceStyle) =>
+ {
+ // RNG only generates placeStyles less than 18, so permit only <18.
+ // Note: Gelatin Crystals(Queen Slime summon) share the same ID as Crystal Shards.
+ // <18 includes all shards except Gelatin Crystals.
+ if (requestedPlaceStyle < 18)
+ {
+ return requestedPlaceStyle;
+ }
+ else
+ {
+ // Return 0 for now, but ideally 0-17 should be returned.
+ return 0;
+ }
+ });
+ PlaceStyleCorrectors.Add(TileID.MinecartTrack,
+ (player, requestedPlaceStyle, actualItemPlaceStyle) =>
+ {
+ // Booster tracks have 2 variations, but only 1 item.
+ // The variation depends on the direction the player is facing.
+ if (actualItemPlaceStyle == 2)
+ {
+ // Check the direction the player is facing.
+ // 1 is right and -1 is left, these are the only possible values.
+ if (player.direction == 1)
+ {
+ // Right-facing booster tracks
+ return 3;
+ }
+ else if (player.direction == -1)
+ {
+ // Left-facing booster tracks
+ return 2;
+ }
+ else
+ {
+ throw new InvalidOperationException("Unrecognized player direction");
+ }
+ }
+ else
+ {
+ // Not a booster track, return as-is.
+ return actualItemPlaceStyle;
+ }
+ });
}
internal void OnGetSection(object sender, GetDataHandlers.GetSectionEventArgs args)
@@ -255,7 +385,10 @@ namespace TShockAPI
int tileY = args.Y;
short editData = args.EditData;
EditType type = args.editDetail;
- byte style = args.Style;
+
+ // 'placeStyle' is a term used in Terraria land to determine which frame of a sprite is displayed when the sprite is placed. The placeStyle
+ // determines the frameX and frameY offsets
+ byte requestedPlaceStyle = args.Style;
try
{
@@ -306,14 +439,38 @@ namespace TShockAPI
return;
}
- if ((args.Player.TPlayer.BiomeTorchHoldStyle(style) != args.Player.TPlayer.BiomeTorchPlaceStyle(style))
- && (selectedItem.placeStyle != style))
+ // This is the actual tile ID we expect the selected item to create. If the tile ID from the packet and the tile ID from the item do not match
+ // we need to inspect further to determine if Terraria is sending funny information (which it does sometimes) or if someone is being malicious
+ var actualTileToBeCreated = selectedItem.createTile;
+ // This is the actual place style we expect the selected item to create. Same as above - if it differs from what the client tells us,
+ // we need to do some inspection to check if its valid
+ var actualItemPlaceStyle = selectedItem.placeStyle;
+
+ // The client has requested to place a style that does not match their held item's actual place style
+ if (requestedPlaceStyle != actualItemPlaceStyle)
{
- TShock.Log.ConsoleError("Bouncer / OnTileEdit rejected from (placestyle) {0} {1} {2} placeStyle: {3} expectedStyle: {4}",
- args.Player.Name, action, editData, style, selectedItem.placeStyle);
- args.Player.SendTileSquare(tileX, tileY, 1);
- args.Handled = true;
- return;
+ var tplayer = args.Player.TPlayer;
+ // Search for an extraneous tile corrector
+ // If none found then it can't be a false positive so deny the action
+ if (!PlaceStyleCorrectors.TryGetValue(actualTileToBeCreated, out PlaceStyleCorrector corrector))
+ {
+ TShock.Log.ConsoleError("Bouncer / OnTileEdit rejected from (placestyle) {0} {1} {2} placeStyle: {3} expectedStyle: {4}",
+ args.Player.Name, action, editData, requestedPlaceStyle, actualItemPlaceStyle);
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+
+ // See if the corrector's expected style matches
+ var correctedPlaceStyle = corrector(tplayer, requestedPlaceStyle, actualItemPlaceStyle);
+ if (requestedPlaceStyle != correctedPlaceStyle)
+ {
+ TShock.Log.ConsoleError("Bouncer / OnTileEdit rejected from (placestyle) {0} {1} {2} placeStyle: {3} expectedStyle: {4}",
+ args.Player.Name, action, editData, requestedPlaceStyle, correctedPlaceStyle);
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
}
}
@@ -382,8 +539,7 @@ namespace TShockAPI
else if (action == EditAction.PlaceTile || action == EditAction.ReplaceTile || action == EditAction.PlaceWall || action == EditAction.ReplaceWall)
{
if ((action == EditAction.PlaceTile && TShock.Config.Settings.PreventInvalidPlaceStyle) &&
- (MaxPlaceStyles.ContainsKey(editData) && style > MaxPlaceStyles[editData]) &&
- (ExtraneousPlaceStyles.ContainsKey(editData) && style > ExtraneousPlaceStyles[editData]))
+ requestedPlaceStyle > GetMaxPlaceStyle(editData))
{
TShock.Log.ConsoleDebug("Bouncer / OnTileEdit rejected from (ms1) {0} {1} {2}", args.Player.Name, action, editData);
args.Player.SendTileSquare(tileX, tileY, 4);
@@ -2206,6 +2362,25 @@ namespace TShockAPI
});
}
+ ///
+ /// Returns the max associated with the given . Or -1 if there's no association
+ ///
+ /// Tile ID to query for
+ /// The max , otherwise -1 if there's no association
+ internal static int GetMaxPlaceStyle(int tileID)
+ {
+ int result;
+ if (ExtraneousPlaceStyles.TryGetValue(tileID, out result)
+ || MaxPlaceStyles.TryGetValue(tileID, out result))
+ {
+ return result;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
// These time values are references from Projectile.cs, at npc.AddBuff() calls.
private static Dictionary NPCAddBuffTimeMax = new Dictionary()
{
diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs
index 4d495209..a8ebe935 100644
--- a/TShockAPI/GetDataHandlers.cs
+++ b/TShockAPI/GetDataHandlers.cs
@@ -1934,6 +1934,7 @@ namespace TShockAPI
return args.Handled;
}
+ ///
/// For use in an Emoji event.
///
public class EmojiEventArgs : GetDataHandledEventArgs
@@ -1967,6 +1968,7 @@ namespace TShockAPI
return args.Handled;
}
+ ///
/// For use in a TileEntityDisplayDollItemSync event.
///
public class DisplayDollItemSyncEventArgs : GetDataHandledEventArgs
@@ -2025,6 +2027,7 @@ namespace TShockAPI
return args.Handled;
}
+ ///
/// For use in an OnRequestTileEntityInteraction event.
///
public class RequestTileEntityInteractionEventArgs : GetDataHandledEventArgs
@@ -4262,6 +4265,10 @@ namespace TShockAPI
///
internal static Dictionary ExtraneousPlaceStyles = new Dictionary
{
+ {TileID.Presents, 6},
+ {TileID.Explosives, 1},
+ {TileID.MagicalIceBlock, 0},
+ {TileID.Crystals, 17},
{TileID.MinecartTrack, 3}
};
diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs
index 723c555c..9eea5c63 100644
--- a/TShockAPI/TSPlayer.cs
+++ b/TShockAPI/TSPlayer.cs
@@ -1561,6 +1561,18 @@ namespace TShockAPI
Main.player[Index].team = team;
NetMessage.SendData((int)PacketTypes.PlayerTeam, -1, -1, NetworkText.Empty, Index);
}
+
+ ///
+ /// Sets the player's pvp.
+ ///
+ /// The state of the pvp mode.
+ public virtual void SetPvP(bool mode, bool withMsg = false)
+ {
+ Main.player[Index].hostile = mode;
+ NetMessage.SendData((int)PacketTypes.TogglePvp, -1, -1, NetworkText.Empty, Index);
+ if (withMsg)
+ TSPlayer.All.SendMessage(Language.GetTextValue(mode ? "LegacyMultiplayer.11" : "LegacyMultiplayer.12", Name), Main.teamColor[Team]);
+ }
private DateTime LastDisableNotification = DateTime.UtcNow;
diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs
index 68bb83b8..dac1e0cb 100644
--- a/TShockAPI/TShock.cs
+++ b/TShockAPI/TShock.cs
@@ -80,7 +80,7 @@ namespace TShockAPI
/// Players - Contains all TSPlayer objects for accessing TSPlayers currently on the server
public static TSPlayer[] Players = new TSPlayer[Main.maxPlayers];
- /// Bans - Static reference to the ban manager for accessing bans & related functions.
+ /// Bans - Static reference to the ban manager for accessing bans & related functions.
public static BanManager Bans;
/// Warps - Static reference to the warp manager for accessing the warp system.
public static WarpManager Warps;
@@ -149,7 +149,7 @@ namespace TShockAPI
///
public static event Action Initialized;
- /// Version - The version required by the TerrariaAPI to be passed back for checking & loading the plugin.
+ /// Version - The version required by the TerrariaAPI to be passed back for checking & loading the plugin.
/// value - The version number specified in the Assembly, based on the VersionNum variable set in this class.
public override Version Version
{
@@ -690,15 +690,24 @@ namespace TShockAPI
}
}
+ private bool tryingToShutdown = false;
+
/// ConsoleCancelHandler - Handles when Ctrl + C is sent to the server for a safe shutdown.
/// The sender
/// The ConsoleCancelEventArgs associated with the event.
private void ConsoleCancelHandler(object sender, ConsoleCancelEventArgs args)
{
+ if (tryingToShutdown)
+ {
+ System.Environment.Exit(1);
+ return;
+ }
// Cancel the default behavior
args.Cancel = true;
- Log.ConsoleInfo("Interrupt received. Saving the world and shutting down.");
+ tryingToShutdown = true;
+
+ Log.ConsoleInfo("Shutting down safely. To force shutdown, send SIGINT (CTRL + C) again.");
// Perform a safe shutdown
TShock.Utils.StopServer(true, "Server console interrupted!");
diff --git a/TShockAPI/Utils.cs b/TShockAPI/Utils.cs
index 42f8ea96..cb38a5ea 100644
--- a/TShockAPI/Utils.cs
+++ b/TShockAPI/Utils.cs
@@ -894,7 +894,7 @@ namespace TShockAPI
Main.recipe[i] = new Recipe();
}
- /// Dumps a matrix of all permissions & all groups in Markdown table format.
+ /// Dumps a matrix of all permissions & all groups in Markdown table format.
/// The save destination.
internal void DumpPermissionMatrix(string path)
{
@@ -1230,7 +1230,7 @@ namespace TShockAPI
for (int i = 0; i < Main.maxItemTypes; i++)
{
item.netDefaults(i);
- if (item.placeStyle > 0)
+ if (item.placeStyle >= 0)
{
if (GetDataHandlers.MaxPlaceStyles.ContainsKey(item.createTile))
{