gamespy_profile_server.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  1. """DWC Network Server Emulator
  2. Copyright (C) 2014 polaris-
  3. Copyright (C) 2014 ToadKing
  4. Copyright (C) 2014 AdmiralCurtiss
  5. Copyright (C) 2014 msoucy
  6. Copyright (C) 2015 Sepalani
  7. This program is free software: you can redistribute it and/or modify
  8. it under the terms of the GNU Affero General Public License as
  9. published by the Free Software Foundation, either version 3 of the
  10. License, or (at your option) any later version.
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. GNU Affero General Public License for more details.
  15. You should have received a copy of the GNU Affero General Public License
  16. along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. """
  18. import logging
  19. import time
  20. import traceback
  21. from twisted.internet.protocol import Factory
  22. from twisted.internet.endpoints import serverFromString
  23. from twisted.protocols.basic import LineReceiver
  24. from twisted.internet import reactor
  25. from twisted.internet.error import ReactorAlreadyRunning
  26. import gamespy.gs_database as gs_database
  27. import gamespy.gs_query as gs_query
  28. import gamespy.gs_utility as gs_utils
  29. import other.utils as utils
  30. import dwc_config
  31. logger = dwc_config.get_logger('GameSpyProfileServer')
  32. address = dwc_config.get_ip_port('GameSpyProfileServer')
  33. class GameSpyProfileServer(object):
  34. def __init__(self):
  35. pass
  36. def start(self):
  37. endpoint = serverFromString(
  38. reactor,
  39. "tcp:%d:interface=%s" % (address[1], address[0])
  40. )
  41. conn = endpoint.listen(PlayerFactory())
  42. try:
  43. if not reactor.running:
  44. reactor.run(installSignalHandlers=0)
  45. except ReactorAlreadyRunning:
  46. pass
  47. class PlayerFactory(Factory):
  48. def __init__(self):
  49. """Player Factory.
  50. Instead of storing the sessions in the database, it might make more
  51. sense to store them in the PlayerFactory.
  52. """
  53. logger.log(logging.INFO,
  54. "Now listening for connections on %s:%d...",
  55. address[0], address[1])
  56. self.sessions = {}
  57. def buildProtocol(self, address):
  58. return PlayerSession(self.sessions, address)
  59. class PlayerSession(LineReceiver):
  60. def __init__(self, sessions, address):
  61. self.setRawMode() # We're dealing with binary data so set to raw mode
  62. self.db = gs_database.GamespyDatabase()
  63. self.sessions = sessions
  64. self.address = address
  65. # Stores any unparsable/incomplete commands until the next
  66. # rawDataReceived
  67. self.remaining_message = ""
  68. self.profileid = 0
  69. self.gameid = ""
  70. self.buddies = []
  71. self.blocked = []
  72. self.status = ""
  73. self.statstring = ""
  74. self.locstring = ""
  75. self.keepalive = int(time.time())
  76. self.sesskey = ""
  77. self.sdkrevision = "0"
  78. def log(self, level, msg, *args, **kwargs):
  79. if not self.profileid:
  80. if not self.gameid:
  81. logger.log(level, "[%s:%d] " + msg,
  82. self.address.host, self.address.port,
  83. *args, **kwargs)
  84. else:
  85. logger.log(level, "[%s:%d | %s] " + msg,
  86. self.address.host, self.address.port, self.gameid,
  87. *args, **kwargs)
  88. else:
  89. if not self.gameid:
  90. logger.log(level, "[%s:%d | %d] " + msg,
  91. self.address.host, self.address.port,
  92. self.profileid, *args, **kwargs)
  93. else:
  94. logger.log(level, "[%s:%d | %d | %s] " + msg,
  95. self.address.host, self.address.port,
  96. self.profileid, self.gameid, *args, **kwargs)
  97. def get_ip_as_int(self, address):
  98. ipaddress = 0
  99. if address is not None:
  100. for n in address.split('.'):
  101. ipaddress = (ipaddress << 8) | int(n)
  102. return ipaddress
  103. def connectionMade(self):
  104. try:
  105. self.transport.setTcpKeepAlive(1)
  106. self.log(logging.INFO,
  107. "Received connection from %s:%d",
  108. self.address.host, self.address.port)
  109. # Create new session id
  110. self.session = ""
  111. # Generate a random challenge string
  112. self.challenge = utils.generate_random_str(
  113. 10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  114. )
  115. # The first command sent to the client is always a login challenge
  116. # containing the server challenge key.
  117. msg = gs_query.create_gamespy_message([
  118. ('__cmd__', "lc"),
  119. ('__cmd_val__', "1"),
  120. ('challenge', self.challenge),
  121. ('id', "1"),
  122. ])
  123. self.log(logging.DEBUG, "SENDING: '%s'...", msg)
  124. self.transport.write(bytes(msg))
  125. except:
  126. self.log(logging.ERROR,
  127. "Unknown exception: %s",
  128. traceback.format_exc())
  129. def connectionLost(self, reason):
  130. try:
  131. self.log(logging.INFO, "%s", "Client disconnected")
  132. self.status = "0"
  133. self.statstring = "Offline"
  134. self.locstring = ""
  135. self.send_status_to_friends()
  136. if self.profileid in self.sessions:
  137. del self.sessions[self.profileid]
  138. self.db.delete_session(self.sesskey)
  139. self.log(logging.INFO, "Deleted session %s", self.session)
  140. except:
  141. self.log(logging.ERROR,
  142. "Unknown exception: %s",
  143. traceback.format_exc())
  144. def rawDataReceived(self, data):
  145. try:
  146. self.log(logging.DEBUG, "RESPONSE: '%s'...", data)
  147. # In the case where command string is too big to fit into one
  148. # read, any parts that could not be successfully parsed are
  149. # stored in the variable remaining_message. On the next
  150. # rawDataReceived command, the remaining message and the data
  151. # are combined to create a full command.
  152. data = self.remaining_message + data
  153. # Check to make sure the data buffer starts with a valid command.
  154. if len(data) > 0 and data[0] != '\\':
  155. # There is data in the buffer but it doesn't start with a \ so
  156. # there's no chance of it being valid. Look for the first
  157. # instance of \final\ and remove everything before it. If
  158. # \final\ is not in the command string then ignore it.
  159. final = "\\final\\"
  160. data = data[data.index(final) + len(final):] \
  161. if final in data else ""
  162. commands, self.remaining_message = \
  163. gs_query.parse_gamespy_message(data)
  164. cmds = {
  165. "login": self.perform_login,
  166. "logout": self.perform_logout,
  167. "getprofile": self.perform_getprofile,
  168. "updatepro": self.perform_updatepro,
  169. "ka": self.perform_ka,
  170. "status": self.perform_status,
  171. "bm": self.perform_bm,
  172. "addbuddy": self.perform_addbuddy,
  173. "delbuddy": self.perform_delbuddy,
  174. "authadd": self.perform_authadd,
  175. }
  176. def cmd_err(data_parsed):
  177. # Maybe write unknown commands to a separate file later so
  178. # new data can be collected more easily?
  179. self.log(logging.ERROR,
  180. "Found unknown command, don't know how to handle"
  181. " '%s'.", data_parsed['__cmd__'])
  182. for data_parsed in commands:
  183. # self.log(-1, data_parsed)
  184. self.log(logging.DEBUG, "%s", data_parsed)
  185. cmds.get(data_parsed['__cmd__'], cmd_err)(data_parsed)
  186. except:
  187. self.log(logging.ERROR,
  188. "Unknown exception: %s",
  189. traceback.format_exc())
  190. def perform_login(self, data_parsed):
  191. authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken'],
  192. self.db)
  193. if authtoken_parsed is None:
  194. self.log(logging.WARNING, "%s", "Invalid Authtoken.")
  195. msg = gs_query.create_gamespy_message([
  196. ('__cmd__', "error"),
  197. ('__cmd_val__', ""),
  198. ('err', '266'),
  199. ('fatal', ''),
  200. ('errmsg', 'There was an error validating the'
  201. ' pre-authentication.'),
  202. ('id', data_parsed['id']),
  203. ])
  204. self.transport.write(bytes(msg))
  205. return
  206. if 'sdkrevision' in data_parsed:
  207. self.sdkrevision = data_parsed['sdkrevision']
  208. # Verify the client's response
  209. valid_response = gs_utils.generate_response(
  210. self.challenge,
  211. authtoken_parsed['challenge'],
  212. data_parsed['challenge'],
  213. data_parsed['authtoken']
  214. )
  215. if data_parsed['response'] != valid_response:
  216. self.log(logging.ERROR,
  217. "ERROR: Got invalid response."
  218. " Got %s, expected %s",
  219. data_parsed['response'], valid_response)
  220. proof = gs_utils.generate_proof(
  221. self.challenge,
  222. authtoken_parsed['challenge'],
  223. data_parsed['challenge'],
  224. data_parsed['authtoken']
  225. )
  226. userid, profileid, gsbrcd, uniquenick = \
  227. gs_utils.login_profile_via_parsed_authtoken(authtoken_parsed,
  228. self.db)
  229. if profileid is not None:
  230. # Successfully logged in or created account, continue
  231. # creating session.
  232. loginticket = gs_utils.base64_encode(
  233. utils.generate_random_str(16)
  234. )
  235. self.sesskey = self.db.create_session(profileid, loginticket)
  236. self.sessions[profileid] = self
  237. self.buddies = self.db.get_buddy_list(self.profileid)
  238. self.blocked = self.db.get_blocked_list(self.profileid)
  239. if self.sdkrevision == "11": # Used in Tatsunoko vs Capcom
  240. def make_list(data):
  241. return [str(d['buddyProfileId'])
  242. for d in data
  243. if d['status'] == 1]
  244. block_list = make_list(self.blocked)
  245. msg = gs_query.create_gamespy_message([
  246. ('__cmd__', "blk"),
  247. ('__cmd_val__', str(len(block_list))),
  248. ('list', ','.join(block_list)),
  249. ])
  250. self.log(logging.DEBUG, "SENDING: %s", msg)
  251. self.transport.write(bytes(msg))
  252. buddy_list = make_list(self.buddies)
  253. msg = gs_query.create_gamespy_message([
  254. ('__cmd__', "bdy"),
  255. ('__cmd_val__', str(len(buddy_list))),
  256. ('list', ','.join(buddy_list)),
  257. ])
  258. self.log(logging.DEBUG, "SENDING: %s", msg)
  259. self.transport.write(bytes(msg))
  260. msg = gs_query.create_gamespy_message([
  261. ('__cmd__', "lc"),
  262. ('__cmd_val__', "2"),
  263. ('sesskey', self.sesskey),
  264. ('proof', proof),
  265. ('userid', userid),
  266. ('profileid', profileid),
  267. ('uniquenick', uniquenick),
  268. # Some kind of token... don't know it gets used or generated,
  269. # but it doesn't seem to have any negative effects if it's
  270. # not properly generated.
  271. ('lt', loginticket),
  272. ('id', data_parsed['id']),
  273. ])
  274. # Take the first 4 letters of gsbrcd instead of gamecd because
  275. # they should be consistent across game regions. For example, the
  276. # US version of Metroid Prime Hunters has the gamecd "AMHE" and
  277. # the first 4 letters of gsbrcd are "AMHE". However, the Japanese
  278. # version of Metroid Prime Hunters has the gamecd "AMHJ" with the
  279. # first 4 letters of bsbrcd as "AMHE". Tetris DS is the other way,
  280. # with the first 4 letters as the Japanese version (ATRJ) while
  281. # the gamecd is region specific (ATRE for US and ATRJ for JP).
  282. # gameid is used to send all people on the player's friends list a
  283. # status updates, so don't make it region specific.
  284. self.gameid = gsbrcd[:4]
  285. self.profileid = int(profileid)
  286. self.log(logging.DEBUG, "SENDING: %s", msg)
  287. self.transport.write(bytes(msg))
  288. # Get pending messages.
  289. self.get_pending_messages()
  290. # Send any friend statuses when the user logs in.
  291. # This will allow the user to see if their friends are hosting a
  292. # game as soon as they log in.
  293. self.get_status_from_friends()
  294. self.send_status_to_friends()
  295. # profile = self.db.get_profile_from_profileid(profileid)
  296. # if profile is not None:
  297. # self.statstring = profile['stat']
  298. # self.locstring = profile['loc']
  299. else:
  300. self.log(logging.INFO, "%s", "Invalid password or banned user")
  301. msg = gs_query.create_gamespy_message([
  302. ('__cmd__', "error"),
  303. ('__cmd_val__', ""),
  304. ('err', '256'),
  305. ('fatal', ''),
  306. ('errmsg', 'Login failed.'),
  307. ('id', data_parsed['id']),
  308. ])
  309. self.log(logging.DEBUG, "SENDING: %s", msg)
  310. self.transport.write(bytes(msg))
  311. def perform_logout(self, data_parsed):
  312. self.log(logging.INFO,
  313. "Session %s has logged off",
  314. data_parsed['sesskey'])
  315. self.db.delete_session(data_parsed['sesskey'])
  316. if self.profileid in self.sessions:
  317. del self.sessions[self.profileid]
  318. self.transport.loseConnection()
  319. def perform_getprofile(self, data_parsed):
  320. # profile = self.db.get_profile_from_session_key(
  321. # data_parsed['sesskey']
  322. # )
  323. profile = self.db.get_profile_from_profileid(data_parsed['profileid'])
  324. # Wii example:
  325. # \pi\\profileid\474888031\nick\5pde5vhn1WR9E2g1t533\userid\442778352
  326. # \email\5pde5vhn1WR9E2g1t533@nds\sig\b126556e5ee62d4da9629dfad0f6b2a8
  327. # \uniquenick\5pde5vhn1WR9E2g1t533\pid\11\lon\0.000000\lat\0.000000
  328. # \loc\\id\2\final\
  329. sig = utils.generate_random_hex_str(32)
  330. msg_d = [
  331. ('__cmd__', "pi"),
  332. ('__cmd_val__', ""),
  333. ('profileid', profile['profileid']),
  334. ('nick', profile['uniquenick']),
  335. ('userid', profile['userid']),
  336. ('email', profile['email']),
  337. ('sig', sig),
  338. ('uniquenick', profile['uniquenick']),
  339. ('pid', profile['pid']),
  340. ]
  341. if profile['firstname']:
  342. # Wii gets a firstname
  343. msg_d.append(('firstname', profile['firstname']))
  344. if profile['lastname']:
  345. msg_d.append(('lastname', profile['lastname']))
  346. msg_d.extend([
  347. ('lon', profile['lon']),
  348. ('lat', profile['lat']),
  349. ('loc', profile['loc']),
  350. ('id', data_parsed['id']),
  351. ])
  352. msg = gs_query.create_gamespy_message(msg_d)
  353. self.log(logging.DEBUG, "SENDING: %s", msg)
  354. self.transport.write(bytes(msg))
  355. def perform_updatepro(self, data_parsed):
  356. """Wii example:
  357. Remove any fields not related to what we should be updating.
  358. To avoid any crashes, make sure the key is actually in the dictionary
  359. before removing it.
  360. """
  361. if "__cmd__" in data_parsed:
  362. data_parsed.pop('__cmd__')
  363. if "__cmd_val__" in data_parsed:
  364. data_parsed.pop('__cmd_val__')
  365. if "updatepro" in data_parsed:
  366. data_parsed.pop('updatepro')
  367. if "partnerid" in data_parsed:
  368. data_parsed.pop('partnerid')
  369. if "sesskey" in data_parsed:
  370. data_parsed.pop('sesskey')
  371. # Create a list of fields to be updated.
  372. for f in data_parsed:
  373. self.db.update_profile(self.profileid, (f, data_parsed[f]))
  374. def perform_ka(self, data_parsed):
  375. self.keepalive = int(time.time())
  376. msg = gs_query.create_gamespy_message([
  377. ('__cmd__', "ka"),
  378. ('__cmd_val__', ""),
  379. ])
  380. self.transport.write(msg)
  381. def perform_status(self, data_parsed):
  382. self.sesskey = data_parsed['sesskey']
  383. self.status = data_parsed['__cmd_val__']
  384. self.statstring = data_parsed['statstring']
  385. self.locstring = data_parsed['locstring']
  386. # Send authorization requests to client
  387. self.get_buddy_requests()
  388. # Send authorizationed message to client
  389. self.get_buddy_authorized()
  390. self.send_status_to_friends()
  391. def perform_bm(self, data_parsed):
  392. # Message to/from clients?
  393. if data_parsed['__cmd_val__'] in ("1", "5", "102", "103"):
  394. if "t" in data_parsed:
  395. # Send message to the profile id in "t"
  396. dest_profileid = int(data_parsed['t'])
  397. dest_profile_buddies = self.db.get_buddy_list(dest_profileid)
  398. dest_msg = data_parsed['msg']
  399. not_buddies = False
  400. # Check if the user is buddies with the target user before
  401. # sending message.
  402. if not_buddies:
  403. for buddy in self.buddies:
  404. if buddy['userProfileId'] == dest_profileid:
  405. not_buddies = True
  406. break
  407. if not_buddies:
  408. for buddy in dest_profile_buddies:
  409. if buddy['userProfileId'] == self.profile:
  410. not_buddies = True
  411. break
  412. # Send error to user if they tried to send a message to
  413. # someone who isn't a buddy.
  414. if not_buddies:
  415. msg = gs_query.create_gamespy_message([
  416. ('__cmd__', "error"),
  417. ('__cmd_val__', ""),
  418. ('err', 2305),
  419. ('errmsg',
  420. "The profile the message was to be sent to is not"
  421. " a buddy."),
  422. ('id', 1),
  423. ])
  424. logger.log(logging.DEBUG,
  425. "Trying to send message to someone who isn't"
  426. " a buddy: %s", msg)
  427. self.transport.write(msg)
  428. return
  429. msg = gs_query.create_gamespy_message([
  430. ('__cmd__', "bm"),
  431. ('__cmd_val__', "1"),
  432. ('f', self.profileid),
  433. ('msg', dest_msg),
  434. ])
  435. if dest_profileid in self.sessions:
  436. self.log(logging.DEBUG,
  437. "SENDING TO %s:%s: %s",
  438. self.sessions[dest_profileid].address.host,
  439. self.sessions[dest_profileid].address.port, msg)
  440. self.sessions[dest_profileid].transport.write(bytes(msg))
  441. self.send_status_to_friends(dest_profileid)
  442. self.get_status_from_friends(dest_profileid)
  443. else:
  444. if data_parsed['__cmd_val__'] == "1":
  445. self.log(logging.DEBUG,
  446. "Saving message to %d: %s",
  447. dest_profileid, msg)
  448. self.db.save_pending_message(self.profileid,
  449. dest_profileid, msg)
  450. else:
  451. msg = gs_query.create_gamespy_message([
  452. ('__cmd__', "error"),
  453. ('__cmd_val__', ""),
  454. ('err', 2307),
  455. ('errmsg', "The buddy to send a message to"
  456. " is offline."),
  457. ('id', 1),
  458. ])
  459. logger.log(logging.DEBUG,
  460. "Trying to send message to someone who"
  461. " isn't online: %s", msg)
  462. self.transport.write(msg)
  463. def perform_addbuddy(self, data_parsed):
  464. newprofileid = int(data_parsed['newprofileid'])
  465. if newprofileid == self.profileid:
  466. logger.log(logging.DEBUG,
  467. "Can't add self as friend: %d == %d",
  468. newprofileid, self.profileid)
  469. return
  470. # Sample:
  471. # \addbuddy\\sesskey\231601763\newprofileid\476756820\reason\\final\
  472. self.buddies = self.db.get_buddy_list(self.profileid)
  473. buddy_exists = False
  474. for buddy in self.buddies:
  475. if buddy['buddyProfileId'] == newprofileid:
  476. buddy_exists = True
  477. break
  478. if not buddy_exists:
  479. self.db.add_buddy(self.profileid, newprofileid)
  480. if newprofileid in self.sessions:
  481. logger.log(logging.DEBUG,
  482. "User is online, sending direct request from"
  483. " profile id %d to profile id %d...",
  484. self.profileid, newprofileid)
  485. # TODO: Add a way to check if a profile id is already a buddy
  486. # using SQL
  487. other_player_authorized = False
  488. target_buddy_list = self.db.get_buddy_list(newprofileid)
  489. logger.log(logging.DEBUG, "%s", target_buddy_list)
  490. for buddy in target_buddy_list:
  491. if buddy['buddyProfileId'] == self.profileid and \
  492. not buddy['blocked']:
  493. other_player_authorized = True
  494. break
  495. if other_player_authorized:
  496. logger.log(logging.DEBUG,
  497. "Automatic authorization: %d (target) already"
  498. " has %d (source) as a friend.",
  499. newprofileid, self.profileid)
  500. # Force them both to add each other
  501. self.send_buddy_request(self.sessions[newprofileid],
  502. self.profileid)
  503. self.send_buddy_request(self.sessions[self.profileid],
  504. newprofileid)
  505. self.send_bm4(newprofileid)
  506. self.db.auth_buddy(newprofileid, self.profileid)
  507. self.db.auth_buddy(self.profileid, newprofileid)
  508. self.send_status_to_friends(newprofileid)
  509. self.get_status_from_friends(newprofileid)
  510. else:
  511. self.send_buddy_request(self.sessions[newprofileid],
  512. self.profileid)
  513. else:
  514. # Trying to add someone who is already a friend.
  515. # Just send status updates.
  516. self.send_status_to_friends(newprofileid)
  517. self.get_status_from_friends(newprofileid)
  518. self.buddies = self.db.get_buddy_list(self.profileid)
  519. def send_bm4(self, playerid):
  520. date = int(time.time())
  521. msg = gs_query.create_gamespy_message([
  522. ('__cmd__', "bm"),
  523. ('__cmd_val__', "4"),
  524. ('f', playerid),
  525. ('date', date),
  526. ('msg', ""),
  527. ])
  528. self.transport.write(bytes(msg))
  529. def perform_delbuddy(self, data_parsed):
  530. """Sample:
  531. \delbuddy\\sesskey\61913621\delprofileid\1\final\
  532. """
  533. self.db.delete_buddy(self.profileid, int(data_parsed['delprofileid']))
  534. self.buddies = self.db.get_buddy_list(self.profileid)
  535. def perform_authadd(self, data_parsed):
  536. """Authorize the other person's friend request.
  537. Sample:
  538. \authadd\\sesskey\231587549\fromprofileid\217936895
  539. \sig\f259f26d3273f8bda23c7c5e4bd8c5aa\final\
  540. """
  541. target_profile = int(data_parsed['fromprofileid'])
  542. self.db.auth_buddy(target_profile, self.profileid)
  543. self.get_buddy_authorized()
  544. self.buddies = self.db.get_buddy_list(self.profileid)
  545. self.send_bm4(target_profile)
  546. self.send_status_to_friends(target_profile)
  547. self.get_status_from_friends(target_profile)
  548. def send_status_to_friends(self, buddy_profileid=None):
  549. """TODO: Cache buddy list so we don't have to query the
  550. database every time."""
  551. self.buddies = self.db.get_buddy_list(self.profileid)
  552. if self.status == "0" and self.statstring == "Offline":
  553. # Going offline, don't need to send the other information.
  554. status_msg = "|s|%s|ss|%s" % (self.status, self.statstring)
  555. else:
  556. status_msg = "|s|%s|ss|%s|ls|%s|ip|%d|p|0|qm|0" % (
  557. self.status, self.statstring, self.locstring,
  558. self.get_ip_as_int(self.address.host)
  559. )
  560. msg = gs_query.create_gamespy_message([
  561. ('__cmd__', "bm"),
  562. ('__cmd_val__', "100"),
  563. ('f', self.profileid),
  564. ('msg', status_msg),
  565. ])
  566. buddy_list = self.buddies
  567. if buddy_profileid is not None:
  568. buddy_list = [{"buddyProfileId": buddy_profileid}]
  569. for buddy in buddy_list:
  570. if buddy['buddyProfileId'] in self.sessions:
  571. # self.log(logging.DEBUG,
  572. # "Sending status to buddy id %s (%s:%d): %s",
  573. # str(buddy['buddyProfileId']),
  574. # self.sessions[
  575. # buddy['buddyProfileId']
  576. # ].address.host,
  577. # self.sessions[
  578. # buddy['buddyProfileId']
  579. # ].address.port, msg)
  580. self.sessions[buddy['buddyProfileId']].transport \
  581. .write(bytes(msg))
  582. def get_status_from_friends(self, buddy_profileid=None):
  583. """This will be called when the player logs in.
  584. Grab the player's buddy list and check the current sessions to
  585. see if anyone is online. If they are online, make them send an update
  586. to the calling client.
  587. """
  588. self.buddies = self.db.get_buddy_list(self.profileid)
  589. buddy_list = self.buddies
  590. if buddy_profileid is not None:
  591. buddy_list = [{"buddyProfileId": buddy_profileid}]
  592. for buddy in self.buddies:
  593. if buddy['status'] != 1:
  594. continue
  595. if buddy['buddyProfileId'] in self.sessions and \
  596. self.sessions[buddy['buddyProfileId']].gameid == self.gameid:
  597. status_msg = "|s|%s|ss|%s|ls|%s|ip|%d|p|0|qm|0" % (
  598. self.sessions[buddy['buddyProfileId']].status,
  599. self.sessions[buddy['buddyProfileId']].statstring,
  600. self.sessions[buddy['buddyProfileId']].locstring,
  601. self.get_ip_as_int(self.sessions[
  602. buddy['buddyProfileId']
  603. ].address.host))
  604. else:
  605. status_msg = "|s|0|ss|Offline"
  606. msg = gs_query.create_gamespy_message([
  607. ('__cmd__', "bm"),
  608. ('__cmd_val__', "100"),
  609. ('f', buddy['buddyProfileId']),
  610. ('msg', status_msg),
  611. ])
  612. self.transport.write(bytes(msg))
  613. def get_buddy_authorized(self):
  614. buddies = self.db.buddy_need_auth_message(self.profileid)
  615. for buddy in buddies:
  616. msg = gs_query.create_gamespy_message([
  617. ('__cmd__', "bm"),
  618. ('__cmd_val__', "1"),
  619. ('f', buddy['userProfileId']),
  620. ('msg', "I have authorized your request to add me to"
  621. " your list"),
  622. ])
  623. self.transport.write(bytes(msg))
  624. self.db.buddy_sent_auth_message(buddy['userProfileId'],
  625. buddy['buddyProfileId'])
  626. def get_buddy_requests(self):
  627. """Get list people who have added the user but haven't been accepted
  628. yet."""
  629. buddies = self.db.get_pending_buddy_requests(self.profileid)
  630. for buddy in buddies:
  631. self.send_buddy_request(self,
  632. buddy['userProfileId'],
  633. buddy['time'])
  634. def send_buddy_request(self, session, profileid, senttime=None):
  635. sig = utils.generate_random_hex_str(32)
  636. msg = "\r\n\r\n"
  637. msg += "|signed|" + sig
  638. if senttime is None:
  639. senttime = int(time.time())
  640. msg = gs_query.create_gamespy_message([
  641. ('__cmd__', "bm"),
  642. ('__cmd_val__', "2"),
  643. ('f', profileid),
  644. ('date', senttime),
  645. ('msg', msg),
  646. ])
  647. session.transport.write(bytes(msg))
  648. def get_pending_messages(self):
  649. messages = self.db.get_pending_messages(self.profileid)
  650. for message in messages:
  651. if message['sourceid'] not in self.blocked:
  652. try:
  653. self.transport.write(bytes(message['msg']))
  654. except:
  655. self.transport.write(bytes(message['msg'], "utf-8"))
  656. if __name__ == "__main__":
  657. gsps = GameSpyProfileServer()
  658. gsps.start()