standalone.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  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. PACE_PARTNERS_DIR = '%s/pace_partners' % os.path.dirname(sys.executable)
  26. else:
  27. SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
  28. STORAGE_DIR = "%s/storage" % SCRIPT_DIR
  29. PACE_PARTNERS_DIR = '%s/pace_partners' % SCRIPT_DIR
  30. CDN_DIR = "%s/cdn" % SCRIPT_DIR
  31. CDN_PROXY = os.path.isfile('%s/cdn-proxy.txt' % STORAGE_DIR)
  32. if not CDN_PROXY and not os.path.isfile('%s/disable_proxy.txt' % STORAGE_DIR):
  33. # If CDN proxy is disabled, try to resolve zwift.com using Google public DNS
  34. try:
  35. import dns.resolver
  36. resolver = dns.resolver.Resolver(configure=False)
  37. resolver.nameservers = ['8.8.8.8', '8.8.4.4']
  38. resolver.cache = dns.resolver.Cache()
  39. resolver.resolve('zwift.com')
  40. # If succeeded, patch create_connection to use resolver
  41. from urllib3.util import connection
  42. orig_create_connection = connection.create_connection
  43. def patched_create_connection(address, *args, **kwargs):
  44. host, port = address
  45. answer = resolver.cache.data.get((host, 1, 1))
  46. if not answer:
  47. try:
  48. answer = resolver.resolve(host)
  49. resolver.cache.put((host, 1, 1), answer)
  50. except Exception as exc:
  51. print('dns.resolver: %s' % repr(exc))
  52. if answer:
  53. address = (answer[0].to_text(), port)
  54. return orig_create_connection(address, *args, **kwargs)
  55. connection.create_connection = patched_create_connection
  56. CDN_PROXY = True
  57. except:
  58. pass
  59. FAKE_DNS_FILE = "%s/fake-dns.txt" % STORAGE_DIR
  60. ENABLE_BOTS_FILE = "%s/enable_bots.txt" % STORAGE_DIR
  61. DISCORD_CONFIG_FILE = "%s/discord.cfg" % STORAGE_DIR
  62. if os.path.isfile(DISCORD_CONFIG_FILE):
  63. from discord_bot import DiscordThread
  64. discord = DiscordThread(DISCORD_CONFIG_FILE)
  65. else:
  66. class DummyDiscord():
  67. def send_message(self, msg, sender_id=None):
  68. pass
  69. def change_presence(self, n):
  70. pass
  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 load_ghosts_folder(folder, ghosts):
  378. if os.path.isdir(folder):
  379. for f in os.listdir(folder):
  380. if f.endswith('.bin'):
  381. with open(os.path.join(folder, f), 'rb') as fd:
  382. g = BotVariables()
  383. g.route = udp_node_msgs_pb2.Ghost()
  384. g.route.ParseFromString(fd.read())
  385. g.date = g.route.states[0].worldTime
  386. ghosts.play.append(g)
  387. def load_ghosts(player_id, state, ghosts):
  388. folder = '%s/%s/ghosts/%s' % (STORAGE_DIR, player_id, zo.get_course(state))
  389. road_folder = '%s/%s' % (folder, zo.road_id(state))
  390. if not zo.is_forward(state): road_folder += '/reverse'
  391. load_ghosts_folder(road_folder, ghosts)
  392. if state.route:
  393. load_ghosts_folder('%s/%s' % (folder, state.route), ghosts)
  394. ghosts.start_road = zo.road_id(state)
  395. ghosts.start_rt = state.roadTime
  396. with open('%s/data/start_lines.txt' % SCRIPT_DIR) as fd:
  397. sl = json.load(fd, object_hook=lambda d: {int(k) if k.lstrip('-').isdigit() else k: v for k, v in d.items()})
  398. if state.route in sl:
  399. ghosts.start_road = sl[state.route]['road']
  400. ghosts.start_rt = sl[state.route]['time']
  401. def regroup_ghosts(player_id):
  402. p = online[player_id]
  403. ghosts = global_ghosts[player_id]
  404. if not ghosts.loaded:
  405. ghosts.loaded = True
  406. load_ghosts(player_id, p, ghosts)
  407. if not ghosts.started and ghosts.play:
  408. ghosts.started = True
  409. for g in ghosts.play:
  410. 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)]
  411. if states:
  412. c = min(states, key=lambda x: sum(abs(r - d) for r, d in zip((p.roadTime, p.distance), x)))
  413. g.position = 0
  414. while g.route.states[g.position].roadTime != c[0] or g.route.states[g.position].distance != c[1]:
  415. g.position += 1
  416. if is_ahead(p, g.route.states[g.position].roadTime):
  417. g.position += 1
  418. ghosts.last_play = 0
  419. def load_pace_partners():
  420. for (root, dirs, files) in os.walk(PACE_PARTNERS_DIR):
  421. for d in dirs:
  422. profile = os.path.join(PACE_PARTNERS_DIR, d, 'profile.bin')
  423. route = os.path.join(PACE_PARTNERS_DIR, d, 'route.bin')
  424. if os.path.isfile(profile) and os.path.isfile(route):
  425. with open(profile, 'rb') as fd:
  426. p = profile_pb2.PlayerProfile()
  427. p.ParseFromString(fd.read())
  428. global_pace_partners[p.id] = BotVariables()
  429. pp = global_pace_partners[p.id]
  430. pp.profile = p
  431. with open(route, 'rb') as fd:
  432. pp.route = udp_node_msgs_pb2.Ghost()
  433. pp.route.ParseFromString(fd.read())
  434. pp.position = 0
  435. def play_pace_partners():
  436. while True:
  437. start = time.perf_counter()
  438. for pp_id in global_pace_partners.keys():
  439. pp = global_pace_partners[pp_id]
  440. if pp.position < len(pp.route.states) - 1: pp.position += 1
  441. else: pp.position = 0
  442. pp.route.states[pp.position].id = pp_id
  443. pause = pacer_update_freq - (time.perf_counter() - start)
  444. if pause > 0: time.sleep(pause)
  445. def get_names():
  446. bots_file = '%s/bot.txt' % STORAGE_DIR
  447. if os.path.isfile(bots_file):
  448. with open(bots_file) as f:
  449. return json.load(f)['riders']
  450. with open('%s/data/names.txt' % SCRIPT_DIR) as f:
  451. data = json.load(f)
  452. riders = []
  453. for _ in range(1000):
  454. is_male = bool(random.getrandbits(1))
  455. riders.append({'first_name': random.choice(data['male_first_names']) if is_male else random.choice(data['female_first_names']),
  456. 'last_name': random.choice(data['last_names']), 'is_male': is_male, 'country_code': random.choice(zo.GD['country_codes'])})
  457. return riders
  458. def load_bots():
  459. multiplier = 1
  460. with open(ENABLE_BOTS_FILE) as f:
  461. try:
  462. multiplier = min(int(f.readline().rstrip('\r\n')), 100)
  463. except ValueError:
  464. pass
  465. i = 1
  466. loop_riders = []
  467. for name in os.listdir(STORAGE_DIR):
  468. path = '%s/%s/ghosts' % (STORAGE_DIR, name)
  469. if os.path.isdir(path):
  470. for (root, dirs, files) in os.walk(path):
  471. for f in files:
  472. if f.endswith('.bin'):
  473. positions = []
  474. for n in range(0, multiplier):
  475. p = profile_pb2.PlayerProfile()
  476. p.CopyFrom(zo.random_profile(p))
  477. p.id = i + 1000000 + n * 10000
  478. global_bots[p.id] = BotVariables()
  479. bot = global_bots[p.id]
  480. if n == 0:
  481. bot.route = udp_node_msgs_pb2.Ghost()
  482. with open(os.path.join(root, f), 'rb') as fd:
  483. bot.route.ParseFromString(fd.read())
  484. else:
  485. bot.route = global_bots[i + 1000000].route
  486. if not positions:
  487. positions = list(range(len(bot.route.states)))
  488. random.shuffle(positions)
  489. bot.position = positions.pop()
  490. if not loop_riders:
  491. loop_riders = get_names()
  492. random.shuffle(loop_riders)
  493. rider = loop_riders.pop()
  494. 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']:
  495. if item in rider:
  496. setattr(p, item, rider[item])
  497. p.hair_type = random.choice(zo.GD['hair_types'])
  498. p.hair_colour = random.randrange(5)
  499. if p.is_male:
  500. p.body_type = random.choice(zo.GD['body_types_male'])
  501. p.facial_hair_type = random.choice(zo.GD['facial_hair_types'])
  502. p.facial_hair_colour = random.randrange(5)
  503. else:
  504. p.body_type = random.choice(zo.GD['body_types_female'])
  505. bot.profile = p
  506. i += 1
  507. def play_bots():
  508. while True:
  509. start = time.perf_counter()
  510. if zo.reload_pacer_bots:
  511. zo.reload_pacer_bots = False
  512. if os.path.isfile(ENABLE_BOTS_FILE):
  513. global_bots.clear()
  514. load_bots()
  515. for bot_id in global_bots.keys():
  516. bot = global_bots[bot_id]
  517. if bot.position < len(bot.route.states) - 1: bot.position += 1
  518. else: bot.position = 0
  519. bot.route.states[bot.position].id = bot_id
  520. pause = bot_update_freq - (time.perf_counter() - start)
  521. if pause > 0: time.sleep(pause)
  522. def remove_inactive():
  523. while True:
  524. for p_id in list(online.keys()):
  525. if zo.world_time() > online[p_id].worldTime + 10000:
  526. zo.logout_player(p_id)
  527. time.sleep(1)
  528. def is_state_new_for(peer_player_state, player_id):
  529. if not player_id in global_news.keys():
  530. global_news[player_id] = {}
  531. for_news = global_news[player_id]
  532. if peer_player_state.id in for_news.keys():
  533. if for_news[peer_player_state.id] == peer_player_state.worldTime:
  534. return False #already sent
  535. for_news[peer_player_state.id] = peer_player_state.worldTime
  536. return True
  537. def nearby_distance(s1, s2):
  538. if s1 is None or s2 is None:
  539. return False, None
  540. if zo.get_course(s1) == zo.get_course(s2):
  541. dist = math.sqrt((s2.x - s1.x)**2 + (s2.z - s1.z)**2 + (s2.y_altitude - s1.y_altitude)**2)
  542. if dist <= 100000 or zo.road_id(s1) == zo.road_id(s2):
  543. return True, dist
  544. return False, None
  545. def is_ahead(state, roadTime):
  546. if zo.is_forward(state):
  547. if state.roadTime > roadTime and abs(state.roadTime - roadTime) < 500000:
  548. return True
  549. else:
  550. if state.roadTime < roadTime and abs(state.roadTime - roadTime) < 500000:
  551. return True
  552. return False
  553. class UDPHandler(socketserver.BaseRequestHandler):
  554. def handle(self):
  555. data = self.request[0]
  556. socket = self.request[1]
  557. ip = self.client_address[0] + str(self.client_address[1])
  558. if not ip in global_clients.keys():
  559. relay_id = int.from_bytes(data[1:5], "big")
  560. if relay_id in global_relay.keys():
  561. global_clients[ip] = global_relay[relay_id]
  562. else:
  563. return
  564. relay = global_clients[ip]
  565. iv = InitializationVector(DeviceType.Relay, ChannelType.UdpClient, relay.udp_ci, relay.udp_r_sn)
  566. p = decode_packet(data, relay.key, iv)
  567. relay.udp_r_sn += 1
  568. if p.ci is not None:
  569. relay.udp_ci = p.ci
  570. relay.udp_t_sn = 0
  571. iv.ci = p.ci
  572. if p.sn is not None:
  573. relay.udp_r_sn = p.sn
  574. recv = udp_node_msgs_pb2.ClientToServer()
  575. try:
  576. recv.ParseFromString(p.payload[1:-4])
  577. except Exception as exc:
  578. print('UDPHandler ParseFromString exception: %s' % repr(exc))
  579. return
  580. client_address = self.client_address
  581. player_id = recv.player_id
  582. state = recv.state
  583. #Add last updates for player if missing
  584. if not player_id in last_pp_updates.keys():
  585. last_pp_updates[player_id] = 0
  586. if not player_id in last_bot_updates.keys():
  587. last_bot_updates[player_id] = 0
  588. if not player_id in last_bookmark_updates.keys():
  589. last_bookmark_updates[player_id] = 0
  590. #Add bookmarks for player if missing
  591. if not player_id in zo.global_bookmarks.keys():
  592. zo.global_bookmarks[player_id] = {}
  593. bookmarks = zo.global_bookmarks[player_id]
  594. #Update player online state
  595. if state.roadTime:
  596. if player_id in online.keys():
  597. if online[player_id].worldTime > state.worldTime:
  598. return #udp is unordered -> drop old state
  599. else:
  600. discord.change_presence(len(online) + 1)
  601. online[player_id] = state
  602. #Add handling of ghosts for player if it's missing
  603. if not player_id in global_ghosts.keys():
  604. global_ghosts[player_id] = GhostsVariables()
  605. global_ghosts[player_id].rec = udp_node_msgs_pb2.Ghost()
  606. global_ghosts[player_id].play = []
  607. ghosts = global_ghosts[player_id]
  608. t = time.monotonic()
  609. if player_id in zo.ghosts_enabled and zo.ghosts_enabled[player_id]:
  610. if state.roadTime and ghosts.last_rt and state.roadTime != ghosts.last_rt:
  611. #Load ghosts when start moving (as of version 1.39 player sometimes enters course 6 road 0 at home screen)
  612. if not ghosts.loaded:
  613. ghosts.loaded = True
  614. load_ghosts(player_id, state, ghosts)
  615. #Save player state as ghost
  616. if t >= ghosts.last_rec + bot_update_freq:
  617. ghosts.rec.states.append(state)
  618. ghosts.last_rec = t
  619. #Start loaded ghosts
  620. if not ghosts.started and ghosts.play and zo.road_id(state) == ghosts.start_road and is_ahead(state, ghosts.start_rt):
  621. regroup_ghosts(player_id)
  622. ghosts.last_rt = state.roadTime
  623. #Set state of player being watched
  624. watching_state = None
  625. if state.watchingRiderId == player_id:
  626. watching_state = state
  627. elif state.watchingRiderId in online.keys():
  628. watching_state = online[state.watchingRiderId]
  629. elif state.watchingRiderId in global_pace_partners.keys():
  630. pp = global_pace_partners[state.watchingRiderId]
  631. watching_state = pp.route.states[pp.position]
  632. elif state.watchingRiderId in global_bots.keys():
  633. bot = global_bots[state.watchingRiderId]
  634. watching_state = bot.route.states[bot.position]
  635. elif state.watchingRiderId in bookmarks.keys():
  636. watching_state = bookmarks[state.watchingRiderId].state
  637. elif state.watchingRiderId > 10000000:
  638. ghost = ghosts.play[math.floor(state.watchingRiderId / 10000000) - 1]
  639. if len(ghost.route.states) > ghost.position:
  640. watching_state = ghost.route.states[ghost.position]
  641. #Check if online players, pace partners, bots and ghosts are nearby
  642. nearby = {}
  643. for p_id in online.keys():
  644. player = online[p_id]
  645. if player.id != player_id:
  646. is_nearby, distance = nearby_distance(watching_state, player)
  647. if is_nearby and is_state_new_for(player, player_id):
  648. nearby[p_id] = distance
  649. if t >= last_pp_updates[player_id] + pacer_update_freq:
  650. last_pp_updates[player_id] = t
  651. for p_id in global_pace_partners.keys():
  652. pp = global_pace_partners[p_id]
  653. is_nearby, distance = nearby_distance(watching_state, pp.route.states[pp.position])
  654. if is_nearby:
  655. nearby[p_id] = distance
  656. if t >= last_bot_updates[player_id] + bot_update_freq:
  657. last_bot_updates[player_id] = t
  658. for p_id in global_bots.keys():
  659. bot = global_bots[p_id]
  660. is_nearby, distance = nearby_distance(watching_state, bot.route.states[bot.position])
  661. if is_nearby:
  662. nearby[p_id] = distance
  663. if t >= last_bookmark_updates[player_id] + 10:
  664. last_bookmark_updates[player_id] = t
  665. for p_id in bookmarks.keys():
  666. is_nearby, distance = nearby_distance(watching_state, bookmarks[p_id].state)
  667. if is_nearby:
  668. nearby[p_id] = distance
  669. if ghosts.started and t >= ghosts.last_play + bot_update_freq:
  670. ghosts.last_play = t
  671. for i, g in enumerate(ghosts.play):
  672. if len(g.route.states) > g.position:
  673. is_nearby, distance = nearby_distance(watching_state, g.route.states[g.position])
  674. if is_nearby:
  675. nearby[player_id + (i + 1) * 10000000] = distance
  676. g.position += 1
  677. #Send nearby riders states or empty message
  678. messages = []
  679. message = udp_node_msgs_pb2.ServerToClient()
  680. message.server_realm = udp_node_msgs_pb2.ZofflineConstants.RealmID
  681. message.player_id = player_id
  682. message.world_time = zo.world_time()
  683. message.cts_latency = message.world_time - recv.world_time
  684. if len(nearby) > 100:
  685. nearby = dict(sorted(nearby.items(), key=lambda item: item[1]))
  686. nearby = dict(itertools.islice(nearby.items(), 100))
  687. for p_id in nearby:
  688. player = None
  689. if p_id in online.keys():
  690. player = online[p_id]
  691. elif p_id in global_pace_partners.keys():
  692. pp = global_pace_partners[p_id]
  693. player = pp.route.states[pp.position]
  694. elif p_id in global_bots.keys():
  695. bot = global_bots[p_id]
  696. player = bot.route.states[bot.position]
  697. elif p_id in bookmarks.keys():
  698. player = bookmarks[p_id].state
  699. elif p_id > 10000000:
  700. ghost = ghosts.play[math.floor(p_id / 10000000) - 1]
  701. player = ghost.route.states[ghost.position - 1]
  702. player.id = p_id
  703. if player != None:
  704. if not p_id in online.keys():
  705. player.worldTime = message.world_time - simulated_latency
  706. player.groupId = 0 # fix bots in event only routes
  707. if len(message.SerializeToString()) + len(player.SerializeToString()) > 1400:
  708. new_msg = udp_node_msgs_pb2.ServerToClient()
  709. new_msg.CopyFrom(message)
  710. messages.append(new_msg)
  711. del message.states[:]
  712. message.states.append(player)
  713. messages.append(message)
  714. for i, msg in enumerate(messages):
  715. msg.num_msgs = len(messages)
  716. msg.msgnum = i + 1
  717. iv.ct = ChannelType.UdpServer
  718. iv.sn = relay.udp_t_sn
  719. r = encode_packet(msg.SerializeToString(), relay.key, iv, None, None, relay.udp_t_sn)
  720. relay.udp_t_sn += 1
  721. socket.sendto(r, client_address)
  722. if os.path.isdir(PACE_PARTNERS_DIR):
  723. load_pace_partners()
  724. pp = threading.Thread(target=play_pace_partners)
  725. pp.start()
  726. if os.path.isfile(ENABLE_BOTS_FILE):
  727. load_bots()
  728. bot = threading.Thread(target=play_bots)
  729. bot.start()
  730. socketserver.ThreadingTCPServer.allow_reuse_address = True
  731. httpd = socketserver.ThreadingTCPServer(('', 80), CDNHandler)
  732. zoffline_thread = threading.Thread(target=httpd.serve_forever)
  733. zoffline_thread.daemon = True
  734. zoffline_thread.start()
  735. tcpserver = socketserver.ThreadingTCPServer(('', 3025), TCPHandler)
  736. tcpserver_thread = threading.Thread(target=tcpserver.serve_forever)
  737. tcpserver_thread.daemon = True
  738. tcpserver_thread.start()
  739. socketserver.ThreadingUDPServer.allow_reuse_address = True
  740. udpserver = socketserver.ThreadingUDPServer(('', 3024), UDPHandler)
  741. udpserver_thread = threading.Thread(target=udpserver.serve_forever)
  742. udpserver_thread.daemon = True
  743. udpserver_thread.start()
  744. ri = threading.Thread(target=remove_inactive)
  745. ri.start()
  746. if os.path.exists(FAKE_DNS_FILE):
  747. from fake_dns import fake_dns
  748. dns = threading.Thread(target=fake_dns, args=(zo.server_ip,))
  749. dns.start()
  750. zo.run_standalone(online, global_relay, global_pace_partners, global_bots, global_ghosts, regroup_ghosts, discord)