Skip to content

Instantly share code, notes, and snippets.

@ymgve
Last active October 31, 2023 05:17
Show Gist options
  • Save ymgve/0b41713a729fea7b13c267f7b1116339 to your computer and use it in GitHub Desktop.
Save ymgve/0b41713a729fea7b13c267f7b1116339 to your computer and use it in GitHub Desktop.
import io, logging, secrets, socket, sqlite3, struct, time, traceback
# TODO replace with something faster
from CryptICE import IceKey
logging.basicConfig(
format="%(asctime)s %(levelname)-8s %(message)s",
filename="trackerserver.log",
encoding="utf-8",
level=logging.DEBUG)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter("%(asctime)s %(levelname)-8s %(message)s"))
logging.getLogger().addHandler(console)
class UserManager:
def __init__(self, srv):
self.srv = srv
self.con = sqlite3.connect("trackerserver.db")
res = self.con.execute("SELECT name FROM sqlite_master")
if res.fetchone() is None:
logging.info("creating user database")
self.con.execute("CREATE TABLE IF NOT EXISTS users(email BLOB PRIMARY KEY, username BLOB, firstname BLOB, lastname BLOB)")
self.con.execute("CREATE TABLE IF NOT EXISTS friend(source INTEGER, target INTEGER)")
self.con.commit()
def get_user_by_email(self, email):
res = self.con.execute("SELECT ROWID + 0x10000, email, username, firstname, lastname FROM users WHERE email = ?", (email,))
return res.fetchone()
def get_user_by_uid(self, uid):
res = self.con.execute("SELECT ROWID + 0x10000, email, username, firstname, lastname FROM users WHERE ROWID + 0x10000 = ?", (uid,))
return res.fetchone()
def search_users(self):
res = self.con.execute("SELECT ROWID + 0x10000, username, firstname, lastname FROM users")
return res.fetchall()
def auth(self, email, username):
row = self.get_user_by_email(email)
if row is None:
logging.info("created user with email %s username %s" % (email, username))
self.con.execute("INSERT INTO users VALUES (?, ?, ?, ?)", (email, username, b"none", b"none"))
self.con.commit()
row = self.get_user_by_email(email)
else:
logging.info("found existing user with email %s" % email)
return row[0]
def update_details(self, uid, username, firstname, lastname):
self.con.execute("UPDATE users SET username = ?, firstname = ?, lastname = ? WHERE ROWID + 0x10000 = ?", (username, firstname, lastname, uid))
self.con.commit()
def request_friend(self, source, target):
res = self.con.execute("SELECT source, target FROM friend WHERE source = ? and target = ?", (source, target))
rows = res.fetchone()
if rows == None:
self.con.execute("INSERT INTO friend VALUES (?, ?)", (source, target))
self.con.commit()
return True
else:
return False
def get_friends_by_source(self, uid):
res = self.con.execute("SELECT target FROM friend WHERE source = ?", (uid,))
return [x[0] for x in res.fetchall()]
def get_friends_by_target(self, uid):
res = self.con.execute("SELECT source FROM friend WHERE target = ?", (uid,))
return [x[0] for x in res.fetchall()]
def pending_friends(self, uid):
wannabe = self.get_friends_by_target(uid)
realfriends = self.get_friends_by_source(uid)
res = []
for friendid in wannabe:
if friendid not in realfriends:
res.append(friendid)
return res
def real_friends(self, uid):
wannabe = self.get_friends_by_target(uid)
realfriends = self.get_friends_by_source(uid)
res = []
for friendid in wannabe:
if friendid in realfriends:
res.append(friendid)
return res
def di(s):
return struct.unpack("<I", s)[0]
def ei(n):
return struct.pack("<I", n)
def parse_data(data, typed=False):
bio = io.BytesIO(data)
res = {}
while True:
datatype = bio.read(1)[0]
if not datatype & 1:
break
key = b""
while True:
c = bio.read(1)
if c == b"\x00":
break
key += c
key = str(key, "utf8")
sz, = struct.unpack("<H", bio.read(2))
value = bio.read(sz)
if datatype & 4 == 0:
if value[-1] == 0:
value = value[:-1]
else:
raise Exception("non-null terminated string")
if typed:
res[key] = (datatype, value)
else:
res[key] = value
return res
def validate_msg(msg, mandatory, optional=()):
for key in mandatory:
if key not in msg:
raise Exception("missing key", key)
for key in msg:
if key != "_id" and key not in mandatory and key not in optional:
raise Exception("unexpected key", key)
class Message:
def __init__(self, client, cmdid, ack=False):
self.client = client
self.sessionid = client.sessionid
self.kv = {}
self.msg = b""
self.cmdid = cmdid
self.seqnum = client.seqnum
self.reply_to = client.r_seqnum
self.padding = b""
self.ack = ack
self.add_int("_id", cmdid)
def add_kv(self, mode, key, value):
if key in self.kv:
raise Exception("duplicate key", key)
self.kv[key] = (mode, value)
self.msg += bytes([mode])
self.msg += bytes(key, "utf8") + b"\x00"
self.msg += struct.pack("<H", len(value))
self.msg += value
def add_bin(self, key, s):
self.add_kv(5, key, s)
def add_int(self, key, n):
self.add_kv(5, key, ei(n))
def add_str(self, key, s):
self.add_kv(1, key, s + b"\x00")
def getpacket(self):
logging.debug("preparing packet with id %d clientid %d sessionid %d seqnum %d replyto %d" %
(self.cmdid, self.client.clientid, self.sessionid, self.seqnum, self.reply_to))
logging.debug("keyvalues %s" % self.kv)
data = struct.pack("<IIIIBB", self.client.clientid, self.sessionid, self.seqnum, self.reply_to, 1, 1)
data += self.msg + b"\x00" + self.padding
if not self.ack:
while len(data) % 8 != 4:
data += b"\x00"
data = b"\x04\x16" + struct.pack("<H", len(data) + 4) + data
if not self.ack:
return b"\xfe\xff\xff\xff" + ice.Encrypt(data)
else:
return data
network_key = (
# n
0xbf973e24beb372c12bea4494450afaee290987fedae8580057e4f15b93b46185b8daf2d952e24d6f9a23805819578693a846e0b8fcc43c23e1f2bf49e843aff4b8e9af6c5e2e7b9df44e29e3c1c93f166e25e42b8f9109be8ad03438845a3c1925504ecc090aabd49a0fc6783746ff4e9e090aa96f1c8009baf9162b66716059,
# e
0x11,
# d
0x4ee3ec697bb34d5e999cb2d3a3f5766210e5ce961de7334b6f7c6361f18682825b2cfa95b8b7894c124ada7ea105ec1eaeb3c5f1d17dfaa55d099a0f5fa366913b171af767fe67fb89f5393efdb69634f74cb41cb7b3501025c4e8fef1ff434307c7200f197b74044e93dbcf50dcc407cbf347b4b817383471cd1de7b5964a9d,
)
newkey = bytes.fromhex("30819d300d06092a864886f70d010101050003818b0030818702818100") + network_key[0].to_bytes(128, byteorder="big") + bytes.fromhex("020111")
ice = IceKey(1, [13, 85, 243, 211, 173, 6, 87, 71])
class Client:
def __init__(self, addr, clientid, sessionid, seqnum, r_seqnum):
self.addr = addr
self.clientid = clientid
self.sessionid = sessionid
self.seqnum = seqnum
self.expected = r_seqnum
self.r_seqnum = r_seqnum
self.ackd = 0
self.queue = {}
self.update_timeout(15)
self.init = False
self.uid = 0
def keep_alive(self):
self.staletime = time.time() + self.timeout
def update_timeout(self, timeout):
self.timeout = timeout
self.keep_alive()
class Packet:
def __init__(self, data):
if data[0:4] == b"\xfe\xff\xff\xff":
data = ice.Decrypt(data[4:])
logging.debug("decrypted %s" % data.hex())
if data[0:2] != b"\x04\x16" or len(data) < 0x16:
raise Exception("BAD HEADER", data.hex())
self.version, self.headersize, self.packetsize, self.clientid, self.sessionid, self.seqnum, self.seqack, self.packetnum, self.totalpackets = struct.unpack("<BBHIIIIBB", data[:0x16])
if self.packetsize != len(data):
raise Exception("BAD SIZE", data.hex())
data += b"\x00" # workaround to ensure keyvalues are terminated
self.data = data
class TrackerServer:
def __init__(self):
logging.info("----------------------------------")
logging.info("server starting")
logging.info("----------------------------------")
self.ipaddrport = ("192.168.0.16", 1200)
self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.s.bind(("", 1200))
self.s.settimeout(0.2)
self.clients = {}
# contains logged in clients
self.clients_by_uid = {}
self.pending = []
self.usermgr = UserManager(self)
def run(self):
while True:
try:
data, addr = self.s.recvfrom(16384)
except socket.timeout:
addr = None
if addr != None:
try:
self.handle_incoming(data, addr)
except Exception as e:
logging.exception(e)
self.do_sends()
self.remove_stale_clients()
def remove_stale_clients(self):
# get keys first since we are modifying the dict during iteration
addrs = list(self.clients)
curr_time = time.time()
for addr in addrs:
client = self.clients[addr]
if client.staletime < curr_time:
if client.uid in self.clients_by_uid:
del self.clients_by_uid[client.uid]
logging.info("removed stale client uid %x" % client.uid)
del self.clients[addr]
logging.info("removed stale client addr %s" % (addr,))
for friendid in self.usermgr.real_friends(client.uid):
self.send_single_user_status(friendid, client.uid)
def enqueue(self, msg):
self.pending.append((0, 0, msg))
# do not increase seqnum for acks
if msg.cmdid != 1:
msg.client.seqnum += 1
def do_sends(self):
newpending = []
curr_time = time.time()
for curr_attempt, sched_time, msg in self.pending:
# still not acknowledged
if msg.seqnum > msg.client.ackd:
if sched_time < curr_time:
if not msg.ack:
if curr_attempt < 5:
newpending.append((curr_attempt + 1, curr_time + 5.0, msg))
else:
logging.warning("not retrying packet after several attempts")
if curr_attempt > 0:
msg.padding += b"\x00" * 8
logging.info("resending attempt %d" % curr_attempt)
data = msg.getpacket()
self.s.sendto(data, msg.client.addr)
logging.info("sending message to %s with id %d" % (msg.client.addr, msg.cmdid))
logging.debug("sending to %s: %s" % (msg.client.addr, data.hex()))
else:
newpending.append((curr_attempt, sched_time, msg))
self.pending = newpending
def handle_incoming(self, data, addr):
logging.debug("received from %s: %s" % (addr, data.hex()))
pkt = Packet(data)
logging.debug("packet header %d %d %d %d" % (pkt.clientid, pkt.sessionid, pkt.seqnum, pkt.seqack))
if pkt.packetnum != 1 or pkt.totalpackets != 1:
raise Exception("multipacket not supported yet")
self.handle_pkt(pkt, addr)
def handle_pkt(self, pkt, addr):
if addr not in self.clients:
client = Client(addr, pkt.clientid, pkt.sessionid, pkt.seqack + 1, pkt.seqnum)
self.clients[addr] = client
client = self.clients[addr]
client.keep_alive()
if pkt.seqnum == 0: # only happens with ack packets?
self.handle_msg(client, pkt) # maybe change to early handling?
return
# if client never got ack for one of their packages, it changes clientid and resets sequence numbers
# we also get here when a client reconnects without properly terminating the connection
if pkt.clientid != client.clientid:
logging.info("client %x is resetting connection?" % client.clientid)
client.clientid = pkt.clientid
if pkt.seqnum != 1 or pkt.seqack != 0:
raise Exception("bad values on connection reset")
client.sessionid = pkt.sessionid
client.seqnum = 1
client.expected = 1
client.ackd = 0
client.queue = {}
if pkt.seqnum < client.expected:
logging.warning("duplicate arrived late %d %d" % (pkt.seqnum, client.expected))
# ugly hack to ensure we insta-send the response the other side probaby missed
# seems to happen when user is loading into a level in a game?
found = False
for index, (curr_attempt, sched_time, msg) in enumerate(self.pending):
if msg.client.clientid == client.clientid and msg.reply_to == pkt.seqnum:
logging.warning("forcing instant resend of message seqnum %d" % msg.seqnum)
self.pending[index] = (curr_attempt + 1, 0, msg)
found = True
break
# if not found, s
if not found:
logging.warning("forcing instant ack message")
msg = Message(client, 1, True)
self.enqueue(msg)
return
if pkt.seqnum in client.queue:
logging.warning("duplicate packet %d %s" % (pkt.seqnum, client.queue))
return
client.queue[pkt.seqnum] = pkt
while client.expected in client.queue:
seqnum = client.expected
self.handle_msg(client, client.queue[seqnum])
del client.queue[seqnum]
client.expected += 1
def handle_msg(self, client, pkt):
msg = parse_data(pkt.data[0x16:])
cmdid = di(msg["_id"])
logging.info("received packet with id %d from uid %x address %s" % (cmdid, client.uid, client.addr))
# log detailed data in packet
msg2 = parse_data(pkt.data[0x16:], True)
for key in msg2:
datatype, value = msg2[key]
logging.debug(" %s %d %s" % (key, datatype, value))
logging.debug("")
client.r_seqnum = pkt.seqnum
if pkt.seqack < client.ackd:
raise Exception("bad seqack", pkt.seqack, client.ackd)
client.ackd = pkt.seqack
if cmdid == 1:
# ACK has id 1 and might have zero as sessionid, early return, we already handled ack
return
if cmdid == 2001:
if pkt.sessionid != 0:
raise Exception("bad sessionid for connect?")
else:
if not client.init:
logging.warning("clientid %d hasn't been initialized, sending reset" % client.clientid)
msg = Message(client, 1004)
msg.add_int("minTime", 0)
msg.add_int("maxTime", 1)
self.enqueue(msg)
return
if client.sessionid != pkt.sessionid:
raise Exception("bad sessionid", client.sessionid, pkt.sessionid)
if cmdid == 2001: # pre-login
validate_msg(msg, ("uid", "email", "status"), ("UserName", "FirewallWindow"))
# uid can be nonzero if the client reconnects - should we skip the auth step if uid and clientid match?
client.uid = di(msg["uid"])
client.email = msg["email"]
client.status = di(msg["status"])
client.sessionid = 0
if "UserName" in msg:
client.username = msg["UserName"]
else:
client.username = client.email
logging.info("pre-login for uid %x email %s status %d" % (client.uid, client.email, client.status))
new_sessionid = secrets.randbits(32)
client.challenge = secrets.randbits(32)
msg = Message(client, 1001)
msg.add_int("sessionID", new_sessionid)
msg.add_int("challenge", client.challenge)
msg.add_kv(5, "key", newkey)
self.enqueue(msg)
# first msg goes out with a sessionid of 0, now we put in the real sessionid
client.sessionid = new_sessionid
client.init = True
elif cmdid == 2002: # login
validate_msg(msg, ("challenge", "sessionID", "status", "build", "hrate", "ticket"), ("PlatformVer",))
challenge = di(msg["challenge"])
sessionid = di(msg["sessionID"])
status = di(msg["status"])
build = di(msg["build"])
hrate = di(msg["hrate"])
ticket = msg["ticket"]
if "PlatformVer" in msg:
platformver = di(msg["PlatformVer"])
else:
platformver = 0
if challenge != client.challenge:
raise Exception("bad challenge")
if sessionid != client.sessionid:
raise Exception("bad sessionid")
logging.info("build %d hrate %d PlatformVer %d" % (build, hrate, platformver))
# for now, assume ticket validates
# user logs on, TODO notify all friends
# msg = Message(client, 1, pkt.seqnum, True)
# creates user if one doesn't exist
uid = self.usermgr.auth(client.email, client.username)
logging.info("authed user with uid %x" % uid)
client.uid = uid
client.status = status
self.clients_by_uid[client.uid] = client
client.update_timeout(hrate // 1000)
msg = Message(client, 1002) # login OK
msg.add_int("status", status)
msg.add_int("userID", client.uid)
msg.add_int("serverID", 1)
msg.add_int("sessionID", client.sessionid)
msg.add_bin("IP", socket.inet_aton(client.addr[0]))
msg.add_int("Port", client.addr[1])
self.enqueue(msg)
# pending friend requests
for friendid in self.usermgr.pending_friends(client.uid):
logging.info("user %x has pending friend request from %x" % (client.uid, friendid))
self.send_friend_request(client, friendid)
# send info about our friends' status
self.send_friends_status(client.uid)
# notify friends that we are online
for friendid in self.usermgr.real_friends(client.uid):
self.send_single_user_status(friendid, client.uid)
# send usernames etc since they might have been updated
for friendid in self.usermgr.get_friends_by_source(client.uid):
self.send_userinfo(client.uid, friendid)
elif cmdid == 2004: # search for users
validate_msg(msg, ("uid", "Email", "UserName", "FirstName", "LastName"))
# ignore search parameters for now, just return all users
nsent = 0
for row in self.usermgr.search_users():
if client.uid != row[0]:
msg = Message(client, 1011) # search result
msg.add_int("uid", row[0])
msg.add_str("UserName", row[1])
msg.add_str("FirstName", row[2])
msg.add_str("LastName", row[3])
self.enqueue(msg)
nsent += 1
if nsent == 0:
msg = Message(client, 1, True)
self.enqueue(msg)
elif cmdid == 2005: # status change
validate_msg(msg, ("status",), ("hrate", "GameIP", "GamePort", "Game"))
client.status = di(msg["status"])
logging.info("uid %x changed status to %d" % (client.uid, client.status))
if client.status == 4: # ingame
client.gameip = msg["GameIP"]
client.gameport = msg["GamePort"] # some weird format?
client.game = msg["Game"]
logging.info("uid %x is playing a game: %s on ip %s port %s" % (client.uid, client.game, socket.inet_ntoa(client.gameip), client.gameport.hex()))
# user set status to offline, so remove from client uid mapping
if client.status == 0:
del self.clients_by_uid[client.uid]
del self.clients[client.addr]
logging.info("uid %d logged off" % client.uid)
if "hrate" in msg:
hrate = di(msg["hrate"])
logging.info("got hrate %d" % hrate)
client.update_timeout(hrate // 1000)
msg = Message(client, 1, True)
self.enqueue(msg)
for friendid in self.usermgr.real_friends(client.uid):
self.send_single_user_status(friendid, client.uid)
elif cmdid == 2006: # respond to friend request from other user
validate_msg(msg, ("targetID", "auth"))
targetuid = di(msg["targetID"])
auth = di(msg["auth"])
if auth == 1:
logging.info("user %x acknowledged friend request from %x" % (client.uid, targetuid))
self.usermgr.request_friend(client.uid, targetuid)
# update friend info here
else:
logging.error("refusing a friend request isn't implemented yet!")
msg = Message(client, 1, True)
self.enqueue(msg)
# update new friend with our status and us with the friend's status
self.send_single_user_status(client.uid, targetuid)
self.send_single_user_status(targetuid, client.uid)
elif cmdid == 2007: # friendship reqest
validate_msg(msg, ("uid", "ReqReason"))
targetuid = di(msg["uid"])
reason = msg["ReqReason"]
logging.info("user %x requests friendship with %x, reason %s" % (client.uid, targetuid, reason))
self.usermgr.request_friend(client.uid, targetuid)
msg = Message(client, 1, True)
self.enqueue(msg)
if targetuid in self.clients_by_uid:
self.send_friend_request(self.clients_by_uid[targetuid], client.uid)
elif cmdid == 2008: # get friend info
validate_msg(msg, ("uid",))
uid = di(msg["uid"])
logging.info("requested info about uid %x" % uid)
self.send_userinfo(client.uid, uid)
elif cmdid == 2009: # change user info
validate_msg(msg, ("uid", "UserName", "FirstName", "LastName"))
uid = di(msg["uid"])
if client.uid != uid:
logging.error("user %x tried to change info for another user %x!" % (client.uid, uid))
else:
# TODO find proper limits here
username = msg["UserName"][:32]
firstname = msg["FirstName"][:32]
lastname = msg["LastName"][:32]
self.usermgr.update_details(client.uid, username, firstname, lastname)
for friendid in self.usermgr.get_friends_by_target(client.uid):
self.send_userinfo(friendid, client.uid)
msg = Message(client, 1, True)
self.enqueue(msg)
elif cmdid == 2010: # forward for user
validate_msg(msg, ("rID", "rUserID", "rSessionID", "rServerID", "rData"))
rid = di(msg["rID"])
ruserid = di(msg["rUserID"])
rsessionid = di(msg["rSessionID"])
rserverid = di(msg["rServerID"])
rdata = msg["rData"]
logging.warning("forwarding message encountered id %d uid %x sessionid %d serverid %d" % (rid, ruserid, rsessionid, rserverid))
msg2 = parse_data(rdata + b"\x00", True)
for key in msg2:
datatype, value = msg2[key]
logging.warning(" %s %d %s" % (key, datatype, value))
msg = Message(client, 1, True)
self.enqueue(msg)
if ruserid not in self.clients_by_uid:
logging.info("user with uid %x is not online, can't send forwarded message!" % ruserid)
else:
targetclient = self.clients_by_uid[ruserid]
msg = Message(targetclient, rid)
msg.msg = rdata
self.enqueue(msg)
elif cmdid == 3001: # message between users, normally chat
uid = di(msg["uid"])
targetid = di(msg["targetID"])
# addon message (builtin games, other things?)
if "AddOnFlag" in msg and di(msg["AddOnFlag"]) == 1:
validate_msg(msg, ("uid", "targetID", "gameID", "addOnSessionID", "MsgDataLen", "MsgData", "AddOnFlag"))
gameid = di(msg["gameID"])
addonsess = di(msg["addOnSessionID"])
msgdatalen = di(msg["MsgDataLen"])
msgdata = msg["MsgData"]
logging.info("user %x sent addon message targetid %x gameid %d sessionid %d msg size %d" % (uid, targetid, gameid, addonsess, msgdatalen))
msg = Message(client, 1, True)
self.enqueue(msg)
if targetid not in self.clients_by_uid:
logging.info("user with uid %x is not online, can't send message!" % targetid)
else:
targetclient = self.clients_by_uid[targetid]
msg = Message(targetclient, 3001)
msg.add_int("uid", uid)
msg.add_int("targetID", targetid)
msg.add_int("gameID", gameid)
msg.add_int("addOnSessionID", addonsess)
msg.add_int("MsgDataLen", msgdatalen)
msg.add_bin("MsgData", msgdata)
msg.add_int("AddOnFlag", 1)
self.enqueue(msg)
# chat message
else:
validate_msg(msg, ("uid", "targetID", "UserName", "status", "Text"))
username = msg["UserName"]
status = di(msg["status"])
text = msg["Text"]
logging.info("user %x with username %s sent a message targetid %x status %x text %s" % (uid, username, targetid, status, text))
msg = Message(client, 1, True)
self.enqueue(msg)
if targetid not in self.clients_by_uid:
logging.info("user with uid %x is not online, can't send message!" % targetid)
else:
targetclient = self.clients_by_uid[targetid]
msg = Message(targetclient, 3001)
msg.add_int("uid", uid)
msg.add_int("targetID", targetid)
msg.add_str("UserName", username)
msg.add_int("status", status)
msg.add_str("Text", text)
self.enqueue(msg)
elif cmdid == 3002: # update block status? not implemented!
validate_msg(msg, ("uid", "Block", "FakeStatus"))
uid = di(msg["uid"])
logging.warning("user %x tried to update block status for user %x but we don't support it" % (client.uid, uid))
msg = Message(client, 1, True)
self.enqueue(msg)
elif cmdid == 3006: # user starts typing
validate_msg(msg, ("state", "ChatID", "UID", "status", "targetID"))
state = di(msg["state"])
chatid = di(msg["ChatID"])
source = di(msg["UID"])
status = di(msg["status"])
target = di(msg["targetID"])
logging.info("user %x is typing, state %x chatid %x status %x target %x" % (source, state, chatid, status, target))
msg = Message(client, 1, True)
self.enqueue(msg)
if target not in self.clients_by_uid:
logging.info("user with uid %x is not online, can't send typing status!" % target)
else:
targetclient = self.clients_by_uid[target]
msg = Message(targetclient, 3006)
msg.add_int("state", state)
msg.add_int("ChatID", chatid)
msg.add_int("UID", source)
msg.add_int("status", status)
msg.add_int("targetID", target)
self.enqueue(msg)
else:
raise Exception("unknown command %d" % cmdid)
def send_friends_status(self, uid):
if uid not in self.clients_by_uid:
logging.info("user with uid %x is not online, can't send friend status!" % uid)
return
client = self.clients_by_uid[uid]
logging.info("preparing to send friend status for %x" % uid)
# format of status struct - 5 or 6 dwords for each entry
# 0 uid
# 1 status
# 2 sessionid
# 3 IP
# 4 port
# 5 serverid (optional)
count = 0
data = b""
for frienduid in self.usermgr.real_friends(uid):
if frienduid in self.clients_by_uid:
friendstatus = self.clients_by_uid[frienduid].status
else:
friendstatus = 0
# this is OUR sessionid, not the friend's
sessionid = client.sessionid
# use server's IP and port since we pass through all messages
ip = socket.inet_aton(self.ipaddrport[0])
port = self.ipaddrport[1]
# ip = socket.inet_aton("127.0.0.1")
# port = 0
data += struct.pack("<III4sII", frienduid, friendstatus, sessionid, ip, port, 1)
count += 1
if count != 0:
msg = Message(client, 1005)
msg.add_int("count", count)
msg.add_bin("status", data)
self.enqueue(msg)
def send_single_user_status(self, friendid, subjectid):
if friendid not in self.clients_by_uid:
logging.info("user with uid %x is not online, can't send friend status for %x!" % (friendid, subjectid))
return
friendclient = self.clients_by_uid[friendid]
if subjectid not in self.clients_by_uid:
status = 0
else:
subjectclient = self.clients_by_uid[subjectid]
status = subjectclient.status
msg = Message(friendclient, 1006) # send user status
msg.add_int("userID", subjectid)
msg.add_int("status", status)
msg.add_int("sessionID", friendclient.sessionid)
msg.add_int("serverID", 1)
msg.add_bin("IP", socket.inet_aton(self.ipaddrport[0]))
msg.add_int("Port", self.ipaddrport[1])
if status == 4:
msg.add_bin("GameIP", subjectclient.gameip)
msg.add_bin("GamePort", subjectclient.gameport)
msg.add_str("Game", subjectclient.game)
self.enqueue(msg)
def send_friend_request(self, client, friendid):
friendid, email, username, firstname, lastname = self.usermgr.get_user_by_uid(friendid)
msg = Message(client, 1015) # friend user request
msg.add_int("uid", friendid)
msg.add_str("email", email)
msg.add_str("UserName", username)
msg.add_str("FirstName", firstname)
msg.add_str("LastName", lastname)
self.enqueue(msg)
def send_userinfo(self, clientuid, uid):
if clientuid not in self.clients_by_uid:
logging.info("user with uid %x is not online, can't send info about uid %d!" % (clientuid, uid))
return
client = self.clients_by_uid[clientuid]
uid, email, username, firstname, lastname = self.usermgr.get_user_by_uid(uid)
msg = Message(client, 1009)
msg.add_int("uid", uid)
msg.add_str("UserName", username)
msg.add_str("FirstName", firstname)
msg.add_str("LastName", lastname)
msg.add_str("Email", email)
self.enqueue(msg)
srv = TrackerServer()
srv.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment