修正角色数据读取位置

新增几个数据包
This commit is contained in:
Longfeng Qin 2024-12-11 19:54:48 +08:00
parent a0c9a9651a
commit 9368309049
19 changed files with 267 additions and 196 deletions

View File

@ -17,6 +17,43 @@ namespace PSO2SERVER
{
public class Client
{
public Client()
{
IsClosed = false;
_readBuffer = new byte[1024 * 64];
_readBufferSize = 0;
InputArc4 = null;
OutputArc4 = null;
Palette = new PSOPalette();
battle_stats = new PlayerStats();
SendPacket(new ServerHelloPacket(0x03, 100, 68833280));
}
public Client(Server server, SocketClient socket)
{
IsClosed = false;
_server = server;
Socket = socket;
socket.DataReceived += HandleDataReceived;
socket.ConnectionLost += HandleConnectionLost;
_readBuffer = new byte[1024 * 64];
_readBufferSize = 0;
InputArc4 = null;
OutputArc4 = null;
Palette = new PSOPalette();
battle_stats = new PlayerStats();
SendPacket(new ServerHelloPacket(0x03, 100, 68833280));
}
public enum PacketType : byte
{
/// NGS packet.
@ -40,24 +77,6 @@ namespace PSO2SERVER
//private int _packetId;
private uint _readBufferSize;
public Client(Server server, SocketClient socket)
{
IsClosed = false;
_server = server;
Socket = socket;
socket.DataReceived += HandleDataReceived;
socket.ConnectionLost += HandleConnectionLost;
_readBuffer = new byte[1024 * 64];
_readBufferSize = 0;
InputArc4 = null;
OutputArc4 = null;
SendPacket(new ServerHelloPacket(0x03, 100, 68833280));
}
/// <summary>
/// Server
/// </summary>
@ -72,8 +91,9 @@ namespace PSO2SERVER
public Character Character { get; set; }
public uint MovementTimestamp { get; internal set; }
public Flags _flags { get; set; }
public PSOPalette Palette { get; set; } = new PSOPalette();
public PlayerStats battle_stats { get; set; } = new PlayerStats();
public PSOPalette Palette { get; set; }
public PlayerStats battle_stats { get; set; }
public UserState UserState { get; set; }
/// <summary>
/// Party

View File

@ -711,8 +711,13 @@ namespace PSO2SERVER
Jobs = client.Character.Jobs
};
var fakeclient = new Client
{
_account = fakePlayer,
Character = fakeChar,
};
var fakePacket = new CharacterSpawnPacket(fakeChar, new PSOLocation(0f, 1f, 0f, 0f, x, y, z), false, false)
var fakePacket = new CharacterSpawnPacket(fakeclient, new PSOLocation(0f, 1f, 0f, 0f, x, y, z), false, false)
{
};
client.SendPacket(fakePacket);

View File

@ -32,8 +32,7 @@ namespace PSO2SERVER.Models
public enum ClassType : byte
{
Unknown = 0xFF,
Hunter = 0,
Hunter,
Ranger,
Force,
Fighter,
@ -48,6 +47,8 @@ namespace PSO2SERVER.Models
Phantom,
Etole,
Luster,
Unknown = 0xFF,
}
// 每个枚举成员的值是通过位移操作计算的:

View File

@ -18,6 +18,8 @@ namespace PSO2SERVER.Models
private List<byte> flags = new List<byte>();
private List<uint> paramsList = new List<uint>();
public Flags() { flags = new List<byte>(); paramsList = new List<uint>(); }
public void Set(int id, byte val)
{
int index = id / 8;

View File

@ -149,7 +149,12 @@ namespace PSO2SERVER.Models
public FixedList<SubPalette> Subpalettes { get; set; } = new FixedList<SubPalette>(6, new SubPalette());
public List<uint> DefaultPas { get; set; } = new List<uint>();
public PSOPalette() { }
public PSOPalette()
{
Palettes = new FixedList<WeaponPalette>(6, new WeaponPalette());
Subpalettes = new FixedList<SubPalette>(6, new SubPalette());
DefaultPas = new List<uint>();
}
public void ReadFromStream(PacketReader reader)
{

View File

@ -238,4 +238,53 @@ namespace PSO2SERVER.Models
Pad2 = pad2 ?? new byte[3]; // 默认值为3字节数组
}
}
public enum UserState
{
LoggingIn, // User is logging in, nothing is set up.
NewUsername, // User is logged in, but no username was set, only user id is set.
CharacterSelect, // User is logged in, account stuff is set up, but no character info.
PreInGame, // User has selected the character, but map and party aren't set up yet.
InGame // User is in the game, character, map, party are set up.
}
public static class UserStateExtensions
{
// 处理不同状态的逻辑
public static string GetStateMessage(this UserState state)
{
switch (state)
{
case UserState.LoggingIn:
return "User is logging in...";
case UserState.NewUsername:
return "User needs to set a username.";
case UserState.CharacterSelect:
return "User is selecting a character.";
case UserState.PreInGame:
return "User is preparing to enter the game.";
case UserState.InGame:
return "User is in the game!";
default:
return "Unknown state.";
}
}
// 比较两个状态,判断当前状态是否在另一个状态之前
public static bool IsBefore(this UserState currentState, UserState otherState)
{
return currentState < otherState;
}
// 比较两个状态,判断当前状态是否在另一个状态之后
public static bool IsAfter(this UserState currentState, UserState otherState)
{
return currentState > otherState;
}
public static void ToLogger(this UserState state)
{
Logger.Write(GetStateMessage(state));
}
}
}

View File

@ -12,6 +12,15 @@ namespace PSO2SERVER.Network
private readonly SocketServer _server;
private int _writePosition = 0;
public SocketClient()
{
_server = new SocketServer(0);
Socket = new TcpClient();
_readBuffer = new byte[1024 * 16];
_writeBuffer = new byte[1024 * 1024]; // too high? too low? not sure
}
public SocketClient(SocketServer server, TcpClient socket)
{
_server = server;

View File

@ -10,14 +10,6 @@ namespace PSO2SERVER.Party
{
public class Party
{
public enum PartyColor : byte
{
Red,
Green,
Yellow,
Blue,
}
public string name;
private List<Client> members;
private Client host;
@ -25,28 +17,28 @@ namespace PSO2SERVER.Party
public PartySettingsPacket partySetting;
public string questname;
public Party(string name, Client host)
public Party(string name, Client Leader)
{
this.name = name;
this.host = host;
this.host = Leader;
members = new List<Client>();
currentQuest = new PartyQuest();
partySetting = new PartySettingsPacket();
questname = string.Empty;
addClientToParty(host);
addClientToParty(Leader);
}
public void addClientToParty(Client c)
{
members.Add(c);
if (members.Count < 1)
{
c.SendPacket(new PartyInitPacket(new Character[1] { c.Character }));
c.SendPacket(new PartyInitPacket(members));
}
else
{
// ???
}
members.Add(c);
c.currentParty = this;
}
@ -83,6 +75,78 @@ namespace PSO2SERVER.Party
}
}
public enum PartyColor : byte
{
Red,
Green,
Yellow,
Blue,
}
[Serializable]
public unsafe struct PartyEntry
{
/// 玩家对象的头部信息包含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[0x03];
/// 未知的字符串,可能是其他信息(如玩家状态或其他描述信息)。
public string unk6;
// PSO VITAPlayStation Vita相关字段已注释掉可能与Vita版本相关。
public string unk10;
// 玩家角色的 ASCII 字符串相关数据,已注释掉。
public string unk7; // ASCII 字符串
// 玩家语言设置(`ShortLanguage` 枚举类型),已注释掉。
public ShortLanguage lang;
// 3字节的未知数据已注释掉。
public fixed byte unk9[3];
}
[Serializable]
public unsafe struct PartyInfo
{

View File

@ -0,0 +1,23 @@
using System;
using PSO2SERVER.Models;
using PSO2SERVER.Protocol.Packets;
namespace PSO2SERVER.Protocol.Handlers
{
[PacketHandlerAttr(0x07, 0x37)]
public class _07_37_UNK : PacketHandler
{
public uint unk { get; set; } = 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);
var reader = new PacketReader(data, position, size);
unk = reader.ReadUInt32();
Logger.WriteObj(unk);
}
}
}

View File

@ -38,7 +38,7 @@ namespace PSO2SERVER.Protocol.Handlers
{
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)
if (character == null)
{
Logger.WriteError("数据库中未找到 {0} 角色ID {1} ({2})"
, context._account.Username

View File

@ -94,7 +94,7 @@ namespace PSO2SERVER.Protocol.Handlers
using (var db = new ServerEf())
{
// Check if any characters exist for this player
var existingCharacters = db.Characters.Where(c => c.Account.AccountId == context._account.AccountId).ToList();
var existingCharacters = db.Characters.Where(c => c.AccountID == context._account.AccountId).ToList();
if (existingCharacters.Count > 0)
{
// Increment ID if characters already exist

View File

@ -24,7 +24,7 @@ namespace PSO2SERVER.Protocol.Handlers
{
if (client.Character.CharacterID == id)
{
var infoPacket = new GuildInfoPacket(context.Character);
var infoPacket = new GuildInfoPacket(context);
context.SendPacket(infoPacket);
Logger.Write("[NFO] Sent guild info to " + client.Character.CharacterID);
break;

View File

@ -1,4 +1,5 @@
using Newtonsoft.Json;
using MySqlX.XDevAPI;
using Newtonsoft.Json;
using PSO2SERVER.Database;
using PSO2SERVER.Models;
using PSO2SERVER.Zone;
@ -58,22 +59,26 @@ namespace PSO2SERVER.Protocol.Packets
//#[SeekAfter(0x60)]
public byte[] Unk12 { get; set; } = new byte[0x40];
public readonly Client _client;
public CharacterSpawnPacket()
{
}
public CharacterSpawnPacket(Character character, PSOLocation locatiion)
public CharacterSpawnPacket(Client c, PSOLocation locatiion)
{
ObjHeader = new ObjectHeader((uint)character.Account.AccountId, ObjectType.Player);
_client = c;
ObjHeader = new ObjectHeader((uint)c._account.AccountId, ObjectType.Player);
ObjPosition = locatiion;
Character = character;
Character = c.Character;
}
public CharacterSpawnPacket(Character character, PSOLocation locatiion, bool isme, bool isgm)
public CharacterSpawnPacket(Client c, PSOLocation locatiion, bool isme, bool isgm)
{
ObjHeader = new ObjectHeader((uint)character.Account.AccountId, ObjectType.Player);
_client = c;
ObjHeader = new ObjectHeader((uint)c._account.AccountId, ObjectType.Player);
ObjPosition = locatiion;
Character = character;
Character = c.Character;
SpawnType = isme ? CharacterSpawnType.Myself : CharacterSpawnType.Other;
GmFlag = isgm ? (uint)1 : 0;
}
@ -102,7 +107,7 @@ namespace PSO2SERVER.Protocol.Packets
pkt.Write(Character.BuildCharacterByteArray());
pkt.Write(Unk11);
pkt.Write(GmFlag);
pkt.WriteFixedLengthUtf16(Character.Account.Nickname, 0x10);
pkt.WriteFixedLengthUtf16(_client._account.Nickname, 0x10);
pkt.BaseStream.Seek(0x60, SeekOrigin.Current);
pkt.Write(Unk12);

View File

@ -1,75 +1,12 @@
using PSO2SERVER.Database;
using PSO2SERVER.Models;
using static PSO2SERVER.Models.CharacterStruct;
using static PSO2SERVER.Party.Party;
using PSO2SERVER.Party;
using System.Collections.Generic;
namespace PSO2SERVER.Protocol.Packets
{
class PartyInitPacket : Packet
public class PartyInitPacket : Packet
{
public unsafe struct PartyEntry
{
/// 玩家对象的头部信息包含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;
/// 队伍队长实例.
@ -81,37 +18,39 @@ namespace PSO2SERVER.Protocol.Packets
/// <summary>
/// 未知
/// </summary>
private string unk2 = "";
private string unk2 = string.Empty;
private Character[] players;
public PartyInitPacket(Character[] players)
private List<Client> Clients = new List<Client>();
public PartyInitPacket(List<Client> clients)
{
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;
this.Clients = clients;
party_object = new ObjectHeader((uint)clients[0]._account.AccountId, ObjectType.Party);
leader = new ObjectHeader((uint)clients[0]._account.AccountId, ObjectType.Player);
people_amount = (uint)Clients.Count;
// 队伍结构数据
for (int i = 0; i < players.Length; i++)
for (int i = 0; i < Clients.Count; i++)
{
entries[i].id = new ObjectHeader((uint)players[i].Account.AccountId, ObjectType.Player); // player of player
entries[i].nickname = players[i].Account.Nickname;
entries[i].char_name = players[i].Name;
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].id = new ObjectHeader((uint)Clients[i]._account.AccountId, ObjectType.Player); // player of player
entries[i].nickname = Clients[i]._account.Nickname;
var character = Clients[i].Character;
entries[i].char_name = character.Name;
entries[i].level = (byte)character.Jobs.entries.hunter.level;
entries[i].sublevel = (byte)character.Jobs.entries.hunter.level2;
entries[i].mainClass = character.Jobs.mainClass;
entries[i].subClass = character.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;
entries[i].unk6 = character.Name;
}
}
public override byte[] Build()
{
if (players.Length < 1)
if (Clients.Count < 1)
return new byte[0];
// xor: 0xD863, sub: 0xA9
@ -145,59 +84,6 @@ namespace PSO2SERVER.Protocol.Packets
}
pkt.WriteAscii(unk2, 0xD863, 0xA9);
//// 队伍结构数据
//for(int i = 0; i < players.Length; i++)
//{
// PartyEntry pe = new PartyEntry();
// pkt.WriteStruct(new ObjectHeader((uint)players[i].Accounts.AccountId, ObjectType.Player)); // player of player
// pkt.WriteUtf16(players[i].name, 0xD863, 0xA9);
// pkt.WriteUtf16(players[i].Accounts.Nickname, 0xD863, 0xA9);
// pkt.Write((byte)players[i].Jobs.entries.hunter.level); // Active class level
// pkt.Write((byte)players[i].Jobs.entries.hunter.level2); // idk
// pkt.Write((byte)players[i].Jobs.mainClass); // idk
// pkt.Write((byte)players[i].Jobs.subClass); // idk
// pkt.Write((byte)i); // idk
// pkt.WriteBytes(0, 7);
// pkt.Write((uint)0); // idk
// ///hp
// pkt.Write((uint)0);
// pkt.Write((uint)0);
// pkt.Write((uint)0);
// /// 玩家所在的地图ID。
// pkt.Write(pe.mapid);
// pkt.Write(pe.unk3);
// pkt.WriteBytes(0, 12);
// pkt.Write((uint)0);
// pkt.Write((uint)0);
// pkt.Write((uint)0);
//}
//for(int i = 0; i < 4 - players.Length; i++) // Empty entries
//{
// pkt.WriteStruct(new ObjectHeader(0, 0)); // player of player
// pkt.WriteMagic(0, 0xD863, 0xA9);
// pkt.WriteMagic(0, 0xD863, 0xA9);
// pkt.Write((byte)0); // Active class level
// pkt.Write((byte)0); // idk
// pkt.Write((byte)0); // idk
// pkt.Write((byte)0); // idk
// pkt.Write((byte)0); // idk
// pkt.WriteBytes(0, 7);
// pkt.Write((uint)0); // idk
// ///hp
// pkt.Write((uint)0);
// pkt.Write((uint)0);
// pkt.Write((uint)0);
// /// 玩家所在的地图ID。
// pkt.Write((ushort)0);
// pkt.Write((ushort)0);
// pkt.WriteBytes(0, 12);
// pkt.Write((uint)0);
// pkt.Write((uint)0);
// pkt.Write((uint)0);
//}
return pkt.ToArray();
}

View File

@ -63,7 +63,7 @@ namespace PSO2SERVER.Protocol.Packets
using (var db = new ServerEf())
{
var chars = db.Characters
.Where(w => w.Account.AccountId == AccountId)
.Where(w => w.AccountID == AccountId)
.OrderBy(o => o.CharacterID) // TODO: 按照最后游玩的角色排序
.Select(s => s);

View File

@ -5,11 +5,11 @@ namespace PSO2SERVER.Protocol.Packets
{
internal class GuildInfoPacket : Packet
{
private readonly Character _character;
private readonly Client Client;
public GuildInfoPacket(Character character)
public GuildInfoPacket(Client client)
{
_character = character;
Client = client;
}
#region implemented abstract members of Packet
@ -25,7 +25,7 @@ namespace PSO2SERVER.Protocol.Packets
writer.Write((byte) 0);
// Character ID
writer.Write((uint) _character.CharacterID);
writer.Write((uint)Client.Character.CharacterID);
// Padding?
for (var i = 0; i < 4; i++)
@ -33,14 +33,14 @@ namespace PSO2SERVER.Protocol.Packets
// Nickname
// TODO: The above and below may be switched around, need more data
writer.WriteFixedLengthUtf16(_character.Account.Nickname, 16);
writer.WriteFixedLengthUtf16(Client._account.Nickname, 16);
// Padding?
for (var i = 0; i < 36; i++)
writer.Write((byte) 0);
// Accounts name
writer.WriteFixedLengthUtf16(_character.Name, 16);
writer.WriteFixedLengthUtf16(Client.Character.Name, 16);
// Unknown?
for (var i = 0; i < 24; i++)

View File

@ -8,11 +8,10 @@ namespace PSO2SERVER.Protocol.Packets
{
public class MissionPassInfoPacket : Packet
{
public FixedList<uint> unk { get; set; } = new FixedList<uint>(0x2F);
public FixedList<uint> unk { get; set; } = new FixedList<uint>(0x2F, 0);
public MissionPassInfoPacket()
{
unk = new FixedList<uint>(0x2F);
}
#region implemented abstract members of Packet

View File

@ -341,6 +341,7 @@
<Compile Include="Object\ObjectManager.cs" />
<Compile Include="Protocol\Handlers\04-ObjectHandler\04-75-ActionEnd.cs" />
<Compile Include="Protocol\Handlers\06-PlayerStatusHandler\06-01-DealDamage.cs" />
<Compile Include="Protocol\Handlers\07-ChatHandler\07-37-UNK.cs" />
<Compile Include="Protocol\Handlers\0B-QuestHandler\0B-20-AcceptQuest.cs" />
<Compile Include="Protocol\Handlers\0F-ItemHandler\0F-01-ItemPickupRequest.cs" />
<Compile Include="Protocol\Handlers\0F-ItemHandler\0F-1C-GetItemDescription.cs" />

View File

@ -133,8 +133,10 @@ namespace PSO2SERVER.Zone
// 设置客户端的账户ID
c.SendPacket(new SetAccountIDPacket(c._account.AccountId));
Logger.WriteObj(c.Character);
// Spawn Character
c.SendPacket(new CharacterSpawnPacket(c.Character, location, true, true));
c.SendPacket(new CharacterSpawnPacket(c, location, true, true));
c.CurrentLocation = location;
c.CurrentMap = this;
@ -151,11 +153,11 @@ namespace PSO2SERVER.Zone
}
// Spawn for others, Spawn others for me
CharacterSpawnPacket spawnMe = new CharacterSpawnPacket(c.Character, location, false, true);
CharacterSpawnPacket spawnMe = new CharacterSpawnPacket(c, location, false, true);
foreach (Client other in Clients)
{
other.SendPacket(spawnMe);
c.SendPacket(new CharacterSpawnPacket(other.Character, other.CurrentLocation, false, true));
c.SendPacket(new CharacterSpawnPacket(other, other.CurrentLocation, false, true));
}
Clients.Add(c);