예제 #1
0
        public AccountInfo(SQLResult result)
        {
            Id                   = result.Read <uint>(0);
            Login                = result.Read <string>(1);
            IsLockedToIP         = result.Read <bool>(2);
            LockCountry          = result.Read <string>(3);
            LastIP               = result.Read <string>(4);
            LoginTicketExpiry    = result.Read <uint>(5);
            IsBanned             = result.Read <ulong>(6) != 0;
            IsPermanenetlyBanned = result.Read <ulong>(7) != 0;
            PasswordVerifier     = result.Read <string>(9);
            Salt                 = result.Read <string>(10);

            GameAccounts = new Dictionary <uint, GameAccountInfo>();
            const int GameAccountFieldsOffset = 8;

            do
            {
                var account = new GameAccountInfo(result.GetFields(), GameAccountFieldsOffset);
                GameAccounts[result.Read <uint>(GameAccountFieldsOffset)] = account;
            } while (result.NextRow());
        }
예제 #2
0
        void HandleAuthSessionCallback(AuthSession authSession, SQLResult result)
        {
            // Stop if the account is not found
            if (result.IsEmpty())
            {
                Log.outError(LogFilter.Network, "HandleAuthSession: Sent Auth Response (unknown account).");
                CloseSocket();
                return;
            }

            AccountInfo account = new AccountInfo(result.GetFields());

            // For hook purposes, we get Remoteaddress at this point.
            string address = GetRemoteIpAddress().ToString();

            byte[] clientSeed = ClientTypeSeed_Win;
            if (account.game.OS == "Wn64")
            {
                clientSeed = ClientTypeSeed_Wn64;
            }
            else if (account.game.OS == "Mc64")
            {
                clientSeed = ClientTypeSeed_Mc64;
            }

            Sha256 digestKeyHash = new Sha256();

            digestKeyHash.Process(account.game.SessionKey, account.game.SessionKey.Length);
            digestKeyHash.Finish(clientSeed, clientSeed.Length);

            HmacSha256 hmac = new HmacSha256(digestKeyHash.Digest);

            hmac.Process(authSession.LocalChallenge, authSession.LocalChallenge.Count);
            hmac.Process(_serverChallenge, 16);
            hmac.Finish(AuthCheckSeed, 16);

            // Check that Key and account name are the same on client and server
            if (!hmac.Digest.Compare(authSession.Digest))
            {
                Log.outError(LogFilter.Network, "WorldSocket.HandleAuthSession: Authentication failed for account: {0} ('{1}') address: {2}", account.game.Id, authSession.RealmJoinTicket, address);
                CloseSocket();
                return;
            }

            Sha256 keyData = new Sha256();

            keyData.Finish(account.game.SessionKey, account.game.SessionKey.Length);

            HmacSha256 sessionKeyHmac = new HmacSha256(keyData.Digest);

            sessionKeyHmac.Process(_serverChallenge, 16);
            sessionKeyHmac.Process(authSession.LocalChallenge, authSession.LocalChallenge.Count);
            sessionKeyHmac.Finish(SessionKeySeed, 16);

            _sessionKey = new byte[40];
            var sessionKeyGenerator = new SessionKeyGenerator(sessionKeyHmac.Digest, 32);

            sessionKeyGenerator.Generate(_sessionKey, 40);

            // As we don't know if attempted login process by ip works, we update last_attempt_ip right away
            PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_LAST_ATTEMPT_IP);

            stmt.AddValue(0, address);
            stmt.AddValue(1, authSession.RealmJoinTicket);

            DB.Login.Execute(stmt);
            // This also allows to check for possible "hack" attempts on account

            stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_ACCOUNT_INFO_CONTINUED_SESSION);
            stmt.AddValue(0, _sessionKey.ToHexString());
            stmt.AddValue(1, account.game.Id);
            DB.Login.Execute(stmt);

            // First reject the connection if packet contains invalid data or realm state doesn't allow logging in
            if (Global.WorldMgr.IsClosed())
            {
                SendAuthResponseError(BattlenetRpcErrorCode.Denied);
                Log.outError(LogFilter.Network, "WorldSocket.HandleAuthSession: World closed, denying client ({0}).", GetRemoteIpAddress());
                CloseSocket();
                return;
            }

            if (authSession.RealmID != Global.WorldMgr.GetRealm().Id.Realm)
            {
                SendAuthResponseError(BattlenetRpcErrorCode.Denied);
                Log.outError(LogFilter.Network, "WorldSocket.HandleAuthSession: Client {0} requested connecting with realm id {1} but this realm has id {2} set in config.",
                             GetRemoteIpAddress().ToString(), authSession.RealmID, Global.WorldMgr.GetRealm().Id.Realm);
                CloseSocket();
                return;
            }

            // Must be done before WorldSession is created
            bool wardenActive = WorldConfig.GetBoolValue(WorldCfg.WardenEnabled);

            if (wardenActive && account.game.OS != "Win" && account.game.OS != "Wn64" && account.game.OS != "Mc64")
            {
                SendAuthResponseError(BattlenetRpcErrorCode.Denied);
                Log.outError(LogFilter.Network, "WorldSocket.HandleAuthSession: Client {0} attempted to log in using invalid client OS ({1}).", address, account.game.OS);
                CloseSocket();
                return;
            }

            //Re-check ip locking (same check as in auth).
            if (account.battleNet.IsLockedToIP) // if ip is locked
            {
                if (account.battleNet.LastIP != address)
                {
                    SendAuthResponseError(BattlenetRpcErrorCode.RiskAccountLocked);
                    Log.outDebug(LogFilter.Network, "HandleAuthSession: Sent Auth Response (Account IP differs).");
                    CloseSocket();
                    return;
                }
            }
            else if (!account.battleNet.LockCountry.IsEmpty() && account.battleNet.LockCountry != "00" && !_ipCountry.IsEmpty())
            {
                if (account.battleNet.LockCountry != _ipCountry)
                {
                    SendAuthResponseError(BattlenetRpcErrorCode.RiskAccountLocked);
                    Log.outDebug(LogFilter.Network, "WorldSocket.HandleAuthSession: Sent Auth Response (Account country differs. Original country: {0}, new country: {1}).", account.battleNet.LockCountry, _ipCountry);
                    CloseSocket();
                    return;
                }
            }

            long mutetime = account.game.MuteTime;

            //! Negative mutetime indicates amount of seconds to be muted effective on next login - which is now.
            if (mutetime < 0)
            {
                mutetime = Time.UnixTime + mutetime;

                stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_MUTE_TIME_LOGIN);
                stmt.AddValue(0, mutetime);
                stmt.AddValue(1, account.game.Id);
                DB.Login.Execute(stmt);
            }

            if (account.IsBanned()) // if account banned
            {
                SendAuthResponseError(BattlenetRpcErrorCode.GameAccountBanned);
                Log.outError(LogFilter.Network, "WorldSocket:HandleAuthSession: Sent Auth Response (Account banned).");
                CloseSocket();
                return;
            }

            // Check locked state for server
            AccountTypes allowedAccountType = Global.WorldMgr.GetPlayerSecurityLimit();

            if (allowedAccountType > AccountTypes.Player && account.game.Security < allowedAccountType)
            {
                SendAuthResponseError(BattlenetRpcErrorCode.ServerIsPrivate);
                Log.outInfo(LogFilter.Network, "WorldSocket:HandleAuthSession: User tries to login but his security level is not enough");
                CloseSocket();
                return;
            }

            Log.outDebug(LogFilter.Network, "WorldSocket:HandleAuthSession: Client '{0}' authenticated successfully from {1}.", authSession.RealmJoinTicket, address);

            // Update the last_ip in the database
            stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_LAST_IP);
            stmt.AddValue(0, address);
            stmt.AddValue(1, authSession.RealmJoinTicket);
            DB.Login.Execute(stmt);

            _worldSession = new WorldSession(account.game.Id, authSession.RealmJoinTicket, account.battleNet.Id, this, account.game.Security, (Expansion)account.game.Expansion,
                                             mutetime, account.game.OS, account.battleNet.Locale, account.game.Recruiter, account.game.IsRectuiter);

            // Initialize Warden system only if it is enabled by config
            //if (wardenActive)
            //_worldSession.InitWarden(_sessionKey);

            _queryProcessor.AddQuery(_worldSession.LoadPermissionsAsync().WithCallback(LoadSessionPermissionsCallback));
        }
예제 #3
0
        public void LoadGroups()
        {
            {
                uint oldMSTime = Time.GetMSTime();

                // Delete all groups whose leader does not exist
                DB.Characters.DirectExecute("DELETE FROM groups WHERE leaderGuid NOT IN (SELECT guid FROM characters)");
                // Delete all groups with less than 2 members
                DB.Characters.DirectExecute("DELETE FROM groups WHERE guid NOT IN (SELECT guid FROM group_member GROUP BY guid HAVING COUNT(guid) > 1)");

                //                                                    0              1           2             3                 4      5          6      7         8       9
                SQLResult result = DB.Characters.Query("SELECT g.leaderGuid, g.lootMethod, g.looterGuid, g.lootThreshold, g.icon1, g.icon2, g.icon3, g.icon4, g.icon5, g.icon6" +
                                                       //  10         11          12         13              14                  15                     16             17          18         19
                                                       ", g.icon7, g.icon8, g.groupType, g.difficulty, g.raiddifficulty, g.legacyRaidDifficulty, g.masterLooterGuid, g.guid, lfg.dungeon, lfg.state FROM groups g LEFT JOIN lfg_data lfg ON lfg.guid = g.guid ORDER BY g.guid ASC");
                if (result.IsEmpty())
                {
                    Log.outInfo(LogFilter.ServerLoading, "Loaded 0 group definitions. DB table `groups` is empty!");
                    return;
                }

                uint count = 0;
                do
                {
                    Group group = new Group();
                    group.LoadGroupFromDB(result.GetFields());
                    AddGroup(group);

                    // Get the ID used for storing the group in the database and register it in the pool.
                    uint storageId = group.GetDbStoreId();

                    RegisterGroupDbStoreId(storageId, group);

                    // Increase the next available storage ID
                    if (storageId == NextGroupDbStoreId)
                    {
                        NextGroupDbStoreId++;
                    }

                    ++count;
                }while (result.NextRow());

                Log.outInfo(LogFilter.ServerLoading, "Loaded {0} group definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
            }

            Log.outInfo(LogFilter.ServerLoading, "Loading Group members...");
            {
                uint oldMSTime = Time.GetMSTime();

                // Delete all rows from group_member or group_instance with no group
                DB.Characters.DirectExecute("DELETE FROM group_member WHERE guid NOT IN (SELECT guid FROM groups)");
                DB.Characters.DirectExecute("DELETE FROM group_instance WHERE guid NOT IN (SELECT guid FROM groups)");
                // Delete all members that does not exist
                DB.Characters.DirectExecute("DELETE FROM group_member WHERE memberGuid NOT IN (SELECT guid FROM characters)");

                //                                                0        1           2            3       4
                SQLResult result = DB.Characters.Query("SELECT guid, memberGuid, memberFlags, subgroup, roles FROM group_member ORDER BY guid");
                if (result.IsEmpty())
                {
                    Log.outInfo(LogFilter.ServerLoading, "Loaded 0 group members. DB table `group_member` is empty!");
                    return;
                }

                uint count = 0;

                do
                {
                    Group group = GetGroupByDbStoreId(result.Read <uint>(0));

                    if (group)
                    {
                        group.LoadMemberFromDB(result.Read <uint>(1), result.Read <byte>(2), result.Read <byte>(3), (LfgRoles)result.Read <byte>(4));
                    }
                    else
                    {
                        Log.outError(LogFilter.Server, "GroupMgr:LoadGroups: Consistency failed, can't find group (storage id: {0})", result.Read <uint>(0));
                    }

                    ++count;
                }while (result.NextRow());

                Log.outInfo(LogFilter.ServerLoading, "Loaded {0} group members in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
            }

            Log.outInfo(LogFilter.ServerLoading, "Loading Group instance saves...");
            {
                uint oldMSTime = Time.GetMSTime();

                //                                                0           1        2              3             4             5            6           7
                SQLResult result = DB.Characters.Query("SELECT gi.guid, i.map, gi.instance, gi.permanent, i.difficulty, i.resettime, i.entranceId, COUNT(g.guid) " +
                                                       "FROM group_instance gi INNER JOIN instance i ON gi.instance = i.id " +
                                                       "LEFT JOIN character_instance ci LEFT JOIN groups g ON g.leaderGuid = ci.guid ON ci.instance = gi.instance AND ci.permanent = 1 GROUP BY gi.instance ORDER BY gi.guid");
                if (result.IsEmpty())
                {
                    Log.outInfo(LogFilter.ServerLoading, "Loaded 0 group-instance saves. DB table `group_instance` is empty!");
                    return;
                }

                uint count = 0;
                do
                {
                    Group group = GetGroupByDbStoreId(result.Read <uint>(0));
                    // group will never be NULL (we have run consistency sql's before loading)

                    MapRecord mapEntry = CliDB.MapStorage.LookupByKey(result.Read <ushort>(1));
                    if (mapEntry == null || !mapEntry.IsDungeon())
                    {
                        Log.outError(LogFilter.Sql, "Incorrect entry in group_instance table : no dungeon map {0}", result.Read <ushort>(1));
                        continue;
                    }

                    uint             diff            = result.Read <byte>(4);
                    DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(diff);
                    if (difficultyEntry == null || difficultyEntry.InstanceType != mapEntry.InstanceType)
                    {
                        continue;
                    }

                    InstanceSave save = Global.InstanceSaveMgr.AddInstanceSave(mapEntry.Id, result.Read <uint>(2), (Difficulty)diff, result.Read <uint>(5), result.Read <uint>(6), result.Read <ulong>(7) != 0, true);
                    group.BindToInstance(save, result.Read <bool>(3), true);
                    ++count;
                }while (result.NextRow());

                Log.outInfo(LogFilter.ServerLoading, "Loaded {0} group-instance saves in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
            }
        }
예제 #4
0
        public void LoadGuilds()
        {
            Log.outInfo(LogFilter.ServerLoading, "Loading Guilds Definitions...");
            {
                uint oldMSTime = Time.GetMSTime();

                //          0          1       2             3              4              5              6
                SQLResult result = DB.Characters.Query("SELECT g.guildid, g.name, g.leaderguid, g.EmblemStyle, g.EmblemColor, g.BorderStyle, g.BorderColor, " +
                                                       //   7                  8       9       10            11          12
                                                       "g.BackgroundColor, g.info, g.motd, g.createdate, g.BankMoney, COUNT(gbt.guildid) " +
                                                       "FROM guild g LEFT JOIN guild_bank_tab gbt ON g.guildid = gbt.guildid GROUP BY g.guildid ORDER BY g.guildid ASC");

                if (result.IsEmpty())
                {
                    Log.outError(LogFilter.Guild, "Loaded 0 guild definitions. DB table `guild` is empty.");
                    return;
                }

                uint count = 0;
                do
                {
                    Guild guild = new Guild();

                    if (!guild.LoadFromDB(result.GetFields()))
                    {
                        continue;
                    }

                    AddGuild(guild);
                    count++;
                } while (result.NextRow());
                Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
            }

            Log.outInfo(LogFilter.ServerLoading, "Loading guild ranks...");
            {
                uint oldMSTime = Time.GetMSTime();

                // Delete orphaned guild rank entries before loading the valid ones
                DB.Characters.DirectExecute("DELETE gr FROM guild_rank gr LEFT JOIN guild g ON gr.guildId = g.guildId WHERE g.guildId IS NULL");

                //                                                   0    1      2       3                4
                SQLResult result = DB.Characters.Query("SELECT guildid, rid, rname, rights, BankMoneyPerDay FROM guild_rank ORDER BY guildid ASC, rid ASC");

                if (result.IsEmpty())
                {
                    Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild ranks. DB table `guild_rank` is empty.");
                }
                else
                {
                    uint count = 0;
                    do
                    {
                        uint  guildId = result.Read <uint>(0);
                        Guild guild   = GetGuildById(guildId);
                        if (guild)
                        {
                            guild.LoadRankFromDB(result.GetFields());
                        }

                        ++count;
                    }while (result.NextRow());

                    Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild ranks in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
                }
            }

            // 3. Load all guild members
            Log.outInfo(LogFilter.ServerLoading, "Loading guild members...");
            {
                uint oldMSTime = Time.GetMSTime();

                // Delete orphaned guild member entries before loading the valid ones
                DB.Characters.DirectExecute("DELETE gm FROM guild_member gm LEFT JOIN guild g ON gm.guildId = g.guildId WHERE g.guildId IS NULL");
                DB.Characters.DirectExecute("DELETE gm FROM guild_member_withdraw gm LEFT JOIN guild_member g ON gm.guid = g.guid WHERE g.guid IS NULL");

                //           0           1        2     3      4        5       6       7       8       9       10
                SQLResult result = DB.Characters.Query("SELECT gm.guildid, gm.guid, rank, pnote, offnote, w.tab0, w.tab1, w.tab2, w.tab3, w.tab4, w.tab5, " +
                                                       //   11      12      13       14      15       16       17      18         19         20
                                                       "w.tab6, w.tab7, w.money, c.name, c.level, c.class, c.gender, c.zone, c.account, c.logout_time " +
                                                       "FROM guild_member gm LEFT JOIN guild_member_withdraw w ON gm.guid = w.guid " +
                                                       "LEFT JOIN characters c ON c.guid = gm.guid ORDER BY gm.guildid ASC");

                if (result.IsEmpty())
                {
                    Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild members. DB table `guild_member` is empty.");
                }
                else
                {
                    uint count = 0;

                    do
                    {
                        uint  guildId = result.Read <uint>(0);
                        Guild guild   = GetGuildById(guildId);
                        if (guild)
                        {
                            guild.LoadMemberFromDB(result.GetFields());
                        }

                        ++count;
                    }while (result.NextRow());

                    Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild members in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
                }
            }

            // 4. Load all guild bank tab rights
            Log.outInfo(LogFilter.ServerLoading, "Loading bank tab rights...");
            {
                uint oldMSTime = Time.GetMSTime();

                // Delete orphaned guild bank right entries before loading the valid ones
                DB.Characters.DirectExecute("DELETE gbr FROM guild_bank_right gbr LEFT JOIN guild g ON gbr.guildId = g.guildId WHERE g.guildId IS NULL");

                //      0        1      2    3        4
                SQLResult result = DB.Characters.Query("SELECT guildid, TabId, rid, gbright, SlotPerDay FROM guild_bank_right ORDER BY guildid ASC, TabId ASC");

                if (result.IsEmpty())
                {
                    Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild bank tab rights. DB table `guild_bank_right` is empty.");
                }
                else
                {
                    uint count = 0;
                    do
                    {
                        uint  guildId = result.Read <uint>(0);
                        Guild guild   = GetGuildById(guildId);
                        if (guild)
                        {
                            guild.LoadBankRightFromDB(result.GetFields());
                        }

                        ++count;
                    }while (result.NextRow());

                    Log.outInfo(LogFilter.ServerLoading, "Loaded {0} bank tab rights in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
                }
            }

            // 5. Load all event logs
            Log.outInfo(LogFilter.ServerLoading, "Loading guild event logs...");
            {
                uint oldMSTime = Time.GetMSTime();

                DB.Characters.DirectExecute("DELETE FROM guild_eventlog WHERE LogGuid > {0}", GuildConst.EventLogMaxRecords);

                //          0        1        2          3            4            5        6
                SQLResult result = DB.Characters.Query("SELECT guildid, LogGuid, EventType, PlayerGuid1, PlayerGuid2, NewRank, TimeStamp FROM guild_eventlog ORDER BY TimeStamp DESC, LogGuid DESC");

                if (result.IsEmpty())
                {
                    Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild event logs. DB table `guild_eventlog` is empty.");
                }
                else
                {
                    uint count = 0;
                    do
                    {
                        uint  guildId = result.Read <uint>(0);
                        Guild guild   = GetGuildById(guildId);
                        if (guild)
                        {
                            guild.LoadEventLogFromDB(result.GetFields());
                        }

                        ++count;
                    }while (result.NextRow());

                    Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild event logs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
                }
            }

            // 6. Load all bank event logs
            Log.outInfo(LogFilter.ServerLoading, "Loading guild bank event logs...");
            {
                uint oldMSTime = Time.GetMSTime();

                // Remove log entries that exceed the number of allowed entries per guild
                DB.Characters.DirectExecute("DELETE FROM guild_bank_eventlog WHERE LogGuid > {0}", GuildConst.BankLogMaxRecords);

                //          0        1      2        3          4           5            6               7          8
                SQLResult result = DB.Characters.Query("SELECT guildid, TabId, LogGuid, EventType, PlayerGuid, ItemOrMoney, ItemStackCount, DestTabId, TimeStamp FROM guild_bank_eventlog ORDER BY TimeStamp DESC, LogGuid DESC");

                if (result.IsEmpty())
                {
                    Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild bank event logs. DB table `guild_bank_eventlog` is empty.");
                }
                else
                {
                    uint count = 0;
                    do
                    {
                        uint  guildId = result.Read <uint>(0);
                        Guild guild   = GetGuildById(guildId);
                        if (guild)
                        {
                            guild.LoadBankEventLogFromDB(result.GetFields());
                        }

                        ++count;
                    }while (result.NextRow());

                    Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild bank event logs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
                }
            }

            // 7. Load all news event logs
            Log.outInfo(LogFilter.ServerLoading, "Loading Guild News...");
            {
                uint oldMSTime = Time.GetMSTime();

                DB.Characters.DirectExecute("DELETE FROM guild_newslog WHERE LogGuid > {0}", GuildConst.NewsLogMaxRecords);

                //      0        1        2          3           4      5      6
                SQLResult result = DB.Characters.Query("SELECT guildid, LogGuid, EventType, PlayerGuid, Flags, Value, Timestamp FROM guild_newslog ORDER BY TimeStamp DESC, LogGuid DESC");

                if (result.IsEmpty())
                {
                    Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild event logs. DB table `guild_newslog` is empty.");
                }
                else
                {
                    uint count = 0;
                    do
                    {
                        uint  guildId = result.Read <uint>(0);
                        Guild guild   = GetGuildById(guildId);
                        if (guild)
                        {
                            guild.LoadGuildNewsLogFromDB(result.GetFields());
                        }

                        ++count;
                    }while (result.NextRow());

                    Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild new logs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
                }
            }

            // 8. Load all guild bank tabs
            Log.outInfo(LogFilter.ServerLoading, "Loading guild bank tabs...");
            {
                uint oldMSTime = Time.GetMSTime();

                // Delete orphaned guild bank tab entries before loading the valid ones
                DB.Characters.DirectExecute("DELETE gbt FROM guild_bank_tab gbt LEFT JOIN guild g ON gbt.guildId = g.guildId WHERE g.guildId IS NULL");

                //         0        1      2        3        4
                SQLResult result = DB.Characters.Query("SELECT guildid, TabId, TabName, TabIcon, TabText FROM guild_bank_tab ORDER BY guildid ASC, TabId ASC");

                if (result.IsEmpty())
                {
                    Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild bank tabs. DB table `guild_bank_tab` is empty.");
                }
                else
                {
                    uint count = 0;
                    do
                    {
                        uint  guildId = result.Read <uint>(0);
                        Guild guild   = GetGuildById(guildId);
                        if (guild)
                        {
                            guild.LoadBankTabFromDB(result.GetFields());
                        }

                        ++count;
                    }while (result.NextRow());

                    Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild bank tabs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
                }
            }

            // 9. Fill all guild bank tabs
            Log.outInfo(LogFilter.ServerLoading, "Filling bank tabs with items...");
            {
                uint oldMSTime = Time.GetMSTime();

                // Delete orphan guild bank items
                DB.Characters.DirectExecute("DELETE gbi FROM guild_bank_item gbi LEFT JOIN guild g ON gbi.guildId = g.guildId WHERE g.guildId IS NULL");

                SQLResult result = DB.Characters.Query(DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_BANK_ITEMS));
                if (result.IsEmpty())
                {
                    Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild bank tab items. DB table `guild_bank_item` or `item_instance` is empty.");
                }
                else
                {
                    uint count = 0;
                    do
                    {
                        ulong guildId = result.Read <ulong>(45);
                        Guild guild   = GetGuildById(guildId);
                        if (guild)
                        {
                            guild.LoadBankItemFromDB(result.GetFields());
                        }

                        ++count;
                    }while (result.NextRow());

                    Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild bank tab items in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
                }
            }

            // 10. Load guild achievements
            Log.outInfo(LogFilter.ServerLoading, "Loading guild achievements...");
            {
                uint oldMSTime = Time.GetMSTime();

                foreach (var pair in GuildStore)
                {
                    PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_ACHIEVEMENT);
                    stmt.AddValue(0, pair.Key);
                    SQLResult achievementResult = DB.Characters.Query(stmt);

                    stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_ACHIEVEMENT_CRITERIA);
                    stmt.AddValue(0, pair.Key);
                    SQLResult criteriaResult = DB.Characters.Query(stmt);

                    pair.Value.GetAchievementMgr().LoadFromDB(achievementResult, criteriaResult);
                }

                Log.outInfo(LogFilter.ServerLoading, "Loaded guild achievements and criterias in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime));
            }

            // 11. Validate loaded guild data
            Log.outInfo(LogFilter.Server, "Validating data of loaded guilds...");
            {
                uint oldMSTime = Time.GetMSTime();

                foreach (var guild in GuildStore.ToList())
                {
                    if (!guild.Value.Validate())
                    {
                        GuildStore.Remove(guild.Key);
                    }
                }

                Log.outInfo(LogFilter.ServerLoading, "Validated data of loaded guilds in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime));
            }
        }