Jump to content

Intel

Premium
  • Posts

    200
  • Joined

  • Last visited

  • Days Won

    4
  • Feedback

    0%

Everything posted by Intel

  1. Yes, it depends on something: When converted to ANSI: which is basically broken, and we can prove it by choosing EUC-KR: and considering the repetition of the characters '占쏙옙', we can say, yeah, the text is fudged. You gotta have a backup to fix it.
  2. UniversalElements has been one of the game changers in the Italian scene regarding innovation in metin2 for a long time, from GameFilesOpen when @ Luscha , my co-worker, was just making maps at the time, then 2014, when we were one of, if not the first server with an offline private shop. We've been working on Metin2 source code stuff since then. In the years, we sold a few systems of our own to some other people (arenas, specialization skills, achievements, a different pet system, rejoin dungeon) and now we wanna extend our services to more. Before going further, a few things first: The systems we are selling were and ARE designed and developed to be used in OUR server first. All you see has been created to be well designed, well written, configurable and bug free. We know the prices can sound higher than others, but what we are selling are highly engineered systems and we believe in quality vs quantity. Most of the time you won't receive only a system but a tool set to expand our design further, meeting any kind of expectation and needs. If you do not use git or something similar, please create a repository with your files server (I will assist you creating one, I am not going to work on notepad++ and backup folders). I will give you access to a repository when/where I'd upload updates to the files (in case of systems with new files to add) or add the edits to be made to the files that concern the systems bought. Prices are based on: Installation time and difficulty Complexity of the code (design and technical) The fact that the systems are being tested on a test server for more than 3 years now + most of them have been tested on live server with additional bug fixes My releases here: Our other works: I could go on and on with more technical stuff, but I won't waste anymore of your time. SYSTEMS Statistics System 2000€ There is NO in game interface. The system is NOT INTENDED to send data to the client but to be shown on your website (think of it like op.gg) PvP Arenas 1500€ Party Finder 800€ Items Affect (Blend Renewal) 800€ Multiple Item Prices (Shop-Ex Renewal2) 300€ Persistent Settings 200€ The system uses nlohman json library. If you use Marty's files and you are already using RapidJSON I can work with that as well. C++11 minimum required. The system also requires the boost library on the server and the client (boost::variant/boost::static_visitor/boost::alghoritm)if for some reason you removed it Sync Position refactor + monitoring (anti cheat) (WIP/Testing) 7000€-10000€(with monitoring) New Abilities' Tooltip 300€ WITH THIS SYSTEM THERE WILL BE NO #DEFINE INCLUDED. THE SKILL_PROTO FILE LOADER AND EVERYTHING RELATED WILL BE OVERWRITTEN. THIS SYSTEM REQUIRES THE BOOST LIBRARY FOR THE CLIENT Reward Enabler (multifarm block) 300/800€ The system requires you to have a way to link the CHARACTER's DESC to some sort of hardware/unique ID (if you have a hardware ban system, it may be present). I can provide one (400€) which includes: hard disk partition's fix (another's HD/HD's partition won't register as a new PC): possibility to add a new option to accept/decline other PCs. Basically, if another person tries to log into your account, they get blocked until you approve access to it. The website part to allow this, is up to your webmaster, not me. It also requires to have C++20 (c++2a) [serverside] Item's deathtime (Promotional items) 400€ Fake Clocks (stop using notices in dungeons) 30€ Improved Damage calculation 2000€ WITH THIS SYSTEM THERE WILL BE NO #DEFINE INCLUDED. EVERYTHING ABOUT DAMAGE CALCULATION WILL BE OVERWRITTEN. Skill System 6000€ (Doc/SQL base file WIP) WITH THIS SYSTEM THERE WILL BE NO #DEFINE INCLUDED. THE SKILL_PROTO AND EVERYTHING RELATED WILL BE OVERWRITTEN This system needs the install of an external lib (pcg_random). GCC MUST be able to enable C++11 Seasons System 1100€ (WIP) Smooth scrolling (PeekWindow/OverflowHidden/ClipMasking) 4000€ WITH THIS SYSTEM THERE WILL BE NO #DEFINE INCLUDED. THE UI/WINDOW FILES AND EVERYTHING RELATED WILL BE OVERWRITTEN. __________________________________________________________________________________________________ I will give free support for small bugfixes or edits. Just remember, like every other normal human being, I cannot be available 24/7 considering job/studies/life/possible future nuclear war. I won't give free support if you touch and break something and the game starts changing the Earth's rotation speed. I will charge you if the bug won't depend on me or my systems. Payments are usually 20% before starting, and the rest after. PayPal only (friends & family). No shitcoins/cryptocoins/memecoins/schizophreniccoins (you get it). __________________________________________________________________________________________________ How to contact me: My discord is denebx128 but first contact me HERE saying like "hey, I am darcraftangel#6969 on discord for a system" otherwise I won't accept random requests. I am not gonna use skype (it's the first thing I completely remove from a Windows install)
  3. You need to reset all the changes made. You broke the encoding of the files. If you want to not have fun, you can restore the files, translate all the Korean stuff in English (no encoding issues for sure), run the build_locale_string.py inside the game/src folder and start translating in your own language line by line. merge_locale_string.py will then make you the new locale_string.txt build_locale_string.py #!/usr/local/bin/python import sys import glob def IsHangulInLine(line): for ch in line: if ord(ch) >= 128: return True return False hanList = [] for fileName in glob.glob("*.cpp"): isComment = False lines = open(fileName).readlines() for line in lines: linePos = lines.index(line) if isComment: commentEnd = line.find("*/") if commentEnd < 0: continue else: line = line[commentEnd+2:] isComment = False else: commentBegin = line.find("/*") if commentBegin >= 0: commentEnd = line.find("*/") if commentEnd >= 0: line = line[:commentBegin] + line[commentEnd+2:] else: isComment = True while True: pos = line.find("TEXT") if pos < 0: break if len(line) < pos + 5: break if line[pos+4] != "(": break pos += 5 if line[pos] != '"': break endPos = line[pos+1:].find('"') if endPos < 0: raise endPos += pos endPos += 2 han = line[pos+1:endPos-1] if not han in hanList: hanList.append(han) line = line[endPos:] out = open("locale_string.txt", "w") for han in hanList: out.write("%s\n" % (han)) print han merge_locale_string.py #!/usr/local/bin/python import sys def ReadLocaleLines(fileName): dstLines = [] srcLines = open(fileName).readlines() for srcLine in srcLines: if srcLine[-2:] == "\r\n": srcLine = srcLine[:-2] elif srcLine[-1:] == "\n": srcLine = srcLine[:-1] dstLines.append(srcLine) return dstLines if len(sys.argv) != 3: print "usage:" print "%s [han_file_name] [locale_file_name]" % (sys.argv[0]) sys.exit() srcList = ReadLocaleLines(sys.argv[1]) dstList = ReadLocaleLines(sys.argv[2]) outList = [] for (srcLine, dstLine) in zip(srcList, dstList): outList.append('"%s";'% (srcLine)) outList.append('"%s";'% (dstLine)) outList.append('') outFile = open("locale_string_out.txt", "w") outFile.write("\n".join(outList)) Or open the files with the encoding EUC-KR, but you still might have encoding issues if you use special symbols/letters of some latin based language. Otherwise ANSI (if I am not mistaken, I haven't needed to be bothered by it for 3+ years now)
  4. I get it, but this was just a proof of concept and float's been used for testing purposes. Obviously it can always be polished.
  5. Yes, I've written how we've theorized about the packet size and old internet connection. Regarding the floats, you could always multiply by 10000 and dividing by 10000 when you need the float value again.
  6. DISCLAIMER: maybe one or two people will recognize this topic from a different community, saying "hey, you are not the original author". I am not, but I am a co-worker of his and have the permission to do so. This topic wants to be a study case of one of the most basic mechanics in Metin: autoattacks and the in game entities' sync position. The discussion will be divided like this: 1. Analysis on the idea and implementation of the synchronization between clients and management of the attacks 1.1 Design problems and bugs in the code 1.2 Consideration about some derived "mechanics" (fly, rolling dagger skill) 2. Challenges faced when working on an highly customized revision of Metin2 3. My approach on how to fix the missing implementation done in 2009 3.1 Result 3.2 Benefits 4. Possible expansions Chapter 1: Movements and Attacks on Metin2 Metin gives charge to the entire flow logic of attacks and synchronization of the clients, to the client itself. The server is merely there and rarely intervenes to validate and force the position of the players at regular intervals (so the the clients actually know the positions of the players and not at random coords). Every client is notified of basic info, such as "I am 123 and I started attacking in this direction" or "I am 123 and I hit 456" or yet "I am 456, I am standing still, here" and every client executes, locally, with his infos on the state of the game (such as 123 was at coords x,y whereas 456 was at coords x2,y2) the animations and displacements, modifying the local state of the characters (after the hit, 456 will be at the coords x3,y3 whereas 123 moved to coords x+1,y+1). Sometimes, precisely 300ms, every client sends the server his state and the server synchronizes the other clients to have something similar to a stable and solid playable game. So, here a first overview of the logic Metin2 has been using for more than 15 years: 123's client starts wielding the sword and sends to the server the HEADER_CG_CHARACTER_MOVE with argument FUNC_COMBO Server broadcasts the packet to all the nearby clients All the clients receive the packet and show 123 wielding the sword 123's client hits 456 pushing it down on a 10m distance and sends the HEADER_CG_ATTACK packet to the server, saying it hit 456 to then process the dmg During this step, the server will give "ownership" to 123 of 456 (establishing that 123 is attacking 456) It will send all the clients the info that 123 is the only player that can push and edit the position of 456 123's client will gain the coords at which 456 will arrive after standing up and keeps them in memory Meanwhile, in 456's client, 123 eventually will hit 456 and it will push him down on a 10m distance After a maximum of 300ms, 123's client will send the position of all the entities it has "ownership" to the server, with the HEADER_CG_SYNC_POSITION packet, making few controls and broadcast the new position to all the clients, even 456's All the clients will pull the characters involved to the indicated coords, which can be corrected or not This process starts forcing the movement and eventually the KNOCK_BACK of the characters to the coords indicated by 123 Eventually, when 456 ends the STANDUP animation, the client will send the HEADER_CG_CHARACTER_MOVE packet with argument FUNC_WAIT communicating again the definitive position of 456 to all the clients This flow has the advantage that all the clients process locally all the in-between actions, therefore it looks "fast paced", just because the server rarely intervenes and supposedly the clients start from the same conditions and they arrive at the same results. One of the biggest con, though, is that the server has very little info about the real state of the game, therefore it can do very little to do something about malicious declaration by one client. Let's get more into it. Chapter 1.1: Design problems and bugs in the code First critic by the system designed by YMIR is that, as already said above, the server has not much information on the state of the clients. I wanna point out: The server only knows the initial position of the players at the start of the flow: before 123 attacks and 456 gets pushed The server will be notified about the new position of 123 and 456 only at the end of the HEADER_CG_SYNC_POSITION packet, which will immediately move the character to the indicated coords Everything that happens in-between these 2 moments is unknown to the server and can't perform any meaningful control on the attacks or coords where 456 gets pushed For example, the server could see 456 to the initial coords, but on the client's side it already moved 4 meters away from the third sword hit and when the final knock back arrives, the server will see a potential illegal movement (third sword hit + fourth) The server, during this process, can only see two states: initial and final, making it completely stupid. Taking aside the security flaw - that we saw paid very well during these years - an acceptable solution would be if the process would be consistent and functional. Unfortunately, it's not like that, at all. The first problem that it inevitably occurs is that, if for some reason, the initial states of the clients are not 100% the same, they will compute different movements, animations and angles. In this picture we can see how a miniscule difference on the B's initial position can change its final destination, rendering the interaction between A and B, kind of interesting, where you can see getting damaged while thinking to be safe. Despite thinking this is a remote situation, it's actually pretty common. Remember that first, HEADER_CG_CHARACTER_MOVE packet is sent first, with argument FUNC_COMBO to communicate the beginning of a combo and eventually further attacks and the new position. If the characters are very close to one another, the attacker might send HEADER_CG_CHARACTER_MOVE, HEADER_CG_ATTACK and HEADER_CG_SYNC_POSITION before that who gets hit even started the attack animation, forcing a little movement and a KOCK_BACK before the sword hits the character in the victim's client. It's the inevitable that until the attacker doesn't send HEADER_CG_SYNC_POSITION, the states that the players see could differ massively and when it actually arrives to the server to align everything, the players find themselves sucked, pushed or slid in positions where they didn't think to be, giving the PvP that Metin2's touch we all (don't) love. More players interact with each other, the more the imprecision rises and the more drastic the forced movements have to be made to reset the synchrony. To make the clients reactive, the developers decided to force the movements and the KNOCK_BACKs before the packet HEADER_CG_SYNC_POSITION is sent and broadcasted: In the function void CPythonPlayerEventHandler::OnHit(UINT uSkill, CActorInstance& rkActorVictim, BOOL isSendPacket) the synchronization is forced with rkActorVictim.TEMP_Push(kVictim.m_lPixelX, kVictim.m_lPixelY); All good for now, if it wasn't for the fact that the coords synchronized are obtained from rkActorVictim.NEW_GetLastPixelPositionRef a function, which calls GetBlendingPosition that does a simple thing: void CActorInstance::GetBlendingPosition(TPixelPosition * pPosition) { if (m_PhysicsObject.isBlending()) { m_PhysicsObject.GetLastPosition(pPosition); pPosition->x += m_x; pPosition->y += m_y; pPosition->z += m_z; } else { pPosition->x = m_x; pPosition->y = m_y; pPosition->z = m_z; } } Basically, it returns the current coords if the character is standing still, or the current coords + m_PhysicsObject.GetLastPosition(pPosition); if the character is getting pushed. This mystic class called CPhysicsObject is responsible of the movements that occur on Metin. It gets told how much an entity is pushed, in what direction and the time of it, to then move the entity. The bug I found was that using m_PhysicsObject.GetLastPosition(pPosition), which returns the whole movement that the entity has to make, GetBlendingPosition doesn't return the final position of the entity, but the current coords + the movement. Despite this looks correct, it isn't when this function is called after the entity moved a little. In that case the returned value will be a little bit further of the destination initially expected. Chapter: Consideration on some derived mechanics (fly, rolling dagger skill) When eventually the positions between the two clients are synchronized, the function that processes the packet HEADER_CG_SYNC_POSITION void CActorInstance::__Push(int x, int y) { if (IsResistFallen()) return; const D3DXVECTOR3& c_rv3Src=GetPosition(); const D3DXVECTOR3 c_v3Dst=D3DXVECTOR3(x, -y, c_rv3Src.z); const D3DXVECTOR3 c_v3Delta=c_v3Dst-c_rv3Src; const int LoopValue = 100; const D3DXVECTOR3 inc=c_v3Delta / LoopValue; D3DXVECTOR3 v3Movement(0.0f, 0.0f, 0.0f); IPhysicsWorld* pWorld = IPhysicsWorld::GetPhysicsWorld(); if (!pWorld) { return; } for(int i = 0; i < LoopValue; ++i) { if (pWorld->isPhysicalCollision(c_rv3Src + v3Movement)) { ResetBlendingPosition(); return; } v3Movement += inc; } SetBlendingPosition(c_v3Dst); const TPixelPosition& kPPosLast2 = NEW_GetLastPixelPositionRef(); if (!IsUsingSkill()) { int len=sqrt(c_v3Delta.x*c_v3Delta.x+c_v3Delta.y*c_v3Delta.y); if (len>150.0f) { InterceptOnceMotion(CRaceMotionData::NAME_DAMAGE_FLYING); PushOnceMotion(CRaceMotionData::NAME_STAND_UP); } } } uses the function void SetBlendingPosition(const TPixelPosition & c_rPosition, float fBlendingTime = 1.0f) which internally says to the client to make a movement and distribute it on a second timespan. During this time, the character enters a state - __IsSyncing() - in which it cannot move at all if not using an ability. This creates that phenomenon called "Fly", where the character slowly slides and can't either move or attack. Basically the Fly is the attempt, from the client, to synchronize his state with other clients. This forces the character in a slow movement in his position and assures that the state can't be changed, blocking every action. If you want my opinion, it's ok for a sword hit to block a character, to give the possibility to perform a physical combo. This is though a mechanic that should be controlled by the developers who, for example, put half a second of "stun" to the hits instead of being a result of the synchronization process with the other clients. More fascinating is the bug with the function GetBlendingPosition: during the execution of the Rolling Dagger skill, if the player is already in a state of IsPushing (it's getting pushed by the fourth sword hit) during the execution of the function OnHit(UINT uSkill, CActorInstance& rkActorVictim, BOOL isSendPacket) a new TEMP_Push will be performed, with the coordinates that, presumably, represent the point of arrival of the character. If this is true for the first hit of the skill, during the second, the victim will already be moved a little further. At this point, the coords returned by GetBlendingPosition are not the ones of the fourth sword hit but that plus something. The player will be then moved a little bit further and another KNOCK_BACK animation is triggered. I don't know if the developers deliberately introduced this dynamic, or if it was a happy accident, but once again I believe that if an ability like Rolling Dagger can bounce a player, this should be coded with specific parameters (external force? refresh push?), but definitely not with an interaction not so visible and counterintuitive that uses a logic most probably bugged. 2. Challenges found with an highly customized revision of Metin2 If you are people who intend to do significant changes to, maybe the skill system, pvp or want to develop a monitoring system for hacks and cheats server side, the current state makes an hard work even harder. The server cannot contribute much to the management of all the states and this is a problem for all those systems based on the precision of the characters' positions during the interactions of the player with the game. Every attempt to make the client more reactive and precise, clashes inevitably with mechanics born from weird and casual interactions of the entities in the client like the "Fly"'s case or Rolling Dagger's that, while BUGS, are still part of the game for years, therefore they must be preserved or emulated 3. My approach on how to resolve the lacks of the implementation done in 2009 My implementation is strongly linked to UniversalElements files (my server) therefore are not 1:1 applicable to most of the existing servers. I will not post much code (it's not a release) but I will describe how to arrive to a similar result and the logic who brought me to determine my choices. Briefly, short said it will be the following: Expansion of the packet HEADER_CG_ATTACK to better describe the action Broadcast of HEADER_CG_ATTACK of all the clients in real time, to fix immediately the state server-side state implementation emulating the movements of the entities in real time Usage of HEADER_CG_SYNC_POSITION only to rollback the state in case wrong info sent from a client are present sent (to cancel an illegal action) Fix of GetBlendingPosition These simple edits look foregone, so much that the only answer to the question "why was it not designed like this from the beginning" was that, at the time, the servers were not able to maintain such an updated state and the internet connections were not enough for large packets used to the frequency of the HEADER_CG_ATTACK one. Therefore, the new logic will appear like this: 123's client starts wielding and sends the server the HEADER_CG_CHARACTER_MOVE packet with argument FUNC_COMBO Server broadcasts the packet to all the clients nearby All the clients receive the packet and show 123 wielding the sword 123's client hit 456 pushing it on a distance of 10 and send the HEADER_CG_ATTACK packet to the server, saying it hit 456 so that it can process the dmg The packet contains info like 456's initial position (with double precision to be exact to the millimeter), timestamp of the attack, which part of the combo generated the hit, duration of the movement in ms The server validates the packet HEADER_CG_ATTACK, that processes the damage and broadcasts the information to the clients Furthermore the server initializes the emulation of the movement from the initial coord to the 456 final's one. Meanwhile in 456's client, 123 eventually will hit 456 making it fall at 10m of distance When 456's client receives the HEADER_CG_ATTACK packet broadcasted by 123, it corrects the internal state whit the new on-the-fly info For example if the client already processed the movement and the KNOCK_BACK, the trajectory will be corrected with the coords validated by the server So, the synchronization corrects the local execution of the client and overwrites it (whether or not the packet HEADER_CG_ATTACK arrives before or after the sword hit in the observer's clients) The big difference though, is that the server keeps an updated state of every entity at every attack and during the execution of the movement. It becomes then able to perform checks, edits and corrections on the data coming from the attacker with high precision. To give an idea to the edits, in the server I added a state that can be executed in parallel in FCM.cpp // Update void CFSM::Update() { // Check New State if(m_pNewState) { // Execute End State m_pCurrentState->ExecuteEndState(); // Set New State m_pCurrentState = m_pNewState; m_pNewState = 0; // Execute Begin State m_pCurrentState->ExecuteBeginState(); } // Check New State if(m_pNewConcurrentState) { // Execute End State if (m_pConcurrentState) m_pConcurrentState->ExecuteEndState(); // Set New State m_pConcurrentState = m_pNewConcurrentState; m_pNewConcurrentState = 0; // Execute Begin State m_pConcurrentState->ExecuteBeginState(); } if (bStopConcurrent && m_pConcurrentState) { // Execute End State m_pConcurrentState->ExecuteEndState(); m_pConcurrentState = 0; bStopConcurrent = false; } // Execute State m_pCurrentState->ExecuteState(); if (m_pConcurrentState) { m_pConcurrentState->ExecuteState(); } } This enables me to use a fourth state in the CHARACTER class CHARACTER::CHARACTER() { m_stateIdle.Set(this, &CHARACTER::BeginStateEmpty, &CHARACTER::StateIdle, &CHARACTER::EndStateEmpty); m_stateMove.Set(this, &CHARACTER::BeginStateEmpty, &CHARACTER::StateMove, &CHARACTER::EndStateEmpty); m_stateBattle.Set(this, &CHARACTER::BeginStateEmpty, &CHARACTER::StateBattle, &CHARACTER::EndStateEmpty); m_stateSyncing.Set(this, &CHARACTER::BeginStateEmpty, &CHARACTER::StateSyncing, &CHARACTER::EndStateEmpty); Initialize(); } void CHARACTER::StateSyncing() { if (IsStone() || IsDoor()) { StopConcurrentState(); return; } DWORD dwElapsedTime = get_dword_time() - m_dwSyncStartTime; float fRate = (float) dwElapsedTime / (float) m_dwSyncDuration; if(fRate > 1.0f) fRate = 1.0f; int x = (int) ((float) (m_posDest.x - m_posStart.x) * fRate + m_posStart.x); int y = (int) ((float) (m_posDest.y - m_posStart.y) * fRate + m_posStart.y); Sync(x, y); if(1.0f == fRate) { StopConcurrentState(); } } /////////////////// ////// To use to gradually "move" the entity on the desired position while it can do whatever it wants (to use when receiving HEADER_CG_ATTACK) bool CHARACTER::BlendSync(long x, long y, unsigned int unDuration) { // TODO distance check required // No need to go the same side as the position (automatic success) if(GetX() == x && GetY() == y) return false; m_posDest.x = m_posStart.x = GetX(); m_posDest.y = m_posStart.y = GetY(); m_posDest.x = x; m_posDest.y = y; m_dwSyncStartTime = get_dword_time(); m_dwSyncDuration = unDuration; m_dwStateDuration = 1; ConcurrentState(m_stateSyncing); return true; } The new TPacketCGAttack will present iself like this typedef struct command_attack { BYTE bHeader; BYTE bType; DWORD dwVID; BOOL bPacket; LONG lSX; LONG lSY; LONG lX; LONG lY; float fSyncDestX; float fSyncDestY; DWORD dwBlendDuration; DWORD dwComboMotion; DWORD dwTime; } TPacketCGAttack; with its counter part TPacketGCAttack typedef struct packet_attack { BYTE bHeader; BYTE bType; DWORD dwAttacakerVID; DWORD dwVID; BOOL bPacket; LONG lSX; LONG lSY; LONG lX; LONG lY; float fSyncDestX; float fSyncDestY; DWORD dwBlendDuration; } TPacketGCAttack; Client side I fixed the inherent logic to GetBlendingPosition and all his variants. To resolve it we must pass to the function that initializes it, not only the displacement, but even the final position. void CPhysicsObject::SetLastPosition(const TPixelPosition& c_rPosition, const TPixelPosition& c_rDeltaPosition, float fBlendingTime) { m_v3FinalPosition.x = float(c_rPosition.x + c_rDeltaPosition.x); m_v3FinalPosition.y = float(c_rPosition.y + c_rDeltaPosition.y); m_v3FinalPosition.z = float(c_rPosition.z + c_rDeltaPosition.z); m_v3DeltaPosition.x = float(c_rDeltaPosition.x); m_v3DeltaPosition.y = float(c_rDeltaPosition.y); m_v3DeltaPosition.z = float(c_rDeltaPosition.z); m_xPushingPosition.Setup(0.0f, c_rDeltaPosition.x, fBlendingTime); m_yPushingPosition.Setup(0.0f, c_rDeltaPosition.y, fBlendingTime); } void CPhysicsObject::GetFinalPosition(TPixelPosition* pPosition) { pPosition->x = (m_v3FinalPosition.x); pPosition->y = (m_v3FinalPosition.y); pPosition->z = (m_v3FinalPosition.z); } void CPhysicsObject::GetDeltaPosition(TPixelPosition* pPosition) { pPosition->x = (m_v3DeltaPosition.x); pPosition->y = (m_v3DeltaPosition.y); pPosition->z = (m_v3DeltaPosition.z); } and fixing the function void CActorInstance::GetBlendingPosition(TPixelPosition * pPosition) { if (m_PhysicsObject.isBlending()) { m_PhysicsObject.GetFinalPosition(pPosition); } else { GetPixelPosition(pPosition); } } The management of the new HEADER_CG_CHARACTER_ATTACK will present itself like this: bool CPythonNetworkStream::RecvCharacterAttackPacket() { TPacketGCAttack kPacket; if (!Recv(sizeof(TPacketGCAttack), &kPacket)) { Tracen("CPythonNetworkStream::RecvCharacterAttackPacket - PACKET READ ERROR"); return false; } if (kPacket.lX && kPacket.lY) { __GlobalPositionToLocalPosition(kPacket.lX, kPacket.lY); } __GlobalPositionToLocalPosition(kPacket.lSX, kPacket.lSY); TPixelPosition tSyncPosition = TPixelPosition{ kPacket.fSyncDestX, kPacket.fSyncDestY, 0 }; m_rokNetActorMgr->AttackActor(kPacket.dwVID, kPacket.dwAttacakerVID, kPacket.lX, kPacket.lY, tSyncPosition, kPacket.dwBlendDuration); return true; } void CNetworkActorManager::AttackActor(DWORD dwVID, DWORD dwAttacakerVID, LONG lDestPosX, LONG lDestPosY, const TPixelPosition& k_pSyncPos, DWORD dwBlendDuration) { std::map<DWORD, SNetworkActorData>::iterator f = m_kNetActorDict.find(dwVID); if (m_kNetActorDict.end() == f) { return; } SNetworkActorData& rkNetActorData = f->second; if (k_pSyncPos.x && k_pSyncPos.y) { CInstanceBase* pkInstFind = __FindActor(rkNetActorData); if (pkInstFind) { const bool bProcessingClientAttack = pkInstFind->ProcessingClientAttack(dwAttacakerVID); pkInstFind->ServerAttack(dwAttacakerVID); // if already blending, update if (bProcessingClientAttack && pkInstFind->IsPushing() && pkInstFind->GetBlendingRemainTime() > 0.15) { pkInstFind->SetBlendingPosition(k_pSyncPos, pkInstFind->GetBlendingRemainTime()); } else { // otherwise sync //pkInstFind->SCRIPT_SetPixelPosition(k_pSyncPos.x, k_pSyncPos.y); pkInstFind->NEW_SyncPixelPosition(k_pSyncPos, dwBlendDuration); } } rkNetActorData.SetPosition(long(k_pSyncPos.x), long(k_pSyncPos.y)); } } ////////////////////// //// Semplified __Push and called only by AttackActor void CActorInstance::__Push(const TPixelPosition& c_rkPPosDst, unsigned int unDuration) { DWORD dwVID = GetVirtualID(); Tracenf("VID %d SyncPixelPosition %f %f", dwVID, c_rkPPosDst.x, c_rkPPosDst.y); if (unDuration == 0) unDuration = 1000; const D3DXVECTOR3& c_rv3Src = GetPosition(); const D3DXVECTOR3 c_v3Delta = c_rkPPosDst - c_rv3Src; SetBlendingPosition(c_rkPPosDst, float(unDuration) / 1000); if (!IsUsingSkill() && !IsResistFallen()) { int len = sqrt(c_v3Delta.x * c_v3Delta.x + c_v3Delta.y * c_v3Delta.y); if (len > 150.0f) { InterceptOnceMotion(CRaceMotionData::NAME_DAMAGE_FLYING); PushOnceMotion(CRaceMotionData::NAME_STAND_UP); } } } ////////////////////// // To understand if the client already started processing the attack or it must be initialized with a new sync_pixelposition: void CInstanceBase::ServerAttack(DWORD dwVID) { m_GraphicThingInstance.ServerAttack(dwVID); } bool CInstanceBase::ProcessingClientAttack(DWORD dwVID) { return m_GraphicThingInstance.ProcessingClientAttack(dwVID); } // client attack decreases the count void CActorInstance::ClientAttack(DWORD dwVID) { if (m_mapAttackSync.find(dwVID) == m_mapAttackSync.end()) { m_mapAttackSync.insert(std::make_pair(dwVID, -1)); } else { if (m_mapAttackSync[dwVID] == 1) { m_mapAttackSync.erase(dwVID); return; } m_mapAttackSync[dwVID]--; } } // server attack increases void CActorInstance::ServerAttack(DWORD dwVID) { if (m_mapAttackSync.find(dwVID) == m_mapAttackSync.end()) { m_mapAttackSync.insert(std::make_pair(dwVID, 1)); } else { if (m_mapAttackSync[dwVID] == -1) { m_mapAttackSync.erase(dwVID); return; } m_mapAttackSync[dwVID]++; } } bool CActorInstance::ProcessingClientAttack(DWORD dwVID) { return m_mapAttackSync.find(dwVID) != m_mapAttackSync.end() && m_mapAttackSync[dwVID] < 0; } // bool CActorInstance::ServerAttackCameFirst(DWORD dwVID) { return m_mapAttackSync.find(dwVID) != m_mapAttackSync.end() && m_mapAttackSync[dwVID] > 0; } New management of the hit which processes locally the movement, collect the infos, and send the packet to the server: struct BlendingPosition { D3DXVECTOR3 source; D3DXVECTOR3 dest; float duration; }; void CActorInstance::__ProcessDataAttackSuccess(const NRaceData::TAttackData & c_rAttackData, CActorInstance & rVictim, const D3DXVECTOR3 & c_rv3Position, UINT uiSkill, BOOL isSendPacket) { if (NRaceData::HIT_TYPE_NONE == c_rAttackData.iHittingType) return; InsertDelay(c_rAttackData.fStiffenTime); BlendingPosition sBlending; memset(&sBlending, 0, sizeof(sBlending)); sBlending.source = rVictim.NEW_GetCurPixelPositionRef(); if (__CanPushDestActor(rVictim) && c_rAttackData.fExternalForce > 0.0f) { const bool bServerAttackAlreadyCame = rVictim.ServerAttackCameFirst(GetVirtualID()); rVictim.ClientAttack(GetVirtualID()); if (!bServerAttackAlreadyCame) { __PushCircle(rVictim); // VICTIM_COLLISION_TEST const D3DXVECTOR3& kVictimPos = rVictim.GetPosition(); rVictim.m_PhysicsObject.IncreaseExternalForce(kVictimPos, c_rAttackData.fExternalForce); rVictim.GetBlendingPosition(&(sBlending.dest)); sBlending.duration = rVictim.m_PhysicsObject.GetRemainingTime(); // VICTIM_COLLISION_TEST_END } } // Invisible Time rVictim.m_fInvisibleTime = CTimer::Instance().GetCurrentSecond() + (c_rAttackData.fInvisibleTime - __GetInvisibleTimeAdjust(uiSkill, c_rAttackData)); // Stiffen Time rVictim.InsertDelay(c_rAttackData.fStiffenTime); // Hit Effect D3DXVECTOR3 vec3Effect(rVictim.m_x, rVictim.m_y, rVictim.m_z); // #0000780: [M2KR] ¼ö·æ Ÿ°Ý±¸ ¹®Á¦ extern bool IS_HUGE_RACE(unsigned int vnum); if (IS_HUGE_RACE(rVictim.GetRace())) { vec3Effect = c_rv3Position; } const D3DXVECTOR3 & v3Pos = GetPosition(); float fHeight = D3DXToDegree(atan2(-vec3Effect.x + v3Pos.x,+vec3Effect.y - v3Pos.y)); // 2004.08.03.myevan.ºôµùÀ̳ª ¹®ÀÇ °æ¿ì Ÿ°Ý È¿°ú°¡ º¸ÀÌÁö ¾Ê´Â´Ù if (rVictim.IsBuilding()||rVictim.IsDoor()) { D3DXVECTOR3 vec3Delta=vec3Effect-v3Pos; D3DXVec3Normalize(&vec3Delta, &vec3Delta); vec3Delta*=30.0f; CEffectManager& rkEftMgr=CEffectManager::Instance(); if (m_dwBattleHitEffectID) rkEftMgr.CreateEffect(m_dwBattleHitEffectID, v3Pos+vec3Delta, D3DXVECTOR3(0.0f, 0.0f, 0.0f)); } else { if(c_rAttackData.isEnemy == 0) { if(rVictim.IsEnemy() || rVictim.IsPC() || rVictim.IsBoss() || rVictim.IsStone()) { return; } } else { CEffectManager& rkEftMgr=CEffectManager::Instance(); if (m_dwBattleHitEffectID) rkEftMgr.CreateEffect(m_dwBattleHitEffectID, vec3Effect, D3DXVECTOR3(0.0f, 0.0f, fHeight)); if (m_dwBattleAttachEffectID) rVictim.AttachEffectByID(0, NULL, m_dwBattleAttachEffectID); } } if (rVictim.IsBuilding()) { // 2004.08.03.ºôµùÀÇ °æ¿ì Èçµé¸®¸é ÀÌ»óÇÏ´Ù } else if (rVictim.IsStone() || rVictim.IsDoor()) { __HitStone(rVictim); } else { /////////// // Motion bool ForceHitGOOD = rVictim.IsPC() && (rVictim.IsKnockDown() || rVictim.__IsStandUpMotion()); if (NRaceData::HIT_TYPE_GOOD == c_rAttackData.iHittingType || (TRUE == rVictim.IsResistFallen())) { __HitGood(rVictim); } else if (NRaceData::HIT_TYPE_GREAT == c_rAttackData.iHittingType) { if(c_rAttackData.isEnemy == 0) { if(rVictim.IsEnemy() || rVictim.IsPC() || rVictim.IsBoss() || rVictim.IsStone()) { return; } else { __HitGreate(rVictim, uiSkill, c_rAttackData.isEnemy); } } else { __HitGreate(rVictim, uiSkill, c_rAttackData.isEnemy); } } else { TraceError("ProcessSucceedingAttacking: Unknown AttackingData.iHittingType %d", c_rAttackData.iHittingType); } } __OnHit(uiSkill, rVictim, isSendPacket, &sBlending); } void CPythonPlayerEventHandler::OnHit(UINT uSkill, CActorInstance& rkActorVictim, BOOL isSendPacket, BlendingPosition* sBlending) { DWORD dwVIDVictim=rkActorVictim.GetVirtualID(); CPythonCharacterManager::Instance().AdjustCollisionWithOtherObjects(&rkActorVictim); BlendingPosition kBlendingPacket; memset(&kBlendingPacket, 0, sizeof(kBlendingPacket)); kBlendingPacket.source = rkActorVictim.NEW_GetCurPixelPositionRef(); if (rkActorVictim.IsPushing()) { kBlendingPacket.dest = rkActorVictim.NEW_GetLastPixelPositionRef(); kBlendingPacket.duration = sBlending->duration; } // Update Target CPythonPlayer::Instance().SetTarget(dwVIDVictim, FALSE); // Update Target //#define ATTACK_TIME_LOG #ifdef ATTACK_TIME_LOG static std::map<DWORD, float> s_prevTimed; float curTime = timeGetTime() / 1000.0f; bool isFirst = false; if (s_prevTimed.end() == s_prevTimed.find(dwVIDVictim)) { s_prevTimed[dwVIDVictim] = curTime; isFirst = true; } float diffTime = curTime-s_prevTimed[dwVIDVictim]; if (diffTime < 0.1f && !isFirst) { TraceError("ATTACK(SPEED_HACK): %.4f(%.4f) %d", curTime, diffTime, dwVIDVictim); } else { TraceError("ATTACK: %.4f(%.4f) %d", curTime, diffTime, dwVIDVictim); } s_prevTimed[dwVIDVictim] = curTime; #endif CPythonNetworkStream& rkStream=CPythonNetworkStream::Instance(); rkStream.SendAttackPacket(uSkill, dwVIDVictim, isSendPacket, kBlendingPacket); } bool CPythonNetworkStream::SendAttackPacket(UINT uMotAttack, DWORD dwVIDVictim, BOOL bPacket, BlendingPosition& sBlending) { NANOBEGIN if (!__CanActMainInstance()) return true; CPythonCharacterManager& rkChrMgr = CPythonCharacterManager::Instance(); CInstanceBase* pkInstMain = rkChrMgr.GetMainInstancePtr(); #ifdef ATTACK_TIME_LOG static DWORD prevTime = timeGetTime(); DWORD curTime = timeGetTime(); TraceError("TIME: %.4f(%.4f) ATTACK_PACKET: %d TARGET: %d", curTime/1000.0f, (curTime-prevTime)/1000.0f, uMotAttack, dwVIDVictim); prevTime = curTime; #endif TPacketCGAttack kPacketAtk; kPacketAtk.header = HEADER_CG_ATTACK; kPacketAtk.bType = uMotAttack; kPacketAtk.dwVictimVID = dwVIDVictim; kPacketAtk.bPacket = bPacket; kPacketAtk.lX = (long)sBlending.dest.x; kPacketAtk.lY = (long)sBlending.dest.y; kPacketAtk.lSX = (long)sBlending.source.x; kPacketAtk.lSY = (long)sBlending.source.y; kPacketAtk.fSyncDestX = sBlending.dest.x; // sources and dest are normalized with both coordinates positive // since fSync are ment to be broadcasted to other clients, the Y has to preserve the negative coord kPacketAtk.fSyncDestY = -sBlending.dest.y; kPacketAtk.dwBlendDuration = (unsigned int) (sBlending.duration *1000); kPacketAtk.dwComboMotion = pkInstMain->GetComboMotion(); kPacketAtk.dwTime = ELTimer_GetServerMSec(); if (kPacketAtk.lX && kPacketAtk.lY) __LocalPositionToGlobalPosition(kPacketAtk.lX, kPacketAtk.lY); __LocalPositionToGlobalPosition(kPacketAtk.lSX, kPacketAtk.lSY); if (!SendSpecial(sizeof(kPacketAtk), &kPacketAtk)) { Tracen("Send Battle Attack Packet Error"); return false; } return SendSequence(); } To preserve the behavior of Rolling Dagger, that was based on a bug, it will be necessary to: //// InstanceBase.cpp /// void CInstanceBase::StateProcess() if (eFunc & FUNC_SKILL) .... // NOTICE HERE THE 1!!!!! NEW_UseSkill(1, eFunc & FUNC_SKILL - 1, uArg&0x0f, (uArg>>4) ? true : false); //Tracen("°¡±õ±â ¶§¹®¿¡ ¿öÇÁ °ø°Ý"); } } break; /// void CInstanceBase::MovementProcess() // TODO get skill vnum // NOTICE HERE THE 1!!!!!!!!!! NEW_UseSkill(1, m_kMovAfterFunc.eFunc & FUNC_SKILL - 1, m_kMovAfterFunc.uArg & 0x0f, (m_kMovAfterFunc.uArg >> 4) ? true : false); .... // ActorInstaceBattle.cpp void CActorInstance::__HitGreate(CActorInstance& rVictim, unsigned int uiSkill, bool isEnemy) { // DISABLE_KNOCKDOWN_ATTACK // !!!! uiSkill if (!uiSkill && rVictim.IsKnockDown()) return; if (rVictim.__IsStandUpMotion()) return; // END_OF_DISABLE_KNOCKDOWN_ATTACK float fRotRad = D3DXToRadian(GetRotation()); float fVictimRotRad = D3DXToRadian(rVictim.GetRotation()); D3DXVECTOR2 v2Normal(sin(fRotRad), cos(fRotRad)); D3DXVECTOR2 v2VictimNormal(sin(fVictimRotRad), cos(fVictimRotRad)); .... and add external force to the msa of the animation to give Rolling Dagger the ability to push therefore making the enemy jump. This way even without the daggers combo it will be possible to do a double or triple rolling. If you wanna make the daggers combo necessary, you can create a new property in the msa like "refresh_displacement" that says to the launcher, in case of a shift happening, the animation must refresh the shifting and knock_back. Chapter 3.1: Result After having applied these structural changes and implementations, you will obtain a revision where the clients will process attacks and movements client side to gain immediate reactivity and the server will perform a validation and synchronization of the states in real-time. The server will act as a referee, while the clients will just execute since they will give the server all the necessary infos to referee. This approach connects the fluidity of the execution of the client with a millimetric real time synchronization of all the clients connected, presenting a more reactive and precise pvp experience. This approach also allows for multiple entities to control the push/movements of another entity at the same time. Therefore it would be possible to do stuff like performing a rolling dagger after the combo of one of your guildmates, or pushing another character already in mid-air. Chapter 3.2: Benefits Other than the precision and reactivity of the synchronization, this approach gives complete control to the server on the actions of the clients say to have done: timestamp, comboArgument manage to detect speed and combohack, starting and final coords are able to detect antifly and rangehacks etc Therefore, in case of malicious intents, the server has the ability to reset to everyone the initial positions. Moreover these changes remove many "obscure" aspects of the combat system. Fly, Rolling Dagger, Falling duration, Flame Strike etc. will all be parameters that the developers and maintainers can manage and orchestrate, not something that happnes because it happens. The Metin2 combat system will, finally, become configurable, not something given for granted and unchangeable. Chapter 4: Possible expansions At this point, changes and integrations are over. Serverside anticheat systems, implementation of a collisions' engine and server side animation to reproduce, on the server, the client's actions, complete removal of the fly.. everything that includes the interaction and management of the synchronization system of the characters between clients and state of the game are finally possible.
  7. Ah yes, my bad. I do have: bool IsInLevelRange(DWORD dwLevel) { if(m_dwLevelMaxLimit == 0) return dwLevel >= m_dwLevelLimit; return dwLevel >= m_dwLevelLimit && dwLevel <= m_dwLevelMaxLimit; } for the drop type limit.
  8. Tonight we deployed the patch with the client optimization and everyone is super happy with the smoothness of the game. Super friendly, you can definitely see he loves his work and he fixed me even two small bugs I would recommend this developer 1000000000000%
  9. Tonight we deployed the patch with the client optimization and everyone is super happy with the smoothness of the game. Super friendly, you can definitely see he loves his work and he fixed me even two small bugs I would recommend this developer 1000000000000% Special quote from a player: (Don't ask me how he is able to play in general LOL)
  10. I still use boost for json but using unordered_map trough the standard ISO instead of boost is something I thought of in the server files and not in the client, so, neat!
  11. If you can pay for the fix, @ Sonitex sells the update to dx9, ask him
  12. you are compiling on a 64bit version of FreeBSD?
  13. You have to rewove -lanticpxsvr from the makefile of course
  14. Oof, that looks like the hackshield lib, which, honestly, is not used. Don't include it in your makefile and don't compile any file related to hackshield and possibly, remove any reference to it.
  15. you are returning nullptr in a boolean function -> return false; (I suppose)
  16. #include "stdafx.h" #include "cipher.h" #ifdef _IMPROVED_PACKET_ENCRYPTION_ #include <cryptopp/modes.h> #include <cryptopp/nbtheory.h> #include <cryptopp/osrng.h> // Diffie-Hellman key agreement #include <cryptopp/dh.h> #include <cryptopp/dh2.h> // AES winner and candidates //#include <cryptopp/aes.h> #include <cryptopp/cast.h> #include <cryptopp/rc6.h> #include <cryptopp/mars.h> #include <cryptopp/serpent.h> #include <cryptopp/twofish.h> // Other block ciphers #include <cryptopp/blowfish.h> #include <cryptopp/camellia.h> #include <cryptopp/des.h> #include <cryptopp/idea.h> #include <cryptopp/rc5.h> #include <cryptopp/seed.h> #include <cryptopp/shacal2.h> #include <cryptopp/skipjack.h> #include <cryptopp/tea.h> //#include <cryptopp/cryptoppLibLink.h> // yes delete this
  17. I was upgrading some stuff and I stumbled upon a little """"bug"""" with MariaDB who uses ma_make_scrambled_password instead of make_scrambled_password, which returned a completely different hash, so, instead of just settling in, I thought "why not just change it altogether?" At first I thought about SHA + salting but a colleague of mine told me about bcrypt, which, basically it already generates a random salt (it's more complicated than that, of course, but w/e, not important). So, first of all, go to input_auth.cpp and search for: char szPasswd[PASSWD_MAX_LEN * 2 + 1]; DBManager::instance().EscapeString(szPasswd, sizeof(szPasswd), passwd, strlen(passwd)); Replace with: char szPasswd[72]; //BCRYPT hash max length 71 + '\0' //DBManager::instance().EscapeString(szPasswd, sizeof(szPasswd), passwd, strlen(passwd)); //we are not passing passwd to the db anymore so we don't care to escape it Then after char szLogin[LOGIN_MAX_LEN * 2 + 1]; DBManager::instance().EscapeString(szLogin, sizeof(szLogin), login, strlen(login)); remove everything in the function but save in another txt what your QID_AUTH_LOGIN query was. Then paste: std::unique_ptr<SQLMsg> pMsg(DBManager::instance().DirectQuery("SELECT password FROM account WHERE login = '%s';", szLogin)); //select hashed password from db from login if(!pMsg->Get() || pMsg->Get()->uiNumRows == 0) //check if login exists { LoginFailure(d, "NOID"); //login not exists sys_log(0, " NOID"); return; } else { if(pMsg->Get()->uiNumRows > 0) //kinda redundant but ye { MYSQL_ROW row; while ((row = mysql_fetch_row(pMsg->Get()->pSQLResult))) { int col = 0; strncpy(szPasswd, row[col++], sizeof(szPasswd)); //copy the hash from db to szPasswd } } if(BCrypt::validatePassword(passwd,szPasswd)) //validate, and if it is valid, do the auth login query { DBManager::instance().ReturnQuery(QID_AUTH_LOGIN, dwKey, p, "SELECT social_id,id,status,availDt - NOW() > 0," "UNIX_TIMESTAMP(silver_expire)," "UNIX_TIMESTAMP(gold_expire)," "UNIX_TIMESTAMP(autoloot_expire)," "UNIX_TIMESTAMP(fish_mind_expire)," "UNIX_TIMESTAMP(marriage_fast_expire)," "UNIX_TIMESTAMP(money_drop_rate_expire)," "UNIX_TIMESTAMP(create_time)" " FROM account WHERE login='%s'", szLogin ); } else{ LoginFailure(d, "WRONGPWD"); sys_log(0, " WRONGPWD"); } } return; Check your original query, add w/e it's missing, as long as it's not password related. Then at the beginning of the file, add to the includes: #include "bcrypt/BCrypt.hpp" #include <memory> //IF NOT PRESENT ALREADY Now, BCrypt.hpp, we don't have this. First of all, thanks to trusch who made a c++ wrapper for bcrypt (GitHub project HERE), but you can also download the static lib already compiled HERE (compiled on FreeBSD 13.0-Release, you can also compile it yourself reading the README file on the GitHub page). Therefore, open Makefile (on the game sources folder) and add: # BCRYPT INCDIR += -I../../../Extern/include/bcrypt LIBS += ../../../Extern/lib/libbcrypt.a it should be something like: # Boost INCDIR += -I/usr/local/include/boost # PCG INCDIR += -I../../../Extern/include/pcg # BCRYPT INCDIR += -I../../../Extern/include/bcrypt LIBS += ../../../Extern/lib/libbcrypt.a # DevIL INCDIR += -I../../../Extern/include/IL LIBS += ../../../Extern/lib/libIL.a\ ../../../Extern/lib/libjasper.a\ ../../../Extern/lib/libpng.a\ ../../../Extern/lib/libtiff.a\ ../../../Extern/lib/libjbig.a\ ../../../Extern/lib/libmng.a\ /usr/lib/liblzma.a\ /usr/lib/libmd.a\ ../../../Extern/lib/liblcms.a\ ../../../Extern/lib/libjpeg.a # MySQL INCDIR += -I/usr/local/include/mysql LIBS += /usr/local/lib/mysql/libmariadbclient.a /usr/lib/libz.a # CryptoPP LIBS += ../../../Extern/lib/libcryptopp.a just to give you an idea where you should place it. Then of course, take the include folder from the GitHub link and add it to your extern folder. Ah, shoot, remember the query? Well, it's not passing the password anymore, so open db.cpp and search for the function: void DBManager::AnalyzeReturnQuery(SQLMsg * pMsg) and replace this: // PASSWORD('%s'), password, securitycode, social_id, id, status char szEncrytPassword[45 + 1] = {0, }; char szPassword[45 + 1] = {0, }; with: // PASSWORD('%s'), password, securitycode, social_id, id, status //char szEncrytPassword[45 + 1] = {0, }; //char szPassword[45 + 1] = {0, }; (or remove it, you know) and same thing for this: /*strlcpy(szEncrytPassword, row[col++], sizeof(szEncrytPassword)); if(!row[col]) { sys_err("error column %d", col); M2_DELETE(pinfo); break; } strlcpy(szPassword, row[col++], sizeof(szPassword)); if(!row[col]) { sys_err("error column %d", col); M2_DELETE(pinfo); break; }*/ And we're done. If you wanna generate your 12345 passwords or something you can use THIS link. The end! EDIT: The end my ass, of course change your account table, the password column should be varchar(72) instead of the previous value.
  18. I've updated my client with lzo2.10 and the latest cryptopp version without any issue (well, except for lzo, needed to set RunTime library to MT/MTd when compiling the lib) cryptopp github page Download the latest version, compile the cryptopp solution in Debug and Release mode, update the include files and libs and try recompiling
  19. Probably TNL? Sometimes I have that problem if I change the TNL (can't remember if it was CPU or GPU). Fact is, DX9 can be suuuper laggy for some people. On a server in 2015 we had this and we had to maintain a dx8 and dx9 version of the launcher, then we just dropped the dx9 version altogether since too many people couldn't run it.
  20. You can copy the name column value into the locale_name/gb2312 column. about the special characters: `name` varchar(24) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'Noname', ', check these setttings, same in the database properties: latin1 - latin1_swedish_ci. If you dump the database it should have something like: SET NAMES utf8mb4; if not, run that specific query: SET NAMES utf8mb4; Additional infos: Change MySQL default character set to UTF-8 in my.cnf?
  21. I made more testings and, I DID needed "funcData=inspect.getargspec(mfunc.im_fun)" because: pkChr->ChatPacket(CHAT_TYPE_COMMAND, "lobby_application_update %d %d %s", bLobbyType, vecResponse.size(), szCommand); that sent me, as the last string something like "0 1". I momentarily fixed it using a delimiter, BUT, remember that if you wanna use inspect.getargspec, well, it does not support cy_functions (it does on python3 with inspect.getfullargspec though).
  22. No need for flags or anything, it's just simple math: (buffvalue)*(0.5+0.5*isself) isself should be a variable set in skill.cpp already, if not: poly.SetVar("isself", (bool)(victim == ch));
  23. Is it leaked, though? I mean, it's the leaked version the only one bugged? Because then a friend of mine got scammed (I got to do the implementation for a friend of mine). He said he bought it from a guy, though I can be wrong. But the leaked version and his were the same with the same issues (because there are more lol).
×
×
  • 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.