Jump to content

masodikbela

Premium
  • Posts

    235
  • Joined

  • Last visited

  • Days Won

    28
  • Feedback

    100%

Posts posted by masodikbela

  1. On 11/4/2023 at 9:06 AM, Intel said:

    You do realize that the presentation in the website is the latest version of Aeldra?

    In addition, I am pretty sure Flourine is not involved in the new Aeldra. I would also add that there may or may not be an ex Aeldra's dev in the team (I cannot confirm it, so take the last thing with a grain of salt).

    Original aeldra never had lycan.

    On 11/3/2023 at 7:17 PM, envy said:

    the files that are for sale are from 2018, stop making false rumors.

    Thats right, its got leaked late 2018 and ppl started to sell my wiki from it around 2019.

    Apart from that there is a really easy way to distinct if something is from the latest aeldra files or not, as no other server has text selection and proper bi-direction text implemented as far as I know and it was fairly new in aeldra from around May 2022. Just open a PM window or normal chat window and try to select/copy texts from it or pasting texts containing both english and arabic texts.

    Apart from the code other assets from the client have been extracted many times before its closure so thats not an indicator that files have been leaked.

    • kekw 1
  2. There was a correction today after many years past the release. There was a heap-use-after-free bug in the regen_free_map function, which means that the regen struct has been freed and after that in the increment part of for-loop accessed this deleted struct for the next regen in line. Normally while its not safe, it still doesn't cause any issues since the just-freed memory part is not overwritten yet, but in rare occasions it can happen. Also with debug mode or debuggers using shadow bytes to detect such cases would immediately notice it. This might have been the reason while my vs debugger got stuck back in the old days.

    Thanks to @ Karbust for noticing this problem with address sanitizer.

    • Metin2 Dev 2
    • muscle 1
    • Love 1
  3. 10 hours ago, Grzyb said:

    @MT2Dev@ martysama0134@ masodikbela

    Very strange solutions, the main problem is

    void ITEM_MANAGER::Update()

    function in item_manager.cpp

    when you fix this function, you fix this problem exploit without MoveItem

    As I explained in details in the "How does it work?" part, there is nothing really to fix in the update function. The main problem is that the newly created item overwrites the original stack, and doesn't remove it properly from the core, creating an actual memory leak and later on loading the id is inside the global id map thus preventing it to be created again. But besides all of this putting an item to an occupied cell is an invalid operation and should be always prohibited.

  4. Hi there devs,

    Its been a while since my last topic, and the reason for waking up from my eternal hyper dream is no less significant than a small possible item dupe exploit that is present in the original leaked source, and therefore very likely still present on most of the active servers including the official. I've seen topics from late 2021 where this bug was abused on prestigious international servers, so its for sure known by some people for a long time, moreover even a related partial fix for the exploit is already present on the board:

    How does it work?

    Spoiler

    Well obviously I will not attach any code or video that could help anyone reproduce the bug to prevent abusing it on other servers, but I will explain how it works. This bug cannot be reproduced under normal circumstances, so its no surprise that it was able to survive for this long. To be able to abuse this, you either have to modify uiinvenotry.py, or have an external tool that can either attach to the binary and call the SendItemMovePacket function, or can send packets either through the client's connection or through a "fake" client mimicing its behaviour.

    So as you can see depending on the server's structure/protection it can be rather hard to execute. The main issue with the server's code is that its either not checking if the source and the destination cell is the same in CHARACTER::MoveItem (in the default source) or in some codes it might be checked, but a potential loophole might left open. The problem lies within the TItemPos's == operator combined with the way EQUIPMENT and INVENTORY window types work. The == operator checks both window and cell, and only returns true if both are the same. On the other hand we all know that EQUIPMENT window type only exists in theory, the actual cells for it are actually arranged after the cells of the INVENTORY. As you can see in IsEquipPosition, the window type can be either INVENTORY or EQUIPMENT, it doesn't matter, as long as its not something else and the cell is in the right range. From the normally existing window types these two that has this behaviour, and therefore the src and dst positions in the MoveItem function can be constructed in a way that both has the same value for the cell, but one has INVENTORY for window type and the other has EQUIPMENT for that.

    Obviously this is a major flaw and after fixing it this type of exploit is no longer possible, but there is an interesting question how this escalates into an actual item dupe when abused. For this question I only have a strong theory, which I didn't actually fact-checked yet:

    When you unstack an item, a completely new "item" is generated with new ID. Its nothing new for most of us, its important to keep in mind. When you unstack an item and you put the "new" stack on the old's position, unless you used the entire stack, its not gonna get destroyed/removed and it will be updated, but won't be visible anymore inside the client, because the new stack will "overwrite" it visually. This stack will be "flushed" to the db at some point (whenever its time for the core to flush the items) so this won't disappear just like that, but on the db's side there is no check for items position, so nothing blocks it to save 2 items on the same position, same with the database there is no unique keys or whatnot to throw an error for a query trying to put 2 items on the same position.

    However whenever you relog to this same core, the original stack of the item will not appear anymore (or at least until the item "cache" (or rather say the DelayedFlush stuff) of the core got flushed).
    On the other hand, if you go to another core, one of the stacks will be put into the first empty position inside the inventory. Now this raises a suspicion. The reason behind rests inside the destructor of the player's character.

    On the destructor all the items of the player in every inventory will be flushed instantly to the db (so other cores will have them up-to-date) and will be removed from the item manager. However on the core where we executed the exploit, the old stack is in a state where its not technically inside the player's inventory (its owner is the player of course, but the item's pointer is no longer stored in the player's item vector) and therefore wont get destroyed when the player logs out (see CHARACTER::ClearItem()). The item will be marked for delayed save, and the clock is ticking for it even if we are already on a different channel. This means that if we time our actions right, the state of the item from the original core will overwrite the cache inside the db, so lets say we delete this stack on the second core (by putting together the item with another stack) and log out (so the state of the item on the second core will be flushed to the db) and then wait for the first core to flush this old state of that now "deleted" stack, basically restoring the item to that old state.

    The reason this "ghost" item will never appears again in the first core if we log in multiple times rests inside the item manager's CreateItem function. As you can see, at the beginning of the function it checks the global map if the item ID is already in there (spoiler: it is obviously in there) and then spitting out a sys_err message about ITEM_ID_DUP and then returning basically nothing, and not doing anything with this ghost item, so basically after the delayed save called once that item will be invisible forever on that core (only gonna overwrite itself on the db once tho), as long as the core is running. So we get a small memory leak next to our dupe exploit. (If you ask me this is more of a serious matter than the exploit 💀)

    How to fix it?

    I think I filled my quota of talking in the how does it work section above, so without further ado:

    Spoiler

    In char_item.cpp at the very beginning of the CHARACTER::MoveItem function add this:

        {
            TItemPos tempSrc = Cell;
            TItemPos tempDst = DestCell;
            if (tempSrc.window_type == EQUIPMENT)
                tempSrc.window_type = INVENTORY;
    
            if (tempDst.window_type == EQUIPMENT)
                tempDst.window_type = INVENTORY;
    
            if (tempSrc == tempDst)
                return false;
        }

    Thats it, but if you are planning on commenting about the fix, here is the most important part of the whole post that you must read before doing so:

    Spoiler

    Before you type something like "🤓🤓🤓🤓 Erm actually you could have just checked the input arguments (Cell, DestCell) and change them, there is actually no need to introduce this 2 new temporary variables just to delete them instantly and increase the code size and the memory consumption by approximately 16 bytes and slowing the whole function down by circa 500 nanoseconds 🤓🤓🤓🤓" let me spare the comment, the reason for it is that I do not know your code, and your code might be relying at some point on the correct window_type, and changing it on the input variables might introduce another bug in that part of the code. The extra code block is there because why not, I just didn't want to leave any footprint of those temp variables and have someone use them later by accident after the block.

     

     

    • Metin2 Dev 14
    • Good 2
    • Love 2
    • Love 15
  5. Alright lets see your points:

    1. Indeed sus, but on the other hand, if you think about it: how can you actually get your toplist well known by players if not convincing an actually running server to give some thoughts about using their site instead of other already existing and already popular sites. As I'm aware by that time there was no other running toplist that was actually trustable and were not already proven to be corrupt in some ways (purchasable votes, easily bottable or something like that). If I would about to start a toplist I for sure would go try convince a big server to consider switching their primary vote for buff system to my site or noone will ever know about my site. (For example see m2devs toplist, literally noone uses it as far as I see.)
    2. This is a vague topic since probably as many ppl as there are will see it differently, as for me I don't see any similarities, the serverlist's logo (as well as the whole design) looks really oldschool, couldn't even compare it with any other server's I know about.
    3. I'm clueless about web development (and if you ask me its work of Satan himself so if he exists he is definitely the real brain behind it). From what little I know I could imagine this might even be some kind of framework generated code or something like that no? Like I said I'm clueless so rather not guess anything else on this point.
    4. I can only say the same thing as I did on the first point.

    All in all I can definitely see some kind of connection between them, so being in partnership at least seems to be very likely and actually would be very logical since the aforementioned points. However these are no proofs to that the toplist is owned by the same person/team. As far as I'm aware the toplist is owned by Zerial, and I can tell that for sure that he is not the owner of Aeldra.

    I don't see much point of this topic, there is no actual hard proof, the only thing I can see is the likely frustration of the author whos' probable interested in other concurrent toplist that is slowly losing popularity to this toplist. If the site turns out to be corrupt as the others (favouring specific servers, selling votes or so) then bring us some proof and I will grab my pitchfork and join the crowd.

    • Good 1
  6. 1 hour ago, DDC said:
     
    void CGraphicWikiRenderTargetTexture::Render() const
    {
    	const float sx = static_cast<float>(m_renderRect.left) - 0.5f + static_cast<float>(m_renderBox.left);
    	const float sy = static_cast<float>(m_renderRect.top) - 0.5f + static_cast<float>(m_renderBox.top);
    	const float ex = static_cast<float>(m_renderRect.left) + (static_cast<float>(m_renderRect.right) - static_cast<float>(m_renderRect.left)) - 0.5f - static_cast<float>(m_renderBox.right);
    	const float ey = static_cast<float>(m_renderRect.top) + (static_cast<float>(m_renderRect.bottom) - static_cast<float>(m_renderRect.top)) - 0.5f - static_cast<float>(m_renderBox.bottom);
    	
    	const float texReverseWidth = 1.0f / (static_cast<float>(m_renderRect.right) - static_cast<float>(m_renderRect.left));
    	const float texReverseHeight = 1.0f / (static_cast<float>(m_renderRect.bottom) - static_cast<float>(m_renderRect.top));
    	
    	const float su = m_renderBox.left * texReverseWidth;
    	const float sv = m_renderBox.top * texReverseHeight;
    	const float eu = ((m_renderRect.right - m_renderRect.left) - m_renderBox.right) * texReverseWidth;
    	const float ev = ((m_renderRect.bottom - m_renderRect.top) - m_renderBox.bottom) * texReverseHeight;
    
    	TPDTVertex pVertices[4];
    	pVertices[0].position = TPosition(sx, sy, 0.0f);
    	pVertices[0].texCoord = TTextureCoordinate(su, sv);
    	pVertices[0].diffuse = 0xffffffff;
    	
    	pVertices[1].position = TPosition(ex, sy, 0.0f);
    	pVertices[1].texCoord = TTextureCoordinate(eu, sv);
    	pVertices[1].diffuse = 0xffffffff;
    	
    	pVertices[2].position = TPosition(sx, ey, 0.0f);
    	pVertices[2].texCoord = TTextureCoordinate(su, ev);
    	pVertices[2].diffuse = 0xffffffff;
    	
    	pVertices[3].position = TPosition(ex, ey, 0.0f);
    	pVertices[3].texCoord = TTextureCoordinate(eu, ev);
    	pVertices[3].diffuse = 0xffffffff;
    	
    	if (SetPDTStream(pVertices, 4))
    	{
    		CGraphicBase::SetDefaultIndexBuffer(CGraphicBase::DEFAULT_IB_FILL_RECT);
    		
    		STATEMANAGER.SetTexture(0, GetD3DRenderTargetTexture());
    		STATEMANAGER.SetTexture(1, NULL);
    		STATEMANAGER.SetVertexShader(D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_DIFFUSE);
    
    
    		STATEMANAGER.DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 4, 0, 2);
    	}
    }

    I think it's wiki from AE... but only for this part, i just don't understand how to use rect,vertices and StateManager!
    If you're refusing to update, i'll understand!

    It is, and its already have clipping inside it, you can control it with the m_renderBox. The values in left, top, right and bottom of that m_renderBox will determine how much space have to be clipped from each side in pixel positions.

    I made a small visual presentation about clipping, more specifically about text clipping, but the theory behind it applys to every kind of texture clipping.spacer.png

    Some additional good read about texture positions and texture rendering: Texture Coordinates, Directly Mapping Texels to Pixels

    • Metin2 Dev 1
    • Love 2
  7. Here is a little collab for the clipping, this extends the ExpandedImageBox, so you can scale/rotate those images and still be able to clip them. The code contains very informative comments so if you want to learn a few tricks you might want to read them.

    Spoiler

    In GrpExpandedImageInstance.cpp replace the whole Render function with this:

    void CGraphicExpandedImageInstance::OnRender(RECT* rMask)
    {
    	// Okay so first we are gonna rework the code to use transofrmation matrix.
    	// To do that we just have to touch the xyzs. This means we have to setup
    	// them to match the original rectangle (without scaling rotation translation).
    	// Just like how we would want to render the image to the left top corner
    	// of the screen as it is in the file.
    	// 
    	// We will obviously need the UVs so I'm not gonna touch that part, it has
    	// nothing to do with the transformation matrix.
    	//
    	// The only thing is that the rotation and scaling is virtual so other codes 
    	// will not see the rect of the scaled/rotated eximagebox correctly so if you 
    	// want to put another image on such eximageboxes you will need extra logic,
    	// perhabs the current structure for clipping will not be enough for that anyway.
    	//
    	// With the DEBUG_CLIPMASK macro you can highlight the parent window's rect.
    
    
    	CGraphicImage* pImage = m_roImage.GetPointer();
    	CGraphicTexture* pTexture = pImage->GetTexturePointer();
    
    	const RECT& c_rRect = pImage->GetRectReference();
    	float texReverseWidth = 1.0f / float(pTexture->GetWidth());
    	float texReverseHeight = 1.0f / float(pTexture->GetHeight());
    	float su = (c_rRect.left - m_RenderingRect.left) * texReverseWidth;
    	float sv = (c_rRect.top - m_RenderingRect.top) * texReverseHeight;
    	float eu = (c_rRect.left + m_RenderingRect.right + (c_rRect.right - c_rRect.left)) * texReverseWidth;
    	float ev = (c_rRect.top + m_RenderingRect.bottom + (c_rRect.bottom - c_rRect.top)) * texReverseHeight;
    
    	// set up the vertex position in screen coordinates
    	// the rendering rect is usually 0, only used for stuff like hp bar, 
    	// normal window base and so on, where you want to replicate the texture
    	// multiple times (aka. wrap texture address mode). Since it will count
    	// as the "part of the basic image box" we will add it here as well.
    	//
    	// The only trick we will apply that we will shift the wrapping to the 
    	// end positions instead of substracting them from the start positions,
    	// since we want to base the original box to 0,0. We will adjust it on
    	// the translation.
    	float sx = 0.f;
    	float sy = 0.f;
    	float ex = float(pImage->GetWidth()) + m_RenderingRect.right + m_RenderingRect.left;
    	float ey = float(pImage->GetHeight()) + m_RenderingRect.bottom + m_RenderingRect.top;
    
    	TPDTVertex vertices[4];
    	vertices[0].position.x = sx;
    	vertices[0].position.y = sy;
    	vertices[0].position.z = 0.0f;
    	vertices[0].texCoord = TTextureCoordinate(su, sv);
    	vertices[0].diffuse = m_DiffuseColor;
    
    	vertices[1].position.x = ex;
    	vertices[1].position.y = sy;
    	vertices[1].position.z = 0.0f;
    	vertices[1].texCoord = TTextureCoordinate(eu, sv);
    	vertices[1].diffuse = m_DiffuseColor;
    
    	vertices[2].position.x = sx;
    	vertices[2].position.y = ey;
    	vertices[2].position.z = 0.0f;
    	vertices[2].texCoord = TTextureCoordinate(su, ev);
    	vertices[2].diffuse = m_DiffuseColor;
    
    	vertices[3].position.x = ex;
    	vertices[3].position.y = ey;
    	vertices[3].position.z = 0.0f;
    	vertices[3].texCoord = TTextureCoordinate(eu, ev);
    	vertices[3].diffuse = m_DiffuseColor;
    
    	// Now we are going to apply a transformation matrix that will
    	// rotate/scale/transpose the vertices to the correct position.
    	//
    	// We can do it 2 different ways, either set up a world transform
    	// so the GPU will do this for us for every vertex we stream to it,
    	// or we can directly apply it here CPU side.
    	// Since we have very few (4) vertices its better to do it here 
    	// since changing device states comes with some overhead, so its 
    	// better to do that only when its absolutely necessary or when
    	// we can spare some cpu cycles by sending the work to the GPU.
    
    	D3DXMATRIX transform, mTemp;
    	D3DXMatrixScaling(&transform, m_v2Scale.x, m_v2Scale.y, 1.f);
    	D3DXMatrixTranslation(&mTemp, -(ex - sx) / 2.f, -(ey - sy) / 2.f, 0.f);
    	transform *= mTemp;
    	D3DXMatrixRotationZ(&mTemp, D3DXToRadian(m_fRotation));
    	transform *= mTemp;
    	D3DXMatrixTranslation(&mTemp, (ex - sx) / 2.f, (ey - sy) / 2.f, 0.f);
    	transform *= mTemp;
    	D3DXMatrixTranslation(&mTemp, m_v2Position.x - m_RenderingRect.left, m_v2Position.y - m_RenderingRect.top, 0.f);
    	transform *= mTemp;
    
    	for (size_t i = 0; i < 4; ++i)
    	{
    		const D3DXVECTOR2 pos(vertices[i].position.x, vertices[i].position.y);
    		D3DXVECTOR4 out;
    		D3DXVec2Transform(&out, &pos, &transform);
    		vertices[i].position.x = out.x - 0.5f;
    		vertices[i].position.y = out.y - 0.5f;
    	}
    
    	switch (m_iRenderingMode)
    	{
    	case RENDERING_MODE_SCREEN:
    	case RENDERING_MODE_COLOR_DODGE:
    		STATEMANAGER.SaveRenderState(D3DRS_SRCBLEND, D3DBLEND_INVDESTCOLOR);
    		STATEMANAGER.SaveRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
    		break;
    	case RENDERING_MODE_MODULATE:
    		STATEMANAGER.SaveRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
    		STATEMANAGER.SaveRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);
    		break;
    	}
    
    	// Since dx8 doesn't have clipping, we have to apply a bit of trickery here.
    	// We are going to use stenciling. This requires to have space for stencil
    	// information in the depth buffer, so you will need to ask the game to
    	// create an appropriate depth buffer, by changing m_dwMinStencilBits to 1
    	// in D3D_CDeviceInfo::FindDepthStencilFormat.
    	// Stencil buffer will store a custom value on every position you render to
    	// and you can later compare it when rendering something else to drop specific
    	// pixels to achieve various effects.
    	// With this method it is possible to later extend the functionality since 
    	// you can basically clip any shape not just rectangle like with dx9 clipping.
    	// This can be useful when your parent window is a rotated exImageBox and you
    	// want to clip inside it.
    	if (rMask)
    	{
    		// First of all we will draw a rectangle equal to the size of the parent
    		// window's size. We don't need any texture pos or colour, so SPVertex
    		// struct would be enough but I cba creating funcs for it so just go with
    		// this one.
    
    		TPDTVertex maskVertices[4];
    		maskVertices[0].position.x = float(rMask->left) - 0.5f;
    		maskVertices[0].position.y = float(rMask->top) - 0.5f;
    		maskVertices[0].position.z = 0.0f;
    
    		maskVertices[1].position.x = float(rMask->right) - 0.5f;
    		maskVertices[1].position.y = float(rMask->top) - 0.5f;
    		maskVertices[1].position.z = 0.0f;
    
    		maskVertices[2].position.x = float(rMask->left) - 0.5f;
    		maskVertices[2].position.y = float(rMask->bottom) - 0.5f;
    		maskVertices[2].position.z = 0.0f;
    
    		maskVertices[3].position.x = float(rMask->right) - 0.5f;
    		maskVertices[3].position.y = float(rMask->bottom) - 0.5f;
    		maskVertices[3].position.z = 0.0f;
    
    		// Normally nothing else uses stencil in the base game, but we will have to
    		// clear the other stuff we set up in previous renders.
    		STATEMANAGER.GetDevice()->Clear(0, NULL, D3DCLEAR_STENCIL, 0, 0.f, 0);
    
    		// Now enable stenciling, and make it to fail always (so this random rectangle
    		// wont show up in our rendertarget).
    		const DWORD zWrite = STATEMANAGER.GetRenderState(D3DRS_ZWRITEENABLE);
    		STATEMANAGER.SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
    		STATEMANAGER.SaveRenderState(D3DRS_STENCILENABLE, TRUE);
    #ifdef DEBUG_CLIPMASK
    		maskVertices[0].diffuse = maskVertices[1].diffuse = maskVertices[2].diffuse = maskVertices[3].diffuse = 0x5F00FF00;
    		STATEMANAGER.SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
    		STATEMANAGER.SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
    #else
    		STATEMANAGER.SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_REPLACE);
    		STATEMANAGER.SetRenderState(D3DRS_STENCILFUNC, D3DCMP_NEVER);
    #endif
    		STATEMANAGER.SetRenderState(D3DRS_STENCILREF, 1);
    
    		// And now just render it just as a normal image.
    		CGraphicBase::SetPDTStream(maskVertices, 4);
    		CGraphicBase::SetDefaultIndexBuffer(CGraphicBase::DEFAULT_IB_FILL_RECT);
    
    		STATEMANAGER.SetTexture(0, NULL);
    		STATEMANAGER.SetTexture(1, NULL);
    		STATEMANAGER.SetVertexShader(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);
    		STATEMANAGER.DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 4, 0, 2);
    
    		// After the render set up the stencil to only succeed on pixels we wrote 1 to.
    #ifdef DEBUG_CLIPMASK
    		STATEMANAGER.SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
    #else
    		STATEMANAGER.SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);
    #endif
    		STATEMANAGER.SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
    
    		STATEMANAGER.SetRenderState(D3DRS_ZWRITEENABLE, zWrite);
    	}
    
    	// set up min/magfilter for scaling, instantly restore mipfilter, we don't need mips
    	DWORD minF, magF, mipF;
    	STATEMANAGER.GetTextureStageState(0, D3DTSS_MINFILTER, &minF);
    	STATEMANAGER.GetTextureStageState(0, D3DTSS_MAGFILTER, &magF);
    	STATEMANAGER.GetTextureStageState(0, D3DTSS_MIPFILTER, &mipF);
    	STATEMANAGER.SetBestFiltering(0);
    	STATEMANAGER.SetTextureStageState(0, D3DTSS_MIPFILTER, mipF);
    
    	// 2004.11.18.myevan.ctrl+alt+del ąÝşą »çżë˝Ă ƨ±â´Â ą®Á¦ 	
    	if (CGraphicBase::SetPDTStream(vertices, 4))
    	{
    		CGraphicBase::SetDefaultIndexBuffer(CGraphicBase::DEFAULT_IB_FILL_RECT);
    
    		STATEMANAGER.SetTexture(0, pTexture->GetD3DTexture());
    		STATEMANAGER.SetTexture(1, NULL);
    		STATEMANAGER.SetVertexShader(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);
    		STATEMANAGER.DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 4, 0, 2);
    	}
    	//STATEMANAGER.DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, 4, 2, c_FillRectIndices, D3DFMT_INDEX16, vertices, sizeof(TPDTVertex));
    	/////////////////////////////////////////////////////////////
    
    	// reset filters
    	STATEMANAGER.SetTextureStageState(0, D3DTSS_MINFILTER, minF);
    	STATEMANAGER.SetTextureStageState(0, D3DTSS_MAGFILTER, magF);
    
    	// And finally disable stenciling again.
    	if (rMask)
    		STATEMANAGER.RestoreRenderState(D3DRS_STENCILENABLE);
    
    	switch (m_iRenderingMode)
    	{
    	case RENDERING_MODE_SCREEN:
    	case RENDERING_MODE_COLOR_DODGE:
    	case RENDERING_MODE_MODULATE:
    		STATEMANAGER.RestoreRenderState(D3DRS_SRCBLEND);
    		STATEMANAGER.RestoreRenderState(D3DRS_DESTBLEND);
    		break;
    	}
    }

    In StateManager.h add this function as public to the CStateManager class:

    LPDIRECT3DDEVICE8 GetDevice() { return m_lpD3DDev; }

    And last in GrpDetector.cpp search for m_dwMinStencilBits and set it to 1.

     

    • Scream 1
    • Love 12
  8. I have limited knowledge of freebsd and network security, but most of the time what ppl do is create a whitelist for the gameserver who can connect to it. By this I mean that on clientside they silently connect or send a request to a different server and they solve some kind of quiz to validate that it is actually a client on this ip, and then they update the whitelist on the firewall side. Now there are lots of room to play, like this validator server could be on a different machine or just listen on a different ip, there could be many other validators, etc.

    This would solve many problem, like you mentioned normally the bottleneck is the application itself like auth which gets overrun by connection requests and then ends up in a spiral of death rendering the application totally irresponsive. Now I believe that the freebsd kernel (and probably most of the os kernels) is very effective dropping unwanted packets so this type of attacks would have little to no noticeable affects on your network/machine. I've seen relatively big botnets fail against this method. Obviously if the attack is so large that the kernel (more specifically the firewall) becomes the bottleneck and your hardware cannot keep up, or your network bandwidth becomes limited because of the amount of data you receive this wont work. In these cases probably nothing you can do anyway and you would require to have an industrial grade hardware firewall instance before your machine (or if you do have it like for example at ovh they do have it afaik you just need control over it).

    Now we can argue how effective is this extra validation method. To be able to manipulate the quiz you have to reverse engineer the client which is already a relatively difficult task and you can make it harder with virtualisations. After that you have to implement this to the botnet itself which is already a problem for some of the botnets, like you dont have that fine grade of control over it. Now you can say that you will just target the validators, this indeed could work but you can anytime add more, change validation method, etc. Also this would only mean that new ips wouldnt be able to connect to your server which would affect relatively few players, because lets say you allow the ips for 48 hours to be whitelisted. The majority of the players log in every day or at least every few days, so they would be fine.

    Like Shogun said its about counter measures, counter counter measures and so on, its just about how patient the attacker is. In general this method is more than enough for a metin server.

    Now about how to implement it: I don't know, never configured any firewalls, never wrote any rules and don't know what tools you have for example for pf on freebsd. Like I said, this is not my field of expertise so feel free to correct me if I'm just talking bullshit now.

    • Metin2 Dev 1
    • Love 1
  9. 7 hours ago, WeedHex said:

    @masodikbela I don't buy random things and I can't use time to make a wiki system using bad based. Anyway  I'll take a look, thanks!

    I meant "don't get scammed by random kids trying to sell public stuff for money" by "don't get scammed by randoms". Because in the early days of the leak I saw many trying to do that with the wiki.

    • Good 1
  10. 21 hours ago, TMP4 said:

    I'm not 100% sure but that happens when your item_proto's row count changed, ex. you added or removed an item.

    But that's a legacy and independent bug, not caused by Ulthar's or masodikbela's release.

    This is because every CItem holds a pointer to the original proto vector (in TItemTable const * m_pProto), which if changes due to relocation or just reordering because of itemID change would obviously cause trouble.

    • Good 2
  11. Very well organized article, good examples and followable thinking process. Rare to see the whats whys and hows answered in one place. Been a while since I had such a good reading from the metin community. Also really appreciate that you didn't post any ready to use implementation with it. This is how every topic should look like.

    • Metin2 Dev 1
    • Love 1
  12. Can you clarify your goal a little bit? So first of all you must know that by default the client will only be able to display characters from the selected codepage (and ofc these characters must be present in the font aswell). What it means that the client will always threat 1 byte of string as a single representable character, while a unicode codepoint can take up to 4 bytes of string (in utf8).

    If you just want to be able to login even tho its not displayed correctly, you can just use for example boost locale (boost::locale::conv::to_utf) with the codepage used on the string, to get a valid unicode string, and then send it to the server for comparison (note that you might have to do normalization on the string to make sure its in the same normalization form as the saved text in the database (in this case probably NFD would be a better choice because we want an exact match of codepoints used and not the physical result of the composed character)). This can be done with utf8proc for example.

    If you want to display characters correctly aswell, then you will have to completely rework the TextLine class and use sophisticated typography processors like uniscribe or directwrite(dx10+) or harfbuzz+fribidi+freetype.

    • Good 1
  13. 3 hours ago, 3bd0 said:

    Sorry maybe I didn't explain myself clearly. I was asking about how to pass the information inside *args back to the callback.

    spacer.png

    So in the function above, it's saving a reference to event (which is the function), but *args is just lost. so when self.eventOverInItem get called later on, it will be missing the arguments that were provided in *args. Or Am I missing something?

    You can just save them like self.eventOverInItemArgs = args, and then later call the event with apply(self.eventOverInItem, self.eventOverInItemArgs). That will call the saved event with the passed arguments.

  14. 23 hours ago, 3bd0 said:

    Apologies if it's a stupid question, python is not my thing. But when I refactor it to *args, how do I then pass those arguments to the function? 0, row, j  and captured by *args, but then what? should I just save them to a variable in SetOverInItemEvent and unpack them when the event is triggered? Or is there an easier way of doing it?

    https://book.pythontips.com/en/latest/args_and_kwargs.html Its explained really well here, basically its when you want to pass different amount of variables you can just use *args as last argument in the function definition catching any remaining variables. It will be a tuple, either with 0 or more than 0 elements.

  15. On 5/14/2021 at 12:29 PM, Sbonkers said:

    So, basically, you're telling me that a MMO server should handle autonomously all the game mechanics using UDP, while TCP should be reserved only for things like item shop and chat system? Concerning multithreaded code, this isn't an issue at all, given my own experience and Rust compile time safety checks. Lastly, I've read something about protobuf, and as far as I can tell, this kind of things is already implemented in a few well known crates for rust, so neither this one should be an issue.

    Well as far as I know its not really a good practice to use UDP and TCP together (its better to use only one) except if it really make sense. In our case the main connection would be the UDP one, so we should somehow find and bind the TCP connection to the same stuff in order to keep a track to our main character, which is necessary for example handling normal chat packet (need position to only send that to the nearby players).

    On 5/14/2021 at 12:29 PM, Sbonkers said:

    Now, let's tackle the main problem, Metin2 server knowledge. Do you have any suggestions on where should i go/what should I do in order to gain some expertise on it?

    Well its always hard to start these kind of things and there is not much help I can provide for this. Maybe the best way to do it is just to start right away coding something. It could be an already public system or anything, the key is to have a goal, like "whenever I kill a monster I want the server to give me an item and write me something in the chat". Then you just split it to subtask like:

    • How can I catch if a character dies?
    • How do I know it was killed by a player?
    • How do I know the dead character was a monster?
    • How do I give item to a character?
    • How do I send a chat message?

    And by doing this you can already see various parts of the server, including CHARACTER class, ITEM class, some netcode and many others. Oviously the more complicated the idea the more parts of the server you can explore that way.

    • Love 2
  16. On 5/6/2021 at 11:37 AM, Sbonkers said:

    to know where to move the first steps to understand how a Metin2 server works, what are the main design errors that you think have been made and what you would expect from a server designed for modern needs

    Huhh thats a little bit too much to ask. Back in its time the solutions they made for various problems were acceptable due to the hardware limits, but nowadays the whole server is utterly trash. The question is more like what is okay for modern needs.

    But to be a little more specific, here is some of the most annoying problems:

    • No serverside checks for some critical game mechanics. There are numerous stuff that depends on the client, and therefore can be easily faked. For example when you attack, the client calculates the positions your attack would hit, and there is only a distance check on serverside if you really hit that entity. There is no other kind of checks, like combo, direction, or more fine tuned stuff. Nowadays it should be completely independent from the client, the server should determine every attack/movement, and the client should just receive the results.
    • No standard packet protocol. The game uses some homemade messy hardcoded tcp network code, where the headers/packets can be messed up easily. Something like google protobuf should solve this problem. Also due to the use of tcp there are noticable delays in movement/attacks so that should be replaced with reliable udp aswell.
    • The cores are single threaded (except the mysql working threads) and to spread the load across the cpu multiple cores are being used (so it means that best case you have one map / cpu core (which obviously very rare in production servers)). But on farmmaps especially on server openings there are large amount of players and sometimes it noticeably draws performance, and literally nothing you can do about it, since you cannot make it use more cpu threads. Because of this higher cpu clock speed is usually prefered over more cpu threads when selecting hardwares for metin servers.
    • Good 1
    • Love 1
    • Love 1
  17. 1 minute ago, Jordan said:

    Thank you!
    Is that safe to use on a live server too or only recommended on a dev server?

    Except /reload p (because it reloads the protos too) its safe to use on live server, if you don't have any new system or adjustment related to those stuff (so considering you use the basic files its safe).

    • Good 1
  18. 3 hours ago, Shahin said:

    So the easiest solution which is also not conflicting with the other time functions on update would be the :

    On 3/6/2021 at 1:34 AM, masodikbela said:
    • Calling effect update even when the client is minimized. This would make the client use somewhat more resource when its minimized, but would still use way more less resource compared to the "fix" in that topic.

    ?

    Yes, the low-effort but working solution is this indeed.

     

    3 hours ago, Shahin said:

    I am still benchmarking the "fix" received by @CHKRZ, but i think on a lower spec pc would run players crazy with the usage. I have 50% Gpu utilization with only one client minimized, 80% with tree clients minimized, on a i7 6700k integraded GPU "HD 630" (which is a high graded integrated PC Gpu, so the difference is huge when we talk about laptop i3/i5 integrated gpu users). 

    I am taking integrated GPUs as a measurement because 70% of players are using a laptop, and most of them have only integrated GPUs.

     

    The base idea is to Optimize, not to use more performance to fix a minor problem. (i'm still talking about the above fix)

    The difference between my suggestion and the "fix" from the other topic is that the latter just masks the problem making the client call render even if the client is minimized, so it uses all the functions it normally does, wasting the resources on something that nobody can see. The former one just calls the effect update which is a cpu-only task, and while normally its one of the performance bottlenecks, still doesn't use much resources on its own (without the other render calls).

     

    3 hours ago, Shahin said:

    So, what would be your suggestion as the best solution, and if you can walk me through it.

    Well not sure what else can I add here, the lowest effort but working solution is the one you asked about. I won't write the code for you and basically I told everything you need for that. Its literally pasting one single line which updates the effects to the place where the game should render, but skips it only because the client is minimized.


    So here are your clues, its now time to split up and investigate.
    sherlock holmes hello GIF by Boomerang Official

    • Love 2
  19. The content in that topic can be visualized in a single gif:
    Water Fix It GIF by Boomerang Official

    The problem with the "speedy effects" after maximizing a minimized client lies here:
    x9rUMIv.png
    As you can see the elapsed time is measured since... well... the last time the effect got updated. You might say that hey, thats ok... well it would be, but if you take a look at the messy game loop inside PythonApplication.cpp you will notice that the call for effect updates is only present in the RenderGame function, which is a render function as its name implies it, and its only called when the game is rendering. And since the game doesn't render anything if its minimized, the time for the effects basically stops, and when the game starts rendering again, it will resume it from that point when it got minimized.

    You might rightfully say that okay, but that doesn't explain the reason why is it "speedy" for a long time, since the time difference should be huge for only one call, then it should return to normal because the m_fLastTime got updated at the end of the function. And at this point I'm officially out of definitive answers.

     

    The real reason might be one of the following:

    • On the first "big" time difference after maximizing the client destabilizes the effect instance, messing up the emission/decorator update on a weird way, making the following n updates unstable as well.
    • The custom timer class is more than weird, it has a "real time" mode and a "custom time" mode, but as I see only the "custom time" mode is used, which works like advancing the timer every time the main loop is called. Since the advancing is always called (its in the update part, not the render part) I somewhat doubt that this is the real reason. Using a different timer (potentially std::chrono) here in the effect update would quickly evaluate this case.

    In short, fixing it could be done by:

    • Using a maximum value for the elapsed time (like it cant be larger than 1/60). This would solve the "speedy" issue, but would cause another bug, making some effects stay longer alive (if you minimize the client).
    • Calling effect update even when the client is minimized. This would make the client use somewhat more resource when its minimized, but would still use way more less resource compared to the "fix" in that topic.
    • Digging a bit more deeper, finding more accurate solutions. (The effects are stateless, or at least it really seems so, so it should mean that the effect should work with any given "elapsed time" at any point, but like I said this might be violated somewhere. The effects are not stateless, since the particle positions are based on the last position + elapsedTime * acceleration.)
    • Love 6
×
×
  • 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.