Merge branch 'zoffline:master' into master

This commit is contained in:
daktak
2024-01-30 16:11:20 +11:00
committed by GitHub
29 changed files with 1733 additions and 1545 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
import os
import sys
import csv
sys.path.append(os.path.join(sys.path[0], 'protobuf')) # otherwise import in .proto does not work
sys.path.insert(0, '../protobuf')
import profile_pb2
import udp_node_msgs_pb2
+1 -1
View File
@@ -31,7 +31,7 @@ with open('MapSchedule_v2.xml', 'w') as f:
f.write(dom.toprettyxml())
CLIMBS = [str(x) for x in range(10000, 10020)]
CLIMBS = [str(x) for x in range(10000, 10022)]
dom = minidom.parseString('<PortalRoads><PortalRoadSchedule><appointments></appointments><VERSION>1</VERSION></PortalRoadSchedule></PortalRoads>')
appts = dom.getElementsByTagName('appointments')[0]
+130
View File
@@ -0,0 +1,130 @@
#!/usr/bin/env python
import json
import os
import requests
import sys
import getpass
def post_credentials(session, username, password):
# Credentials POSTing and tokens retrieval
# POST https://secure.zwift.com/auth/realms/zwift/tokens/access/codes
try:
response = session.post(
url="https://secure.zwift.com/auth/realms/zwift/protocol/openid-connect/token",
headers={
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Connection": "keep-alive",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "secure.zwift.com",
"User-Agent": "Zwift/1.5 (iPhone; iOS 9.0.2; Scale/2.00)",
"Accept-Language": "en-US;q=1",
},
data={
"client_id": "Zwift_Mobile_Link",
"username": username,
"password": password,
"grant_type": "password",
},
allow_redirects=False,
verify=True,
)
if response.status_code != 200:
print('Response HTTP Status Code: {status_code}'.format(
status_code=response.status_code))
print('Response HTTP Response Body: {content}'.format(
content=response.content))
json_dict = json.loads(response.content)
return (json_dict["access_token"], json_dict["refresh_token"], json_dict["expires_in"])
except requests.exceptions.RequestException as e:
print('HTTP Request failed: %s' % e)
except KeyError as e:
print('Invalid uname and/or password')
exit(-1)
def get_game_info(session, access_token):
try:
response = session.get(
url="https://us-or-rly101.zwift.com/api/game_info",
headers={
"Accept": "*/*",
"Connection": "keep-alive",
"Host": "us-or-rly101.zwift.com",
"User-Agent": "Zwift/115 CFNetwork/758.0.2 Darwin/15.0.0",
"Authorization": "Bearer %s" % access_token,
"Accept-Language": "en-us",
"Zwift-Api-Version": "2.6"
},
verify=True,
)
if response.status_code != 200:
print('Response HTTP Status Code: {status_code}'.format(
status_code=response.status_code))
return response.content
except requests.exceptions.RequestException as e:
print('HTTP Request failed: %s' % e)
def logout(session, refresh_token):
# Logout
# POST https://secure.zwift.com/auth/realms/zwift/tokens/logout
try:
response = session.post(
url="https://secure.zwift.com/auth/realms/zwift/tokens/logout",
headers={
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Connection": "keep-alive",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "secure.zwift.com",
"User-Agent": "Zwift/1.5 (iPhone; iOS 9.0.2; Scale/2.00)",
"Accept-Language": "en-US;q=1",
},
data={
"client_id": "Zwift_Mobile_Link",
"refresh_token": refresh_token,
},
verify=True,
)
if response.status_code != 204:
print('Response HTTP Status Code: {status_code}'.format(
status_code=response.status_code))
print('Response HTTP Response Body: {content}'.format(
content=response.content))
except requests.exceptions.RequestException as e:
print('HTTP Request failed: %s' % e)
def main(argv):
username = input("Enter Zwift login (e-mail): ")
if not sys.stdin.isatty(): # This terminal cannot support input without displaying text
print(f'*WARNING* The current shell ({os.name}) cannot support hidden text entry.')
print(f'Your password entry WILL BE VISIBLE.')
print(f'If you are running a bash shell under windows, try executing this program via winpty:')
print(f'>winpty python {argv[0]}')
password = input("Enter password (will be shown):")
else:
password = getpass.getpass("Enter password: ")
session = requests.session()
access_token, refresh_token, expired_in = post_credentials(session, username, password)
game_info = get_game_info(session, access_token).decode('utf-8')
with open('../game_info.txt', 'wb') as f:
f.write(game_info.encode('utf-8-sig'))
logout(session, refresh_token)
if __name__ == '__main__':
try:
main(sys.argv)
except KeyboardInterrupt:
pass
except SystemExit as se:
print("ERROR:", se)
+59
View File
@@ -0,0 +1,59 @@
import os
import time
import math
import signal
import threading
import xml.etree.ElementTree as ET
from urllib3 import PoolManager
from binascii import crc32
def sigint_handler(num, frame):
os._exit(0)
signal.signal(signal.SIGINT, sigint_handler)
def download(files, folder):
global downloaded
manager = PoolManager()
for file in files:
path = file.find('path').text
length = int(file.find('length').text)
checksum = int(file.find('checksum').text) % (1 << 32)
file_name = os.path.join(local_path, folder, path.replace('\\', os.sep))
dir_name = os.path.dirname(file_name)
if not os.path.isdir(dir_name):
os.makedirs(dir_name)
while not os.path.isfile(file_name) or os.path.getsize(file_name) != length or (crc32(open(file_name, 'rb').read()) != checksum and checksum != 4294967295):
open(file_name, 'wb').write(manager.request('GET', '%s%s/%s' % (base_url, folder, path.replace('\\', '/'))).data)
downloaded += 1
base_url = 'http://cdn.zwift.com/gameassets/Zwift_Updates_Root/'
local_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'cdn', 'gameassets', 'Zwift_Updates_Root')
for file in ['Zwift_ver_cur.xml', 'ZwiftMac_ver_cur.xml']:
tree = ET.parse(os.path.join(local_path, file))
root = tree.getroot()
manifest = root.get('manifest')
manifest_checksum = int(root.get('manifest_checksum')) % (1 << 32)
manifest_file = os.path.join(local_path, manifest)
while not os.path.isfile(manifest_file) or crc32(open(manifest_file, 'rb').read()) != manifest_checksum:
open(manifest_file, 'wb').write(PoolManager().request('GET', base_url + manifest).data)
tree = ET.parse(manifest_file)
root = tree.getroot()
folder = root.get('folder')
all_files = list(root.iter('file'))
total = len(all_files)
downloaded = 0
threads = 5
c = math.ceil(total / threads)
for i in range(0, threads):
files = all_files[i * c:i * c + c]
thread = threading.Thread(target=download, args=(files, folder))
thread.start()
print("Downloading files from %s" % manifest)
while True:
time.sleep(1)
completed = 50 * downloaded // total
print('\r[%s] %s%% (%s of %s)' % ('#' * completed + '.' * (50 - completed), round(100 * downloaded / total, 1), downloaded, total), end='', flush=True)
if downloaded == total:
break
print()
+35 -21
View File
@@ -1,31 +1,45 @@
# Get start lines from files Zwift\assets\Worlds\world*\data_1.wad\Worlds\world*\routes\routes*.xml
import os
import xml.etree.ElementTree as ET
import re
import csv
import subprocess
worlds = 'C:\\Program Files (x86)\\Zwift\\assets\\Worlds'
world_names = {
'1': 'Watopia',
'2': 'Richmond',
'3': 'London',
'4': 'New York',
'5': 'Innsbruck',
'6': 'Bologna',
'7': 'Yorkshire',
'8': 'Crit City',
'9': 'Makuri Islands',
'10': 'France',
'11': 'Paris',
'12': 'Gravel Mountain',
'13': 'Scotland'
}
data = []
for directory in os.listdir('.'):
if not os.path.isdir(directory):
continue
for file in os.listdir(directory):
if not file.endswith('.xml'):
continue
with open(os.path.join(directory, file)) as f:
xml = f.read()
tree = ET.fromstring(re.sub(r"(<\?xml[^>]+\?>)", r"\1<root>", xml) + "</root>")
route = tree.find('route')
name = route.get('name').strip()
nameHash = int.from_bytes(int(route.get('nameHash')).to_bytes(4, 'little'), 'little', signed=True)
checkpoints = list(tree.find('highrescheckpoint').iter('entry'))
startRoad = checkpoints[0].get('road')
startTime = int(float(checkpoints[0].get('time')) * 1000000 + 5000)
data.append([nameHash, startRoad, startTime, directory, name])
for directory in os.listdir(worlds):
world = directory[5:]
if os.path.isdir(os.path.join(worlds, directory)) and world in world_names:
subprocess.run(['wad_unpack.exe', os.path.join(worlds, directory, 'data_1.wad')])
routes = os.path.join('Worlds', directory, 'routes')
for file in os.listdir(routes):
with open(os.path.join(routes, file)) as f:
xml = f.read()
tree = ET.fromstring(re.sub(r"(<\?xml[^>]+\?>)", r"\1<root>", xml) + "</root>")
route = tree.find('route')
name = route.get('name').strip()
nameHash = int.from_bytes(int(route.get('nameHash')).to_bytes(4, 'little'), 'little', signed=True)
checkpoints = list(tree.find('highrescheckpoint').iter('entry'))
startRoad = checkpoints[0].get('road')
startTime = int(float(checkpoints[0].get('time')) * 1000000 + 5000)
data.append([nameHash, startRoad, startTime, world_names[world], name])
with open('start_lines.csv', 'w', newline='') as f:
writer = csv.writer(f)
+12
View File
@@ -0,0 +1,12 @@
import json
import sys
sys.path.insert(0, '../protobuf')
import login_pb2
from google.protobuf.json_format import MessageToDict
with open("login", "rb") as f:
login = login_pb2.LoginResponse()
login.ParseFromString(f.read())
with open('../economy_config.txt', 'w') as f:
json.dump(MessageToDict(login, preserving_proto_field_name=True)['economy_config'], f)
+1 -1
View File
@@ -31,7 +31,7 @@ import json
import os
import requests
import sys
sys.path.append(os.path.join(sys.path[0], 'protobuf')) # otherwise import in .proto does not work
sys.path.insert(0, '../protobuf')
import activity_pb2
import profile_pb2
+22
View File
@@ -0,0 +1,22 @@
import json
import sys
sys.path.insert(0, '../protobuf')
import variants_pb2
from google.protobuf.json_format import MessageToDict
with open("variant", "rb") as f:
variants = variants_pb2.FeatureResponse()
variants.ParseFromString(f.read())
keep = ['zwift_launcher_']
with open("../variants.txt") as f:
vs = [d for d in json.load(f)['variants'] if any(d['name'].startswith(s) for s in keep)]
for v in MessageToDict(variants)['variants']:
if 'fields' in v['values']:
v['values']['fields'] = dict(sorted(v['values']['fields'].items()))
vs.append(v)
with open("../variants.txt", "w") as f:
json.dump({'variants': sorted(vs, key=lambda x: x['name'])}, f, indent=2)