Jump to content

Recommended Posts

Hey everyone! I wanted to share some important information about a fix related to Cube renewal systems, particularly the Wordard version. Recently, a serious issue was discovered that could be exploited by people with little to none stuff to do 😄

What’s the Problem?

So here’s the deal: the system didn’t have proper checks on the server side for the count_item parameter. This means that you could craft items without actually having the required ingredients.

For example, if an item needs yang (the in-game currency) to be crafted, the system wouldn’t check if you had enough yang in your inventory. As a result, you could craft items without needing the necessary materials. This could even lead to your yang balance going negative (GOLD_OVERFLOW).

Why Is This a Big Deal?

Having a negative yang balance means you could essentially exploit the game. Imagine being able to buy high-priced items or transfer unlimited yang—pretty game-breaking, right? You could buy whatever you want from the shop without any limits, throwing the in-game economy out of whack.

Acknowledgments

A huge shoutout goes to @ Amun for fixing this issue!

Code:
 

// game/src/unique_item.h
// find
	DRAGON_HEART_VNUM = 100000,

// add
	UNIQUE_ITEM_CRAFT_CHANCE_IMPROVEMENT = 0,



// game/src/cube_renewal.h
// find
void Cube_Make(LPCHARACTER ch, int index, int count_item, int index_item_improve);

// replace with
void Cube_Make(LPCHARACTER ch, int index, int make_count, int index_item_improve);



// game/src/cube_renewal.cpp
// find
void Cube_Make(LPCHARACTER ch, int index, int count_item, int index_item_improve)

// Replace entire function with:

// @Amun: index_item_improve will be used as a bool flag, otherwise you'll run out of items if it requires,
// for example, 40 items for improvement and want to make 10 crafts.. Change it to bool if you want to
void Cube_Make(LPCHARACTER ch, int index, int make_count, int index_item_improve)
{
	if (!ch || !ch->GetDesc())
		return;

	if (!ch->IsCubeOpen())
	{
		//ch->LocaleChatPacket(CHAT_TYPE_INFO, 224, "");
		return;
	}

	LPCHARACTER	npc = ch->GetQuestNPC();
	if (!npc)
		return;

	char buf[512]{};
	if (index < 0 || make_count <= 0)
	{
		LPCHARACTER npc = ch->GetQuestNPC();
		snprintf(buf, sizeof(buf), "%d %d %d %lu", index, make_count, index_item_improve, npc->GetRaceNum());
		LogManager::Instance().CharLog(ch, ch->GetGold(), "CUBE_EXPLOIT", buf);
		return;
	}

	int index_value = 0;
	const TCubeResultList& resultList = cube_info_map[npc->GetRaceNum()];
	for (TCubeResultList::const_iterator iter = resultList.begin(); resultList.end() != iter; ++iter, ++index_value)
	{
		if (index_value != index)
			continue;

		const SCubeMaterialInfo& materialInfo = *iter;
		TItemTable* p = ITEM_MANAGER::instance().GetTable(materialInfo.reward.vnum);
		if (!p)
		{
			sys_err("Invalid item table for vnum %u at craft npc %u, index %d", materialInfo.reward.vnum, npc->GetRaceNum(), index_value);
			return;
		}

		if (!IS_SET(p->dwFlags, ITEM_FLAG_STACKABLE))
			make_count = 1;

		// Don't allow them to make more than a stack(unless you edit it to handle it)
		if (make_count > 1 && make_count * materialInfo.reward.count > 200) // replace with stack limit constant
			make_count = 200 / materialInfo.reward.count; // replace with stack limit constant

		long long price = static_cast<long long>(materialInfo.gold) * make_count;
		if (price < 0) // in case we update for long long material gold
			return;

		if (ch->GetGold() < price)
		{
			//ch->LocaleChatPacket(CHAT_TYPE_INFO, 225, "");
			return;
		}

		for (int i = 0; i < materialInfo.material.size(); ++i)
		{
			if (ch->CountSpecifyItem(materialInfo.material[i].vnum) < materialInfo.material[i].count * make_count)
			{
				// not enough materials
				return;
			}
		}

		// TODO: Replace UNIQUE_ITEM_CRAFT_CHANCE_IMPROVEMENT in unique_item.h with actual item vnum if you want the feature to work.
		int bonusPctFromItem = 0;
		bool isUsingBonusItem = index_item_improve >= 0 && UNIQUE_ITEM_CRAFT_CHANCE_IMPROVEMENT;
		if (isUsingBonusItem) // check unique item as well, in case it's null
		{
			constexpr int maxPctBonus = 40; // max 40% extra pct bonus, 1 item for each %
			int totalImprovementItemCount = ch->CountSpecifyItem(UNIQUE_ITEM_CRAFT_CHANCE_IMPROVEMENT);
			int requiredImprovementItemCount = MINMAX(0, 100 - materialInfo.percent, maxPctBonus) * make_count;
			if (requiredImprovementItemCount > totalImprovementItemCount)
			{
				// Not enough for all the items. Send a chat packet or something
				return;
			}
			bonusPctFromItem = MINMAX(0, 100 - materialInfo.percent, maxPctBonus);
		}

		int successfulReqCount = 0;
		for (int i = 0; i < make_count; ++i)
		{
			int pct = number(1, 100);
			if (pct <= materialInfo.percent + bonusPctFromItem)
			{
				successfulReqCount++;
			}
		}

		LPITEM pItem = nullptr;
		if (successfulReqCount)
		{
			pItem = ITEM_MANAGER::instance().CreateItem(
				materialInfo.reward.vnum,
				materialInfo.reward.count * successfulReqCount,
				0UL, true, -1, false,
				materialInfo.reward.rarity);

			if (!pItem) // crafting failed, but not because of the player
				return;
		}

		if (pItem)
		{
			int emptyPos = pItem->IsDragonSoul() ? ch->GetEmptyDragonSoulInventory(pItem) : ch->GetEmptyInventoryEx(pItem);
			if (emptyPos < 0)
			{
				//ch->LocaleChatPacket(CHAT_TYPE_INFO, 191, ""); // not enough space
				M2_DESTROY_ITEM(pItem);
				return;
			}

			pItem->AddToCharacter(ch, TItemPos(pItem->IsDragonSoul() ? DRAGON_SOUL_INVENTORY : INVENTORY, emptyPos));
			snprintf(buf, sizeof(buf), "%d %d %d %lu %lu %lu", index, make_count, index_item_improve, npc->GetRaceNum(), pItem->GetVnum(), pItem->GetCount());
			LogManager::Instance().CharLog(ch, ch->GetGold(), "CUBE_MAKE", buf);
		}
		else
		{
			snprintf(buf, sizeof(buf), "%d %d %d %lu %lu %lu", index, make_count, index_item_improve, npc->GetRaceNum(), 0, 0);
			LogManager::Instance().CharLog(ch, ch->GetGold(), "CUBE_FAIL", buf);
		}

		// Take gold
		ch->PointChange(POINT_GOLD, -price, false);

		// Take item that offers bonus percentage, if any
		if (isUsingBonusItem && bonusPctFromItem) // check unique item as well, in case it's null
			ch->RemoveSpecifyItem(UNIQUE_ITEM_CRAFT_CHANCE_IMPROVEMENT, bonusPctFromItem * make_count);

		// Take the materials
		for (int i = 0; i < materialInfo.material.size(); ++i)
			ch->RemoveSpecifyItem(materialInfo.material[i].vnum, materialInfo.material[i].count * make_count);
	}
}

 

Edited by FrenchForeignLegion
  • Metin2 Dev 3
  • muscle 1
  • Love 8

Software Engineer @ CNH Industrial (NAFTA/EMEA)

Link to comment
https://metin2.dev/topic/33160-fix-cube-renewal-duping-exploit/
Share on other sites

Don't use any images from : imgur, turkmmop, freakgamers, inforge, hizliresim... Or your content will be deleted without notice...
Use : https://metin2.download/media/add/

Please use https://metin2.download/ when uploading files smaller than 100MB, otherwise the approval will take longer due to manual upload.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.