diff --git a/CHANGELOG.md b/CHANGELOG.md
index effb8191..c24fd45d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,9 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
## Upcoming Changes
* New permission `tshock.tp.pylon` to enable teleporting via Teleportation Pylons (@QuiCM)
* New permission `tshock.journey.research` to enable sharing research via item sacrifice (@QuiCM)
+* Add Emoji event to GetDataHandler. This packet is received when a player tries to display an emote.
+ * Adding EmojiHandler to handle an exploit. Adding `tshock.sendemoji` permission and checks. Added this permission to guest group by default.
+* Handling SyncCavernMonsterType packet to prevent an exploit where players could modify the server's cavern monster types and make the server spawn any NPCs - including bosses - onto other players.
* 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 10)
diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs
index 95a4cb40..ec7a4164 100644
--- a/TShockAPI/Bouncer.cs
+++ b/TShockAPI/Bouncer.cs
@@ -38,6 +38,7 @@ namespace TShockAPI
{
internal Handlers.SendTileSquareHandler STSHandler { get; set; }
internal Handlers.NetModules.NetModulePacketHandler NetModuleHandler { get; set; }
+ internal Handlers.EmojiHandler EmojiHandler { get; set; }
internal Handlers.LandGolfBallInCupHandler LandGolfBallInCupHandler { get; set; }
/// Constructor call initializes Bouncer and related functionality.
@@ -50,6 +51,9 @@ namespace TShockAPI
NetModuleHandler = new Handlers.NetModules.NetModulePacketHandler();
GetDataHandlers.ReadNetModule += NetModuleHandler.OnReceive;
+ EmojiHandler = new Handlers.EmojiHandler();
+ GetDataHandlers.Emoji += EmojiHandler.OnReceiveEmoji;
+
LandGolfBallInCupHandler = new Handlers.LandGolfBallInCupHandler();
GetDataHandlers.LandGolfBallInCup += LandGolfBallInCupHandler.OnLandGolfBallInCup;
diff --git a/TShockAPI/DB/GroupManager.cs b/TShockAPI/DB/GroupManager.cs
index 584ad374..3cff37a1 100644
--- a/TShockAPI/DB/GroupManager.cs
+++ b/TShockAPI/DB/GroupManager.cs
@@ -65,7 +65,8 @@ namespace TShockAPI.DB
Permissions.canpartychat,
Permissions.cantalkinthird,
Permissions.canchat,
- Permissions.synclocalarea));
+ Permissions.synclocalarea,
+ Permissions.sendemoji));
AddDefaultGroup("default", "guest",
string.Join(",",
diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs
index 2abb5757..6a420ecd 100644
--- a/TShockAPI/GetDataHandlers.cs
+++ b/TShockAPI/GetDataHandlers.cs
@@ -151,10 +151,12 @@ namespace TShockAPI
{ PacketTypes.CrystalInvasionStart, HandleOldOnesArmy },
{ PacketTypes.PlayerHurtV2, HandlePlayerDamageV2 },
{ PacketTypes.PlayerDeathV2, HandlePlayerKillMeV2 },
+ { PacketTypes.Emoji, HandleEmoji },
{ PacketTypes.SyncRevengeMarker, HandleSyncRevengeMarker },
{ PacketTypes.LandGolfBallInCup, HandleLandGolfBallInCup },
{ PacketTypes.FishOutNPC, HandleFishOutNPC },
- { PacketTypes.FoodPlatterTryPlacing, HandleFoodPlatterTryPlacing }
+ { PacketTypes.FoodPlatterTryPlacing, HandleFoodPlatterTryPlacing },
+ { PacketTypes.SyncCavernMonsterType, HandleSyncCavernMonsterType }
};
}
@@ -1868,7 +1870,41 @@ namespace TShockAPI
}
///
- /// For use in a LandGolfBallInCup event.
+ /// For use in an Emoji event.
+ ///
+ public class EmojiEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// The player index in the packet, who sends the emoji.
+ ///
+ public byte PlayerIndex { get; set; }
+ ///
+ /// The ID of the emoji, that is being received.
+ ///
+ public byte EmojiID { get; set; }
+ }
+ ///
+ /// Called when a player sends an emoji.
+ ///
+ public static HandlerList Emoji = new HandlerList();
+ private static bool OnEmoji(TSPlayer player, MemoryStream data, byte playerIndex, byte emojiID)
+ {
+ if (Emoji == null)
+ return false;
+
+ var args = new EmojiEventArgs
+ {
+ Player = player,
+ Data = data,
+ PlayerIndex = playerIndex,
+ EmojiID = emojiID
+ };
+ Emoji.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use in a LandBallInCup event.
///
public class LandGolfBallInCupEventArgs : GetDataHandledEventArgs
{
@@ -3606,6 +3642,32 @@ namespace TShockAPI
return false;
}
+
+ private static bool HandleEmoji(GetDataHandlerArgs args)
+ {
+ byte playerIndex = args.Data.ReadInt8();
+ byte emojiID = args.Data.ReadInt8();
+
+ if (OnEmoji(args.Player, args.Data, playerIndex, emojiID))
+ return true;
+
+ 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 HandleSyncRevengeMarker(GetDataHandlerArgs args)
{
@@ -3662,6 +3724,13 @@ namespace TShockAPI
return false;
}
+ private static bool HandleSyncCavernMonsterType(GetDataHandlerArgs args)
+ {
+ args.Player.Kick("Exploit attempt detected!");
+ TShock.Log.ConsoleDebug($"HandleSyncCavernMonsterType: Player is trying to modify NPC cavernMonsterType; this is a crafted packet! - From {args.Player.Name}");
+ return true;
+ }
+
public enum EditAction
{
KillTile = 0,
diff --git a/TShockAPI/Handlers/EmojiHandler.cs b/TShockAPI/Handlers/EmojiHandler.cs
new file mode 100644
index 00000000..b21cd53e
--- /dev/null
+++ b/TShockAPI/Handlers/EmojiHandler.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TShockAPI.Handlers
+{
+ ///
+ /// Handles emoji packets and checks for validity and permissions
+ ///
+ public class EmojiHandler
+ {
+ public void OnReceiveEmoji(object sender, GetDataHandlers.EmojiEventArgs args)
+ {
+ if (args.PlayerIndex != args.Player.Index)
+ {
+ TShock.Log.ConsoleError($"EmojiHandler: Emoji packet rejected for ID spoofing. Expected {args.Player.Index}, received {args.PlayerIndex} from {args.Player.Name}.");
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.HasPermission(Permissions.sendemoji))
+ {
+ args.Player.SendErrorMessage("You do not have permission to send emotes!");
+ args.Handled = true;
+ return;
+ }
+ }
+ }
+}
diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs
index 3b401004..643882f8 100644
--- a/TShockAPI/Permissions.cs
+++ b/TShockAPI/Permissions.cs
@@ -474,6 +474,9 @@ namespace TShockAPI
[Description("Player can resync themselves with server state.")]
public static readonly string synclocalarea = "tshock.synclocalarea";
+
+ [Description("Player can send emotes.")]
+ public static readonly string sendemoji = "tshock.sendemoji";
#endregion
///
/// Lists all commands associated with a given permission
diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj
index d1e03b61..495127c8 100644
--- a/TShockAPI/TShockAPI.csproj
+++ b/TShockAPI/TShockAPI.csproj
@@ -97,6 +97,7 @@
+