From 2eb0c38164f2c76cc7f2cb8cf93d93838ea5cfb8 Mon Sep 17 00:00:00 2001 From: zoffline Date: Fri, 26 Oct 2018 21:22:41 -0400 Subject: [PATCH] Add support for Android devices (proper commit...) --- README.md | 25 ++++++++++++++++++++++ zwift_offline.py | 55 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 1a184f2..b471bc4 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,31 @@ to generate your own certificates and do the same. +
Android (requires a rooted device) + +* Install Zwift on the device +* Append the contents of ``ssl/cert-zwift-com.pem`` to ``/data/data/com.zwift.zwiftgame/dataES/cacerts.pem`` on the device + * Note: this file will only exist after the first run of Zwift since it's downloaded after the initial install + * Simple approach to achieve this if your device doesn't have a text editor: + * ``adb push ssl/cert-zwift-com.pem /data/data/com.zwift.zwiftgame/dataES/`` + * In ``adb shell``: ``cd /data/data/com.zwift.zwiftgame/dataES/`` + * In ``adb shell``: ``cat cert-zwift-com.pem >> cacerts.pem`` + * However you do it, ensure the permissions and ownership of the file remains the same. +* Modify the device's /etc/hosts file + * Append this line: `` us-or-rly101.zwift.com secure.zwift.com cdn.zwift.com`` +
(Where ```` is the ip address of the machine running zoffline. If + it's running on the same machine as Zwift, use ``127.0.0.1`` as the ip.) + * If no text editor on the device, recommend: + * ``adb pull /etc/hosts`` + * (modify on PC) + * ``adb push hosts /etc/hosts`` +* Start Zwift and sign in using any email/password + +Why: We need to redirect Zwift to use zoffline and convince Zwift to +accept zoffline's self signed certificates for Zwift's domain names. Feel free +to generate your own certificates and do the same. + +
#### Enabling/Disabling zoffline diff --git a/zwift_offline.py b/zwift_offline.py index a3328fa..cae5114 100644 --- a/zwift_offline.py +++ b/zwift_offline.py @@ -24,7 +24,8 @@ import protobuf.profile_pb2 as profile_pb2 import protobuf.segment_result_pb2 as segment_result_pb2 import protobuf.world_pb2 as world_pb2 -app = Flask(__name__) +# Android uses https for cdn +app = Flask(__name__, static_folder='cdn/gameassets', static_url_path='/gameassets') SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) STORAGE_DIR = "%s/storage" % SCRIPT_DIR @@ -337,29 +338,47 @@ def api_profiles_goals_id(player_id, goal_id): return '', 200 +def relay_worlds_generic(world_id=None): + # Android client also requests a JSON version + if request.headers['Accept'] == 'application/json': + world = { 'currentDateTime': int(time.time()), + 'currentWorldTime': int(time.time())*1000, + 'friendsInWorld': [], + 'mapId': 1, + 'name': 'Public Watopia', + 'playerCount': 0, + 'worldId': 1 + } + if world_id: + world['mapId'] = world_id + return jsonify(world) + else: + return jsonify([ world ]) + else: # protobuf request + worlds = world_pb2.Worlds() + world = worlds.worlds.add() + world.id = 1 + world.name = 'Public Watopia' + world.f3 = 1 + # Windows client crashes if playerCount is 0 + world.f5 = 1 # playerCount + world.world_time = int(time.time())*1000 + world.real_time = int(time.time()) + if world_id: + world.id = world_id + return worlds.SerializeToString() + else: + return world.SerializeToString() + + @app.route('/relay/worlds', methods=['GET']) def relay_worlds(): - worlds = world_pb2.Worlds() - world = worlds.worlds.add() - world.id = 1 - world.name = 'Public Watopia' - world.f3 = 1 - world.f5 = 1 - world.world_time = int(time.time())*1000 - world.real_time = int(time.time()) - return worlds.SerializeToString(), 200 + return relay_worlds_generic() @app.route('/relay/worlds/', methods=['GET']) def relay_worlds_id(world_id): - # XXX: Will need to keep MapSchedule.xml up to date - return jsonify({ 'currentDateTime': int(time.time()), - 'currentWorldTime': int(time.time())*1000, - 'friendsInWorld': [ ], - 'mapId': world_id, - 'name': 'Public Watopia', - 'playerCount': 0, - 'worldId': 1 }) + return relay_worlds_generic(world_id) @app.route('/relay/worlds//join', methods=['POST'])