Meh...so with the release of [Only registered and activated users can see links. Click Here To Register...] I can pretty much release my packet structure used for the character parsing.
As a little extra I will add the most complete structure for 0x3015 / 0x3019 you may find for free.
SERVER_AGENT_CHARACTER_DATA (0x3013):
SERVER_AGENT_ENTITY_SPAWN (0x3015, 0x3019):
SOME TYPOS MAY HAVE SLIPPED IN WHILE WRITING MY CODE INTO PSEUDO. YOU SHOULD DOUBLE CHECK A FEW THINGS BUT THE GENERAL STRUCTURE IS CORRECT FOR THE MOST PART, SORRY. ¯\_(ツ)_/¯
As a little extra I will add the most complete structure for 0x3015 / 0x3019 you may find for free.
SERVER_AGENT_CHARACTER_DATA (0x3013):
Code:
4 uint ServerTime //SROTimeStamp
4 uint RefObjID
1 byte Scale
1 byte CurLevel
1 byte MaxLevel
8 ulong ExpOffset
4 uint SExpOffset
8 ulong RemainGold
4 uint RemainSkillPoint
2 ushort RemainStatPoint
1 byte RemainHwanCount
4 uint GatheredExpPoint
4 uint HP
4 uint MP
1 byte AutoInverstExp
1 byte DailyPK
2 ushort TotalPK
4 uint PKPenaltyPoint
1 byte HwanLevel
1 byte FreePVP //0 = None, 1 = Red, 2 = Gray, 3 = Blue, 4 = White, 5 = Gold
//Inventory
1 byte Inventory.Size
1 byte Inventory.ItemCount
for (int i = 0; i < Inventory.ItemCount; i++)
{
1 byte item.Slot
4 uint item.RentType
if(item.RentType == 1)
{
2 ushort item.RentInfo.CanDelete
4 uint item.RentInfo.PeriodBeginTime
4 uint item.RentInfo.PeriodEndTime
}
else if(item.RentType == 2)
{
2 ushort item.RentInfo.CanDelete
2 ushort item.RentInfo.CanRecharge
4 uint item.RentInfo.MeterRateTime
}
else if(item.RentType == 3)
{
2 ushort item.RentInfo.CanDelete
2 ushort item.RentInfo.CanRecharge
4 uint item.RentInfo.PeriodBeginTime
4 uint item.RentInfo.PeriodEndTime
4 uint item.RentInfo.PackingTime
}
4 uint item.RefItemID
if(item.TypeID1 == 3)
{
//ITEM_
if(item.TypeID2 == 1)
{
//ITEM_CH
//ITEM_EU
//AVATAR_
1 byte item.OptLevel
8 ulong item.Variance
4 uint item.Data //Durability
1 byte item.MagParamNum
for(int paramIndex = 0; paramIndex < item.MagParamNum; paramIndex++)
{
4 uint magParam.Type
4 uint magParam.Value
}
1 byte bindingOptionType //1 = Socket
1 byte bindingOptionCount
for (int bindingOptionIndex = 0; bindingOptionIndex < bindingOptionCount; bindingOptionIndex++)
{
1 byte bindingOption.Slot
4 uint bindingOption.ID
4 uint bindingOption.nParam1
}
1 byte bindingOptionType //2 = Advanced elixir
1 byte bindingOptionCount
for (int bindingOptionIndex = 0; bindingOptionIndex < bindingOptionCount; bindingOptionIndex++)
{
1 byte bindingOption.Slot
4 uint bindingOption.ID
4 uint bindingOption.OptValue
}
}
else if(item.TypeID2 == 2)
{
if(item.TypeID3 == 1)
{
//ITEM_COS_P
1 byte State
4 uint RefObjID
2 ushort Name.Length
* string Name
if(item.TypeID4 == 2)
{
//ITEM_COS_P (Ability)
4 uint SecondsToRentEndTime
}
1 byte unkByte0
}
else if(item.TypeID3 == 2)
{
//ITEM_ETC_TRANS_MONSTER
4 uint RefObjID
}
else if(item.TypeID3 == 3)
{
//MAGIC_CUBE
4 uint Quantity //Do not confuse with StackCount, this indicates the amount of elixirs in the cube
}
}
else if(item.TypeID2 == 3)
{
//ITEM_ETC
2 ushort item.StackCount
if(item.TypeID3 == 11)
{
if(item.TypeID4 == 1 || item.TypeID4 == 2)
{
//MAGICSTONE, ATTRSTONE
1 byte AttributeAssimilationProbability
}
}
else if(item.TypeID3 == 14 || item.TypeID4 == 2)
{
//ITEM_MALL_GACHA_CARD_WIN
//ITEM_MALL_GACHA_CARD_LOSE
1 byte item.MagParamCount
for (int paramIndex = 0; paramIndex < MagParamNum; paramIndex++)
{
4 uint magParam.Type
4 uint magParam.Value
}
}
}
}
}
//AvatarInventory
1 byte AvatarInventory.Size
1 byte AvatarInventory.ItemCount
for (int i = 0; i < Inventory.ItemCount; i++)
{
1 byte item.Slot
4 uint item.RentType
if(item.RentType == 1)
{
2 ushort item.RentInfo.CanDelete
4 uint item.RentInfo.PeriodBeginTime
4 uint item.RentInfo.PeriodEndTime
}
else if(item.RentType == 2)
{
2 ushort item.RentInfo.CanDelete
2 ushort item.RentInfo.CanRecharge
4 uint item.RentInfo.MeterRateTime
}
else if(item.RentType == 3)
{
2 ushort item.RentInfo.CanDelete
2 ushort item.RentInfo.CanRecharge
4 uint item.RentInfo.PeriodBeginTime
4 uint item.RentInfo.PeriodEndTime
4 uint item.RentInfo.PackingTime
}
4 uint item.RefItemID
if(item.TypeID1 == 3)
{
//ITEM_
if(item.TypeID2 == 1) //TODO: Narrow filters for AvatarInventory
{
//ITEM_CH
//ITEM_EU
//AVATAR_
1 byte item.OptLevel
8 ulong item.Variance
4 uint item.Data //Durability
1 byte item.MagParamNum
for(int paramIndex = 0; paramIndex < item.MagParamNum; paramIndex++)
{
4 uint magParam.Type
4 uint magParam.Value
}
1 byte bindingOptionType //1 = Socket
1 byte bindingOptionCount
for (int bindingOptionIndex = 0; bindingOptionIndex < bindingOptionCount; bindingOptionIndex++)
{
1 byte bindingOption.Slot
4 uint bindingOption.ID
4 uint bindingOption.nParam1
}
1 byte bindingOptionType //2 = Advanced elixir
1 byte bindingOptionCount
for (int bindingOptionIndex = 0; bindingOptionIndex < bindingOptionCount; bindingOptionIndex++)
{
1 byte bindingOption.Slot
4 uint bindingOption.ID
4 uint bindingOption.OptValue
}
}
}
}
1 byte unkByte1 //not a counter
//Masteries
1 byte nextMastery
while(nextMastery == 1)
{
4 uint mastery.ID
1 byte mastery.Level
1 byte nextMastery
}
1 byte unkByte2 //not a counter
//Skills
1 byte nextSkill
while(nextSkill == 1)
{
4 uint skill.ID
1 byte skill.Enabled
1 byte nextSkill
}
//Quests
2 ushort CompletedQuestCount
* uint[] CompletedQuests
1 byte ActiveQuestCount
for(int activeQuestIndex = 0; activeQuestIndex < ActiveQuestCount; activeQuestIndex++)
{
4 uint quest.RefQuestID
1 byte quest.AchivementCount
1 byte quest.RequiresAutoShareParty
1 byte quest.Type
if(quest.Type == 28)
{
4 uint remainingTime
}
1 byte quest.Status
if(quest.Type != 8)
{
1 byte quest.ObjectiveCount
for(int objectiveIndex = 0; objectiveIndex < quest.ObjectiveCount; objectiveIndex++)
{
1 byte objective.ID
1 byte objective.Status //0 = Done, 1 = On
2 ushort objective.Name.Length
* string objective.Name
1 byte objective.TaskCount
for(int taskIndex = 0; taskIndex < objective.TaskCount; taskIndex++)
{
4 uint task.Value
}
}
}
if(quest.Type == 88)
{
1 byte RefObjCount
for(int refObjIndex = 0; refObjIndex < RefObjCount; refObjIndex++)
{
4 uint RefObjID //NPCs
}
}
}
1 byte unkByte3 //Structure changes!!!
//CollectionBook
4 uint CollectionBookStartedThemeCount
for(int i = 0; i < StartedCollectionCount)
{
4 uint theme.Index
4 uint theme.StartedDateTime //SROTimeStamp
4 uint theme.Pages
}
4 uint UniqueID
//Position
2 ushort Position.RegionID
4 float Position.X
4 float Position.Y
4 float Position.Z
2 ushort Position.Angle
//Movement
1 byte Movement.HasDestination
1 byte Movement.Type
if(Movement.HasDestination)
{
2 ushort Movement.DestinationRegion
if(Position.RegionID < short.MaxValue)
{
//World
2 ushort Movement.DestinationOffsetX
2 ushort Movement.DestinationOffsetY
2 ushort Movement.DestinationOffsetZ
}
else
{
//Dungeon
4 uint Movement.DestinationOffsetX
4 uint Movement.DestinationOffsetY
4 uint Movement.DestinationOffsetZ
}
}
else
{
1 byte Movement.Source //0 = Spinning, 1 = Sky-/Key-walking
2 ushort Movement.Angle //Represents the new angle, character is looking at
}
//State
1 byte State.LifeState //1 = Alive, 2 = Dead
1 byte State.unkByte0
1 byte State.MotionState //0 = None, 2 = Walking, 3 = Running, 4 = Sitting
1 byte State.Status //0 = None, 1 = Hwan, 2 = Untouchable, 3 = GameMasterInvincible, 5 = GameMasterInvisible, 5 = ?, 6 = Stealth, 7 = Invisible
4 float State.WalkSpeed
4 float State.RunSpeed
4 float State.HwanSpeed
1 byte State.BuffCount
for (int i = 0; i < State.BuffCount; i++)
{
4 uint Buff.RefSkillID
4 uint Buff.Duration
if(skill.Params.Contains(1701213281))
{
//1701213281 -> atfe -> "auto transfer effect" like Recovery Division
1 bool IsCreator
}
}
2 ushort Name.Length
* string Name
2 ushort JobName.Length
* string JobName
1 byte JobType
1 byte JobLevel
4 uint JobExp
4 uint JobContribution
4 uint JobReward
1 byte PVPState //0 = White, 1 = Purple, 2 = Red
1 byte TransportFlag
1 byte InCombat
if(TransportFlag == 1)
{
4 uint Transport.UniqueID
}
1 byte PVPFlag //0 = Red Side, 1 = Blue Side, 0xFF = None
8 ulong GuideFlag
4 uint JID
1 byte GMFlag
1 byte ActivationFlag //ConfigType:0 --> (0 = Not activated, 7 = activated)
1 byte Hotkeys.Count //ConfigType:1
for(int i = 0; i < hotkeyCount; i++)
{
1 byte hotkey.SlotSeq
1 byte hotkey.SlotContentType
4 uint hotkey.SlotData
}
2 ushort AutoHPConfig //ConfigType:11
2 ushort AutoMPConfig //ConfigType:12
2 ushort AutoUniversalConfig //ConfigType:13
1 byte AutoPotionDelay //ConfigType:14
1 byte blockedWhisperCount
for(int i = 0; i < blockedWhisperCount; i++)
{
2 ushort Target.Length
* string Target
}
4 uint unkUShort0 //Structure changes!!!
1 byte unkByte4 //Structure changes!!!
//EOP
Code:
4 uint RefObjID
if(obj.TypeID1 == 1)
{
//BIONIC:
// CHARACTER
// NPC
// NPC_FORTRESS_STRUCT
// NPC_MOB
// NPC_COS
// NPC_FORTRESS_COS
if(obj.TypeID2 == 1)
{
//CHARACTER
1 byte Scale
1 byte HwanLevel
1 byte PVPCape //0 = None, 1 = Red, 2 = Gray, 3 = Blue, 4 = White, 5 = Orange
1 byte AutoInverstExp //1 = Beginner Icon, 2 = Helpful, 3 = Beginner & Helpful
//Inventory
1 byte Inventory.Size
1 byte Inventory.ItemCount
for (int i = 0; i < Inventory.ItemCount; i++)
{
4 uint item.RefItemID
if(item.TypeID1 == 3 && item.TypeID2 == 1)
{
1 byte item.OptLevel
}
}
//AvatarInventory
1 byte Inventory.Size
1 byte Inventory.ItemCount
for (int i = 0; i < Inventory.ItemCount; i++)
{
4 uint item.RefItemID
if(item.TypeID1 == 3 && item.TypeID2 == 1)
{
1 byte item.OptLevel
}
}
//Mask
1 byte HasMask
if(HasMask)
{
4 uint mask.RefObjID
if (maskObject.TypeID1 == obj.TypeID1 &&
maskObject.TypeID2 == obj.TypeID2)
{
//Duplicate
1 byte Mask.Scale
1 byte Mask.ItemCount
for(int i = 0; i < Mask.ItemCount; i++)
{
4 uint item.RefItemID
}
}
}
}
else if(obj.TypeID2 == 2 && obj.TypeID3 == 5)
{
//NPC_FORTRESS_STRUCT
4 uint HP
4 uint RefEventStructID
2 ushort State
}
4 uint UniqueID
//Position
2 ushort Position.RegionID
4 float Position.X
4 float Position.Y
4 float Position.Z
2 ushort Position.Angle
//Movement
1 byte Movement.HasDestination
1 byte Movement.Type
if(Movement.HasDestination)
{
//MD (Mouse destination)
2 ushort Movement.DestinationRegion
if(Position.RegionID < short.MaxValue)
{
//World
2 ushort Movement.DestinationOffsetX
2 ushort Movement.DestinationOffsetY
2 ushort Movement.DestinationOffsetZ
}
else
{
//Dungeon
4 uint Movement.DestinationOffsetX
4 uint Movement.DestinationOffsetY
4 uint Movement.DestinationOffsetZ
}
}
else
{
1 byte Movement.Source //0 = Spinning, 1 = Sky-/Key-walking
2 ushort Movement.Angle //Represents the new angle, character is looking at
//FC_GO_FORWARD
//
}
//State
1 byte State.LifeState //1 = Alive, 2 = Dead
1 byte State.unkByte0
1 byte State.MotionState //0 = None, 2 = Walking, 3 = Running, 4 = Sitting
1 byte State.Status //0 = None, 1 = Hwan, 2 = Untouchable, 3 = GameMasterInvincible, 5 = GameMasterInvisible, 5 = ?, 6 = Stealth, 7 = Invisible
4 float State.WalkSpeed
4 float State.RunSpeed
4 float State.HwanSpeed
1 byte State.BuffCount
for (int i = 0; i < State.BuffCount; i++)
{
4 uint Buff.RefSkillID
4 uint Buff.Duration
if(skill.Params.Contains(1701213281))
{
//1701213281 -> atfe -> "auto transfer effect" like Recovery Division
1 bool IsCreator
}
}
if(obj.TypeID2 == 1)
{
//CHARACTER
2 ushort Name.Length
2 string Name
1 byte JobType //0 = None, 1 = Trader, 2 = Tief, 3 = Hunter
1 byte JobLevel
1 byte PVPState //0 = White (Neutral), 1 = Purple (Assaulter), 2 = Red
1 byte TransportFlag
1 byte InCombat
if(TransportFlag)
{
4 uint Transport.UniqueID
}
1 byte ScrollMode //0 = None, 1 = Return Scroll, 2 = Bandit Return Scroll
1 byte InteractMode //0 = None 2 = P2P, 4 = P2N_TALK, 6 = OPNMKT_DEAL
1 byte unkByte4
//Guild
2 ushort Guild.Name.Length
* string Guild.Name
if(Inventory.ContainsJobEquipment == false)
{
4 uint Guild.ID
2 ushort GuildMember.Nickname.Length
* string GuildMember.Nickname
4 uint Guild.LastCrestRev
4 uint Union.ID
4 uint Union.LastCrestRev
1 byte Guild.IsFriendly //0 = Hostile, 1 = Friendly
1 byte GuildMember.SiegeAuthority //See SiegeAuthority.cs
}
if(InteractMode == 4) //P2N_TALK
{
2 ushort Stall.Name.Length
* string Stall.Name
4 uint Stall.RefItemID //Decoration
}
1 byte EquipmentCooldown
1 byte PKFlag
}
else if(obj.TypeID2 == 2)
{
//NPC
1 byte TalkFlag
if(TalkFlag == 2)
{
1 byte TalkOptionCount
* byte[] TalkOptions
}
if(obj.TypeID3 == 2)
{
//NPC_MOB
1 byte Rarity
if(obj.TypeID4 == 2 || obj.TypeID4 == 3)
{
1 byte Appearance //Randomized by server.
}
}
else if(obj.TypeID3 == 3)
{
//NPC_COS
if(obj.TypeID4 == 3 || obj.TypeID4 == 4)
{
//NPC_COS_P (Growth)
//NPC_COS_P (Ability)
2 ushort Name.Length
2 string Name
}
if(obj.TypeID4 == 5)
{
//NPC_COS_GUILD
2 ushort GuildName.Length
2 string GuildName
}
else
{
2 ushort Owner.Name.Length
2 string Owner.Name
}
if(obj.TypeID4 == 2 || //NPC_COS_T
obj.TypeID4 == 3 || //NPC_COS_P (Growth)
obj.TypeID4 == 4 || //NPC_COS_P (Ability)
obj.TypeID4 == 5) //NPC_COS_GUILD
{
1 byte JobType
if(obj.TypeID4 != 4) //NO NPC_COS_P (Ability)
{
1 byte MurderFlag //0 = White, 1 = Purple, 2 = Red
}
if(obj.TypeID4 == 5)
{
//NPC_COS_GUILD
4 uint Owner.RefObjID
}
}
4 uint Owner.UniqueID
}
else if (obj.TypeID3 == 4)
{
//NPC_FORTRESS_COS
4 uint Guild.ID
2 ushort Guild.Name.Length
* string Guild.Name
}
}
}
else if(obj.TypeID1 == 3)
{
//ITEM:
// ITEM_EQUIP
// ITEM_ETC
// ITEM_ETC_MONEY_GOLD
// ITEM_ETC_TRADE
// ITEM_ETC_QUEST
if(obj.TypeID2 == 1)
{
//ITEM_EQUIP
1 byte OptLevel
}
else if(obj.TypeID2 == 3)
{
//ITEM_ETC
if(obj.TypeID3 == 5 && obj.TypeID4 == 0)
{
//ITEM_ETC_MONEY_GOLD
4 uint Amount
}
else if(obj.TypeID3 == 8 || obj.TypeID3 == 9)
{
//ITEM_ETC_TRADE
//ITEM_ETC_QUEST
4 ushort Owner.Name.Length
* string Owner.Name
}
}
4 uint UniqueID
2 ushort Position.RegionID
4 float Position.X
4 float Position.Y
4 float Position.Z
2 ushort Position.Angle
1 bool hasOwner
if(hasOwner)
{
4 uint Owner.JID
}
1 byte Rarity
}
else if(obj.TypeID1 == 4)
{
//PORTALS:
// STORE
// INS_TELEPORTER
4 uint UniqueID
2 ushort Position.RegionID
4 float Position.X
4 float Position.Y
4 float Position.Z
2 ushort Position.Angle
1 byte unkByte0
1 byte unkByte1
1 byte unkByte2
1 byte unkByte3
if(unkByte3 == 1)
{
//Regular
4 uint unkUInt0
4 uint unkUInt1
}
else if(unkByte3 == 6)
{
//Dimension Hole
2 ushort Owner.Name.Length
* string Owner.Name
4 uint Owner.UniqueID
}
if(unkByte1 == 1)
{
//STORE_EVENTZONE_DEFAULT
4 uint unkUInt3
1 byte unkByte5
}
}
else if(RefObjID == uint.MaxValue)
{
//EVENT_ZONE (Traps, Buffzones, ...)
2 ushort unkUShort0
4 uint RefSkillID
4 uint UniqueID
2 ushort Position.RegionID
4 float Position.X
4 float Position.Y
4 float Position.Z
2 ushort Position.Angle
}
if(packet.Opcode == 0x3015)
{
if(obj.TypeID1 == 0x01 || obj.TypeID1 == 0x4)
{
//BIONIC and STORE
1 byte unkByte6
}
else if(obj.TypeID1 == 0x03)
{
1 byte DropSource
4 uint Dropper.UniqueID
}
}
//EOP