修正部分数据包结构

This commit is contained in:
Sancaros 2024-12-07 01:44:49 +08:00
parent 6f9d732941
commit 8e6f344974
9 changed files with 351 additions and 135 deletions

View File

@ -110,11 +110,42 @@ namespace PSO2SERVER.Database
}
}
public string Unk4 { get; set; }
public byte[] Unk4 { get; set; } = new byte[152];
public virtual Account Account { get; set; }
public byte[] fulldata { get; set; }
public byte[] BuildCharacterByteArray()
{
PacketWriter writer = new PacketWriter();
// 序列化字段CharacterID, AccountID, Unk1, VoiceType, Unk2, VoicePitch
writer.Write((uint)CharacterID);
writer.Write((uint)AccountID);
writer.Write((uint)Unk1);
writer.Write((uint)VoiceType);
writer.Write((ushort)Unk2);
writer.Write(VoicePitch);
// 序列化 Name (假设固定长度字符串16个字节UTF-16 编码)
writer.WriteFixedLengthUtf16(Name, 0x10);
// 序列化 Looks
writer.WriteStruct(Looks);
// 序列化 Unk3
writer.Write((uint)Unk3);
// 序列化 Jobs
writer.WriteStruct(Jobs);
// 序列化 Unk4
writer.Write(Unk4);
// 最后返回字节流
return writer.ToArray();
}
}
public class NPC

View File

@ -9,26 +9,26 @@ namespace PSO2SERVER.Models
[StructLayout(LayoutKind.Sequential)]
public unsafe struct ShortItemId
{
byte ItemType;
byte Id;
ushort Subid;
public byte ItemType;
public byte Id;
public ushort Subid;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct ItemId
{
ushort ItemType;
ushort Id;
ushort Unk3;
ushort Subid;
public ushort ItemType;
public ushort Id;
public ushort Unk3;
public ushort Subid;
}
[StructLayout(LayoutKind.Sequential)]
public struct PSO2Items
{
long guid;
ItemId id;
Items data;
public ulong uuid;
public ItemId id;
public Items data;
}
[StructLayout(LayoutKind.Explicit)]
@ -58,73 +58,73 @@ namespace PSO2SERVER.Models
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PSO2ItemWeapon
{
byte flags;
byte element;
byte force;
byte grind;
byte grindPercent;
byte unknown1;
short unknown2;
fixed short affixes[8];
uint potential;
byte extend;
byte unknown3;
ushort unknown4;
uint unknown5;
uint unknown6;
public byte flags;
public byte element;
public byte force;
public byte grind;
public byte grindPercent;
public byte unknown1;
public short unknown2;
public fixed short affixes[8];
public uint potential;
public byte extend;
public byte unknown3;
public ushort unknown4;
public uint unknown5;
public uint unknown6;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PSO2ItemClothing
{
ushort flags;
fixed byte unk1[0x14];
public ushort flags;
public fixed byte unk1[0x14];
public HSVColor Color;
fixed byte unk2[0xA];
ushort Unk3;
public fixed byte unk2[0xA];
public ushort Unk3;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PSO2ItemConsumable
{
ushort flags;
fixed byte unk1[0x24];
ushort amount;
public ushort flags;
public fixed byte unk1[0x24];
public ushort amount;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PSO2ItemCamo
{
byte unk1;
byte unk2;
byte unk3;
fixed byte unk4[0x24];
byte unk5;
public byte unk1;
public byte unk2;
public byte unk3;
public fixed byte unk4[0x24];
public byte unk5;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PSO2ItemUnit
{
byte flags;
byte EnhLevel;
byte EnhPercent;
byte Unk1;
public byte flags;
public byte EnhLevel;
public byte EnhPercent;
public byte Unk1;
// 使用 fixed 数组来存储附加信息
fixed ushort Affixes[8]; // Item affix IDs (0 to 4095)
public fixed ushort Affixes[8]; // Item affix IDs (0 to 4095)
fixed byte unk4[0x7];
uint Potential;
public fixed byte unk4[0x7];
public uint Potential;
// 使用 fixed 数组来存储未知字段
fixed byte Unk2[4];
public fixed byte Unk2[4];
uint Unk3;
ushort Unk4;
ushort Unk5;
public uint Unk3;
public ushort Unk4;
public ushort Unk5;
// 提供访问固定数组的属性
Span<ushort> AffixSpan
public Span<ushort> AffixSpan
{
get
{
@ -148,8 +148,8 @@ namespace PSO2SERVER.Models
public TimeSpan EndDate; // 对应 Rust 的 Duration
/// Campaign title (固定长度 0x3E).
private const int TitleLength = 0x3E;
private byte[] titleBytes;
public const int TitleLength = 0x3E;
public byte[] titleBytes;
public string Title
{
@ -163,8 +163,8 @@ namespace PSO2SERVER.Models
}
/// Campaign conditions (固定长度 0x102).
private const int ConditionsLength = 0x102;
private byte[] conditionsBytes;
public const int ConditionsLength = 0x102;
public byte[] conditionsBytes;
public string Conditions
{
@ -243,8 +243,8 @@ namespace PSO2SERVER.Models
MemoryStream stream;
//TODO
ItemTypes type = ItemTypes.Consumable;
byte[] data = new byte[Size];
public ItemTypes type = ItemTypes.Consumable;
public byte[] data = new byte[Size];
public override string ToString()
{

100
Server/Models/Time.cs Normal file
View File

@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PSO2SERVER.Models
{
public struct Duration : IEquatable<Duration>, IComparable<Duration>
{
// The number of seconds in the duration
public long Seconds { get; }
// The number of nanoseconds, should always be between 0 and 999,999,999
public int Nanoseconds { get; }
// Constructor to initialize a Duration with seconds and nanoseconds
public Duration(long seconds, int nanoseconds)
{
if (nanoseconds < 0 || nanoseconds >= 1_000_000_000)
{
throw new ArgumentOutOfRangeException(nameof(nanoseconds), "Nanoseconds must be between 0 and 999,999,999.");
}
Seconds = seconds;
Nanoseconds = nanoseconds;
}
// A default zero-length duration
public static Duration Zero => new Duration(0, 0);
// Adds two durations together
public static Duration operator +(Duration left, Duration right)
{
long totalSeconds = left.Seconds + right.Seconds;
int totalNanoseconds = left.Nanoseconds + right.Nanoseconds;
// Handle carry over for nanoseconds
if (totalNanoseconds >= 1_000_000_000)
{
totalSeconds++;
totalNanoseconds -= 1_000_000_000;
}
return new Duration(totalSeconds, totalNanoseconds);
}
// Subtracts one duration from another
public static Duration operator -(Duration left, Duration right)
{
long totalSeconds = left.Seconds - right.Seconds;
int totalNanoseconds = left.Nanoseconds - right.Nanoseconds;
// Handle borrow for nanoseconds
if (totalNanoseconds < 0)
{
totalSeconds--;
totalNanoseconds += 1_000_000_000;
}
return new Duration(totalSeconds, totalNanoseconds);
}
// Compare two durations
public int CompareTo(Duration other)
{
if (Seconds < other.Seconds) return -1;
if (Seconds > other.Seconds) return 1;
return Nanoseconds.CompareTo(other.Nanoseconds);
}
// Check if two durations are equal
public bool Equals(Duration other)
{
return Seconds == other.Seconds && Nanoseconds == other.Nanoseconds;
}
// Override ToString() for custom string representation
public override string ToString()
{
return $"{Seconds}s {Nanoseconds}ns";
}
// Static method to create a Duration from milliseconds
public static Duration FromMilliseconds(long milliseconds)
{
long seconds = milliseconds / 1000;
int nanoseconds = (int)((milliseconds % 1000) * 1_000_000);
return new Duration(seconds, nanoseconds);
}
// Static method to create a Duration from seconds and nanoseconds
public static Duration FromSeconds(long seconds, int nanoseconds)
{
return new Duration(seconds, nanoseconds);
}
}
}

View File

@ -15,56 +15,53 @@ namespace PSO2SERVER.Protocol.Handlers
{
#region implemented abstract members of PacketHandler
public struct CharacterCreatePacket
{
public int CharacterID;
public int AccountID;
public int Unk1;
public int VoiceType;
public short Unk2;
public short VoicePitch;
public string Name;
public int Unk3;
public LooksParam Looks;
public JobParam Jobs;
public string unk4; // 148 字节的字节数组
}
private CharacterCreatePacket packet = new CharacterCreatePacket();
public uint CharacterID { get; set; }
public uint AccountID { get; set; }
public uint Unk1 { get; set; }
public uint VoiceType { get; set; }
public ushort Unk2 { get; set; }
public short VoicePitch { get; set; }
public string Name { get; set; }
public LooksParam Looks { get; set; }
public uint Unk3 { get; set; }
public JobParam Jobs { get; set; }
public byte[] unk4 { get; set; } = new byte[152]; // 152 字节的字节数组
public override void HandlePacket(Client context, byte flags, byte[] data, uint position, uint size)
{
if (context._account == null)
return;
var reader = new PacketReader(data, position, size);
var info = string.Format("[<--] 接收到的数据 (hex): {0} 字节", data.Length);
Logger.WriteHex(info, data);
packet.CharacterID = reader.ReadInt32();
packet.AccountID = reader.ReadInt32();
packet.Unk1 = reader.ReadInt32();//Unk1
packet.VoiceType = reader.ReadInt32(); // VoiceType
packet.Unk2 = reader.ReadInt16(); // 5 unknown bytes
packet.VoicePitch = reader.ReadInt16(); // VoiceData
packet.Name = reader.ReadFixedLengthUtf16(16);
packet.Unk3 = reader.ReadInt32();
packet.Looks = reader.ReadStruct<LooksParam>();
packet.Jobs = reader.ReadStruct<JobParam>();
packet.unk4 = reader.ReadFixedLengthUtf16(76);
var reader = new PacketReader(data, position, size);
CharacterID = reader.ReadUInt32();
AccountID = reader.ReadUInt32();
Unk1 = reader.ReadUInt32();//Unk1
VoiceType = reader.ReadUInt32(); // VoiceType
Unk2 = reader.ReadUInt16(); // 5 unknown bytes
VoicePitch = reader.ReadInt16(); // VoiceData
Name = reader.ReadFixedLengthUtf16(16);
Looks = reader.ReadStruct<LooksParam>();
Unk3 = reader.ReadUInt32();
Jobs = reader.ReadStruct<JobParam>();
unk4 = reader.ReadBytes(unk4.Length);
var newCharacter = new Character
{
CharacterID = 0,
AccountID = context._account.AccountId,
Unk1 = packet.Unk1,
VoiceType = packet.VoiceType,
Unk2 = packet.Unk2,
VoicePitch = packet.VoicePitch,
Name = packet.Name,
Unk3 = packet.Unk3,
Jobs = packet.Jobs,
Looks = packet.Looks,
Unk1 = (int)Unk1,
VoiceType = (int)VoiceType,
Unk2 = (short)Unk2,
VoicePitch = VoicePitch,
Name = Name,
Looks = Looks,
Unk3 = (int)Unk3,
Jobs = Jobs,
Unk4 = unk4,
Account = context._account,
fulldata = data
};

View File

@ -42,7 +42,7 @@ namespace PSO2SERVER.Protocol.Packets
public byte unk9 { get; set; }
public ushort unk10 { get; set; }
public Character _character { get; set; }
public uint unk11 { get; set; }
//public uint unk11 { get; set; }
/// Set to `1` if the player is a GM.
public uint gm_flag { get; set; }
public string nickname { get; set; }
@ -86,18 +86,8 @@ namespace PSO2SERVER.Protocol.Packets
pkt.Write(unk9);
pkt.Write(unk10);
// Character data.
pkt.Write((uint)_character.AccountID);
pkt.Write((uint)_character.CharacterID);
pkt.Write((uint)_character.Unk1);//4
pkt.Write((uint)_character.VoiceType);//4
pkt.Write((ushort)_character.Unk2);//2
pkt.Write(_character.VoicePitch);//2
pkt.WriteFixedLengthUtf16(_character.Name, 0x10);
pkt.Write((uint)_character.Unk3); // 0x90
pkt.WriteStruct(_character.Looks);
pkt.WriteStruct(_character.Jobs);
pkt.WriteBytes(0, 148);
pkt.Write(unk11);
pkt.Write(_character.BuildCharacterByteArray());
//pkt.Write(unk11);
pkt.Write(gm_flag);
pkt.WriteFixedLengthUtf16(_character.Account.Nickname, 0x10);
for (var i = 0; i < 0x60; i++)

View File

@ -12,6 +12,29 @@ namespace PSO2SERVER.Protocol.Packets
{
public class CharacterListPacket : Packet
{
// Available characters
public List<Character> Characters { get; set; } = new List<Character>();
// Equipped items (List of 10 Items for each character)
public List<PSO2Items[]> EquippedItems { get; set; } = new List<PSO2Items[]>();
// Character play times (30 times)
public uint[] PlayTimes { get; set; } = new uint[30];
// Character deletion flags (flag, deletion timestamp) (30 pairs of (u32, u32))
public (uint flag, uint timestamp)[] DeletionFlags { get; set; } = new (uint, uint)[30];
// Character ship transfer flags (30 pairs of (u32, u32))
public (uint flag, uint timestamp)[] TransferFlags { get; set; } = new (uint, uint)[30];
// Account accessory flag (unknown)
public ushort AccountAccessory { get; set; }
// Login survey flag
public uint LoginSurvey { get; set; }
// Ad flag
public uint Ad { get; set; }
// Ninji note: This packet may be followed by extra data,
// after a fixed-length array of character data structures.
@ -37,8 +60,6 @@ namespace PSO2SERVER.Protocol.Packets
public override byte[] Build()
{
var writer = new PacketWriter();
using (var db = new ServerEf())
{
var chars = db.Characters
@ -46,41 +67,51 @@ namespace PSO2SERVER.Protocol.Packets
.OrderBy(o => o.CharacterID) // TODO: 按照最后游玩的角色排序
.Select(s => s);
writer.Write((uint)chars.Count()); // 写入玩家数量
writer.Write((uint)0);
foreach (var ch in chars)
{
//JobParam jobParam = v;
//jobParam.mainClass = ClassType.Luster;
//jobParam.subClass = ClassType.Phantom;
//jobParam.entries.Luster.level = 100;
Characters.Add(ch);
writer.Write((uint)0);
// 创建一个 Consumable 类型的物品
PSO2ItemConsumable consumableItem = new PSO2ItemConsumable
{
amount = 10,
};
writer.Write((uint)ch.CharacterID);//4
writer.Write((uint)ch.AccountID);//4
writer.Write((uint)ch.Unk1);//4
writer.Write((uint)ch.VoiceType);//4
writer.Write((ushort)ch.Unk2);//2
writer.Write(ch.VoicePitch);//2
writer.WriteFixedLengthUtf16(ch.Name, 16);
//Logger.WriteInternal("[CHR] 新增名为 {0} 的新角色.", ch.Name);
writer.Write((uint)ch.Unk3);
writer.WriteStruct(ch.Looks);
writer.WriteStruct(ch.Jobs);
writer.WriteBytes(0, 148);
PSO2Items[] items = new PSO2Items[10];
for (var i = 0; i < 10; i++)
{
items[i] = new PSO2Items
{
uuid = (ulong)i,
id = new ItemId
{
ItemType = 3,
Id = 1,
Subid = 0,
},
data = new Items { Consumable = consumableItem }
};
}
EquippedItems.Add(items);
}
//for (var i = 0; i < 640; i++)
// writer.Write((byte)1);
}
//for (var i = 0; i < 30; i++)
// writer.Write((uint)1);
//writer.Write(new byte[240]);
//writer.Write(new byte[240]);
var writer = new PacketWriter();
writer.Write((uint)Characters.Count()); // 写入玩家数量
writer.Write((uint)0);
foreach (var ch in Characters)
{
writer.Write((uint)0);
writer.Write(ch.BuildCharacterByteArray());//4
}
return writer.ToArray();

View File

@ -0,0 +1,43 @@
using PSO2SERVER.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PSO2SERVER.Protocol.Packets
{
public class SendSystemMessagePacket : Packet
{
public string _msg { get; set; }
public uint unk1 { get; set; }
public uint unk2 { get; set; }
public uint unk3 { get; set; }
public SendSystemMessagePacket(string msg, uint unk1, uint unk2, uint unk3)
{
_msg = msg;
this.unk1 = 0x00002196;
this.unk2 = unk2;
this.unk3 = unk3;
}
#region implemented abstract members of Packet
public override byte[] Build()
{
var pkt = new PacketWriter();
pkt.WriteUtf16(_msg, 0x2126, 0xB0);
pkt.Write(unk1);
pkt.Write(unk2);
pkt.Write(unk3);
return pkt.ToArray();
}
public override PacketHeader GetHeader()
{
return new PacketHeader(0x19, 0x08, PacketFlags.PACKED);
}
#endregion
}
}

View File

@ -8,9 +8,25 @@ namespace PSO2SERVER.Protocol.Packets
{
public class SetLobbyEventPacket : Packet
{
/// Event string ID.
public string event_name { get; set; } //Ascii
/// Voice line string ID.
public string voice_line { get; set; } //Ascii
/// Event start timestamp.
public Duration start_time { get; set; }
/// Event end timestamp.
public Duration end_time { get; set; }
public uint repeat_secs { get; set; }
public ulong unk4 { get; set; }
public SetLobbyEventPacket()
public SetLobbyEventPacket(string eventname, string voiceline, Duration starttime, Duration endtime, uint repeatsecs, ulong unk4)
{
this.event_name = eventname;
this.voice_line = voiceline;
this.start_time = starttime;
this.end_time = endtime;
this.repeat_secs = repeatsecs;
this.unk4 = unk4;
}
#region implemented abstract members of Packet
@ -18,12 +34,18 @@ namespace PSO2SERVER.Protocol.Packets
public override byte[] Build()
{
var pkt = new PacketWriter();
pkt.WriteAscii(event_name, 0xA6E4, 0xFB);
pkt.WriteAscii(voice_line, 0xA6E4, 0xFB);
pkt.WriteStruct(start_time);
pkt.WriteStruct(end_time);
pkt.Write(repeat_secs);
pkt.Write(unk4);
return pkt.ToArray();
}
public override PacketHeader GetHeader()
{
return new PacketHeader(0x19, 0x09, PacketFlags.None);
return new PacketHeader(0x19, 0x09, PacketFlags.PACKED);
}
#endregion

View File

@ -177,6 +177,7 @@
<Compile Include="Models\PSOPalette.cs" />
<Compile Include="Models\Quest.cs" />
<Compile Include="Models\RevealedRegions.cs" />
<Compile Include="Models\Time.cs" />
<Compile Include="Network\PortChecker.cs" />
<Compile Include="Object\ObjectManager.cs" />
<Compile Include="Protocol\Handlers\0B-QuestHandler\0B-20-AcceptQuest.cs" />
@ -249,6 +250,7 @@
<Compile Include="Protocol\Handlers\4D-ClassicMissionPassHandler\4D-02-MissionPassRequest.cs" />
<Compile Include="Protocol\Handlers\4D-ClassicMissionPassHandler\4D-00-MissionPassInfoRequest.cs" />
<Compile Include="Protocol\Packets\0B-QuestPacket\0B-1B-QuestCategoryStopperPacket.cs" />
<Compile Include="Protocol\Packets\19-LobbyPacket\19-08-SendSystemMessagePacket.cs" />
<Compile Include="Protocol\Packets\1E-UnkPacket\1E-0C-Unk1E0CPacket.cs" />
<Compile Include="Protocol\Packets\0B-QuestPacket\0B-1C-QuestDifficultySendFinishedPacket.cs" />
<Compile Include="Protocol\Packets\0F-ItemPacket\0F-0C-LoadEquipedPacket.cs" />