Example #1
0
        bool CanOpenMailBox(ObjectGuid guid)
        {
            if (guid == GetPlayer().GetGUID())
            {
                if (!HasPermission(RBACPermissions.CommandMailbox))
                {
                    Log.outWarn(LogFilter.ChatSystem, "{0} attempt open mailbox in cheating way.", GetPlayer().GetName());
                    return(false);
                }
            }
            else if (guid.IsGameObject())
            {
                if (!GetPlayer().GetGameObjectIfCanInteractWith(guid, GameObjectTypes.Mailbox))
                {
                    return(false);
                }
            }
            else if (guid.IsAnyTypeCreature())
            {
                if (!GetPlayer().GetNPCIfCanInteractWith(guid, NPCFlags.Mailbox, NPCFlags2.None))
                {
                    return(false);
                }
            }
            else
            {
                return(false);
            }

            return(true);
        }
Example #2
0
        void HandleMoveTimeSkipped(MoveTimeSkipped moveTimeSkipped)
        {
            Unit mover = GetPlayer().GetUnitBeingMoved();

            if (mover == null)
            {
                Log.outWarn(LogFilter.Player, $"WorldSession.HandleMoveTimeSkipped wrong mover state from the unit moved by {GetPlayer().GetGUID()}");
                return;
            }

            // prevent tampered movement data
            if (moveTimeSkipped.MoverGUID != mover.GetGUID())
            {
                Log.outWarn(LogFilter.Player, $"WorldSession.HandleMoveTimeSkipped wrong guid from the unit moved by {GetPlayer().GetGUID()}");
                return;
            }

            mover.m_movementInfo.Time += moveTimeSkipped.TimeSkipped;

            MoveSkipTime moveSkipTime = new();

            moveSkipTime.MoverGUID   = moveTimeSkipped.MoverGUID;
            moveSkipTime.TimeSkipped = moveTimeSkipped.TimeSkipped;
            mover.SendMessageToSet(moveSkipTime, _player);
        }
Example #3
0
        uint AdjustClientMovementTime(uint time)
        {
            long movementTime = (long)time + _timeSyncClockDelta;

            if (_timeSyncClockDelta == 0 || movementTime < 0 || movementTime > 0xFFFFFFFF)
            {
                Log.outWarn(LogFilter.Misc, "The computed movement time using clockDelta is erronous. Using fallback instead");
                return(GameTime.GetGameTimeMS());
            }
            else
            {
                return((uint)movementTime);
            }
        }
Example #4
0
        public void Update()
        {
            if (_initialized)
            {
                uint currentTimestamp = Time.GetMSTime();
                uint diff             = currentTimestamp - _previousTimestamp;
                _previousTimestamp = currentTimestamp;

                if (_dataSent)
                {
                    uint maxClientResponseDelay = WorldConfig.GetUIntValue(WorldCfg.WardenClientResponseDelay);
                    if (maxClientResponseDelay > 0)
                    {
                        // Kick player if client response delays more than set in config
                        if (_clientResponseTimer > maxClientResponseDelay * Time.InMilliseconds)
                        {
                            Log.outWarn(LogFilter.Warden, "{0} (latency: {1}, IP: {2}) exceeded Warden module response delay for more than {3} - disconnecting client",
                                        _session.GetPlayerInfo(), _session.GetLatency(), _session.GetRemoteAddress(), Time.secsToTimeString(maxClientResponseDelay, true));
                            _session.KickPlayer();
                        }
                        else
                        {
                            _clientResponseTimer += diff;
                        }
                    }
                }
                else
                {
                    if (diff >= _checkTimer)
                    {
                        RequestData();
                    }
                    else
                    {
                        _checkTimer -= diff;
                    }
                }
            }
        }
Example #5
0
        public override void HandleHashResult(ByteBuffer buff)
        {
            // Verify key
            if (buff.ReadBytes(20) != WardenModuleWin.ClientKeySeedHash)
            {
                Log.outWarn(LogFilter.Warden, "{0} failed hash reply. Action: {0}", _session.GetPlayerInfo(), Penalty());
                return;
            }

            Log.outDebug(LogFilter.Warden, "Request hash reply: succeed");

            // Change keys here
            _inputKey  = WardenModuleWin.ClientKeySeed;
            _outputKey = WardenModuleWin.ServerKeySeed;

            _inputCrypto.PrepareKey(_inputKey);
            _outputCrypto.PrepareKey(_outputKey);

            _initialized = true;

            _previousTimestamp = GameTime.GetGameTimeMS();
        }
Example #6
0
        //todo fix me
        public bool EvaluateOpcode(WorldPacket packet, long time)
        {
            uint maxPacketCounterAllowed = 0;// GetMaxPacketCounterAllowed(p.GetOpcode());

            // Return true if there no limit for the opcode
            if (maxPacketCounterAllowed == 0)
            {
                return(true);
            }

            if (!_PacketThrottlingMap.ContainsKey(packet.GetOpcode()))
            {
                _PacketThrottlingMap[packet.GetOpcode()] = new PacketCounter();
            }

            PacketCounter packetCounter = _PacketThrottlingMap[packet.GetOpcode()];

            if (packetCounter.lastReceiveTime != time)
            {
                packetCounter.lastReceiveTime = time;
                packetCounter.amountCounter   = 0;
            }

            // Check if player is flooding some packets
            if (++packetCounter.amountCounter <= maxPacketCounterAllowed)
            {
                return(true);
            }

            Log.outWarn(LogFilter.Network, "AntiDOS: Account {0}, IP: {1}, Ping: {2}, Character: {3}, flooding packet (opc: {4} (0x{4}), count: {5})",
                        Session.GetAccountId(), Session.GetRemoteAddress(), Session.GetLatency(), Session.GetPlayerName(), packet.GetOpcode(), packetCounter.amountCounter);

            switch (_policy)
            {
            case Policy.Log:
                return(true);

            case Policy.Kick:
                Log.outInfo(LogFilter.Network, "AntiDOS: Player kicked!");
                return(false);

            case Policy.Ban:
                BanMode bm       = (BanMode)WorldConfig.GetIntValue(WorldCfg.PacketSpoofBanmode);
                uint    duration = WorldConfig.GetUIntValue(WorldCfg.PacketSpoofBanduration);  // in seconds
                string  nameOrIp = "";
                switch (bm)
                {
                case BanMode.Character:         // not supported, ban account
                case BanMode.Account:
                    Global.AccountMgr.GetName(Session.GetAccountId(), out nameOrIp);
                    break;

                case BanMode.IP:
                    nameOrIp = Session.GetRemoteAddress();
                    break;
                }
                Global.WorldMgr.BanAccount(bm, nameOrIp, duration, "DOS (Packet Flooding/Spoofing", "Server: AutoDOS");
                Log.outInfo(LogFilter.Network, "AntiDOS: Player automatically banned for {0} seconds.", duration);
                return(false);
            }
            return(true);
        }
Example #7
0
        public override void HandleData(ByteBuffer buff)
        {
            Log.outDebug(LogFilter.Warden, "Handle data");

            _dataSent            = false;
            _clientResponseTimer = 0;

            ushort Length   = buff.ReadUInt16();
            uint   Checksum = buff.ReadUInt32();

            if (!IsValidCheckSum(Checksum, buff.GetData(), Length))
            {
                Log.outWarn(LogFilter.Warden, "{0} failed checksum. Action: {1}", _session.GetPlayerInfo(), Penalty());
                return;
            }

            // TIMING_CHECK
            {
                byte result = buff.ReadUInt8();
                // @todo test it.
                if (result == 0x00)
                {
                    Log.outWarn(LogFilter.Warden, "{0} failed timing check. Action: {1}", _session.GetPlayerInfo(), Penalty());
                    return;
                }

                uint newClientTicks = buff.ReadUInt32();

                uint ticksNow = GameTime.GetGameTimeMS();
                uint ourTicks = newClientTicks + (ticksNow - _serverTicks);

                Log.outDebug(LogFilter.Warden, "ServerTicks {0}", ticksNow);         // Now
                Log.outDebug(LogFilter.Warden, "RequestTicks {0}", _serverTicks);    // At request
                Log.outDebug(LogFilter.Warden, "Ticks {0}", newClientTicks);         // At response
                Log.outDebug(LogFilter.Warden, "Ticks diff {0}", ourTicks - newClientTicks);
            }

            BigInteger      rs;
            WardenCheck     rd;
            WardenCheckType type;
            ushort          checkFailed = 0;

            foreach (var id in _currentChecks)
            {
                rd = Global.WardenCheckMgr.GetWardenDataById(id);
                rs = Global.WardenCheckMgr.GetWardenResultById(id);

                type = rd.Type;
                switch (type)
                {
                case WardenCheckType.Memory:
                {
                    byte Mem_Result = buff.ReadUInt8();

                    if (Mem_Result != 0)
                    {
                        Log.outDebug(LogFilter.Warden, "RESULT MEM_CHECK not 0x00, CheckId {0} account Id {1}", id, _session.GetAccountId());
                        checkFailed = id;
                        continue;
                    }

                    if (buff.ReadBytes(rd.Length).Compare(rs.ToByteArray()))
                    {
                        Log.outDebug(LogFilter.Warden, "RESULT MEM_CHECK fail CheckId {0} account Id {1}", id, _session.GetAccountId());
                        checkFailed = id;
                        continue;
                    }

                    Log.outDebug(LogFilter.Warden, "RESULT MEM_CHECK passed CheckId {0} account Id {1}", id, _session.GetAccountId());
                    break;
                }

                case WardenCheckType.PageA:
                case WardenCheckType.PageB:
                case WardenCheckType.Driver:
                case WardenCheckType.Module:
                {
                    byte value = 0xE9;
                    if (buff.ReadUInt8() != value)
                    {
                        if (type == WardenCheckType.PageA || type == WardenCheckType.PageB)
                        {
                            Log.outDebug(LogFilter.Warden, "RESULT PAGE_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId());
                        }
                        if (type == WardenCheckType.Module)
                        {
                            Log.outDebug(LogFilter.Warden, "RESULT MODULE_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId());
                        }
                        if (type == WardenCheckType.Driver)
                        {
                            Log.outDebug(LogFilter.Warden, "RESULT DRIVER_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId());
                        }
                        checkFailed = id;
                        continue;
                    }

                    if (type == WardenCheckType.PageA || type == WardenCheckType.PageB)
                    {
                        Log.outDebug(LogFilter.Warden, "RESULT PAGE_CHECK passed CheckId {0} account Id {1}", id, _session.GetAccountId());
                    }
                    else if (type == WardenCheckType.Module)
                    {
                        Log.outDebug(LogFilter.Warden, "RESULT MODULE_CHECK passed CheckId {0} account Id {1}", id, _session.GetAccountId());
                    }
                    else if (type == WardenCheckType.Driver)
                    {
                        Log.outDebug(LogFilter.Warden, "RESULT DRIVER_CHECK passed CheckId {0} account Id {1}", id, _session.GetAccountId());
                    }
                    break;
                }

                case WardenCheckType.LuaStr:
                {
                    byte Lua_Result = buff.ReadUInt8();

                    if (Lua_Result != 0)
                    {
                        Log.outDebug(LogFilter.Warden, "RESULT LUA_STR_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId());
                        checkFailed = id;
                        continue;
                    }

                    byte luaStrLen = buff.ReadUInt8();
                    if (luaStrLen != 0)
                    {
                        Log.outDebug(LogFilter.Warden, "Lua string: {0}", buff.ReadString(luaStrLen));
                    }

                    Log.outDebug(LogFilter.Warden, "RESULT LUA_STR_CHECK passed, CheckId {0} account Id {1}", id, _session.GetAccountId());
                    break;
                }

                case WardenCheckType.MPQ:
                {
                    byte Mpq_Result = buff.ReadUInt8();

                    if (Mpq_Result != 0)
                    {
                        Log.outDebug(LogFilter.Warden, "RESULT MPQ_CHECK not 0x00 account id {0}", _session.GetAccountId());
                        checkFailed = id;
                        continue;
                    }

                    if (!buff.ReadBytes(20).Compare(rs.ToByteArray()))         // SHA1
                    {
                        Log.outDebug(LogFilter.Warden, "RESULT MPQ_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId());
                        checkFailed = id;
                        continue;
                    }

                    Log.outDebug(LogFilter.Warden, "RESULT MPQ_CHECK passed, CheckId {0} account Id {1}", id, _session.GetAccountId());
                    break;
                }

                default:                                            // Should never happen
                    break;
                }
            }

            if (checkFailed > 0)
            {
                WardenCheck check = Global.WardenCheckMgr.GetWardenDataById(checkFailed);
                Log.outWarn(LogFilter.Warden, "{0} failed Warden check {1}. Action: {2}", _session.GetPlayerInfo(), checkFailed, Penalty(check));
            }

            // Set hold off timer, minimum timer should at least be 1 second
            uint holdOff = WorldConfig.GetUIntValue(WorldCfg.WardenClientCheckHoldoff);

            _checkTimer = (holdOff < 1 ? 1 : holdOff) * Time.InMilliseconds;
        }
Example #8
0
        void HandleSendMail(SendMail packet)
        {
            if (packet.Info.Attachments.Count > SharedConst.MaxMailItems)                      // client limit
            {
                GetPlayer().SendMailResult(0, MailResponseType.Send, MailResponseResult.TooManyAttachments);
                return;
            }

            if (!CanOpenMailBox(packet.Info.Mailbox))
            {
                return;
            }

            if (string.IsNullOrEmpty(packet.Info.Target))
            {
                return;
            }

            Player player = GetPlayer();

            if (player.getLevel() < WorldConfig.GetIntValue(WorldCfg.MailLevelReq))
            {
                SendNotification(CypherStrings.MailSenderReq, WorldConfig.GetIntValue(WorldCfg.MailLevelReq));
                return;
            }

            ObjectGuid receiverGuid = ObjectGuid.Empty;

            if (ObjectManager.NormalizePlayerName(ref packet.Info.Target))
            {
                receiverGuid = Global.CharacterCacheStorage.GetCharacterGuidByName(packet.Info.Target);
            }

            if (receiverGuid.IsEmpty())
            {
                Log.outInfo(LogFilter.Network, "Player {0} is sending mail to {1} (GUID: not existed!) with subject {2}" +
                            "and body {3} includes {4} items, {5} copper and {6} COD copper with StationeryID = {7}",
                            GetPlayerInfo(), packet.Info.Target, packet.Info.Subject, packet.Info.Body,
                            packet.Info.Attachments.Count, packet.Info.SendMoney, packet.Info.Cod, packet.Info.StationeryID);
                player.SendMailResult(0, MailResponseType.Send, MailResponseResult.RecipientNotFound);
                return;
            }

            if (packet.Info.SendMoney < 0)
            {
                GetPlayer().SendMailResult(0, MailResponseType.Send, MailResponseResult.InternalError);
                Log.outWarn(LogFilter.Server, "Player {0} attempted to send mail to {1} ({2}) with negative money value (SendMoney: {3})",
                            GetPlayerInfo(), packet.Info.Target, receiverGuid.ToString(), packet.Info.SendMoney);
                return;
            }

            if (packet.Info.Cod < 0)
            {
                GetPlayer().SendMailResult(0, MailResponseType.Send, MailResponseResult.InternalError);
                Log.outWarn(LogFilter.Server, "Player {0} attempted to send mail to {1} ({2}) with negative COD value (Cod: {3})",
                            GetPlayerInfo(), packet.Info.Target, receiverGuid.ToString(), packet.Info.Cod);
                return;
            }

            Log.outInfo(LogFilter.Network, "Player {0} is sending mail to {1} ({2}) with subject {3} and body {4}" +
                        "includes {5} items, {6} copper and {7} COD copper with StationeryID = {8}",
                        GetPlayerInfo(), packet.Info.Target, receiverGuid.ToString(), packet.Info.Subject,
                        packet.Info.Body, packet.Info.Attachments.Count, packet.Info.SendMoney, packet.Info.Cod, packet.Info.StationeryID);

            if (player.GetGUID() == receiverGuid)
            {
                player.SendMailResult(0, MailResponseType.Send, MailResponseResult.CannotSendToSelf);
                return;
            }

            uint cost = (uint)(!packet.Info.Attachments.Empty() ? 30 * packet.Info.Attachments.Count : 30);  // price hardcoded in client

            long reqmoney = cost + packet.Info.SendMoney;

            // Check for overflow
            if (reqmoney < packet.Info.SendMoney)
            {
                player.SendMailResult(0, MailResponseType.Send, MailResponseResult.NotEnoughMoney);
                return;
            }

            if (!player.HasEnoughMoney(reqmoney) && !player.IsGameMaster())
            {
                player.SendMailResult(0, MailResponseType.Send, MailResponseResult.NotEnoughMoney);
                return;
            }

            Player receiver = Global.ObjAccessor.FindPlayer(receiverGuid);

            Team receiverTeam          = 0;
            byte mailsCount            = 0;                       //do not allow to send to one player more than 100 mails
            byte receiverLevel         = 0;
            uint receiverAccountId     = 0;
            uint receiverBnetAccountId = 0;

            if (receiver)
            {
                receiverTeam          = receiver.GetTeam();
                mailsCount            = (byte)receiver.GetMails().Count;
                receiverLevel         = (byte)receiver.getLevel();
                receiverAccountId     = receiver.GetSession().GetAccountId();
                receiverBnetAccountId = receiver.GetSession().GetBattlenetAccountId();
            }
            else
            {
                CharacterCacheEntry characterInfo = Global.CharacterCacheStorage.GetCharacterCacheByGuid(receiverGuid);
                if (characterInfo != null)
                {
                    receiverTeam      = Player.TeamForRace(characterInfo.RaceId);
                    receiverLevel     = characterInfo.Level;
                    receiverAccountId = characterInfo.AccountId;
                }

                PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_MAIL_COUNT);
                stmt.AddValue(0, receiverGuid.GetCounter());

                SQLResult result = DB.Characters.Query(stmt);
                if (!result.IsEmpty())
                {
                    mailsCount = (byte)result.Read <ulong>(0);
                }

                receiverBnetAccountId = Global.BNetAccountMgr.GetIdByGameAccount(receiverAccountId);
            }

            // do not allow to have more than 100 mails in mailbox.. mails count is in opcode byte!!! - so max can be 255..
            if (mailsCount > 100)
            {
                player.SendMailResult(0, MailResponseType.Send, MailResponseResult.RecipientCapReached);
                return;
            }

            // test the receiver's Faction... or all items are account bound
            bool accountBound = !packet.Info.Attachments.Empty();

            foreach (var att in packet.Info.Attachments)
            {
                Item item = player.GetItemByGuid(att.ItemGUID);
                if (item)
                {
                    ItemTemplate itemProto = item.GetTemplate();
                    if (itemProto == null || !itemProto.GetFlags().HasAnyFlag(ItemFlags.IsBoundToAccount))
                    {
                        accountBound = false;
                        break;
                    }
                }
            }

            if (!accountBound && player.GetTeam() != receiverTeam && !HasPermission(RBACPermissions.TwoSideInteractionMail))
            {
                player.SendMailResult(0, MailResponseType.Send, MailResponseResult.NotYourTeam);
                return;
            }

            if (receiverLevel < WorldConfig.GetIntValue(WorldCfg.MailLevelReq))
            {
                SendNotification(CypherStrings.MailReceiverReq, WorldConfig.GetIntValue(WorldCfg.MailLevelReq));
                return;
            }

            List <Item> items = new List <Item>();

            foreach (var att in packet.Info.Attachments)
            {
                if (att.ItemGUID.IsEmpty())
                {
                    player.SendMailResult(0, MailResponseType.Send, MailResponseResult.MailAttachmentInvalid);
                    return;
                }

                Item item = player.GetItemByGuid(att.ItemGUID);

                // prevent sending bag with items (cheat: can be placed in bag after adding equipped empty bag to mail)
                if (!item)
                {
                    player.SendMailResult(0, MailResponseType.Send, MailResponseResult.MailAttachmentInvalid);
                    return;
                }

                if (!item.CanBeTraded(true))
                {
                    player.SendMailResult(0, MailResponseType.Send, MailResponseResult.EquipError, InventoryResult.MailBoundItem);
                    return;
                }

                if (item.IsBoundAccountWide() && item.IsSoulBound() && player.GetSession().GetAccountId() != receiverAccountId)
                {
                    if (!item.IsBattlenetAccountBound() || player.GetSession().GetBattlenetAccountId() == 0 || player.GetSession().GetBattlenetAccountId() != receiverBnetAccountId)
                    {
                        player.SendMailResult(0, MailResponseType.Send, MailResponseResult.EquipError, InventoryResult.NotSameAccount);
                        return;
                    }
                }

                if (item.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.Conjured) || item.m_itemData.Expiration != 0)
                {
                    player.SendMailResult(0, MailResponseType.Send, MailResponseResult.EquipError, InventoryResult.MailBoundItem);
                    return;
                }

                if (packet.Info.Cod != 0 && item.HasItemFlag(ItemFieldFlags.Wrapped))
                {
                    player.SendMailResult(0, MailResponseType.Send, MailResponseResult.CantSendWrappedCod);
                    return;
                }

                if (item.IsNotEmptyBag())
                {
                    player.SendMailResult(0, MailResponseType.Send, MailResponseResult.EquipError, InventoryResult.DestroyNonemptyBag);
                    return;
                }

                items.Add(item);
            }

            player.SendMailResult(0, MailResponseType.Send, MailResponseResult.Ok);

            player.ModifyMoney(-reqmoney);
            player.UpdateCriteria(CriteriaTypes.GoldSpentForMail, cost);

            bool needItemDelay = false;

            MailDraft draft = new MailDraft(packet.Info.Subject, packet.Info.Body);

            SQLTransaction trans = new SQLTransaction();

            if (!packet.Info.Attachments.Empty() || packet.Info.SendMoney > 0)
            {
                bool log = HasPermission(RBACPermissions.LogGmTrade);
                if (!packet.Info.Attachments.Empty())
                {
                    foreach (var item in items)
                    {
                        if (log)
                        {
                            Log.outCommand(GetAccountId(), "GM {0} ({1}) (Account: {2}) mail item: {3} (Entry: {4} Count: {5}) to player: {6} ({7}) (Account: {8})",
                                           GetPlayerName(), GetPlayer().GetGUID().ToString(), GetAccountId(), item.GetTemplate().GetName(), item.GetEntry(), item.GetCount(),
                                           packet.Info.Target, receiverGuid.ToString(), receiverAccountId);
                        }

                        item.SetNotRefundable(GetPlayer()); // makes the item no longer refundable
                        player.MoveItemFromInventory(item.GetBagSlot(), item.GetSlot(), true);

                        item.DeleteFromInventoryDB(trans);     // deletes item from character's inventory
                        item.SetOwnerGUID(receiverGuid);
                        item.SetState(ItemUpdateState.Changed);
                        item.SaveToDB(trans);                  // recursive and not have transaction guard into self, item not in inventory and can be save standalone

                        draft.AddItem(item);
                    }

                    // if item send to character at another account, then apply item delivery delay
                    needItemDelay = player.GetSession().GetAccountId() != receiverAccountId;
                }

                if (log && packet.Info.SendMoney > 0)
                {
                    Log.outCommand(GetAccountId(), "GM {0} ({1}) (Account: {{2}) mail money: {3} to player: {4} ({5}) (Account: {6})",
                                   GetPlayerName(), GetPlayer().GetGUID().ToString(), GetAccountId(), packet.Info.SendMoney, packet.Info.Target, receiverGuid.ToString(), receiverAccountId);
                }
            }

            // If theres is an item, there is a one hour delivery delay if sent to another account's character.
            uint deliver_delay = needItemDelay ? WorldConfig.GetUIntValue(WorldCfg.MailDeliveryDelay) : 0;

            // Mail sent between guild members arrives instantly
            Guild guild = Global.GuildMgr.GetGuildById(player.GetGuildId());

            if (guild)
            {
                if (guild.IsMember(receiverGuid))
                {
                    deliver_delay = 0;
                }
            }

            // don't ask for COD if there are no items
            if (packet.Info.Attachments.Empty())
            {
                packet.Info.Cod = 0;
            }

            // will delete item or place to receiver mail list
            draft.AddMoney((ulong)packet.Info.SendMoney).AddCOD((uint)packet.Info.Cod).SendMailTo(trans, new MailReceiver(receiver, receiverGuid.GetCounter()), new MailSender(player), string.IsNullOrEmpty(packet.Info.Body) ? MailCheckMask.Copied : MailCheckMask.HasBody, deliver_delay);

            player.SaveInventoryAndGoldToDB(trans);
            DB.Characters.CommitTransaction(trans);
        }