First of all you may want to read the packethooks.txt that comes with Pol first.
It's inside the main directory of your Pol server if you unpacked all files that are
delivered inside the different cores.
I'll give only some information - mostly examples - to make the whole packet
hook thing perhaps a little easier.
What is a packet hook?
Well the easiest way to describe that is, that it overrides the pol core on
incoming packets from the client. This may be packets already handled by
the core (e.g. status bar), where you want to do that in a different way than the
core does. This could also be packets that are not even handled by the core,
e.g. party system, chat, open door macro, quest button ..., where you want
to build these systems.
Fine, but what the hell is a packet?
A packet is a combination of hexadecimal values, that forces the client or
the server to do something. For example each action you take on your client,
leads to one or more packets send to the server (walking, fighting, put something on the
ground, come near a multi ....). And vice versa.
The first value of the packet is always the identifier mostly called the command (cmd),
not always followed by the length of the packet. The length can be a fixed or a variable value.
After that all packets may be different. Some have subcommands which gave a lot
more opportunities so you may lost your way *g*
Do i always need a packet hook, when it comes to handling a packet?
Simply no. Packet only send to the client don't need a packet hook.
A small example is the weather packet. If you want to let it rain on the
client you can send the packet directly from your script.
Code: Select all
var rainpacket := "65005000";
SendPacket( who, rainpacket );
65 is the command/identifier for the weather packet.
00 is rain in this case (type of weather, could also be e.g. 02 for snow.
50 is the severity of the weather (number of particles)
00 is the last unknown byte of the weather packet. Could be temperature.
This is the easiest way to send such small packets without using the
CreatePacket(type,size) command from polsys.em
O.k. so far ,but i want my server to have this chat window. What now?
Well, first take a cup of coffee or a bottle of beer. This takes longer. But only
one beer, otherwise you perhaps loose the way
I'm using the chat packethook from tekproxy inside the SVN Pol Distro as
an example here, because everybody can take a look inside and it shows
most of the things you need to build your own packet hook.
You can even see what happens if you set 1 or 2 bytes wrong and the whole
damn system don't work cause the client only understands: "Huh?".
This is surely no offense to tekproxy, i think he hadn't the time to finish it
yet. You'll find the scripts and configurations in the SVN under
Distro/pkg/packetHooks/Chat/
The most annoying part with packet hooks is, that you'll have to restart
the server most times when you make a change inside the script.
That's because of the exported functions used.
Lets take a look at the first thing you have to build, the uopacket.cfg.
uopacket.cfg
This configuration file contains the main and - if exists - subpackets that
you wanted to be hooked. I'll take parts from the original to explain the
usage.
Code: Select all
Packet 0xB3 // Chat Text
{
Length variable
SubCommandOffset 8
SubCommandLength 1
}
Packet 0xB5 // Open Chat Window
{
Length 64
ReceiveFunction doChat:handleOpenChatWindow
}
SubPacket 0xB3 // Close Chat Window
{
SubCommandID 0x58
ReceiveFunction doChatText:handleCloseChat
}
SubPacket 0xB3
{
SubCommandID 0x65
ReceiveFunction doChatText:handleSendPrivateMessage
}
You see that two packets are hooked. 0xB5 which opens the chat window
and 0xB3 with its subpackets, which are used for the main handling of
the UO chat.
For packet 0xB5 there is a fixed length of 64 bytes given and if a player
clicks the chat button or use the chat macro the exported function
handleOpenChatWindow is called from inside the doChat.ecl.
Because 0xB3 has subpackets the first thing is to hook the main packet
and tell the server where to find the subpackets inside the main packet.
The length is of course variable cause it contains messages, names...
The SubCommandOffset 8 tells the server to search the subpacket
command at in this case the eigth byte after the command.
The SubCommandLength 1 tells the server that the subpacket command
is 1 byte long.
As a reminder, packet 0xB3 is atm wrong in the packet list, it looks like
this:
Code: Select all
BYTE[1] cmd
BYTE[2] Length
BYTE[3] Unicode Language
BYTE[2] Unknown(00 00)
BYTE[1] Subcommand/Type
Now that we defined the main packet, we can define the subpackets,
e.g. the one for sending private messages inside the chat which is
subcommand 0x65. So we define SubPacket 0xB3 with its
SubCommandID 0x65 and what exported function to use when
receiving this subcommand.
In this case handleSendPrivateMessage inside doChatText.ecl which
starts the script handleSendPrivateMessage giving the character and
packet ref in an array to that script.
This one contains nothing yet inside the distro, so i give a slightly changed
version of mine here, to continue explaining step by step.
Code: Select all
program SendPrivateMessage(params)
var character := params[1];
var packet := params[2];
params := "";
var contemp := packet.GetUnicodeString(9, (packet.GetSize() / 2) - 5);
var ctemp1 := {},ctemp2 := {}, switch := 0;
foreach nr in contemp
if((nr != 32) && (!switch))
ctemp1.append(nr);
else
if(!switch)
switch := 1;
else
ctemp2.append(nr);
endif
endif
endforeach
var from_name := GetObjProperty(character, "ChatName");
if(CChrZ(ctemp1) == from_name)
SendSysMessage(character, "Do you like monologues?");
return 0;
endif
var pmtext := "Private message from " + from_name + ": ";
var newpacket := CreatePacket(0xB3, MSGLEN_VARIABLE);
newpacket.SetString(3, character.uclang, 0);
newpacket.SetInt16(6, 0); // Null terminator
newpacket.SetInt8(8, 0x65); // Subcommand send private message
newpacket.SetUnicodeString(newpacket.GetSize(), CAscZ(pmtext), 0);
newpacket.SetInt16(newpacket.GetSize(), 0x20); // seperator
newpacket.SetUnicodeString(newpacket.GetSize(), ctemp2, 0);
newpacket.SetInt16(newpacket.GetSize(), 0); // Null terminator
var existing := CH_ListPlayers();
foreach ser in existing
if(ser != character.serial)
var member := SystemFindObjectBySerial(ser);
var memname := GetObjProperty(member, "ChatName");
if(memname == CChrZ(ctemp1))
SendChatPacket(newpacket, member, from_name);
break;
endif
endif
endforeach
return 1;
endprogram
The first part of the script (until var pmtext) extracts the info from what the client (player)
sends as packet. This contains the chat name where to send the private message and
the message itself. This is atm not so important.
Now we build the packet to send to that guy step by step:
Code: Select all
var newpacket := CreatePacket(0xB3, MSGLEN_VARIABLE);
This one creates the packet 0xB3 with a variable length.
Code: Select all
newpacket.SetString(3, character.uclang, 0);
This one adds the unicode language as string at the third byte after the command.
The zero at the end means that no null terminator is set. If you set it to 1 then
two bytes 00 00 follow the unicode language string as terminator. You'll see that
i set them in the next line by hand for a better view of the packet.
You may want to look at
http://docs.polserver.com/pol096/objref.php#Packet
for these packet handling commands.
Code: Select all
newpacket.SetInt16(6, 0); // Null terminator
Because the unicode language string is 3 bytes long starting at byte 3
after the packet command, the 2 bytes null terminator (00 00) starts at
byte 6. So .SetInt16 means it sets 2 bytes. In this case 2 bytes with zero
(00 00).
Code: Select all
newpacket.SetInt8(8, 0x65); // Subcommand send private message
Sets the 0x65 subcommand at byte 8 after the packet command. Because it only
one byte .SetInt8 is used.
Code: Select all
newpacket.SetUnicodeString(newpacket.GetSize(), CAscZ(pmtext), 0);
This is the part where the guy is mentioned who has written the message. We append it as
unicode string at the end of the until now build packet. Which is obviously position 9 after
the packet command, but .GetSize() makes it easier, you don't need to count anymore

The CAscZ() function turns the text into an array of ascii values, which is important for the
unicode stuff. The zero again means that no null terminator is set.
Code: Select all
newpacket.SetInt16(newpacket.GetSize(), 0x20); // seperator
Here its important to set a seperator between the text containing the name of the writer of
that message and the message itself. In this case 0x0020 is used because the client search
for that to seperate them again.
Code: Select all
newpacket.SetUnicodeString(newpacket.GetSize(), ctemp2, 0);
This one appends the message to the so far build packet and doesn't
set a null terminator. ctemp2 contains an array of ascii values only
for your understanding. The client will turn it back into the readable
message.
Code: Select all
newpacket.SetInt16(newpacket.GetSize(), 0); // Null terminator
Now the last two bytes of our packet, again a null terminator. Now we finished the packet and
can send it to the one the writer has choosen. This is done in the last part of the script, but
to make it easier call him choosen_one and send him the packet, which would look this way:
newpacket.SendPacket(choosen_one);
choosen_one surely must be inside the chat
I hope that short guide helps you out. Perhaps i complete it a little bit more, if i find some
spare time
