|
You last visited: Today at 18:09
Advertisement
[Guide] Fixing the party matching 1-hour DC bug (0x386f)
Discussion on [Guide] Fixing the party matching 1-hour DC bug (0x386f) within the SRO PServer Guides & Releases forum part of the SRO Private Server category.
08/24/2021, 09:40
|
#1
|
elite*gold: 260
Join Date: Aug 2008
Posts: 560
Received Thanks: 3,778
|
[Guide] Fixing the party matching 1-hour DC bug (0x386f)
As was first posted about in this thread from 2012,  , 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:
Code:
[t048][6117][9:22:50 PM][Client->Agent][C->S][7069][47 bytes]
0000000000 00 00 00 00 00 00 00 00 05 00 01 6E 21 00 46 6F ...........n!.Fo
0000000000 72 20 6F 70 65 6E 20 68 75 6E 74 69 6E 67 20 6F r open hunting o
0000000000 6E 20 74 68 65 20 53 69 6C 6B 72 6F 61 64 21 n the Silkroad!.
[6118][9:22:50 PM][Agent->GameA][C->S][7069][51 bytes]
0000000000 00 00 00 00 00 00 00 00 05 00 01 6E 21 00 46 6F ...........n!.Fo
0000000000 72 20 6F 70 65 6E 20 68 75 6E 74 69 6E 67 20 6F r open hunting o
0000000000 6E 20 74 68 65 20 53 69 6C 6B 72 6F 61 64 21 5A n the Silkroad!Z
0000000000 18 0B 00 ................
[6119][9:22:50 PM][GameA->Shard][C->S][7866][58 bytes]
0000000000 0A 91 AB 01 00 5A 18 0B 00 00 00 00 00 00 00 00 ..«..Z..........
0000000000 00 05 00 01 6E 21 00 46 6F 72 20 6F 70 65 6E 20 ....n!.For open
0000000000 68 75 6E 74 69 6E 67 20 6F 6E 20 74 68 65 20 53 hunting on the S
0000000000 69 6C 6B 72 6F 61 64 21 00 00 ilkroad!........
[6121][9:22:50 PM][Shard->GameA][S->C][B866][9 bytes]
0000000000 91 AB 01 00 01 12 00 00 00 .«..............
[6122][9:22:50 PM][GameA->Agent][S->C][B069][48 bytes][1-Broadcast][5]
0000000000 01 12 00 00 00 00 00 00 00 05 00 01 6E 21 00 46 ............n!.F
0000000000 6F 72 20 6F 70 65 6E 20 68 75 6E 74 69 6E 67 20 or open hunting
0000000000 6F 6E 20 74 68 65 20 53 69 6C 6B 72 6F 61 64 21 on the Silkroad!
[t048][6123][9:22:50 PM][Agent->Client][S->C][B069][48 bytes]
0000000000 01 12 00 00 00 00 00 00 00 05 00 01 6E 21 00 46 ............n!.F
0000000000 6F 72 20 6F 70 65 6E 20 68 75 6E 74 69 6E 67 20 or open hunting
0000000000 6F 6E 20 74 68 65 20 53 69 6C 6B 72 6F 61 64 21 on the Silkroad!
Then, let's look at the packet flow for when the user deletes their party entry:
Code:
[t048][6159][9:22:52 PM][Client->Agent][C->S][706B][4 bytes]
0000000000 12 00 00 00 ................
[6160][9:22:52 PM][Agent->GameA][C->S][706B][8 bytes]
0000000000 12 00 00 00 5A 18 0B 00 ....Z...........
[6162][9:22:52 PM][GameA->Shard][C->S][7866][13 bytes]
0000000000 0C 91 AB 01 00 5A 18 0B 00 12 00 00 00 ..«..Z..........
[6163][9:22:52 PM][Shard->GameA][S->C][B866][9 bytes]
0000000000 91 AB 01 00 01 12 00 00 00 .«..............
[6164][9:22:52 PM][GameA->Agent][S->C][B06B][5 bytes][1-Broadcast][5]
0000000000 01 12 00 00 00 ................
[t048][6165][9:22:52 PM][Agent->Client][S->C][B06B][5 bytes]
0000000000 01 12 00 00 00 ................
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...........
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)"
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.
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).
[NEW] 4b. Right click the "Characteristics" field of the newly named section. Choose the "Change Section Flags"
[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.
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"
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
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"
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.
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.
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.
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.
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"
14. In the new window that pops up, just press OK, since we selected the right amount of bytes to fill already.
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!)
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 @  for testing out the guide and discovering the problem that the newly added 4b/4c steps solve!
|
|
|
08/25/2021, 09:52
|
#2
|
elite*gold: 0
Join Date: Oct 2020
Posts: 104
Received Thanks: 19
|
the hard explanation for beginners upload files shard or
|
|
|
08/25/2021, 13:28
|
#3
|
elite*gold: 0
Join Date: May 2006
Posts: 667
Received Thanks: 348
|
Great work, I actually thought of this from an entirely different angle.
I can simply initiate the delete of the Party entry on behalf of the Party Master using a filter, I keep track of the Party and Party Match data, (which I assume most servers should do anyway since the rise of custom events), moments before the impending DC is taking place.
This way, I'm preemptively calling the deletion of the Party match prematurely, just like it was intended to do in the first place, no harm no foul.
These might help somebody to fill in the blanks:
PHP Code:
/// <summary>
/// Represents a generic Party Match entry.
/// PartyMatch might exist while there is no underlying Party instance, depending on a the guild master action.
/// Although both are independent of each other, they are still associated with one another.
/// </summary>
public class PartyMatch
{
public DateTime formedTime = DateTime.Now;
public readonly int MatchingID;
public readonly PartySettings PartyType;
/// <summary>
/// This value may be assigned and reassigned after the Party form is editted
/// </summary>
public int AssociatedPartyID { get; set; }
public PartyPurpose PartyObjective { get; set; }
public byte MinLevel { get; set; }
public byte MaxLevel { get; set; }
public string Title { get; set; }
/// <summary>
/// The amount of time that the PartyMatch entry is active (Shown in the Party Matching window)
/// </summary>
public int EntryActiveMinutes { get { return PartyActiveTime(formedTime); } }
}
SERVER_PARTY_MATCH_EDITED_RESPONSE = 0xB06A
SERVER_PARTY_MATCHING_RESPONSE = 0xB069
PHP Code:
public static int EditPartyMatch(Packet packet, AgentModule session, bool overrideInstance = false)
{
// Called when a user successfully register (Form) a party to the party matching system
// Party should be removed from the matching system after 1 hour by default
// 0045055C address for the party matching time out (1 hour)
try
{
if (packet.ReadInt8() == 0x01) // Success flag
{
int partyMatchNumber = packet.ReadInt32();
int associatedPartyID = packet.ReadInt32(); // 0 = No Party instance associated.
PartySettings partySettings = (PartySettings)packet.ReadUInt8();
PartyPurpose partyPurpose = (PartyPurpose)packet.ReadUInt8();
byte partyMatchEntryLevel = packet.ReadUInt8();
byte partyMatchMaxLevel = packet.ReadUInt8();
string partyMatchTitle = packet.ReadAscii();
// Create an instance of PartyMatch, automatically handles it in the main container
new PartyMatch(partyMatchNumber, associatedPartyID, partySettings, partyPurpose, partyMatchEntryLevel, partyMatchMaxLevel, partyMatchTitle, session);
// Check if the reason this snippet was executed was because the Party Master
// Modified the Party Match instance
if (overrideInstance)
{
// An instance of PartyMatch with this ID already exist
// Update the old one with the new values that can be changed
if (Parties.partyMatchings.TryGetValue(partyMatchNumber, out PartyMatch partyMatch))
{
partyMatch.AssociatedPartyID = associatedPartyID;
partyMatch.PartyObjective = partyPurpose;
partyMatch.MinLevel = partyMatchEntryLevel;
partyMatch.MaxLevel = partyMatchMaxLevel;
partyMatch.Title = partyMatchTitle;
}
}
return partyMatchNumber;
}
return 0;
}
catch (Exception ex) { Utils.WriteLine($"EditPartyMatch failed: {ex}"); return 0; }
}
I should mention that if the Party ID is 0, there is a Party Match while there is no Party object associated with it. This also may change depending on the Party Master behavior.
The Party ID is stored on SR_ShardManager and it is index based, that is to be zeroed once the module is reinitialized.
To wrap it all up:
PHP Code:
// We loop through all of our PartyMatch records
foreach (var ptMatch in Parties.partyMatchings)
{
// Calculate if 1 hour has passed since the PartyMatch was formed
int activeTime = ptMatch.Value.EntryActiveMinutes;
if (ptMatch.Value.formedTime != default && activeTime >= 59)
{
// Signal to stop checking after a single attempt to close.
ptMatch.Value.formedTime = default;
// We preemptively close the PartyMatch before the 1 hour is up
// That way, the character won't get disconnected by the server modules. (0x386f on GS)
AgentModule masterSession = null;
// Get the Party
if (ptMatch.Value.AssociatedPartyID == 0)
{
// There is no associated Party, the only session is the Party Master
// Its safe to assume that the current record MatchID only resides within the Paty Master session
masterSession = SocketServer.agCons.FirstOrDefault(s => s.Value.PartyMatchingID == ptMatch.Key).Value;
}
else
{
if (Parties.parties.TryGetValue(ptMatch.Value.AssociatedPartyID, out Party party))
{
PartyMember ptMaster = party.PartyMaster;
if (ptMaster != null)
{
masterSession = ptMaster.Session;
}
}
}
// Delete the entry if we have found a session to work with
if (masterSession != null)
{
// Prepare the Party deletion packet
Packet deleteEntry = new Packet(AgentModule.CLIENT_PARTY_MATCHING_DELETE);
deleteEntry.WriteUInt32(masterSession.PartyMatchingID);
// Send the packet across the network and the security protocol
masterSession.remoteSecurity.Send(deleteEntry);
masterSession.BeginSendTo(ProxyEndpoint.Module);
Utils.WriteLine($"Party Match ID: {ptMatch.Key} has been forcefully closed on behalf of the Party Master.", Log.Special);
}
}
}
It's a bit janky, but it works.
|
|
|
08/25/2021, 14:39
|
#4
|
elite*gold: 0
Join Date: Jul 2013
Posts: 47
Received Thanks: 11
|
Thank you bro!
|
|
|
08/25/2021, 15:13
|
#5
|
elite*gold: 50
Join Date: Apr 2011
Posts: 49
Received Thanks: 9
|
Great release thanks.
|
|
|
08/25/2021, 15:48
|
#6
|
elite*gold: 0
Join Date: Jan 2011
Posts: 391
Received Thanks: 87
|
Share ur shard manager for download
|
|
|
08/25/2021, 18:33
|
#7
|
elite*gold: 0
Join Date: Oct 2019
Posts: 53
Received Thanks: 5
|
Share ur shard manager for download
|
|
|
08/25/2021, 19:27
|
#8
|
elite*gold: 100
Join Date: Sep 2017
Posts: 1,108
Received Thanks: 903
|
Never thought anyone would want parties to be auto deleted from the matching (personally think there's no point of that), that's why I gave out the timeout skip fix.
|
|
|
08/25/2021, 20:18
|
#9
|
elite*gold: 260
Join Date: Aug 2008
Posts: 560
Received Thanks: 3,778
|
Quote:
Originally Posted by Isoline*
Great work, I actually thought of this from an entirely different angle.
I can simply initiate the delete of the Party entry on behalf of the Party Master using a filter, I keep track of the Party and Party Match data, (which I assume most servers should do anyway since the rise of custom events), moments before the impending DC is taking place.
This way, I'm preemptively calling the deletion of the Party match prematurely, just like it was intended to do in the first place, no harm no foul.
|
Nice, thanks for sharing! I guess I should have mentioned that as the third viable solution, since I did see a few people talk about just solving the problem using a filter, which is totally valid and perfectly fine. My main goal was looking for a simple way to solve the problem in the files themselves, in relation to the two types of fixes that have been posted so far that involve making changes to the module.
The main reason is just simplicity/security. Giving out a proxy means you need to give out a reliable, performant, and safe solution, and that's not easy to do. One of the things I first played with, was using a module proxy to drop the 0x386f packet. Then, it was to make ShardManager also send the party id, so I could then inject the packet the user should send from Agent->Shard. It was then I realized, all I need to do is change the packet being sent in ShardManager to accomplish the same, because ShardManager is already sending GameServer a packet on behalf of the client.
That was fortunate, because I'm not quite ready to post a module proxy, which is more headache than a simple fix like this. With a ShardManager patch, virtually anyone can get access to the solution without introducing another element like a specific proxy (or code) to their setup. How people want to implement the fix is up to them though.
JellyBitz has a great GameServer addon project:  I'm sure everyone is aware of. Doing the same for ShardManager and starting with a fix like this would certainly make things simple for people who want to just download and have ready to use solutions. However, I always try to focus on the initial process and solving a problem in a way that works, and then working towards making it better.
Now that I've done a bunch of testing, I'm positive this works correctly. However, there's no need for me to try and make the best implementation for it. That's why I do guides like this with explanations, if someone wants to create a patcher that does this, great. If others want to offer doing the work for someone as a service, I'm fine with that too. This is just one little thing that needs to be fixed out of a ton of others, so it's more about keeping the cycle going of things that need to be done better, rather than just giving handouts that people will never need to do anything themselves again (which I'm against, but it's a nuanced conversation).
As I've started reversing the server files the past several months (since I started in June), and begun getting an understanding of the backend network and stuff, I'm beginning to see a lot of opportunity to doing things differently. I'll only briefly mention it, because it deserves its own topic along with examples and comparisons, but why are people still using filters that sit between the client/agentserver? I've seen a few projects from DaxterSoul and someone else who explored doing module filters, but it largely seems like people aren't building around the backend.
I still have a few bugs/exploits to check out, but right now I'm thinking there's a far easier, superior way to do filtering from the server side rather than the public facing client->server side. My old releases were not for server stuff, as they were from the pre-leaked files era, but it is painful seeing people use dev tools in a way they weren't intended. I understand people just using what's available to make do, but there's more to doing this stuff than just whatever implementation I choose to go with.
For this particular bug, if I were to try to solve it via a proxy, I'd want to use a new different type of proxy where I have support for process memory interaction, so rather than having to parse packets and rebuild state, I can just access it from the server via some means of interop. Basically, it's more like just exposing what the server already has access to, to server sided proxies, to avoid having to do "double the work" with parsing packets. To me, that's the "new and interesting" element of revisiting this stuff, so I'd not want to just settle on doing packet driven stuff like before because it works unless it was the only way to solve a particular problem.
That's not to take away from your solution or any solutions like that, because at the end of the day, we just need something that fixes the problem, and being able to fix the problem sets you apart from not being able to fix the problem. For me though, it's that pursuit of trying to find new and better ways to do things that keeps me going. I'd start with this module patch first, just to get an understanding of what the underlying code is doing (and what's what in memory), and then start to look for alternative ways to do things. The fruits of that labor are for a different thread though, but anyways, cheers!
Quote:
Originally Posted by #HB
Never thought anyone would want parties to be auto deleted from the matching (personally think there's no point of that), that's why I gave out the timeout skip fix.
|
Who wants to play a mmo in a party anyways
I kinda feel the same. I myself don't care about the actual problem/solution in terms of it being a game feature. Rather, I'm just interested in the dev aspect of it. To me it just seemed like a long time problem without a solution that fixes the underlying issue, so my goal was to make such a solution, that's it.
|
|
|
08/26/2021, 07:10
|
#10
|
elite*gold: 1014
Join Date: Apr 2015
Posts: 1,028
Received Thanks: 1,243
|
Great Release . WB Pushedx
|
|
|
08/26/2021, 13:22
|
#11
|
elite*gold: 0
Join Date: May 2006
Posts: 667
Received Thanks: 348
|
Quote:
Originally Posted by pushedx
Nice, thanks for sharing! I guess I should have mentioned that as the third viable solution, since I did see a few people talk about just solving the problem using a filter, which is totally valid and perfectly fine. My main goal was looking for a simple way to solve the problem in the files themselves, in relation to the two types of fixes that have been posted so far that involve making changes to the module.
The main reason is just simplicity/security. Giving out a proxy means you need to give out a reliable, performant, and safe solution, and that's not easy to do. One of the things I first played with, was using a module proxy to drop the 0x386f packet. Then, it was to make ShardManager also send the party id, so I could then inject the packet the user should send from Agent->Shard. It was then I realized, all I need to do is change the packet being sent in ShardManager to accomplish the same, because ShardManager is already sending GameServer a packet on behalf of the client.
That was fortunate, because I'm not quite ready to post a module proxy, which is more headache than a simple fix like this. With a ShardManager patch, virtually anyone can get access to the solution without introducing another element like a specific proxy (or code) to their setup. How people want to implement the fix is up to them though.
JellyBitz has a great GameServer addon project:  I'm sure everyone is aware of. Doing the same for ShardManager and starting with a fix like this would certainly make things simple for people who want to just download and have ready to use solutions. However, I always try to focus on the initial process and solving a problem in a way that works, and then working towards making it better.
Now that I've done a bunch of testing, I'm positive this works correctly. However, there's no need for me to try and make the best implementation for it. That's why I do guides like this with explanations, if someone wants to create a patcher that does this, great. If others want to offer doing the work for someone as a service, I'm fine with that too. This is just one little thing that needs to be fixed out of a ton of others, so it's more about keeping the cycle going of things that need to be done better, rather than just giving handouts that people will never need to do anything themselves again (which I'm against, but it's a nuanced conversation).
As I've started reversing the server files the past several months (since I started in June), and begun getting an understanding of the backend network and stuff, I'm beginning to see a lot of opportunity to doing things differently. I'll only briefly mention it, because it deserves its own topic along with examples and comparisons, but why are people still using filters that sit between the client/agentserver? I've seen a few projects from DaxterSoul and someone else who explored doing module filters, but it largely seems like people aren't building around the backend.
I still have a few bugs/exploits to check out, but right now I'm thinking there's a far easier, superior way to do filtering from the server side rather than the public facing client->server side. My old releases were not for server stuff, as they were from the pre-leaked files era, but it is painful seeing people use dev tools in a way they weren't intended. I understand people just using what's available to make do, but there's more to doing this stuff than just whatever implementation I choose to go with.
For this particular bug, if I were to try to solve it via a proxy, I'd want to use a new different type of proxy where I have support for process memory interaction, so rather than having to parse packets and rebuild state, I can just access it from the server via some means of interop. Basically, it's more like just exposing what the server already has access to, to server sided proxies, to avoid having to do "double the work" with parsing packets. To me, that's the "new and interesting" element of revisiting this stuff, so I'd not want to just settle on doing packet driven stuff like before because it works unless it was the only way to solve a particular problem.
That's not to take away from your solution or any solutions like that, because at the end of the day, we just need something that fixes the problem, and being able to fix the problem sets you apart from not being able to fix the problem. For me though, it's that pursuit of trying to find new and better ways to do things that keeps me going. I'd start with this module patch first, just to get an understanding of what the underlying code is doing (and what's what in memory), and then start to look for alternative ways to do things. The fruits of that labor are for a different thread though, but anyways, cheers!
Who wants to play a mmo in a party anyways
I kinda feel the same. I myself don't care about the actual problem/solution in terms of it being a game feature. Rather, I'm just interested in the dev aspect of it. To me it just seemed like a long time problem without a solution that fixes the underlying issue, so my goal was to make such a solution, that's it.
|
I couldn't agree more. Making a reliable proxy is not something to take lightly, let alone adding an inner-module communication one between nodes, which introduce obvious overhead. It can get real messy.
My way of fixing this was merely a proof of concept as I was already keeping track of systems like Party and Party Match, the base was already there for me so it was more convenient.
I must concede the reverse engineering approach is better. But that is assuming, that the person who make this knows what he's doing. RE is a delicate business, and require a proper extensive research and knowledge of how things should work. Even to achieve as something as "straight forward" as this.
Anything is possible, some people were even able to implement a frickin' web browser into the client. The idea of a memory based backend proxy in an effort to avoid doing "double the work" is amazing. Not only that, it could also open the door for many different things that already reside within the modules. An untapped well of abilities which could have made things much better, much earlier.
I also think you are kind of late to the party, pun intended lol.
There is a very small group of people capable of exploring and innovating new stuff, and they all keep to themselves, small groups of loners.
All the others as you can see in the comments, well, they just want the solution, no questions asked, download link attached.
Frankly for me, I never really cared about the fix and just like you, I took pleasure at the journey or the dev aspect of it. Don't get me wrong I wouldn't want to discourage you, I wish more people would contribute and aspire to do things in a better manner. But it comes to the point where not many of us left and the end goal for a dying if not already dead-game doesn't justify the means.
|
|
|
08/26/2021, 15:50
|
#12
|
elite*gold: 56
Join Date: Oct 2013
Posts: 1,165
Received Thanks: 774
|
hmm... pretty interesting apporach but that makes me think...
if the disconnect if caused by the shard sending packet 0x386F what would happen if we send this packet to the agent server?
the agent server is known to accept some shard packets and forward them to the game server... I sadly don't have a server setup yet to test it but if I had discovered that stuff my first idea would be... again something exploitable?
I mean obv. you would need the JID but what stops you from brute forcing them?
the fix would be quite simple tho... just block the packet in the proxy server aka packet filter however it would be interesting to know anyway... you got any info about that @pushedx?
|
|
|
08/26/2021, 21:51
|
#13
|
elite*gold: 260
Join Date: Aug 2008
Posts: 560
Received Thanks: 3,778
|
Quote:
Originally Posted by Syloxx
hmm... pretty interesting apporach but that makes me think...
if the disconnect if caused by the shard sending packet 0x386F what would happen if we send this packet to the agent server?
|
That case is basically covered under this:
Quote:
|
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.
|
In the case you bring up, the client is now just sending an "unknown opcode" and they would be D/Ced from the server. The problem in the party match case is that ShardManager is telling GameServer a player sent an unknown packet, when they really didn't.
As for exploiting packets with other account JIDs, this only works when Joymax messes up the packet processing, and processes the JID when they are not the last 4 bytes of the packet. Otherwise, when AgentServer relays a packet to the system, it appends your JID at the end, and under normal circumstances, that's what gets processed.
The whole JID exploit thing is why the 0x7005 exploit is a problem:
I had looked into that exploit earlier with my module proxy to understand the type of exploit it was, and it's a pretty serious design flaw. Here's that packet flow here:
The important part:
Code:
[2293237][4:29:51 PM][Agent->GameB][C->S][7005][5 bytes]
0000000000 02 2F 60 0B 00 ./`.............
[2293284][4:29:55 PM][GameB->Shard][C->S][7005][5 bytes]
0000000000 2F 60 0B 00 04 /`..............
When a user injects the exploit packet, the "GameB->Shard" packet will end up having the additional 4 bytes of their JID at the end, but to the server, it'll only display that as being unprocessed bytes and doesn't treat it as an error. That's a massive design flaw on their part, security wise, and any other operations they have that use a similar processing system are exploitable as a result (and you can see this by all the exploits people post in the 0x3XXX opcode range).
The solution  posted in that thread is more along the correct way to go about fixing those issues. You can't parse the first byte and check against 2 or not, because what if the player's JID ends with a 0x02? You have to check the total size, which would now be 5 + 4 bytes rather than 5, and you can be sure it's an exploit packet.
However, the only way to really block unknown exploits related to packet data is to have a 100% complete Client->Server packet processing code, and you santaize every single packet with the expected values. That's because of the nature of this exploit:
What if you had a correct opcode, correct size, but a data value was outside of the expected range, and resulted in such an issue? You can code specifically for each exploit, but then as soon as someone finds similar overlap elsewhere, you're going to suffer the consequences. That's why if you had 100% complete Client->Server packet processing code, you can at least verify all user input to a degree, and avoid the unknown stuff.
Of course, that type of filtering won't solve exploits that are a result of game bugs with legitimate packets and things being sent out of order or at times they're not expected. No easy way to handle that case unless you know the exact state things are supposed to be in at all times, and are actively trying to detect stuff like that, which isn't typically how games are coded.
|
|
|
08/27/2021, 05:21
|
#14
|
elite*gold: 15
Join Date: Jul 2014
Posts: 1,614
Received Thanks: 1,375
|
Some people are too good for this world, cheers.
|
|
|
08/27/2021, 20:19
|
#15
|
elite*gold: 0
Join Date: May 2010
Posts: 62
Received Thanks: 78
|
1 exploit for party matching and i don't think its fixed yet xd
if u set the title with more than 1000 character then whole party matching system will be unavailable to use for whole server until u dc xd
|
|
|
Similar Threads
|
Helllllllllllllp DC when make party matching 1 hour
03/19/2020 - SRO Private Server - 5 Replies
--REMOVED--
|
Party Matching Accept Party Packet
05/09/2018 - SRO Coding Corner - 5 Replies
while i'm parsing a party match Accept packet i have got this packet
01 00 00 00 //?? ................
EA 03 00 00 //static ................
01 //static
i want to know what is this it not same as any char i have accept it's party i thought it is a Unique id that is right?
|
Party Matching Bug
05/20/2013 - Rohan - 2 Replies
Anyone know how to do the Party matching bug in which level gaps would be removed . . . ? :mofo:
|
party matching
10/31/2007 - Silkroad Online - 0 Replies
Hi got a problem with the system i can' t push the auto match button why is that??
|
All times are GMT +1. The time now is 18:11.
|
|