diff --git a/Maple2.File.Ingest/Mapper/ServerTableMapper.cs b/Maple2.File.Ingest/Mapper/ServerTableMapper.cs index 731ed03b1..b2715973b 100644 --- a/Maple2.File.Ingest/Mapper/ServerTableMapper.cs +++ b/Maple2.File.Ingest/Mapper/ServerTableMapper.cs @@ -1647,8 +1647,8 @@ private ShopItemTable ParseShopItems() { restrictedBuyData = new RestrictedBuyData { Days = item.dayOfWeek.Length == 0 ? [] : Array.ConvertAll(item.dayOfWeek, day => (ShopBuyDay) day), TimeRanges = buyTimeOfDays, - StartTime = string.IsNullOrEmpty(item.startDate) ? 0 : DateTime.ParseExact(item.startDate, "yyyy-MM-dd-HH-mm", CultureInfo.InvariantCulture).ToEpochSeconds(), - EndTime = string.IsNullOrEmpty(item.endDate) ? 0 : DateTime.ParseExact(item.endDate, "yyyy-MM-dd-HH-mm-ss", CultureInfo.InvariantCulture).ToEpochSeconds(), + StartTime = string.IsNullOrEmpty(item.startDate) ? 0 : DateTime.ParseExact(item.startDate, "yyyy-MM-dd-HH-mm-ss", CultureInfo.InvariantCulture).ToEpochSeconds(), + EndTime = string.IsNullOrEmpty(item.endDate) ? 0 : DateTime.ParseExact(item.endDate, "yyyy-MM-dd-HH-mm-ss", CultureInfo.InvariantCulture).ToEpochSeconds() }; } diff --git a/Maple2.Model/Game/Emote.cs b/Maple2.Model/Game/Emote.cs index 138d121e9..4ee2b77d9 100644 --- a/Maple2.Model/Game/Emote.cs +++ b/Maple2.Model/Game/Emote.cs @@ -2,11 +2,14 @@ namespace Maple2.Model.Game; -[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 16)] +[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 26)] public readonly struct Emote { public readonly int Id; public readonly int Level = 1; + public readonly bool Unknown1; public readonly long ExpiryTime; + public readonly long UnknownTime; + public readonly bool Unknown2; public Emote(int id, long expiryTime = 0) { Id = id; diff --git a/Maple2.Model/Game/Item/Item.cs b/Maple2.Model/Game/Item/Item.cs index eb49af3a8..4b0085040 100644 --- a/Maple2.Model/Game/Item/Item.cs +++ b/Maple2.Model/Game/Item/Item.cs @@ -195,7 +195,6 @@ private long GetExpiryTime() { public void WriteTo(IByteWriter writer) { writer.WriteInt(Amount); - writer.WriteInt(); writer.WriteInt(-1); writer.WriteLong(CreationTime); writer.WriteLong(ExpiryTime); @@ -221,19 +220,18 @@ public void WriteTo(IByteWriter writer) { } else if (Music != null) { writer.WriteClass(Music); } else if (Badge != null) { - writer.WriteClass(Badge); + writer.WriteClass(Badge); // TODO } writer.WriteClass(Transfer ?? ItemTransfer.Default); writer.WriteClass(Socket ?? ItemSocket.Default); writer.WriteClass(CoupleInfo ?? ItemCoupleInfo.Default); - writer.WriteClass(Binding ?? ItemBinding.Default); + writer.WriteClass(Transfer?.Binding ?? ItemBinding.Default); } public void ReadFrom(IByteReader reader) { Amount = reader.ReadInt(); reader.ReadInt(); - reader.ReadInt(); CreationTime = reader.ReadLong(); ExpiryTime = reader.ReadLong(); reader.ReadLong(); diff --git a/Maple2.Model/Game/Item/ItemEnchant.cs b/Maple2.Model/Game/Item/ItemEnchant.cs index 5118fe0d9..9c4417a0e 100644 --- a/Maple2.Model/Game/Item/ItemEnchant.cs +++ b/Maple2.Model/Game/Item/ItemEnchant.cs @@ -34,7 +34,9 @@ public void WriteTo(IByteWriter writer) { writer.WriteInt(Enchants); writer.WriteInt(EnchantExp); writer.WriteByte(EnchantCharges); - writer.WriteLong(); // Destabilized timestamp + + writer.WriteLong(); + writer.WriteInt(); writer.WriteInt(); writer.WriteInt();// Enchantment attempts writer.WriteBool(Tradeable); diff --git a/Maple2.Model/Game/Item/ItemOption.cs b/Maple2.Model/Game/Item/ItemOption.cs index f8369b6b4..75fb44aa8 100644 --- a/Maple2.Model/Game/Item/ItemOption.cs +++ b/Maple2.Model/Game/Item/ItemOption.cs @@ -3,27 +3,27 @@ namespace Maple2.Model.Game; -[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 8)] -public readonly record struct BasicOption(int Value, float Rate = 0) { - public BasicOption(float percent) : this(0, percent) { } +[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 4)] +public readonly record struct BasicOption(int Value) { + public BasicOption(float percent) : this((int) percent * 100) { } public static BasicOption operator +(BasicOption self, BasicOption other) { - return new BasicOption(self.Value + other.Value, self.Rate + other.Rate); + return new BasicOption(self.Value + other.Value); } public static BasicOption operator -(BasicOption self, BasicOption other) { - return new BasicOption(Math.Max(self.Value - other.Value, 0), Math.Max(self.Rate - other.Rate, 0)); + return new BasicOption(Math.Max(self.Value - other.Value, 0)); } } -[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 8)] -public readonly record struct SpecialOption(float Rate, float Value = 0) { +[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 4)] +public readonly record struct SpecialOption(float Rate) { public static SpecialOption operator +(SpecialOption self, SpecialOption other) { - return new SpecialOption(self.Rate + other.Rate, self.Value + other.Value); + return new SpecialOption(self.Rate + other.Rate); } public static SpecialOption operator -(SpecialOption self, SpecialOption other) { - return new SpecialOption(Math.Max(self.Rate - other.Rate, 0), Math.Max(self.Value - other.Value, 0)); + return new SpecialOption(Math.Max(self.Rate - other.Rate, 0)); } } diff --git a/Maple2.Model/Game/Item/ItemStats.cs b/Maple2.Model/Game/Item/ItemStats.cs index a26bd4a3c..10b433145 100644 --- a/Maple2.Model/Game/Item/ItemStats.cs +++ b/Maple2.Model/Game/Item/ItemStats.cs @@ -9,18 +9,14 @@ namespace Maple2.Model.Game; public sealed class ItemStats : IByteSerializable, IByteDeserializable { public static readonly ItemStats Default = new ItemStats(); - public const int TYPE_COUNT = 9; + public const int TYPE_COUNT = 5; public enum Type { Constant = 0, Static = 1, Random = 2, Title = 3, - Empowerment1 = 4, - Empowerment2 = 5, - Empowerment3 = 6, - Empowerment4 = 7, - Empowerment5 = 8, + Empowerment = 4, } private readonly Option[] options; @@ -58,7 +54,6 @@ public Option this[Type type] { } public void WriteTo(IByteWriter writer) { - writer.WriteByte(); for (int i = 0; i < TYPE_COUNT; i++) { Option option = options[i]; writer.WriteShort((short) option.Basic.Count); @@ -71,13 +66,12 @@ public void WriteTo(IByteWriter writer) { writer.WriteShort((short) type); writer.Write(specialOption); } - - writer.WriteInt(); } + + // writer.WriteInt(); // this version of kms2 does not have this? } public void ReadFrom(IByteReader reader) { - reader.ReadByte(); for (int i = 0; i < TYPE_COUNT; i++) { Option option = options[i]; short basicCount = reader.ReadShort(); @@ -91,7 +85,7 @@ public void ReadFrom(IByteReader reader) { option.Special[type] = reader.Read(); } - reader.ReadInt(); + // reader.ReadInt(); // this version of kms2 does not have this? } } diff --git a/Maple2.Model/Game/Item/ItemTransfer.cs b/Maple2.Model/Game/Item/ItemTransfer.cs index d632627d1..3eee921d0 100644 --- a/Maple2.Model/Game/Item/ItemTransfer.cs +++ b/Maple2.Model/Game/Item/ItemTransfer.cs @@ -45,7 +45,7 @@ public void WriteTo(IByteWriter writer) { writer.WriteBool(false); // CItemTransfer[9] *bit-1* writer.WriteInt(RemainTrades); // CItemTransfer[10] writer.WriteInt(RepackageCount); // CItemTransfer[11] - writer.WriteByte(); // CItemTransfer[12] + writer.WriteBool(false); // CItemTransfer[12] writer.WriteBool(true); // CItemTransfer[9] *bit-10* (socketTransfer?) // CharBound means untradable, unsellable, bound to char (ignores TransferFlag) @@ -60,7 +60,7 @@ public void ReadFrom(IByteReader reader) { reader.ReadByte(); RemainTrades = reader.ReadInt(); RepackageCount = reader.ReadInt(); - reader.ReadByte(); + reader.ReadBool(); reader.ReadBool(); bool isBound = reader.ReadBool(); if (isBound) { diff --git a/Maple2.Model/Game/Sync/StateSync.cs b/Maple2.Model/Game/Sync/StateSync.cs index c935bbb9a..647394dab 100644 --- a/Maple2.Model/Game/Sync/StateSync.cs +++ b/Maple2.Model/Game/Sync/StateSync.cs @@ -35,18 +35,15 @@ public enum Flag : byte { public int SyncNumber; #region Flag1 - public int EmotionId; // Skill ID for emotes + flying mounts (not ground) - public short Flag1Unknown2; // Goes to 1 for flying mounts + public int Flag1Unknown; #endregion #region Flag2 - public Vector3 Flag2Unknown1; - public string? Flag2Unknown2; + public int Flag2Unknown; #endregion #region Flag3 - public int Flag3Unknown1; - public string? Flag3Unknown2; + public short Flag3Unknown; #endregion #region AnimationFlag @@ -54,33 +51,23 @@ public enum Flag : byte { #endregion #region Flag5 - public int Flag5Unknown1; + public Vector3 Flag5Unknown1; public string? Flag5Unknown2; #endregion #region Flag6 public int Flag6Unknown1; - public int Flag6Unknown2; - public byte Flag6Unknown3; - public Vector3 Flag6Position; - public Vector3 Flag6Rotation; + public string? Flag6Unknown2; #endregion public virtual void WriteTo(IByteWriter writer) { writer.Write(State); writer.Write(SubState); - writer.Write(Flags); - - if (Flags.HasFlag(Flag.Flag1)) { - writer.WriteInt(EmotionId); - writer.WriteShort(Flag1Unknown2); - } - writer.Write(Position); writer.WriteShort(Rotation); writer.WriteByte(Animation); - if (Animation > 127) { + if (Animation == 128) { writer.WriteFloat(UnknownFloat1); writer.WriteFloat(UnknownFloat2); } @@ -89,46 +76,39 @@ public virtual void WriteTo(IByteWriter writer) { writer.WriteByte(Unknown1); writer.WriteShort(Rotation2); writer.WriteShort(Unknown3); + + writer.Write(Flags); + if (Flags.HasFlag(Flag.Flag1)) { + writer.WriteInt(Flag1Unknown); + } if (Flags.HasFlag(Flag.Flag2)) { - writer.Write(Flag2Unknown1); - writer.WriteUnicodeString(Flag2Unknown2 ?? ""); + writer.WriteInt(Flag2Unknown); } if (Flags.HasFlag(Flag.Flag3)) { - writer.WriteInt(Flag3Unknown1); - writer.WriteUnicodeString(Flag3Unknown2 ?? ""); + writer.WriteShort(Flag3Unknown); } if (Flags.HasFlag(Flag.Animation)) { writer.WriteUnicodeString(AnimationName ?? ""); } if (Flags.HasFlag(Flag.Flag5)) { - writer.WriteInt(Flag5Unknown1); + writer.Write(Flag5Unknown1); writer.WriteUnicodeString(Flag5Unknown2 ?? ""); } if (Flags.HasFlag(Flag.Flag6)) { writer.WriteInt(Flag6Unknown1); - writer.WriteInt(Flag6Unknown2); - writer.WriteByte(Flag6Unknown3); - writer.Write(Flag6Position); - writer.Write(Flag6Rotation); + writer.WriteUnicodeString(Flag6Unknown2 ?? ""); } - writer.WriteInt(SyncNumber); } public virtual void ReadFrom(IByteReader reader) { State = reader.Read(); SubState = reader.Read(); - Flags = reader.Read(); - - if (Flags.HasFlag(Flag.Flag1)) { - EmotionId = reader.ReadInt(); - Flag1Unknown2 = reader.ReadShort(); - } Position = reader.Read(); Rotation = reader.ReadShort(); // CoordS / 10 (Rotation?) Animation = reader.ReadByte(); - if (Animation > 127) { // if animation < 0 (signed) + if (Animation == 128) { UnknownFloat1 = reader.ReadFloat(); UnknownFloat2 = reader.ReadFloat(); } @@ -137,27 +117,26 @@ public virtual void ReadFrom(IByteReader reader) { Rotation2 = reader.ReadShort(); // CoordS / 10 Unknown3 = reader.ReadShort(); // CoordS / 1000 + Flags = reader.Read(); + if (Flags.HasFlag(Flag.Flag1)) { + Flag1Unknown = reader.ReadInt(); + } if (Flags.HasFlag(Flag.Flag2)) { - Flag2Unknown1 = reader.Read(); - Flag2Unknown2 = reader.ReadUnicodeString(); + Flag2Unknown = reader.ReadInt(); } if (Flags.HasFlag(Flag.Flag3)) { - Flag3Unknown1 = reader.ReadInt(); - Flag3Unknown2 = reader.ReadUnicodeString(); + Flag3Unknown = reader.ReadShort(); } if (Flags.HasFlag(Flag.Animation)) { AnimationName = reader.ReadUnicodeString(); } if (Flags.HasFlag(Flag.Flag5)) { - Flag5Unknown1 = reader.ReadInt(); + Flag5Unknown1 = reader.Read(); Flag5Unknown2 = reader.ReadUnicodeString(); } if (Flags.HasFlag(Flag.Flag6)) { Flag6Unknown1 = reader.ReadInt(); - Flag6Unknown2 = reader.ReadInt(); - Flag6Unknown3 = reader.ReadByte(); - Flag6Position = reader.Read(); - Flag6Rotation = reader.Read(); + Flag6Unknown2 = reader.ReadUnicodeString(); } SyncNumber = reader.ReadInt(); @@ -169,13 +148,13 @@ public override string ToString() { builder.AppendLine($" Position:{Position}, Rotation:{Rotation}, Speed:{Speed}"); builder.AppendLine($" Animation:{Animation} ({UnknownFloat1}, {UnknownFloat2}), Unknown1:{Unknown1}, Rotation2:{Rotation2}, Unknown3:{Unknown3}"); if (Flags.HasFlag(Flag.Flag1)) { - builder.Append($"Flag1: {EmotionId}, {Flag1Unknown2}"); + builder.Append($"Flag1: {Flag1Unknown}"); } if (Flags.HasFlag(Flag.Flag2)) { - builder.Append($"Flag2: {Flag2Unknown1}, {Flag2Unknown2}"); + builder.Append($"Flag2: {Flag2Unknown}"); } if (Flags.HasFlag(Flag.Flag3)) { - builder.Append($"Flag3: {Flag3Unknown1}, {Flag3Unknown2}"); + builder.Append($"Flag3: {Flag3Unknown}"); } if (Flags.HasFlag(Flag.Animation)) { builder.Append($"Animation: {AnimationName}"); @@ -184,8 +163,7 @@ public override string ToString() { builder.Append($"Flag5: {Flag5Unknown1}, {Flag5Unknown2}"); } if (Flags.HasFlag(Flag.Flag6)) { - builder.Append($"Flag6: {Flag6Unknown1}, {Flag6Unknown2}, {Flag6Unknown3}"); - builder.Append($"- Position:{Flag6Position}, Rotation:{Flag6Rotation}"); + builder.Append($"Flag6: {Flag6Unknown1}, {Flag6Unknown2}"); } return builder.ToString(); diff --git a/Maple2.Model/Game/User/SkillPoint.cs b/Maple2.Model/Game/User/SkillPoint.cs index 938535c85..f42422670 100644 --- a/Maple2.Model/Game/User/SkillPoint.cs +++ b/Maple2.Model/Game/User/SkillPoint.cs @@ -34,6 +34,7 @@ public void WriteTo(IByteWriter writer) { writer.Write(source); writer.WriteClass(point); } + writer.WriteInt(); } public class PointRank : IByteSerializable { diff --git a/Maple2.Model/Validators/CharacterNameValidator.cs b/Maple2.Model/Validators/CharacterNameValidator.cs new file mode 100644 index 000000000..b6bc7ae35 --- /dev/null +++ b/Maple2.Model/Validators/CharacterNameValidator.cs @@ -0,0 +1,88 @@ +using System.Text.RegularExpressions; +using Maple2.Model.Error; +using Maple2.Model.Metadata; + +namespace Maple2.Model.Validators; + +public static class CharacterNameValidator { + // Common forbidden words that should not be allowed in character names + private static readonly HashSet ForbiddenWords = new(StringComparer.OrdinalIgnoreCase) { + "admin", "moderator", "gm", "gamemaster", "staff", "bot", "system", "server", + "maple", "nexon", "maplestory", "maple2", "administrator", "support", "helper", + "fuck", "shit", "bitch", "damn", "ass", "hell", "crap", "piss" + }; + + // Names that are completely banned + private static readonly HashSet BannedNames = new(StringComparer.OrdinalIgnoreCase) { + "admin", "administrator", "moderator", "gm", "gamemaster", "staff", "system", "server", + "maple", "nexon", "maplestory", "maple2", "support", "helper", "bot", "null", "undefined" + }; + + // Regex pattern for valid character names (Unicode letters, numbers, and some special characters, no spaces) + private static readonly Regex ValidNamePattern = new(@"^[\p{L}0-9\-_]+$", RegexOptions.Compiled); + + /// + /// Validates a character name according to all rules. + /// + /// The character name to validate + /// CharacterCreateError code if invalid, null if valid + public static CharacterCreateError? ValidateName(string name) { + if (string.IsNullOrWhiteSpace(name)) { + return CharacterCreateError.s_char_err_name; + } + + // Use the original name for validation + string validatedName = name; + + // Check length constraints + if (validatedName.Length < Constant.CharacterNameLengthMin) { + return CharacterCreateError.s_char_err_name; + } + + if (validatedName.Length > Constant.CharacterNameLengthMax) { + return CharacterCreateError.s_char_err_system; + } + + // Check if the name is completely banned + if (BannedNames.Contains(validatedName)) { + return CharacterCreateError.s_char_err_ban_all; + } + + // Check for forbidden words + if (ContainsForbiddenWord(validatedName)) { + return CharacterCreateError.s_char_err_ban_any; + } + + // Check character pattern (Unicode letters, numbers, hyphens, underscores only, no spaces) + if (!ValidNamePattern.IsMatch(validatedName)) { + return CharacterCreateError.s_char_err_name; + } + + // Check for names that are only whitespace/special characters + if (validatedName.All(c => !char.IsLetterOrDigit(c))) { + return CharacterCreateError.s_char_err_name; + } + + return null; // Valid name + } + + /// + /// Checks if the name contains any forbidden words. + /// + /// The name to check + /// True if it contains forbidden words + private static bool ContainsForbiddenWord(string name) { + string lowerName = name.ToLowerInvariant(); + return ForbiddenWords.Any(word => lowerName.Contains(word)); + } + + /// + /// Gets the forbidden word that was found in the name (for error messages). + /// + /// The name to check + /// The forbidden word found, or null if none + public static string? GetForbiddenWord(string name) { + string lowerName = name.ToLowerInvariant(); + return ForbiddenWords.FirstOrDefault(word => lowerName.Contains(word)); + } +} diff --git a/Maple2.Server.Core/Constants/RecvOp.cs b/Maple2.Server.Core/Constants/RecvOp.cs index 521ee1ec6..7840a962b 100644 --- a/Maple2.Server.Core/Constants/RecvOp.cs +++ b/Maple2.Server.Core/Constants/RecvOp.cs @@ -1,6 +1,7 @@ namespace Maple2.Server.Core.Constants; -public enum RecvOp : ushort { +public enum RecvOp : ushort +{ Null = 0x000, ResponseVersion = 0x0001, Patch = 0x0002, @@ -56,136 +57,136 @@ public enum RecvOp : ushort { GuideObjectSync = 0x0035, RequestSetCraftMode = 0x0036, RequestCube = 0x0037, - RequestApartment = 0x0038, - Ugc = 0x0039, - MeretMarket = 0x003A, - KeyTable = 0x003B, - Channel = 0x003C, - Liftable = 0x003D, - MyInfo = 0x003E, - RelocateWorld = 0x003F, - ItemMerge = 0x0040, - RequestRide = 0x0041, - RideSync = 0x0042, - FittingDoll = 0x0043, - BonusGame = 0x0044, - ResolvePenalty = 0x0045, - TakeBoat = 0x0046, - Achieve = 0x0047, - BadgeEquip = 0x0048, - RequestTaxi = 0x0049, - Trade = 0x004A, - RequestWorldmap = 0x004B, - Guild = 0x004C, - GroupChat = 0x004D, - RequestHomeBank = 0x004E, - RequestHomeDoctor = 0x004F, - Ah = 0x0050, - SetDebugMode = 0x0051, - RequestHome = 0x0052, - RequestReport = 0x0053, - FurnishingStorage = 0x0054, - RequestMoveEventField = 0x0055, - LogSend = 0x0056, - DpsMode = 0x0057, - GuideRecord = 0x0058, - Rank = 0x0059, - RequestSkipTutorial = 0x005A, - ItemDismantle = 0x005B, - RideConsumeEp = 0x005C, - RequestAdditionalEffect = 0x005D, - RecallUser = 0x005E, - RequestItemEnchant = 0x005F, - BlackMarket = 0x0060, - Gamble = 0x0061, - Pvp = 0x0062, - Maid = 0x0063, - NewsNotification = 0x0064, - SmartRecommendBilling = 0x0065, - SystemShop = 0x0066, - Attendance = 0x0067, - PcBangBonus = 0x0068, - MaidCraftItem = 0x0069, - RequestUserEnv = 0x006A, - Cash = 0x006B, - Insignia = 0x006C, - RequestMoveField = 0x006D, - WaitingTicket = 0x006E, - PartySearch = 0x006F, - RecallScroll = 0x0070, - PotentialAbility = 0x0071, - EnchantScroll = 0x0072, - GlobalPortal = 0x0073, - Fishing = 0x0074, - PlayInstrument = 0x0075, - ChangeAttributes = 0x0076, - ChangeAttributesScroll = 0x0077, - TransferEnchant = 0x0078, - RequestPet = 0x0079, - RequestPetInventory = 0x007A, - NoticeDialog = 0x007B, - SkillMacro = 0x007C, - Banword = 0x007D, - CheckCharName = 0x007E, - PlatformProtectPacket = 0x007F, - PlatformAccountSafe = 0x0080, - State = 0x0081, - MesoMarket = 0x0082, - GlobalFactor = 0x0083, - SmartPush = 0x0084, - PlayArcade = 0x0085, - EnterEventField = 0x0086, - CardReverseGame = 0x0087, - RequestItemLock = 0x0088, - ItemSocketSystem = 0x0089, - CharacterAbility = 0x008A, - Tutorial = 0x008B, - ItemSocketScroll = 0x008C, - ItemRepack = 0x008D, - Mapleopoly = 0x008E, - PremiumClub = 0x008F, - HomeAction = 0x0091, - Mastery = 0x0093, - ConstructShop = 0x0094, - Club = 0x0096, - NexonArena = 0x0097, - EventReward = 0x0098, - NameChange = 0x0099, - AttributePoint = 0x009A, - ItemRepeat = 0x009B, - ItemExchangeScroll = 0x009C, - RequestTutorialItem = 0x009D, - DungeonReward = 0x009E, - PlayerHost = 0x009F, - TreasureMap = 0x00A0, - ItemExtractionScroll = 0x00A1, // GlamourAnvil - Mentor = 0x00A2, - RequestSkillBookTree = 0x00A3, - BuddyEmote = 0x00A4, - BuddyBadge = 0x00A5, - SyncWorld = 0x00A6, - BirthdayCard = 0x00A7, - SuperWorldChat = 0x00A8, - Microgame = 0x00A9, - StateReact = 0x00AA, - TreeWatering = 0x00AB, - Survival = 0x00AC, - SpectateMushtopia = 0x00AD, - Prestige = 0x00AE, - Lapenshard = 0x00AF, - Ugd = 0x00B0, - WorldChampion = 0x00B1, - ServerEnter = 0x00B2, - MinimapPing = 0x00B3, - // 0x00B4, CClientPlatformNexonAmerica sub_7F3AE0 - MsgBox = 0x00B5, - Wardrobe = 0x00B6, - SystemInfo = 0x00B7, - SidePopup = 0x00B8, - ChatSticker = 0x00B9, - WebScreenshot = 0x00BA, - FileHash = 0x00BB, - Wedding = 0x00BC, - WeddingBillboard = 0x00BD, - LimitBreak = 0x00BE, + // -1 Offset for KMS2 + Ugc = 0x0038, + MeretMarket = 0x0039, + KeyTable = 0x003A, + Channel = 0x003B, + Liftable = 0x003C, + MyInfo = 0x003D, + RelocateWorld = 0x003E, + ItemMerge = 0x003F, + RequestRide = 0x0040, + RideSync = 0x0041, + FittingDoll = 0x0042, + BonusGame = 0x0043, + ResolvePenalty = 0x0044, + TakeBoat = 0x0045, + Achieve = 0x0046, + BadgeEquip = 0x0047, + RequestTaxi = 0x0048, + Trade = 0x0049, + RequestWorldmap = 0x004A, + Guild = 0x004B, + GroupChat = 0x004C, + RequestHomeBank = 0x004D, + RequestHomeDoctor = 0x004E, + Ah = 0x004F, + SetDebugMode = 0x0050, + RequestHome = 0x0051, + RequestReport = 0x0052, + FurnishingStorage = 0x0053, + RequestMoveEventField = 0x0054, + LogSend = 0x0055, + DpsMode = 0x0056, + GuideRecord = 0x0057, + Rank = 0x0058, + RequestSkipTutorial = 0x0059, + ItemDismantle = 0x005A, + RideConsumeEp = 0x005B, + RequestAdditionalEffect = 0x005C, + RecallUser = 0x005D, + RequestItemEnchant = 0x005E, + BlackMarket = 0x005F, + Gamble = 0x0060, + Pvp = 0x0061, + Maid = 0x0062, + NewsNotification = 0x0063, + SmartRecommendBilling = 0x0064, + SystemShop = 0x0065, + Attendance = 0x0066, + PcBangBonus = 0x0067, + MaidCraftItem = 0x0068, + RequestUserEnv = 0x0069, + Cash = 0x006A, + Insignia = 0x006B, + RequestMoveField = 0x006C, + WaitingTicket = 0x006D, + PartySearch = 0x006E, + RecallScroll = 0x006F, + PotentialAbility = 0x0070, + EnchantScroll = 0x0071, + GlobalPortal = 0x0072, + Fishing = 0x0073, + PlayInstrument = 0x0074, + ChangeAttributes = 0x0075, + ChangeAttributesScroll = 0x0076, + TransferEnchant = 0x0077, + RequestPet = 0x0078, + RequestPetInventory = 0x0079, + NoticeDialog = 0x007A, + SkillMacro = 0x007B, + Banword = 0x007C, + CheckCharName = 0x007D, + PlatformProtectPacket = 0x007E, + PlatformAccountSafe = 0x007F, + State = 0x0080, + MesoMarket = 0x0081, + GlobalFactor = 0x0082, + SmartPush = 0x0083, + PlayArcade = 0x0084, + EnterEventField = 0x0085, + CardReverseGame = 0x0086, + RequestItemLock = 0x0087, + ItemSocketSystem = 0x0088, + CharacterAbility = 0x0089, + Tutorial = 0x008A, + ItemSocketScroll = 0x008B, + ItemRepack = 0x008C, + Mapleopoly = 0x008D, + PremiumClub = 0x008E, + HomeAction = 0x0090, + Mastery = 0x0092, + ConstructShop = 0x0093, + Club = 0x0095, + NexonArena = 0x0096, + EventReward = 0x0097, + NameChange = 0x0098, + AttributePoint = 0x0099, + ItemRepeat = 0x009A, + ItemExchangeScroll = 0x009B, + RequestTutorialItem = 0x009C, + DungeonReward = 0x009D, + ItemBillboard = 0x009E, + TreasureMap = 0x009F, + ItemExtractionScroll = 0x00A0, // GlamourAnvil + Mentor = 0x00A1, + RequestSkillBookTree = 0x00A2, + BuddyEmote = 0x00A3, + BuddyBadge = 0x00A4, + SyncWorld = 0x00A5, + BirthdayCard = 0x00A6, + SuperWorldChat = 0x00A7, + Microgame = 0x00A8, + StateReact = 0x00A9, + TreeWatering = 0x00AA, + Survival = 0x00AB, + SpectateMushtopia = 0x00AC, + Prestige = 0x00AD, + Lapenshard = 0x00AE, + Ugd = 0x00AF, + WorldChampion = 0x00B0, + ServerEnter = 0x00B1, + MinimapPing = 0x00B2, + // 0x00B3, CClientPlatformNexonAmerica sub_7F3AE0 + MsgBox = 0x00B4, + Wardrobe = 0x00B5, + SystemInfo = 0x00B6, + SidePopup = 0x00B7, + ChatSticker = 0x00B8, + WebScreenshot = 0x00B9, + FileHash = 0x00BA, + Wedding = 0x00BB, + WeddingBillboard = 0x00BC, + LimitBreak = 0x00BD, } diff --git a/Maple2.Server.Core/Constants/SendOp.cs b/Maple2.Server.Core/Constants/SendOp.cs index 7b0c410f0..7cc759a7f 100644 --- a/Maple2.Server.Core/Constants/SendOp.cs +++ b/Maple2.Server.Core/Constants/SendOp.cs @@ -46,250 +46,252 @@ public enum SendOp : ushort { FieldAddItem = 0x002B, FieldRemoveItem = 0x002C, FieldPickupItem = 0x002D, - FieldMutateItem = 0x002E, - Stat = 0x002F, - UserBattle = 0x0030, - UserSkinColor = 0x0031, - Beauty = 0x0032, - AdventurerBar = 0x0033, - RevivalConfirm = 0x0034, - Revival = 0x0035, - RevivalCount = 0x0036, - UserState = 0x0037, - ExpUp = 0x0038, - LevelUp = 0x0039, - Meso = 0x003A, - Meret = 0x003B, - CurrencyToken = 0x003C, - SkillUse = 0x003D, - SkillDamage = 0x003E, - SkillSync = 0x003F, - SkillCancel = 0x0040, - SkillUseFailed = 0x0041, - StateSkill = 0x0042, - SkillCooldown = 0x0043, - SkillResetCooldown = 0x0044, - SkillPoint = 0x0045, - AttributePoint = 0x0046, - CharacterCreate = 0x0047, - Buff = 0x0048, - FieldPortal = 0x0049, - Job = 0x004A, - NpcMonologue = 0x004B, - NpcTalk = 0x004C, - RegionSkill = 0x004D, - FunctionCube = 0x004E, - Trigger = 0x004F, - Breakable = 0x0050, - RoomTimer = 0x0051, - Shop = 0x0052, - Quest = 0x0053, - Party = 0x0054, - Mail = 0x0055, - FieldAddNpc = 0x0056, - FieldRemoveNpc = 0x0057, - FieldDeadNpc = 0x0058, - NpcControl = 0x0059, - InteractNpc = 0x005A, - FieldAddPet = 0x005B, - FieldRemovePet = 0x005C, - SyncPetTamingPoint = 0x005D, - Tombstone = 0x005E, - Achieve = 0x005F, - UserMoveByPortal = 0x0060, - ItemTitle = 0x0061, - MassiveEvent = 0x0062, - Buddy = 0x0063, - AdminBlock = 0x0064, - InteractObject = 0x0065, - StateConsumeEp = 0x0066, - StateFallDamage = 0x0067, - Cinematic = 0x0068, - Admin = 0x0069, - SetCraftMode = 0x006A, - ResponseCube = 0x006B, - LoadCubes = 0x006C, - Ugc = 0x006D, - MeretMarket = 0x006E, - Gvg = 0x006F, - GuideObject = 0x0070, - KeyTable = 0x0071, - FollowNpc = 0x0072, - Notice = 0x0073, - RelocateWorld = 0x0074, - Liftable = 0x0075, - ItemMerge = 0x0076, - Vibrate = 0x0077, - HideVibrate = 0x0078, - ShowVibrate = 0x0079, - CharacterInfo = 0x007A, - ResponseRide = 0x007B, - RideSync = 0x007C, - FittingDoll = 0x007D, - BonusGame = 0x007E, - LoadUgcMap = 0x007F, - ProxyGameObj = 0x0080, - BadgeEquip = 0x0081, - Taxi = 0x0082, - FindFields = 0x0083, - Trade = 0x0084, - InvincibleEffect = 0x0085, - Worldmap = 0x0086, - MoveEventField = 0x0087, - DpsStat = 0x0088, - DebugMode = 0x0089, - StoryBook = 0x008A, - GuideRecord = 0x008B, - Guild = 0x008C, - GroupChat = 0x008D, - RecallUser = 0x008E, - Rank = 0x008F, - AppendMessageCommon = 0x0090, - AppendMessageString = 0x0091, - AppendMessageKillBoss = 0x0092, - AppendClientLog = 0x0093, - AppendMessageAssistBonus = 0x0094, - Ah = 0x0095, - UiText = 0x0096, - PlayNpcSound = 0x0097, - ItemDismantle = 0x0098, - ItemEnchant = 0x0099, - BlackMarket = 0x009A, - MesoMarket = 0x009B, - TeamPvp = 0x009C, - WebOpen = 0x009D, - Gamble = 0x009E, - FieldMaid = 0x009F, - UserMaid = 0x00A0, - NewsNotification = 0x00A1, - SmartRecommendBilling = 0x00A2, - SystemShop = 0x00A3, - AutoRevive = 0x00A4, - PlayerKillNotice = 0x00A5, - Attendance = 0x00A6, - PcBangBonus = 0x00A7, - DeadUser = 0x00A8, - DynamicChannel = 0x00A9, - UserEnv = 0x00AA, - MaidCraftItem = 0x00AB, - EnterUgcMap = 0x00AC, - ItemUse = 0x00AD, - ItemBox = 0x00AE, - Cash = 0x00AF, - MyInfo = 0x00B0, - //UNKNOWN = 0x00B1, // nullsub - WorldShareInfo = 0x00B2, - Insignia = 0x00B3, - GameEvent = 0x00B4, - BannerList = 0x00B5, - WaitingTicketUpdate = 0x00B6, - SetPcBang = 0x00B7, - Pvp = 0x00B8, - HomeCommand = 0x00B9, - CharMaxCount = 0x00BA, - World = 0x00BB, - ItemDropNotice = 0x00BC, - PartySearch = 0x00BD, - RecallScroll = 0x00BE, - UserConditionEvent = 0x00BF, - PotentialAbility = 0x00C0, - EnchantScroll = 0x00C1, - BossRanking = 0x00C2, - GlobalPortal = 0x00C3, - QuizEvent = 0x00C4, - PlaySystemSound = 0x00C5, - Fishing = 0x00C6, - DarkStream = 0x00C7, - NpsInfo = 0x00C8, - PlayInstrument = 0x00C9, - ChangeAttributes = 0x00CA, - ChangeAttributesScroll = 0x00CB, - FieldProperty = 0x00CC, - GameEventUserValue = 0x00CD, - ResponsePet = 0x00CE, - Mastery = 0x00CF, - PetInventory = 0x00D0, - NoticeDialog = 0x00D1, - AaErr = 0x00D2, - SkillMacro = 0x00D3, - //BANWORD = 0x00D4, // nullsub - CheckCharNameResult = 0x00D5, - PlatformProtectPacket = 0x00D6, - //PLATFORM_ACCOUNT_SAFE = 0x00D7, // nullsub - GlobalFactor = 0x00D8, - SmartPush = 0x00D9, - PlayArcade = 0x00DA, - DebugState = 0x00DB, - CardReverseGame = 0x00DC, - ItemLock = 0x00DD, - HomeBank = 0x00DE, - HomeDoctor = 0x00DF, - ItemSocketSystem = 0x00E0, - CharacterAbility = 0x00E1, - ShadowBuff = 0x00E2, - ShadowExpedition = 0x00E3, - ItemSocketScroll = 0x00E5, - ItemRepackage = 0x00E6, - Mapleopoly = 0x00E7, - BypassKey = 0x00E8, - NpcNotice = 0x00EA, - LocalCamera = 0x00EB, - HomeAction = 0x00EC, - PremiumClub = 0x00ED, - SteamCashShop = 0x00EF, - InGameRank = 0x00F0, - DungeonMatch = 0x00F1, - DungeonWaiting = 0x00F2, - OneTimeEffect = 0x00F3, - CameraInterpolation = 0x00F4, - TimeScale = 0x00F5, - LegionBattle = 0x00F6, - ItemScript = 0x00F7, - Club = 0x00F8, - EventReward = 0x00F9, - HomeInvite = 0x00FA, - TaskEvent = 0x00FB, - StateFishingData = 0x00FC, - DungeonReviveCount = 0x00FD, - ChangeBackground = 0x0100, - RoomStageDungeon = 0x0101, - DungeonMission = 0x0102, - ItemExchange = 0x0104, - BindItem = 0x0105, - TransferEnchant = 0x0106, - PlayerHost = 0x0107, - GmCommand = 0x0108, - GlamourAnvil = 0x0109, - PetSkinBadge = 0x010A, - DungeonHelp = 0x010B, - Mentor = 0x010C, - SkillBookTree = 0x010E, - BuddyEmote = 0x010F, - BuddyBadge = 0x0110, - SyncWorld = 0x0112, - BirthdayCard = 0x0113, - SuperWorldChat = 0x0114, - Microgame = 0x0116, - Reactor = 0x0117, - SurvivalEvent = 0x0118, - MeretUse = 0x0119, - HalloweenEvent = 0x011A, - Survival = 0x011B, - SpectateMushtopia = 0x011C, - Lapenshard = 0x011D, - Prestige = 0x011E, - Ugd = 0x011F, - WorldChampion = 0x0121, - SyncValue = 0x0122, - MinimapPing = 0x0123, - MsgBox = 0x0124, - Wardrobe = 0x0125, - DynamicBanword = 0x0126, - RequestSystemInfo = 0x0127, - ChatStamp = 0x0128, - Wedding = 0x012A, - WeddingBillboard = 0x012C, - HideAndSeek = 0x012D, - MessengerBrowserStamp = 0x012E, - LimitBreak = 0x0130, + // FieldMutateItem = 0x002E, + // -1 Offset for KMS2 + Stat = 0x002E, + UserBattle = 0x002F, + UserSkinColor = 0x0030, + Beauty = 0x0031, + AdventurerBar = 0x0032, + RevivalConfirm = 0x0033, + Revival = 0x0034, + RevivalCount = 0x0035, + UserState = 0x0036, + ExpUp = 0x0037, + LevelUp = 0x0038, + Meso = 0x0039, + Meret = 0x003A, + CurrencyToken = 0x003B, + SkillUse = 0x003C, + SkillDamage = 0x003D, + SkillSync = 0x003E, + SkillCancel = 0x003F, + SkillUseFailed = 0x0040, + StateSkill = 0x0041, + SkillCooldown = 0x0042, + SkillResetCooldown = 0x0043, + SkillPoint = 0x0044, + AttributePoint = 0x0045, + CharacterCreate = 0x0046, + Buff = 0x0047, + FieldPortal = 0x0048, + Job = 0x0049, + NpcMonologue = 0x004A, + NpcTalk = 0x004B, + RegionSkill = 0x004C, + FunctionCube = 0x004D, + Trigger = 0x004E, + Breakable = 0x004F, + RoomTimer = 0x0050, + Shop = 0x0051, + Quest = 0x0052, + Party = 0x0053, + Mail = 0x0054, + FieldAddNpc = 0x0055, + FieldRemoveNpc = 0x0056, + FieldDeadNpc = 0x0057, + NpcControl = 0x0058, + InteractNpc = 0x0059, + FieldAddPet = 0x005A, + FieldRemovePet = 0x005B, + SyncPetTamingPoint = 0x005C, + Tombstone = 0x005D, + Achieve = 0x005E, + UserMoveByPortal = 0x005F, + // -2 Offset for KMS2 + MassiveEvent = 0x0060, + Buddy = 0x0061, + AdminBlock = 0x0062, + InteractObject = 0x0063, + StateConsumeEp = 0x0064, + StateFallDamage = 0x0065, + Cinematic = 0x0066, + Admin = 0x0067, + SetCraftMode = 0x0068, + ResponseCube = 0x0069, + LoadCubes = 0x006A, + Ugc = 0x006B, + MeretMarket = 0x006C, + Gvg = 0x006D, + GuideObject = 0x006E, + KeyTable = 0x006F, + FollowNpc = 0x0070, + Notice = 0x0071, + RelocateWorld = 0x0072, + Liftable = 0x0073, + ItemMerge = 0x0074, + Vibrate = 0x0075, + HideVibrate = 0x0076, + ShowVibrate = 0x0077, + CharacterInfo = 0x0078, + ResponseRide = 0x0079, + RideSync = 0x007A, + FittingDoll = 0x007B, + BonusGame = 0x007C, + LoadUgcMap = 0x007D, + ProxyGameObj = 0x007E, + BadgeEquip = 0x007F, + Taxi = 0x0080, + FindFields = 0x0081, + Trade = 0x0082, + InvincibleEffect = 0x0083, + Worldmap = 0x0084, + MoveEventField = 0x0085, + DpsStat = 0x0086, + DebugMode = 0x0087, + StoryBook = 0x0088, + GuideRecord = 0x0089, + Guild = 0x008A, + GroupChat = 0x008B, + RecallUser = 0x008C, + Rank = 0x008D, + AppendMessageCommon = 0x008E, + AppendMessageString = 0x008F, + AppendMessageKillBoss = 0x0090, + AppendClientLog = 0x0091, + AppendMessageAssistBonus = 0x0092, + Ah = 0x0093, + UiText = 0x0094, + PlayNpcSound = 0x0095, + ItemDismantle = 0x0096, + ItemEnchant = 0x0097, + BlackMarket = 0x0098, + MesoMarket = 0x0099, + TeamPvp = 0x009A, + WebOpen = 0x009B, + Gamble = 0x009C, + FieldMaid = 0x009D, + UserMaid = 0x009E, + NewsNotification = 0x009F, + SmartRecommendBilling = 0x00A0, + SystemShop = 0x00A1, + AutoRevive = 0x00A2, + PlayerKillNotice = 0x00A3, + Attendance = 0x00A4, + PcBangBonus = 0x00A5, + DeadUser = 0x00A6, + DynamicChannel = 0x00A7, + UserEnv = 0x00A8, + MaidCraftItem = 0x00A9, + EnterUgcMap = 0x00AA, + ItemUse = 0x00AB, + ItemBox = 0x00AC, + Cash = 0x00AD, + MyInfo = 0x00AE, + //UNKNOWN = 0x00AF, // nullsub + WorldShareInfo = 0x00B0, + Insignia = 0x00B1, + GameEvent = 0x00B2, + BannerList = 0x00B3, + WaitingTicketUpdate = 0x00B4, + SetPcBang = 0x00B5, + Pvp = 0x00B6, + HomeCommand = 0x00B7, + CharMaxCount = 0x00B8, + World = 0x00B9, + ItemDropNotice = 0x00BA, + PartySearch = 0x00BB, + RecallScroll = 0x00BC, + UserConditionEvent = 0x00BD, + PotentialAbility = 0x00BE, + EnchantScroll = 0x00BF, + BossRanking = 0x00C0, + GlobalPortal = 0x00C1, + QuizEvent = 0x00C2, + PlaySystemSound = 0x00C3, + Fishing = 0x00C4, + DarkStream = 0x00C5, + NpsInfo = 0x00C6, + PlayInstrument = 0x00C7, + ChangeAttributes = 0x00C8, + ChangeAttributesScroll = 0x00C9, + FieldProperty = 0x00CA, + GameEventUserValue = 0x00CB, + ResponsePet = 0x00CC, + Mastery = 0x00CD, + PetInventory = 0x00CE, + NoticeDialog = 0x00CF, + AaErr = 0x00D0, + SkillMacro = 0x00D1, + //BANWORD = 0x00D2, // nullsub + CheckCharNameResult = 0x00D3, + PlatformProtectPacket = 0x00D4, + //PLATFORM_ACCOUNT_SAFE = 0x00D5, // nullsub + GlobalFactor = 0x00D6, + SmartPush = 0x00D7, + PlayArcade = 0x00D8, + DebugState = 0x00D9, + CardReverseGame = 0x00DA, + ItemLock = 0x00DB, + HomeBank = 0x00DC, + HomeDoctor = 0x00DD, + ItemSocketSystem = 0x00DE, + CharacterAbility = 0x00DF, + ShadowBuff = 0x00E0, + ShadowExpedition = 0x00E1, + ItemSocketScroll = 0x00E3, + ItemRepackage = 0x00E4, + Mapleopoly = 0x00E5, + BypassKey = 0x00E6, + NpcNotice = 0x00E8, + LocalCamera = 0x00E9, + HomeAction = 0x00EA, + PremiumClub = 0x00EB, + SteamCashShop = 0x00ED, + InGameRank = 0x00EE, + DungeonMatch = 0x00EF, + DungeonWaiting = 0x00F0, + OneTimeEffect = 0x00F1, + CameraInterpolation = 0x00F2, + TimeScale = 0x00F3, + LegionBattle = 0x00F4, + ItemScript = 0x00F5, + Club = 0x00F6, + EventReward = 0x00F7, + HomeInvite = 0x00F8, + TaskEvent = 0x00F9, + StateFishingData = 0x00FA, + DungeonReviveCount = 0x00FB, + ChangeBackground = 0x00FE, + RoomStageDungeon = 0x00FF, + DungeonMission = 0x0100, + ItemExchange = 0x0102, + BindItem = 0x0103, + TransferEnchant = 0x0104, + PlayerHost = 0x0105, + GmCommand = 0x0106, + GlamourAnvil = 0x0107, + PetSkinBadge = 0x0108, + DungeonHelp = 0x0109, + Mentor = 0x010A, + SkillBookTree = 0x010C, + BuddyEmote = 0x010D, + BuddyBadge = 0x010E, + SyncWorld = 0x0110, + BirthdayCard = 0x0111, + SuperWorldChat = 0x0112, + Microgame = 0x0114, + Reactor = 0x0115, + SurvivalEvent = 0x0116, + MeretUse = 0x0117, + HalloweenEvent = 0x0118, + Survival = 0x0119, + SpectateMushtopia = 0x011A, + Lapenshard = 0x011B, + Prestige = 0x011C, + Ugd = 0x011D, + WorldChampion = 0x011F, + SyncValue = 0x0120, + MinimapPing = 0x0121, + MsgBox = 0x0122, + Wardrobe = 0x0123, + DynamicBanword = 0x0124, + RequestSystemInfo = 0x0125, + ChatStamp = 0x0126, + Wedding = 0x0128, + WeddingBillboard = 0x012A, + HideAndSeek = 0x012B, + MessengerBrowserStamp = 0x012C, + LimitBreak = 0x012E, + } diff --git a/Maple2.Server.Core/Network/Session.cs b/Maple2.Server.Core/Network/Session.cs index 3ea9568fd..e678641e2 100644 --- a/Maple2.Server.Core/Network/Session.cs +++ b/Maple2.Server.Core/Network/Session.cs @@ -19,8 +19,8 @@ public enum PatchType : byte { } public abstract class Session : IDisposable { - public const uint VERSION = 12; - private const uint BLOCK_IV = 12; // TODO: should this be variable + public const uint VERSION = 2525; // KMS2 2022-04-13 + private const uint BLOCK_IV = 23; // TODO: should this be variable private const int HANDSHAKE_SIZE = 19; private const int STOP_TIMEOUT = 2000; diff --git a/Maple2.Server.Core/PacketHandlers/ResponseVersionHandler.cs b/Maple2.Server.Core/PacketHandlers/ResponseVersionHandler.cs index 0c18058a9..0a1175a30 100644 --- a/Maple2.Server.Core/PacketHandlers/ResponseVersionHandler.cs +++ b/Maple2.Server.Core/PacketHandlers/ResponseVersionHandler.cs @@ -13,7 +13,7 @@ public override void Handle(T session, IByteReader packet) { packet.ReadShort(); // 47 var locale = packet.Read(); - if (version != Session.VERSION || locale != Locale.NA) { + if (version != Session.VERSION /*|| locale != Locale.NA */) { session.Disconnect(); } } diff --git a/Maple2.Server.Core/Packets/CharacterListPacket.cs b/Maple2.Server.Core/Packets/CharacterListPacket.cs index 33a386bcd..ffcf1d703 100644 --- a/Maple2.Server.Core/Packets/CharacterListPacket.cs +++ b/Maple2.Server.Core/Packets/CharacterListPacket.cs @@ -172,8 +172,6 @@ private static void WriteCharacter(this IByteWriter writer, Account account, Cha #region Unknown writer.WriteUnicodeString(); writer.WriteLong(); - writer.WriteLong(); - writer.WriteLong(); #endregion writer.WriteInt(); // Unknown Count writer.WriteByte(); diff --git a/Maple2.Server.Core/Packets/Helper/EquipPacketHelper.cs b/Maple2.Server.Core/Packets/Helper/EquipPacketHelper.cs index 218fcab51..20621b58e 100644 --- a/Maple2.Server.Core/Packets/Helper/EquipPacketHelper.cs +++ b/Maple2.Server.Core/Packets/Helper/EquipPacketHelper.cs @@ -10,7 +10,7 @@ public static class EquipPacketHelper { public static void WriteEquip(this IByteWriter writer, Item equip) { writer.WriteInt(equip.Id); writer.WriteLong(equip.Uid); - writer.WriteUnicodeString(equip.EquipSlot().ToString()); + writer.Write(equip.EquipSlot()); writer.WriteInt(equip.Rarity); writer.WriteClass(equip); } diff --git a/Maple2.Server.Core/Packets/ServerListPacket.cs b/Maple2.Server.Core/Packets/ServerListPacket.cs index 0d404d0e5..791e776c3 100644 --- a/Maple2.Server.Core/Packets/ServerListPacket.cs +++ b/Maple2.Server.Core/Packets/ServerListPacket.cs @@ -19,6 +19,7 @@ public static ByteWriter Error() { public static ByteWriter Load(string serverName, IList serverIps, ICollection channels) { var pWriter = Packet.Of(SendOp.ServerList); + pWriter.WriteString("dev"); // env (Live, Staging, qa, dev) pWriter.Write(Command.Load); pWriter.WriteInt(1); // Unknown pWriter.WriteUnicodeString(serverName); diff --git a/Maple2.Server.Game/Manager/Config/SkillInfo.cs b/Maple2.Server.Game/Manager/Config/SkillInfo.cs index 72c224e27..706c1f9fe 100644 --- a/Maple2.Server.Game/Manager/Config/SkillInfo.cs +++ b/Maple2.Server.Game/Manager/Config/SkillInfo.cs @@ -193,17 +193,16 @@ public IEnumerable GetSkills(SkillType type, SkillRank rank) { } public void WriteTo(IByteWriter writer) { - writer.WriteInt((int) Job); - writer.WriteByte(1); // Count - + writer.WriteInt(1); writer.WriteInt((int) Job.Code()); + int count = 0; for (int i = 0; i < SKILL_TYPES; i++) { - int count = 0; for (int j = 0; j < SKILL_RANKS; j++) { count += Skills[i, j].Count + SubSkills[i, j].Count; } - writer.WriteByte((byte) count); - + } + writer.WriteByte((byte) count); + for (int i = 0; i < SKILL_TYPES; i++) { for (int j = 0; j < SKILL_RANKS; j++) { foreach (Skill skill in Skills[i, j].Values) { writer.WriteClass(skill); @@ -251,11 +250,11 @@ public void SetLevel(short level, bool notify = true) { } public void WriteTo(IByteWriter writer) { - writer.WriteBool(Notify); - writer.WriteBool(Level > 0); writer.WriteInt(Id); writer.WriteInt(Math.Max((int) Level, 1)); - writer.WriteByte(); + writer.WriteBool(false); + writer.WriteBool(Level > 0); + writer.WriteBool(Notify); Notify = false; } diff --git a/Maple2.Server.Game/Manager/ItemMergeManager.cs b/Maple2.Server.Game/Manager/ItemMergeManager.cs index 9d2ba918f..2ca809f40 100644 --- a/Maple2.Server.Game/Manager/ItemMergeManager.cs +++ b/Maple2.Server.Game/Manager/ItemMergeManager.cs @@ -139,20 +139,20 @@ public void Empower(long itemUid, long catalystUid) { (BasicAttribute attribute, ItemMergeTable.Option mergeOption) = mergeSlot.BasicOptions.ElementAt(selectedIndex); (int value, float rate) = Roll(mergeOption); - upgradeItem.Stats![ItemStats.Type.Empowerment1] = new ItemStats.Option { - Basic = { - [attribute] = new BasicOption(value, rate), - }, - }; + // upgradeItem.Stats![ItemStats.Type.Empowerment1] = new ItemStats.Option { + // Basic = { + // [attribute] = new BasicOption(value, rate), + // }, + // }; } else { (SpecialAttribute attribute, ItemMergeTable.Option mergeOption) = mergeSlot.SpecialOptions.ElementAt(selectedIndex - mergeSlot.BasicOptions.Count); (int value, float rate) = Roll(mergeOption); - upgradeItem.Stats![ItemStats.Type.Empowerment1] = new ItemStats.Option { - Special = { - [attribute] = new SpecialOption(rate, value), - }, - }; + // upgradeItem.Stats![ItemStats.Type.Empowerment1] = new ItemStats.Option { + // Special = { + // [attribute] = new SpecialOption(rate, value), + // }, + // }; } session.Send(ItemMergePacket.Empower(upgradeItem)); diff --git a/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs b/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs index 0d40073c4..6298ee65c 100644 --- a/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs +++ b/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs @@ -335,11 +335,11 @@ public void OnStateSync(StateSync stateSync) { break; case ActorState.EmotionIdle: if (UpdateStateSyncTimeTracking()) { - Field.SkillMetadata.TryGet(stateSync.EmotionId, 1, out SkillMetadata? emote); - if (emote == null) { - break; - } - Session.ConditionUpdate(ConditionType.emotiontime, codeString: emote.Property.Emotion); + // Field.SkillMetadata.TryGet(stateSync.EmotionId, 1, out var emote); + // if (emote == null) { + // break; + // } + // Session.ConditionUpdate(ConditionType.emotiontime, codeString: emote.Property.Emotion); } break; // TODO: Any more condition states? diff --git a/Maple2.Server.Game/Model/Field/Entity/FieldNpcSpawnPoint.cs b/Maple2.Server.Game/Model/Field/Entity/FieldNpcSpawnPoint.cs index 59d0b61bf..5bc0f3706 100644 --- a/Maple2.Server.Game/Model/Field/Entity/FieldNpcSpawnPoint.cs +++ b/Maple2.Server.Game/Model/Field/Entity/FieldNpcSpawnPoint.cs @@ -42,7 +42,7 @@ public void TriggerSpawn(bool forceFullSpawn = false) { foreach (SpawnPointNPCListEntry spawn in Value.NpcList) { if (!Field.NpcMetadata.TryGet(spawn.NpcId, out NpcMetadata? npcMetadata)) { - Log.Logger.Warning("Npc {NpcId} failed to load for map {MapId}", spawn.NpcId, Field.MapId); + // Log.Logger.Warning("Npc {NpcId} failed to load for map {MapId}", spawn.NpcId, Field.MapId); continue; } diff --git a/Maple2.Server.Game/Model/Stats.cs b/Maple2.Server.Game/Model/Stats.cs index 651ff8ac7..e3448ce20 100644 --- a/Maple2.Server.Game/Model/Stats.cs +++ b/Maple2.Server.Game/Model/Stats.cs @@ -193,12 +193,12 @@ public void AddTotal(long amount) { public void AddTotal(BasicOption option) { AddTotal(option.Value); - Rate += option.Rate; + // Rate += option.Value; } public void AddTotal(SpecialOption option) { - AddTotal((int) option.Value); - Rate += option.Rate; + AddTotal((int) option.Rate); + // Rate += option.Rate; } public void AddRate(float rate) { diff --git a/Maple2.Server.Game/PacketHandlers/CheckCharacterNameHandler.cs b/Maple2.Server.Game/PacketHandlers/CheckCharacterNameHandler.cs index 87b449e6e..473f0cecf 100644 --- a/Maple2.Server.Game/PacketHandlers/CheckCharacterNameHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/CheckCharacterNameHandler.cs @@ -1,5 +1,7 @@ using Maple2.Database.Storage; +using Maple2.Model.Error; using Maple2.Model.Metadata; +using Maple2.Model.Validators; using Maple2.PacketLib.Tools; using Maple2.Server.Core.Constants; using Maple2.Server.Game.PacketHandlers.Field; @@ -23,13 +25,10 @@ public override void Handle(GameSession session, IByteReader packet) { string characterName = packet.ReadUnicodeString(); long itemUid = packet.ReadLong(); - if (characterName.Length < Constant.CharacterNameLengthMin) { - session.Send(CharacterListPacket.CreateError(s_char_err_name)); - return; - } - - if (characterName.Length > Constant.CharacterNameLengthMax) { - session.Send(CharacterListPacket.CreateError(s_char_err_system)); + // Validate character name using comprehensive validator + var nameError = CharacterNameValidator.ValidateName(characterName); + if (nameError != null) { + session.Send(CharacterListPacket.CreateError(nameError.Value)); return; } diff --git a/Maple2.Server.Game/PacketHandlers/GuildHandler.cs b/Maple2.Server.Game/PacketHandlers/GuildHandler.cs index 9a7a504ea..b88466b6f 100644 --- a/Maple2.Server.Game/PacketHandlers/GuildHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/GuildHandler.cs @@ -69,6 +69,8 @@ private enum Command : byte { public override void Handle(GameSession session, IByteReader packet) { var command = packet.Read(); + + return; switch (command) { case Command.Create: HandleCreate(session, packet); diff --git a/Maple2.Server.Game/PacketHandlers/ItemEquipHandler.cs b/Maple2.Server.Game/PacketHandlers/ItemEquipHandler.cs index c52bb3861..28ef71bd6 100644 --- a/Maple2.Server.Game/PacketHandlers/ItemEquipHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/ItemEquipHandler.cs @@ -31,13 +31,9 @@ public override void Handle(GameSession session, IByteReader packet) { private void HandleEquip(GameSession session, IByteReader packet) { long itemUid = packet.ReadLong(); - string equipSlotStr = packet.ReadUnicodeString(); + var equipSlot = packet.Read(); bool isSkin = packet.ReadBool(); - if (!Enum.TryParse(equipSlotStr, out EquipSlot equipSlot)) { - return; - } - // Disconnect if this fails to avoid bad state. if (session.Item.Equips.Equip(itemUid, equipSlot, isSkin)) { session.Stats.Refresh(); @@ -57,6 +53,6 @@ private void HandleUnequip(GameSession session, IByteReader packet) { // This is probably for Skin2 private void HandleEquip2(GameSession session, IByteReader packet) { long itemUid = packet.ReadLong(); - string equipSlotStr = packet.ReadUnicodeString(); + var equipSlot = packet.Read(); } } diff --git a/Maple2.Server.Game/PacketHandlers/SkillHandler.cs b/Maple2.Server.Game/PacketHandlers/SkillHandler.cs index 2680d707b..34a7a5fa3 100644 --- a/Maple2.Server.Game/PacketHandlers/SkillHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/SkillHandler.cs @@ -105,7 +105,7 @@ private void HandleUse(GameSession session, IByteReader packet) { record.IsHold = packet.ReadBool(); if (record.IsHold) { record.HoldInt = packet.ReadInt(); - record.HoldString = packet.ReadUnicodeString(); + record.HoldString = packet.ReadUnicodeString().Trim('\0'); if (session.Player.DebugSkills) { session.Send(NoticePacket.Message($"Skill.Use: {skillId}, {skillUid}; IsHold: true; HoldInt: {record.HoldInt}; HoldString: {record.HoldString}; UnkBool: {record.Unknown}")); diff --git a/Maple2.Server.Game/PacketHandlers/UserSyncHandler.cs b/Maple2.Server.Game/PacketHandlers/UserSyncHandler.cs index eae4a3320..23c27b8f1 100644 --- a/Maple2.Server.Game/PacketHandlers/UserSyncHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/UserSyncHandler.cs @@ -32,16 +32,13 @@ public override void Handle(GameSession session, IByteReader packet) { ActorState.MicroGameRps => new StateSyncRps(), ActorState.MicroGameCoupleDance => new StateSyncCoupleDance(), ActorState.WeddingEmotion => new StateSyncWeddingEmotion(), - _ => new StateSync(), + _ => new StateSync() }; stateSync.ReadFrom(packet); stateSyncs[i] = stateSync; packet.ReadInt(); // ClientTicks - if (playerState != ActorState.WeddingEmotion) { - packet.ReadInt(); // ServerTicks - } } using (var buffer = new PoolByteWriter()) { diff --git a/Maple2.Server.Game/Packets/AdminPacket.cs b/Maple2.Server.Game/Packets/AdminPacket.cs new file mode 100644 index 000000000..109cffdd9 --- /dev/null +++ b/Maple2.Server.Game/Packets/AdminPacket.cs @@ -0,0 +1,16 @@ +using Maple2.PacketLib.Tools; +using Maple2.Server.Core.Constants; +using Maple2.Server.Core.Packets; + +namespace Maple2.Server.Game.Packets; + +public static class AdminPacket { + + public static ByteWriter Enable() { + ByteWriter pWriter = Packet.Of(SendOp.Admin); + pWriter.WriteByte(); + pWriter.WriteByte(255); + + return pWriter; + } +} diff --git a/Maple2.Server.Game/Packets/DungeonRoomPacket.cs b/Maple2.Server.Game/Packets/DungeonRoomPacket.cs index 3c67a3786..d893c3c28 100644 --- a/Maple2.Server.Game/Packets/DungeonRoomPacket.cs +++ b/Maple2.Server.Game/Packets/DungeonRoomPacket.cs @@ -79,6 +79,8 @@ public static ByteWriter RankRewards(IDictionary rankRew pWriter.WriteInt(id); pWriter.WriteClass(reward); } + pWriter.WriteInt(); + pWriter.WriteLong(); return pWriter; } diff --git a/Maple2.Server.Game/Packets/EmotePacket.cs b/Maple2.Server.Game/Packets/EmotePacket.cs index 407cc908b..bfdc31212 100644 --- a/Maple2.Server.Game/Packets/EmotePacket.cs +++ b/Maple2.Server.Game/Packets/EmotePacket.cs @@ -27,7 +27,10 @@ public static ByteWriter Load(IList emotes) { public static ByteWriter Learn(Emote emote) { var pWriter = Packet.Of(SendOp.Emote); pWriter.Write(Command.Learn); - pWriter.Write(emote); + pWriter.WriteInt(emote.Id); + pWriter.WriteInt(emote.Level); + pWriter.WriteLong(emote.ExpiryTime); + pWriter.WriteLong(); return pWriter; } diff --git a/Maple2.Server.Game/Packets/EnchantScrollPacket.cs b/Maple2.Server.Game/Packets/EnchantScrollPacket.cs index 56ae72a42..0f3b21d7a 100644 --- a/Maple2.Server.Game/Packets/EnchantScrollPacket.cs +++ b/Maple2.Server.Game/Packets/EnchantScrollPacket.cs @@ -55,7 +55,6 @@ public static ByteWriter Preview(Item item, EnchantScrollType scrollType, IDicti pWriter.WriteInt(minOptions.Count); foreach ((BasicAttribute attribute, BasicOption delta) in minOptions) { pWriter.WriteShort((short) attribute); - pWriter.WriteFloat(delta.Rate); pWriter.WriteInt(delta.Value); } break; @@ -63,13 +62,11 @@ public static ByteWriter Preview(Item item, EnchantScrollType scrollType, IDicti pWriter.WriteInt(maxOptions.Count); foreach ((BasicAttribute attribute, BasicOption delta) in maxOptions) { pWriter.WriteShort((short) attribute); - pWriter.WriteFloat(delta.Rate); pWriter.WriteInt(delta.Value); } pWriter.WriteInt(minOptions.Count); foreach ((BasicAttribute attribute, BasicOption delta) in minOptions) { pWriter.WriteShort((short) attribute); - pWriter.WriteFloat(delta.Rate); pWriter.WriteInt(delta.Value); } break; diff --git a/Maple2.Server.Game/Packets/EquipPacket.cs b/Maple2.Server.Game/Packets/EquipPacket.cs index 89c5bfbff..1158f5494 100644 --- a/Maple2.Server.Game/Packets/EquipPacket.cs +++ b/Maple2.Server.Game/Packets/EquipPacket.cs @@ -15,7 +15,7 @@ public static ByteWriter EquipItem(IActor player, Item item, byte type) pWriter.WriteInt(player.ObjectId); pWriter.WriteInt(item.Id); pWriter.WriteLong(item.Uid); - pWriter.WriteUnicodeString(item.EquipSlot().ToString()); + pWriter.Write(item.EquipSlot()); pWriter.WriteInt(item.Rarity); pWriter.WriteByte(type); // 0, 1, 2 (rest invalid) pWriter.WriteClass(item); diff --git a/Maple2.Server.Game/Packets/FieldPacket.cs b/Maple2.Server.Game/Packets/FieldPacket.cs index 1a5d80947..dbf525150 100644 --- a/Maple2.Server.Game/Packets/FieldPacket.cs +++ b/Maple2.Server.Game/Packets/FieldPacket.cs @@ -121,8 +121,6 @@ public static ByteWriter AddPlayer(GameSession session) { pWriter.WriteLong(player.Account.PremiumTime); pWriter.WriteInt(); pWriter.WriteByte(); - pWriter.WriteInt(); // Tail - pWriter.WriteInt(); pWriter.WriteShort(); return pWriter; @@ -331,8 +329,6 @@ private static void WriteCharacter(this IByteWriter writer, GameSession session) writer.WriteClass(character.Mastery); #region Unknown writer.WriteUnicodeString(); // Login username - writer.WriteLong(); // Session Id - writer.WriteLong(); writer.WriteLong(); #endregion writer.WriteInt(); // Unknown Count diff --git a/Maple2.Server.Game/Packets/ItemInventoryPacket.cs b/Maple2.Server.Game/Packets/ItemInventoryPacket.cs index 27affe189..7e34545ae 100644 --- a/Maple2.Server.Game/Packets/ItemInventoryPacket.cs +++ b/Maple2.Server.Game/Packets/ItemInventoryPacket.cs @@ -1,4 +1,5 @@ -using Maple2.Model.Enum; +using Maple2.Model; +using Maple2.Model.Enum; using Maple2.Model.Error; using Maple2.Model.Game; using Maple2.PacketLib.Tools; @@ -14,14 +15,14 @@ private enum Command : byte { Remove = 1, UpdateAmount = 2, Move = 3, - Load = 7, - NotifyNew = 8, - LoadTab = 10, - ExpandComplete = 12, - Reset = 13, - ExpandCount = 14, - Error = 15, - UpdateItem = 16, + Load = 6, + NotifyNew = 7, + LoadTab = 8, + ExpandComplete = 10, + Reset = 11, + ExpandCount = 12, + Error = 13, + UpdateItem = 14, } public static ByteWriter Add(Item item) { @@ -31,7 +32,6 @@ public static ByteWriter Add(Item item) { pWriter.WriteLong(item.Uid); pWriter.WriteShort(item.Slot); pWriter.WriteInt(item.Rarity); - pWriter.WriteUnicodeString(); // EquipSlot pWriter.WriteClass(item); return pWriter; @@ -85,7 +85,7 @@ public static ByteWriter NotifyNew(long uid, int amount) { pWriter.Write(Command.NotifyNew); pWriter.WriteLong(uid); pWriter.WriteInt(amount); - pWriter.WriteUnicodeString(); // EquipSlot + pWriter.WriteUnicodeString(); return pWriter; } diff --git a/Maple2.Server.Game/Packets/ServerEnterPacket.cs b/Maple2.Server.Game/Packets/ServerEnterPacket.cs index af43b2597..bc316ba5a 100644 --- a/Maple2.Server.Game/Packets/ServerEnterPacket.cs +++ b/Maple2.Server.Game/Packets/ServerEnterPacket.cs @@ -3,6 +3,7 @@ using Maple2.Server.Core.Constants; using Maple2.Server.Core.Packets; using Maple2.Server.Game.Model; +using Maple2.Tools.Extensions; namespace Maple2.Server.Game.Packets; @@ -34,9 +35,9 @@ public static ByteWriter Request(IActor fieldPlayer) { pWriter.WriteLong(player.Currency.MenteeToken); pWriter.WriteLong(player.Currency.StarPoint); pWriter.WriteLong(player.Currency.MesoToken); + pWriter.WriteUnicodeString(player.Character.Picture); pWriter.WriteByte(); - pWriter.WriteByte(); pWriter.WriteShort((short) player.Unlock.Maps.Count); foreach (int mapId in player.Unlock.Maps) { pWriter.WriteInt(mapId); @@ -50,7 +51,6 @@ public static ByteWriter Request(IActor fieldPlayer) { pWriter.WriteUnicodeString("http://127.0.0.1:8080"); pWriter.WriteUnicodeString(); pWriter.WriteUnicodeString("^https?://127\\.0\\.0\\.1(:\\d+)?"); - pWriter.WriteUnicodeString(); return pWriter; } diff --git a/Maple2.Server.Game/Session/GameSession.cs b/Maple2.Server.Game/Session/GameSession.cs index 411d4a18d..f5f4cc40b 100644 --- a/Maple2.Server.Game/Session/GameSession.cs +++ b/Maple2.Server.Game/Session/GameSession.cs @@ -453,6 +453,8 @@ public bool EnterField() { Send(StatsPacket.Init(Player)); Field.Broadcast(StatsPacket.Update(Player), Player.Session); + Send(AdminPacket.Enable()); + //TODO: Save current hp/sp/ep in memory. This will help determine if player is dead upon login. var pWriter = Packet.Of(SendOp.UserState); pWriter.WriteInt(Player.ObjectId); diff --git a/Maple2.Server.Game/Util/ItemStatsCalculator.cs b/Maple2.Server.Game/Util/ItemStatsCalculator.cs index a3830687e..31c1ed98b 100644 --- a/Maple2.Server.Game/Util/ItemStatsCalculator.cs +++ b/Maple2.Server.Game/Util/ItemStatsCalculator.cs @@ -251,7 +251,7 @@ public bool RandomizeValues(Item item, ItemOption itemOptionMetadata, ref ItemSt foreach (SpecialAttribute attribute in option.Special.Keys) { if (table.SpecialValues.TryGetValue(attribute, out ItemEquipVariationTable.Set[]? values)) { int value = GetValue(specialAttribute: attribute, tableValues: values, rollMax: rollMax); - option.Special[attribute] = new SpecialOption(0f, value * option.MultiplyFactor); + option.Special[attribute] = new SpecialOption(value * option.MultiplyFactor); } else if (table.SpecialRates.TryGetValue(attribute, out ItemEquipVariationTable.Set[]? rates)) { int rateInt = GetValue(specialAttribute: attribute, tableRates: rates, rollMax: rollMax); option.Special[attribute] = new SpecialOption((rateInt / 1000f) * option.MultiplyFactor); @@ -540,7 +540,7 @@ private static ItemStats.Option ConstantItemOption(ItemOptionConstant option) { statResult.Add(attribute, new BasicOption(rate)); } foreach ((SpecialAttribute attribute, int value) in option.SpecialValues) { - specialResult.Add(attribute, new SpecialOption(0f, value)); + specialResult.Add(attribute, new SpecialOption(value)); } foreach ((SpecialAttribute attribute, float rate) in option.SpecialRates) { specialResult.Add(attribute, new SpecialOption(rate)); @@ -619,7 +619,7 @@ bool AddResult(ItemOption.Entry entry, IDictionary if (specialDict.ContainsKey(attribute)) return true; // Cannot add duplicate values, retry. if (entry.Values != null) { - specialDict.Add(attribute, new SpecialOption(0f, Random.Shared.Next(entry.Values.Value.Min, entry.Values.Value.Max + 1))); + specialDict.Add(attribute, new SpecialOption(Random.Shared.Next(entry.Values.Value.Min, entry.Values.Value.Max + 1))); } else if (entry.Rates != null) { float delta = entry.Rates.Value.Max - entry.Rates.Value.Min; specialDict.Add(attribute, new SpecialOption(Random.Shared.NextSingle() * delta + entry.Rates.Value.Min)); diff --git a/Maple2.Server.Game/appsettings.json b/Maple2.Server.Game/appsettings.json index 1d35405db..126efd031 100644 --- a/Maple2.Server.Game/appsettings.json +++ b/Maple2.Server.Game/appsettings.json @@ -26,7 +26,7 @@ "template": "{@t:HH:mm:ss.fff} {Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1),12} [{@l:u3}] <{ThreadId}> {@m}\n{@x}", "theme": "Serilog.Templates.Themes.TemplateTheme::Literate, Serilog.Expressions" }, - "restrictedToMinimumLevel": "Debug" + "restrictedToMinimumLevel": "Verbose" } }, { diff --git a/Maple2.Server.Login/PacketHandlers/CharacterManagementHandler.cs b/Maple2.Server.Login/PacketHandlers/CharacterManagementHandler.cs index a57530bc3..2490da1f1 100644 --- a/Maple2.Server.Login/PacketHandlers/CharacterManagementHandler.cs +++ b/Maple2.Server.Login/PacketHandlers/CharacterManagementHandler.cs @@ -7,8 +7,10 @@ using Maple2.Database.Storage; using Maple2.Model.Common; using Maple2.Model.Enum; +using Maple2.Model.Error; using Maple2.Model.Game; using Maple2.Model.Metadata; +using Maple2.Model.Validators; using Maple2.PacketLib.Tools; using Maple2.Server.Core.Constants; using Maple2.Server.Core.PacketHandlers; @@ -113,13 +115,10 @@ private void HandleCreate(LoginSession session, IByteReader packet) { var job = (Job) ((int) jobCode * 10); string name = packet.ReadUnicodeString(); - if (name.Length < Constant.CharacterNameLengthMin) { - session.Send(CharacterListPacket.CreateError(s_char_err_name)); - return; - } - - if (name.Length > Constant.CharacterNameLengthMax) { - session.Send(CharacterListPacket.CreateError(s_char_err_system)); + // Validate character name using comprehensive validator + CharacterCreateError? nameError = CharacterNameValidator.ValidateName(name); + if (nameError != null) { + session.Send(CharacterListPacket.CreateError(nameError.Value)); return; } @@ -137,8 +136,9 @@ private void HandleCreate(LoginSession session, IByteReader packet) { int equipCount = packet.ReadByte(); for (int i = 0; i < equipCount; i++) { int id = packet.ReadInt(); - string slotStr = packet.ReadUnicodeString(); - if (!Enum.TryParse(slotStr, out EquipSlot slot) || slot is SK or OH or Unknown) { + byte slotByte = packet.ReadByte(); + EquipSlot slot = (EquipSlot) slotByte; + if (slot is SK or OH or Unknown) { session.Send(CharacterListPacket.CreateError(s_char_err_invalid_def_item)); return; } diff --git a/Maple2.Server.Login/PacketHandlers/ResponseKeyHandler.cs b/Maple2.Server.Login/PacketHandlers/ResponseKeyHandler.cs index f48e7507e..919997332 100644 --- a/Maple2.Server.Login/PacketHandlers/ResponseKeyHandler.cs +++ b/Maple2.Server.Login/PacketHandlers/ResponseKeyHandler.cs @@ -37,6 +37,10 @@ public override void Handle(LoginSession session, IByteReader packet) { MigrateInResponse response = World.MigrateIn(request); session.Init(accountId, machineId); session.Send(MigrationPacket.MoveResult(ok)); + + // KMS never sends ServerEnter so this needs to be included + session.ListServers(); + session.ListCharacters(); } catch (Exception ex) when (ex is RpcException or InvalidOperationException) { session.Send(MigrationPacket.MoveResult(s_move_err_default)); session.Disconnect(); diff --git a/Maple2.Server.Login/appsettings.json b/Maple2.Server.Login/appsettings.json index 63148fb91..830a00677 100644 --- a/Maple2.Server.Login/appsettings.json +++ b/Maple2.Server.Login/appsettings.json @@ -21,7 +21,7 @@ "template": "{@t:HH:mm:ss.fff} {Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1),12} [{@l:u3}] <{ThreadId}> {@m}\n{@x}", "theme": "Serilog.Templates.Themes.TemplateTheme::Literate, Serilog.Expressions" }, - "restrictedToMinimumLevel": "Debug" + "restrictedToMinimumLevel": "Verbose" } }, { diff --git a/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs b/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs new file mode 100644 index 000000000..f7800fbeb --- /dev/null +++ b/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs @@ -0,0 +1,184 @@ +using Maple2.Model.Error; +using Maple2.Model.Validators; + +namespace Maple2.Server.Tests.Validators; + +public class CharacterNameValidatorTests { + [Test] + public void ValidName_ShouldReturnNull() { + // Valid names should return null (no error) + Assert.That(CharacterNameValidator.ValidateName("ValidName"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("Test123"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("User_Name"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("Cool-Name"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("ab"), Is.Null); // minimum length + Assert.That(CharacterNameValidator.ValidateName("abcdefghijkl"), Is.Null); // maximum length + } + + [Test] + public void TooShortName_ShouldReturnNameError() { + // Names shorter than minimum should return name error + Assert.That(CharacterNameValidator.ValidateName("a"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName(""), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName(" "), Is.EqualTo(CharacterCreateError.s_char_err_name)); + } + + [Test] + public void TooLongName_ShouldReturnSystemError() { + // Names longer than maximum should return system error + Assert.That(CharacterNameValidator.ValidateName("abcdefghijklm"), Is.EqualTo(CharacterCreateError.s_char_err_system)); + Assert.That(CharacterNameValidator.ValidateName("ThisNameIsTooLong"), Is.EqualTo(CharacterCreateError.s_char_err_system)); + } + + [Test] + public void BannedName_ShouldReturnBanAllError() { + // Completely banned names should return ban_all error + Assert.That(CharacterNameValidator.ValidateName("admin"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("ADMIN"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("moderator"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("gm"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("system"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("maple"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + } + + [Test] + public void ForbiddenWord_ShouldReturnBanAnyError() { + // Names containing forbidden words should return ban_any error + Assert.That(CharacterNameValidator.ValidateName("TestAdmin"), Is.EqualTo(CharacterCreateError.s_char_err_ban_any)); + Assert.That(CharacterNameValidator.ValidateName("MyStaff"), Is.EqualTo(CharacterCreateError.s_char_err_ban_any)); + Assert.That(CharacterNameValidator.ValidateName("CoolBot"), Is.EqualTo(CharacterCreateError.s_char_err_ban_any)); + Assert.That(CharacterNameValidator.ValidateName("fucked"), Is.EqualTo(CharacterCreateError.s_char_err_ban_any)); + Assert.That(CharacterNameValidator.ValidateName("shitty"), Is.EqualTo(CharacterCreateError.s_char_err_ban_any)); + } + + [Test] + public void InvalidCharacters_ShouldReturnNameError() { + // Names with invalid characters should return name error + Assert.That(CharacterNameValidator.ValidateName("test@name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("name#test"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("test$name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("test%name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("test*name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + } + + [Test] + public void OnlySpecialCharacters_ShouldReturnNameError() { + // Names with only special characters should return name error + Assert.That(CharacterNameValidator.ValidateName("--"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("__"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName(" "), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("-_-"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + } + + [Test] + public void NullOrWhitespace_ShouldReturnNameError() { + // Null or whitespace names should return name error + Assert.That(CharacterNameValidator.ValidateName(null!), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName(""), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName(" "), Is.EqualTo(CharacterCreateError.s_char_err_name)); + } + + [Test] + public void GetForbiddenWord_ShouldReturnCorrectWord() { + // Should return the forbidden word found in the name + Assert.That(CharacterNameValidator.GetForbiddenWord("TestAdmin"), Is.EqualTo("admin")); + Assert.That(CharacterNameValidator.GetForbiddenWord("MyStaff"), Is.EqualTo("staff")); + Assert.That(CharacterNameValidator.GetForbiddenWord("CoolBot"), Is.EqualTo("bot")); + Assert.That(CharacterNameValidator.GetForbiddenWord("fucked"), Is.EqualTo("fuck")); + Assert.That(CharacterNameValidator.GetForbiddenWord("ValidName"), Is.Null); + } + + [Test] + public void CaseInsensitiveValidation_ShouldWork() { + // Validation should be case insensitive + Assert.That(CharacterNameValidator.ValidateName("ADMIN"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("Admin"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("aDmIn"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + + Assert.That(CharacterNameValidator.ValidateName("TESTADMIN"), Is.EqualTo(CharacterCreateError.s_char_err_ban_any)); + Assert.That(CharacterNameValidator.ValidateName("TestAdmin"), Is.EqualTo(CharacterCreateError.s_char_err_ban_any)); + Assert.That(CharacterNameValidator.ValidateName("testADMIN"), Is.EqualTo(CharacterCreateError.s_char_err_ban_any)); + } + + [Test] + public void SpaceValidation_ShouldWork() { + // Names with spaces should be invalid + Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Cool Player"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("a b"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + + // Names with leading or trailing spaces should be invalid + Assert.That(CharacterNameValidator.ValidateName(" ValidName"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("ValidName "), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName(" ValidName "), Is.EqualTo(CharacterCreateError.s_char_err_name)); + + // Names with multiple consecutive spaces should be invalid + Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("A B"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + } + + [Test] + public void JapaneseCharacterNames_ShouldBeValid() { + // Japanese Hiragana characters should be valid + Assert.That(CharacterNameValidator.ValidateName("さくら"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("ひろし"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("あいうえお"), Is.Null); + + // Japanese Katakana characters should be valid + Assert.That(CharacterNameValidator.ValidateName("サクラ"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("ヒロシ"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("アイウエオ"), Is.Null); + + // Japanese Kanji characters should be valid + Assert.That(CharacterNameValidator.ValidateName("田中"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("山田"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("佐藤"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("鈴木"), Is.Null); + + // Mixed Japanese characters should be valid + Assert.That(CharacterNameValidator.ValidateName("さくら123"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("田中ひろし"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("サクラ_田中"), Is.Null); + } + + [Test] + public void KoreanCharacterNames_ShouldBeValid() { + // Korean Hangul characters should be valid + Assert.That(CharacterNameValidator.ValidateName("김철수"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("이영희"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("박민수"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("정수진"), Is.Null); + + // Korean with numbers should be valid + Assert.That(CharacterNameValidator.ValidateName("김철수123"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("이영희_456"), Is.Null); + + // Korean with allowed special characters should be valid + Assert.That(CharacterNameValidator.ValidateName("박민수-정"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("정수진_김"), Is.Null); + } + + [Test] + public void ChineseCharacterNames_ShouldBeValid() { + // Simplified Chinese characters should be valid + Assert.That(CharacterNameValidator.ValidateName("王小明"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("李小红"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("张三"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("刘德华"), Is.Null); + + // Traditional Chinese characters should be valid + Assert.That(CharacterNameValidator.ValidateName("王小明"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("陳大文"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("黃志強"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("林美玲"), Is.Null); + + // Chinese with numbers should be valid + Assert.That(CharacterNameValidator.ValidateName("王小明123"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("李小红_456"), Is.Null); + + // Chinese with allowed special characters should be valid + Assert.That(CharacterNameValidator.ValidateName("张三-李四"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("刘德华_陈"), Is.Null); + } +} diff --git a/Maple2.Server.World/Containers/BlackMarketLookup.cs b/Maple2.Server.World/Containers/BlackMarketLookup.cs index a7c9dece5..9e393598b 100644 --- a/Maple2.Server.World/Containers/BlackMarketLookup.cs +++ b/Maple2.Server.World/Containers/BlackMarketLookup.cs @@ -107,12 +107,11 @@ private bool ItemStatCheck(ItemStats stats, Dictionary