standalone.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826
  1. #!/usr/bin/env python
  2. import os
  3. import signal
  4. import struct
  5. import sys
  6. import threading
  7. import time
  8. import json
  9. import math
  10. import random
  11. import itertools
  12. import socketserver
  13. from urllib3 import PoolManager
  14. from http.server import SimpleHTTPRequestHandler
  15. from datetime import datetime, timedelta
  16. from Crypto.Cipher import AES
  17. import zwift_offline as zo
  18. import udp_node_msgs_pb2
  19. import tcp_node_msgs_pb2
  20. import profile_pb2
  21. if getattr(sys, 'frozen', False):
  22. # If we're running as a pyinstaller bundle
  23. SCRIPT_DIR = sys._MEIPASS
  24. STORAGE_DIR = "%s/storage" % os.path.dirname(sys.executable)
  25. else:
  26. SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
  27. STORAGE_DIR = "%s/storage" % SCRIPT_DIR
  28. CDN_DIR = "%s/cdn" % SCRIPT_DIR
  29. CDN_PROXY = os.path.isfile('%s/cdn-proxy.txt' % STORAGE_DIR)
  30. if not CDN_PROXY and not os.path.isfile('%s/disable_proxy.txt' % STORAGE_DIR):
  31. # If CDN proxy is disabled, try to resolve zwift.com using Google public DNS
  32. try:
  33. import dns.resolver
  34. resolver = dns.resolver.Resolver(configure=False)
  35. resolver.nameservers = ['8.8.8.8', '8.8.4.4']
  36. resolver.cache = dns.resolver.Cache()
  37. resolver.resolve('zwift.com')
  38. # If succeeded, patch create_connection to use resolver
  39. from urllib3.util import connection
  40. orig_create_connection = connection.create_connection
  41. def patched_create_connection(address, *args, **kwargs):
  42. host, port = address
  43. answer = resolver.cache.data.get((host, 1, 1))
  44. if not answer:
  45. try:
  46. answer = resolver.resolve(host)
  47. resolver.cache.put((host, 1, 1), answer)
  48. except Exception as exc:
  49. print('dns.resolver: %s' % repr(exc))
  50. if answer:
  51. address = (answer[0].to_text(), port)
  52. return orig_create_connection(address, *args, **kwargs)
  53. connection.create_connection = patched_create_connection
  54. CDN_PROXY = True
  55. except:
  56. pass
  57. PACE_PARTNERS_DIR = "%s/robopacers" % STORAGE_DIR
  58. FAKE_DNS_FILE = "%s/fake-dns.txt" % STORAGE_DIR
  59. ENABLE_BOTS_FILE = "%s/enable_bots.txt" % STORAGE_DIR
  60. DISCORD_CONFIG_FILE = "%s/discord.cfg" % STORAGE_DIR
  61. if os.path.isfile(DISCORD_CONFIG_FILE):
  62. from discord_bot import DiscordThread
  63. discord = DiscordThread(DISCORD_CONFIG_FILE)
  64. else:
  65. class DummyDiscord():
  66. def send_message(self, msg, sender_id=None):
  67. pass
  68. def change_presence(self, n):
  69. pass
  70. announce = False
  71. discord = DummyDiscord()
  72. bot_update_freq = 3
  73. pacer_update_freq = 1
  74. simulated_latency = 300 #makes bots animation smoother than using current time
  75. last_pp_updates = {}
  76. last_bot_updates = {}
  77. last_bookmark_updates = {}
  78. global_ghosts = {}
  79. online = {}
  80. global_pace_partners = {}
  81. global_bots = {}
  82. global_news = {} #player id to dictionary of peer_player_id->worldTime
  83. global_relay = {}
  84. global_clients = {}
  85. def sigint_handler(num, frame):
  86. httpd.shutdown()
  87. httpd.server_close()
  88. tcpserver.shutdown()
  89. tcpserver.server_close()
  90. udpserver.shutdown()
  91. udpserver.server_close()
  92. os._exit(0)
  93. signal.signal(signal.SIGINT, sigint_handler)
  94. class CDNHandler(SimpleHTTPRequestHandler):
  95. def translate_path(self, path):
  96. path = SimpleHTTPRequestHandler.translate_path(self, path)
  97. relpath = os.path.relpath(path, os.getcwd())
  98. fullpath = os.path.join(CDN_DIR, relpath)
  99. return fullpath
  100. def do_GET(self):
  101. # Check if client requested the map be overridden
  102. if self.path == '/gameassets/MapSchedule_v2.xml' and self.client_address[0] in zo.map_override:
  103. self.send_response(200)
  104. self.send_header('Content-type', 'text/xml')
  105. self.end_headers()
  106. start = datetime.today() - timedelta(days=1)
  107. output = '<MapSchedule><appointments><appointment map="%s" start="%s"/></appointments><VERSION>1</VERSION></MapSchedule>' % (zo.map_override[self.client_address[0]], start.strftime("%Y-%m-%dT00:01-04"))
  108. self.wfile.write(output.encode())
  109. del zo.map_override[self.client_address[0]]
  110. return
  111. if self.path == '/gameassets/PortalRoadSchedule_v1.xml' and self.client_address[0] in zo.climb_override:
  112. self.send_response(200)
  113. self.send_header('Content-type', 'text/xml')
  114. self.end_headers()
  115. start = datetime.today() - timedelta(days=1)
  116. output = '<PortalRoads><PortalRoadSchedule><appointments><appointment road="%s" portal="0" start="%s"/></appointments><VERSION>1</VERSION></PortalRoadSchedule></PortalRoads>' % (zo.climb_override[self.client_address[0]], start.strftime("%Y-%m-%dT00:01-04"))
  117. self.wfile.write(output.encode())
  118. del zo.climb_override[self.client_address[0]]
  119. return
  120. if CDN_PROXY and self.path.startswith('/gameassets/') and not self.path.endswith('_ver_cur.xml') and not ('User-Agent' in self.headers and 'python-urllib3' in self.headers['User-Agent']):
  121. try:
  122. self.send_response(200)
  123. self.end_headers()
  124. self.wfile.write(PoolManager().request('GET', 'http://cdn.zwift.com%s' % self.path).data)
  125. return
  126. except Exception as exc:
  127. print('Error trying to proxy: %s' % repr(exc))
  128. SimpleHTTPRequestHandler.do_GET(self)
  129. class DeviceType:
  130. Relay = 1
  131. Zc = 2
  132. class ChannelType:
  133. UdpClient = 1
  134. UdpServer = 2
  135. TcpClient = 3
  136. TcpServer = 4
  137. class Packet:
  138. flags = None
  139. ri = None
  140. ci = None
  141. sn = None
  142. payload = None
  143. class InitializationVector:
  144. def __init__(self, dt = 0, ct = 0, ci = 0, sn = 0):
  145. self._dt = struct.pack('!h', dt)
  146. self._ct = struct.pack('!h', ct)
  147. self._ci = struct.pack('!h', ci)
  148. self._sn = struct.pack('!i', sn)
  149. @property
  150. def dt(self):
  151. return self._dt
  152. @dt.setter
  153. def dt(self, v):
  154. self._dt = struct.pack('!h', v)
  155. @property
  156. def ct(self):
  157. return self._ct
  158. @ct.setter
  159. def ct(self, v):
  160. self._ct = struct.pack('!h', v)
  161. @property
  162. def ci(self):
  163. return self._ci
  164. @ci.setter
  165. def ci(self, v):
  166. self._ci = struct.pack('!h', v)
  167. @property
  168. def sn(self):
  169. return self._sn
  170. @sn.setter
  171. def sn(self, v):
  172. self._sn = struct.pack('!i', v)
  173. @property
  174. def data(self):
  175. return bytearray(2) + self._dt + self._ct + self._ci + self._sn
  176. def decode_packet(data, key, iv):
  177. p = Packet()
  178. s = 1
  179. p.flags = data[0]
  180. if p.flags & 4:
  181. p.ri = int.from_bytes(data[s:s+4], "big")
  182. s += 4
  183. if p.flags & 2:
  184. p.ci = int.from_bytes(data[s:s+2], "big")
  185. iv.ci = p.ci
  186. s += 2
  187. if p.flags & 1:
  188. p.sn = int.from_bytes(data[s:s+4], "big")
  189. iv.sn = p.sn
  190. s += 4
  191. aesgcm = AES.new(key, AES.MODE_GCM, iv.data)
  192. p.payload = aesgcm.decrypt(data[s:])
  193. return p
  194. def encode_packet(payload, key, iv, ri, ci, sn):
  195. flags = 0
  196. header = b''
  197. if ri is not None:
  198. flags = flags | 4
  199. header += struct.pack('!i', ri)
  200. if ci is not None:
  201. flags = flags | 2
  202. header += struct.pack('!h', ci)
  203. if sn is not None:
  204. flags = flags | 1
  205. header += struct.pack('!i', sn)
  206. aesgcm = AES.new(key, AES.MODE_GCM, iv.data)
  207. header = struct.pack('b', flags) + header
  208. aesgcm.update(header)
  209. ep, tag = aesgcm.encrypt_and_digest(payload)
  210. return header + ep + tag[:4]
  211. class TCPHandler(socketserver.BaseRequestHandler):
  212. def handle(self):
  213. self.data = self.request.recv(1024)
  214. ip = self.client_address[0] + str(self.client_address[1])
  215. if not ip in global_clients.keys():
  216. relay_id = int.from_bytes(self.data[3:7], "big")
  217. ENCRYPTION_KEY_FILE = "%s/%s/encryption_key.bin" % (STORAGE_DIR, relay_id)
  218. if relay_id in global_relay.keys():
  219. with open(ENCRYPTION_KEY_FILE, 'wb') as f:
  220. f.write(global_relay[relay_id].key)
  221. elif os.path.isfile(ENCRYPTION_KEY_FILE):
  222. with open(ENCRYPTION_KEY_FILE, 'rb') as f:
  223. global_relay[relay_id] = zo.Relay(f.read())
  224. else:
  225. print('No encryption key for relay ID %s' % relay_id)
  226. return
  227. global_clients[ip] = global_relay[relay_id]
  228. if int.from_bytes(self.data[0:2], "big") != len(self.data) - 2:
  229. print("Wrong packet size")
  230. return
  231. relay = global_clients[ip]
  232. iv = InitializationVector(DeviceType.Relay, ChannelType.TcpClient, relay.tcp_ci, 0)
  233. p = decode_packet(self.data[2:], relay.key, iv)
  234. if p.ci is not None:
  235. relay.tcp_ci = p.ci
  236. relay.tcp_r_sn = 1
  237. relay.tcp_t_sn = 0
  238. iv.ci = p.ci
  239. if len(p.payload) > 1 and p.payload[1] != 0:
  240. print("TCPHandler hello(0) expected, got %s" % p.payload[1])
  241. return
  242. hello = udp_node_msgs_pb2.ClientToServer()
  243. try:
  244. hello.ParseFromString(p.payload[2:-4]) #2 bytes: payload length, 1 byte: =0x1 (TcpClient::sendClientToServer) 1 byte: type; payload; 4 bytes: hash
  245. #type: TcpClient::sayHello(=0x0), TcpClient::sendSubscribeToSegment(=0x1), TcpClient::processSegmentUnsubscription(=0x1)
  246. except Exception as exc:
  247. print('TCPHandler ParseFromString exception: %s' % repr(exc))
  248. return
  249. # send packet containing UDP server (127.0.0.1)
  250. msg = udp_node_msgs_pb2.ServerToClient()
  251. msg.player_id = hello.player_id
  252. msg.world_time = 0
  253. details1 = msg.udp_config.relay_addresses.add()
  254. details1.lb_realm = udp_node_msgs_pb2.ZofflineConstants.RealmID
  255. details1.lb_course = 6 # watopia crowd
  256. details1.ip = zo.server_ip
  257. details1.port = 3022
  258. details2 = msg.udp_config.relay_addresses.add()
  259. details2.lb_realm = 0 #generic load balancing realm
  260. details2.lb_course = 0 #generic load balancing course
  261. details2.ip = zo.server_ip
  262. details2.port = 3022
  263. msg.udp_config.uc_f2 = 10
  264. msg.udp_config.uc_f3 = 30
  265. msg.udp_config.uc_f4 = 3
  266. wdetails1 = msg.udp_config_vod_1.relay_addresses_vod.add()
  267. wdetails1.lb_realm = udp_node_msgs_pb2.ZofflineConstants.RealmID
  268. wdetails1.lb_course = 6 # watopia crowd
  269. wdetails1.relay_addresses.append(details1)
  270. wdetails2 = msg.udp_config_vod_1.relay_addresses_vod.add()
  271. wdetails2.lb_realm = 0 #generic load balancing realm
  272. wdetails2.lb_course = 0 #generic load balancing course
  273. wdetails2.relay_addresses.append(details2)
  274. msg.udp_config_vod_1.port = 3022
  275. payload = msg.SerializeToString()
  276. iv.ct = ChannelType.TcpServer
  277. r = encode_packet(payload, relay.key, iv, None, None, None)
  278. relay.tcp_t_sn += 1
  279. self.request.sendall(struct.pack('!h', len(r)) + r)
  280. player_id = hello.player_id
  281. self.request.settimeout(1) #make recv non-blocking
  282. while True:
  283. self.data = b''
  284. try:
  285. self.data = self.request.recv(1024)
  286. i = 0
  287. while i < len(self.data):
  288. size = int.from_bytes(self.data[i:i+2], "big")
  289. packet = self.data[i:i+size+2]
  290. iv.ct = ChannelType.TcpClient
  291. iv.sn = relay.tcp_r_sn
  292. p = decode_packet(packet[2:], relay.key, iv)
  293. relay.tcp_r_sn += 1
  294. if len(p.payload) > 1 and p.payload[1] == 1:
  295. subscr = udp_node_msgs_pb2.ClientToServer()
  296. try:
  297. subscr.ParseFromString(p.payload[2:-4])
  298. except Exception as exc:
  299. print('TCPHandler ParseFromString exception: %s' % repr(exc))
  300. if subscr.subsSegments:
  301. msg1 = udp_node_msgs_pb2.ServerToClient()
  302. msg1.server_realm = udp_node_msgs_pb2.ZofflineConstants.RealmID
  303. msg1.player_id = subscr.player_id
  304. msg1.world_time = zo.world_time()
  305. msg1.ackSubsSegm.extend(subscr.subsSegments)
  306. payload1 = msg1.SerializeToString()
  307. iv.ct = ChannelType.TcpServer
  308. iv.sn = relay.tcp_t_sn
  309. r = encode_packet(payload1, relay.key, iv, None, None, None)
  310. relay.tcp_t_sn += 1
  311. self.request.sendall(struct.pack('!h', len(r)) + r)
  312. i += size + 2
  313. except:
  314. pass #timeout is ok here
  315. try:
  316. #if ZC need to be registered
  317. if player_id in zo.zc_connect_queue:
  318. zc_params = udp_node_msgs_pb2.ServerToClient()
  319. zc_params.player_id = player_id
  320. zc_params.world_time = 0
  321. zc_params.zc_local_ip = zo.zc_connect_queue[player_id][0]
  322. zc_params.zc_local_port = zo.zc_connect_queue[player_id][1] #simple:21587, secure:21588
  323. if zo.zc_connect_queue[player_id][2] != "None":
  324. zc_params.zc_key = zo.zc_connect_queue[player_id][2]
  325. zc_params.zc_protocol = udp_node_msgs_pb2.IPProtocol.TCP #=2
  326. zc_params_payload = zc_params.SerializeToString()
  327. iv.ct = ChannelType.TcpServer
  328. iv.sn = relay.tcp_t_sn
  329. r = encode_packet(zc_params_payload, relay.key, iv, None, None, None)
  330. relay.tcp_t_sn += 1
  331. self.request.sendall(struct.pack('!h', len(r)) + r)
  332. zo.zc_connect_queue.pop(player_id)
  333. messages = []
  334. #PlayerUpdate
  335. if player_id in zo.player_update_queue and len(zo.player_update_queue[player_id]) > 0:
  336. message = udp_node_msgs_pb2.ServerToClient()
  337. message.server_realm = udp_node_msgs_pb2.ZofflineConstants.RealmID
  338. message.player_id = player_id
  339. message.world_time = zo.world_time()
  340. for player_update_proto in list(zo.player_update_queue[player_id]):
  341. if len(message.SerializeToString()) + len(player_update_proto) > 1400:
  342. new_msg = udp_node_msgs_pb2.ServerToClient()
  343. new_msg.CopyFrom(message)
  344. messages.append(new_msg)
  345. del message.updates[:]
  346. player_update = message.updates.add()
  347. player_update.ParseFromString(player_update_proto)
  348. zo.player_update_queue[player_id].remove(player_update_proto)
  349. messages.append(message)
  350. else: #keepalive
  351. messages.append(msg)
  352. for message in messages:
  353. message_payload = message.SerializeToString()
  354. iv.ct = ChannelType.TcpServer
  355. iv.sn = relay.tcp_t_sn
  356. r = encode_packet(message_payload, relay.key, iv, None, None, None)
  357. relay.tcp_t_sn += 1
  358. self.request.sendall(struct.pack('!h', len(r)) + r)
  359. except Exception as exc:
  360. print('TCPHandler loop exception: %s' % repr(exc))
  361. break
  362. class BotVariables:
  363. profile = None
  364. route = None
  365. date = 0
  366. position = 0
  367. class GhostsVariables:
  368. loaded = False
  369. started = False
  370. rec = None
  371. play = None
  372. last_rec = 0
  373. last_play = 0
  374. last_rt = 0
  375. start_road = 0
  376. start_rt = 0
  377. def get_routes():
  378. with open('%s/data/start_lines.txt' % SCRIPT_DIR) as fd:
  379. return json.load(fd, object_hook=lambda d: {int(k) if k.lstrip('-').isdigit() else k: v for k, v in d.items()})
  380. def get_route_name(state):
  381. routes = get_routes()
  382. if state.route in routes:
  383. return routes[state.route]['name']
  384. return zo.courses_lookup[zo.get_course(state)]
  385. def load_ghosts_folder(folder, ghosts):
  386. if os.path.isdir(folder):
  387. for f in os.listdir(folder):
  388. if f.endswith('.bin'):
  389. with open(os.path.join(folder, f), 'rb') as fd:
  390. g = BotVariables()
  391. g.route = udp_node_msgs_pb2.Ghost()
  392. g.route.ParseFromString(fd.read())
  393. g.date = g.route.states[0].worldTime
  394. ghosts.play.append(g)
  395. def load_ghosts(player_id, state, ghosts):
  396. folder = '%s/%s/ghosts/%s' % (STORAGE_DIR, player_id, zo.get_course(state))
  397. road_folder = '%s/%s' % (folder, zo.road_id(state))
  398. if not zo.is_forward(state): road_folder += '/reverse'
  399. load_ghosts_folder(road_folder, ghosts)
  400. if state.route:
  401. load_ghosts_folder('%s/%s' % (folder, state.route), ghosts)
  402. ghosts.start_road = zo.road_id(state)
  403. ghosts.start_rt = state.roadTime
  404. sl = get_routes()
  405. if state.route in sl:
  406. ghosts.start_road = sl[state.route]['road']
  407. ghosts.start_rt = sl[state.route]['time']
  408. def regroup_ghosts(player_id):
  409. p = online[player_id]
  410. ghosts = global_ghosts[player_id]
  411. if not ghosts.loaded:
  412. ghosts.loaded = True
  413. load_ghosts(player_id, p, ghosts)
  414. if not ghosts.started and ghosts.play:
  415. ghosts.started = True
  416. for g in ghosts.play:
  417. states = [(s.roadTime, s.distance) for s in g.route.states if zo.road_id(s) == zo.road_id(p) and zo.is_forward(s) == zo.is_forward(p)]
  418. if states:
  419. c = min(states, key=lambda x: sum(abs(r - d) for r, d in zip((p.roadTime, p.distance), x)))
  420. g.position = 0
  421. while g.route.states[g.position].roadTime != c[0] or g.route.states[g.position].distance != c[1]:
  422. g.position += 1
  423. if is_ahead(p, g.route.states[g.position].roadTime):
  424. g.position += 1
  425. ghosts.last_play = 0
  426. def load_pace_partners():
  427. for (root, dirs, files) in os.walk(PACE_PARTNERS_DIR):
  428. for d in dirs:
  429. profile = os.path.join(PACE_PARTNERS_DIR, d, 'profile.bin')
  430. route = os.path.join(PACE_PARTNERS_DIR, d, 'route.bin')
  431. if os.path.isfile(profile) and os.path.isfile(route):
  432. with open(profile, 'rb') as fd:
  433. p = profile_pb2.PlayerProfile()
  434. p.ParseFromString(fd.read())
  435. global_pace_partners[p.id] = BotVariables()
  436. pp = global_pace_partners[p.id]
  437. pp.profile = p
  438. with open(route, 'rb') as fd:
  439. pp.route = udp_node_msgs_pb2.Ghost()
  440. pp.route.ParseFromString(fd.read())
  441. pp.position = 0
  442. def play_pace_partners():
  443. while True:
  444. start = time.perf_counter()
  445. for pp_id in global_pace_partners.keys():
  446. pp = global_pace_partners[pp_id]
  447. if pp.position < len(pp.route.states) - 1: pp.position += 1
  448. else: pp.position = 0
  449. pp.route.states[pp.position].id = pp_id
  450. pause = pacer_update_freq - (time.perf_counter() - start)
  451. if pause > 0: time.sleep(pause)
  452. def get_names():
  453. bots_file = '%s/bot.txt' % STORAGE_DIR
  454. if os.path.isfile(bots_file):
  455. with open(bots_file) as f:
  456. return json.load(f)['riders']
  457. with open('%s/data/names.txt' % SCRIPT_DIR) as f:
  458. data = json.load(f)
  459. riders = []
  460. for _ in range(1000):
  461. is_male = bool(random.getrandbits(1))
  462. riders.append({'first_name': random.choice(data['male_first_names']) if is_male else random.choice(data['female_first_names']),
  463. 'last_name': random.choice(data['last_names']), 'is_male': is_male, 'country_code': random.choice(zo.GD['country_codes'])})
  464. return riders
  465. def load_bots():
  466. multiplier = 1
  467. with open(ENABLE_BOTS_FILE) as f:
  468. try:
  469. multiplier = min(int(f.readline().rstrip('\r\n')), 100)
  470. except ValueError:
  471. pass
  472. i = 1
  473. loop_riders = []
  474. for name in os.listdir(STORAGE_DIR):
  475. path = '%s/%s/ghosts' % (STORAGE_DIR, name)
  476. if os.path.isdir(path):
  477. for (root, dirs, files) in os.walk(path):
  478. for f in files:
  479. if f.endswith('.bin'):
  480. positions = []
  481. for n in range(0, multiplier):
  482. p = profile_pb2.PlayerProfile()
  483. p.CopyFrom(zo.random_profile(p))
  484. p.id = i + 1000000 + n * 10000
  485. global_bots[p.id] = BotVariables()
  486. bot = global_bots[p.id]
  487. if n == 0:
  488. bot.route = udp_node_msgs_pb2.Ghost()
  489. with open(os.path.join(root, f), 'rb') as fd:
  490. bot.route.ParseFromString(fd.read())
  491. else:
  492. bot.route = global_bots[i + 1000000].route
  493. if not positions:
  494. positions = list(range(len(bot.route.states)))
  495. random.shuffle(positions)
  496. bot.position = positions.pop()
  497. if not loop_riders:
  498. loop_riders = get_names()
  499. random.shuffle(loop_riders)
  500. rider = loop_riders.pop()
  501. for item in ['first_name', 'last_name', 'is_male', 'country_code', 'ride_jersey', 'bike_frame', 'bike_frame_colour', 'bike_wheel_front', 'bike_wheel_rear', 'ride_helmet_type', 'glasses_type', 'ride_shoes_type', 'ride_socks_type']:
  502. if item in rider:
  503. setattr(p, item, rider[item])
  504. p.hair_type = random.choice(zo.GD['hair_types'])
  505. p.hair_colour = random.randrange(5)
  506. if p.is_male:
  507. p.body_type = random.choice(zo.GD['body_types_male'])
  508. p.facial_hair_type = random.choice(zo.GD['facial_hair_types'])
  509. p.facial_hair_colour = random.randrange(5)
  510. else:
  511. p.body_type = random.choice(zo.GD['body_types_female'])
  512. bot.profile = p
  513. i += 1
  514. def play_bots():
  515. while True:
  516. start = time.perf_counter()
  517. if zo.reload_pacer_bots:
  518. zo.reload_pacer_bots = False
  519. if os.path.isfile(ENABLE_BOTS_FILE):
  520. global_bots.clear()
  521. load_bots()
  522. for bot_id in global_bots.keys():
  523. bot = global_bots[bot_id]
  524. if bot.position < len(bot.route.states) - 1: bot.position += 1
  525. else: bot.position = 0
  526. bot.route.states[bot.position].id = bot_id
  527. pause = bot_update_freq - (time.perf_counter() - start)
  528. if pause > 0: time.sleep(pause)
  529. def remove_inactive():
  530. while True:
  531. for p_id in list(online.keys()):
  532. if zo.world_time() > online[p_id].worldTime + 30000:
  533. zo.save_bookmark(online[p_id], 'Last ' + ('run' if online[p_id].sport == profile_pb2.Sport.RUNNING else 'ride'))
  534. online.pop(p_id)
  535. discord.change_presence(len(online))
  536. if discord.announce:
  537. discord.send_message("Leaving", p_id)
  538. zo.logout_player(p_id)
  539. time.sleep(5)
  540. def is_state_new_for(peer_player_state, player_id):
  541. if not player_id in global_news.keys():
  542. global_news[player_id] = {}
  543. for_news = global_news[player_id]
  544. if peer_player_state.id in for_news.keys():
  545. if for_news[peer_player_state.id] == peer_player_state.worldTime:
  546. return False #already sent
  547. for_news[peer_player_state.id] = peer_player_state.worldTime
  548. return True
  549. def nearby_distance(s1, s2):
  550. if s1 is None or s2 is None:
  551. return False, None
  552. if zo.get_course(s1) == zo.get_course(s2):
  553. dist = math.sqrt((s2.x - s1.x)**2 + (s2.z - s1.z)**2 + (s2.y_altitude - s1.y_altitude)**2)
  554. if dist <= 100000 or zo.road_id(s1) == zo.road_id(s2):
  555. return True, dist
  556. return False, None
  557. def is_ahead(state, roadTime):
  558. if zo.is_forward(state):
  559. if state.roadTime > roadTime and abs(state.roadTime - roadTime) < 500000:
  560. return True
  561. else:
  562. if state.roadTime < roadTime and abs(state.roadTime - roadTime) < 500000:
  563. return True
  564. return False
  565. class UDPHandler(socketserver.BaseRequestHandler):
  566. def handle(self):
  567. data = self.request[0]
  568. socket = self.request[1]
  569. ip = self.client_address[0] + str(self.client_address[1])
  570. if not ip in global_clients.keys():
  571. relay_id = int.from_bytes(data[1:5], "big")
  572. if relay_id in global_relay.keys():
  573. global_clients[ip] = global_relay[relay_id]
  574. else:
  575. return
  576. relay = global_clients[ip]
  577. iv = InitializationVector(DeviceType.Relay, ChannelType.UdpClient, relay.udp_ci, relay.udp_r_sn)
  578. p = decode_packet(data, relay.key, iv)
  579. relay.udp_r_sn += 1
  580. if p.ci is not None:
  581. relay.udp_ci = p.ci
  582. relay.udp_t_sn = 0
  583. iv.ci = p.ci
  584. if p.sn is not None:
  585. relay.udp_r_sn = p.sn
  586. recv = udp_node_msgs_pb2.ClientToServer()
  587. try:
  588. recv.ParseFromString(p.payload[1:-4])
  589. except Exception as exc:
  590. print('UDPHandler ParseFromString exception: %s' % repr(exc))
  591. return
  592. client_address = self.client_address
  593. player_id = recv.player_id
  594. state = recv.state
  595. #Add last updates for player if missing
  596. if not player_id in last_pp_updates.keys():
  597. last_pp_updates[player_id] = 0
  598. if not player_id in last_bot_updates.keys():
  599. last_bot_updates[player_id] = 0
  600. if not player_id in last_bookmark_updates.keys():
  601. last_bookmark_updates[player_id] = 0
  602. #Add bookmarks for player if missing
  603. if not player_id in zo.global_bookmarks.keys():
  604. zo.global_bookmarks[player_id] = {}
  605. bookmarks = zo.global_bookmarks[player_id]
  606. #Update player online state
  607. if state.roadTime:
  608. if player_id in online.keys():
  609. if online[player_id].worldTime > state.worldTime:
  610. return #udp is unordered -> drop old state
  611. online[player_id] = state
  612. elif zo.world_time() < state.worldTime + 10000:
  613. online[player_id] = state
  614. discord.change_presence(len(online))
  615. if discord.announce:
  616. discord.send_message("%s in %s" % (('Running' if state.sport == profile_pb2.Sport.RUNNING else 'Riding'), get_route_name(state)), player_id)
  617. #Add handling of ghosts for player if it's missing
  618. if not player_id in global_ghosts.keys():
  619. global_ghosts[player_id] = GhostsVariables()
  620. global_ghosts[player_id].rec = udp_node_msgs_pb2.Ghost()
  621. global_ghosts[player_id].play = []
  622. ghosts = global_ghosts[player_id]
  623. t = time.monotonic()
  624. if player_id in zo.ghosts_enabled and zo.ghosts_enabled[player_id]:
  625. if state.roadTime and ghosts.last_rt and state.roadTime != ghosts.last_rt:
  626. #Load ghosts when start moving (as of version 1.39 player sometimes enters course 6 road 0 at home screen)
  627. if not ghosts.loaded:
  628. ghosts.loaded = True
  629. load_ghosts(player_id, state, ghosts)
  630. #Save player state as ghost
  631. if t >= ghosts.last_rec + bot_update_freq:
  632. ghosts.rec.states.append(state)
  633. ghosts.last_rec = t
  634. #Start loaded ghosts
  635. if not ghosts.started and ghosts.play and zo.road_id(state) == ghosts.start_road and is_ahead(state, ghosts.start_rt):
  636. regroup_ghosts(player_id)
  637. ghosts.last_rt = state.roadTime
  638. #Set state of player being watched
  639. watching_state = None
  640. if state.watchingRiderId == player_id:
  641. watching_state = state
  642. elif state.watchingRiderId in online.keys():
  643. watching_state = online[state.watchingRiderId]
  644. elif state.watchingRiderId in global_pace_partners.keys():
  645. pp = global_pace_partners[state.watchingRiderId]
  646. watching_state = pp.route.states[pp.position]
  647. elif state.watchingRiderId in global_bots.keys():
  648. bot = global_bots[state.watchingRiderId]
  649. watching_state = bot.route.states[bot.position]
  650. elif state.watchingRiderId in bookmarks.keys():
  651. watching_state = bookmarks[state.watchingRiderId].state
  652. elif state.watchingRiderId > 10000000:
  653. ghost = ghosts.play[math.floor(state.watchingRiderId / 10000000) - 1]
  654. if len(ghost.route.states) > ghost.position:
  655. watching_state = ghost.route.states[ghost.position]
  656. #Check if online players, pace partners, bots and ghosts are nearby
  657. nearby = {}
  658. for p_id in online.keys():
  659. player = online[p_id]
  660. if player.id != player_id and zo.world_time() < player.worldTime + 10000:
  661. is_nearby, distance = nearby_distance(watching_state, player)
  662. if is_nearby and is_state_new_for(player, player_id):
  663. nearby[p_id] = distance
  664. if t >= last_pp_updates[player_id] + pacer_update_freq:
  665. last_pp_updates[player_id] = t
  666. for p_id in global_pace_partners.keys():
  667. pp = global_pace_partners[p_id]
  668. is_nearby, distance = nearby_distance(watching_state, pp.route.states[pp.position])
  669. if is_nearby:
  670. nearby[p_id] = distance
  671. if t >= last_bot_updates[player_id] + bot_update_freq:
  672. last_bot_updates[player_id] = t
  673. for p_id in global_bots.keys():
  674. bot = global_bots[p_id]
  675. is_nearby, distance = nearby_distance(watching_state, bot.route.states[bot.position])
  676. if is_nearby:
  677. nearby[p_id] = distance
  678. if t >= last_bookmark_updates[player_id] + 10:
  679. last_bookmark_updates[player_id] = t
  680. for p_id in bookmarks.keys():
  681. is_nearby, distance = nearby_distance(watching_state, bookmarks[p_id].state)
  682. if is_nearby:
  683. nearby[p_id] = distance
  684. if ghosts.started and t >= ghosts.last_play + bot_update_freq:
  685. ghosts.last_play = t
  686. for i, g in enumerate(ghosts.play):
  687. if len(g.route.states) > g.position:
  688. is_nearby, distance = nearby_distance(watching_state, g.route.states[g.position])
  689. if is_nearby:
  690. nearby[player_id + (i + 1) * 10000000] = distance
  691. g.position += 1
  692. #Send nearby riders states or empty message
  693. messages = []
  694. message = udp_node_msgs_pb2.ServerToClient()
  695. message.server_realm = udp_node_msgs_pb2.ZofflineConstants.RealmID
  696. message.player_id = player_id
  697. message.world_time = zo.world_time()
  698. message.cts_latency = message.world_time - recv.world_time
  699. if len(nearby) > 100:
  700. nearby = dict(sorted(nearby.items(), key=lambda item: item[1]))
  701. nearby = dict(itertools.islice(nearby.items(), 100))
  702. for p_id in nearby:
  703. player = None
  704. if p_id in online.keys():
  705. player = online[p_id]
  706. elif p_id in global_pace_partners.keys():
  707. pp = global_pace_partners[p_id]
  708. player = pp.route.states[pp.position]
  709. elif p_id in global_bots.keys():
  710. bot = global_bots[p_id]
  711. player = bot.route.states[bot.position]
  712. elif p_id in bookmarks.keys():
  713. player = bookmarks[p_id].state
  714. elif p_id > 10000000:
  715. ghost = ghosts.play[math.floor(p_id / 10000000) - 1]
  716. player = ghost.route.states[ghost.position - 1]
  717. player.id = p_id
  718. if player != None:
  719. if not p_id in online.keys():
  720. player.worldTime = message.world_time - simulated_latency
  721. player.groupId = 0 # fix bots in event only routes
  722. if len(message.SerializeToString()) + len(player.SerializeToString()) > 1400:
  723. new_msg = udp_node_msgs_pb2.ServerToClient()
  724. new_msg.CopyFrom(message)
  725. messages.append(new_msg)
  726. del message.states[:]
  727. message.states.append(player)
  728. messages.append(message)
  729. for i, msg in enumerate(messages):
  730. msg.num_msgs = len(messages)
  731. msg.msgnum = i + 1
  732. iv.ct = ChannelType.UdpServer
  733. iv.sn = relay.udp_t_sn
  734. r = encode_packet(msg.SerializeToString(), relay.key, iv, None, None, relay.udp_t_sn)
  735. relay.udp_t_sn += 1
  736. socket.sendto(r, client_address)
  737. if os.path.isdir(PACE_PARTNERS_DIR):
  738. load_pace_partners()
  739. pp = threading.Thread(target=play_pace_partners)
  740. pp.start()
  741. if os.path.isfile(ENABLE_BOTS_FILE):
  742. load_bots()
  743. bot = threading.Thread(target=play_bots)
  744. bot.start()
  745. socketserver.ThreadingTCPServer.allow_reuse_address = True
  746. httpd = socketserver.ThreadingTCPServer(('', 80), CDNHandler)
  747. zoffline_thread = threading.Thread(target=httpd.serve_forever)
  748. zoffline_thread.daemon = True
  749. zoffline_thread.start()
  750. tcpserver = socketserver.ThreadingTCPServer(('', 3025), TCPHandler)
  751. tcpserver_thread = threading.Thread(target=tcpserver.serve_forever)
  752. tcpserver_thread.daemon = True
  753. tcpserver_thread.start()
  754. socketserver.ThreadingUDPServer.allow_reuse_address = True
  755. udpserver = socketserver.ThreadingUDPServer(('', 3024), UDPHandler)
  756. udpserver_thread = threading.Thread(target=udpserver.serve_forever)
  757. udpserver_thread.daemon = True
  758. udpserver_thread.start()
  759. ri = threading.Thread(target=remove_inactive)
  760. ri.start()
  761. if os.path.exists(FAKE_DNS_FILE):
  762. from fake_dns import fake_dns
  763. dns = threading.Thread(target=fake_dns, args=(zo.server_ip,))
  764. dns.start()
  765. zo.run_standalone(online, global_relay, global_pace_partners, global_bots, global_ghosts, regroup_ghosts, discord)