diff --git a/Server/AsciiString.cs b/Server/AsciiString.cs index 32a92ac..e203beb 100644 --- a/Server/AsciiString.cs +++ b/Server/AsciiString.cs @@ -8,6 +8,9 @@ namespace PSO2SERVER { private byte[] data; + // 获取字符串的长度 + public int Length => data.Length; + // 从字符串初始化 public AsciiString(string value) { @@ -23,7 +26,7 @@ namespace PSO2SERVER // 从字节数组初始化 public AsciiString(byte[] bytes) { - data = bytes; + data = bytes ?? throw new ArgumentNullException(nameof(bytes), "字节数组不能为 null"); } // 获取 ASCII 字符串,去除尾部的 null 字节 @@ -67,7 +70,59 @@ namespace PSO2SERVER return new AsciiString(value); } - // 获取字符串的长度 - public int Length => data.Length; + // 写入固定长度 ASCII 字符串,超出部分截断,空余部分用零填充 + public void WriteFixedLengthASCII(string str, int charCount) + { + // 确保字符串长度不超过规定的 charCount + var writeAmount = Math.Min(str.Length, charCount); + var paddingAmount = charCount - writeAmount; + + byte[] byteArray = Encoding.ASCII.GetBytes(str.Substring(0, writeAmount)); + + // 可能存在的填充 + byte[] resultArray = new byte[charCount]; + Array.Copy(byteArray, resultArray, byteArray.Length); + + // 用零填充剩余部分 + if (paddingAmount > 0) + { + for (int i = byteArray.Length; i < charCount; i++) + { + resultArray[i] = 0; + } + } + + data = resultArray; + } + + // 写入固定长度的 UTF-16 字符串,超出部分截断,空余部分用零填充 + public void WriteFixedLengthUtf16(string str, int charCount) + { + var writeAmount = Math.Min(str.Length, charCount); + var paddingAmount = charCount - writeAmount; + + byte[] byteArray = Encoding.Unicode.GetBytes(str.Substring(0, writeAmount)); + + // 可能存在的填充 + byte[] resultArray = new byte[charCount * 2]; // UTF-16 每个字符占 2 个字节 + Array.Copy(byteArray, resultArray, byteArray.Length); + + // 用零填充剩余部分 + if (paddingAmount > 0) + { + for (int i = byteArray.Length; i < charCount * 2; i++) + { + resultArray[i] = 0; + } + } + + data = resultArray; + } + + // 重写 ToString 方法,返回字符串表示 + public string GetStringRepresentation() + { + return Encoding.ASCII.GetString(data); + } } } diff --git a/Server/ConsoleSystem.cs b/Server/ConsoleSystem.cs index a230f87..0244bde 100644 --- a/Server/ConsoleSystem.cs +++ b/Server/ConsoleSystem.cs @@ -712,9 +712,8 @@ namespace PSO2SERVER }; - var fakePacket = new CharacterSpawnPacket(fakeChar, new PSOLocation(0f, 1f, 0f, 0f, x, y, z)) + var fakePacket = new CharacterSpawnPacket(fakeChar, new PSOLocation(0f, 1f, 0f, 0f, x, y, z), false) { - IsItMe = false }; client.SendPacket(fakePacket); diff --git a/Server/Logger.cs b/Server/Logger.cs index ffc3bed..0818b27 100644 --- a/Server/Logger.cs +++ b/Server/Logger.cs @@ -1,6 +1,6 @@ using System; using System.IO; - +using System.Runtime.InteropServices; using PSO2SERVER.Protocol.Packets; namespace PSO2SERVER @@ -226,5 +226,32 @@ namespace PSO2SERVER Writer.WriteLine("\t\t" + DateTime.Now); Writer.WriteLine("--------------------------------------------------"); } + + // 方法:打印结构体的二进制数据 + public static void WriteStructBinary(string text, object obj) + { + var byteArray = StructToByteArray(obj); + WriteHex(text, byteArray); + } + + // 将结构体转换为字节数组 + public static byte[] StructToByteArray(object obj) + { + int size = Marshal.SizeOf(obj); + byte[] byteArray = new byte[size]; + IntPtr ptr = Marshal.AllocHGlobal(size); + + try + { + Marshal.StructureToPtr(obj, ptr, false); + Marshal.Copy(ptr, byteArray, 0, size); + } + finally + { + Marshal.FreeHGlobal(ptr); + } + + return byteArray; + } } } \ No newline at end of file diff --git a/Server/Models/Character.cs b/Server/Models/Character.cs index c07b6d2..6b15efb 100644 --- a/Server/Models/Character.cs +++ b/Server/Models/Character.cs @@ -68,8 +68,6 @@ namespace PSO2SERVER.Models [Flags] public enum ClassTypeField : ushort { - Unknown = 0xFF, - None = 0, Hunter = 1 << 0, Ranger = 1 << 1, Force = 1 << 2, @@ -129,13 +127,13 @@ namespace PSO2SERVER.Models [StructLayout(LayoutKind.Sequential)] public unsafe struct JobParam { - public ClassType mainClass; - public ClassType subClass; - public ushort unk2; - public ClassTypeField enabledClasses; - public ushort unk3; - public Entries entries; //TODO: Make this a fixed array - public fixed ushort unk_maxlevel[15]; + public ClassType mainClass;//1 + public ClassType subClass;//1 + public ushort unk2;//2 + public ClassTypeField enabledClasses;//2 + public ushort unk3;//2 + public Entries entries; //TODO: Make this a fixed array 24 * 8 + public fixed ushort unk_maxlevel[15];//30 } public enum RunAnimation : ushort diff --git a/Server/Models/PSO2Item.cs b/Server/Models/PSO2Item.cs index 8ddec27..2c3fdbd 100644 --- a/Server/Models/PSO2Item.cs +++ b/Server/Models/PSO2Item.cs @@ -7,7 +7,7 @@ using static PSO2SERVER.Models.Character; namespace PSO2SERVER.Models { - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential)] public unsafe struct ShortItemId { byte ItemType; @@ -15,7 +15,7 @@ namespace PSO2SERVER.Models ushort Subid; } - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential)] public unsafe struct ItemId { ushort ItemType; @@ -51,12 +51,12 @@ namespace PSO2SERVER.Models //public PSO2ItemNone None; } - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential)] public unsafe struct PSO2ItemNone { } - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential)] public unsafe struct PSO2ItemWeapon { byte flags; @@ -75,7 +75,7 @@ namespace PSO2SERVER.Models uint unknown6; } - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential)] public unsafe struct PSO2ItemClothing { ushort flags; @@ -85,7 +85,7 @@ namespace PSO2SERVER.Models ushort Unk3; } - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential)] public unsafe struct PSO2ItemConsumable { ushort flags; @@ -93,7 +93,7 @@ namespace PSO2SERVER.Models ushort amount; } - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential)] public unsafe struct PSO2ItemCamo { byte unk1; @@ -103,7 +103,7 @@ namespace PSO2SERVER.Models byte unk5; } - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential)] public unsafe struct PSO2ItemUnit { byte flags; diff --git a/Server/Models/Quest.cs b/Server/Models/Quest.cs index f536b04..c576d14 100644 --- a/Server/Models/Quest.cs +++ b/Server/Models/Quest.cs @@ -117,7 +117,7 @@ namespace PSO2SERVER.Models NotSet = 0x0000000800000000, } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public unsafe struct QuestDefiniton { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32 - 8)] diff --git a/Server/Protocol/Handlers/03-ServerHandler/03-03-InitialLoad.cs b/Server/Protocol/Handlers/03-ServerHandler/03-03-InitialLoad.cs index 5f4f442..27c8f88 100644 --- a/Server/Protocol/Handlers/03-ServerHandler/03-03-InitialLoad.cs +++ b/Server/Protocol/Handlers/03-ServerHandler/03-03-InitialLoad.cs @@ -35,7 +35,7 @@ namespace PSO2SERVER.Protocol.Handlers //var setPlayerId = new PacketWriter(); //setPlayerId.WriteAccountHeader((uint)context._account.AccountId); //context.SendPacket(0x06, 0x00, 0, setPlayerId.ToArray()); - context.SendPacket(new SetPlayerIDPacket(context._account.AccountId)); + context.SendPacket(new SetAccountIDPacket(context._account.AccountId)); // Spawn Account new CharacterSpawn().HandlePacket(context, flags, data, position, size); diff --git a/Server/Protocol/Handlers/11-ClientHandler/11-00-SegaIDLogin.cs b/Server/Protocol/Handlers/11-ClientHandler/11-00-SegaIDLogin.cs index f5d7f81..97e2774 100644 --- a/Server/Protocol/Handlers/11-ClientHandler/11-00-SegaIDLogin.cs +++ b/Server/Protocol/Handlers/11-ClientHandler/11-00-SegaIDLogin.cs @@ -112,11 +112,18 @@ namespace PSO2SERVER.Protocol.Handlers if (!users.Any()) { // Check if there is an empty field - if (string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password)) + if (string.IsNullOrWhiteSpace(Username)) { - error = "用户名或密码为空."; + error = "Username Empty."; user = null; } + + if (string.IsNullOrWhiteSpace(Password)) + { + error = "Password Empty #1."; + user = null; + } + // Check for special characters else if (!Regex.IsMatch(Username, "^[a-zA-Z0-9 ]*$", RegexOptions.IgnoreCase)) { @@ -145,20 +152,21 @@ namespace PSO2SERVER.Protocol.Handlers { user = users.First(); - if (Password != user.Password) - { - if (Password == "") - { - error = "密码为空."; - user = null; - } - else - if (!BCrypt.Net.BCrypt.Verify(Password, user.Password)) - { - error = "密码错误."; - user = null; - } - } + //TODO 方便GM测试 + //if (Password != user.Password) + //{ + // if (Password == "") + // { + // error = "Password Empty #2."; + // user = null; + // } + // else + // if (!BCrypt.Net.BCrypt.Verify(Password, user.Password)) + // { + // error = "Wrong Password."; + // user = null; + // } + //} } context.SendPacket(new LoginDataPacket("Server AuthList 1", error, (user == null) ? (uint)0 : (uint)user.AccountId)); diff --git a/Server/Protocol/Handlers/11-ClientHandler/11-05-CharacterCreate.cs b/Server/Protocol/Handlers/11-ClientHandler/11-05-CharacterCreate.cs index 9414b7e..69c8851 100644 --- a/Server/Protocol/Handlers/11-ClientHandler/11-05-CharacterCreate.cs +++ b/Server/Protocol/Handlers/11-ClientHandler/11-05-CharacterCreate.cs @@ -35,6 +35,8 @@ namespace PSO2SERVER.Protocol.Handlers var looks = reader.ReadStruct(); var jobs = reader.ReadStruct(); + Logger.WriteStructBinary(info, jobs); + Logger.WriteInternal("[CHR] {0} 创建了名为 {1} 的新角色.", context._account.Username, name); var newCharacter = new Character { diff --git a/Server/Protocol/Handlers/11-ClientHandler/11-BC-CharacterShipTransferCanceldRequest.cs b/Server/Protocol/Handlers/11-ClientHandler/11-BC-CharacterShipTransferCanceldRequest.cs index bb003e3..8dc66a5 100644 --- a/Server/Protocol/Handlers/11-ClientHandler/11-BC-CharacterShipTransferCanceldRequest.cs +++ b/Server/Protocol/Handlers/11-ClientHandler/11-BC-CharacterShipTransferCanceldRequest.cs @@ -8,14 +8,20 @@ namespace PSO2SERVER.Protocol.Handlers [PacketHandlerAttr(0x11, 0xBC)] class CharacterShipTransferCanceldRequest : PacketHandler { - public uint player_id { get; set; } + public struct CharacterShipTransferCanceldRequestPacket + { + public uint player_id { get; set; } + } + + CharacterShipTransferCanceldRequestPacket pkt = new CharacterShipTransferCanceldRequestPacket(); + public override void HandlePacket(Client context, byte flags, byte[] data, uint position, uint size) { var info = string.Format("[<--] 接收到的数据 (hex): {0} 字节", data.Length); Logger.WriteHex(info, data); var reader = new PacketReader(data, position, size); - player_id = reader.ReadUInt32(); + pkt = reader.ReadStruct(); context.SendPacket(new CharacterShipTransferCancelPacket(MoveStatus.Success));// TODO 需要补充完整 } diff --git a/Server/Protocol/Handlers/11-ClientHandler/11-DC-CharacterSurvey.cs b/Server/Protocol/Handlers/11-ClientHandler/11-DC-CharacterSurvey.cs new file mode 100644 index 0000000..e3aab75 --- /dev/null +++ b/Server/Protocol/Handlers/11-ClientHandler/11-DC-CharacterSurvey.cs @@ -0,0 +1,31 @@ +using System; +using PSO2SERVER.Models; +using PSO2SERVER.Protocol.Packets; +using static PSO2SERVER.Protocol.Handlers.CharacterNewNameRequest; + +namespace PSO2SERVER.Protocol.Handlers +{ + [PacketHandlerAttr(0x11, 0xDC)] + class CharacterSurvey : PacketHandler + { + public struct CharacterSurveyPacket + { + public uint choice { get; set; } + } + + public override void HandlePacket(Client context, byte flags, byte[] data, uint position, uint size) + { + var info = string.Format("[<--] 接收到的数据 (hex): {0} 字节", data.Length); + Logger.WriteHex(info, data); + + // 创建一个结构体实例 + var packet = new CharacterSurveyPacket(); + + // 读取数据并填充到结构体中 + var reader = new PacketReader(data, position, size); + packet.choice = reader.ReadUInt32(); + + context.SendPacket(new NoPayloadPacket(0x11, 0xDD)); + } + } +} diff --git a/Server/Protocol/PacketWriter.cs b/Server/Protocol/PacketWriter.cs index c3e4875..3c8dc9d 100644 --- a/Server/Protocol/PacketWriter.cs +++ b/Server/Protocol/PacketWriter.cs @@ -150,9 +150,9 @@ namespace PSO2SERVER.Protocol } } - public void WriteAccountHeader(uint id) + public void WriteAccountHeader(uint AccountId) { - Write(id); + Write(AccountId); Write((uint) 0); Write((ushort)ObjectType.Player); Write((ushort) 0); diff --git a/Server/Protocol/Packets/06-PlayerStatusPacket/06-00-SetPlayerIDPacket.cs b/Server/Protocol/Packets/06-PlayerStatusPacket/06-00-SetAccountIDPacket.cs similarity index 70% rename from Server/Protocol/Packets/06-PlayerStatusPacket/06-00-SetPlayerIDPacket.cs rename to Server/Protocol/Packets/06-PlayerStatusPacket/06-00-SetAccountIDPacket.cs index 936b598..9ea7bb3 100644 --- a/Server/Protocol/Packets/06-PlayerStatusPacket/06-00-SetPlayerIDPacket.cs +++ b/Server/Protocol/Packets/06-PlayerStatusPacket/06-00-SetAccountIDPacket.cs @@ -6,13 +6,13 @@ using System.Text; namespace PSO2SERVER.Protocol.Packets { - public class SetPlayerIDPacket : Packet + public class SetAccountIDPacket : Packet { - private readonly int _PlayerId; + private readonly int AccountId; - public SetPlayerIDPacket(int PlayerId) + public SetAccountIDPacket(int AccountId) { - _PlayerId = PlayerId; + this.AccountId = AccountId; } #region implemented abstract members of Packet @@ -20,7 +20,7 @@ namespace PSO2SERVER.Protocol.Packets public override byte[] Build() { var pkt = new PacketWriter(); - pkt.WriteAccountHeader((uint)_PlayerId); + pkt.WriteAccountHeader((uint)AccountId); return pkt.ToArray(); } diff --git a/Server/Protocol/Packets/08-SpawnPacket/08-04-CharacterSpawnPacket.cs b/Server/Protocol/Packets/08-SpawnPacket/08-04-CharacterSpawnPacket.cs index 98012aa..ac7a482 100644 --- a/Server/Protocol/Packets/08-SpawnPacket/08-04-CharacterSpawnPacket.cs +++ b/Server/Protocol/Packets/08-SpawnPacket/08-04-CharacterSpawnPacket.cs @@ -1,4 +1,6 @@ using PSO2SERVER.Models; +using System.Runtime.InteropServices; +using static PSO2SERVER.Protocol.Packets.CharacterSpawnPacket; namespace PSO2SERVER.Protocol.Packets { @@ -50,19 +52,23 @@ namespace PSO2SERVER.Protocol.Packets writer.Write((uint)602); // 0x48 writer.Write((uint)1); // 0x4C writer.Write((uint)53); // 0x50 - writer.Write((uint)0); // 0x54 - writer.Write((uint)(IsItMe ? 47 : 39)); // 0x58 + writer.Write((uint)100); // 0x54 + writer.Write((uint)(IsItMe ? CharacterSpawnType.Myself : CharacterSpawnType.Other)); // 0x58 writer.Write((ushort)559); // 0x5C writer.Write((ushort)306); // 0x5E - writer.Write((uint)_character.Account.AccountId); // player ID copy - writer.Write((uint)0); // "char array ugggghhhhh" according to PolarisLegacy + + // Character + writer.Write((uint)_character.AccountID); // player ID copy + writer.Write((uint)_character.CharacterID); // "char array ugggghhhhh" according to PolarisLegacy writer.Write((uint)0); // "voiceParam_unknown4" writer.Write((uint)0); // "voiceParam_unknown8" writer.WriteFixedLengthUtf16(_character.Name, 16); writer.Write((uint)0); // 0x90 writer.WriteStruct(_character.Looks); writer.WriteStruct(_character.Jobs); + writer.WriteFixedLengthUtf16("", 32); // title? + writer.Write((uint)0); // 0x204 writer.Write((uint)0); // gmflag? writer.WriteFixedLengthUtf16(_character.Account.Nickname, 16); // Nickname, maybe not 16 chars? diff --git a/Server/Protocol/Packets/0B-QuestPacket/0B-1A-QuestDifficultyPacket.cs b/Server/Protocol/Packets/0B-QuestPacket/0B-1A-QuestDifficultyPacket.cs index 9c582d7..2e66606 100644 --- a/Server/Protocol/Packets/0B-QuestPacket/0B-1A-QuestDifficultyPacket.cs +++ b/Server/Protocol/Packets/0B-QuestPacket/0B-1A-QuestDifficultyPacket.cs @@ -63,7 +63,7 @@ namespace PSO2SERVER.Protocol.Packets } //Size: 308 bytes, confirmed in unpacker - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public unsafe struct QuestDifficulty { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)] diff --git a/Server/Protocol/Packets/11-ClientPacket/11-03-CharacterListPacket.cs b/Server/Protocol/Packets/11-ClientPacket/11-03-CharacterListPacket.cs index 835bc53..a863f06 100644 --- a/Server/Protocol/Packets/11-ClientPacket/11-03-CharacterListPacket.cs +++ b/Server/Protocol/Packets/11-ClientPacket/11-03-CharacterListPacket.cs @@ -54,8 +54,6 @@ namespace PSO2SERVER.Protocol.Packets writer.Write((uint)ch.AccountID); writer.Write((uint)ch.CharacterID); - //for (var i = 0; i < 0x10; i++) - // writer.Write((byte)0); writer.Write(ch.Unk1); writer.Write(ch.VoiceType); writer.Write(ch.Unk2); diff --git a/Server/Server.csproj b/Server/Server.csproj index ad45f53..f325265 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -176,6 +176,7 @@ + @@ -273,7 +274,7 @@ - + diff --git a/Server/Zone/Map.cs b/Server/Zone/Map.cs index 39f556d..77fbd46 100644 --- a/Server/Zone/Map.cs +++ b/Server/Zone/Map.cs @@ -140,10 +140,8 @@ namespace PSO2SERVER.Zone c.CurrentZone.RemoveClient(c); } - //var setPlayerId = new PacketWriter(); - //setPlayerId.WriteAccountHeader((uint)c._account.AccountId); - //c.SendPacket(0x06, 0x00, 0, setPlayerId.ToArray()); - c.SendPacket(new SetPlayerIDPacket(c._account.AccountId)); + // 设置客户端的账户ID + c.SendPacket(new SetAccountIDPacket(c._account.AccountId)); // Spawn Character c.SendPacket(new CharacterSpawnPacket(c.Character, location));