mirror of
https://github.com/zoffline/zwift-offline.git
synced 2025-12-12 15:49:40 -08:00
Use name from db if profile doesn't exist
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,6 +5,3 @@ __pycache__/
|
||||
build/
|
||||
dist/
|
||||
logs/
|
||||
.vscode
|
||||
/protobuf/profile_ed.proto
|
||||
/protobuf/protoc.exe
|
||||
|
||||
@@ -1,300 +0,0 @@
|
||||
meta:
|
||||
id: zwift_profile
|
||||
application: Zwift
|
||||
title: Zwift profile protobuf
|
||||
endian: le
|
||||
imports:
|
||||
- /common/vlq_base128_le
|
||||
seq:
|
||||
- id: pairs
|
||||
type: pair
|
||||
repeat: eos
|
||||
types:
|
||||
pair:
|
||||
seq:
|
||||
- id: key
|
||||
type: vlq_base128_le
|
||||
- id: value
|
||||
type:
|
||||
switch-on: wire_type
|
||||
cases:
|
||||
'wire_types::varint': vlq_base128_le_with_crc32
|
||||
'wire_types::len_delimited': delimited_bytes
|
||||
'wire_types::bit_64': u8le
|
||||
'wire_types::bit_32': may_be_crc32
|
||||
instances:
|
||||
wire_type:
|
||||
value: 'key.value & 0b111'
|
||||
enum: wire_types
|
||||
field_tag:
|
||||
value: 'key.value >> 3'
|
||||
enums:
|
||||
wire_types:
|
||||
0: varint
|
||||
1: bit_64
|
||||
2: len_delimited
|
||||
3: group_start
|
||||
4: group_end
|
||||
5: bit_32
|
||||
may_be_crc32:
|
||||
seq:
|
||||
- id: val
|
||||
type: u4
|
||||
enum: e_str_crc32
|
||||
vlq_base128_le_with_crc32:
|
||||
seq:
|
||||
- id: groups
|
||||
type: group
|
||||
repeat: until
|
||||
repeat-until: not _.has_next
|
||||
types:
|
||||
group:
|
||||
doc: |
|
||||
One byte group, clearly divided into 7-bit "value" chunk and 1-bit "continuation" flag.
|
||||
seq:
|
||||
- id: b
|
||||
type: u1
|
||||
instances:
|
||||
has_next:
|
||||
value: (b & 0b1000_0000) != 0
|
||||
doc: If true, then we have more bytes to read
|
||||
value:
|
||||
value: b & 0b0111_1111
|
||||
doc: The 7-bit (base128) numeric value chunk of this group
|
||||
instances:
|
||||
len:
|
||||
value: groups.size
|
||||
value:
|
||||
value: >-
|
||||
groups[0].value
|
||||
+ (len >= 2 ? (groups[1].value << 7) : 0)
|
||||
+ (len >= 3 ? (groups[2].value << 14) : 0)
|
||||
+ (len >= 4 ? (groups[3].value << 21) : 0)
|
||||
+ (len >= 5 ? (groups[4].value << 28) : 0)
|
||||
+ (len >= 6 ? (groups[5].value << 35) : 0)
|
||||
+ (len >= 7 ? (groups[6].value << 42) : 0)
|
||||
+ (len >= 8 ? (groups[7].value << 49) : 0)
|
||||
doc: Resulting value as normal integer
|
||||
enum: e_str_crc32
|
||||
delimited_bytes:
|
||||
seq:
|
||||
- id: len
|
||||
type: vlq_base128_le
|
||||
- id: body
|
||||
size: len.value
|
||||
type:
|
||||
switch-on: _parent.field_tag
|
||||
cases:
|
||||
33: f33
|
||||
f33:
|
||||
seq:
|
||||
- id: game_saves
|
||||
type: game_save
|
||||
repeat: eos #until
|
||||
#repeat-until: _.id == game_save::id::end_mark5
|
||||
game_save:
|
||||
seq:
|
||||
- id: id
|
||||
type: u4
|
||||
enum: id
|
||||
- id: length
|
||||
type: u4
|
||||
- id: data
|
||||
size: length - 8
|
||||
type:
|
||||
switch-on: id
|
||||
cases:
|
||||
'id::tracking16_var': t_tracking
|
||||
'id::my_garage9_50': t_garage
|
||||
'id::achiev15_var': t_achiev
|
||||
'id::challenge6_var': t_challenge
|
||||
enums:
|
||||
id:
|
||||
0x10000001: accessories1_100 #2048bit=0x100 bytes, for example "Humans/Accessories/Gloves/ZwiftKOMGloves01.xml" maps to bit 318
|
||||
0x1000000B: accessories1r_100 #=save_type1_100 on read, not saved
|
||||
|
||||
0x10000002: achiev_badges2_40 #512bit(bagdes deprecated by game_1_19_achievement_service_src_of_truth) = 0x40
|
||||
0x1000000C: achiev_badges2r_40 #=achiev_badges2_40 on read, not saved
|
||||
|
||||
0x10000006: challenge6_var #challenges: ChallengeManager::HandleSavedata
|
||||
|
||||
0x10000009: my_garage9_50 #garage items
|
||||
|
||||
0x10000007: save_type7_40 #512bit = 0x40 (all 0) - reserved for future?
|
||||
0x1000000D: save_type7r_40 #=save_type7_040on read, not saved
|
||||
|
||||
0x1000000F: achiev15_var #AchievementManager chunks - badges in progress
|
||||
|
||||
0x10000010: tracking16_var #TrackingData
|
||||
|
||||
0x10000011: old_goals17r_var #old goals data format
|
||||
|
||||
0x10000005: end_mark5 #mark end of all savings, no data
|
||||
t_challenge:
|
||||
seq:
|
||||
- id: cur_challenge_id
|
||||
type: u4
|
||||
enum: e_str_crc32
|
||||
- id: length
|
||||
type: u4
|
||||
- id: items
|
||||
type: t_challenge_item
|
||||
repeat: eos
|
||||
types:
|
||||
t_challenge_item:
|
||||
seq:
|
||||
- id: dummy0a
|
||||
type: u4
|
||||
- id: id
|
||||
type: u4
|
||||
enum: e_chal_hash
|
||||
- id: total #total counter, in meters (lazy updated)
|
||||
type: f4
|
||||
- id: accumulated #counted when challenge selected, in meters (lazy updated)
|
||||
type: f4
|
||||
- id: selected
|
||||
type: u1
|
||||
- id: dummy0b
|
||||
type: u1
|
||||
- id: garbage #or not?
|
||||
type: u1
|
||||
- id: dummy0c
|
||||
type: u1
|
||||
t_achiev:
|
||||
seq:
|
||||
- id: items
|
||||
type: t_achiev_item
|
||||
repeat: eos
|
||||
types:
|
||||
t_achiev_item:
|
||||
seq:
|
||||
- id: id
|
||||
type: u4
|
||||
enum: id
|
||||
- id: data
|
||||
type:
|
||||
switch-on: id
|
||||
cases:
|
||||
'id::fanview': t_fanview
|
||||
_: t_rideon
|
||||
types:
|
||||
t_fanview:
|
||||
seq:
|
||||
- id: length
|
||||
type: u4
|
||||
- id: data
|
||||
size: length - 8 #time?
|
||||
t_rideon:
|
||||
seq:
|
||||
- id: length
|
||||
type: u4
|
||||
- id: unknown
|
||||
type: u4
|
||||
- id: given
|
||||
type: u4
|
||||
- id: ext_data
|
||||
size: length - 16
|
||||
enums:
|
||||
id:
|
||||
0x1e: give_ride_on1
|
||||
0x1f: give_ride_on2
|
||||
0x20: give_ride_on3
|
||||
0x1c: fanview
|
||||
t_garage:
|
||||
seq:
|
||||
- id: items
|
||||
type: u4
|
||||
enum: e_str_crc32
|
||||
repeat: eos
|
||||
t_tracking:
|
||||
seq:
|
||||
- id: count
|
||||
type: u4
|
||||
- id: items
|
||||
type: t_track_item
|
||||
repeat: expr
|
||||
repeat-expr: count
|
||||
t_track_item:
|
||||
seq:
|
||||
- id: id
|
||||
type: u4
|
||||
enum: e_str_crc32
|
||||
- id: vt
|
||||
type: u4
|
||||
enum: e_vt_id
|
||||
- id: u4_val
|
||||
type: u4
|
||||
- id: f4_val
|
||||
type: f4
|
||||
enums:
|
||||
e_chal_hash:
|
||||
1231: climb_mt_everest
|
||||
1234153: ride_california
|
||||
15313453: tour_italy
|
||||
1234153: challenge_unk
|
||||
e_vt_id:
|
||||
1: float
|
||||
2: int
|
||||
3: byte
|
||||
e_str_crc32:
|
||||
0xe1bb3ffa: wh_front_mnt # "...\\ZWIFTMOUNTAIN\\FRONT.XML" = -507822086
|
||||
0x6b1396db: wh_rear_mnt # "...\\ZWIFTMOUNTAIN\\REAR.XML" = 1796445915
|
||||
0x7d8c357d: fr_carbon # "...\\ZWIFT_CARBON\\CONFIG.XML" = 2106340733
|
||||
0x0563E97A: jers_orange # "...\\ORIGINALS_ZWIFTSTANDARDORANGE.XML" = 90433914
|
||||
0x37bbc526: helm01_zwift # "...\\ZWIFTHELMET01.XML" = 935052582
|
||||
0x4dd46f4d: hair01_male # "...\\MALEHAIR01" = 1305767757
|
||||
0x6E292D88: wh_camp_buhr # "BIKES\\WHEELS\\CAMPAGNOLO_BORA_ULTRA\\CAMPAGNOLO_BORA_ULTRA_HIGH_REAR.GDE" = 1848192392
|
||||
0xDD4C7F63: acc_cj_op4 # "HUMANS\\ACCESSORIES\\CYCLINGJERSEYS\\ORIGINALS_PLAIN_04.XML" = -582189213
|
||||
0x9e2c6328: pool_size # "PoolSize"
|
||||
0xe8ed6e3d: swimming_pace_0 # "SwimmingPace_0"
|
||||
0x9fea5eab: swimming_pace_1 # "SwimmingPace_1"
|
||||
0x06e30f11: swimming_pace_2 # "SwimmingPace_2"
|
||||
0x71e43f87: swimming_pace_3 # "SwimmingPace_3"
|
||||
0xef80aa24: swimming_pace_4 # "SwimmingPace_4"
|
||||
0x836cff9c: running_pace_1mi # "RunningPace_1mi"
|
||||
0xd55234df: running_pace_5km # "RunningPace_5km"
|
||||
0x4e699e99: running_pace_10km # "RunningPace_10km"
|
||||
0xdb69cd45: running_pace_hm # "RunningPace_hm"
|
||||
0x45eae0cb: running_pace_fm # "RunningPace_fm"
|
||||
0xe795b583: running_pace_1mi_estimated # "RunningPace_1mi_estimated"
|
||||
0x78c80977: running_pace_5km_estimated # "RunningPace_5km_estimated"
|
||||
0xaf8e1b0f: running_pace_10km_estimated # "RunningPace_10km_estimated"
|
||||
0x1c8c50e0: running_pace_hm_estimated # "RunningPace_hm_estimated"
|
||||
0xf5bd83fe: running_pace_fm_estimated # "RunningPace_fm_estimated"
|
||||
0x560fcbd5: use_skill_levelrunning # "UseSkillLevelRunning"
|
||||
0xb2fd90ee: use_skill_levelcycling # "UseSkillLevelCycling"
|
||||
0xec37cb97: cycling_skill_level # "CyclingSkillLevel"
|
||||
0xc682fcc0: running_skill_level # "RunningSkillLevel"
|
||||
0x598443fb: completed_any_workout # "COMPLETEDANYWORKOUT"
|
||||
0x5b66cc9c: scotty_watching_tutorial_1 # "SCOTTY_WATCHING_TUTORIAL_1"
|
||||
0x424ea4d3: scotty_leaderboard_tutorial # "SCOTTY_LEADERBOARD_TUTORIAL"
|
||||
0xe6bb413b: scotty_ridersnearby_tutorial # "SCOTTY_RIDERSNEARBY_TUTORIAL"
|
||||
0xbf79811f: mixtape_2019_1 # "MIXTAPE_2019_1"
|
||||
0x2670d0a5: mixtape_2019_2 # "MIXTAPE_2019_2"
|
||||
0x5177e033: mixtape_2019_3 # "MIXTAPE_2019_3"
|
||||
0xcf137590: mixtape_2019_4 # "MIXTAPE_2019_4"
|
||||
0xb8144506: mixtape_2019_5 # "MIXTAPE_2019_5"
|
||||
0x211d14bc: mixtape_2019_6 # "MIXTAPE_2019_6"
|
||||
0x561a242a: mixtape_2019_7 # "MIXTAPE_2019_7"
|
||||
0xc6a539bb: mixtape_2019_8 # "MIXTAPE_2019_8"
|
||||
0x34f647b2: spinwheel_spincount # "SPINWHEEL_SPINCOUNT"
|
||||
0xe412bb7b: current_eula_version # "CURRENT_EULA_VERSION"
|
||||
0x2ea8df6a: rode_with_zml # "RODE_WITH_ZML"
|
||||
0x5ef9ad14: zar2021 # "ZAR2021"
|
||||
0xdaa16f19: rfto2021 # "RFTO2021"
|
||||
0xc72b3ccd: pacerbot_dropin_clicked # "PACERBOTDROPINCLICKED"
|
||||
0xfea634fb: scotty_dropin_tutorial_01 # "SCOTTY_DROPIN_TUTORIAL_01"
|
||||
0x67af6541: scotty_dropin_tutorial_02 # "SCOTTY_DROPIN_TUTORIAL_02"
|
||||
0x10a855d7: scotty_dropin_tutorial_03 # "SCOTTY_DROPIN_TUTORIAL_03"
|
||||
0x8eccc074: scotty_dropin_tutorial_04 # "SCOTTY_DROPIN_TUTORIAL_04"
|
||||
0xf9cbf0e2: scotty_dropin_tutorial_05 # "SCOTTY_DROPIN_TUTORIAL_05"
|
||||
0x60c2a158: scotty_dropin_tutorial_06 # "SCOTTY_DROPIN_TUTORIAL_06"
|
||||
0x17c591ce: scotty_dropin_tutorial_07 # "SCOTTY_DROPIN_TUTORIAL_07"
|
||||
0xd83c4c7a: scotty_pairing_outro # "SCOTTY_PAIRING_OUTRO"
|
||||
0xd0646944: scotty_pairing_intro # "SCOTTY_PAIRING_INTRO"
|
||||
0x2cebca4b: setup_profile_info # "SETUPPROFILEINFO"
|
||||
0xb6061fba: current_route_version # "CURRENTROUTEVERSION"
|
||||
0x06880226: android_setup_region # "ANDROID_SETUPREGION"
|
||||
0x0509c9a9: last_zml_advert_time # "LAST_ZML_ADVERT_TIME"
|
||||
0xd21df098: completed_orientation_ride # "COMPLETEDORIENTATIONRIDE"
|
||||
0xeebaf1fd: completed_welcome_ride # "COMPLETEDWELCOMERIDE"
|
||||
264
zwift_offline.py
264
zwift_offline.py
@@ -62,12 +62,6 @@ logger = logging.getLogger('zoffline')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logging.getLogger('sqlalchemy.engine').setLevel(logging.WARN)
|
||||
|
||||
if os.name == 'nt' and platform.release() == '10' and platform.version() >= '10.0.14393':
|
||||
# Fix ANSI color in Windows 10 version 10.0.14393 (Windows Anniversary Update)
|
||||
import ctypes
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
# If we're running as a pyinstaller bundle
|
||||
SCRIPT_DIR = sys._MEIPASS
|
||||
@@ -121,14 +115,8 @@ if MULTIPLAYER:
|
||||
with open(CREDENTIALS_KEY_FILE, 'rb') as f:
|
||||
credentials_key = f.read()
|
||||
|
||||
try:
|
||||
with open('%s/strava-client.txt' % STORAGE_DIR, 'r') as f:
|
||||
client_id = f.readline().rstrip('\r\n')
|
||||
client_secret = f.readline().rstrip('\r\n')
|
||||
except Exception as exc:
|
||||
#logger.warn('strava-client: %s' % repr(exc))
|
||||
client_id = '28117'
|
||||
client_secret = '41b7b7b76d8cfc5dc12ad5f020adfea17da35468'
|
||||
STRAVA_CLIENT_ID = '28117'
|
||||
STRAVA_CLIENT_SECRET = '41b7b7b76d8cfc5dc12ad5f020adfea17da35468'
|
||||
|
||||
from tokens import *
|
||||
|
||||
@@ -435,24 +423,24 @@ def imageSrc(player_id):
|
||||
|
||||
def get_partial_profile(player_id):
|
||||
if not player_id in player_partial_profiles:
|
||||
#Read from disk
|
||||
partial_profile = PartialProfile()
|
||||
partial_profile.player_id = player_id
|
||||
if player_id in global_pace_partners.keys():
|
||||
profile = global_pace_partners[player_id].profile
|
||||
elif player_id in global_bots.keys():
|
||||
profile = global_bots[player_id].profile
|
||||
else:
|
||||
#Read from disk
|
||||
profile_file = '%s/%s/profile.bin' % (STORAGE_DIR, player_id)
|
||||
if os.path.isfile(profile_file):
|
||||
try:
|
||||
with open(profile_file, 'rb') as fd:
|
||||
profile = profile_pb2.PlayerProfile()
|
||||
profile.ParseFromString(fd.read())
|
||||
except Exception as exc:
|
||||
logger.warn('get_partial_profile: %s' % repr(exc))
|
||||
return None
|
||||
else: return None
|
||||
partial_profile = PartialProfile()
|
||||
partial_profile.player_id = player_id
|
||||
with open(profile_file, 'rb') as fd:
|
||||
profile = profile_pb2.PlayerProfile()
|
||||
profile.ParseFromString(fd.read())
|
||||
else:
|
||||
user = User.query.filter_by(player_id=player_id).first()
|
||||
partial_profile.first_name = user.first_name
|
||||
partial_profile.last_name = user.last_name
|
||||
return partial_profile
|
||||
partial_profile.imageSrc = imageSrc(player_id)
|
||||
partial_profile.first_name = profile.first_name
|
||||
partial_profile.last_name = profile.last_name
|
||||
@@ -676,7 +664,7 @@ def strava():
|
||||
flash("stravalib is not installed. Skipping Strava authorization attempt.")
|
||||
return redirect('/user/%s/' % current_user.username)
|
||||
client = Client()
|
||||
url = client.authorization_url(client_id=client_id,
|
||||
url = client.authorization_url(client_id=STRAVA_CLIENT_ID,
|
||||
redirect_uri='https://launcher.zwift.com/authorization',
|
||||
scope='activity:write')
|
||||
return redirect(url)
|
||||
@@ -689,10 +677,10 @@ def authorization():
|
||||
try:
|
||||
client = Client()
|
||||
code = request.args.get('code')
|
||||
token_response = client.exchange_code_for_token(client_id=client_id, client_secret=client_secret, code=code)
|
||||
token_response = client.exchange_code_for_token(client_id=STRAVA_CLIENT_ID, client_secret=STRAVA_CLIENT_SECRET, code=code)
|
||||
with open(os.path.join(STORAGE_DIR, str(current_user.player_id), 'strava_token.txt'), 'w') as f:
|
||||
f.write(client_id + '\n');
|
||||
f.write(client_secret + '\n');
|
||||
f.write(STRAVA_CLIENT_ID + '\n');
|
||||
f.write(STRAVA_CLIENT_SECRET + '\n');
|
||||
f.write(token_response['access_token'] + '\n');
|
||||
f.write(token_response['refresh_token'] + '\n');
|
||||
f.write(str(token_response['expires_at']) + '\n');
|
||||
@@ -2048,28 +2036,27 @@ def api_profiles_activities_id(player_id, activity_id):
|
||||
def api_profiles_activities_rideon(receiving_player_id):
|
||||
sending_player_id = request.json['profileId']
|
||||
profile = get_partial_profile(sending_player_id)
|
||||
if not profile == None:
|
||||
player_update = udp_node_msgs_pb2.WorldAttribute()
|
||||
player_update.server_realm = udp_node_msgs_pb2.ZofflineConstants.RealmID
|
||||
player_update.wa_type = udp_node_msgs_pb2.WA_TYPE.WAT_RIDE_ON
|
||||
player_update.world_time_born = world_time()
|
||||
player_update.world_time_expire = player_update.world_time_born + 9890
|
||||
player_update.timestamp = int(get_utc_time() * 1000000)
|
||||
player_update = udp_node_msgs_pb2.WorldAttribute()
|
||||
player_update.server_realm = udp_node_msgs_pb2.ZofflineConstants.RealmID
|
||||
player_update.wa_type = udp_node_msgs_pb2.WA_TYPE.WAT_RIDE_ON
|
||||
player_update.world_time_born = world_time()
|
||||
player_update.world_time_expire = player_update.world_time_born + 9890
|
||||
player_update.timestamp = int(get_utc_time() * 1000000)
|
||||
|
||||
ride_on = udp_node_msgs_pb2.RideOn()
|
||||
ride_on.player_id = int(sending_player_id)
|
||||
ride_on.to_player_id = int(receiving_player_id)
|
||||
ride_on.firstName = profile.first_name
|
||||
ride_on.lastName = profile.last_name
|
||||
ride_on.countryCode = profile.country_code
|
||||
ride_on = udp_node_msgs_pb2.RideOn()
|
||||
ride_on.player_id = int(sending_player_id)
|
||||
ride_on.to_player_id = int(receiving_player_id)
|
||||
ride_on.firstName = profile.first_name
|
||||
ride_on.lastName = profile.last_name
|
||||
ride_on.countryCode = profile.country_code
|
||||
|
||||
player_update.payload = ride_on.SerializeToString()
|
||||
player_update.payload = ride_on.SerializeToString()
|
||||
|
||||
enqueue_player_update(receiving_player_id, player_update.SerializeToString())
|
||||
enqueue_player_update(receiving_player_id, player_update.SerializeToString())
|
||||
|
||||
receiver = get_partial_profile(receiving_player_id)
|
||||
message = 'Ride on ' + receiver.first_name + ' ' + receiver.last_name + '!'
|
||||
discord.send_message(message, sending_player_id)
|
||||
receiver = get_partial_profile(receiving_player_id)
|
||||
message = 'Ride on ' + receiver.first_name + ' ' + receiver.last_name + '!'
|
||||
discord.send_message(message, sending_player_id)
|
||||
return '{}', 200
|
||||
|
||||
def stime_to_timestamp(stime):
|
||||
@@ -2618,29 +2605,28 @@ def add_player_to_world(player, course_world, is_pace_partner):
|
||||
course_id = get_course(player)
|
||||
if course_id in course_world.keys():
|
||||
partial_profile = get_partial_profile(player.id)
|
||||
if not partial_profile == None:
|
||||
online_player = None
|
||||
if is_pace_partner:
|
||||
online_player = course_world[course_id].pacer_bots.add()
|
||||
online_player.route = partial_profile.route
|
||||
if player.sport == profile_pb2.Sport.CYCLING:
|
||||
online_player.ride_power = player.power
|
||||
else:
|
||||
online_player.speed = player.speed
|
||||
online_player = None
|
||||
if is_pace_partner:
|
||||
online_player = course_world[course_id].pacer_bots.add()
|
||||
online_player.route = partial_profile.route
|
||||
if player.sport == profile_pb2.Sport.CYCLING:
|
||||
online_player.ride_power = player.power
|
||||
else:
|
||||
online_player = course_world[course_id].others.add()
|
||||
online_player.id = player.id
|
||||
online_player.firstName = partial_profile.first_name
|
||||
online_player.lastName = partial_profile.last_name
|
||||
online_player.distance = player.distance
|
||||
online_player.time = player.time
|
||||
online_player.country_code = partial_profile.country_code
|
||||
online_player.sport = player.sport
|
||||
online_player.power = player.power
|
||||
online_player.x = player.x
|
||||
online_player.y_altitude = player.y_altitude
|
||||
online_player.z = player.z
|
||||
course_world[course_id].zwifters += 1
|
||||
online_player.speed = player.speed
|
||||
else:
|
||||
online_player = course_world[course_id].others.add()
|
||||
online_player.id = player.id
|
||||
online_player.firstName = partial_profile.first_name
|
||||
online_player.lastName = partial_profile.last_name
|
||||
online_player.distance = player.distance
|
||||
online_player.time = player.time
|
||||
online_player.country_code = partial_profile.country_code
|
||||
online_player.sport = player.sport
|
||||
online_player.power = player.power
|
||||
online_player.x = player.x
|
||||
online_player.y_altitude = player.y_altitude
|
||||
online_player.z = player.z
|
||||
course_world[course_id].zwifters += 1
|
||||
|
||||
|
||||
def relay_worlds_generic(server_realm=None):
|
||||
@@ -2961,6 +2947,71 @@ def relay_worlds_leave(server_realm):
|
||||
return '{"worldtime":%ld}' % world_time()
|
||||
|
||||
|
||||
@app.route('/experimentation/v1/variant', methods=['POST'])
|
||||
@jwt_to_session_cookie
|
||||
@login_required
|
||||
def experimentation_v1_variant():
|
||||
variants = variants_pb2.FeatureResponse()
|
||||
if b'game_1_27_0_disable_encryption_bypass' in request.stream.read():
|
||||
v1 = variants.variants.add()
|
||||
v1.name = "game_1_26_2_data_encryption"
|
||||
v1.value = True
|
||||
v2 = variants.variants.add()
|
||||
v2.name = "game_1_27_0_disable_encryption_bypass"
|
||||
v2.value = True
|
||||
else:
|
||||
with open(os.path.join(SCRIPT_DIR, "variants.txt")) as f:
|
||||
Parse(f.read(), variants)
|
||||
v = variants.variants.add()
|
||||
v.name = "game_1_20_home_screen"
|
||||
v.value = current_user.new_home
|
||||
return variants.SerializeToString(), 200
|
||||
|
||||
def get_profile_saved_game_achiev2_40_bytes():
|
||||
profile_file = '%s/%s/profile.bin' % (STORAGE_DIR, current_user.player_id)
|
||||
if not os.path.isfile(profile_file):
|
||||
return b''
|
||||
with open(profile_file, 'rb') as fd:
|
||||
profile = profile_pb2.PlayerProfile()
|
||||
profile.ParseFromString(fd.read())
|
||||
if len(profile.saved_game) > 0x150 and profile.saved_game[0x108] == 2: #checking 2 from 0x10000002: achiev_badges2_40
|
||||
return profile.saved_game[0x110:0x110+0x40] #0x110 = accessories1_100 + 2x8-byte headers
|
||||
else:
|
||||
return b''
|
||||
|
||||
@app.route('/api/achievement/loadPlayerAchievements', methods=['GET'])
|
||||
@jwt_to_session_cookie
|
||||
@login_required
|
||||
def achievement_loadPlayerAchievements():
|
||||
achievements_file = os.path.join(STORAGE_DIR, str(current_user.player_id), 'achievements.bin')
|
||||
if not os.path.isfile(achievements_file):
|
||||
converted = profile_pb2.Achievements()
|
||||
old_achiev_bits = get_profile_saved_game_achiev2_40_bytes()
|
||||
for ach_id in range(8 * len(old_achiev_bits)):
|
||||
if (old_achiev_bits[ach_id // 8] >> (ach_id % 8)) & 0x1:
|
||||
converted.achievements.add().id = ach_id
|
||||
with open(achievements_file, 'wb') as f:
|
||||
f.write(converted.SerializeToString())
|
||||
with open(achievements_file, 'rb') as f:
|
||||
return f.read(), 200
|
||||
|
||||
@app.route('/api/achievement/unlock', methods=['POST'])
|
||||
@jwt_to_session_cookie
|
||||
@login_required
|
||||
def achievement_unlock():
|
||||
if not request.stream:
|
||||
return '', 400
|
||||
with open(os.path.join(STORAGE_DIR, str(current_user.player_id), 'achievements.bin'), 'wb') as f:
|
||||
f.write(request.stream.read())
|
||||
return '', 202
|
||||
|
||||
# if we respond to this request with an empty json a "tutorial" will be presented in ZCA
|
||||
# and for each completed step it will POST /api/achievement/unlock/<id>
|
||||
@app.route('/api/achievement/category/<category_id>', methods=['GET'])
|
||||
def api_achievement_category(category_id):
|
||||
return '', 404 # returning error for now, since some steps can't be completed
|
||||
|
||||
|
||||
@app.teardown_request
|
||||
def teardown_request(exception):
|
||||
db.session.close()
|
||||
@@ -3096,9 +3147,9 @@ def migrate_database():
|
||||
db.session.execute('vacuum') #shrink database
|
||||
|
||||
|
||||
def check_columns():
|
||||
rows = db.session.execute(sqlalchemy.text("PRAGMA table_info(user)"))
|
||||
should_have_columns = User.metadata.tables['user'].columns
|
||||
def check_columns(table_class, table_name):
|
||||
rows = db.session.execute(sqlalchemy.text("PRAGMA table_info(%s)" % table_name))
|
||||
should_have_columns = table_class.metadata.tables[table_name].columns
|
||||
current_columns = list()
|
||||
for row in rows:
|
||||
current_columns.append(row[1])
|
||||
@@ -3114,7 +3165,7 @@ def check_columns():
|
||||
defaulttext = ""
|
||||
else:
|
||||
defaulttext = " DEFAULT %s" % column.default.arg
|
||||
db.session.execute(sqlalchemy.text("ALTER TABLE user ADD %s %s %s%s;" % (column.name, str(column.type), nulltext, defaulttext)))
|
||||
db.session.execute(sqlalchemy.text("ALTER TABLE %s ADD %s %s %s%s;" % (table_name, column.name, str(column.type), nulltext, defaulttext)))
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@@ -3130,7 +3181,7 @@ def before_first_request():
|
||||
move_old_profile()
|
||||
db.create_all(app=app)
|
||||
db.session.commit()
|
||||
check_columns()
|
||||
check_columns(User, 'user')
|
||||
migrate_database()
|
||||
db.session.close()
|
||||
|
||||
@@ -3280,71 +3331,6 @@ def auth_realms_zwift_tokens_access_codes():
|
||||
return FAKE_JWT, 200
|
||||
|
||||
|
||||
@app.route('/experimentation/v1/variant', methods=['POST'])
|
||||
@jwt_to_session_cookie
|
||||
@login_required
|
||||
def experimentation_v1_variant():
|
||||
variants = variants_pb2.FeatureResponse()
|
||||
if b'game_1_27_0_disable_encryption_bypass' in request.stream.read():
|
||||
v1 = variants.variants.add()
|
||||
v1.name = "game_1_26_2_data_encryption"
|
||||
v1.value = True
|
||||
v2 = variants.variants.add()
|
||||
v2.name = "game_1_27_0_disable_encryption_bypass"
|
||||
v2.value = True
|
||||
else:
|
||||
with open(os.path.join(SCRIPT_DIR, "variants.txt")) as f:
|
||||
Parse(f.read(), variants)
|
||||
v = variants.variants.add()
|
||||
v.name = "game_1_20_home_screen"
|
||||
v.value = current_user.new_home
|
||||
return variants.SerializeToString(), 200
|
||||
|
||||
def get_profile_saved_game_achiev2_40_bytes():
|
||||
profile_file = '%s/%s/profile.bin' % (STORAGE_DIR, current_user.player_id)
|
||||
if not os.path.isfile(profile_file):
|
||||
return b''
|
||||
with open(profile_file, 'rb') as fd:
|
||||
profile = profile_pb2.PlayerProfile()
|
||||
profile.ParseFromString(fd.read())
|
||||
if len(profile.saved_game) > 0x150 and profile.saved_game[0x108] == 2: #checking 2 from 0x10000002: achiev_badges2_40
|
||||
return profile.saved_game[0x110:0x110+0x40] #0x110 = accessories1_100 + 2x8-byte headers
|
||||
else:
|
||||
return b''
|
||||
|
||||
@app.route('/api/achievement/loadPlayerAchievements', methods=['GET'])
|
||||
@jwt_to_session_cookie
|
||||
@login_required
|
||||
def achievement_loadPlayerAchievements():
|
||||
achievements_file = os.path.join(STORAGE_DIR, str(current_user.player_id), 'achievements.bin')
|
||||
if not os.path.isfile(achievements_file):
|
||||
converted = profile_pb2.Achievements()
|
||||
old_achiev_bits = get_profile_saved_game_achiev2_40_bytes()
|
||||
for ach_id in range(8 * len(old_achiev_bits)):
|
||||
if (old_achiev_bits[ach_id // 8] >> (ach_id % 8)) & 0x1:
|
||||
converted.achievements.add().id = ach_id
|
||||
with open(achievements_file, 'wb') as f:
|
||||
f.write(converted.SerializeToString())
|
||||
with open(achievements_file, 'rb') as f:
|
||||
return f.read(), 200
|
||||
|
||||
@app.route('/api/achievement/unlock', methods=['POST'])
|
||||
@jwt_to_session_cookie
|
||||
@login_required
|
||||
def achievement_unlock():
|
||||
if not request.stream:
|
||||
return '', 400
|
||||
with open(os.path.join(STORAGE_DIR, str(current_user.player_id), 'achievements.bin'), 'wb') as f:
|
||||
f.write(request.stream.read())
|
||||
return '', 202
|
||||
|
||||
# if we respond to this request with an empty json a "tutorial" will be presented in ZCA
|
||||
# and for each completed step it will POST /api/achievement/unlock/<id>
|
||||
@app.route('/api/achievement/category/<category_id>', methods=['GET'])
|
||||
def api_achievement_category(category_id):
|
||||
return '', 404 # returning error for now, since some steps can't be completed
|
||||
|
||||
|
||||
def run_standalone(passed_online, passed_global_relay, passed_global_pace_partners, passed_global_bots, passed_global_ghosts, passed_ghosts_enabled, passed_save_ghost, passed_player_update_queue, passed_discord):
|
||||
global online
|
||||
global global_relay
|
||||
|
||||
Reference in New Issue
Block a user