From 9467ddc5dd7d3b2d5aded2f5800f57e84f8e456d Mon Sep 17 00:00:00 2001 From: oldnapalm <38410858+oldnapalm@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:29:29 -0300 Subject: [PATCH] Download economy config --- LEIAME.md | 2 +- README.md | 2 +- get_profile.py | 39 ++++++++++++++++++++++++++++++++++ get_profile.spec | 2 +- protobuf/Makefile | 1 + protobuf/login.proto | 49 +++++++++++++++++++++++++++++++++++++++++++ protobuf/login_pb2.py | 38 +++++++++++++++++++++++++++++++++ protobuf/make.bat | 1 + 8 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 protobuf/login.proto create mode 100644 protobuf/login_pb2.py diff --git a/LEIAME.md b/LEIAME.md index cdc1e0c..063bc8f 100644 --- a/LEIAME.md +++ b/LEIAME.md @@ -30,4 +30,4 @@ Entre no grupo **zoffline** no Strava https://www.strava.com/clubs/zoffline * Desative o zwift-offline (veja [Ativando/Desativando o zwift-offline](https://github.com/oldnapalm/zoffline-helper/blob/master/LEIAME.md#ativandodesativando-o-zwift-offline) acima). * Execute o **get_profile**, digite seu login do Zwift (e-mail) e sua senha. -* Serão criados os arquivos ``profile.bin`` e ``achievements.bin``, mova esses arquivos para a pasta ``storage\1``. +* Serão criados os arquivos ``profile.bin``, ``achievements.bin`` e ``economy_config.txt``, mova esses arquivos para a pasta ``storage\1``. diff --git a/README.md b/README.md index 061526b..607f79e 100644 --- a/README.md +++ b/README.md @@ -30,4 +30,4 @@ Join the **zoffline** Strava group https://www.strava.com/clubs/zoffline * Disable zwift-offline (see [Enabling/Disabling zwift-offline](https://github.com/oldnapalm/zoffline-helper#enablingdisabling-zwift-offline) above). * Run **get_profile**, type your Zwift login (e-mail) and password. -* Move the resulting ``profile.bin`` and ``achievements.bin`` into the ``storage\1`` directory. +* Move the resulting ``profile.bin``, ``achievements.bin`` and ``economy_config.txt`` into the ``storage\1`` directory. diff --git a/get_profile.py b/get_profile.py index d0217d7..a3438bf 100644 --- a/get_profile.py +++ b/get_profile.py @@ -31,6 +31,10 @@ import os import requests import sys +sys.path.append(os.path.join(sys.path[0], 'protobuf')) # otherwise import in .proto does not work +import login_pb2 +from google.protobuf.json_format import MessageToDict +from random import randbytes if getattr(sys, 'frozen', False): @@ -118,6 +122,33 @@ def query(session, access_token, route): print('HTTP Request failed: %s' % e) +def api_login(session, access_token, login_request): + try: + response = session.post( + url="https://us-or-rly101.zwift.com/api/users/login", + headers={ + "Content-Type": "application/x-protobuf-lite", + "Accept": "application/x-protobuf-lite", + "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", + }, + data=login_request.SerializeToString(), + verify=args.verifyCert, + ) + + if args.verbose: + 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 @@ -203,6 +234,14 @@ def main(argv): achievements = query(session, access_token, "achievement/loadPlayerAchievements") with open('%s/achievements.bin' % SCRIPT_DIR, 'wb') as f: f.write(achievements) + login_request = login_pb2.LoginRequest() + login_request.key = randbytes(16) + login_response = login_pb2.LoginResponse() + login_response.ParseFromString(api_login(session, access_token, login_request)) + login_response_dict = MessageToDict(login_response, preserving_proto_field_name=True) + if 'economy_config' in login_response_dict: + with open('%s/economy_config.txt' % SCRIPT_DIR, 'w') as f: + json.dump(login_response_dict['economy_config'], f, indent=2) logout(session, refresh_token) diff --git a/get_profile.spec b/get_profile.spec index ece5b16..a70336d 100644 --- a/get_profile.spec +++ b/get_profile.spec @@ -6,7 +6,7 @@ import sys sys.modules['FixTk'] = None a = Analysis(['get_profile.py'], - pathex=[], + pathex=['protobuf'], binaries=[], datas=[], hiddenimports=[], diff --git a/protobuf/Makefile b/protobuf/Makefile index bf45798..5693e22 100644 --- a/protobuf/Makefile +++ b/protobuf/Makefile @@ -1,5 +1,6 @@ all: protoc --python_out=. activity.proto + protoc --python_out=. login.proto protoc --python_out=. profile.proto protoc --python_out=. per-session-info.proto protoc --python_out=. udp-node-msgs.proto diff --git a/protobuf/login.proto b/protobuf/login.proto new file mode 100644 index 0000000..91bc4d3 --- /dev/null +++ b/protobuf/login.proto @@ -0,0 +1,49 @@ +syntax = "proto2"; +/* XXX: This is a first approximation of login response. Not looked into or verified. */ +import "per-session-info.proto"; + +message LoginResponse { + required string session_state = 1; + required PerSessionInfo info = 2; + optional uint32 relay_session_id = 3; + optional uint32 expiration = 4; // minutes + optional EconomyConfig economy_config = 5; +} + +message LoginRequest { + optional AnalyticsEventProperties properties = 1; + required bytes key = 2; +} + +message AnalyticsEventProperty { + required string f1 = 1; + required string f2 = 2; +} + +message AnalyticsEventProperties { + repeated AnalyticsEventProperty property = 2; +} + +message RelaySessionRefreshResponse { + required uint32 relay_session_id = 1; + required uint32 expiration = 2; // minutes +} + +message EconomyConfig { + repeated Level cycling_levels = 1; + repeated Level running_levels = 2; + required uint32 f3 = 3; + required uint32 f4 = 4; + required uint32 f5 = 5; + optional uint32 transition_start = 6; + optional uint32 transition_end = 7; +} + +message Level { + required uint32 level = 1; + required uint32 xp = 2; + required uint32 drops = 3; + optional uint32 f4 = 4; + optional string entitlement_1 = 5; + optional string entitlement_2 = 6; +} diff --git a/protobuf/login_pb2.py b/protobuf/login_pb2.py new file mode 100644 index 0000000..5b4cce8 --- /dev/null +++ b/protobuf/login_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: login.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +import per_session_info_pb2 as per__session__info__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0blogin.proto\x1a\x16per-session-info.proto\"\x9b\x01\n\rLoginResponse\x12\x15\n\rsession_state\x18\x01 \x02(\t\x12\x1d\n\x04info\x18\x02 \x02(\x0b\x32\x0f.PerSessionInfo\x12\x18\n\x10relay_session_id\x18\x03 \x01(\r\x12\x12\n\nexpiration\x18\x04 \x01(\r\x12&\n\x0e\x65\x63onomy_config\x18\x05 \x01(\x0b\x32\x0e.EconomyConfig\"J\n\x0cLoginRequest\x12-\n\nproperties\x18\x01 \x01(\x0b\x32\x19.AnalyticsEventProperties\x12\x0b\n\x03key\x18\x02 \x02(\x0c\"0\n\x16\x41nalyticsEventProperty\x12\n\n\x02\x66\x31\x18\x01 \x02(\t\x12\n\n\x02\x66\x32\x18\x02 \x02(\t\"E\n\x18\x41nalyticsEventProperties\x12)\n\x08property\x18\x02 \x03(\x0b\x32\x17.AnalyticsEventProperty\"K\n\x1bRelaySessionRefreshResponse\x12\x18\n\x10relay_session_id\x18\x01 \x02(\r\x12\x12\n\nexpiration\x18\x02 \x02(\r\"\xa5\x01\n\rEconomyConfig\x12\x1e\n\x0e\x63ycling_levels\x18\x01 \x03(\x0b\x32\x06.Level\x12\x1e\n\x0erunning_levels\x18\x02 \x03(\x0b\x32\x06.Level\x12\n\n\x02\x66\x33\x18\x03 \x02(\r\x12\n\n\x02\x66\x34\x18\x04 \x02(\r\x12\n\n\x02\x66\x35\x18\x05 \x02(\r\x12\x18\n\x10transition_start\x18\x06 \x01(\r\x12\x16\n\x0etransition_end\x18\x07 \x01(\r\"k\n\x05Level\x12\r\n\x05level\x18\x01 \x02(\r\x12\n\n\x02xp\x18\x02 \x02(\r\x12\r\n\x05\x64rops\x18\x03 \x02(\r\x12\n\n\x02\x66\x34\x18\x04 \x01(\r\x12\x15\n\rentitlement_1\x18\x05 \x01(\t\x12\x15\n\rentitlement_2\x18\x06 \x01(\t') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'login_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _LOGINRESPONSE._serialized_start=40 + _LOGINRESPONSE._serialized_end=195 + _LOGINREQUEST._serialized_start=197 + _LOGINREQUEST._serialized_end=271 + _ANALYTICSEVENTPROPERTY._serialized_start=273 + _ANALYTICSEVENTPROPERTY._serialized_end=321 + _ANALYTICSEVENTPROPERTIES._serialized_start=323 + _ANALYTICSEVENTPROPERTIES._serialized_end=392 + _RELAYSESSIONREFRESHRESPONSE._serialized_start=394 + _RELAYSESSIONREFRESHRESPONSE._serialized_end=469 + _ECONOMYCONFIG._serialized_start=472 + _ECONOMYCONFIG._serialized_end=637 + _LEVEL._serialized_start=639 + _LEVEL._serialized_end=746 +# @@protoc_insertion_point(module_scope) diff --git a/protobuf/make.bat b/protobuf/make.bat index ddaeea7..f8839f0 100644 --- a/protobuf/make.bat +++ b/protobuf/make.bat @@ -1,5 +1,6 @@ del *_pb2.py *_pb2.pyc protoc --python_out=. activity.proto +protoc --python_out=. login.proto protoc --python_out=. profile.proto protoc --python_out=. per-session-info.proto protoc --python_out=. udp-node-msgs.proto