Exemple #1
0
        public async Task RenameCharacter(CharacterRenameRequest packet, IRakConnection connection)
        {
            var session = Server.SessionCache.GetSession(connection.EndPoint);

            await using var ctx = new UchuContext();

            if (ctx.Characters.Any(c => c.Name == packet.Name || c.CustomName == packet.Name))
            {
                connection.Send(new CharacterRenameResponse
                {
                    ResponseId = CharacterRenamingResponse.NameAlreadyInUse
                }
                                );

                return;
            }

            var chr = await ctx.Characters.FindAsync(packet.CharacterId);

            chr.CustomName   = packet.Name;
            chr.NameRejected = false;
            chr.LastActivity = DateTimeOffset.Now.ToUnixTimeSeconds();

            await ctx.SaveChangesAsync();

            connection.Send(new CharacterRenameResponse
            {
                ResponseId = CharacterRenamingResponse.Success
            }
                            );

            await SendCharacterList(connection, session.UserId);
        }
Exemple #2
0
        public async Task RenameCharacter(CharacterRenameRequest packet, IRakConnection connection)
        {
            var session = UchuServer.SessionCache.GetSession(connection.EndPoint);

            await using var ctx = new UchuContext();

            // Check if the name already exists and return proper response if so
            if (ctx.Characters.Any(c => c.Name == packet.Name || c.CustomName == packet.Name))
            {
                connection.Send(new CharacterRenameResponse
                {
                    ResponseId = CharacterRenamingResponse.NameAlreadyInUse
                }
                                );

                return;
            }

            // If the name is free, update accordingly and notify the client
            var chr = await ctx.Characters.FindAsync(packet.CharacterId);

            chr.CustomName   = packet.Name;
            chr.NameRejected = false;
            chr.LastActivity = DateTimeOffset.Now.ToUnixTimeSeconds();

            await ctx.SaveChangesAsync();

            connection.Send(new CharacterRenameResponse
            {
                ResponseId = CharacterRenamingResponse.Success
            }
                            );

            await SendCharacterList(connection, session.UserId);
        }
Exemple #3
0
        public async Task ValidateClientHandler(SessionInfoPacket packet, IRakConnection connection)
        {
            Logger.Information($"{connection.EndPoint}'s validating client for world!");

            if (!Server.SessionCache.IsKey(packet.SessionKey))
            {
                await connection.CloseAsync();

                Logger.Warning($"{connection} attempted to connect with an invalid session key");
                return;
            }

            Server.SessionCache.RegisterKey(connection.EndPoint, packet.SessionKey);
            var session = Server.SessionCache.GetSession(connection.EndPoint);

            await using var ctx = new UchuContext();

            // Try to find the player, disconnect if the player is invalid
            var character = await ctx.Characters.FindAsync(session.CharacterId);

            if (character == null)
            {
                Logger.Warning(
                    $"{connection} attempted to connect to world with an invalid character {session.CharacterId}"
                    );

                connection.Send(new DisconnectNotifyPacket
                {
                    DisconnectId = DisconnectId.CharacterNotFound
                });

                return;
            }

            // Initialize zone for player
            var zoneId = (ZoneId)character.LastZone;

            if (zoneId == ZoneId.VentureExplorerCinematic)
            {
                zoneId = ZoneId.VentureExplorer;
            }

            var worldServer = (WorldServer)Server;
            var zone        = await worldServer.GetZoneAsync(zoneId);

            Server.SessionCache.SetZone(connection.EndPoint, zoneId);

            connection.Send(new WorldInfoPacket
            {
                ZoneId        = zoneId,
                Checksum      = zone.Checksum,
                SpawnPosition = zone.ZoneInfo.LuzFile.SpawnPoint
            });
        }
        public async Task ValidateClientHandler(SessionInfoPacket packet, IRakConnection connection)
        {
            Logger.Information($"{connection.EndPoint}'s validating client for world!");

            var verified = await UchuServer.ValidateUserAsync(connection, packet.Username, packet.SessionKey);

            if (!verified)
            {
                return;
            }

            var session = UchuServer.SessionCache.GetSession(connection.EndPoint);

            await using var ctx = new UchuContext();

            // Try to find the player, disconnect if the player is invalid
            var character = await ctx.Characters.FindAsync(session.CharacterId);

            if (character == null)
            {
                Logger.Warning(
                    $"{connection} attempted to connect to world with an invalid character {session.CharacterId}"
                    );

                connection.Send(new DisconnectNotifyPacket
                {
                    DisconnectId = DisconnectId.CharacterNotFound
                });

                return;
            }

            // Initialize zone for player
            var zoneId = (ZoneId)character.LastZone;

            if (zoneId == 0)
            {
                zoneId = 1000;
            }

            var worldServer = (WorldUchuServer)UchuServer;
            var zone        = await worldServer.GetZoneAsync(zoneId);

            UchuServer.SessionCache.SetZone(connection.EndPoint, zoneId);

            connection.Send(new WorldInfoPacket
            {
                ZoneId        = zoneId,
                Checksum      = zone.Checksum,
                SpawnPosition = zone.SpawnPosition
            });
        }
        public static void Send(this IRakConnection @this, ISerializable serializable)
        {
            if (@this == null)
            {
                throw new ArgumentNullException(nameof(@this),
                                                ResourceStrings.RakConnectionExtensions_Send_ConnectionNullException);
            }
            if (serializable == null)
            {
                throw new ArgumentNullException(nameof(serializable),
                                                ResourceStrings.RakConnectionExtensions_Send_StreamNullException);
            }
            Logger.Debug($"Sending {serializable}");

            using var stream = new MemoryStream();
            using var writer = new BitWriter(stream);

            writer.Write(serializable);

            try
            {
                @this.Send(stream.ToArray());
            }
            catch (IOException e)
            {
                Logger.Error(e);
            }
        }
Exemple #6
0
        public async Task JoinWorld(JoinWorldRequest packet, IRakConnection connection)
        {
            UchuServer.SessionCache.SetCharacter(connection.EndPoint, packet.CharacterId);

            await using var ctx = new UchuContext();
            var character = await ctx.Characters.FirstAsync(c => c.Id == packet.CharacterId);

            character.LastActivity = DateTimeOffset.Now.ToUnixTimeSeconds();
            await ctx.SaveChangesAsync();

            var zone        = (ZoneId)character.LastZone;
            var requestZone = (ZoneId)(zone == 0 ? 1000 : zone);

            // We don't want to lock up the server on a world server request, as it may take time.
            var _ = Task.Run(async() =>
            {
                // Request world server.
                var server = await ServerHelper.RequestWorldServerAsync(UchuServer, requestZone);
                if (server == default)
                {
                    // If there's no server available, error
                    var session = UchuServer.SessionCache.GetSession(connection.EndPoint);
                    await SendCharacterList(connection, session.UserId);

                    return;
                }

                // Send to world server.
                connection.Send(new ServerRedirectionPacket
                {
                    Address = UchuServer.Host,
                    Port    = (ushort)server.Port
                });
            });
        }
Exemple #7
0
        public async Task DeleteCharacter(CharacterDeleteRequest packet, IRakConnection connection)
        {
            await using var ctx = new UchuContext();

            try
            {
                ctx.Characters.Remove(await ctx.Characters.FindAsync(packet.CharacterId));
                await ctx.SaveChangesAsync();

                connection.Send(new CharacterDeleteResponse());
            }
            catch (Exception e)
            {
                Logger.Error($"Character deletion failed for {connection}'s character {packet.CharacterId}\n{e}");
                connection.Send(new CharacterDeleteResponse {
                    Success = false
                });
            }
        }
Exemple #8
0
        private static async Task SendCharacterList(IRakConnection connection, long userId)
        {
            await using var ctx = new UchuContext();

            var user = await ctx.Users.Include(u => u.Characters).ThenInclude(c => c.Items)
                       .SingleAsync(u => u.UserId == userId);

            var charCount = user.Characters.Count;
            var chars     = new CharacterListResponse.Character[charCount];

            user.Characters.Sort((u2, u1) =>
                                 DateTimeOffset.Compare(DateTimeOffset.FromUnixTimeSeconds(u1.LastActivity),
                                                        DateTimeOffset.FromUnixTimeSeconds(u2.LastActivity)));

            for (var i = 0; i < charCount; i++)
            {
                var chr = user.Characters[i];

                var items = chr.Items.Where(itm => itm.IsEquipped).Select(itm => (uint)itm.LOT).ToArray();
                chars[i] = new CharacterListResponse.Character
                {
                    CharacterId    = chr.CharacterId,
                    Name           = chr.Name,
                    UnnaprovedName = chr.CustomName,
                    NameRejected   = chr.NameRejected,
                    FreeToPlay     = chr.FreeToPlay,
                    ShirtColor     = (uint)chr.ShirtColor,
                    ShirtStyle     = (uint)chr.ShirtStyle,
                    PantsColor     = (uint)chr.PantsColor,
                    HairStyle      = (uint)chr.HairStyle,
                    HairColor      = (uint)chr.HairColor,
                    Lh             = (uint)chr.Lh,
                    Rh             = (uint)chr.Rh,
                    EyebrowStyle   = (uint)chr.EyebrowStyle,
                    EyeStyle       = (uint)chr.EyeStyle,
                    MouthStyle     = (uint)chr.MouthStyle,
                    LastZone       = (ZoneId)chr.LastZone,
                    LastInstance   = (ushort)chr.LastInstance,
                    LastClone      = (uint)chr.LastClone,
                    LastActivity   = (ulong)chr.LastActivity,
                    ItemCount      = (ushort)items.Length,
                    Items          = items
                };
            }

            connection.Send(new CharacterListResponse
            {
                CharacterCount = (byte)charCount,
                CharacterIndex = (byte)user.CharacterIndex,
                Characters     = chars
            });

            Logger.Debug($"Sent character list to {connection}");
        }
        public static void Send(this IRakConnection @this, ISerializable serializable)
        {
            Logger.Information($"Sending {serializable}");

            using var stream = new MemoryStream();
            using var writer = new BitWriter(stream);

            writer.Write(serializable);

            @this.Send(stream.ToArray());
        }
Exemple #10
0
        public async Task JoinWorld(JoinWorldRequest packet, IRakConnection connection)
        {
            Server.SessionCache.SetCharacter(connection.EndPoint, packet.CharacterId);

            await using var ctx = new UchuContext();

            var character = await ctx.Characters.FirstAsync(c => c.CharacterId == packet.CharacterId);

            character.LastActivity = DateTimeOffset.Now.ToUnixTimeSeconds();

            await ctx.SaveChangesAsync();

            var zone = (ZoneId)character.LastZone;

            var requestZone = zone == ZoneId.VentureExplorerCinematic ? ZoneId.VentureExplorer : zone;

            //
            // We don't want to lock up the server on a world server request, as it may take time.
            //

            var _ = Task.Run(async() =>
            {
                //
                // Request world server.
                //

                var server = await ServerHelper.RequestWorldServerAsync(requestZone);

                if (server == default)
                {
                    //
                    // Error
                    //

                    var session = Server.SessionCache.GetSession(connection.EndPoint);

                    await SendCharacterList(connection, session.UserId);

                    return;
                }

                //
                // Send to world server.
                //

                connection.Send(new ServerRedirectionPacket
                {
                    Address = Server.GetHost(),
                    Port    = (ushort)server.Port
                });
            });
        }
        public static void Send(this IRakConnection @this, MemoryStream stream)
        {
            if (@this == null)
            {
                throw new ArgumentNullException(nameof(@this),
                                                ResourceStrings.RakConnectionExtensions_Send_ConnectionNullException);
            }
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream),
                                                ResourceStrings.RakConnectionExtensions_Send_StreamNullException);
            }

            @this.Send(stream.ToArray());
        }
        public void Handshake(HandshakePacket packet, IRakConnection connection)
        {
            if (packet.GameVersion != 171022)
            {
                Logger.Warning($"Handshake attempted with client of Game version: {packet.GameVersion}");
                return;
            }

            const int port = 21836;

            connection.Send(new HandshakePacket
            {
                ConnectionType = Server.Port == port ? 0x01u : 0x04u,
                Address        = Server.GetHost()
            });
        }
        /// <summary>
        /// Sends a struct (packet) to a given connection.
        /// </summary>
        /// <param name="this">RakNet connection to send over.</param>
        /// <param name="packet">Packet to send.</param>
        /// <typeparam name="T">Type of the packet.</typeparam>
        /// <exception cref="ArgumentNullException">Connection is null.</exception>
        public static void Send <T>(this IRakConnection @this, T packet) where T : struct
        {
            if (@this == null)
            {
                throw new ArgumentNullException(nameof(@this),
                                                ResourceStrings.RakConnectionExtensions_Send_ConnectionNullException);
            }
            Logger.Debug($"Sending {packet}");

            try
            {
                @this.Send(StructPacketParser.WritePacket(packet).ToArray());
            }
            catch (IOException e)
            {
                Logger.Error(e);
            }
        }
Exemple #14
0
        public void Handshake(HandshakePacket packet, IRakConnection connection)
        {
            if (packet == null)
            {
                throw new ArgumentNullException(nameof(packet),
                                                ResourceStrings.GeneralHandler_Handshake_PacketNullException);
            }

            if (packet.GameVersion != 171022)
            {
                Logger.Warning($"Handshake attempted with client of Game version: {packet.GameVersion}");
            }

            connection.Send(new HandshakePacket
            {
                ConnectionType = UchuServer.Port == UchuServer.Config.Networking.AuthenticationPort ? 0x01u : 0x04u,
                Address        = UchuServer.Host
            });
        }
Exemple #15
0
        public void Handshake(HandshakePacket packet, IRakConnection connection)
        {
            if (packet == null)
            {
                throw new ArgumentNullException(nameof(packet),
                                                ResourceStrings.GeneralHandler_Handshake_PacketNullException);
            }

            if (packet.GameVersion != 171022)
            {
                Logger.Warning($"Handshake attempted with client of Game version: {packet.GameVersion}");
            }

            // TODO: Use resource / setting
            const int port = 21836;

            connection.Send(new HandshakePacket
            {
                ConnectionType = UchuServer.Port == port ? 0x01u : 0x04u,
                Address        = UchuServer.Host
            });
        }
Exemple #16
0
        public async Task ClientLoadCompleteHandler(ClientLoadCompletePacket packet, IRakConnection connection)
        {
            Logger.Information($"{connection.EndPoint}'s client load completed...");

            var session = Server.SessionCache.GetSession(connection.EndPoint);

            await using var ctx = new UchuContext();
            var character = await ctx.Characters
                            .Include(c => c.Items)
                            .Include(c => c.User)
                            .Include(c => c.Missions)
                            .ThenInclude(m => m.Tasks).ThenInclude(m => m.Values)
                            .SingleAsync(c => c.CharacterId == session.CharacterId);

            var zoneId = (ZoneId)character.LastZone;

            if (zoneId == ZoneId.VentureExplorerCinematic)
            {
                zoneId             = ZoneId.VentureExplorer;
                character.LastZone = (int)zoneId;

                await ctx.SaveChangesAsync();
            }

            Server.SessionCache.SetZone(connection.EndPoint, zoneId);

            // Zone should already be initialized at this point.
            var zone = await((WorldServer)Server).GetZoneAsync(zoneId);

            // Send the character init XML data for this world to the client
            await SendCharacterXmlDataToClient(character, connection, session);

            var player = await Player.ConstructAsync(character, connection, zone);

            if (character.LandingByRocket)
            {
                character.LandingByRocket = false;
                await ctx.SaveChangesAsync();
            }

            player.Message(new PlayerReadyMessage {
                Associate = player
            });

            player.Message(new DoneLoadingObjectsMessage {
                Associate = player
            });

            var relations = ctx.Friends.Where(f =>
                                              f.FriendTwoId == character.CharacterId
                                              ).ToArray();

            foreach (var friend in relations.Where(f => !f.RequestHasBeenSent))
            {
                connection.Send(new NotifyFriendRequestPacket
                {
                    FriendName          = (await ctx.Characters.SingleAsync(c => c.CharacterId == friend.FriendTwoId)).Name,
                    IsBestFriendRequest = friend.RequestingBestFriend
                });
            }
        }
 public static void Send(this IRakConnection @this, MemoryStream stream)
 {
     @this.Send(stream.ToArray());
 }
Exemple #18
0
        public async Task CharacterCreate(CharacterCreateRequest packet, IRakConnection connection)
        {
            var session = Server.SessionCache.GetSession(connection.EndPoint);

            uint shirtLot;
            uint pantsLot;

            await using (var ctx = new CdClientContext())
            {
                //
                //    Shirt
                //
                var shirtColor = await ctx.BrickColorsTable.FirstOrDefaultAsync(c => c.Id == packet.ShirtColor);

                var shirtName =
                    $"{(shirtColor != null ? shirtColor.Description : "Bright Red")} Shirt {packet.ShirtStyle}";
                var shirt = ctx.ObjectsTable.ToArray().FirstOrDefault(o =>
                                                                      string.Equals(o.Name, shirtName, StringComparison.CurrentCultureIgnoreCase));
                shirtLot = (uint)(shirt != null ? shirt.Id : 4049);  // Select 'Bright Red Shirt 1' if not found.

                //
                //    Pants
                //
                var pantsColor = await ctx.BrickColorsTable.FirstOrDefaultAsync(c => c.Id == packet.PantsColor);

                var pantsName = $"{(pantsColor != null ? pantsColor.Description : "Bright Red")} Pants";
                var pants     = ctx.ObjectsTable.ToArray().FirstOrDefault(o =>
                                                                          string.Equals(o.Name, pantsName, StringComparison.CurrentCultureIgnoreCase));
                pantsLot = (uint)(pants != null ? pants.Id : 2508);  // Select 'Bright Red Pants' if not found.
            }

            var first  = (await Server.Resources.ReadTextAsync("names/minifigname_first.txt")).Split('\n');
            var middle = (await Server.Resources.ReadTextAsync("names/minifigname_middle.txt")).Split('\n');
            var last   = (await Server.Resources.ReadTextAsync("names/minifigname_last.txt")).Split('\n');

            var name = (
                first[packet.Predefined.First] +
                middle[packet.Predefined.Middle] +
                last[packet.Predefined.Last]
                ).Replace("\r", "");

            await using (var ctx = new UchuContext())
            {
                if (ctx.Characters.Any(c => c.Name == packet.CharacterName))
                {
                    Logger.Debug($"{connection} character create rejected due to duplicate name");
                    connection.Send(new CharacterCreateResponse
                    {
                        ResponseId = CharacterCreationResponse.CustomNameInUse
                    }
                                    );

                    return;
                }

                if (ctx.Characters.Any(c => c.Name == name))
                {
                    Logger.Debug($"{connection} character create rejected due to duplicate pre-made name");
                    connection.Send(new CharacterCreateResponse
                    {
                        ResponseId = CharacterCreationResponse.PredefinedNameInUse
                    }
                                    );

                    return;
                }

                var user = await ctx.Users.Include(u => u.Characters).SingleAsync(u => u.UserId == session.UserId);

                user.Characters.Add(new Character
                {
                    CharacterId   = IdUtilities.GenerateObjectId(),
                    Name          = name,
                    CustomName    = packet.CharacterName,
                    ShirtColor    = packet.ShirtColor,
                    ShirtStyle    = packet.ShirtStyle,
                    PantsColor    = packet.PantsColor,
                    HairStyle     = packet.HairStyle,
                    HairColor     = packet.HairColor,
                    Lh            = packet.Lh,
                    Rh            = packet.Rh,
                    EyebrowStyle  = packet.EyebrowStyle,
                    EyeStyle      = packet.EyeStyle,
                    MouthStyle    = packet.MouthStyle,
                    LastZone      = (int)ZoneId.VentureExplorerCinematic,
                    LastInstance  = 0,
                    LastClone     = 0,
                    InventorySize = 20,
                    LastActivity  = DateTimeOffset.Now.ToUnixTimeSeconds(),
                    Items         = new List <InventoryItem>
                    {
                        new InventoryItem
                        {
                            InventoryItemId = IdUtilities.GenerateObjectId(),
                            LOT             = (int)shirtLot,
                            Slot            = 0,
                            Count           = 1,
                            InventoryType   = (int)InventoryType.Items,
                            IsEquipped      = true
                        },
                        new InventoryItem
                        {
                            InventoryItemId = IdUtilities.GenerateObjectId(),
                            LOT             = (int)pantsLot,
                            Slot            = 1,
                            Count           = 1,
                            InventoryType   = (int)InventoryType.Items,
                            IsEquipped      = true
                        }
                    },
                    CurrentImagination = 0,
                    MaximumImagination = 0
                });

                Logger.Debug(
                    $"{user.Username} created character \"{packet.CharacterName}\" with the pre-made name of \"{name}\"");

                await ctx.SaveChangesAsync();

                connection.Send(new CharacterCreateResponse
                {
                    ResponseId = CharacterCreationResponse.Success
                }
                                );

                await SendCharacterList(connection, session.UserId);
            }
        }
Exemple #19
0
        public async Task LoginRequestHandler(ClientLoginInfoPacket packet, IRakConnection connection)
        {
            await using var ctx = new UchuContext();

            var info = new ServerLoginInfoPacket
            {
                CharacterInstanceAddress = Server.GetHost(),
                CharacterInstancePort    = ushort.MaxValue,
                ChatInstanceAddress      = Server.GetHost(),
                ChatInstancePort         = 2004
            };

            var characterSpecification = await ServerHelper.GetServerByType(ServerType.Character);

            if (characterSpecification == default)
            {
                info.LoginCode = LoginCode.InsufficientPermissions;
                info.Error     = new ServerLoginInfoPacket.ErrorMessage
                {
                    Message = "No character server instance is running. Please try again later."
                };
            }
            else
            {
                info.CharacterInstancePort = (ushort)characterSpecification.Port;

                if (!await ctx.Users.AnyAsync(u => u.Username == packet.Username))
                {
                    info.LoginCode = LoginCode.InsufficientPermissions;
                    info.Error     = new ServerLoginInfoPacket.ErrorMessage
                    {
                        Message = "We have no records of that Username and Password combination. Please try again."
                    };
                }
                else
                {
                    var user = await ctx.Users.SingleAsync(u => u.Username == packet.Username);

                    if (user != null && BCrypt.Net.BCrypt.EnhancedVerify(packet.Password, user.Password))
                    {
                        if (user.Banned)
                        {
                            info.LoginCode = LoginCode.InsufficientPermissions;
                            info.Error     = new ServerLoginInfoPacket.ErrorMessage
                            {
                                Message = $"This account has been banned by an admin. Reason:\n{user.BannedReason ?? "Unknown"}"
                            };
                        }
                        else if (!string.IsNullOrWhiteSpace(user.CustomLockout))
                        {
                            info.LoginCode = LoginCode.InsufficientPermissions;
                            info.Error     = new ServerLoginInfoPacket.ErrorMessage
                            {
                                Message = user.CustomLockout
                            };
                        }
                        else
                        {
                            var key = Server.SessionCache.CreateSession(user.UserId);

                            info.LoginCode = LoginCode.Success;
                            info.UserKey   = key;

                            //
                            // I don't intend, nor do I see anyone else, using these.
                            // Except maybe FirstTimeOnSubscription for the fancy screen.
                            //

                            info.FreeToPlay = user.FreeToPlay;
                            info.FirstLoginWithSubscription = user.FirstTimeOnSubscription;

                            //
                            // No longer the first time on subscription
                            //

                            user.FirstTimeOnSubscription = false;

                            await ctx.SaveChangesAsync();
                        }
                    }
                    else
                    {
                        info.LoginCode = LoginCode.InsufficientPermissions;
                        info.Error     = new ServerLoginInfoPacket.ErrorMessage
                        {
                            Message = "We have no records of that Username and Password combination. Please try again."
                        };
                    }
                }
            }

            connection.Send(info);
        }
Exemple #20
0
        public async Task LoginRequestHandler(ClientLoginInfoPacket packet, IRakConnection connection)
        {
            await using var ctx = new UchuContext();

            var info = new ServerLoginInfoPacket
            {
                CharacterInstanceAddress = UchuServer.Host,
                CharacterInstancePort    = ushort.MaxValue,
                ChatInstanceAddress      = UchuServer.Host,
                ChatInstancePort         = 2004
            };

            var characterSpecification = await UchuServer.Api.RunCommandAsync <InstanceInfoResponse>(
                UchuServer.MasterApi, $"instance/basic?t={(int) ServerType.Character}"
                ).ConfigureAwait(false);

            if (!characterSpecification.Success)
            {
                Logger.Error(characterSpecification.FailedReason);

                info.LoginCode = LoginCode.InsufficientPermissions;
                info.Error     = new ErrorMessage
                {
                    Message = "No character server instance is running. Please try again later."
                };
            }
            else
            {
                info.CharacterInstancePort = (ushort)characterSpecification.Info.Port;

                if (!await ctx.Users.AnyAsync(u => string.Equals(u.Username.ToUpper(), packet.Username.ToUpper()) && !u.Sso))
                {
                    info.LoginCode = LoginCode.InsufficientPermissions;
                    info.Error     = new ErrorMessage
                    {
                        Message = "We have no records of that Username and Password combination. Please try again."
                    };
                }
                else
                {
                    var user = await ctx.Users.FirstAsync(u => string.Equals(u.Username.ToUpper(), packet.Username.ToUpper()) && !u.Sso);

                    if (user != null && BCrypt.Net.BCrypt.EnhancedVerify(packet.Password, user.Password))
                    {
                        if (user.Banned)
                        {
                            info.LoginCode = LoginCode.InsufficientPermissions;
                            info.Error     = new ErrorMessage
                            {
                                Message = $"This account has been banned by an admin. Reason:\n{user.BannedReason ?? "Unknown"}"
                            };
                        }
                        else if (!string.IsNullOrWhiteSpace(user.CustomLockout))
                        {
                            info.LoginCode = LoginCode.InsufficientPermissions;
                            info.Error     = new ErrorMessage
                            {
                                Message = user.CustomLockout
                            };
                        }
                        else
                        {
                            var key = UchuServer.SessionCache.CreateSession(user.Id);

                            info.LoginCode = LoginCode.Success;
                            info.UserKey   = key;

                            //
                            // I don't intend, nor do I see anyone else, using these.
                            // Except maybe FirstTimeOnSubscription for the fancy screen.
                            //

                            info.FreeToPlay = user.FreeToPlay;
                            info.FirstLoginWithSubscription = user.FirstTimeOnSubscription;

                            //
                            // No longer the first time on subscription
                            //

                            user.FirstTimeOnSubscription = false;

                            await ctx.SaveChangesAsync();
                        }
                    }
                    else
                    {
                        info.LoginCode = LoginCode.InsufficientPermissions;
                        info.Error     = new ErrorMessage
                        {
                            Message = "We have no records of that Username and Password combination. Please try again."
                        };
                    }
                }
            }

            connection.Send(info);
        }