Jump to content

Fix Duplication of Items through Channel Switcher


Ikarus_

Recommended Posts

  • Developer
 

 

252709OrangeWarning.pngWARNING: If your teleporter panel in lua doesn't use pc.can_warp you must add it obv !  It's important to check if a player can warp using pc.can_warp before to call pc.warp

 

 

 

 

 

Hello community,

 

I have always seen people go crazy for this problem which is solved with 2 lines of code, in particular with the various offlineshops (mine, or the free release great and ken) that are unfairly blamed for having duplication bugs. No!


I believe that if you warp a player on another core without performing any checks you can't blame those who made the other systems which are then used to take advantage of the vulnerability you caused.


If you implement something you need to make sure there are adequate checks to avoid causing vulnerabilities. In my opinion the cause of the duplication is not the various offlineshops, but the channel switcher which does not perform not even half checks before executing the warp.

 

Anyway, no more chatter.
I will explain here how to fix this problem.

 

The first thing to check is if the channel switcher is using CHARACTER :: CanWarp to check if the player can be connected on a new channel or not.

 

here i wanna paste a commonly seen function channel switcher to connect the player (char.cpp):
 


void CHARACTER::ChannelSwitch(int iNewChannel){
    long lAddr;
    long lMapIndex;
    WORD wPort;
    long x = this->GetX();
    long y = this->GetY();

    if (!CMapLocation::instance().Get(x, y, lMapIndex, lAddr, wPort))
    {
        return;
    }

    if(lMapIndex >= 10000){
        return;
    }

    std::map<WORD, int>ch;

    for(int i = 0; i < 4; i++){
        for(int i2 = 1; i2 < 9; i2++){
            ch[30*1000 + i*100 + i2] = i+1;
        }
    }
    int chan;
    if(ch.find(wPort) != ch.end()){
        chan = ch[wPort];
    }else{return;}
    Stop();
    Save();

    if(GetSectree()){
        GetSectree()->RemoveEntity(this);
        ViewCleanup();

        EncodeRemovePacket(this);
    }

    TPacketGCWarp p;
    p.bHeader    = HEADER_GC_WARP;
    p.lX    = x;
    p.lY    = y;
    p.lAddr    = lAddr;
    p.wPort    = (wPort - 100*(chan-1) + 100*(iNewChannel-1));

    GetDesc()->Packet(&p, sizeof(TPacketGCWarp));
}


How you can see, no checks are performed to check if the character are using any systems which may give problems.

So Here what we need is to add CanWarp check at the beginning of the method.

 

void CHARACTER::ChannelSwitch(int iNewChannel){
    //* START DUPLICATION FIX
    //* prevent problems about duplication of items
    //* using safebox/exchange/shop/acce
    if(!CanWarp()){
      return;
    }
    //* END DUPLICATION FIX

    long lAddr;
    long lMapIndex;
    WORD wPort;
    long x = this->GetX();
    long y = this->GetY();

    if (!CMapLocation::instance().Get(x, y, lMapIndex, lAddr, wPort))
    {
      return;
    }

    if(lMapIndex >= 10000){
      return;
    }

    std::map<WORD, int>ch;

    for(int i = 0; i < 4; i++){
      for(int i2 = 1; i2 < 9; i2++){
        ch[30*1000 + i*100 + i2] = i+1;
      }
    }
    int chan;
    if(ch.find(wPort) != ch.end()){
      chan = ch[wPort];
    }else{return;}
    Stop();
    Save();

    if(GetSectree()){
      GetSectree()->RemoveEntity(this);
      ViewCleanup();

      EncodeRemovePacket(this);
    }

    TPacketGCWarp p;
    p.bHeader    = HEADER_GC_WARP;
    p.lX    = x;
    p.lY    = y;
    p.lAddr    = lAddr;
    p.wPort    = (wPort - 100*(chan-1) + 100*(iNewChannel-1));

    GetDesc()->Packet(&p, sizeof(TPacketGCWarp));
}

 

This is enoght to solve 100% of the problems if your CanWarp is a complete check about all your system installed.

If not, you just need to adapt CanWarp with the systems you have installed.
I will make an example with my offlineshop so that the speech is clear to anyone.

 

 

Let's take a look to the Default CanWarp method.
The first thing to note is that its purpose is obviously to return true if the player is able to reconnect without any problems.

 


bool CHARACTER::CanWarp() const
{
	const int iPulse = thecore_pulse();
	const int limit_time = PASSES_PER_SEC(g_nPortalLimitTime);

	if ((iPulse - GetSafeboxLoadTime()) < limit_time)
		return false;

	if ((iPulse - GetExchangeTime()) < limit_time)
		return false;

	if ((iPulse - GetMyShopTime()) < limit_time)
		return false;

	if ((iPulse - GetRefineTime()) < limit_time)
		return false;

	if (GetExchange() || GetMyShop() || GetShopOwner() || IsOpenSafebox() || IsCubeOpen())
		return false;
	return true;
}

 

How you can see here you can find a lot of checks which they are checking the last time of every auction may be used to take exploit the warp.
What we need to do here is to add new checks, to cover all the systems installed and fix all vulnerabilities.

 

An example about an offlineshop would be:
 

bool CHARACTER::CanWarp() const
{
	const int iPulse = thecore_pulse();
	const int limit_time = PASSES_PER_SEC(g_nPortalLimitTime);

	if ((iPulse - GetSafeboxLoadTime()) < limit_time)
		return false;

	if ((iPulse - GetExchangeTime()) < limit_time)
		return false;

	if ((iPulse - GetMyShopTime()) < limit_time)
		return false;

	if ((iPulse - GetRefineTime()) < limit_time)
		return false;

	if (GetExchange() || GetMyShop() || GetShopOwner() || IsOpenSafebox() || IsCubeOpen())
		return false;

#ifdef __ENABLE_NEW_OFFLINESHOP__
	if (iPulse - GetOfflineShopUseTime() < limit_time)
		return false;

	if (GetOfflineShopGuest() || GetAuctionGuest())
		return false;
#endif

	return true;
}

 

 

Note: I m using GetOfflineShopGuest and GetAuctionGuest which are methods used on my shop to get the pointer to the opened offlineshop/auction but if you are using another offlineshop system you need to find the equivalent way to check if the player is guest into a offline shop.

The method GetOfflineShopUseTime is a new method which i m implementing to check the last use time of the system.

Let's see how to implement it (char.cpp)

 

//SEARCH 
		void			ResetStopTime();
		DWORD			GetStopTime() const;

//ADD UNDER 

#ifdef __ENABLE_NEW_OFFLINESHOP__
  public:
		int			GetOfflineShopUseTime() const {return m_iOfflineShopUseTime;}
		void			SetOfflineShopUseTime(){m_iOfflineShopUseTime = thecore_pulse();}

  private:
		int			m_iOfflineShopUseTime = 0;
#endif

 

Here we are instantiating a new int where we can store the current time when player use the systemUsing SetOfflineShopUseTime the method will update the value of m_iOfflineShopUseTime and using GetOfflineShopUseTime we can check the last time player used the system (as done into CanWarp).

 

What's missing to do?
We need to use SetOfflineShopUseTime where the player is using our offlineshop (buy item, open shop, edit shop, close shop, ecc.) , so that the last use time is actually updated when needed, making the check in can warp effective.

A similar check can be done for other systems (eg acce system) to make our CanWarp safer and more effective.


I hope it's usefull.
Bye ;)

Edited by Ikarus_
  • Metin2 Dev 1
  • Good 1
  • Love 21

My youtube channel  on which you can see my works here

Link to comment
Share on other sites

  • 2 months later...

Can you help me insert the fix in great's offline shop please?

 

I didn't understand what to put in bool CHARACTER :: CanWarp () const

 

my char.h define:

 

#ifdef OFFLINE_SHOP
	public:
		void			OpenMyShop(const char * c_pszSign, TShopItemTable * pTable, BYTE bItemCount, DWORD days);
		void			SendShops(bool isGm = false);
		void			OpenShop(DWORD id, const char *name, bool onboot = false);
		void			SetPrivShop(DWORD shop_id) { bprivShop = shop_id; }
		BOOL			IsPrivShop(void)  const { return bprivShop>0; }
		DWORD			GetPrivShop()  const { return bprivShop; }
		void			SetPrivShopOwner(DWORD id) { bprivShopOwner = id; }
		DWORD			GetPrivShopOwner()  const { return bprivShopOwner; }
		void			DeleteMyShop();
		DWORD			GetShopTime()  const { return dw_ShopTime; }
		void			SetShopTime(DWORD time) { dw_ShopTime = time; }
		void			SetShopSign(const char * name);
		void			LoadPrivShops();
		TPrivShop		GetPrivShopTable(DWORD id);
		void			RemovePrivShopTable(DWORD id);
		void			UpdatePrivShopTable(DWORD id, TPrivShop shop);
		void			UpdateShopItems();
		void			SendShopCost();
	private:
		PSHOP_MAP		m_mapshops;
		DWORD			bprivShop;
		DWORD			bprivShopOwner;
		DWORD			dw_ShopTime;
	public:
		void			StartRefreshShopEvent();
	protected:
		LPEVENT			m_pkRefreshShopEvent;
	public:
		void			StartShopEditModeEvent();
		void			SetShopEditMode(bool val);
		bool			GetShopEditMode() { return m_bShopEditMode; }
		void			SetShopEditModeTick();
		DWORD			GetShopEditModeTick() { return m_dwShopEditModeTick; }
	protected:
		LPEVENT			m_pkEditShopEvent;
		bool			m_bShopEditMode;
		DWORD			m_dwShopEditModeTick;
#endif	

#ifdef GIFT_SYSTEM
	protected:
		void			AddGiftGrid(int page);
		int				AddGiftGridItem(int page, int size);
		GIFT_MAP		m_mapGiftGrid;
		LPEVENT			m_pkGiftRefresh;
		DWORD			m_dwLastGiftPage;
	public:
		void			StartRefreshGift();
		void			LoadGiftPage(int page);
		void			RefreshGift();
		int				GetGiftPages() { return m_mapGiftGrid.size(); }
		int				GetLastGiftPage() { return m_dwLastGiftPage; }
#endif

 

Link to comment
Share on other sites

  • 6 months later...
On 5/22/2021 at 8:00 PM, Ikarus_ said:

Added an important disclaimer at the beginning!

for your disclaimer, should it not be enough if you check the CanWarp directly at the pc_warp function in questlua_pc? so you dont need to add the check to EVERY quest

 

like this

 

		if (!ch->CanWarp())
		{
			ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("CANNOT_WARP_AFTER_TRADING"));
			lua_pushboolean(L, false);
			return 1;
		}

 

Edited by Mafuyu
Link to comment
Share on other sites

  • Developer
22 hours ago, Mafuyu said:

for your disclaimer, should it not be enough if you check the CanWarp directly at the pc_warp function in questlua_pc? so you dont need to add the check to EVERY quest

 

like this

 


		if (!ch->CanWarp())
		{
			ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("CANNOT_WARP_AFTER_TRADING"));
			lua_pushboolean(L, false);
			return 1;
		}

 

I don't like it, but feel free to use it

My youtube channel  on which you can see my works here

Link to comment
Share on other sites

Announcements



×
×
  • 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.