Offering a reward for double-login fix [5165]

01/17/2014 00:08 ~Templar~#1
I've been wracking my brain over this glitch for too long, I'm willing to wire some of the money my private server has made to someone for a viable solution to this problem. (like 30 bucks)
I know its probably something SO simple I'm just missing it.
The source is tanels 5165 NewestCOServer source (no flaming).

The problem: When you open up multiple clients and type in the info and log them in at the EXACT same time, the server has no way to deal with it and logs in both (or as many as you try) at the same time.

Here is where it handles logging in to a character, and where it prevents logging a character again if it is ALREADY online:
Code:
                        if (PacketID == 1052)
                        {
                            try
                            {
                                ulong CryptoKey = BitConverter.ToUInt64(Data, 4);

                                AuthWorker.AuthInfo Info = (AuthWorker.AuthInfo)AuthWorker.KeyedClients[CryptoKey];
                                GC.AuthInfo = Info;
                                GC.MessageID = (uint)Rnd.Next(50000);
                                GC.Soc = StO.Sock;
                                if (GC.AuthInfo.LogonType == 3)
                                {
                                    GC.AddSend(Packets.SystemMessage(GC.MessageID, "You have been banned!"));
                                }

                                if (GC.AuthInfo.LogonType == 2)
                                    GC.AddSend(Packets.SystemMessage(GC.MessageID, "NEW_ROLE"));
                                else if (GC.AuthInfo.LogonType == 1)
                                {
                                    string Acc = "";
                                    GC.MyChar = Database.LoadCharacter(GC.AuthInfo.Character, ref Acc);
                                    while (Game.World.H_Chars.Contains(GC.MyChar.EntityID))
                                    {
                                        GC.MyChar = Database.LoadCharacter(GC.AuthInfo.Character, ref Acc);
                                        Game.Character Old = (Game.Character)Game.World.H_Chars[GC.MyChar.EntityID];
                                        Old.MyClient.Disconnect();
                                        Program.WriteLine("   " + GC.MyChar.Name + " was kicked off a different client.");
                                        new System.Threading.Thread(new ThreadStart(
                                        delegate()
                                        {
                                            System.Threading.Thread.Sleep(200);
                                            Game.World.H_Chars.Remove(Old.EntityID);
                                            System.Threading.Thread.Sleep(200);
                                            GC.LocalMessage(2000, "This account has been logged in from different client! You are advised to change you password immediately!");
                                        }
                                        )).Start();

                                    }
                                    GC.MyChar.MyClient = GC;
                                    GC.MyChar.AccountName = Acc;
                                    GC.AddSend(Packets.SystemMessage(GC.MessageID, "ANSWER_OK"));
                                    GC.AddSend(Packets.CharacterInfo(GC.MyChar));
                                    GC.AddSend(Packets.Status(GC.MyChar.EntityID, Game.Status.VIPLevel, GC.MyChar.VipLevel));
                                    GC.AddSend(Packets.Time());
                                    GC.AddSend(Packets.Donators(GC.MyChar));
                                    GC.AddSend(Packets.Status(GC.MyChar.EntityID, Game.Status.Effect, 0));
                                    Program.WriteLine("      " + GC.MyChar.Name + " has logged on.");
                                    foreach (Character C in World.H_Chars.Values)
                                    {
                                        if (Game.World.H_Chars.Contains(GC.MyChar.EntityID > 1))
                                        {
                                            GC.Disconnect();
                                        }
                                    }
                                }
                                GC.EndSend();
                            }
                            catch { }
                        }
                        else PacketHandler.Handle(GC, Data);
                    }
                }
                else
                {
                    GC.Crypto.Decrypt(Data);
                    PacketHandler.Handle(GC, Data);
                }
            }
            catch (Exception Exc) { Program.WriteLine(Exc); }
        }
I have tried making a LoginTimes int with LoginTimes++ every time you login and a check to see if you have more that one login.

I have tried thread sleeps to make one client login before another.

I have tried: if (Game.World.H_Chars.Contains(GC.MyChar.EntityID > 1))

No luck.

I'm all out of ideas. Any help would be greatly appreciated.
Thanks ahead of time.
01/17/2014 00:36 asdfghjklwertyuiopzxcvbnm#2
[Only registered and activated users can see links. Click Here To Register...]
lock the list of online accounts

other could correct me if im wrong, 2 clients with 2 connections invokes the same method subscribed in the socket event at the same time, each of them pass the check before the list is updated which cause this exploit
so to solve it you could lock the list of online accounts so the second connection should wait until the first lock is released which will guarantee that the list is updated and the check will disconnect the first account

please someone correct me if i am wrong, good luck and next time look at better source before you give away money
01/17/2014 01:25 Spirited#3
I'm pretty sure I released a fix for this a while back when I worked on this source. Looking back, the best thing to do would be to replace that collection with a ConcurrentDictionary. A concurrent dictionary has the "TryAdd" method, where it checks if the dictionary already contains the key, and if it does not, it adds the object to the collection and returns true.
01/17/2014 04:21 ~Templar~#4
I figured this was another simple fix but the methods of fixing it seems rather complicated.
I was hoping someone would have a fixed line of code laying around somewhere. :S

I have checked how other sources deal with packet 1052 and the one's I have looked at deal with the entire packet in just a few lines and are done with it and I see nothing involving checks for multiple logging.

I will look into both of those solutions. They sound simple in theory but putting them into practice might be above my skill level...
01/17/2014 06:33 asdfghjklwertyuiopzxcvbnm#5
just first make sure you add logged in chars to the H_Chars (couldn't find that at snippit)

and i guess their is a syntax/logical error right their
at
if (Game.World.H_Chars.Contains(GC.MyChar.EntityID > 1))

it should rather be
if (Game.World.H_Chars.Contains(GC.MyChar.EntityID) > 1)

as for right now you are invoking the list contains method with bool value of (GC.MyChar.EntityID > 1) where infact you want to compare the return value with 1

but yeah go for what fang said it's an easy fix, you will only replace the list with ConcurrentDictionary(uint, Game.Character) or something like that, and replace all add/remove methods with tryadd and tryremove, couple of checks on the returned value and you are good to go
01/17/2014 07:10 ~Templar~#6
I dropped the whole
if (Game.World.H_Chars.Contains(GC.MyChar.EntityID > 1))
thing, I forgot that was in there.

I actually found an easy fix different than any of the suggested ones by recording the name of the last player to login with
public static string LastLogin;
and checking to see if LastLogin matches the character trying to login
if (LastLogin != GC.MyChar.Name)
allow login
else throw message

after some time LastLogin goes back to null.

This essentially puts a delay time on logging in the same character.

And now it works! Thanks for the advice!
01/17/2014 12:33 Aceking#7
Quote:
Originally Posted by ~Templar~ View Post
I dropped the whole
if (Game.World.H_Chars.Contains(GC.MyChar.EntityID > 1))
thing, I forgot that was in there.

I actually found an easy fix different than any of the suggested ones by recording the name of the last player to login with
public static string LastLogin;
and checking to see if LastLogin matches the character trying to login
if (LastLogin != GC.MyChar.Name)
allow login
else throw message

after some time LastLogin goes back to null.

This essentially puts a delay time on logging in the same character.

And now it works! Thanks for the advice!
What if a third person is attempting to login at the same time as the other person with 2 characters? And it ends up like this:

Player 1 First Char Login
Player 2 Login
Player 1 Second Char Login

All within ms of each other, then your check would likely fail
01/17/2014 12:51 Y u k i#8
30$?

Easy money. Skype me :D I need more pizza and coke!
01/17/2014 19:22 Spirited#9
$30 is way too much for something like this. I'll do it for free. Here you are.


Cleaned. No more whining from certain members. I'm quite done with it. Nobody here is privileged. You all follow the same rules, no exceptions. Now, let's get back on topic, yes?