As was first posted about in this thread from 2012, [Only registered and activated users can see links. Click Here To Register...], it's been well known there's a 1-hour party leader DC bug with VSRO 188. Since then, there's been a few other posts with fixes that pretty much work, but don't really solve the underlying problem of not having party match entries deleted after 1 hour. I took a look into this problem tonight, and came up with what I'm confident is a correct fix to be able to have party match entries deleted after 1 hour (or however long if you want to modify the time check from 60 minutes).
First, to re-explain the problem. After a party match entry has existed for 1 hour, ShardManager will send GameServer 0x386f on behalf of the party leader, and then removes the party match entry. GameServer does not have a packet handler for 0x386f though, and since it thinks the client sent it an unknown opcode, it will disconnect the player, despite the fact it was ShardManager who sent the packet.
So far, two fixes have been presented. Change the 0x386f opcode (or block it in a server sided module proxy) or skip the 60m timeout check. The problem with changing/blocking the opcode is that the party entry removal logic still runs in ShardManager, so the party leader has to relog (or possibly teleport I've read) to be able to reform the party. If you skip the 60m timeout check, you now have party entries that will linger about until they are cancelled or the user logs out (presumably, I've not verified if they do or don't leak).
My fix will use some assembly edits to implement new logic that will result in the desired behavior of having party matching entries expire after the timeout. Before I get into the solution itself, first I'll talk about how I arrived at the solution and how I've verified that it results in the correct, expected behavior with thus far no side effects.
First, let's look at the packet flow for when a user forms a party:
Then, let's look at the packet flow for when the user deletes their party entry:
From this, I learned that AgentServer passes the client's party delete request (0x706B) to GameServer. The first 4 bytes are the party id, and the last 4 bytes are the account JID. If you "SELECT * FROM [VSRO_ACCOUNT].[dbo].[TB_User] where JID = 0xB185A", you'll get the row for the account I was using, which was t048 (which I also have labeled on the Client->Agent connection).
Right now, ShardManager is telling GameSever the client has sent an empty packet, 0x386f (remember the 4 bytes in 0x386f are the JID, so the servers know who is sending the data). Instead, we want to simulate this packet:
Lucky for us, with how the SRO server architecture works, ShardManager is already sending GameServer a packet on the client's behalf (0x386f). What if instead we send a 0x706B packet to GameServer? To make this work, we'd also have to write the party id first, and then let the normal 0x386f packet building logic run (which writes the JID of the party leader).
Then, we'd need to stop the ShardManager from removing the party entry itself, because that's the second main problem that needs to be solved. If we were to do that, then when the party match timeout happens, we're pretending the client has cancelled the request, so the system correctly removes the party entry as if a user initiated the action, and then they'd be able to form a new party as if nothing happened.
This is what my fix does. I use a codecave to a new section added to the server, change the packet opcode, write the party id (the party entry object is stored in EBX), and then jump back to the packet building so the JID gets written. Then, I NOP the party entry removal code so everything stays in sync, and the party match timeout feature works as expected because the system thinks the user initiated the cancel, even though it was the server.
The end result is that everything seems to work as you'd expect. To verify this works without side effects, and why I'm confident it's the correct solution, I just look at the resulting packets of the process, and make sure they matched the manual action's result, which they do. In addition, I make sure I can form a new party and another character verifies there's nothing unexpected going on, which there wasn't.
I made a small video showing everything mentioned above. It's a bit boring because I wait through 2 party match refresh packets, but I cover all the main cases in showing that the fix should work. It's only a few minutes long though, so hopefully it's not too painful. I wanted to show the entire process though, so that's why I don't cut out the time. Towards the end I have some comments in the client about the asm itself. I also did testing with multiple clients at once and joining the party and everything works as it should.
Demo Video:
Guide:
A lot of people seem to use and reference Stud_PE, but I myself use CFF Explorer (free edition). Likewise, I'll be using x32dbg and not Ollydbg. This guide will use CFF Explorer, but you can perform similar steps however you want, or opt to implement the solution in an injected dll, etc... The purpose of this guide is to just show how to solve the problem, it's up to users to pick which implementation of the solution works best for them.
Due to the unfortunate false positives with old server files, no edited binaries will be posted. This guide can be used on already modified ShardManagers, so it'll just take a little work to have the fix applied to non-vanilla files. As always, be sure to keep backups in case anything goes wrong with the edits!
1. Open your ShardManager in CFF Explorer.
2. Click on the "Section Headers" treeview entry, right click in the empty grey space in the section header table view, and then choose "Add Section (Empty Space)"
[Only registered and activated users can see links. Click Here To Register...]
3. Enter a size of 1000 and hit OK. This makes the size 0x1000 bytes, which should be more than enough for lots of patches, but section sizes are generally good to have in multiples of 0x1000.
[Only registered and activated users can see links. Click Here To Register...]
4. Double click in the "Name" field of the new entry and type in a new, tiny name that begins with a period. Naming the section is good practice to make it easier to refer to when you have more edits, but you only have a limited amount of characters for section names (8 total characters).
[Only registered and activated users can see links. Click Here To Register...]
[NEW] 4b. Right click the "Characteristics" field of the newly named section. Choose the "Change Section Flags"
[Only registered and activated users can see links. Click Here To Register...]
[NEW] 4c. Click the checkboxes for the "Is executable" and "Contains code" options. Click OK to save the changes. This will solve a problem that might arise around Step 12 where the section to jmp to is not marked as executable. However, I'm unsure the conditions for which this happens, since it didn't happen for me, but happened for another user.
[Only registered and activated users can see links. Click Here To Register...]
5. Click on the Save icon. Choose NOT to overwrite your original file and give it a new name. We will now edit our exe in x32dbg.
6. Open your new ShardManager in x32dbg and click on the "Memory Map" icon in the top bar.
7. Select the section you added for this fix, and right click and choose "Follow in Disassembler"
[Only registered and activated users can see links. Click Here To Register...]
8. Now pay close attention, because you can't just mindlessly copy and paste and not read the directions. The initial patch bytes are as follows:
It's 0x21 bytes, so copy them to your clipboard, highlight a bunch of bytes in your section, then right click the highlighted bytes, and choose "Binary Paste"
[Only registered and activated users can see links. Click Here To Register...]
9. Now highlight the call instruction and press spacebar to assemble. Change the text to "call 0x402DF0" and click OK. NOTE: Your final bytes will not match mine, because your section will most likely be in a different address than mine is.
[Only registered and activated users can see links. Click Here To Register...]
10. The assemble box will now move to the jmp instruction, so you don't have to highlight it and press spacebar again (but you can if you accidently closed the assemble window). Change the text to "jmp 0x4505DE" and click OK. Close the assemble window since we don't need it here anymore. NOTE: Your final bytes will not match mine, because your section will most likely be in a different address than mine is.
[Only registered and activated users can see links. Click Here To Register...]
11. Highlight the first instruction in this code section. Right click it and choose "Label -> Label Current Address". I typed in the name "ptfix", but you can use whatever, just remember what you wrote.
[Only registered and activated users can see links. Click Here To Register...]
We've now completed adding in the custom logic for the fix. We now have a few more patches to go to make it use this logic and to fix the issue of stopping the shard manager from deleting the party match entry.
12. Press "ctrl + g" to go to an address. Paste in 0x4505D9 and press OK. Now press spacebar to bring up the assemble window again. Change the instruction to "jmp ptfix" (or use whatever name you wrote in step 11). Make sure the "Keep Size" option is selected and press OK and close the assemble window. While the size should be the exact same, when editing client code you want to be mindful of not accidently making changes that overwrite other instructions unexpectedly! NOTE: Your final bytes will not match mine, because your section will most likely be in a different address than mine is.
[Only registered and activated users can see links. Click Here To Register...]
13. Press "ctrl + g" to go to an address. Paste in 0x450619 and press OK. Select this address through the jne at 0x45062A. Right click the selection and choose "Binary -> Fill with NOPs"
[Only registered and activated users can see links. Click Here To Register...]
14. In the new window that pops up, just press OK, since we selected the right amount of bytes to fill already.
[Only registered and activated users can see links. Click Here To Register...]
15. Now it's time to save our changes. Click "File -> Patch file..." to bring up the patches window. Click on the "Patch File" button, and make a new name for the final ShardManager and click Save (as you can't overwrite the current file while it's in use in x32dbg!)
[Only registered and activated users can see links. Click Here To Register...]
16. Now before you close this x32dbg instance, go ahead and open your new final ShardManager in a new x32dbg instance and verify you made all the changes correctly. This is so if you missed something by accident, you don't have to repeat the entire process, and can just re-save once you correct any mistakes.
Congratulations! You should now have a modified ShardManager that does not have the 1-hour DC. In order to test and verify the patch was made without issues, assuming you don't want to waste an hour for it to trigger, you can perform the following edit:
[OPTIONAL] 17. Press "ctrl + g" and go to address 0x45055F. Right click the line and choose "Binary -> Fill with NOPs" and then press OK. Repeat step 15, but make sure to give this ShardManager a different name, as you'll want to test once with the instant timeout version, and then switch back to the normal one after confirming the patch works as expected.
As another tip, as it's been mentioned in another thread, but I'll mention it again here for completeness, you can edit the 0x3C in line 0x45055C to change from a 60m timeout so some other value, which will be limited to 0xFF max (255m) due to the instruction size limitation.
Once you've verified everything, you should be good to go, so you can close your x32dbg instances and CFF Explorer if it's still open. As I mentioned earlier, I don't believe there will be any side effects based on my testing and observations, and the fix is easily verified as being working because the client is notified of the party being removed without being DCed correctly now.
That completes this guide, if I've missed anything or if something isn't clear, please let me know and I'll get it sorted out. I do re-read my threads and try to make sure I don't make any big mistakes, but things can slip by since I'll read what's in my mind and not always what I wrote, so it's no problem fixing anything wrong.
Happy coding! :)
[Edit 1] Thanks to @[Only registered and activated users can see links. Click Here To Register...] for testing out the guide and discovering the problem that the newly added 4b/4c steps solve!
First, to re-explain the problem. After a party match entry has existed for 1 hour, ShardManager will send GameServer 0x386f on behalf of the party leader, and then removes the party match entry. GameServer does not have a packet handler for 0x386f though, and since it thinks the client sent it an unknown opcode, it will disconnect the player, despite the fact it was ShardManager who sent the packet.
So far, two fixes have been presented. Change the 0x386f opcode (or block it in a server sided module proxy) or skip the 60m timeout check. The problem with changing/blocking the opcode is that the party entry removal logic still runs in ShardManager, so the party leader has to relog (or possibly teleport I've read) to be able to reform the party. If you skip the 60m timeout check, you now have party entries that will linger about until they are cancelled or the user logs out (presumably, I've not verified if they do or don't leak).
My fix will use some assembly edits to implement new logic that will result in the desired behavior of having party matching entries expire after the timeout. Before I get into the solution itself, first I'll talk about how I arrived at the solution and how I've verified that it results in the correct, expected behavior with thus far no side effects.
First, let's look at the packet flow for when a user forms a party:
Then, let's look at the packet flow for when the user deletes their party entry:
From this, I learned that AgentServer passes the client's party delete request (0x706B) to GameServer. The first 4 bytes are the party id, and the last 4 bytes are the account JID. If you "SELECT * FROM [VSRO_ACCOUNT].[dbo].[TB_User] where JID = 0xB185A", you'll get the row for the account I was using, which was t048 (which I also have labeled on the Client->Agent connection).
Right now, ShardManager is telling GameSever the client has sent an empty packet, 0x386f (remember the 4 bytes in 0x386f are the JID, so the servers know who is sending the data). Instead, we want to simulate this packet:
Code:
[6160][9:22:52 PM][Agent->GameA][C->S][706B][8 bytes] 0000000000 12 00 00 00 5A 18 0B 00 ....Z...........
Then, we'd need to stop the ShardManager from removing the party entry itself, because that's the second main problem that needs to be solved. If we were to do that, then when the party match timeout happens, we're pretending the client has cancelled the request, so the system correctly removes the party entry as if a user initiated the action, and then they'd be able to form a new party as if nothing happened.
This is what my fix does. I use a codecave to a new section added to the server, change the packet opcode, write the party id (the party entry object is stored in EBX), and then jump back to the packet building so the JID gets written. Then, I NOP the party entry removal code so everything stays in sync, and the party match timeout feature works as expected because the system thinks the user initiated the cancel, even though it was the server.
The end result is that everything seems to work as you'd expect. To verify this works without side effects, and why I'm confident it's the correct solution, I just look at the resulting packets of the process, and make sure they matched the manual action's result, which they do. In addition, I make sure I can form a new party and another character verifies there's nothing unexpected going on, which there wasn't.
I made a small video showing everything mentioned above. It's a bit boring because I wait through 2 party match refresh packets, but I cover all the main cases in showing that the fix should work. It's only a few minutes long though, so hopefully it's not too painful. I wanted to show the entire process though, so that's why I don't cut out the time. Towards the end I have some comments in the client about the asm itself. I also did testing with multiple clients at once and joining the party and everything works as it should.
Demo Video:
|
|
Guide:
A lot of people seem to use and reference Stud_PE, but I myself use CFF Explorer (free edition). Likewise, I'll be using x32dbg and not Ollydbg. This guide will use CFF Explorer, but you can perform similar steps however you want, or opt to implement the solution in an injected dll, etc... The purpose of this guide is to just show how to solve the problem, it's up to users to pick which implementation of the solution works best for them.
Due to the unfortunate false positives with old server files, no edited binaries will be posted. This guide can be used on already modified ShardManagers, so it'll just take a little work to have the fix applied to non-vanilla files. As always, be sure to keep backups in case anything goes wrong with the edits!
1. Open your ShardManager in CFF Explorer.
2. Click on the "Section Headers" treeview entry, right click in the empty grey space in the section header table view, and then choose "Add Section (Empty Space)"
[Only registered and activated users can see links. Click Here To Register...]
3. Enter a size of 1000 and hit OK. This makes the size 0x1000 bytes, which should be more than enough for lots of patches, but section sizes are generally good to have in multiples of 0x1000.
[Only registered and activated users can see links. Click Here To Register...]
4. Double click in the "Name" field of the new entry and type in a new, tiny name that begins with a period. Naming the section is good practice to make it easier to refer to when you have more edits, but you only have a limited amount of characters for section names (8 total characters).
[Only registered and activated users can see links. Click Here To Register...]
[NEW] 4b. Right click the "Characteristics" field of the newly named section. Choose the "Change Section Flags"
[Only registered and activated users can see links. Click Here To Register...]
[NEW] 4c. Click the checkboxes for the "Is executable" and "Contains code" options. Click OK to save the changes. This will solve a problem that might arise around Step 12 where the section to jmp to is not marked as executable. However, I'm unsure the conditions for which this happens, since it didn't happen for me, but happened for another user.
[Only registered and activated users can see links. Click Here To Register...]
5. Click on the Save icon. Choose NOT to overwrite your original file and give it a new name. We will now edit our exe in x32dbg.
6. Open your new ShardManager in x32dbg and click on the "Memory Map" icon in the top bar.
7. Select the section you added for this fix, and right click and choose "Follow in Disassembler"
[Only registered and activated users can see links. Click Here To Register...]
8. Now pay close attention, because you can't just mindlessly copy and paste and not read the directions. The initial patch bytes are as follows:
Code:
66 C7 00 6B 70 8D 54 24 1C 8B 0B 89 4C 24 1C 52 B8 04 00 00 00 8B CE E8 00 00 00 00 E9 00 00 00 00
[Only registered and activated users can see links. Click Here To Register...]
9. Now highlight the call instruction and press spacebar to assemble. Change the text to "call 0x402DF0" and click OK. NOTE: Your final bytes will not match mine, because your section will most likely be in a different address than mine is.
[Only registered and activated users can see links. Click Here To Register...]
10. The assemble box will now move to the jmp instruction, so you don't have to highlight it and press spacebar again (but you can if you accidently closed the assemble window). Change the text to "jmp 0x4505DE" and click OK. Close the assemble window since we don't need it here anymore. NOTE: Your final bytes will not match mine, because your section will most likely be in a different address than mine is.
[Only registered and activated users can see links. Click Here To Register...]
11. Highlight the first instruction in this code section. Right click it and choose "Label -> Label Current Address". I typed in the name "ptfix", but you can use whatever, just remember what you wrote.
[Only registered and activated users can see links. Click Here To Register...]
We've now completed adding in the custom logic for the fix. We now have a few more patches to go to make it use this logic and to fix the issue of stopping the shard manager from deleting the party match entry.
12. Press "ctrl + g" to go to an address. Paste in 0x4505D9 and press OK. Now press spacebar to bring up the assemble window again. Change the instruction to "jmp ptfix" (or use whatever name you wrote in step 11). Make sure the "Keep Size" option is selected and press OK and close the assemble window. While the size should be the exact same, when editing client code you want to be mindful of not accidently making changes that overwrite other instructions unexpectedly! NOTE: Your final bytes will not match mine, because your section will most likely be in a different address than mine is.
[Only registered and activated users can see links. Click Here To Register...]
13. Press "ctrl + g" to go to an address. Paste in 0x450619 and press OK. Select this address through the jne at 0x45062A. Right click the selection and choose "Binary -> Fill with NOPs"
[Only registered and activated users can see links. Click Here To Register...]
14. In the new window that pops up, just press OK, since we selected the right amount of bytes to fill already.
[Only registered and activated users can see links. Click Here To Register...]
15. Now it's time to save our changes. Click "File -> Patch file..." to bring up the patches window. Click on the "Patch File" button, and make a new name for the final ShardManager and click Save (as you can't overwrite the current file while it's in use in x32dbg!)
[Only registered and activated users can see links. Click Here To Register...]
16. Now before you close this x32dbg instance, go ahead and open your new final ShardManager in a new x32dbg instance and verify you made all the changes correctly. This is so if you missed something by accident, you don't have to repeat the entire process, and can just re-save once you correct any mistakes.
Congratulations! You should now have a modified ShardManager that does not have the 1-hour DC. In order to test and verify the patch was made without issues, assuming you don't want to waste an hour for it to trigger, you can perform the following edit:
[OPTIONAL] 17. Press "ctrl + g" and go to address 0x45055F. Right click the line and choose "Binary -> Fill with NOPs" and then press OK. Repeat step 15, but make sure to give this ShardManager a different name, as you'll want to test once with the instant timeout version, and then switch back to the normal one after confirming the patch works as expected.
As another tip, as it's been mentioned in another thread, but I'll mention it again here for completeness, you can edit the 0x3C in line 0x45055C to change from a 60m timeout so some other value, which will be limited to 0xFF max (255m) due to the instruction size limitation.
Once you've verified everything, you should be good to go, so you can close your x32dbg instances and CFF Explorer if it's still open. As I mentioned earlier, I don't believe there will be any side effects based on my testing and observations, and the fix is easily verified as being working because the client is notified of the party being removed without being DCed correctly now.
That completes this guide, if I've missed anything or if something isn't clear, please let me know and I'll get it sorted out. I do re-read my threads and try to make sure I don't make any big mistakes, but things can slip by since I'll read what's in my mind and not always what I wrote, so it's no problem fixing anything wrong.
Happy coding! :)
[Edit 1] Thanks to @[Only registered and activated users can see links. Click Here To Register...] for testing out the guide and discovering the problem that the newly added 4b/4c steps solve!