standalone.py 33 KB

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