Making a packetlogger
In this page you will learn how to make a packetlogger using the Python API and how to perform actions when a certain packet is sent or received.
This tutorial assumes you are already familiar with the basic workflow of the Python API. If this is not the case we recommend you to read the previous page to get the basic concepts and then return back to this page.
The packet manager service
The bot exposes a service to interact with packets. The service request and responses are specified in the API Reference.
As we can see, the services offers 5 different requests:
subscribe
: Notifies the bot that you want to start reding packets so that it can allocate the necessary resources for you.unsubscribe
: Notifies the bot that you don't want to read packets so that it can free the resources that were reserved previously when you subscribed.getPendingSendPackets
: Returns a stream to read the packets that are sent from the game client to the server.getPendingRecvPackets
: Returns a stream to read the packets that the game client receives from the game server.send
: Sends a packet to the game serverrecv
: Receives a packet in the game client (this simulates receiving a packet from the game server so the information altered will be fake).
When using the subscribe
, unsubscribe
, getPendingSendPackets
and getPendingRecvPackets
functions we need to pass an unique id
as parameter. This unique identifier string will allow the bot to identify your client and send you the apropiate responses. The identifier must be the same across different calls to this functions. Let's say you subscribe with an identifier of Bobby
, now if you want to use any of the other functions like unsubscribe
or getPendingSendPackets
you must use the same identifier.
When you subscribe to be able to read packets the bot, internally, creates 2 queues of packets, one for sent and one for received, for your specified id
. It is very important that you take this into consideration, because of this you must always follow this rules:
Call
unsubscribe
when you don't need to read packets to prevent the bot from pushing packets into the queues that were allocated for you.Constantly call
getPendingSendPackets
andgetPendingRecvPackets
to let the bot remove the packets that you have processed.
If this rules are not followed it is possible that the bot ends up pushing packets into the queues indefinitely leading into a memory exhaustion and crashing your game or even worse, your pc.
Packetlogger
First we are going to import the necessary classes and modules and create the client instance as in the previous tutorial:
from phoenixapi.api import PhoenixApi
from phoenixapi.finder import create_api_from_name
CHAR_NAME = "Insert your character name here"
client: PhoenixApi = create_api_from_name(CHAR_NAME)
Next, we subscribe using the packet manager client to let the bot know that we want to read packets:
client.packet_manager.subscribe()
Now, we start an endless loop to read the packets and print them to the console:
try:
while True:
pending_send = client.packet_manager.get_pending_send_packets()
for packet in pending_send:
print(f"[SEND]: {packet}")
pending_recv = client.packet_manager.get_pending_recv_packets()
for packet in pending_recv:
print(f"[RECV]: {packet}")
except KeyboardInterrupt:
print("Interrupted by user.")
finally:
client.packet_manager.unsubscribe()
Note here that we are following the rules mentioned in The packet manager service. When the script ends we are unsubscribing to let the bot free the resources that were allocated for us and we are also constantly querying the pending packets and processing them to avoid any memory exhaustion errors.
Finally, wen can run the script:
python packetlogger.py
And we get the following output:
[SEND]: pulse 5160 0
[RECV]: in 3 24 2644 32 141 2 100 100 0 0 0 -1 0 0 -1 - 0 -1 0 0 0 0 0 0 0 0 0 0 0 0 0
[SEND]: walk 70 142 0 12
[SEND]: walk 67 143 0 12
[SEND]: walk 66 144 0 12
Improving the packetlogger
Now that we have a basic packetlogger up and running we can improve it a little bit. First we are going to add some filters so that we don't get spammed with packets like mv
that is received everytime an entity moves. We are also going to add a small example of how to do something when a certain packet is read.
We start by creating 2 tuples that will contain the packets that we want to filter out of our packetlogger:
send_filter = ("ncif", "ptctl")
recv_filter = ("mv", "eff", "pst", "st", "cond")
Now, when we are reading the packets we can skip those that match the packet names of the filters:
# Inside the while True loop
pending_send = client.packet_manager.get_pending_send_packets()
for packet in pending_send:
if packet.data.startswith(send_filter):
continue
print(f"[SEND]: {packet.data}")
pending_recv = client.packet_manager.get_pending_recv_packets()
for packet in pending_recv:
if packet.data.startswith(recv_filter):
continue
print(f"[RECV]: {packet.data}")
Next, we are going to create two functions to handle send and recv packets respectively and call them when we are parsing the packets:
def handle_send(client: PhoenixApi, packet: str):
pass
def handle_recv(client: PhoenixApi, packet: str):
pass
# [...]
# Inside the while True loop
pending_send = client.packet_manager.get_pending_send_packets()
for packet in pending_send:
handle_send(client, packet)
if packet.data.startswith(send_filter):
continue
print(f"[SEND]: {packet}")
pending_recv = client.packet_manager.get_pending_recv_packets()
for packet in pending_recv:
handle_recv(client, packet)
if packet.data.startswith(recv_filter):
continue
print(f"[RECV]: {packet}")
Finally, in the handle_send
function we are going to check if the packet is a say
packet, this is the packet you send when you write in the general chat, if the text of the message is ping
we are going to receive a fake packet with the message pong
:
def handle_send(client: api.Phoenix, packet: str):
if packet == "say ping":
client.packet_manager.recv("spk 1 1 5 Hatz pong")
If we run the script and we write ping
in the game's chat we should see something like this:
[Hatz]: ping
(whisper)[Hatz]: pong
You can find the full source code of this example here.
Last updated