Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[refactor/perf] Deduplicate HasHackedItemStacks slot checks#3215

Open
Xekep wants to merge 2 commits intoPryaxis:general-develfrom
TerraZ-Team:pr/micro-perf-stackcheck-dedup
Open

[refactor/perf] Deduplicate HasHackedItemStacks slot checks#3215
Xekep wants to merge 2 commits intoPryaxis:general-develfrom
TerraZ-Team:pr/micro-perf-stackcheck-dedup

Conversation

@Xekep
Copy link

@Xekep Xekep commented Feb 27, 2026

Summary

  • remove large duplicated slot-check branches in HasHackedItemStacks`n- keep equivalent checks via a single helper and per-container loops

Scope

  • focused change in TSPlayer.HasHackedItemStacks only
  • no unrelated file changes

item.netDefaults(loadout3Dye[index].type);
item.Prefix(loadout3Dye[index].prefix);
item.AffixName();
CheckSlotStack(trash, "Stack cheat detected. Remove trash item {0} ({1}) and then rejoin.", checkNegative: false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why skip the negative stack check for the trash item?

Item trash = TPlayer.trashItem;
for (int i = 0; i < NetItem.MaxInventory; i++)

bool CheckSlotStack(Item item, string warningMessage, bool checkNegative = true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value doesn't seem to be used

SendErrorMessage(GetString("Stack cheat detected. Remove dye {0} ({1}) and then rejoin.", item.Name, dye[index].stack));
}
}
SendErrorMessage(GetString(warningMessage, item.Name, item.stack));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetString should be used on the raw strings directly for strings extraction, not inside the helper method.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fair points.

  1. About checkNegative: false for trash:
    I left it this way on purpose to keep old behavior. Before refactor, trash only checked stack > maxStack (no negative check). I added a comment to make that explicit. If you want, I can change this too, but I’d prefer a separate PR since that’s behavior-changing.

  2. About CheckSlotStack return value:
    Yep, agreed - leftover from the first refactor. I changed it to void in 85e1f323.

  3. About GetString:
    Also agreed. I moved messages to direct GetString("...") calls on raw literals, then pass the localized format into the helper. No nested GetString now. Included in 85e1f323.

@pacenadhif778
Copy link
Contributor

i try your all pr and i get bug when use this pr player stuck in requesting tile data

@pacenadhif778
Copy link
Contributor

no plugin installed (need someone to test it too) to validate my issue

@hakusaro
Copy link
Member

@greptile review

@greptile-apps
Copy link

greptile-apps bot commented Feb 27, 2026

Greptile Summary

This PR refactors HasHackedItemStacks to eliminate ~250 lines of duplicated slot-checking code by extracting the common logic into a CheckSlotStack helper function. While the refactoring improves maintainability and correctly preserves the legacy trash slot behavior, it introduces a critical security vulnerability.

Critical Issue:

  • The refactored code directly uses item.maxStack from player inventory items instead of creating a fresh Item object and calling netDefaults() to obtain the server-authoritative maxStack value
  • This breaks the established security pattern used throughout TShock (see Bouncer.cs:1232 and GetDataHandlers.cs:3346)
  • A hacked client that modifies both stack AND maxStack values would bypass this validation entirely

What needs to be fixed:

  • Restore the new Item() + netDefaults() pattern inside CheckSlotStack to validate against server-authoritative max stack values

Confidence Score: 1/5

  • This PR has a critical security vulnerability that weakens stack cheat detection
  • Score of 1 reflects a critical security regression in the anti-cheat system. While the refactoring approach is sound and the code is cleaner, the removal of the netDefaults() validation pattern introduces a vulnerability that could allow hackers to bypass stack limit checks. This must be fixed before merging.
  • TShockAPI/TSPlayer.cs requires immediate attention to fix the security vulnerability in CheckSlotStack

Important Files Changed

Filename Overview
TShockAPI/TSPlayer.cs Refactored HasHackedItemStacks to remove code duplication but introduced critical security vulnerability by removing netDefaults() validation

Last reviewed commit: 85e1f32

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

if (item == null || item.type == 0)
return;

if (item.stack > item.maxStack || (checkNegative && item.stack < 0))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical security issue: directly using item.maxStack breaks the established validation pattern used throughout TShock

The old code created a fresh Item and called netDefaults() to get server-authoritative maxStack:

Item item = new Item();
item.netDefaults(inventory[i].type);
if (inventory[i].stack > item.maxStack || inventory[i].stack < 0)

This same pattern is used in:

  • Bouncer.cs:1232-1234 (OnItemDrop validation)
  • GetDataHandlers.cs:3346-3352 (HandleChestItem validation)

The refactored code uses item.maxStack from the player's item directly. If a hacked client modifies both stack AND maxStack on their items, this check will fail to detect the cheat.

Fix: Restore the netDefaults pattern:

void CheckSlotStack(Item playerItem, string warningMessage, bool checkNegative = true)
{
	if (playerItem == null || playerItem.type == 0)
		return;

	Item referenceItem = new Item();
	referenceItem.netDefaults(playerItem.type);
	
	if (playerItem.stack > referenceItem.maxStack || (checkNegative && playerItem.stack < 0))
	{
		check = true;
		if (shouldWarnPlayer)
		{
			SendErrorMessage(warningMessage, referenceItem.Name, playerItem.stack);
		}
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants