修正大量数据包

This commit is contained in:
Longfeng Qin 2024-11-25 23:33:41 +08:00
parent c830d8de6d
commit b446a4a8ad
36 changed files with 591 additions and 190 deletions

73
Server/AsciiString.cs Normal file
View File

@ -0,0 +1,73 @@
using System;
using System.Linq;
using System.Text;
namespace PSO2SERVER
{
public class AsciiString
{
private byte[] data;
// 从字符串初始化
public AsciiString(string value)
{
// 确保字符串中只包含 ASCII 字符
if (value.Any(c => c > 127))
{
throw new ArgumentException("字符串包含非 ASCII 字符。");
}
data = Encoding.ASCII.GetBytes(value);
}
// 从字节数组初始化
public AsciiString(byte[] bytes)
{
data = bytes;
}
// 获取 ASCII 字符串,去除尾部的 null 字节
public string GetString()
{
return Encoding.ASCII.GetString(data).TrimEnd('\0');
}
// 获取底层字节数组
public byte[] ToBytes()
{
return data;
}
// 重写 ToString 方法,直接返回 ASCII 字符串
public override string ToString()
{
return GetString();
}
// 检查字节数组是否只包含有效的 ASCII 字符
public bool IsValidAscii()
{
return data.All(b => b < 128);
}
// 静态方法,从字节数组创建 AsciiString 对象
public static AsciiString FromBytes(byte[] bytes)
{
return new AsciiString(bytes);
}
// 静态方法,如果字符串过长则截断
public static AsciiString TruncateIfNeeded(string value, int maxLength)
{
if (value.Length > maxLength)
{
value = value.Substring(0, maxLength);
}
return new AsciiString(value);
}
// 获取字符串的长度
public int Length => data.Length;
}
}

View File

@ -17,7 +17,7 @@ namespace PSO2SERVER
private readonly byte[] _readBuffer;
private readonly Server _server;
internal ICryptoTransform InputArc4, OutputArc4;
private int _packetId;
//private int _packetId;
private uint _readBufferSize;
public Client(Server server, SocketClient socket)
@ -268,7 +268,7 @@ namespace PSO2SERVER
// 确保文件名不包含不允许的字符
var safePrefix = string.Join("_", prefix.Split(Path.GetInvalidFileNameChars()));
var filename = Path.Combine(packetPath, $"0x{typeA:X2}-0x{typeB:X2}-{packet.Length}-{safePrefix}-{timePart}.bin");
var filename = Path.Combine(packetPath, $"0x{typeA:X2}-0x{typeB:X2}-{packet.Length}字节-{safePrefix}-{timePart}.bin");
try
{

View File

@ -704,7 +704,7 @@ namespace PSO2SERVER
var fakeChar = new Character
{
Character_ID = 12345678 + new Random().Next(),
CharacterID = 12345678 + new Random().Next(),
Account = fakePlayer,
Name = playerName,
Looks = client.Character.Looks,

View File

@ -44,8 +44,8 @@ namespace PSO2SERVER.Database
public string Password { get; set; }
public string Nickname { get; set; }
public string SettingsIni { get; set; }
public string Cpu_Info { get; set; }
public string Video_Info { get; set; }
public string CpuInfo { get; set; }
public string VideoInfo { get; set; }
}
public class PlayerSystemInfo

View File

@ -9,10 +9,17 @@ namespace PSO2SERVER.Models
{
public class Character
{
public enum ShortLanguage : byte
{
Japanese,
English,
Unknown,
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct CharParam
{
public int character_id;
public int CharacterID;
public int player_id;
public uint unk1;
public uint voice_type;
@ -261,12 +268,12 @@ namespace PSO2SERVER.Models
[Key]
public int Id { get; set; }
public int Character_ID { get; set; }
public int Player_ID { get; set; }
public int CharacterID { get; set; }
public int AccountID { get; set; }
public uint Unk1 { get; set; }
public uint Voice_Type { get; set; }
public uint VoiceType { get; set; }
public ushort Unk2 { get; set; }
public short Voice_Pitch { get; set; }
public short VoicePitch { get; set; }
public string Name { get; set; }

View File

@ -1,64 +1,73 @@
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using PSO2SERVER.Packets;
using PSO2SERVER.Packets;
namespace PSO2SERVER.Models
{
public enum ObjectType : UInt16
// 对象类型枚举
public enum ObjectType : ushort
{
Unknown = 0,
Player = 4,
Map = 5,
Object = 6,
StaticObject = 7,
Quest = 11,
Party = 13,
World = 16,
APC = 22,
Unknown = 0x0000,
/// 玩家对象。
Player = 0x0004,
/// 区域对象。
Map = 0x0005,
/// 大部分的对象和NPC。
Object = 0x0006,
/// 一些可破坏的物体(例如一些树木)。
StaticObject = 0x0007,
/// 任务对象。11
Quest = 0x000B,
/// 队伍对象。13
Party = 0x000D,
/// 世界地图池中的对象。16
World = 0x0010,
/// 非玩家可控的伙伴。22
APC = 0x0016,
/// 未定义的类型。
Undefined = 0xFFFF
}
// 对象头部结构体
public struct ObjectHeader
{
/// Id of the object.
public UInt32 ID;
public UInt32 padding; // Always is padding
/// Type of the object.
/// 对象的ID。
public uint ID;
public uint padding; // 总是有填充
/// 对象的类型。
public ObjectType ObjectType;
/// Zone id of the object. Not set for players.
public UInt16 MapID;
/// 对象所在的区域ID。玩家对象没有设置这个字段。
public ushort MapID;
// 只包含ID和类型的构造函数
public ObjectHeader(uint id, ObjectType type) : this()
{
ID = id;
ObjectType = type;
}
/// Zone id of the object. Not set for players.
public ObjectHeader(uint id, ObjectType type, UInt16 mapid) : this()
// 包含ID、类型和区域ID的构造函数
public ObjectHeader(uint id, ObjectType type, ushort mapid) : this()
{
ID = id;
ObjectType = type;
MapID = mapid;
}
// 从数据流中读取数据并填充到当前结构体
public void ReadFromStream(PacketReader reader)
{
ID = reader.ReadUInt32();
padding = reader.ReadUInt32(); // 读取填充
ObjectType = (ObjectType)reader.ReadUInt16();
MapID = reader.ReadUInt16();
ID = reader.ReadUInt32(); // 读取对象ID
padding = reader.ReadUInt32(); // 读取填充部分
ObjectType = (ObjectType)reader.ReadUInt16(); // 读取对象类型
MapID = reader.ReadUInt16(); // 读取区域ID
}
// 将当前结构体的数据写入到数据流
public void WriteToStream(PacketWriter writer)
{
writer.Write(ID);
writer.Write(padding); // 写入填充
writer.Write((UInt16)ObjectType);
writer.Write(MapID);
writer.Write(ID); // 写入对象ID
writer.Write(padding); // 写入填充部分
writer.Write((ushort)ObjectType); // 写入对象类型
writer.Write(MapID); // 写入区域ID
}
}
}

View File

@ -12,9 +12,9 @@ namespace PSO2SERVER.Models
{
public struct PSOObjectThing
{
public UInt32 Data;
public uint Data;
public PSOObjectThing(UInt32 data)
public PSOObjectThing(uint data)
{
Data = data;
}
@ -23,7 +23,7 @@ namespace PSO2SERVER.Models
public ObjectHeader Header { get; set; }
public PSOLocation Position { get; set; }
public string Name { get; set; }
public UInt32 ThingFlag { get; set; }
public uint ThingFlag { get; set; }
public PSOObjectThing[] Things { get; set; }
public virtual byte[] GenerateSpawnBlob()

View File

@ -131,14 +131,14 @@ namespace PSO2SERVER.Packets.Handlers
Username = Username.ToLower(),
Password = BCrypt.Net.BCrypt.HashPassword(Password),
Nickname = Username.ToLower(),
// Since we can't display the nickname prompt yet, just default it to the username
// Since we can't display the Nickname prompt yet, just default it to the username
SettingsIni = File.ReadAllText(ServerApp.ServerSettingsKey)
};
db.Account.Add(user);
db.SaveChanges();
//context.SendPacket(0x11, 0x1e, 0x0, new byte[0x44]); // Request nickname
context.SendPacket(new NicknameRequestPacket()); // Request nickname
//context.SendPacket(0x11, 0x1e, 0x0, new byte[0x44]); // Request Nickname
context.SendPacket(new NicknameRequestPacket()); // Request Nickname
}
}
else

View File

@ -36,7 +36,7 @@ namespace PSO2SERVER.Packets.Handlers
{
try
{
var character = db.Characters.FirstOrDefault(c => c.Player_ID == charId);
var character = db.Characters.FirstOrDefault(c => c.CharacterID == charId && c.AccountID == context._account.AccountId);
if (character == null || character.Account == null || character.Account.AccountId != context._account.AccountId)
{

View File

@ -48,15 +48,15 @@ namespace PSO2SERVER.Packets.Handlers
if (existingCharacters.Count > 0)
{
// Increment ID if characters already exist
newCharacter.Character_ID = existingCharacters.Max(c => c.Character_ID) + 1;
newCharacter.CharacterID = existingCharacters.Max(c => c.CharacterID) + 1;
}
else
{
// Start with ID 1 if no characters exist
newCharacter.Character_ID = 1;
newCharacter.CharacterID = 1;
}
newCharacter.Player_ID = context._account.AccountId;
newCharacter.AccountID = context._account.AccountId;
//Logger.Write("newCharacter.CharacterId {0} {1}", newCharacter.CharacterId, context._account.AccountId);

View File

@ -23,7 +23,7 @@ namespace PSO2SERVER.Packets.Handlers
{
foreach (var character in db.Characters)
if (character.Character_ID == id)
if (character.CharacterID == id)
{
db.Characters.Remove(character);
db.ChangeTracker.DetectChanges();

View File

@ -22,11 +22,11 @@ namespace PSO2SERVER.Packets.Handlers
foreach (var client in ServerApp.Instance.Server.Clients)
{
if (client.Character.Character_ID == id)
if (client.Character.CharacterID == id)
{
var infoPacket = new GuildInfoPacket(context.Character);
context.SendPacket(infoPacket);
Logger.Write("[NFO] Sent guild info to " + client.Character.Character_ID);
Logger.Write("[NFO] Sent guild info to " + client.Character.CharacterID);
break;
}
}

View File

@ -42,12 +42,12 @@ namespace PSO2SERVER.Packets.Handlers
if (!string.IsNullOrWhiteSpace(cpu_info))
{
player.Cpu_Info = cpu_info;
player.CpuInfo = cpu_info;
}
if (!string.IsNullOrWhiteSpace(video_info))
{
player.Video_Info = video_info;
player.VideoInfo = video_info;
}
// 保存更改并捕获可能的异常

View File

@ -7,7 +7,7 @@ namespace PSO2SERVER.Packets.Handlers
[PacketHandlerAttr(0x11, 0x97)]
class CharacterRenameRequestPacket : PacketHandler
{
public uint player_id { get; set; }
public uint CharacterID { get; set; }
public override void HandlePacket(Client context, byte flags, byte[] data, uint position, uint size)
{
var info = string.Format("[<--] 接收到的数据 (hex): {0} 字节", data.Length);
@ -16,6 +16,9 @@ namespace PSO2SERVER.Packets.Handlers
if (context._account == null)
return;
var reader = new PacketReader(data);
var CharacterID = reader.ReadUInt32();
context.SendPacket(new CharacterRenamePacket());
}
}

View File

@ -13,7 +13,7 @@ namespace PSO2SERVER.Packets.Handlers
{
public struct CharacterNewNameRequestPacket
{
public uint player_id;
public uint CharacterID;
public string Name;
}
@ -30,7 +30,7 @@ namespace PSO2SERVER.Packets.Handlers
// 读取数据并填充到结构体中
var reader = new PacketReader(data, position, size);
packet.player_id = reader.ReadUInt32();
packet.CharacterID = reader.ReadUInt32();
packet.Name = reader.ReadFixedLengthUtf16(16); // 读取16字节长度的名字
NewNameStatus isdone = NewNameStatus.Failure;
@ -39,7 +39,7 @@ namespace PSO2SERVER.Packets.Handlers
using (var db = new ServerEf())
{
// 先检测角色是否存在
var existingCharacters = db.Characters.Where(c => c.Account.AccountId == context._account.AccountId).ToList();
var existingCharacters = db.Characters.Where(c => c.CharacterID == packet.CharacterID && c.AccountID == context._account.AccountId).ToList();
if (existingCharacters.Count > 0)
{
// 更新角色名字字段
@ -52,10 +52,9 @@ namespace PSO2SERVER.Packets.Handlers
isdone = NewNameStatus.Success;
}
}
}
context.SendPacket(new CharacterNewNamePacket(isdone, packet.player_id, packet.Name));
context.SendPacket(new CharacterNewNamePacket(isdone, packet.CharacterID, packet.Name));
}
}
}

View File

@ -10,8 +10,8 @@ namespace PSO2SERVER.Packets.Handlers
{
public struct Unk11B0Packet
{
public uint unk1;
public uint unk2;
public uint unk1 { get; set; }
public uint unk2 { get; set; }
}
public override void HandlePacket(Client context, byte flags, byte[] data, uint position, uint size)
@ -25,7 +25,7 @@ namespace PSO2SERVER.Packets.Handlers
// 读取数据并填充到结构体中
var reader = new PacketReader(data, position, size);
packet.unk1 = reader.ReadUInt32();
packet.unk1 = reader.ReadUInt32();
packet.unk2 = reader.ReadUInt32();
}
}
}

View File

@ -38,7 +38,7 @@ namespace PSO2SERVER.Packets.Handlers
// {
// try
// {
// var character = db.Characters.FirstOrDefault(c => c.Player_ID == charId);
// var character = db.Characters.FirstOrDefault(c => c.AccountID == charId);
// if (character == null || character.Account == null || character.Account.AccountId != context._account.AccountId)
// {

View File

@ -0,0 +1,51 @@
using System;
using System.Linq;
using PSO2SERVER.Database;
using PSO2SERVER.Models;
using PSO2SERVER.Packets.PSOPackets;
namespace PSO2SERVER.Packets.Handlers
{
[PacketHandlerAttr(0x11, 0xEA)]
class NicknameError : PacketHandler
{
public struct NicknameErrorPacket
{
public uint unk1 { get; set; }
public string Nickname { 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 NicknameErrorPacket();
// 读取数据并填充到结构体中
var reader = new PacketReader(data, position, size);
packet.unk1 = reader.ReadUInt32();//0x02????
packet.Nickname = reader.ReadUtf16(0x4544, 0x14); ;
context._account.Nickname = packet.Nickname;
// 修改数据库字段
using (var db = new ServerEf())
{
// 先检测角色是否存在
var existingAccount = db.Account.Where(c => c.AccountId == context._account.AccountId).ToList();
if (existingAccount.Count > 0)
{
// 更新角色名字字段
var Account = existingAccount.FirstOrDefault();
if (Account != null)
{
Account.Nickname = packet.Nickname;
db.SaveChanges(); // 保存修改
}
}
}
}
}
}

View File

@ -11,6 +11,8 @@ namespace PSO2SERVER.Packets.Handlers
{
var info = string.Format("[<--] 接收到的数据 (hex): {0} 字节", data.Length);
Logger.WriteHex(info, data);
//context.SendPacket(new NoPayloadPacket(0x19, 0x05));
}
}
}

View File

@ -11,6 +11,9 @@ namespace PSO2SERVER.Packets.Handlers
{
var info = string.Format("[<--] 接收到的数据 (hex): {0} 字节", data.Length);
Logger.WriteHex(info, data);
//context.SendPacket(new NoPayloadPacket(0x19, 0x07));
}
}
}

View File

@ -0,0 +1,16 @@
using System;
using PSO2SERVER.Models;
using PSO2SERVER.Packets.PSOPackets;
namespace PSO2SERVER.Packets.Handlers
{
[PacketHandlerAttr(0x1F, 0x09)]
class _1F_09_UNK : PacketHandler
{
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);
}
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using PSO2SERVER.Models;
using PSO2SERVER.Packets.PSOPackets;
namespace PSO2SERVER.Packets.Handlers
{
[PacketHandlerAttr(0x23, 0x0B)]
class SkitItemAddRequest : PacketHandler
{
public struct SkitItemAddRequestPacket
{
// Skit name - fixed length of 0x20 bytes (32 bytes).
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
public byte[] skit_name;
// Unknown field (u32)
public uint unk;
// 初始化字段的构造函数
public SkitItemAddRequestPacket(string skitName, uint unkValue)
{
// Initialize skit_name with a fixed size (32 bytes)
skit_name = new byte[0x20];
// Convert the skitName string to ASCII bytes and copy to the skit_name array
byte[] nameBytes = Encoding.ASCII.GetBytes(skitName);
Array.Copy(nameBytes, skit_name, Math.Min(nameBytes.Length, 0x20));
// Set the unk field
unk = unkValue;
}
// Optional: Method to get the skit_name as a string
public string GetSkitName()
{
// Convert the byte array back to a string, stripping off any padding (null characters)
return Encoding.ASCII.GetString(skit_name).TrimEnd('\0');
}
}
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);
// 提取 skit_name 和 unk 字段
byte[] skitNameBytes = new byte[0x20];
Array.Copy(data, position, skitNameBytes, 0, 0x20);
uint unk = BitConverter.ToUInt32(data, (int)(position + 0x20));
// 创建一个 SkitItemAddRequestPacket 实例
var packet = new SkitItemAddRequestPacket(Encoding.ASCII.GetString(skitNameBytes).TrimEnd('\0'), unk);
// 打印处理结果(仅作示例)
Console.WriteLine("Skit Name: " + packet.GetSkitName());
Console.WriteLine("Unk Value: " + packet.unk);
context.SendPacket(new SkitItemAddResponsePacket(packet.GetSkitName(), packet.unk));
}
}
}

View File

@ -65,7 +65,7 @@ namespace PSO2SERVER.Packets.PSOPackets
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?
writer.WriteFixedLengthUtf16(_character.Account.Nickname, 16); // Nickname, maybe not 16 chars?
for (var i = 0; i < 64; i++)
writer.Write((byte)0);

View File

@ -3,76 +3,205 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using PSO2SERVER.Models;
using static PSO2SERVER.Models.Character;
using static PSO2SERVER.Party.Party;
namespace PSO2SERVER.Packets.PSOPackets
{
class PartyInitPacket : Packet
{
private Character[] members;
public PartyInitPacket(Character[] members)
public unsafe struct PartyEntry
{
this.members = members;
/// 玩家对象的头部信息包含ID、类型、区域ID等
public ObjectHeader id;
/// 玩家昵称。
public string nickname;
/// 玩家角色名称。
public string char_name;
/// 主职业的等级。
public byte level;
/// 副职业的等级。
public byte sublevel;
/// 玩家主职业类型(`ClassType` 枚举类型)。
public ClassType mainClass;
/// 玩家副职业类型(`ClassType` 枚举类型)。
public ClassType subClass;
/// 玩家在队伍中的颜色标识。
public PartyColor color;
/// 7字节的未知数据通常是填充或者未定义用途
public fixed byte unk1[7];
/// 一个未知的 uint 类型数据,具体用途不明。
public uint unk2;
/// 玩家HP体力的三个数值。具体原因为什么有三个值目前不明。
public fixed uint hp[3];
/// 玩家所在的地图ID。
public ushort mapid;
/// 另一个未知的 ushort 类型数据。
public ushort unk3;
/// 12字节的未知数据。
public fixed byte unk4[0x0C];
/// 3个未知的 uint 数组数据。
public fixed uint unk5[3];
/// 未知的字符串,可能是其他信息(如玩家状态或其他描述信息)。
public string unk6;
/// PSO VITAPlayStation Vita相关字段已注释掉可能与Vita版本相关。
/// public string unk10;
/// 玩家角色的 ASCII 字符串相关数据,已注释掉。
/// public AsciiString unk7; // ASCII 字符串
/// 玩家语言设置(`ShortLanguage` 枚举类型),已注释掉。
/// public ShortLanguage lang;
/// 3字节的未知数据已注释掉。
/// public fixed byte unk9[3];
}
/// 队伍实例.
private ObjectHeader party_object;
/// 队伍队长实例.
private ObjectHeader leader;
/// 队伍人数.
private uint people_amount;
/// 队伍成员.
private PartyEntry[] entries = new PartyEntry[4]; // 这个是一个包含 4 个 `PartyEntry` 结构的数组表示队伍中的4个成员。
/// <summary>
/// 未知
/// </summary>
private AsciiString unk2;
private Character[] players;
public PartyInitPacket(Character[] players)
{
this.players = players;
party_object = new ObjectHeader((uint)players[0].Account.AccountId, ObjectType.Party);
leader = new ObjectHeader((uint)players[0].Account.AccountId, ObjectType.Player);
people_amount = (uint)players.Length;
// 队伍结构数据
for (int i = 0; i < players.Length; i++)
{
entries[i].id = new ObjectHeader((uint)players[i].Account.AccountId, ObjectType.Player); // Header of player
entries[i].nickname = players[i].Account.Nickname;
entries[i].char_name = players[i].Name;
//writer.WriteUtf16(players[i].Name, 0xD863, 0xA9);
//writer.WriteUtf16(players[i].Account.Nickname, 0xD863, 0xA9);
entries[i].level = (byte)players[i].Jobs.entries.hunter.level;
entries[i].sublevel = (byte)players[i].Jobs.entries.hunter.level2;
entries[i].mainClass = players[i].Jobs.mainClass;
entries[i].subClass = players[i].Jobs.subClass;
entries[i].color = (PartyColor)i;
entries[i].unk2 = 0;
entries[i].mapid = 0;
entries[i].unk3 = 0;
entries[i].unk6 = players[i].Name;
}
}
public override byte[] Build()
{
if (members.Length < 1)
if (players.Length < 1)
return new byte[0];
// xor: 0xD863, sub: 0xA9
PacketWriter writer = new PacketWriter();
writer.WriteBytes(0, 12); // Unknown 12 bytes, not obj header
writer.WriteStruct(new ObjectHeader((uint)members[0].Account.AccountId, ObjectType.Player)); // Account receiving the thing
writer.WriteStruct(members.Length); // Likely partymembercount
for(int i = 0; i < members.Length; i++)
writer.WriteStruct(party_object);
writer.WriteStruct(leader); // Account receiving the thing
writer.Write(people_amount); // Likely partymembercount
for (int i = 0; i < 4; i++)
{
writer.WriteStruct(new ObjectHeader((uint)members[i].Account.AccountId, ObjectType.Player)); // Header of player
writer.WriteUtf16(members[i].Name, 0xD863, 0xA9);
writer.WriteUtf16(members[i].Account.Nickname, 0xD863, 0xA9);
writer.Write((byte)members[i].Jobs.entries.hunter.level); // Active class level
writer.Write((byte)0); // idk
writer.Write((byte)4); // idk
writer.Write((byte)0xFF); // idk
writer.Write((byte)0); // idk
writer.Write((byte)0); // idk
writer.Write((byte)0); // idk
writer.Write((byte)0xFF); // idk
writer.Write((byte)0xFF); // idk
writer.Write((byte)0); // idk
writer.Write((ushort)0);
writer.Write((ushort)0);
writer.Write((ushort)0);
writer.Write((ushort)0);
writer.Write((ushort)0);
writer.WriteStruct(entries[i].id); // Header of player
writer.WriteUtf16(entries[i].nickname, 0xD863, 0xA9);
writer.WriteUtf16(entries[i].char_name, 0xD863, 0xA9);
writer.Write(entries[i].level); // Active class level
writer.Write(entries[i].sublevel); // idk
writer.Write((byte)entries[i].mainClass); // idk
writer.Write((byte)entries[i].subClass); // idk
writer.Write((byte)entries[i].color); // idk
writer.WriteBytes(0, 7);
writer.Write(entries[i].unk2); // idk
///hp
writer.Write((uint)0);
writer.Write((uint)0);
writer.Write((uint)0);
/// 玩家所在的地图ID。
writer.Write(entries[i].mapid);
writer.Write(entries[i].unk3);
writer.WriteBytes(0, 12);
writer.Write(0);
writer.Write(0);
writer.Write((uint)0);
writer.Write((uint)0);
writer.WriteUtf16(entries[i].unk6, 0xD863, 0xA9);
}
for(int i = 0; i < 4 - members.Length; i++) // Empty entries
{
writer.WriteStruct(new ObjectHeader(0, 0)); // Header of player
writer.WriteMagic(0, 0xD863, 0xA9);
writer.WriteMagic(0, 0xD863, 0xA9);
writer.Write((byte)0); // Active class level
writer.Write((byte)0); // idk
writer.Write((byte)0); // idk
writer.Write((byte)0); // idk
writer.Write((byte)0); // idk
writer.Write((byte)0); // idk
writer.Write((byte)0); // idk
writer.Write((byte)0); // idk
writer.Write((byte)0); // idk
writer.Write((byte)0); // idk
writer.Write((ushort)0);
writer.Write((ushort)0);
writer.Write((ushort)0);
writer.Write((ushort)0);
writer.Write((ushort)0);
writer.WriteBytes(0, 12);
writer.Write(0);
}
writer.WriteAscii("123456", 0xD863, 0xA9);
//// 队伍结构数据
//for(int i = 0; i < players.Length; i++)
//{
// PartyEntry pe = new PartyEntry();
// writer.WriteStruct(new ObjectHeader((uint)players[i].Account.AccountId, ObjectType.Player)); // Header of player
// writer.WriteUtf16(players[i].Name, 0xD863, 0xA9);
// writer.WriteUtf16(players[i].Account.Nickname, 0xD863, 0xA9);
// writer.Write((byte)players[i].Jobs.entries.hunter.level); // Active class level
// writer.Write((byte)players[i].Jobs.entries.hunter.level2); // idk
// writer.Write((byte)players[i].Jobs.mainClass); // idk
// writer.Write((byte)players[i].Jobs.subClass); // idk
// writer.Write((byte)i); // idk
// writer.WriteBytes(0, 7);
// writer.Write((uint)0); // idk
// ///hp
// writer.Write((uint)0);
// writer.Write((uint)0);
// writer.Write((uint)0);
// /// 玩家所在的地图ID。
// writer.Write(pe.mapid);
// writer.Write(pe.unk3);
// writer.WriteBytes(0, 12);
// writer.Write((uint)0);
// writer.Write((uint)0);
// writer.Write((uint)0);
//}
//for(int i = 0; i < 4 - players.Length; i++) // Empty entries
//{
// writer.WriteStruct(new ObjectHeader(0, 0)); // Header of player
// writer.WriteMagic(0, 0xD863, 0xA9);
// writer.WriteMagic(0, 0xD863, 0xA9);
// writer.Write((byte)0); // Active class level
// writer.Write((byte)0); // idk
// writer.Write((byte)0); // idk
// writer.Write((byte)0); // idk
// writer.Write((byte)0); // idk
// writer.WriteBytes(0, 7);
// writer.Write((uint)0); // idk
// ///hp
// writer.Write((uint)0);
// writer.Write((uint)0);
// writer.Write((uint)0);
// /// 玩家所在的地图ID。
// writer.Write((ushort)0);
// writer.Write((ushort)0);
// writer.WriteBytes(0, 12);
// writer.Write((uint)0);
// writer.Write((uint)0);
// writer.Write((uint)0);
//}
return writer.ToArray();
}

View File

@ -42,25 +42,24 @@ namespace PSO2SERVER.Packets.PSOPackets
{
var chars = db.Characters
.Where(w => w.Account.AccountId == _PlayerId)
.OrderBy(o => o.Character_ID) // TODO: 按照最后游玩的角色排序
.OrderBy(o => o.CharacterID) // TODO: 按照最后游玩的角色排序
.Select(s => s);
writer.Write((uint)chars.Count()); // 写入玩家数量
for (var i = 0; i < 0x4; i++) // 可能是账户ID
writer.Write((byte)0);
writer.Write((uint)_PlayerId);
foreach (var ch in chars)
{
writer.Write((uint)ch.Character_ID);
writer.Write((uint)_PlayerId);
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.Voice_Type);
writer.Write(ch.VoiceType);
writer.Write(ch.Unk2);
writer.Write(ch.Voice_Pitch);
writer.Write(ch.VoicePitch);
writer.Write((uint)0);
writer.WriteFixedLengthUtf16(ch.Name, 16);

View File

@ -21,13 +21,13 @@ namespace PSO2SERVER.Packets.PSOPackets
}
private uint _account_id;
private int _character_id;
private int _CharacterID;
private CharacterShipTransferRightsPacket pkt = new CharacterShipTransferRightsPacket();
public CharacterMovePacket(int character_id, uint account_id)
public CharacterMovePacket(int CharacterID, uint account_id)
{
_account_id = account_id;
_character_id = character_id;
_CharacterID = CharacterID;
pkt = new CharacterShipTransferRightsPacket
{
Status = 0,

View File

@ -8,9 +8,10 @@ namespace PSO2SERVER.Packets.PSOPackets
{
public class BannerListPacket : Packet
{
public BannerListPacket()
private AsciiString banners { get; set; }
public BannerListPacket(string banners)
{
this.banners = new AsciiString(banners);
}
#region implemented abstract members of Packet
@ -18,12 +19,13 @@ namespace PSO2SERVER.Packets.PSOPackets
public override byte[] Build()
{
var pkt = new PacketWriter();
pkt.WriteAscii(banners, 0xD67D, 0xF5);
return pkt.ToArray();
}
public override PacketHeader GetHeader()
{
return new PacketHeader(0x11, 0xED, PacketFlags.None);
return new PacketHeader(0x11, 0xED, PacketFlags.PACKED);
}
#endregion

View File

@ -24,7 +24,7 @@ namespace PSO2SERVER.Packets.PSOPackets
writer.Write((byte) 0);
// Character ID
writer.Write((uint) _character.Character_ID);
writer.Write((uint) _character.CharacterID);
// Padding?
for (var i = 0; i < 4; i++)

View File

@ -25,8 +25,8 @@ namespace PSO2SERVER.Packets.PSOPackets
Palette.WriteToStream(pkt);
byte[] byteArray = pkt.ToArray();
var info = string.Format("[-->] 发送的数据 (hex): {0} 字节", byteArray.Length);
Logger.WriteHex(info, byteArray);
//var info = string.Format("[-->] 发送的数据 (hex): {0} 字节", byteArray.Length);
//Logger.WriteHex(info, byteArray);
return pkt.ToArray();
}

View File

@ -8,18 +8,15 @@ namespace PSO2SERVER.Packets.PSOPackets
{
public class AccountFlagsPacket : Packet
{
/// <summary>
/// Account flags.
/// </summary>
public List<byte> Flags { get; set; } = new List<byte>(0x400);
/// <summary>
/// Account parameters.
/// </summary>
public List<uint> Params { get; set; } = new List<uint>(0x100);
// Account flags (0x400 bytes)
public List<byte> Flags { get; set; }
// Account parameters (0x100 bytes)
public List<uint> Params { get; set; }
public AccountFlagsPacket()
{
Flags = new List<byte>(new byte[0x400]);
Params = new List<uint>(new uint[0x100]);
}
#region implemented abstract members of Packet
@ -27,6 +24,8 @@ namespace PSO2SERVER.Packets.PSOPackets
public override byte[] Build()
{
var pkt = new PacketWriter();
pkt.Write(new byte[0x400]);
pkt.Write(new byte[0x400]);
return pkt.ToArray();
}

View File

@ -12,15 +12,17 @@ namespace PSO2SERVER.Packets.PSOPackets
/// <summary>
/// Character flags.
/// </summary>
public List<byte> Flags { get; set; } = new List<byte>(0xC00);
public List<byte> Flags { get; set; }
/// <summary>
/// Character parameters.
/// </summary>
public List<uint> Params { get; set; } = new List<uint>(0x100);
public List<uint> Params { get; set; }
public CharacterFlagsPacket()
{
Flags = new List<byte>(new byte[0xC00]);
Params = new List<uint>(new uint[0x100]);
}
#region implemented abstract members of Packet
@ -28,6 +30,8 @@ namespace PSO2SERVER.Packets.PSOPackets
public override byte[] Build()
{
var pkt = new PacketWriter();
pkt.Write(new byte[0xC00]);
pkt.Write(new byte[0x400]);
return pkt.ToArray();
}

View File

@ -1,31 +0,0 @@
using PSO2SERVER.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PSO2SERVER.Packets.PSOPackets
{
public class SkitItemAddRequestPacket : Packet
{
public SkitItemAddRequestPacket()
{
}
#region implemented abstract members of Packet
public override byte[] Build()
{
var pkt = new PacketWriter();
return pkt.ToArray();
}
public override PacketHeader GetHeader()
{
return new PacketHeader(0x23, 0x0B, PacketFlags.None);
}
#endregion
}
}

View File

@ -1,26 +1,62 @@
using PSO2SERVER.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace PSO2SERVER.Packets.PSOPackets
{
public class SkitItemAddResponsePacket : Packet
{
// Skit name - fixed length of 0x20 bytes (32 bytes).
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
public byte[] skit_name;
// Unknown field (u32)
public uint unk;
// 默认构造函数
public SkitItemAddResponsePacket()
{
// 默认情况下skit_name 初始化为空的字节数组
skit_name = new byte[0x20];
}
// 带参数的构造函数,方便直接初始化字段
public SkitItemAddResponsePacket(string skitName, uint unkValue)
{
// 初始化 skit_name 字段为 32 字节
skit_name = new byte[0x20];
// 将字符串转换为字节数组并填充到 skit_name 中
byte[] nameBytes = Encoding.ASCII.GetBytes(skitName);
Array.Copy(nameBytes, skit_name, Math.Min(nameBytes.Length, 0x20));
// 设置 unk 字段的值
unk = unkValue;
}
// 获取 skit_name 字段作为字符串,去除 null 字符
public string GetSkitName()
{
return Encoding.ASCII.GetString(skit_name).TrimEnd('\0');
}
#region implemented abstract members of Packet
// 构建数据包的方法
public override byte[] Build()
{
var pkt = new PacketWriter();
// 写入 skit_name 字段
pkt.Write(skit_name, 0, skit_name.Length);
// 写入 unk 字段
pkt.Write(unk);
// 返回构建好的字节数组
return pkt.ToArray();
}
// 获取数据包头
public override PacketHeader GetHeader()
{
return new PacketHeader(0x23, 0x0C, PacketFlags.None);
@ -28,4 +64,4 @@ namespace PSO2SERVER.Packets.PSOPackets
#endregion
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
@ -26,7 +27,7 @@ namespace PSO2SERVER.Packets
public void WriteAscii(string str, uint xor, uint sub)
{
if(str == null || str == "")
if((str == null) || (str == "") || (str.Length == 0))
{
WriteMagic(0, xor, sub);
@ -48,6 +49,37 @@ namespace PSO2SERVER.Packets
}
}
public void WriteAscii(AsciiString str, uint xor, uint sub)
{
if (str == null || str.Length == 0)
{
// 如果 AsciiString 为空或长度为 0写入 magic长度为 0
WriteMagic(0, xor, sub);
}
else
{
// Magic, followed by string, followed by null terminator,
// followed by padding characters if needed.
var charCount = (uint)str.Length;
var padding = (4 - (charCount + 1) % 4) % 4; // +1 用于计算 null 终结符的空间
// 写入 Magic 值charCount + 1包括 null 终结符)
WriteMagic(charCount + 1, xor, sub);
// 写入字符串的字节数据
Write(str.ToBytes());
// 写入 null 终结符
Write((byte)0);
// 填充 null 字节以保证 4 字节对齐
for (var i = 0; i < padding; i++)
{
Write((byte)0);
}
}
}
internal void WritePosition(PSOLocation location)
{
Write(Helper.FloatToHalfPrecision(location.RotX));
@ -61,7 +93,7 @@ namespace PSO2SERVER.Packets
public void WriteUtf16(string str, uint xor, uint sub)
{
if (str.Length == 0)
if ((str == null) || (str == "") || (str.Length == 0))
{
WriteMagic(0, xor, sub);
}
@ -154,10 +186,5 @@ namespace PSO2SERVER.Packets
Write(b);
}
}
internal void WriteFixedLengthUtf16(int i, int v)
{
throw new NotImplementedException();
}
}
}

View File

@ -9,6 +9,14 @@ namespace PSO2SERVER.Party
{
public class Party
{
public enum PartyColor : byte
{
Red,
Green,
Yellow,
Blue,
}
public string name;
private List<Client> members;
private Client host;

View File

@ -160,6 +160,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AsciiString.cs" />
<Compile Include="Config.cs" />
<Compile Include="ConsoleSystem.cs" />
<Compile Include="Crypto\KeyLoader.cs" />
@ -173,6 +174,8 @@
<Compile Include="Models\Quest.cs" />
<Compile Include="Network\PortChecker.cs" />
<Compile Include="Object\ObjectManager.cs" />
<Compile Include="Packets\Handlers\1F-UNKHandler\1F-09-UNK1F09.cs" />
<Compile Include="Packets\Handlers\23-FlagHandler\23-0B-SkitItemAddRequest.cs" />
<Compile Include="Packets\Handlers\03-ServerHandler\03-03-InitialLoad.cs" />
<Compile Include="Packets\Handlers\03-ServerHandler\03-0C-ServerPong.cs" />
<Compile Include="Packets\Handlers\03-ServerHandler\03-10-MapLoaded.cs" />
@ -200,6 +203,7 @@
<Compile Include="Packets\Handlers\0E-PartyHandler\0E-19-ChatStatusHandler.cs" />
<Compile Include="Packets\Handlers\11-ClientHandler\11-9B-CharacterNewNameRequest.cs" />
<Compile Include="Packets\Handlers\11-ClientHandler\11-97-CharacterRenameRequest.cs" />
<Compile Include="Packets\Handlers\11-ClientHandler\11-EA-NicknameError.cs" />
<Compile Include="Packets\Handlers\11-ClientHandler\11-BC-CharacterShipTransferCanceldRequest.cs" />
<Compile Include="Packets\Handlers\11-ClientHandler\11-B8-CharacterShipTransferRightsRequest.cs" />
<Compile Include="Packets\Handlers\11-ClientHandler\11-06-DeleteCharacter.cs" />
@ -363,7 +367,6 @@
<Compile Include="Packets\PSOPackets\23-FlagPackets\23-0D-Unk230DPacket.cs" />
<Compile Include="Packets\PSOPackets\23-FlagPackets\23-0E-Unk230EPacket.cs" />
<Compile Include="Packets\PSOPackets\23-FlagPackets\23-15-Unk2315Packet.cs" />
<Compile Include="Packets\PSOPackets\23-FlagPackets\23-0B-SkitItemAddRequestPacket.cs" />
<Compile Include="Packets\PSOPackets\23-FlagPackets\23-07-CharacterFlagsPacket.cs" />
<Compile Include="Packets\PSOPackets\2D-PlayerShopPacket\2D-03-ProductSearchResponsePacket.cs" />
<Compile Include="Packets\PSOPackets\2D-PlayerShopPacket\2D-13-RecruitingAlliancesResponsePacket.cs" />