Mojira Archive
MC-229413

Crafting table is able to duplicate items

Crafting table is able to dupe items. Code analysis (yarn mappings):
 
method_30010/internalOnSlotClick in ScreenHandler.java in 1.16.5

} else if (arg == SlotActionType.THROW && lv2.getCursorStack().isEmpty() && i >= 0) {
    Slot lv19 = this.slots.get(i);
    if (lv19 != null && lv19.hasStack() && lv19.canTakeItems(arg2)) {
        ItemStack lv20 = lv19.takeStack((j == 0) ? 1 : lv19.getStack().getCount());
        lv19.onTakeItem(arg2, lv20);
        arg2.dropItem(lv20, true);
    }
}

method_30010/internalOnSlotClick in ScreenHandler.java in 1.17

} else if (actionType == SlotActionType.THROW && this.getCursorStack().isEmpty() && slotIndex >= 0) {
    slot5 = (Slot)this.slots.get(slotIndex);
    j = button == 0 ? 1 : slot5.getStack().getCount();
    itemStack6 = slot5.takeStackRange(j, Integer.MAX_VALUE, player);                    
    player.dropItem(itemStack6, true);
}

In 1.17, the hasStack check was removed. Presumably, this check was deemed redundant and "moved". Thus, in 1.17 takeStackRange is able to be called even if the slot is empty.

public ItemStack takeStackRange(int min, int max, PlayerEntity player) {
    Optional<ItemStack> optional = this.tryTakeStackRange(min, max, player); 
    optional.ifPresent((stack) -> {
        this.onTakeItem(player, stack);
    });
    return (ItemStack)optional.orElse(ItemStack.EMPTY);
}

which calls

public Optional<ItemStack> tryTakeStackRange(int min, int max, PlayerEntity player) {
    if (!this.canTakeItems(player)) {
        return Optional.empty();
    } else if (!this.canTakePartial(player) && max < this.getStack().getCount()) {
        return Optional.empty();
    } else {
        min = Math.min(min, max);
        ItemStack itemStack = this.takeStack(min);
        if (this.getStack().isEmpty()) {
            this.setStack(ItemStack.EMPTY);
        }
        return Optional.of(itemStack);
    }
}

One important thing to note, is in the takeStackRange method, it only checks if the result of tryTakeStackRange is present (with the optional). This means, that the containing onTakeItem method can be called even if tryTakeStackRange returns an empty stack.

Moving on, the onTakeItem method is overridden by the crafting table's result slot (
CraftingResultSlot.java).

public void onTakeItem(PlayerEntity player, ItemStack stack) {     
    this.onCrafted(stack);
    DefaultedList<ItemStack> defaultedList = player.world.getRecipeManager().getRemainingStacks(RecipeType.CRAFTING, this.input, player.world);
    for(int i = 0; i < defaultedList.size(); ++i) {
        ItemStack itemStack = this.input.getStack(i);
        ItemStack itemStack2 = (ItemStack)defaultedList.get(i);
        if (!itemStack.isEmpty()) {
            this.input.removeStack(i, 1);
            itemStack = this.input.getStack(i);
        }
        if (!itemStack2.isEmpty()) {
            if (itemStack.isEmpty()) {
                this.input.setStack(i, itemStack2);
            } else if (ItemStack.areItemsEqualIgnoreDamage(itemStack, itemStack2) && ItemStack.areTagsEqual(itemStack, itemStack2)) { 
                itemStack2.increment(itemStack.getCount());
                this.input.setStack(i, itemStack2);
            } else if (!this.player.getInventory().insertStack(itemStack2)) { 
                this.player.dropItem(itemStack2, false);
            }
        }
    }
}

In this method, recipe configurations are compared with the input slots. Moreover, this method is also able to modify the input slot items. Particularly of concern, the code

 

} else if (ItemStack.areItemsEqualIgnoreDamage(itemStack, itemStack2) && ItemStack.areTagsEqual(itemStack, itemStack2)) {
    itemStack2.increment(itemStack.getCount());                  
    this.input.setStack(i, itemStack2);
}

checks if the input slot item and the recipe slot are equal, and when that is true, it will increment the input slot item stack. Combined with the fact that this method can be called even if the recipe is incomplete, this effectively allows for one to duplicate certain items.

 

All one needs to do in order to make use of this bug, is to write a small client-side only script to send click packets configured to throw the output slot of the crafting table. Afterwords, any items in the input slots will be duplicated. 

A video demonstrating this has been attached below. It was achieved in a multiplayer server in survival. A client-side only script was used to send the appropriate packets, however during certain situations (server-client desync), this bug was observed with vanilla clients as well.

Fixed

Andrew S

[Mojang] Bartosz Bok

2021-06-18, 12:05 AM

2021-06-28, 03:38 PM

2021-06-28, 01:50 PM

1

2

Plausible

Very Important

Crafting

crafting-table, dupe

1.17

1.17.1 Pre-release 2