├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── cmake └── FindLibUV.cmake ├── include └── playapi │ ├── api.h │ ├── checkin.h │ ├── device_info.h │ ├── experiments.h │ ├── file_login_cache.h │ ├── http_task.h │ ├── login.h │ ├── login_cache.h │ ├── task.h │ └── util │ ├── base64.h │ ├── config.h │ ├── http.h │ ├── http_request_pool.h │ └── rand.h ├── lib └── playapi │ ├── api.cpp │ ├── checkin.cpp │ ├── device_info.cpp │ ├── experiments.cpp │ ├── file_login_cache.cpp │ ├── login.cpp │ └── util │ ├── base64.cpp │ ├── config.cpp │ ├── http.cpp │ ├── http_request_pool.cpp │ └── rand.cpp ├── proto ├── gsf.proto ├── play_browse.proto ├── play_common.proto ├── play_containers.proto ├── play_details.proto ├── play_device_config.proto ├── play_document.proto ├── play_download.proto ├── play_filter_rules.proto ├── play_link.proto ├── play_ownership.proto ├── play_respone.proto ├── play_search.proto ├── play_settings.proto └── play_toc.proto └── src ├── arg_list.h ├── common.cpp ├── common.h ├── config.cpp ├── config.h ├── gplaydl.cpp └── gplayver.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | cmake-build-debug/ 3 | devices/ 4 | playdl.conf 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(gplaydl) 3 | 4 | set(CMAKE_CXX_STANDARD 11) 5 | 6 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") 7 | 8 | find_package(Threads REQUIRED) 9 | find_package(ZLIB REQUIRED) 10 | find_package(CURL REQUIRED) 11 | find_package(Protobuf REQUIRED) 12 | find_package(LibUV REQUIRED) 13 | 14 | if (NOT DEFINED Protobuf_LIBRARIES) 15 | set(Protobuf_LIBRARIES ${PROTOBUF_LIBRARIES}) 16 | set(Protobuf_INCLUDE_DIRS ${PROTOBUF_INCLUDE_DIRS}) 17 | endif() 18 | 19 | protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS proto/gsf.proto proto/play_common.proto proto/play_document.proto proto/play_respone.proto proto/play_settings.proto proto/play_toc.proto proto/play_download.proto proto/play_filter_rules.proto proto/play_ownership.proto proto/play_containers.proto proto/play_link.proto proto/play_device_config.proto proto/play_search.proto proto/play_browse.proto proto/play_details.proto) 20 | 21 | set(LIB_UTIL_SOURCE_FILES lib/playapi/util/http.cpp include/playapi/util/http.h lib/playapi/util/config.cpp include/playapi/util/config.h lib/playapi/util/rand.cpp include/playapi/util/rand.h lib/playapi/util/base64.cpp include/playapi/util/base64.h) 22 | set(LIB_SOURCE_FILES lib/playapi/login.cpp include/playapi/login.h lib/playapi/device_info.cpp include/playapi/device_info.h lib/playapi/checkin.cpp include/playapi/checkin.h include/playapi/api.h lib/playapi/api.cpp lib/playapi/experiments.cpp include/playapi/experiments.h include/playapi/login_cache.h include/playapi/file_login_cache.h lib/playapi/file_login_cache.cpp include/playapi/task.h include/playapi/util/http_request_pool.h lib/playapi/util/http_request_pool.cpp include/playapi/http_task.h) 23 | add_library(gplayapi STATIC ${LIB_SOURCE_FILES} ${LIB_UTIL_SOURCE_FILES} ${PROTO_SRCS}) 24 | target_link_libraries(gplayapi ${CURL_LIBRARIES} ${ZLIB_LIBRARIES} ${Protobuf_LIBRARIES} ${LIBUV_LIBRARIES} Threads::Threads) 25 | target_include_directories(gplayapi PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${CURL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIRS} ${Protobuf_INCLUDE_DIRS} ${LIBUV_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) 26 | 27 | add_executable(gplaydl src/gplaydl.cpp src/common.cpp src/config.cpp src/config.h) 28 | target_link_libraries(gplaydl gplayapi) 29 | target_include_directories(gplaydl PUBLIC ${CURL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIRS}) 30 | 31 | add_executable(gplayver src/gplayver.cpp src/common.cpp src/config.cpp src/config.h) 32 | target_link_libraries(gplayver gplayapi) 33 | target_include_directories(gplayver PUBLIC ${CURL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIRS}) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /cmake/FindLibUV.cmake: -------------------------------------------------------------------------------- 1 | # Finds the `uv` library. 2 | # This file is released under the Public Domain. 3 | # Once done this will define 4 | # LIBUV_FOUND - Set to true if evdev has been found 5 | # LIBUV_INCLUDE_DIRS - The evdev include directories 6 | # LIBUV_LIBRARIES - The libraries needed to use evdev 7 | 8 | find_package(PkgConfig) 9 | pkg_check_modules(PC_LIBUV QUIET libuv) 10 | 11 | find_path(LIBUV_INCLUDE_DIR 12 | NAMES uv.h 13 | HINTS ${PC_LIBUV_INCLUDEDIR} ${PC_LIBUV_INCLUDE_DIRS}) 14 | find_library(LIBUV_LIBRARY 15 | NAMES uv libuv 16 | HINTS ${PC_LIBUV_LIBDIR} ${PC_LIBUV_LIBRARY_DIRS}) 17 | 18 | include(FindPackageHandleStandardArgs) 19 | find_package_handle_standard_args(LIBUV DEFAULT_MSG 20 | LIBUV_LIBRARY LIBUV_INCLUDE_DIR) 21 | 22 | mark_as_advanced(LIBUV_INCLUDE_DIR LIBUV_LIBRARY) 23 | 24 | set(LIBUV_LIBRARIES ${LIBUV_LIBRARY}) 25 | set(LIBUV_INCLUDE_DIRS ${LIBUV_INCLUDE_DIR}) -------------------------------------------------------------------------------- /include/playapi/api.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "util/http.h" 6 | #include 7 | #include 8 | #include "experiments.h" 9 | #include "checkin.h" 10 | 11 | namespace playapi { 12 | 13 | class device_info; 14 | class login_api; 15 | 16 | class api { 17 | 18 | public: 19 | 20 | enum class request_content_type { 21 | none, url_encoded, protobuf 22 | }; 23 | struct request_options { 24 | request_content_type content_type = request_content_type::none; 25 | bool include_checkin_consistency_token = true; 26 | bool include_content_filters = true; 27 | bool include_network_type = true; 28 | bool include_toc_cookie = true; 29 | bool include_device_config_token = false; 30 | }; 31 | 32 | private: 33 | 34 | device_info& device; 35 | login_api* login; 36 | std::string url; 37 | 38 | std::mutex auth_mutex; 39 | std::string auth_email, auth_token; 40 | checkin_result checkin_data; 41 | 42 | std::string build_user_agent(); 43 | 44 | void add_headers(http_request& req, const request_options& options); 45 | 46 | task_ptr invalidate_token(); 47 | 48 | public: 49 | 50 | using request_task = task_ptr; 51 | 52 | mutable std::mutex info_mutex; 53 | std::string device_config_token; 54 | std::string toc_cookie; 55 | experiments_list experiments; 56 | 57 | 58 | api(device_info& device, const std::string& url = "https://android.clients.google.com/fdfe/") : device(device), 59 | url(url) {} 60 | 61 | task_ptr set_auth(login_api& login); 62 | 63 | void set_checkin_data(const checkin_result& result); 64 | 65 | request_task 66 | send_request(http_method method, const std::string& path, const request_options& options); 67 | 68 | request_task 69 | send_request(http_method method, const std::string& path, const std::string& bin_data, 70 | const request_options& options); 71 | 72 | request_task 73 | send_request(http_method method, const std::string& path, 74 | const std::vector>& pairs, const request_options& options); 75 | 76 | request_task fetch_user_settings(); 77 | 78 | request_task fetch_toc(); 79 | 80 | request_task upload_device_config(const std::string& gcm_reg_id = "", bool upload_full_config = true); 81 | 82 | request_task accept_tos(const std::string& token, bool allow_marketing_emails = false); 83 | 84 | request_task get_search_suggestions(std::string q, int backend_id = 3, int icon_size = 120, bool request_navigational = true); 85 | 86 | request_task search(const std::string& q, int backend_id = 3); 87 | 88 | request_task details(const std::string& app); 89 | 90 | request_task 91 | delivery(const std::string& app, int version_code, const std::string& library_token, 92 | const std::string& delivery_token = std::string(), int previous_version_code = -1, 93 | std::vector const& patch_formats = std::vector(), 94 | const std::string& cert_hash = std::string(), 95 | const std::string& self_update_md5_cert_hash = std::string()); 96 | 97 | 98 | 99 | void set_device_config_token(std::string value) { 100 | std::lock_guard l (info_mutex); 101 | device_config_token = std::move(value); 102 | } 103 | 104 | void set_toc_cookie(std::string value) { 105 | std::lock_guard l (info_mutex); 106 | toc_cookie = std::move(value); 107 | } 108 | 109 | struct bulk_details_request { 110 | std::string name; 111 | int installed_version_code = -1; 112 | bool include_details = false; 113 | 114 | bulk_details_request(std::string const& name) : name(name) {} 115 | }; 116 | 117 | request_task bulk_details(std::vector const& v); 118 | 119 | 120 | }; 121 | 122 | } 123 | -------------------------------------------------------------------------------- /include/playapi/checkin.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "task.h" 7 | 8 | namespace playapi { 9 | 10 | class device_info; 11 | class login_api; 12 | 13 | struct checkin_result { 14 | 15 | long long time = 0; // 0 if never 16 | unsigned long long android_id = 0; 17 | unsigned long long security_token = 0; 18 | std::string device_data_version_info; 19 | 20 | std::string get_string_android_id() const; 21 | 22 | }; 23 | 24 | class checkin_api { 25 | 26 | device_info const& device; 27 | 28 | struct auth_user { 29 | std::string email, auth_cookie; 30 | }; 31 | std::mutex auth_mutex; 32 | std::vector auth; 33 | 34 | public: 35 | 36 | checkin_api(device_info const& device); 37 | 38 | task_ptr add_auth(login_api& login); 39 | 40 | void clear_auth() { 41 | std::lock_guard l (auth_mutex); 42 | auth.clear(); 43 | } 44 | 45 | task_ptr perform_checkin(const checkin_result& last_checkin = checkin_result()); 46 | 47 | }; 48 | 49 | } -------------------------------------------------------------------------------- /include/playapi/device_info.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace playapi { 8 | 9 | namespace proto { namespace gsf { class DeviceConfigurationProto; }} 10 | 11 | class config; 12 | 13 | enum class device_type { 14 | unknown = 1, phone, tablet, tv, glass, wearable = 7 15 | }; 16 | static device_type device_type_from_string(const std::string& str); 17 | static std::string device_type_to_string(device_type type); 18 | 19 | enum class device_touch_screen { 20 | undefinied = 0, notouch, stylus, finger 21 | }; 22 | static device_touch_screen device_touch_screen_from_string(const std::string& str); 23 | static std::string device_touch_screen_to_string(device_touch_screen type); 24 | 25 | enum class device_keyboard { 26 | undefinied = 0, nokeys, qwerty, twelvekey 27 | }; 28 | static device_keyboard device_keyboard_from_string(const std::string& str); 29 | static std::string device_keyboard_to_string(device_keyboard type); 30 | 31 | enum class device_navigation { 32 | undefinied = 0, nonav, dpad, trackball, wheel 33 | }; 34 | static device_navigation device_navigation_from_string(const std::string& str); 35 | static std::string device_navigation_to_string(device_navigation type); 36 | 37 | enum class device_screen_layout { 38 | undefinied = 0, small, normal, large, xlarge 39 | }; 40 | static device_screen_layout device_screen_layout_from_string(const std::string& str); 41 | static std::string device_screen_layout_to_string(device_screen_layout type); 42 | 43 | struct device_info { 44 | 45 | // constants 46 | 47 | // some made up data to fall back to in case user doesn't specify a valid device 48 | device_type type = device_type::tablet; 49 | std::string build_fingerprint = "diy/desktop/desktop:6.0/DSKTOP/desktop:user/release-keys"; 50 | std::string build_id = "DSKTOP"; 51 | std::string build_product = "desktop"; 52 | std::string build_brand = "diy"; 53 | std::string build_radio; 54 | std::string build_bootloader = "unknown"; 55 | std::string build_client = "android-google"; 56 | long long build_timestamp = 1480546800; 57 | int build_google_services = 10084470; 58 | std::string build_device = "D001"; 59 | int build_sdk_version = 23; 60 | std::string build_version_string = "6.0.1"; 61 | std::string build_model = "DIY_D001"; 62 | std::string build_manufacturer = "diy"; 63 | std::string build_build_product = "desktop"; 64 | bool build_ota_installed = false; 65 | std::vector> build_google_packages; 66 | std::string build_security_patch = "2019-01-05"; 67 | 68 | device_touch_screen config_touch_screen = device_touch_screen::finger; 69 | device_keyboard config_keyboard = device_keyboard::qwerty; 70 | device_navigation config_navigation = device_navigation::nonav; 71 | device_screen_layout config_screen_layout = device_screen_layout::xlarge; 72 | bool config_has_hard_keyboard = false; 73 | bool config_has_five_way_navig = false; 74 | int config_screen_density = 120; 75 | int config_gles_version = 0x30000; 76 | std::vector config_system_shared_libraries = {"android.test.runner", 77 | "com.android.future.usb.accessory", 78 | "com.android.location.provider", 79 | "com.google.android.gms", "javax.obex", 80 | "org.apache.http.legacy"}; 81 | std::vector> config_system_features; // this will be initialized in the constructor 82 | std::vector config_native_platforms = {"x86", "armeabi-x7a", "armeabi"}; 83 | int config_screen_width = 1920; 84 | int config_screen_height = 1080; 85 | std::vector config_system_supported_locales = {"en", "en-US"}; 86 | std::vector config_gl_extensions; 87 | int config_smallest_screen_width_dp = 1080; 88 | bool config_low_ram = false; 89 | long long config_total_ram = 4093796352; 90 | int config_cores = 4; 91 | bool voice_capable = false; // this is not in config because it's somewhere else in the proto file 92 | bool wide_screen = false; 93 | std::vector ota_certs; 94 | 95 | bool config_keyguard_device_secure = false; 96 | std::string country = "us"; 97 | std::string locale = "en_US"; 98 | std::string time_zone = "America/New_York"; 99 | 100 | std::string roaming = "WIFI::"; 101 | std::string cell_operator; 102 | std::string sim_operator; 103 | int user_number = 0; 104 | int user_serial_number = 0; 105 | 106 | std::string gservices_digest; // digest of the gservices config 107 | 108 | std::string mac_addr_type = "wifi"; 109 | std::string mac_addr; 110 | bool mac_addr_generate = true; 111 | std::string meid; 112 | bool meid_generate = false; 113 | std::string serial_number; 114 | bool serial_number_generate = false; 115 | std::string serial_number_generate_chars; 116 | int serial_number_generate_length = 8; 117 | 118 | // fields 119 | 120 | long long random_logging_id = 0; 121 | 122 | std::string generated_mac_addr; 123 | std::string generated_meid; 124 | std::string generated_serial_number; 125 | 126 | device_info(); 127 | 128 | void load(config& conf); 129 | 130 | void generate_fields(); 131 | 132 | void fill_device_config_proto(proto::gsf::DeviceConfigurationProto& proto, 133 | bool feature_add_gles_version_if_zero = false) const; 134 | 135 | }; 136 | 137 | } -------------------------------------------------------------------------------- /include/playapi/experiments.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace playapi { 8 | 9 | class http_request; 10 | 11 | class experiments_list { 12 | 13 | public: 14 | 15 | static const long long ENCODED_TARGETS = 12610177; 16 | 17 | static std::set supported_experiments; 18 | 19 | std::set enabled_experiments; 20 | std::set other_experiments; 21 | 22 | bool is_enabled(long long experiment) const; 23 | 24 | void set_targets(const proto::finsky::response::Targets& experiments); 25 | 26 | void set_targets(const std::string& experiments); 27 | 28 | void add_headers(http_request& req); 29 | 30 | std::string get_comma_separated_target_list() const; 31 | 32 | }; 33 | 34 | 35 | } -------------------------------------------------------------------------------- /include/playapi/file_login_cache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "login_cache.h" 4 | #include 5 | #include 6 | 7 | namespace playapi { 8 | 9 | class file_login_cache : public login_cache { 10 | 11 | private: 12 | std::mutex mutex; 13 | std::string path; 14 | std::map, 15 | std::pair> auth_cookies; 16 | 17 | void load(); 18 | 19 | void save(); 20 | 21 | public: 22 | file_login_cache(std::string path) : path(std::move(path)) { 23 | load(); 24 | } 25 | 26 | void clear() { 27 | { 28 | std::lock_guard l (mutex); 29 | auth_cookies.clear(); 30 | } 31 | save(); 32 | } 33 | 34 | void cache(std::string const& service, std::string const& app, std::string const& token, 35 | std::chrono::system_clock::time_point expire) override { 36 | { 37 | std::lock_guard l (mutex); 38 | auth_cookies[{service, app}] = {token, expire}; 39 | } 40 | save(); 41 | } 42 | 43 | std::string get_cached(std::string const& service, std::string const& app) override { 44 | std::lock_guard l (mutex); 45 | auto ret = auth_cookies.find({service, app}); 46 | if (ret != auth_cookies.end() && ret->second.second > std::chrono::system_clock::now()) 47 | return ret->second.first; 48 | return std::string(); 49 | } 50 | 51 | }; 52 | 53 | } -------------------------------------------------------------------------------- /include/playapi/http_task.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "task.h" 4 | 5 | namespace playapi { 6 | 7 | class http_task : public task { 8 | 9 | private: 10 | http_request req; 11 | 12 | public: 13 | http_task(http_request req) : req(std::move(req)) { 14 | } 15 | 16 | static task_ptr make(http_request req) { 17 | return task_ptr(new http_task(std::move(req))); 18 | } 19 | 20 | void call(std::function success, std::function error) override { 21 | req.perform([success, error](http_response resp) { 22 | try { 23 | success(std::move(resp)); 24 | } catch (std::exception& e) { 25 | error(std::current_exception()); 26 | } 27 | }, error); 28 | } 29 | 30 | http_response call() override { 31 | return req.perform(); 32 | } 33 | 34 | }; 35 | 36 | } -------------------------------------------------------------------------------- /include/playapi/login.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "login_cache.h" 6 | #include "http_task.h" 7 | 8 | namespace playapi { 9 | 10 | class device_info; 11 | struct checkin_result; 12 | 13 | class login_api { 14 | 15 | public: 16 | 17 | enum class certificate { 18 | android, google 19 | }; 20 | 21 | private: 22 | 23 | struct login_request { 24 | std::string service, app; 25 | bool via_password = false; 26 | std::string email, password; 27 | std::string token; 28 | bool is_access_token = false; 29 | bool is_add_account = false; 30 | certificate cert = certificate::google; 31 | 32 | login_request(const std::string& service, const std::string& app, std::string email, std::string password, 33 | certificate cert = certificate::google) : service(service), app(app), via_password(true), 34 | email(email), password(password), cert(cert) {} 35 | 36 | login_request(const std::string& service, const std::string& app, const std::string& email, 37 | const std::string& token, bool is_access_token, bool is_add_account, 38 | certificate cert = certificate::google) : service(service), app(app), email(email), token(token), 39 | is_access_token(is_access_token), 40 | is_add_account(is_access_token), cert(cert) {} 41 | }; 42 | 43 | const device_info& device; 44 | 45 | std::mutex mutex; 46 | std::string email, token; 47 | std::string android_id; 48 | 49 | login_cache& cache; 50 | 51 | task_ptr perform(const login_request& request); 52 | 53 | std::string handle_response(http_response& resp, login_request const& request, std::chrono::system_clock::time_point start); 54 | 55 | public: 56 | 57 | login_api(const device_info& device, login_cache& cache) : device(device), cache(cache) { 58 | } 59 | 60 | task_ptr perform(const std::string& email, const std::string& password); 61 | 62 | // this function will perform login using the specified access token 63 | // this token is NOT the same as the token that you can provide to set_token 64 | task_ptr perform_with_access_token(const std::string& access_token, const std::string& email = std::string(), 65 | bool add_account = false); 66 | 67 | task_ptr verify(); 68 | 69 | 70 | task_ptr fetch_service_auth_cookie(const std::string& service, const std::string& app, 71 | certificate cert, bool force_refresh = false); 72 | 73 | 74 | void set_checkin_data(const checkin_result& result); 75 | 76 | bool has_token() const { 77 | return token.length() > 0; 78 | } 79 | 80 | std::string const& get_token() const { 81 | return token; 82 | } 83 | 84 | std::string const& get_email() const { 85 | return email; 86 | } 87 | 88 | void set_token(std::string const& email, std::string const& token) { 89 | this->email = email; 90 | this->token = token; 91 | } 92 | 93 | }; 94 | 95 | } -------------------------------------------------------------------------------- /include/playapi/login_cache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace playapi { 8 | 9 | class login_cache { 10 | 11 | public: 12 | virtual void cache(std::string const& service, std::string const& app, std::string const& token, 13 | std::chrono::system_clock::time_point expire) = 0; 14 | 15 | virtual std::string get_cached(std::string const& service, std::string const& app) = 0; 16 | 17 | }; 18 | 19 | } -------------------------------------------------------------------------------- /include/playapi/task.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "util/http.h" 9 | 10 | namespace playapi { 11 | 12 | template 13 | class task; 14 | 15 | template 16 | using task_ptr = std::shared_ptr>; 17 | 18 | template 19 | class task : public std::enable_shared_from_this> { 20 | 21 | public: 22 | virtual ~task() {} 23 | virtual void call(Args&&... args, std::function success, 24 | std::function error) = 0; 25 | virtual void call(Args&&... args) = 0; 26 | 27 | template 28 | task_ptr then(task_ptr task); 29 | 30 | template 31 | task_ptr then(std::function task); 32 | 33 | template 34 | task_ptr then(std::function ()> task); 35 | 36 | }; 37 | 38 | template 39 | class task : public std::enable_shared_from_this> { 40 | 41 | public: 42 | virtual ~task() {} 43 | virtual void call(Args&&... args, std::function success, 44 | std::function error) = 0; 45 | virtual T call(Args&&... args) = 0; 46 | 47 | template 48 | task_ptr then(task_ptr task); 49 | 50 | template 51 | task_ptr then(std::function task); 52 | 53 | template 54 | task_ptr then(std::function (T)> task); 55 | 56 | }; 57 | 58 | template 59 | class pre_set_task : public task { 60 | 61 | private: 62 | T value; 63 | 64 | public: 65 | explicit pre_set_task(T value) : value(std::move(value)) { 66 | } 67 | 68 | static task_ptr make(T value) { 69 | return task_ptr(new pre_set_task(value)); 70 | } 71 | 72 | void call(std::function success, std::function error) { 73 | auto valuet = value; 74 | success(std::move(valuet)); 75 | } 76 | 77 | T call() { 78 | return value; 79 | } 80 | 81 | }; 82 | 83 | template 84 | class function_task; 85 | 86 | template 87 | class function_task : public task { 88 | 89 | private: 90 | std::function func; 91 | 92 | public: 93 | function_task(std::function func) : func(std::move(func)) { 94 | } 95 | 96 | static task_ptr make(std::function func) { 97 | return task_ptr(new function_task(func)); 98 | } 99 | 100 | void call(Args&&... args, std::function success, std::function error) override { 101 | try { 102 | func(std::forward(args)...); 103 | success(); 104 | } catch (std::exception& e) { 105 | error(std::current_exception()); 106 | } 107 | } 108 | 109 | void call(Args&&... args) override { 110 | return func(std::forward(args)...); 111 | } 112 | 113 | }; 114 | 115 | template 116 | class function_task : public task { 117 | 118 | private: 119 | std::function func; 120 | 121 | public: 122 | function_task(std::function func) : func(std::move(func)) { 123 | } 124 | 125 | static task_ptr make(std::function func) { 126 | return task_ptr(new function_task(func)); 127 | } 128 | 129 | void call(Args&&... args, std::function success, std::function error) override { 130 | try { 131 | success(func(std::forward(args)...)); 132 | } catch (std::exception& e) { 133 | error(std::current_exception()); 134 | } 135 | } 136 | 137 | T call(Args&&... args) override { 138 | return func(std::forward(args)...); 139 | } 140 | 141 | }; 142 | 143 | template 144 | class flat_function_task : public task { 145 | 146 | private: 147 | std::function (Args&&...)> func; 148 | 149 | public: 150 | flat_function_task(std::function (Args&&...)> func) : func(std::move(func)) { 151 | } 152 | 153 | static task_ptr make(std::function (Args&&...)> func) { 154 | return task_ptr(new flat_function_task(func)); 155 | } 156 | 157 | void call(Args&&... args, std::function success, std::function error) override { 158 | task_ptr ret; 159 | try { 160 | ret = func(std::forward(args)...); 161 | if (!ret) 162 | throw std::runtime_error("flat_function_task's function must return a valid pointer"); 163 | } catch (std::exception& e) { 164 | error(std::current_exception()); 165 | return; 166 | } 167 | ret->call(success, error); 168 | } 169 | 170 | T call(Args&&... args) override { 171 | return func(std::forward(args)...)->call(); 172 | } 173 | 174 | }; 175 | 176 | template 177 | class merged_task; 178 | 179 | template 180 | class merged_task : public task { 181 | 182 | private: 183 | task_ptr t1; 184 | task_ptr t2; 185 | 186 | public: 187 | merged_task(task_ptr t1, task_ptr t2) : t1(std::move(t1)), t2(std::move(t2)) { 188 | } 189 | 190 | void call(Args&&... args, std::function success, 191 | std::function error) override { 192 | auto t2 = this->t2; 193 | t1->call(args..., [t2, success, error](T&& t) { 194 | t2->call(std::move(t), success, error); 195 | }, [error](std::exception_ptr e) { 196 | error(e); 197 | }); 198 | } 199 | 200 | void call(Args&&... args) override { 201 | return t2->call(t1->call(args...)); 202 | } 203 | 204 | }; 205 | 206 | template 207 | class merged_task : public task { 208 | 209 | private: 210 | task_ptr t1; 211 | task_ptr t2; 212 | 213 | public: 214 | merged_task(task_ptr t1, task_ptr t2) : t1(std::move(t1)), t2(std::move(t2)) { 215 | } 216 | 217 | void call(Args&&... args, std::function success, 218 | std::function error) override { 219 | auto t2 = this->t2; 220 | t1->call(args..., [t2, success, error]() { 221 | t2->call(success, error); 222 | }, [error](std::exception_ptr e) { 223 | error(e); 224 | }); 225 | } 226 | 227 | T2 call(Args&&... args) override { 228 | t1->call(args...); 229 | return t2->call(); 230 | } 231 | 232 | }; 233 | 234 | template 235 | class merged_task : public task { 236 | 237 | private: 238 | task_ptr t1; 239 | task_ptr t2; 240 | 241 | public: 242 | merged_task(task_ptr t1, task_ptr t2) : t1(std::move(t1)), t2(std::move(t2)) { 243 | } 244 | 245 | void call(Args&&... args, std::function success, 246 | std::function error) override { 247 | auto t2 = this->t2; 248 | t1->call(args..., [t2, success, error](T&& t) { 249 | t2->call(std::move(t), success, error); 250 | }, [error](std::exception_ptr e) { 251 | error(e); 252 | }); 253 | } 254 | 255 | T2 call(Args&&... args) override { 256 | return t2->call(t1->call(args...)); 257 | } 258 | 259 | }; 260 | 261 | template 262 | template 263 | inline task_ptr task::then(task_ptr task) { 264 | return task_ptr(new merged_task(this->shared_from_this(), std::move(task))); 265 | } 266 | 267 | template 268 | template 269 | inline task_ptr task::then(std::function task) { 270 | return this->then(function_task::make(std::move(task))); 271 | } 272 | 273 | template 274 | template 275 | inline task_ptr task::then(std::function ()> task) { 276 | return this->then(flat_function_task::make(std::move(task))); 277 | } 278 | 279 | template 280 | template 281 | inline task_ptr task::then(task_ptr task) { 282 | return task_ptr(new merged_task(this->shared_from_this(), std::move(task))); 283 | } 284 | 285 | template 286 | template 287 | inline task_ptr task::then(std::function task) { 288 | return this->then(function_task::make(std::move(task))); 289 | } 290 | 291 | template 292 | template 293 | inline task_ptr task::then(std::function (T)> task) { 294 | return this->then(flat_function_task::make(std::move(task))); 295 | } 296 | 297 | } -------------------------------------------------------------------------------- /include/playapi/util/base64.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace playapi { 6 | 7 | class base64 { 8 | 9 | private: 10 | 11 | static char table[65]; 12 | static unsigned char reverse_table[256]; 13 | static bool reverse_table_initialized; 14 | 15 | static void init_reverse_table(); 16 | 17 | public: 18 | 19 | static std::string encode(const std::string& input); 20 | 21 | static std::string decode(const std::string& input, const char* skip_chars = "\r\n"); 22 | 23 | }; 24 | 25 | } -------------------------------------------------------------------------------- /include/playapi/util/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace playapi { 9 | 10 | class config { 11 | 12 | private: 13 | 14 | struct entry { 15 | bool is_array; 16 | std::string text; 17 | std::vector array; 18 | }; 19 | 20 | std::map entries; 21 | 22 | static std::regex escape_key_regex; 23 | static std::regex unescape_key_regex; 24 | static std::regex escape_val_regex; 25 | static std::regex unescape_val_regex; 26 | static std::regex unescape_regex; 27 | 28 | public: 29 | 30 | static std::istream& read_line(std::istream& stream, std::string& line); 31 | 32 | static void write_value(const std::string& value, std::ostream& stream, bool array = false); 33 | 34 | static std::string unescape_value(const std::string& value); 35 | 36 | /* formatting options */ 37 | size_t indent = 4; 38 | bool array_append_comma = false; 39 | 40 | void set(std::string name, std::string val); 41 | 42 | void set_int(std::string name, int val); 43 | 44 | void set_long(std::string name, long long val); 45 | 46 | void set_bool(std::string name, bool b); 47 | 48 | void set_array(std::string name, std::vector val); 49 | 50 | 51 | std::string get(std::string name, std::string default_val = "") const; 52 | 53 | int get_int(std::string name, int default_val = 0) const; 54 | 55 | long long get_long(std::string name, long long default_val = 0) const; 56 | 57 | bool get_bool(std::string name, bool default_val = false) const; 58 | 59 | std::vector 60 | get_array(std::string name, std::vector default_val = std::vector()) const; 61 | 62 | 63 | void load(std::istream& stream); 64 | 65 | void save(std::ostream& stream); 66 | 67 | }; 68 | 69 | } -------------------------------------------------------------------------------- /include/playapi/util/http.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "http_request_pool.h" 10 | 11 | namespace playapi { 12 | 13 | class http_request; 14 | 15 | class url_encoded_entity { 16 | 17 | public: 18 | 19 | std::vector> pairs; 20 | 21 | void add_pair(const std::string& key, const std::string& val); 22 | 23 | std::string encode(CURL* curl) const; 24 | 25 | std::string encode() const; 26 | 27 | }; 28 | 29 | struct http_response { 30 | 31 | private: 32 | 33 | CURL* curl; 34 | CURLcode curlCode; 35 | long statusCode; 36 | std::string body; 37 | 38 | public: 39 | 40 | http_response(CURL* curl, CURLcode curlCode, long statusCode, std::string body); 41 | http_response(http_response&& r); 42 | ~http_response(); 43 | 44 | http_response& operator=(http_response&& r); 45 | 46 | operator bool() const { return curlCode == CURLE_OK; } 47 | 48 | inline long get_status_code() const { return statusCode; } 49 | 50 | inline const std::string& get_body() const { return body; } 51 | 52 | }; 53 | 54 | enum class http_method { 55 | GET, POST, PUT 56 | }; 57 | 58 | class http_request { 59 | 60 | public: 61 | typedef std::function progress_callback; 63 | typedef std::function output_callback; 64 | 65 | private: 66 | 67 | std::string url; 68 | std::string body; 69 | http_method method; 70 | std::map headers; 71 | std::string user_agent; 72 | std::string encoding; 73 | long timeout = 10L; 74 | bool follow_location = false; 75 | progress_callback callback_progress; 76 | output_callback callback_output; 77 | 78 | static size_t curl_stringstream_write_func(void* ptr, size_t size, size_t nmemb, std::stringstream* s); 79 | static size_t curl_write_func(void* ptr, size_t size, size_t nmemb, output_callback* s); 80 | static int curl_xferinfo(void* ptr, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); 81 | 82 | CURL* build(std::stringstream& output, bool copy_body = false); 83 | 84 | struct pool_entry : public http_request_pool::base_entry { 85 | std::stringstream output; 86 | std::function success; 87 | std::function error; 88 | progress_callback callback_progress; 89 | output_callback callback_output; 90 | 91 | ~pool_entry() override = default; 92 | void done(CURL* curl, CURLcode code) override; 93 | }; 94 | 95 | public: 96 | 97 | http_request() {} 98 | 99 | http_request(std::string url) : url(std::move(url)) {} 100 | 101 | void set_url(const std::string& url) { this->url = url; } 102 | 103 | void set_body(const std::string& str) { this->body = str; } 104 | 105 | void set_body(const url_encoded_entity& ent); 106 | 107 | void set_gzip_body(const std::string& str); 108 | 109 | void set_method(http_method method) { this->method = method; } 110 | 111 | void add_header(const std::string& key, const std::string& value); 112 | 113 | void set_user_agent(const std::string& ua) { this->user_agent = ua; } 114 | 115 | void set_encoding(const std::string& encoding) { this->encoding = encoding; } 116 | 117 | void set_custom_output_func(output_callback callback) { this->callback_output = callback; } 118 | 119 | void set_timeout(long timeout) { this->timeout = timeout; } 120 | 121 | void set_follow_location(bool follow_location) { this->follow_location = follow_location; } 122 | 123 | void set_progress_callback(progress_callback callback) { this->callback_progress = callback; } 124 | 125 | http_response perform(); 126 | 127 | CURL* perform(std::function success, std::function error, 128 | http_request_pool& pool = http_request_pool::default_instance()); 129 | 130 | }; 131 | 132 | } -------------------------------------------------------------------------------- /include/playapi/util/http_request_pool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace playapi { 10 | 11 | class http_request_pool { 12 | 13 | private: 14 | struct socket_data { 15 | http_request_pool* pool; 16 | curl_socket_t socket; 17 | uv_poll_t poll_handle; 18 | 19 | socket_data(http_request_pool* pool, curl_socket_t s); 20 | 21 | void delete_later(); 22 | }; 23 | 24 | CURLM* curlm; 25 | std::thread thread; 26 | uv_loop_t loop; 27 | uv_timer_t timeout_timer; 28 | uv_async_t notify_handle; 29 | std::mutex mutex; 30 | bool stopping = false; 31 | std::vector add_queue, remove_queue; 32 | 33 | void run(); 34 | 35 | void interrupt(); 36 | 37 | void handle_interrupt(); 38 | 39 | void handle_socket_action(curl_socket_t socket, int flags); 40 | 41 | static int curl_socket_func(CURL* curl, curl_socket_t s, int what, void* userp, void* socketp); 42 | 43 | static int curl_timer_func(CURLM* multi, long timeout_ms, void *userp); 44 | 45 | public: 46 | static http_request_pool& default_instance() { 47 | static http_request_pool pool; 48 | return pool; 49 | } 50 | 51 | struct base_entry { 52 | virtual ~base_entry() = default; 53 | virtual void done(CURL* curl, CURLcode code) = 0; 54 | }; 55 | 56 | http_request_pool(); 57 | ~http_request_pool(); 58 | 59 | CURLM* handle() { return curlm; } 60 | 61 | void add(CURL* curl) { 62 | mutex.lock(); 63 | add_queue.push_back(curl); 64 | mutex.unlock(); 65 | interrupt(); 66 | } 67 | 68 | void remove(CURL* curl) { 69 | mutex.lock(); 70 | remove_queue.push_back(curl); 71 | mutex.unlock(); 72 | interrupt(); 73 | } 74 | 75 | }; 76 | 77 | } -------------------------------------------------------------------------------- /include/playapi/util/rand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace playapi { 6 | 7 | class rand { 8 | 9 | private: 10 | 11 | static bool initialized; 12 | 13 | static std::mt19937 rng; 14 | 15 | public: 16 | 17 | static void initialize(); 18 | 19 | template 20 | static T next_int(T min, T max) { 21 | if (!initialized) 22 | initialize(); 23 | std::uniform_int_distribution dist(min, max); 24 | return dist(rng); 25 | } 26 | 27 | }; 28 | 29 | } -------------------------------------------------------------------------------- /lib/playapi/api.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace playapi; 12 | 13 | task_ptr api::set_auth(login_api& login) { 14 | std::lock_guard l(auth_mutex); 15 | this->login = &login; 16 | return login.fetch_service_auth_cookie("androidmarket", "com.android.vending", login_api::certificate::google)-> 17 | then([this](std::string&& token) { 18 | std::lock_guard l(auth_mutex); 19 | auth_email = this->login->get_email(); 20 | auth_token = token; 21 | }); 22 | } 23 | 24 | task_ptr api::invalidate_token() { 25 | return login->fetch_service_auth_cookie("androidmarket", "com.android.vending", login_api::certificate::google, true)-> 26 | then([this](std::string&& token) { 27 | std::lock_guard l(auth_mutex); 28 | auth_email = this->login->get_email(); 29 | auth_token = token; 30 | }); 31 | } 32 | 33 | void api::set_checkin_data(const checkin_result& result) { 34 | std::lock_guard l(auth_mutex); 35 | checkin_data = result; 36 | } 37 | 38 | std::string api::build_user_agent() { 39 | std::stringstream ua; 40 | ua << "Android-Finsky/14.2.63-all [0] [PR] 240807813 (api=3,versionCode=81426300,"; 41 | ua << "sdk=" << device.build_sdk_version << ","; 42 | ua << "device=" << device.build_device << ","; 43 | ua << "hardware=" << device.build_product << ","; 44 | ua << "product=" << device.build_build_product << ","; 45 | ua << "platformVersionRelease=" << device.build_version_string << ","; 46 | ua << "buildId=" << device.build_id << ","; 47 | ua << "isWideScreen=" << (device.wide_screen ? "1" : "0") << ","; 48 | ua << "supportedAbis="; 49 | bool first = true; 50 | for (auto const& abi : device.config_native_platforms) { 51 | if (!first) 52 | ua << ";"; 53 | ua << abi; 54 | first = false; 55 | } 56 | ua << ")"; 57 | return ua.str(); 58 | } 59 | 60 | void api::add_headers(http_request& req, const request_options& options) { 61 | std::lock_guard l(auth_mutex); 62 | std::lock_guard l2 (info_mutex); 63 | assert(auth_token.length() > 0); 64 | req.set_user_agent(build_user_agent()); 65 | req.add_header("Authorization", "GoogleLogin auth=" + auth_token); 66 | if (options.content_type == request_content_type::protobuf) 67 | req.add_header("Content-Type", "application/x-protobuf"); 68 | std::string locale = device.locale; 69 | std::replace(locale.begin(), locale.end(), '_', '-'); 70 | req.add_header("Accept-Language", locale); 71 | if (checkin_data.android_id != 0) 72 | req.add_header("X-DFE-Device-Id", checkin_data.get_string_android_id()); 73 | if (options.include_content_filters) 74 | req.add_header("X-DFE-Content-Filters", ""); 75 | if (options.include_network_type) 76 | req.add_header("X-DFE-Network-Type", "4"); 77 | req.add_header("X-DFE-Request-Params", "timeoutMs=2500"); 78 | req.add_header("X-DFE-Client-Id", "am-android-google"); 79 | if (options.include_checkin_consistency_token && !checkin_data.device_data_version_info.empty()) 80 | req.add_header("X-DFE-Device-Checkin-Consistency-Token", checkin_data.device_data_version_info); 81 | if (options.include_device_config_token && !device_config_token.empty()) 82 | req.add_header("X-DFE-Device-Config-Token", device_config_token); 83 | if (options.include_toc_cookie && !toc_cookie.empty()) 84 | req.add_header("X-DFE-Cookie", toc_cookie); 85 | experiments.add_headers(req); 86 | } 87 | 88 | api::request_task api::send_request(http_method method, const std::string& path, const std::string& bin_data, 89 | const request_options& options) { 90 | using ret_type = proto::finsky::response::ResponseWrapper; 91 | http_request req(url + path); 92 | req.set_method(method); 93 | add_headers(req, options); 94 | req.set_body(bin_data); 95 | return http_task::make(req)->then([this, method, path, bin_data, options](http_response&& resp) { 96 | if (!resp) 97 | throw std::runtime_error("Failed to send request"); 98 | if (resp.get_status_code() == 401) { 99 | return invalidate_token()->then([this, method, path, bin_data, options]() { 100 | return send_request(method, path, bin_data, options); 101 | }); 102 | } 103 | ret_type ret; 104 | if (!ret.ParseFromString(resp.get_body())) 105 | throw std::runtime_error("Failed to parse response"); 106 | #ifndef NDEBUG 107 | printf("api response body = %s\n", ret.DebugString().c_str()); 108 | #endif 109 | if (ret.has_targets()) { 110 | std::lock_guard lock (info_mutex); 111 | experiments.set_targets(ret.targets()); 112 | } 113 | return pre_set_task::make(ret); 114 | }); 115 | } 116 | 117 | api::request_task api::send_request(http_method method, const std::string& path, const request_options& options) { 118 | return send_request(method, path, std::string(), options); 119 | } 120 | 121 | api::request_task api::send_request(http_method method, const std::string& path, 122 | const std::vector>& pairs, 123 | const request_options& options) { 124 | url_encoded_entity u; 125 | for (const auto& pair : pairs) 126 | u.add_pair(pair.first, pair.second); 127 | return send_request(method, path, u.encode(), options); 128 | } 129 | 130 | api::request_task api::fetch_user_settings() { 131 | return send_request(http_method::GET, "userSettings", request_options()); 132 | } 133 | 134 | api::request_task api::fetch_toc() { 135 | request_options opt; 136 | opt.include_device_config_token = true; 137 | opt.include_toc_cookie = true; 138 | return send_request(http_method::GET, "toc", opt); 139 | } 140 | 141 | api::request_task api::upload_device_config(const std::string& gcm_reg_id, bool upload_full_config) { 142 | request_options opt; 143 | opt.content_type = request_content_type::protobuf; 144 | opt.include_device_config_token = true; 145 | 146 | proto::finsky::device_config::UploadDeviceConfigRequest req; 147 | if (upload_full_config) 148 | device.fill_device_config_proto(*req.mutable_deviceconfiguration()); 149 | if (gcm_reg_id.length() > 0) 150 | req.set_gcmregistrationid(gcm_reg_id); 151 | req.mutable_shortdescription()->set_brand(device.build_brand); 152 | req.mutable_shortdescription()->set_manufacturer(device.build_manufacturer); 153 | req.mutable_shortdescription()->set_fingerprint(device.build_fingerprint); 154 | req.mutable_shortdescription()->set_usercount(1); 155 | req.mutable_shortdescription()->set_securitypatch(device.build_security_patch); 156 | req.mutable_dataservicesubscriber(); 157 | #ifndef NDEBUG 158 | printf("Upload Device Config: %s\n", req.DebugString().c_str()); 159 | #endif 160 | return send_request(http_method::POST, "uploadDeviceConfig", req.SerializeAsString(), opt); 161 | } 162 | 163 | api::request_task api::accept_tos(const std::string& token, bool allow_marketing_emails) { 164 | url_encoded_entity e; 165 | e.add_pair("toscme", allow_marketing_emails ? "true" : "false"); 166 | e.add_pair("tost", token); 167 | return send_request(http_method::POST, "acceptTos", e.encode(), request_options()); 168 | } 169 | 170 | api::request_task api::get_search_suggestions(std::string q, int backend_id, int icon_size, bool request_navigational) { 171 | url_encoded_entity e; 172 | e.add_pair("q", q); 173 | e.add_pair("c", std::to_string(backend_id)); 174 | e.add_pair("ssis", std::to_string(icon_size)); 175 | e.add_pair("sst", "2"); 176 | if (request_navigational) 177 | e.add_pair("sst", "3"); 178 | return send_request(http_method::GET, "searchSuggest?" + e.encode(), request_options()); 179 | } 180 | 181 | api::request_task api::search(const std::string& q, int backend_id) { 182 | url_encoded_entity e; 183 | e.add_pair("c", std::to_string(backend_id)); 184 | e.add_pair("q", q); 185 | return send_request(http_method::GET, "search?" + e.encode(), request_options()); 186 | } 187 | 188 | api::request_task api::details(const std::string& app) { 189 | url_encoded_entity e; 190 | e.add_pair("doc", app); 191 | return send_request(http_method::GET, "details?" + e.encode(), request_options()); 192 | } 193 | 194 | api::request_task api::delivery(const std::string& app, int version_code, const std::string& library_token, 195 | const std::string& delivery_token, int previous_version_code, 196 | const std::vector& patch_formats, const std::string& cert_hash, 197 | const std::string& self_update_md5_cert_hash) { 198 | url_encoded_entity e; 199 | e.add_pair("doc", app); 200 | e.add_pair("ot", "1"); 201 | if (library_token.length() > 0) 202 | e.add_pair("st", library_token); 203 | if (version_code != -1) 204 | e.add_pair("vc", std::to_string(version_code)); 205 | if (previous_version_code != -1) { 206 | e.add_pair("bvc", std::to_string(previous_version_code)); 207 | for (auto& p : patch_formats) 208 | e.add_pair("pf", p); 209 | } 210 | if (self_update_md5_cert_hash.length() > 0) 211 | e.add_pair("shh", self_update_md5_cert_hash); 212 | if (cert_hash.length() > 0) 213 | e.add_pair("ch", cert_hash); 214 | e.add_pair("fdcf", "1"); 215 | e.add_pair("fdcf", "2"); 216 | if (delivery_token.length() > 0) 217 | e.add_pair("dtok", delivery_token); 218 | return send_request(http_method::GET, "delivery?" + e.encode(), request_options()); 219 | } 220 | 221 | api::request_task api::bulk_details(std::vector const& v) { 222 | request_options opt; 223 | opt.include_device_config_token = true; 224 | opt.content_type = request_content_type::protobuf; 225 | 226 | proto::finsky::details::BulkDetailsRequest req; 227 | for (auto const& p : v) { 228 | auto e = req.add_entry(); 229 | e->set_docid(p.name); 230 | if (p.installed_version_code != -1) 231 | e->set_installedversioncode(p.installed_version_code); 232 | e->set_includedetails(p.include_details); 233 | } 234 | return send_request(http_method::POST, "bulkDetails", req.SerializeAsString(), opt); 235 | } -------------------------------------------------------------------------------- /lib/playapi/checkin.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace playapi; 13 | 14 | std::string checkin_result::get_string_android_id() const { 15 | std::stringstream ss; 16 | ss << std::hex << android_id; 17 | return ss.str(); 18 | } 19 | 20 | checkin_api::checkin_api(device_info const& device) : device(device) { 21 | // 22 | } 23 | 24 | task_ptr checkin_api::add_auth(login_api& login) { 25 | return login.fetch_service_auth_cookie("ac2dm", "com.google.android.gsf", login_api::certificate::google, true)-> 26 | then([this, &login](std::string&& token) { 27 | auth_user user; 28 | user.email = login.get_email(); 29 | user.auth_cookie = token; 30 | assert(user.email.length() > 0 && user.auth_cookie.length() > 0); 31 | auth.push_back(user); 32 | }); 33 | } 34 | 35 | task_ptr checkin_api::perform_checkin(const checkin_result& last) { 36 | assert(auth.size() > 0); 37 | 38 | // build checkin request 39 | proto::gsf::AndroidCheckinRequest req; 40 | req.set_version(3); 41 | req.set_fragment(0); 42 | proto::gsf::AndroidCheckinProto* checkin = req.mutable_checkin(); 43 | checkin->set_devicetype((int) device.type); 44 | if (last.time == 0) { 45 | checkin->set_lastcheckinmsec(0); 46 | proto::gsf::AndroidEventProto* event = checkin->add_event(); 47 | event->set_tag("event_log_start"); 48 | event->set_timemsec(std::time(nullptr) * 1000LL - 49 | rand::next_int(30LL * 1000LL, 5L * 60LL * 1000LL)); // 30s-5m before now 50 | } else { 51 | checkin->set_lastcheckinmsec(last.time); 52 | req.set_securitytoken(last.security_token); 53 | req.set_devicedataversioninfo(last.device_data_version_info); 54 | } 55 | if (device.gservices_digest.length() > 0) 56 | req.set_digest(device.gservices_digest); 57 | if (last.android_id != 0) 58 | req.set_id((long long) last.android_id); 59 | 60 | proto::gsf::AndroidBuildProto* build = checkin->mutable_build(); 61 | if (device.build_fingerprint.length() > 0) 62 | build->set_id(device.build_fingerprint); 63 | if (device.build_product.length() > 0) 64 | build->set_product(device.build_product); 65 | if (device.build_brand.length() > 0) 66 | build->set_carrier(device.build_brand); 67 | if (device.build_radio.length() > 0) 68 | build->set_radio(device.build_radio); 69 | if (device.build_bootloader.length() > 0) 70 | build->set_bootloader(device.build_bootloader); 71 | if (device.build_device.length() > 0) 72 | build->set_device(device.build_device); 73 | if (device.build_model.length() > 0) 74 | build->set_model(device.build_model); 75 | if (device.build_manufacturer.length() > 0) 76 | build->set_manufacturer(device.build_manufacturer); 77 | if (device.build_build_product.length() > 0) 78 | build->set_buildproduct(device.build_build_product); 79 | build->set_timestamp(device.build_timestamp); 80 | build->set_sdkversion(device.build_sdk_version); 81 | build->set_otainstalled(device.build_ota_installed); 82 | 83 | if (device.build_client.length() > 0) 84 | build->set_client(device.build_client); 85 | build->set_googleservices(device.build_google_services); 86 | for (const auto& e : device.build_google_packages) { 87 | proto::gsf::AndroidBuildProto::PackageVersion* pkg = build->add_googlepackage(); 88 | pkg->set_name(e.first); 89 | pkg->set_version(e.second); 90 | } 91 | if (device.build_security_patch.length() > 0) 92 | build->set_securitypatch(device.build_security_patch); 93 | 94 | device.fill_device_config_proto(*req.mutable_deviceconfiguration(), true); 95 | checkin->set_voicecapable(device.voice_capable); 96 | 97 | req.set_locale(device.locale); 98 | req.set_timezone(device.time_zone); 99 | req.set_loggingid(device.random_logging_id); 100 | 101 | if (device.roaming.length() > 0) 102 | checkin->set_roaming(device.roaming); 103 | if (device.cell_operator.length() > 0) 104 | checkin->set_celloperator(device.cell_operator); 105 | if (device.sim_operator.length() > 0) 106 | checkin->set_celloperator(device.sim_operator); 107 | checkin->set_usernumber(device.user_number); 108 | req.set_userserialnumber(device.user_serial_number); 109 | 110 | if (device.mac_addr_type.size() > 0 && device.mac_addr.size() > 0) { 111 | req.add_macaddrtype(device.mac_addr_type); 112 | req.add_macaddr(device.mac_addr); 113 | } 114 | if (device.meid.size() > 0) 115 | req.set_meid(device.meid); 116 | if (device.serial_number.size() > 0) 117 | req.set_serialnumber(device.serial_number); 118 | for (const auto& e : device.ota_certs) 119 | req.add_otacert(e); 120 | auth_mutex.lock(); 121 | for (const auto& user : auth) { 122 | req.add_accountcookie("[" + user.email + "]"); 123 | req.add_accountcookie(user.auth_cookie); 124 | } 125 | auth_mutex.unlock(); 126 | 127 | http_request http("https://android.clients.google.com/checkin"); 128 | http.add_header("Content-type", "application/x-protobuffer"); 129 | http.set_encoding("gzip,deflate"); 130 | http.add_header("Content-encoding", "gzip"); 131 | http.add_header("Accept-encoding", "gzip"); 132 | http.set_method(http_method::POST); 133 | http.set_user_agent( 134 | "Dalvik/2.1.0 (Linux; U; Android " + device.build_version_string + "; " + device.build_model + " Build/" + 135 | device.build_id + ")"); 136 | #ifndef NDEBUG 137 | printf("Checkin data: %s\n", req.DebugString().c_str()); 138 | #endif 139 | http.set_gzip_body(req.SerializeAsString()); 140 | 141 | return http_task::make(http)->then([](http_response&& http_resp) { 142 | if (!http_resp) 143 | throw std::runtime_error("Failed to send checkin"); 144 | 145 | proto::gsf::AndroidCheckinResponse resp; 146 | if (!resp.ParseFromString(http_resp.get_body())) 147 | throw std::runtime_error("Failed to parse checkin"); 148 | #ifndef NDEBUG 149 | printf("Checkin response: %s\n", resp.DebugString().c_str()); 150 | #endif 151 | checkin_result res; 152 | res.time = resp.timemsec(); 153 | res.android_id = resp.androidid(); 154 | res.security_token = resp.securitytoken(); 155 | res.device_data_version_info = resp.devicedataversioninfo(); 156 | return res; 157 | }); 158 | } -------------------------------------------------------------------------------- /lib/playapi/device_info.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace playapi; 12 | 13 | device_type playapi::device_type_from_string(const std::string& str) { 14 | if (str == "phone") 15 | return device_type::phone; 16 | if (str == "tablet") 17 | return device_type::tablet; 18 | if (str == "tv") 19 | return device_type::tv; 20 | if (str == "glass") 21 | return device_type::glass; 22 | if (str == "wearable") 23 | return device_type::wearable; 24 | return device_type::unknown; 25 | } 26 | std::string playapi::device_type_to_string(device_type type) { 27 | switch (type) { 28 | case device_type::phone: 29 | return "phone"; 30 | case device_type::tablet: 31 | return "tablet"; 32 | case device_type::tv: 33 | return "tv"; 34 | case device_type::glass: 35 | return "glass"; 36 | case device_type::wearable: 37 | return "wearable"; 38 | default: 39 | return "unknown"; 40 | } 41 | } 42 | 43 | device_touch_screen playapi::device_touch_screen_from_string(const std::string& str) { 44 | if (str == "notouch") 45 | return device_touch_screen::notouch; 46 | if (str == "stylus") 47 | return device_touch_screen::stylus; 48 | if (str == "finger") 49 | return device_touch_screen::finger; 50 | return device_touch_screen::undefinied; 51 | } 52 | std::string playapi::device_touch_screen_to_string(device_touch_screen type) { 53 | switch (type) { 54 | case device_touch_screen::notouch: 55 | return "notouch"; 56 | case device_touch_screen::stylus: 57 | return "stylus"; 58 | case device_touch_screen::finger: 59 | return "finger"; 60 | default: 61 | return "undefinied"; 62 | } 63 | } 64 | 65 | device_keyboard playapi::device_keyboard_from_string(const std::string& str) { 66 | if (str == "nokeys") 67 | return device_keyboard::nokeys; 68 | if (str == "qwerty") 69 | return device_keyboard::qwerty; 70 | if (str == "twelvekey") 71 | return device_keyboard::twelvekey; 72 | return device_keyboard::undefinied; 73 | } 74 | std::string playapi::device_keyboard_to_string(device_keyboard type) { 75 | switch (type) { 76 | case device_keyboard::nokeys: 77 | return "nokeys"; 78 | case device_keyboard::qwerty: 79 | return "qwerty"; 80 | case device_keyboard::twelvekey: 81 | return "twelvekey"; 82 | default: 83 | return "undefinied"; 84 | } 85 | } 86 | 87 | device_navigation playapi::device_navigation_from_string(const std::string& str) { 88 | if (str == "nonav") 89 | return device_navigation::nonav; 90 | if (str == "dpad") 91 | return device_navigation::dpad; 92 | if (str == "trackball") 93 | return device_navigation::trackball; 94 | if (str == "wheel") 95 | return device_navigation::wheel; 96 | return device_navigation::undefinied; 97 | } 98 | std::string playapi::device_navigation_to_string(device_navigation type) { 99 | switch (type) { 100 | case device_navigation::nonav: 101 | return "nonav"; 102 | case device_navigation::dpad: 103 | return "dpad"; 104 | case device_navigation::trackball: 105 | return "trackball"; 106 | case device_navigation::wheel: 107 | return "wheel"; 108 | default: 109 | return "undefinied"; 110 | } 111 | } 112 | 113 | device_screen_layout playapi::device_screen_layout_from_string(const std::string& str) { 114 | if (str == "small") 115 | return device_screen_layout::small; 116 | if (str == "normal") 117 | return device_screen_layout::normal; 118 | if (str == "large") 119 | return device_screen_layout::large; 120 | if (str == "xlarge") 121 | return device_screen_layout::xlarge; 122 | return device_screen_layout::undefinied; 123 | } 124 | std::string playapi::device_screen_layout_to_string(device_screen_layout type) { 125 | switch (type) { 126 | case device_screen_layout::small: 127 | return "small"; 128 | case device_screen_layout::normal: 129 | return "normal"; 130 | case device_screen_layout::large: 131 | return "large"; 132 | case device_screen_layout::xlarge: 133 | return "xlarge"; 134 | default: 135 | return "undefinied"; 136 | } 137 | } 138 | 139 | device_info::device_info() { 140 | std::vector features = {"android.hardware.audio.output", "android.hardware.bluetooth", 141 | "android.hardware.camera", "android.hardware.camera.any", 142 | "android.hardware.camera.autofocus", "android.hardware.camera.flash", 143 | "android.hardware.camera.front", "android.hardware.ethernet", 144 | "android.hardware.faketouch", "android.hardware.location", 145 | "android.hardware.location.gps", "android.hardware.location.network", 146 | "android.hardware.microphone", "android.hardware.screen.landscape", 147 | "android.hardware.screen.portrait", "android.hardware.sensor.accelerometer", 148 | "android.hardware.sensor.compass", "android.hardware.sensor.gyroscope", 149 | "android.hardware.sensor.light", "android.hardware.sensor.proximity", 150 | "android.hardware.touchscreen", "android.hardware.touchscreen.multitouch", 151 | "android.hardware.touchscreen.multitouch.distinct", 152 | "android.hardware.touchscreen.multitouch.jazzhand", 153 | "android.hardware.usb.accessory", "android.hardware.usb.host", 154 | "android.hardware.wifi", "android.hardware.wifi.direct", 155 | "android.software.app_widgets", "android.software.backup", 156 | "android.software.connectionservice", "android.software.device_admin", 157 | "android.software.input_methods", "android.software.live_wallpaper", 158 | "android.software.managed_users", "android.software.print", 159 | "android.software.sip", "android.software.sip.voip", 160 | "android.software.voice_recognizers", "android.software.webview", 161 | "com.google.android.feature.GOOGLE_BUILD", 162 | "com.google.android.feature.GOOGLE_EXPERIENCE"}; 163 | for (const std::string& feature : features) 164 | config_system_features.push_back({feature, 0}); 165 | } 166 | 167 | void device_info::load(config& conf) { 168 | type = device_type_from_string(conf.get("device_type", device_type_to_string(type))); 169 | build_fingerprint = conf.get("build.fingerprint", build_fingerprint); 170 | build_id = conf.get("build.id", build_id); 171 | build_product = conf.get("build.product", build_product); 172 | build_brand = conf.get("build.brand", build_brand); 173 | build_radio = conf.get("build.radio", build_radio); 174 | build_bootloader = conf.get("build.bootloader", build_bootloader); 175 | build_client = conf.get("build.client", build_client); 176 | build_timestamp = conf.get_long("build.timestamp", build_timestamp); 177 | build_google_services = conf.get_int("build.google_services", build_google_services); 178 | build_device = conf.get("build.device", build_device); 179 | build_sdk_version = conf.get_int("build.sdk_version", build_sdk_version); 180 | build_version_string = conf.get("build.version_string", build_version_string); 181 | build_model = conf.get("build.model", build_model); 182 | build_manufacturer = conf.get("build.manufacturer", build_manufacturer); 183 | build_build_product = conf.get("build.build_product", build_build_product); 184 | build_ota_installed = conf.get_bool("build.ota_installed", build_ota_installed); 185 | auto pkgs_list = conf.get_array("build.google_packages"); 186 | if (pkgs_list.size() > 0) { 187 | build_google_packages.clear(); 188 | for (const auto& pkg : pkgs_list) { 189 | auto iof = pkg.find(':'); 190 | if (iof == std::string::npos) 191 | continue; 192 | build_google_packages.push_back({pkg.substr(0, iof), std::stoi(pkg.substr(iof + 1))}); 193 | } 194 | } 195 | build_security_patch = conf.get("build.security_patch", build_security_patch); 196 | 197 | config_touch_screen = device_touch_screen_from_string( 198 | conf.get("config.touch_screen", device_touch_screen_to_string(config_touch_screen))); 199 | config_keyboard = device_keyboard_from_string( 200 | conf.get("config.keyboard", device_keyboard_to_string(config_keyboard))); 201 | config_navigation = device_navigation_from_string( 202 | conf.get("config.navigation", device_navigation_to_string(config_navigation))); 203 | config_screen_layout = device_screen_layout_from_string( 204 | conf.get("config.screen_layout", device_screen_layout_to_string(config_screen_layout))); 205 | config_has_hard_keyboard = conf.get_bool("config.has_hard_keyboard", config_has_hard_keyboard); 206 | config_has_five_way_navig = conf.get_bool("config.has_five_way_navig", config_has_five_way_navig); 207 | config_screen_density = conf.get_int("config.screen_density", config_screen_density); 208 | config_gles_version = conf.get_int("config.gles_version", config_gles_version); 209 | config_system_shared_libraries = conf.get_array("config.system_shared_libraries", config_system_shared_libraries); 210 | auto features_list = conf.get_array("config.system_features"); 211 | if (features_list.size() > 0) { 212 | config_system_features.clear(); 213 | for (const auto& feature : features_list) { 214 | std::string name = feature; 215 | int gles_ver = 0; 216 | auto iof = name.find(':'); 217 | if (iof != std::string::npos) { 218 | gles_ver = std::stoi(name.substr(iof + 1)); 219 | name = name.substr(0, iof); 220 | } 221 | config_system_features.push_back({name, gles_ver}); 222 | } 223 | } 224 | config_native_platforms = conf.get_array("config.native_platforms", config_native_platforms); 225 | config_screen_width = conf.get_int("config.screen_width", config_screen_width); 226 | config_screen_height = conf.get_int("config.screen_height", config_screen_height); 227 | config_system_supported_locales = conf.get_array("config.system_supported_locales", 228 | config_system_supported_locales); 229 | config_gl_extensions = conf.get_array("config.gl_extensions", config_gl_extensions); 230 | config_smallest_screen_width_dp = conf.get_int("config.smallest_screen_width_dp", config_smallest_screen_width_dp); 231 | config_low_ram = conf.get_bool("config.low_raw", config_low_ram); 232 | config_total_ram = conf.get_long("config.total_ram", config_total_ram); 233 | config_cores = conf.get_int("config.cores", config_cores); 234 | voice_capable = conf.get_bool("voice_capable", voice_capable); 235 | wide_screen = conf.get_bool("wide_screen", wide_screen); 236 | ota_certs = conf.get_array("ota_certs", ota_certs); 237 | 238 | config_keyguard_device_secure = conf.get_bool("config.keyguard_device_secure", config_keyguard_device_secure); 239 | country = conf.get("country", country); 240 | locale = conf.get("locale", locale); 241 | time_zone = conf.get("time_zone", time_zone); 242 | 243 | gservices_digest = conf.get(gservices_digest, gservices_digest); 244 | 245 | roaming = conf.get("roaming", roaming); 246 | cell_operator = conf.get("cell_operator", cell_operator); 247 | sim_operator = conf.get("sim_operator", sim_operator); 248 | user_number = conf.get_int("user_number", user_number); 249 | user_serial_number = conf.get_int("user_serial_number", user_serial_number); 250 | 251 | mac_addr_type = conf.get("mac_addr_type", mac_addr_type); 252 | if (mac_addr_type == "none") 253 | mac_addr_type = ""; 254 | mac_addr = conf.get("mac_addr", mac_addr); 255 | if (mac_addr == "generate") { 256 | mac_addr = ""; 257 | mac_addr_generate = true; 258 | } 259 | meid = conf.get("meid", meid); 260 | if (meid == "generate") { 261 | meid = ""; 262 | meid_generate = true; 263 | } 264 | serial_number = conf.get("serial_number", serial_number); 265 | if (serial_number.length() > strlen("generate(") && 266 | memcmp(serial_number.c_str(), "generate(", strlen("generate(")) == 0) { 267 | serial_number = serial_number.substr(strlen("generate(")); 268 | serial_number = serial_number.substr(0, serial_number.find(')')); 269 | auto iof = serial_number.find(','); 270 | 271 | serial_number_generate = true; 272 | serial_number_generate_length = std::stoi(serial_number.substr(0, iof)); 273 | serial_number_generate_chars = serial_number.substr(iof + 1); 274 | serial_number_generate_chars = config::unescape_value(serial_number_generate_chars.substr( 275 | serial_number_generate_chars.find_first_not_of(" "))); 276 | serial_number = ""; 277 | } else if (serial_number == "generate") { 278 | serial_number_generate = true; 279 | serial_number = ""; 280 | } 281 | } 282 | 283 | void device_info::generate_fields() { 284 | // generate serial number 285 | if (serial_number_generate) { 286 | if (generated_serial_number.length() <= 0) { 287 | for (int i = 0; i < serial_number_generate_length; i++) { 288 | int j = rand::next_int(0, (int) serial_number_generate_chars.length() - 1); 289 | generated_serial_number.push_back(serial_number_generate_chars[j]); 290 | } 291 | } 292 | serial_number = generated_serial_number; 293 | } 294 | 295 | // generate mac address 296 | if (mac_addr_generate) { 297 | if (generated_mac_addr.length() <= 0) { 298 | unsigned char bin_addr[6]; 299 | for (int i = 0; i < 6; i++) 300 | bin_addr[i] = rand::next_int((unsigned char) 0, UCHAR_MAX); 301 | bin_addr[0] &= 0xfe; 302 | bin_addr[0] |= 0x02; 303 | std::stringstream ss; 304 | for (int i = 0; i < 6; i++) 305 | ss << std::hex << std::setfill('0') << std::setw(2) << (int) bin_addr[i]; 306 | generated_mac_addr = ss.str(); 307 | assert(generated_mac_addr.length() == 12); 308 | } 309 | mac_addr = generated_mac_addr; 310 | } 311 | 312 | // generate meid 313 | if (meid_generate) { 314 | if (generated_meid.length() <= 0) { 315 | generated_meid.resize(15); 316 | int chksm = 0; 317 | for (int i = 0; i < 14; i++) { 318 | int j = rand::next_int(0, 9); 319 | generated_meid[i] = (char) (j + '0'); 320 | if (j * 2 >= 10) 321 | chksm += (j * 2) % 10 + 1; 322 | else 323 | chksm += j * 2; 324 | } 325 | generated_meid[14] = (char) ((10 - chksm % 10) + '0'); 326 | } 327 | meid = generated_meid; 328 | } 329 | 330 | // generate random logging id 331 | if (random_logging_id == 0) 332 | random_logging_id = rand::next_int(1, LLONG_MAX); 333 | } 334 | 335 | void device_info::fill_device_config_proto(proto::gsf::DeviceConfigurationProto& config, 336 | bool feature_add_gles_version_if_zero) const { 337 | config.set_touchscreen((int) config_touch_screen); 338 | config.set_keyboard((int) config_keyboard); 339 | config.set_navigation((int) config_navigation); 340 | config.set_screenlayout((int) config_screen_layout); 341 | config.set_hashardkeyboard(config_has_hard_keyboard); 342 | config.set_hasfivewaynavigation(config_has_five_way_navig); 343 | config.set_screendensity(config_screen_density); 344 | config.set_glesversion(config_gles_version); 345 | for (const auto& e : config_system_shared_libraries) 346 | config.add_systemsharedlibrary(e); 347 | for (const auto& e : config_system_features) 348 | config.add_systemavailablefeature(e.first); 349 | for (const auto& e : config_native_platforms) 350 | config.add_nativeplatform(e); 351 | config.set_screenwidth(config_screen_width); 352 | config.set_screenheight(config_screen_height); 353 | for (const auto& e : config_system_supported_locales) 354 | config.add_systemsupportedlocale(e); 355 | for (const auto& e : config_gl_extensions) 356 | config.add_glextension(e); 357 | config.set_smallestscreenwidthdp(config_smallest_screen_width_dp); 358 | config.set_lowramdevice(config_low_ram); 359 | config.set_totalmemorybytes(config_total_ram); 360 | config.set_maxnumofcpucores(config_cores); 361 | for (const auto& e : config_system_features) { 362 | proto::gsf::DeviceConfigurationProto::FeatureWithGLVersion* feature = config.add_newsystemavailablefeature(); 363 | feature->set_name(e.first); 364 | if (feature_add_gles_version_if_zero || e.second != 0) 365 | feature->set_glesversion(e.second); 366 | } 367 | config.set_screenlayout2((int) 2); 368 | config.set_keyguarddevicesecure(config_keyguard_device_secure); 369 | } -------------------------------------------------------------------------------- /lib/playapi/experiments.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace playapi; 9 | 10 | std::set experiments_list::supported_experiments = {12609897, 12609898, 12609899, 12609900, 12609901, 12610192, 12617884, 12605163, 12605193, 12602328, 12603136, 12602050, 12605524, 12605725, 12602810, 12602812, 12602814, 12602816, 12603704, 12602035, 12603133, 89, 12602358, 12611632, 87, 12602761, 12602049, 12604203, 12606756, 12603961, 12603137, 12602735, 12603102, 742, 12602880, 12602636, 12603110, 12602819, 12602796, 12603108, 12603105, 12606634, 12603097, 12603067, 12603106, 12603098, 12602778, 12602779, 12602780, 12602795, 12604148, 12603117, 12602981, 12603286, 12610657, 12603428, 12604244, 12604245, 12604246, 12603772, 12608143, 12606710, 12603401, 12603109, 12603396, 12603367, 12610437, 12620479, 12612210, 12612626, 12613802, 12603513, 12603385, 12603301, 12603329, 12603516, 12606902, 12606904, 12606908, 12606916, 12606932, 12611365, 12606876, 12608821, 12608822, 12608823, 12608824, 12608825, 12608870, 12609117, 12609118, 12603517, 12603647, 12603707, 12603787, 12603719, 12604322, 12605436, 12613372, 12612541, 12612650, 12612651, 12612652, 12612653, 12612654, 12612667, 12620763, 12603770, 12603948, 12605594, 12605701, 12606771, 12608685, 12609856, 12608873, 12604572, 12604043, 12604266, 12607839, 12604323, 12604230, 12605131, 12605994, 12607638, 12604154, 12607073, 12604300, 12603992, 12604383, 12604224, 12604379, 12604380, 12604381, 12604360, 12605120, 12605124, 12610660, 12622374, 12608255, 12608340, 12608402, 12608403, 12608405, 12608404, 12608406, 12608407, 12617519, 12617520, 12617521, 12617522, 12617523, 12617524, 12605174, 12605175, 12605176, 12605177, 12605178, 12605179, 12605180, 12604072, 12604073, 12604074, 12604075, 12604076, 12604164, 12604165, 12604166, 12604167, 12606496, 12604524, 12605079, 12605080, 12605081, 12605208, 12605209, 12619714, 12605212, 12605213, 12619715, 12605215, 12605419, 12605261, 12609857, 12609858, 12609859, 12605458, 12620853, 12620854, 12620855, 12620856, 12620857, 12605648, 12605417, 12617783, 12619031, 12605418, 12605728, 12605750, 12605765, 12605993, 12606635, 12605956, 12606692, 12606497, 12605975, 12606982, 12608423, 12607000, 12608688, 12609928, 12609861, 12608807, 12606444, 12607353, 12607692, 12606829, 12613101, 12607749, 12608135, 12607225, 12607380, 12607314, 12607338, 12607603, 12607553, 12607818, 12607746, 12613454, 12611545, 12611082, 12611607, 12608225, 12608282, 12608339, 12608271, 12608272, 12608409, 12608410, 12608411, 12608412, 12608413, 12609134, 12608498, 12608826, 12619749, 12608725, 12608795, 12617419, 12611207, 12611459, 12609314, 12609315, 12609316, 12609286, 12609603, 12609656, 12609657, 12609806, 12609807, 12609726, 12609676, 12610177, 12609170, 12610205, 12610438, 12611789, 12612263, 12616872, 12619461, 12610799, 12611069, 12611253, 12611610, 12611038, 12613073, 12611548, 12610420, 12614849, 12619632, 12608887, 12611404, 12611586, 12611636, 12610748, 12614241, 12611058, 12612385, 12617441, 12612611, 12612246, 12613100, 12613099, 12613469, 12614383, 12612198, 12614846, 12611192, 12615220, 12616628, 12616209, 12616251, 12616260, 12616127, 12616321, 12616199, 12616624, 12612366, 12616397, 12617487, 12616420, 12617418, 12616559, 12616560, 12616561, 12616694, 12618334, 12618335, 12618336, 12618337, 12618338, 12619116, 12619166, 12619131, 12618805, 12619085, 12618915, 12618916, 12618917, 12617427, 12617606, 12617436, 12617604, 12617485, 12616313, 12617689, 12617885, 12617782, 12618333, 12618706, 12618749, 12618989, 12618990, 12618942, 12620867, 12622345, 12619065, 12618829, 12619074, 12619183, 12619184, 12619185, 12619186, 12619120, 12619633, 12620349, 12618726, 12620006, 12622573, 12620267, 12617943, 12619925, 12619926, 12619927, 12620008, 12620294, 12620064, 12620120, 12620148, 12619928, 12620121, 12620266, 12619024, 12620061, 12620054, 12619996, 12620495, 12620496, 12617808, 12620811, 12620843, 12620804, 12620805, 12620806, 12622334, 12622452, 12618928, 12622495, 12622545, 12604225, 12604226, 12604227, 12604228, 12604229, 12606611, 12606612, 12606613, 12606614, 12606615, 12606616, 12606617, 12606618, 12606619, 12606620, 12604101, 12611536, 12603123, 12603125, 12603127, 12603128, 12603129, 12603130, 12603642, 12602748, 12604235, 12604236, 12614972, 12603144, 12603145, 12603146, 12603147, 12603148, 12603149, 12603118, 12603119, 12603120, 12603121, 12603122, 12603247, 12603248, 12603249, 12603250, 12603251, 12620351, 12602374, 12604366, 12603408, 12603629, 12604357, 12604382, 12607368, 12606007, 12606677, 12605290, 12616572, 12610569, 12606765, 12608094, 12608854, 12607739, 12610202, 12608625, 12606978, 12608896, 12609510, 12609511, 12609722, 12609522, 12609703, 12610416, 12610417, 12610418, 12610398, 12611376, 12610679, 12610421, 12620435, 12620436, 12620437, 12610211, 12610490, 12610491, 12609980, 12609956, 12611039, 12611040, 12611208, 12611703, 12611622, 12613776, 12613110, 12614201, 12617586, 12617693, 12617696, 12619200, 12620147, 12612655, 12616358, 12619128, 12619556, 12619566, 12619745, 12619746, 12617688, 12618830, 12617924, 12620345, 12608663}; 11 | 12 | void experiments_list::set_targets(const proto::finsky::response::Targets& experiments) { 13 | enabled_experiments.clear(); 14 | other_experiments.clear(); 15 | for (long long e : experiments.targetid()) { 16 | if (supported_experiments.count(e) > 0) { 17 | enabled_experiments.insert(e); 18 | } else { 19 | other_experiments.insert(e); 20 | } 21 | } 22 | } 23 | 24 | void experiments_list::set_targets(const std::string& experiments) { 25 | enabled_experiments.clear(); 26 | other_experiments.clear(); 27 | std::stringstream ss(experiments); 28 | std::string es; 29 | while (std::getline(ss, es, ',')) { 30 | long long e = std::stoi(es); 31 | if (supported_experiments.count(e) > 0) { 32 | enabled_experiments.insert(e); 33 | } else { 34 | other_experiments.insert(e); 35 | } 36 | } 37 | } 38 | 39 | bool experiments_list::is_enabled(long long experiment) const { 40 | return (supported_experiments.count(experiment) > 0); 41 | } 42 | 43 | void experiments_list::add_headers(http_request& req) { 44 | if (is_enabled(ENCODED_TARGETS)) { 45 | proto::finsky::EncodedTargets encoded_targets; 46 | encoded_targets.set_version(1); 47 | long long p = 0; 48 | for (long long e : enabled_experiments) { 49 | encoded_targets.add_supportedtarget(e - p); 50 | p = e; 51 | } 52 | p = 0; 53 | for (long long e : other_experiments) { 54 | encoded_targets.add_othertarget(e - p); 55 | p = e; 56 | } 57 | req.add_header("X-DFE-Encoded-Targets", base64::encode(encoded_targets.SerializeAsString())); 58 | } else { 59 | std::stringstream ss; 60 | bool f = true; 61 | for (long long e : enabled_experiments) { 62 | if (f) 63 | f = false; 64 | else 65 | ss << ','; 66 | ss << e; 67 | } 68 | req.add_header("X-DFE-Supported-Targets", ss.str()); 69 | ss.clear(); 70 | f = true; 71 | for (long long e : other_experiments) { 72 | if (f) 73 | f = false; 74 | else 75 | ss << ','; 76 | ss << e; 77 | } 78 | req.add_header("X-DFE-Other-Targets", ss.str()); 79 | } 80 | } 81 | 82 | std::string experiments_list::get_comma_separated_target_list() const { 83 | std::stringstream ss; 84 | std::set m = enabled_experiments; 85 | m.insert(other_experiments.begin(), other_experiments.end()); 86 | bool f = true; 87 | for (long long e : m) { 88 | if (f) 89 | f = false; 90 | else 91 | ss << ','; 92 | ss << e; 93 | } 94 | return ss.str(); 95 | } -------------------------------------------------------------------------------- /lib/playapi/file_login_cache.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace playapi; 6 | 7 | void file_login_cache::load() { 8 | std::lock_guard l (mutex); 9 | config c; 10 | { 11 | std::ifstream fs(path); 12 | c.load(fs); 13 | } 14 | int n = c.get_int("token_count", 0); 15 | for (int i = 0; i < n; i++) { 16 | auto p = "token." + std::to_string(i) + "."; 17 | auto exp = std::chrono::system_clock::time_point(std::chrono::milliseconds(c.get_long(p + "expires"))); 18 | auth_cookies[{c.get(p + "service"), c.get(p + "app")}] = {c.get(p + "token"), exp}; 19 | } 20 | } 21 | 22 | void file_login_cache::save() { 23 | std::lock_guard l (mutex); 24 | config c; 25 | int count = 0; 26 | for (auto const& e : auth_cookies) { 27 | if (e.second.second <= std::chrono::system_clock::now()) 28 | continue; 29 | auto p = "token." + std::to_string(count) + "."; 30 | c.set(p + "service", e.first.first); 31 | c.set(p + "app", e.first.second); 32 | c.set(p + "token", e.second.first); 33 | c.set_long(p + "expires", std::chrono::duration_cast( 34 | e.second.second.time_since_epoch()).count()); 35 | count++; 36 | } 37 | c.set_int("token_count", count); 38 | { 39 | std::ofstream fs(path); 40 | c.save(fs); 41 | } 42 | } -------------------------------------------------------------------------------- /lib/playapi/login.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace playapi; 10 | 11 | task_ptr login_api::perform(const login_request& request) { 12 | auto start = std::chrono::system_clock::now(); 13 | http_request req("https://android.clients.google.com/auth"); 14 | req.set_user_agent("GoogleAuth/1.4 (" + device.build_product + " " + device.build_id + "); gzip"); 15 | req.set_method(http_method::POST); 16 | url_encoded_entity ent; 17 | ent.add_pair("accountType", "HOSTED_OR_GOOGLE"); 18 | if (request.via_password) { 19 | ent.add_pair("Email", request.email); 20 | ent.add_pair("Passwd", request.password); 21 | } else { 22 | ent.add_pair("Token", request.token); 23 | if (request.is_access_token) 24 | ent.add_pair("ACCESS_TOKEN", "1"); 25 | if (!request.email.empty()) 26 | ent.add_pair("Email", request.email); 27 | if (request.is_add_account) { 28 | ent.add_pair("add_account", "1"); 29 | } else if (!request.email.empty()) { 30 | ent.add_pair("check_email", "1"); 31 | } 32 | } 33 | if (android_id.length() > 0) { 34 | ent.add_pair("androidId", android_id); 35 | req.add_header("device", android_id); 36 | } 37 | 38 | ent.add_pair("has_permission", "1"); 39 | ent.add_pair("service", request.service); 40 | ent.add_pair("source", "android"); 41 | ent.add_pair("app", request.app); 42 | req.add_header("app", request.app); 43 | ent.add_pair("device_country", device.country); 44 | ent.add_pair("lang", device.locale); 45 | ent.add_pair("sdk_version", std::to_string(device.build_sdk_version)); 46 | if (request.cert == certificate::android) 47 | ent.add_pair("client_sig", "61ed377e85d386a8dfee6b864bd85b0bfaa5af81"); 48 | else 49 | ent.add_pair("client_sig", "38918a453d07199354f8b19af05ec6562ced5788"); 50 | ent.add_pair("system_partition", "1"); 51 | req.set_body(ent); 52 | 53 | return http_task::make(req)->then([this, request, start](http_response&& res) { 54 | return handle_response(res, request, start); 55 | }); 56 | } 57 | 58 | std::string login_api::handle_response(http_response& resp, login_request const& request, 59 | std::chrono::system_clock::time_point start) { 60 | const std::string& body = resp.get_body(); 61 | std::map respValMap; 62 | for (size_t i = 0; i < body.length();) { 63 | size_t j = body.find('=', i); 64 | size_t k = body.find('\n', j); 65 | if (j == std::string::npos || k == std::string::npos) 66 | break; 67 | std::string key = body.substr(i, j - i); 68 | std::string val = body.substr(j + 1, k - (body[k - 1] == '\r' ? 1 : 0) - (j + 1)); 69 | respValMap[key] = val; 70 | i = k + 1; 71 | } 72 | if (respValMap.count("Error") > 0) 73 | throw std::runtime_error("Login error: " + respValMap.at("Error")); 74 | if (respValMap.count("Auth") <= 0) 75 | throw std::runtime_error("No auth cookie field returned"); 76 | auto auth_val = respValMap.at("Auth"); 77 | auto expires = start; 78 | if (respValMap.count("Expires") > 0) { 79 | expires += std::chrono::seconds(std::stoi(respValMap.at("Expires"))); 80 | } else { 81 | if (strncmp(auth_val.c_str(), "oauth2:", 7) == 0) 82 | expires += std::chrono::hours(1); 83 | else 84 | expires += std::chrono::hours(24 * 14); 85 | } 86 | cache.cache(request.service, request.app, auth_val, expires); 87 | if (request.via_password || request.is_access_token) { 88 | if (respValMap.count("Token") <= 0) 89 | throw std::runtime_error("No Oauth2 token returned"); 90 | set_token(request.email, respValMap.at("Token")); 91 | } 92 | if (request.is_access_token) { 93 | if (respValMap.count("Email") <= 0) 94 | throw std::runtime_error("No Email returned"); 95 | this->email = respValMap.at("Email"); 96 | } 97 | return auth_val; 98 | } 99 | 100 | task_ptr login_api::perform(const std::string& email, const std::string& password) { 101 | return perform(login_request("ac2dm", "com.google.android.gsf", email, password))->then([](std::string&&){}); 102 | } 103 | 104 | task_ptr login_api::perform_with_access_token(const std::string& access_token, const std::string& email, 105 | bool add_account) { 106 | return perform(login_request("ac2dm", "com.google.android.gsf", email, access_token, true, add_account))-> 107 | then([](std::string&&){}); 108 | } 109 | 110 | task_ptr login_api::verify() { 111 | return perform(login_request("ac2dm", "com.google.android.gsf", std::string(), token, false, false))-> 112 | then([](std::string&&){}); 113 | } 114 | 115 | task_ptr login_api::fetch_service_auth_cookie(const std::string& service, const std::string& app, 116 | certificate cert, bool force_refresh) { 117 | if (token.empty()) 118 | throw std::runtime_error("No user authenticated."); 119 | auto cookie = cache.get_cached(service, app); 120 | if (!cookie.empty() && !force_refresh) 121 | return pre_set_task::make(cookie); 122 | return perform(login_request(service, app, email, token, false, false, cert)); 123 | } 124 | 125 | void login_api::set_checkin_data(const checkin_result& result) { 126 | if (result.android_id != 0) 127 | android_id = result.get_string_android_id(); 128 | } 129 | -------------------------------------------------------------------------------- /lib/playapi/util/base64.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace playapi; 6 | 7 | char base64::table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 8 | unsigned char base64::reverse_table[256]; 9 | bool base64::reverse_table_initialized = false; 10 | 11 | void base64::init_reverse_table() { 12 | memset(reverse_table, 255, sizeof(reverse_table)); 13 | for (int i = 0; i < 64; i++) { 14 | reverse_table[table[i]] = (unsigned char) i; 15 | } 16 | reverse_table['='] = 64; 17 | reverse_table_initialized = true; 18 | } 19 | 20 | std::string base64::encode(const std::string& input) { 21 | size_t block_count = (input.size() + 2) / 3; 22 | std::string outp; 23 | outp.resize(block_count * 4); 24 | unsigned char i0, i1, i2, n0, n1, n2, n3; 25 | for (size_t i = 0; i < block_count; i++) { 26 | i0 = (unsigned char) input[i * 3]; 27 | i1 = (unsigned char) (input.length() > i * 3 + 1 ? input[i * 3 + 1] : 0); 28 | i2 = (unsigned char) (input.length() > i * 3 + 2 ? input[i * 3 + 2] : 0); 29 | n0 = (unsigned char) ((i0 >> 2) & 0x3f); 30 | n1 = (unsigned char) (((i0 & 3) << 4) | ((i1 >> 4) & 0xf)); 31 | n2 = (unsigned char) (((i1 & 0xf) << 2) | ((i2 >> 6) & 3)); 32 | n3 = (unsigned char) (i2 & 0x3f); 33 | outp[i * 4] = table[n0]; 34 | outp[i * 4 + 1] = table[n1]; 35 | outp[i * 4 + 2] = input.length() > i * 3 + 1 ? table[n2] : '='; 36 | outp[i * 4 + 3] = input.length() > i * 3 + 2 ? table[n3] : '='; 37 | } 38 | return std::move(outp); 39 | } 40 | 41 | std::string base64::decode(const std::string& input, const char* skip_chars) { 42 | if (!reverse_table_initialized) 43 | init_reverse_table(); 44 | std::string output; 45 | output.reserve((input.size() + 3) / 4 * 3); 46 | unsigned char i[4]; 47 | size_t p = 0; 48 | while (p < input.size()) { 49 | for (int n = 0; n < 4; n++) { 50 | while (true) { 51 | if ((i[n] = reverse_table[input[p]]) != 255) { 52 | if ((i[n] == 64 && n < 2) || (n == 3 && i[n - 1] == 64 && i[n] != 64)) 53 | throw std::runtime_error("Invalid '=' character"); 54 | p++; 55 | break; 56 | } 57 | if (strchr(skip_chars, input[p]) == NULL) 58 | throw std::runtime_error("Invalid character at " + std::to_string(p)); 59 | if (++p == input.size()) 60 | throw std::runtime_error("Unexpected end of input"); 61 | } 62 | } 63 | output.push_back((unsigned char) ((i[0] << 2) | ((i[1] >> 4) & 3))); 64 | if (i[2] != 64) 65 | output.push_back((unsigned char) (((i[1] & 0xf) << 4) | ((i[2] >> 2) & 0xf))); 66 | if (i[2] != 64 && i[3] != 64) 67 | output.push_back((unsigned char) (((i[2] & 3) << 6) | i[3])); 68 | } 69 | return output; 70 | } 71 | -------------------------------------------------------------------------------- /lib/playapi/util/config.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | using namespace playapi; 7 | 8 | std::regex config::escape_key_regex("=|\\\\|;"); 9 | std::regex config::unescape_key_regex("\\\\(?==|;)"); // remove backslashes before special chars 10 | std::regex config::escape_val_regex("\"|\n|\\\\"); 11 | std::regex config::unescape_val_regex("\\\\(?=\"|\n)"); // remove backslashes before special chars 12 | std::regex config::unescape_regex("\\\\\\\\"); // change double backslashes to a single backslash 13 | 14 | std::string config::get(std::string name, std::string default_val) const { 15 | if (entries.count(name) > 0 && !entries.at(name).is_array) 16 | return entries.at(name).text; 17 | return default_val; 18 | } 19 | 20 | int config::get_int(std::string name, int default_val) const { 21 | if (entries.count(name) > 0 && !entries.at(name).is_array) 22 | return std::stoi(entries.at(name).text); 23 | return default_val; 24 | } 25 | 26 | long long config::get_long(std::string name, long long default_val) const { 27 | if (entries.count(name) > 0 && !entries.at(name).is_array) 28 | return std::stoll(entries.at(name).text); 29 | return default_val; 30 | } 31 | 32 | bool config::get_bool(std::string name, bool default_val) const { 33 | if (entries.count(name) > 0 && !entries.at(name).is_array) { 34 | const std::string& str = entries.at(name).text; 35 | return (str == "true" || str == "1" || str == "on"); 36 | } 37 | return default_val; 38 | } 39 | 40 | std::vector config::get_array(std::string name, std::vector default_val) const { 41 | if (entries.count(name) > 0 && entries.at(name).is_array) 42 | return entries.at(name).array; 43 | return default_val; 44 | } 45 | 46 | void config::set(std::string name, std::string val) { 47 | entries[name] = {false, val, {}}; 48 | } 49 | 50 | void config::set_int(std::string name, int val) { 51 | entries[name] = {false, std::to_string(val), {}}; 52 | } 53 | 54 | void config::set_long(std::string name, long long val) { 55 | entries[name] = {false, std::to_string(val), {}}; 56 | } 57 | 58 | void config::set_bool(std::string name, bool b) { 59 | entries[name] = {false, b ? "true" : "false", {}}; 60 | } 61 | 62 | void config::set_array(std::string name, std::vector val) { 63 | entries[name] = {true, std::string(), val}; 64 | } 65 | 66 | std::string config::unescape_value(const std::string& value) { 67 | if (value.length() > 0 && value[0] == '"') { 68 | if (value[value.size() - 1] != '"') 69 | throw std::runtime_error("Badly formatted value: starts with a quote, but doesn't end with one"); 70 | std::string ret = value.substr(1, value.size() - 2); 71 | ret = std::regex_replace(ret, unescape_val_regex, ""); 72 | ret = std::regex_replace(ret, unescape_regex, "\\"); 73 | return ret; 74 | } else { 75 | return value; 76 | } 77 | } 78 | 79 | void config::write_value(const std::string& value, std::ostream& stream, bool array) { 80 | if (value.length() > 0 && (value[0] == '[' || value[0] == '"' || value[0] == ' ' || 81 | value[value.length() - 1] == '\\' || value.find('\n') != std::string::npos || 82 | (array && value[value.length() - 1] == ','))) { 83 | // must quote & escape 84 | stream << '"' << std::regex_replace(value, escape_val_regex, "\\$&") << '"'; 85 | } else { 86 | stream << value; 87 | } 88 | } 89 | 90 | std::istream& config::read_line(std::istream& stream, std::string& line) { 91 | while (std::getline(stream, line)) { 92 | if (line.size() == 0 || line[0] == ';') 93 | continue; 94 | while (line[line.length() - 1] == '\\') { 95 | line += '\n'; 96 | std::string tmp_line; 97 | if (!std::getline(stream, tmp_line)) 98 | throw std::runtime_error("Bad formatted config line: backslash at the end of the last line."); 99 | line += tmp_line; 100 | } 101 | return stream; 102 | } 103 | return stream; 104 | } 105 | 106 | void config::load(std::istream& stream) { 107 | std::string line; 108 | while (read_line(stream, line)) { 109 | size_t pos = line.find(" = "); 110 | if (pos == std::string::npos) 111 | continue; 112 | std::string key = line.substr(0, pos); 113 | key = std::regex_replace(key, unescape_key_regex, ""); 114 | key = std::regex_replace(key, unescape_regex, "\\"); 115 | std::string val = line.substr(pos + 3); 116 | if (val == "[") { 117 | // array 118 | std::vector vals; 119 | while (true) { 120 | if (!read_line(stream, line)) 121 | throw std::runtime_error("An array was opened, but was not closed."); 122 | if (line == "]") 123 | break; 124 | size_t line_indent; 125 | for (line_indent = 0; line_indent < line.size(); line_indent++) { 126 | if (line[line_indent] != ' ' && line[line_indent] != '\t') 127 | break; 128 | } 129 | if (line[line.length() - 1] == ',') 130 | line = line.substr(0, line.length() - 1); 131 | vals.push_back(unescape_value(line.substr(line_indent))); 132 | } 133 | set_array(key, vals); 134 | continue; 135 | } 136 | set(key, unescape_value(val)); 137 | } 138 | } 139 | 140 | void config::save(std::ostream& stream) { 141 | for (const auto& e : entries) { 142 | stream << std::regex_replace(e.first, escape_key_regex, "\\$&") << " = "; 143 | if (e.second.is_array) { 144 | stream << "["; 145 | bool f = true; 146 | for (const auto& a : e.second.array) { 147 | if (!f) { 148 | if (array_append_comma) 149 | stream << ",\n"; 150 | } else 151 | f = false; 152 | stream << '\n'; 153 | for (int i = 0; i < indent; i++) 154 | stream << ' '; 155 | write_value(a, stream, true); 156 | } 157 | stream << "\n]\n"; 158 | } else { 159 | write_value(e.second.text, stream); 160 | stream << '\n'; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /lib/playapi/util/http.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | using namespace playapi; 7 | 8 | void url_encoded_entity::add_pair(const std::string& key, const std::string& val) { 9 | pairs.push_back({key, val}); 10 | } 11 | 12 | std::string url_encoded_entity::encode(CURL* curl) const { 13 | std::stringstream ss; 14 | bool f = true; 15 | for (const auto& pair : pairs) { 16 | if (f) 17 | f = false; 18 | else 19 | ss << "&"; 20 | 21 | char* escapedKey = curl_easy_escape(curl, pair.first.c_str(), (int) pair.first.length()); 22 | char* escapedVal = curl_easy_escape(curl, pair.second.c_str(), (int) pair.second.length()); 23 | ss << escapedKey << "=" << escapedVal; 24 | curl_free(escapedKey); 25 | curl_free(escapedVal); 26 | } 27 | return ss.str(); 28 | } 29 | 30 | std::string url_encoded_entity::encode() const { 31 | CURL* curl = curl_easy_init(); 32 | assert(curl != nullptr); 33 | std::string ret = encode(curl); 34 | curl_easy_cleanup(curl); 35 | return std::move(ret); 36 | } 37 | 38 | http_response::http_response(CURL* curl, CURLcode curlCode, long statusCode, std::string body) : 39 | curl(curl), curlCode(curlCode), statusCode(statusCode), body(std::move(body)) { 40 | // 41 | } 42 | 43 | http_response::http_response(http_response&& r) : curl(r.curl), curlCode(r.curlCode), statusCode(r.statusCode), 44 | body(r.body) { 45 | r.curl = nullptr; 46 | r.curlCode = CURLE_FAILED_INIT; 47 | r.statusCode = 0; 48 | r.body = std::string(); 49 | } 50 | 51 | http_response& http_response::operator=(http_response&& r) { 52 | curl = r.curl; 53 | curlCode = r.curlCode; 54 | body = r.body; 55 | r.curl = nullptr; 56 | r.curlCode = CURLE_FAILED_INIT; 57 | r.body = std::string(); 58 | return *this; 59 | } 60 | 61 | http_response::~http_response() { 62 | curl_easy_cleanup(curl); 63 | curl = nullptr; 64 | } 65 | 66 | void http_request::set_body(const url_encoded_entity& ent) { 67 | set_body(ent.encode()); 68 | } 69 | 70 | void http_request::add_header(const std::string& key, const std::string& value) { 71 | headers[key] = value; 72 | } 73 | 74 | void http_request::set_gzip_body(const std::string& str) { 75 | z_stream zs; 76 | zs.zalloc = Z_NULL; 77 | zs.zfree = Z_NULL; 78 | zs.opaque = Z_NULL; 79 | int ret = deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY); 80 | assert(ret == Z_OK); 81 | 82 | zs.avail_in = (uInt) str.length(); 83 | zs.next_in = (unsigned char*) str.data(); 84 | std::string out; 85 | while(true) { 86 | out.resize(out.size() + 4096); 87 | zs.avail_out = 4096; 88 | zs.next_out = (unsigned char*) out.data(); 89 | ret = deflate(&zs, Z_FINISH); 90 | assert(ret != Z_STREAM_ERROR); 91 | if (zs.avail_out != 0) { 92 | out.resize(out.size() - zs.avail_out); 93 | break; 94 | } 95 | } 96 | deflateEnd(&zs); 97 | body = std::move(out); 98 | } 99 | 100 | size_t http_request::curl_stringstream_write_func(void* ptr, size_t size, size_t nmemb, std::stringstream* s) { 101 | s->write((char*) ptr, size * nmemb); 102 | return size * nmemb; 103 | } 104 | size_t http_request::curl_write_func(void* ptr, size_t size, size_t nmemb, output_callback* s) { 105 | return (*s)((char*) ptr, size * nmemb); 106 | } 107 | int http_request::curl_xferinfo(void* ptr, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { 108 | progress_callback* req = (progress_callback*) ptr; 109 | (*req)(dltotal, dlnow, ultotal, ulnow); 110 | return 0; 111 | } 112 | 113 | CURL* http_request::build(std::stringstream& output, bool copy_body) { 114 | CURL* curl = curl_easy_init(); 115 | assert(curl != nullptr); 116 | assert(url.length() > 0); 117 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); 118 | switch (method) { 119 | case http_method::GET: 120 | curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); 121 | break; 122 | case http_method::POST: 123 | curl_easy_setopt(curl, CURLOPT_POST, 1L); 124 | break; 125 | case http_method::PUT: 126 | curl_easy_setopt(curl, CURLOPT_PUT, 1L); 127 | break; 128 | } 129 | if (user_agent.length() > 0) 130 | curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent.c_str()); 131 | if (encoding.length() > 0) 132 | curl_easy_setopt(curl, CURLOPT_ENCODING, encoding.c_str()); 133 | if (body.length() > 0) { 134 | curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long) body.length()); 135 | curl_easy_setopt(curl, copy_body ? CURLOPT_COPYPOSTFIELDS : CURLOPT_POSTFIELDS, body.c_str()); 136 | } 137 | if (headers.size() > 0) { 138 | struct curl_slist* chunk = NULL; 139 | if (method == http_method::POST) 140 | chunk = curl_slist_append(chunk, "Expect:"); 141 | for (const auto& header : headers) { 142 | chunk = curl_slist_append(chunk, (header.first + ": " + header.second).c_str()); 143 | } 144 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); 145 | } 146 | curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); 147 | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, follow_location ? 1L : 0L); 148 | 149 | #ifndef NDEBUG 150 | printf("http request: %s, body = %s\n", url.c_str(), body.c_str()); 151 | #endif 152 | if (callback_output) { 153 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &callback_output); 154 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_func); 155 | } else { 156 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &output); 157 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_stringstream_write_func); 158 | } 159 | if (callback_progress) { 160 | curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, curl_xferinfo); 161 | curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &callback_progress); 162 | curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); 163 | } 164 | return curl; 165 | } 166 | 167 | http_response http_request::perform() { 168 | std::stringstream output; 169 | CURL* curl = build(output); 170 | CURLcode ret = curl_easy_perform(curl); 171 | long status; 172 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); 173 | #ifndef NDEBUG 174 | printf("http response body: %s\n", output.str().c_str()); 175 | #endif 176 | return http_response(curl, ret, status, output.str()); 177 | } 178 | 179 | CURL* http_request::perform(std::function success, std::function error, 180 | http_request_pool& pool) { 181 | pool_entry* ei = new pool_entry; 182 | ei->success = success; 183 | ei->error = error; 184 | CURL* curl = build(ei->output, true); 185 | if (callback_output) { 186 | ei->callback_output = callback_output; 187 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ei->callback_output); 188 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_func); 189 | } 190 | if (callback_progress) { 191 | ei->callback_progress = callback_progress; 192 | curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, curl_xferinfo); 193 | curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &ei->callback_progress); 194 | curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); 195 | } 196 | curl_easy_setopt(curl, CURLOPT_PRIVATE, ei); 197 | pool.add(curl); 198 | return curl; 199 | } 200 | 201 | void http_request::pool_entry::done(CURL* curl, CURLcode code) { 202 | long status; 203 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); 204 | #ifndef NDEBUG 205 | printf("http response body: %s\n", output.str().c_str()); 206 | #endif 207 | success(http_response(curl, code, status, output.str())); 208 | } -------------------------------------------------------------------------------- /lib/playapi/util/http_request_pool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace playapi; 7 | 8 | http_request_pool::http_request_pool() { 9 | curlm = curl_multi_init(); 10 | 11 | uv_loop_init(&loop); 12 | uv_timer_init(&loop, &timeout_timer); 13 | timeout_timer.data = this; 14 | uv_async_init(&loop, ¬ify_handle, [](uv_async_t* handle) { 15 | ((http_request_pool*) handle->data)->handle_interrupt(); 16 | }); 17 | notify_handle.data = this; 18 | thread = std::thread(std::bind(&http_request_pool::run, this)); 19 | } 20 | 21 | http_request_pool::~http_request_pool() { 22 | mutex.lock(); 23 | stopping = true; 24 | mutex.unlock(); 25 | interrupt(); 26 | thread.join(); 27 | uv_loop_close(&loop); 28 | } 29 | 30 | void http_request_pool::interrupt() { 31 | uv_async_send(¬ify_handle); 32 | } 33 | 34 | void http_request_pool::handle_interrupt() { 35 | std::unique_lock lock(mutex); 36 | if (stopping) { 37 | curl_multi_cleanup(curlm); 38 | uv_close((uv_handle_t*) &timeout_timer, [](uv_handle_t*){}); 39 | uv_close((uv_handle_t*) ¬ify_handle, [](uv_handle_t*){}); 40 | uv_stop(&loop); 41 | return; 42 | } 43 | for (CURL* handle : add_queue) 44 | curl_multi_add_handle(curlm, handle); 45 | for (CURL* handle : remove_queue) 46 | curl_multi_remove_handle(curlm, handle); 47 | } 48 | 49 | http_request_pool::socket_data::socket_data(http_request_pool* pool, curl_socket_t s) : pool(pool), socket(s) { 50 | uv_poll_init_socket(&pool->loop, &poll_handle, s); 51 | poll_handle.data = this; 52 | } 53 | 54 | void http_request_pool::socket_data::delete_later() { 55 | uv_close((uv_handle_t *) &poll_handle, [](uv_handle_t* handle) { 56 | delete (http_request_pool::socket_data*) handle->data; 57 | }); 58 | } 59 | 60 | void http_request_pool::handle_socket_action(curl_socket_t socket, int flags) { 61 | int handles; 62 | curl_multi_socket_action(curlm, socket, flags, &handles); 63 | 64 | struct CURLMsg* m; 65 | while ((m = curl_multi_info_read(curlm, &handles))) { 66 | if (m->msg == CURLMSG_DONE) { 67 | base_entry* entry; 68 | curl_easy_getinfo(m->easy_handle, CURLINFO_PRIVATE, &entry); 69 | curl_easy_setopt(m->easy_handle, CURLOPT_PRIVATE, NULL); 70 | entry->done(m->easy_handle, m->data.result); 71 | delete entry; 72 | } 73 | } 74 | } 75 | 76 | int http_request_pool::curl_socket_func(CURL* curl, curl_socket_t s, int what, void* userp, void* socketp) { 77 | socket_data* socketd = (socket_data*) socketp; 78 | if (what == CURL_POLL_IN || what == CURL_POLL_OUT || what == CURL_POLL_INOUT) { 79 | if (socketd == nullptr) { 80 | socketd = new socket_data((http_request_pool*) userp, s); 81 | curl_multi_assign(((http_request_pool*) userp)->curlm, s, socketd); 82 | } 83 | int mask = 0; 84 | if (what == CURL_POLL_IN || what == CURL_POLL_INOUT) 85 | mask |= UV_READABLE; 86 | if (what == CURL_POLL_OUT || what == CURL_POLL_INOUT) 87 | mask |= UV_WRITABLE; 88 | uv_poll_start(&socketd->poll_handle, mask, [](uv_poll_t* handle, int status, int events) { 89 | socket_data* data = (socket_data*) handle->data; 90 | int flags = 0; 91 | if (events & UV_READABLE) 92 | flags |= CURL_CSELECT_IN; 93 | if (events & UV_WRITABLE) 94 | flags |= CURL_CSELECT_OUT; 95 | data->pool->handle_socket_action(data->socket, flags); 96 | }); 97 | } else if (what == CURL_POLL_REMOVE && socketd) { 98 | uv_poll_stop(&socketd->poll_handle); 99 | socketd->delete_later(); 100 | curl_multi_assign(((http_request_pool*) userp)->curlm, s, nullptr); 101 | } 102 | return 0; 103 | } 104 | 105 | int http_request_pool::curl_timer_func(CURLM* multi, long timeout_ms, void* userp) { 106 | if (timeout_ms <= 0) { 107 | uv_timer_stop(&((http_request_pool*) userp)->timeout_timer); 108 | if (timeout_ms == 0) 109 | ((http_request_pool*) userp)->handle_socket_action(CURL_SOCKET_TIMEOUT, 0); 110 | } else { 111 | uv_timer_start(&((http_request_pool*) userp)->timeout_timer, [](uv_timer_t* handle) { 112 | ((http_request_pool*) handle->data)->handle_socket_action(CURL_SOCKET_TIMEOUT, 0); 113 | }, timeout_ms, 0); 114 | } 115 | return 0; 116 | } 117 | 118 | void http_request_pool::run() { 119 | curl_multi_setopt(curlm, CURLMOPT_SOCKETFUNCTION, curl_socket_func); 120 | curl_multi_setopt(curlm, CURLMOPT_SOCKETDATA, this); 121 | curl_multi_setopt(curlm, CURLMOPT_TIMERFUNCTION, curl_timer_func); 122 | curl_multi_setopt(curlm, CURLMOPT_TIMERDATA, this); 123 | uv_run(&loop, UV_RUN_DEFAULT); 124 | } 125 | -------------------------------------------------------------------------------- /lib/playapi/util/rand.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace playapi; 4 | 5 | bool rand::initialized = false; 6 | std::mt19937 rand::rng; 7 | 8 | void rand::initialize() { 9 | rng = std::mt19937(std::random_device{}()); 10 | rng.discard(700000); 11 | initialized = true; 12 | } -------------------------------------------------------------------------------- /proto/gsf.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | // generated with https://github.com/egirault/googleplay-api/blob/master/androguard/androproto.py 4 | // then modified a bit by hand to add the new fields that were added after the move to gms from gsf 5 | // also look at https://github.com/microg/android_packages_apps_GmsCore/blob/master/play-services-core/src/main/protos-repo/checkin.proto 6 | package playapi.proto.gsf; 7 | 8 | message AndroidCheckinRequest { 9 | optional string imei = 1; 10 | optional int64 id = 2; 11 | optional string digest = 3; 12 | optional AndroidCheckinProto checkin = 4; 13 | optional string desiredBuild = 5; 14 | optional string locale = 6; 15 | optional int64 loggingId = 7; 16 | optional string marketCheckin = 8; 17 | repeated string macAddr = 9; 18 | optional string meid = 10; 19 | repeated string accountCookie = 11; 20 | optional string timeZone = 12; 21 | optional fixed64 securityToken = 13; 22 | optional int32 version = 14; 23 | repeated string otaCert = 15; 24 | optional string serialNumber = 16; 25 | optional string esn = 17; 26 | optional DeviceConfigurationProto deviceConfiguration = 18; 27 | repeated string macAddrType = 19; 28 | optional int32 fragment = 20; 29 | optional string userName = 21; 30 | optional int32 userSerialNumber = 22; 31 | optional string droidguardResult = 24; 32 | optional string deviceDataVersionInfo = 25; 33 | optional bool fetchSystemUpdates = 29; 34 | } 35 | message AndroidCheckinResponse { 36 | optional bool statsOk = 1; 37 | repeated AndroidIntentProto intent = 2; 38 | optional int64 timeMsec = 3; 39 | optional string digest = 4; 40 | repeated GservicesSetting setting = 5; 41 | optional bool marketOk = 6; 42 | optional fixed64 androidId = 7; 43 | optional fixed64 securityToken = 8; 44 | optional bool settingsDiff = 9; 45 | repeated string deleteSetting = 10; 46 | optional string deviceDataVersionInfo = 12; 47 | } 48 | message GservicesSetting { 49 | optional bytes name = 1; 50 | optional bytes value = 2; 51 | } 52 | message DeviceConfigurationProto { 53 | message FeatureWithGLVersion { 54 | optional string name = 1; 55 | optional int32 glEsVersion = 2; 56 | } 57 | optional int32 touchScreen = 1; 58 | optional int32 keyboard = 2; 59 | optional int32 navigation = 3; 60 | optional int32 screenLayout = 4; 61 | optional bool hasHardKeyboard = 5; 62 | optional bool hasFiveWayNavigation = 6; 63 | optional int32 screenDensity = 7; 64 | optional int32 glEsVersion = 8; 65 | repeated string systemSharedLibrary = 9; 66 | repeated string systemAvailableFeature = 10; 67 | repeated string nativePlatform = 11; 68 | optional int32 screenWidth = 12; 69 | optional int32 screenHeight = 13; 70 | repeated string systemSupportedLocale = 14; 71 | repeated string glExtension = 15; 72 | optional int32 maxApkDownloadSizeMb = 17; 73 | optional int32 smallestScreenWidthDp = 18; 74 | optional bool lowRamDevice = 19; 75 | optional int64 totalMemoryBytes = 20; 76 | optional int32 maxNumOfCpuCores = 21; 77 | repeated FeatureWithGLVersion newSystemAvailableFeature = 26; 78 | optional int32 screenLayout2 = 27; 79 | optional bool keyguardDeviceSecure = 28; 80 | } 81 | message AndroidBuildProto { 82 | message PackageVersion { 83 | optional int32 version = 1; 84 | optional string name = 2; 85 | } 86 | optional string id = 1; 87 | optional string product = 2; 88 | optional string carrier = 3; 89 | optional string radio = 4; 90 | optional string bootloader = 5; 91 | optional string client = 6; 92 | optional int64 timestamp = 7; 93 | optional int32 googleServices = 8; 94 | optional string device = 9; 95 | optional int32 sdkVersion = 10; 96 | optional string model = 11; 97 | optional string manufacturer = 12; 98 | optional string buildProduct = 13; 99 | optional bool otaInstalled = 14; 100 | repeated PackageVersion googlePackage = 15; 101 | optional string securityPatch = 19; 102 | } 103 | message AndroidCheckinReasonProto { 104 | optional int32 reasonType = 1; // what are the possible reason types? 105 | optional int32 attemptCount = 2; 106 | optional string sourcePackage = 3; 107 | optional string sourceClass = 4; 108 | optional bool sourceForce = 5; 109 | } 110 | message AndroidCheckinProto { 111 | optional AndroidBuildProto build = 1; 112 | optional int64 lastCheckinMsec = 2; 113 | repeated AndroidEventProto event = 3; 114 | repeated AndroidStatisticProto stat = 4; 115 | repeated string requestedGroup = 5; 116 | optional string cellOperator = 6; 117 | optional string simOperator = 7; 118 | optional string roaming = 8; 119 | optional int32 userNumber = 9; 120 | optional int32 deviceType = 14; // 1 - unknown, 2 - phone, 3 - tablet, 4 - tv, 5 - glass, 7 - wearable 121 | optional AndroidCheckinReasonProto reason = 15; 122 | optional bool voiceCapable = 18; 123 | } 124 | message AndroidEventProto { 125 | optional string tag = 1; 126 | optional string value = 2; 127 | optional int64 timeMsec = 3; 128 | } 129 | message AndroidIntentProto { 130 | optional string action = 1; 131 | optional string dataUri = 2; 132 | optional string mimeType = 3; 133 | optional string javaClass = 4; 134 | repeated group Extra = 5 { 135 | optional string name = 6; 136 | optional string value = 7; 137 | } 138 | } 139 | message AndroidStatisticProto { 140 | optional string tag = 1; 141 | optional int32 count = 2; 142 | optional float sum = 3; 143 | } -------------------------------------------------------------------------------- /proto/play_browse.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky.browse; 4 | 5 | import "play_common.proto"; 6 | import "play_link.proto"; 7 | 8 | message BrowseResponse { 9 | optional string contentsUrl = 1; 10 | optional string promoUrl = 2; 11 | repeated BrowseLink category = 3; 12 | repeated BrowseLink breadcrumb = 4; 13 | repeated QuickLink quickLink = 5; 14 | optional bytes serverLogsCookie = 6; 15 | optional string title = 7; 16 | optional int32 backendId = 8; 17 | repeated BrowseTab browseTab = 9; 18 | optional int32 landingTabIndex = 10; 19 | optional int32 quickLinkTabIndex = 11; 20 | optional int32 quickLinkFallbackTabIndex = 12; 21 | optional bool isFamilySafe = 14; 22 | optional int32 tabStyle = 15; 23 | } 24 | message BrowseTab { 25 | optional string title = 1; 26 | optional bytes serverLogsCookie = 2; 27 | optional string listUrl = 3; 28 | repeated BrowseLink category = 4; 29 | repeated QuickLink quickLink = 5; 30 | optional string quickLinkTitle = 6; 31 | optional string categoriesTitle = 7; 32 | optional int32 backendId = 8; 33 | optional string highlightsBannerUrl = 9; 34 | optional TabBubble entertainmentTabBubble = 10; 35 | } 36 | message BrowseLink { 37 | optional string name = 1; 38 | optional string dataUrl = 3; 39 | optional bytes serverLogsCookie = 4; 40 | optional Image image = 5; 41 | } 42 | message QuickLink { 43 | optional string name = 1; 44 | optional Image image = 2; 45 | optional link.ResolvedLink link = 3; 46 | optional bool displayRequired = 4; 47 | optional bytes serverLogsCookie = 5; 48 | optional int32 backendId = 6; 49 | optional bool prismStyle = 7; 50 | } 51 | message TabBubble { 52 | optional string text = 1; 53 | } -------------------------------------------------------------------------------- /proto/play_common.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky; 4 | 5 | message HttpCookie { 6 | optional string name = 1; 7 | optional string value = 2; 8 | } 9 | message TimePeriod { 10 | optional int32 unit = 1; 11 | optional int32 count = 2; 12 | } 13 | message MonthAndDay { 14 | optional int32 month = 1; 15 | optional int32 day = 2; 16 | } 17 | message SignedData { 18 | optional string signedData = 1; 19 | optional string signature = 2; 20 | } 21 | message Docid { 22 | optional string backendDocid = 1; 23 | optional int32 type = 2; 24 | optional int32 backend = 3; 25 | } 26 | 27 | message Offer { 28 | optional int64 micros = 1; 29 | optional string currencyCode = 2; 30 | optional string formattedAmount = 3; 31 | repeated Offer convertedPrice = 4; 32 | optional bool checkoutFlowRequired = 5; 33 | optional int64 fullPriceMicros = 6; 34 | optional string formattedFullAmount = 7; 35 | optional int32 offerType = 8; 36 | optional RentalTerms rentalTerms = 9; 37 | optional int64 onSaleDate = 10; 38 | repeated string promotionLabel = 11; 39 | optional SubscriptionTerms subscriptionTerms = 12; 40 | optional string formattedName = 13; 41 | optional string formattedDescription = 14; 42 | optional bool preorder = 15; 43 | optional int32 onSaleDateDisplayTimeZoneOffsetMsec = 16; 44 | optional int32 licensedOfferType = 17; 45 | optional SubscriptionContentTerms subscriptionContentTerms = 18; 46 | optional string offerId = 19; 47 | optional int64 preorderFulfillmentDisplayDate = 20; 48 | optional LicenseTerms licenseTerms = 21; 49 | optional bool temporarilyFree = 22; 50 | optional VoucherOfferTerms voucherTerms = 23; 51 | repeated OfferPayment offerPayment = 24; 52 | optional bool repeatLastPayment = 25; 53 | optional string buyButtonLabel = 26; 54 | optional bool instantPurchaseEnabled = 27; 55 | } 56 | message RentalTerms { 57 | optional int32 DEPRECATEDGrantPeriodSeconds = 1; 58 | optional int32 DEPRECATEDActivatePeriodSeconds = 2; 59 | optional TimePeriod grantPeriod = 3; 60 | optional TimePeriod activatePeriod = 4; 61 | optional int64 grantEndTimeSeconds = 5; 62 | } 63 | message OfferPayment { 64 | optional int64 micros = 1; 65 | optional string currencyCode = 2; 66 | optional OfferPaymentPeriod offerPaymentPeriod = 3; 67 | repeated OfferPaymentOverride offerPaymentOverride = 4; 68 | } 69 | message OfferPaymentPeriod { 70 | optional TimePeriod duration = 1; 71 | optional MonthAndDay start = 2; 72 | optional MonthAndDay end = 3; 73 | } 74 | message OfferPaymentOverride { 75 | optional int64 micros = 1; 76 | optional MonthAndDay start = 2; 77 | optional MonthAndDay end = 3; 78 | } 79 | message SeasonalSubscriptionInfo { 80 | message Payment { 81 | optional int64 micros = 1; 82 | optional string currencyCode = 2; 83 | optional string formattedAmount = 3; 84 | optional TimePeriod period = 4; 85 | } 86 | optional MonthAndDay periodStart = 1; 87 | optional MonthAndDay periodEnd = 2; 88 | optional bool prorated = 4; 89 | optional Payment postTrialConversionPayment = 5; 90 | } 91 | message SubscriptionTerms { 92 | message SubscriptionReplacement { 93 | optional Docid newDocid = 1; 94 | repeated Docid oldDocid = 2; 95 | optional bool keepNextRecurrenceTime = 3; 96 | optional bool replaceOnFirstRecurrence = 4; 97 | } 98 | optional TimePeriod recurringPeriod = 1; 99 | optional TimePeriod trialPeriod = 2; 100 | optional string formattedPriceWithRecurrencePeriod = 3; 101 | optional SeasonalSubscriptionInfo seasonalSubscriptionInfo = 4; 102 | repeated Docid replaceDocid = 5; 103 | optional TimePeriod gracePeriod = 6; 104 | optional bool resignup = 7; 105 | optional int64 initialValidUntilTimestampMsec = 8; 106 | optional string nextPaymentCurrencyCode = 9; 107 | optional int64 nextPaymentPriceMicros = 10; 108 | optional bool enableAppSpecifiedTrialPeriod = 11; 109 | optional SubscriptionReplacement subscriptionReplacement = 12; 110 | } 111 | message SubscriptionContentTerms { 112 | optional Docid requiredSubscription = 1; 113 | } 114 | 115 | message GroupLicenseKey { 116 | optional fixed64 dasherCustomerId = 1; 117 | optional Docid docid = 2; 118 | optional int32 licensedOfferType = 3; 119 | optional int32 type = 4; 120 | optional int32 rentalPeriodDays = 5; 121 | } 122 | message GroupLicenseInfo { 123 | optional int32 licensedOfferType = 1; 124 | optional fixed64 gaiaGroupId = 2; 125 | optional GroupLicenseKey groupLicenseKey = 3; 126 | } 127 | message LicenseTerms { 128 | optional GroupLicenseKey groupLicenseKey = 1; 129 | } 130 | message LicensedDocumentInfo { 131 | repeated fixed64 gaiaGroupId = 1; 132 | optional string groupLicenseCheckoutOrderId = 2; 133 | optional GroupLicenseKey groupLicenseKey = 3; 134 | optional fixed64 assignedByGaiaId = 4; 135 | optional string DEPRECATEDAssignmentId = 5; 136 | } 137 | 138 | message RedemptionRecordKey { 139 | optional int64 publisherId = 1; 140 | optional int64 campaignId = 2; 141 | optional int64 codeGroupId = 3; 142 | optional int64 recordId = 4; 143 | optional int32 type = 5; 144 | } 145 | 146 | message VoucherId { 147 | optional Docid voucherDocid = 1; 148 | optional RedemptionRecordKey key = 2; 149 | } 150 | message VoucherOfferTerms { 151 | repeated Docid voucherDocid = 1; 152 | optional int64 voucherPriceMicros = 2; 153 | optional string voucherFormattedAmount = 3; 154 | } 155 | message LibraryVoucher { 156 | optional VoucherId voucherId = 1; 157 | } 158 | 159 | message OwnershipInfo { 160 | optional int64 initiationTimestampMsec = 1; 161 | optional int64 validUntilTimestampMsec = 2; 162 | optional bool autoRenewing = 3; 163 | optional int64 refundTimeoutTimestampMsec = 4; 164 | optional int64 postDeliveryRefundWindowMsec = 5; 165 | optional SignedData developerPurchaseInfo = 6; 166 | optional bool preordered = 7; 167 | optional bool hidden = 8; 168 | optional RentalTerms rentalTerms = 9; 169 | optional GroupLicenseInfo groupLicenseInfo = 10; 170 | optional LicensedDocumentInfo licensedDocumentInfo = 11; 171 | optional int32 quantity = 12; 172 | optional int64 libraryExpirationTimestampMsec = 14; 173 | optional LibraryVoucher libraryVoucher = 15; 174 | optional Docid bundleDocid = 16; 175 | optional bool bonus = 17; 176 | optional int64 storedValidUntilTimestampMsec = 18; 177 | } 178 | 179 | message Image { 180 | optional int32 imageType = 1; 181 | optional group Dimension = 2 { 182 | optional int32 width = 3; 183 | optional int32 height = 4; 184 | optional int32 aspectRatio = 18; 185 | } 186 | optional string imageUrl = 5; 187 | optional string altTextLocalized = 6; 188 | optional string secureUrl = 7; 189 | optional int32 positionInSequence = 8; 190 | optional bool supportsFifeUrlOptions = 9; 191 | optional group Citation = 10 { 192 | optional string titleLocalized = 11; 193 | optional string url = 12; 194 | } 195 | optional int32 durationSeconds = 14; 196 | optional string fillColorRgb = 15; 197 | optional bool autogen = 16; 198 | optional Attribution attribution = 17; 199 | optional string backgroundColorRgb = 19; 200 | optional ImagePalette palette = 20; 201 | optional int32 deviceClass = 21; 202 | optional bool supportsFifeMonogramOption = 22; 203 | } 204 | message ImagePalette { 205 | optional string lightVibrantRgb = 1; 206 | optional string vibrantRgb = 2; 207 | optional string darkVibrantRgb = 3; 208 | optional string lightMutedRgb = 4; 209 | optional string mutedRgb = 5; 210 | optional string darkMutedRgb = 6; 211 | } 212 | message Attribution { 213 | optional string sourceTitle = 1; 214 | optional string sourceUrl = 2; 215 | optional string licenseTitle = 3; 216 | optional string licenseUrl = 4; 217 | } 218 | 219 | message CertificateSet { 220 | repeated string certificateHash = 1; 221 | } 222 | 223 | message EncodedTargets { 224 | optional int32 version = 1; 225 | repeated int64 supportedTarget = 2; 226 | repeated int64 otherTarget = 3; 227 | } -------------------------------------------------------------------------------- /proto/play_containers.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky.containers; 4 | 5 | import "play_common.proto"; 6 | 7 | message ContainerMetadata { 8 | optional string browseUrl = 1; 9 | optional string nextPageUrl = 2; 10 | optional double relevance = 3; 11 | optional int64 estimatedResults = 4; 12 | optional string analyticsCookie = 5; 13 | optional bool ordered = 6; 14 | repeated ContainerView containerView = 7; 15 | optional Image leftIcon = 8; 16 | } 17 | message ContainerView { 18 | optional bool selected = 1; 19 | optional string title = 2; 20 | optional string listUrl = 3; 21 | optional bytes serverLogsCookie = 4; 22 | } -------------------------------------------------------------------------------- /proto/play_details.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky.details; 4 | 5 | import "play_common.proto"; 6 | import "play_document.proto"; 7 | import "play_link.proto"; 8 | 9 | message BulkDetailsRequest { 10 | message Entry { 11 | required string docid = 1; 12 | optional int32 installedVersionCode = 2; 13 | optional bool includeDetails = 3; 14 | // optional int32 unknown4 = 4; 15 | // optional ??? unknown7 = 7; 16 | } 17 | /* 18 | deprecated 19 | repeated string docid = 1; 20 | optional bool includeChildDocs = 2; 21 | optional bool includeDetails = 3; 22 | optional string sourcePackageName = 4; 23 | repeated int32 installedVersionCode = 7; 24 | */ 25 | repeated Entry entry = 8; 26 | } 27 | message BulkDetailsResponse { 28 | repeated BulkDetailsEntry entry = 1; 29 | } 30 | message BulkDetailsEntry { 31 | optional document.DocV2 doc = 1; 32 | } 33 | 34 | message DetailsResponse { 35 | // deprecated optional DocV1 docV1 = 1; 36 | optional document.Review userReview = 3; 37 | optional document.DocV2 docV2 = 4; 38 | optional string footerHtml = 5; 39 | optional bytes serverLogsCookie = 6; 40 | repeated DiscoveryBadge discoveryBadge = 7; 41 | optional bool enableReviews = 8; 42 | } 43 | message DiscoveryBadge { 44 | optional string title = 1; 45 | optional Image image = 2; 46 | optional int32 backgroundColor = 3; 47 | optional DiscoveryBadgeLink discoveryBadgeLink = 4; 48 | optional bytes serverLogsCookie = 5; 49 | optional bool isPlusOne = 6; 50 | optional float aggregateRating = 7; 51 | optional int32 userStarRating = 8; 52 | optional string downloadCount = 9; 53 | optional string downloadUnits = 10; 54 | optional string contentDescription = 11; 55 | optional PlayerBadge playerBadge = 12; 56 | optional FamilyAgeRangeBadge familyAgeRangeBadge = 13; 57 | optional FamilyCategoryBadge familyCategoryBadge = 14; 58 | } 59 | message DiscoveryBadgeLink { 60 | optional link.Link link = 1; 61 | optional string userReviewsUrl = 2; 62 | optional string criticReviewsUrl = 3; 63 | } 64 | message PlayerBadge { 65 | optional Image overlayIcon = 1; 66 | } 67 | message FamilyAgeRangeBadge { 68 | } 69 | message FamilyCategoryBadge { 70 | } -------------------------------------------------------------------------------- /proto/play_device_config.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky.device_config; 4 | 5 | import "gsf.proto"; 6 | import "play_common.proto"; 7 | 8 | message MobileSubscriber { 9 | optional int64 imsi = 1; 10 | optional string spn = 2; 11 | optional string gid1 = 3; 12 | } 13 | message DataServiceSubscriber { 14 | optional MobileSubscriber mobileSubscriber = 1; 15 | } 16 | message ShortDescription { 17 | // 1 is device admin pkg info 18 | optional string brand = 2; 19 | optional string fingerprint = 3; 20 | optional fixed64 serialHash = 4; 21 | optional int32 userCount = 5; 22 | optional string manufacturer = 6; 23 | optional string securityPatch = 7; 24 | } 25 | message AccountInfo { 26 | optional string androidIdAndUserHash = 1; // sha256(androidId + "-" + accountName) 27 | } 28 | message UploadDeviceConfigRequest { 29 | optional gsf.DeviceConfigurationProto deviceConfiguration = 1; 30 | optional string manufacturer = 2; 31 | optional string gcmRegistrationId = 3; 32 | optional DataServiceSubscriber dataServiceSubscriber = 4; 33 | optional ShortDescription shortDescription = 6; 34 | optional AccountInfo accountInfo = 7; 35 | } 36 | message UploadDeviceConfigResponse { 37 | optional string uploadDeviceConfigToken = 1; 38 | } -------------------------------------------------------------------------------- /proto/play_document.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky.document; 4 | 5 | import "play_common.proto"; 6 | import "play_containers.proto"; 7 | import "play_filter_rules.proto"; 8 | import "play_link.proto"; 9 | import "play_download.proto"; 10 | 11 | message DocV2 { 12 | optional string docid = 1; 13 | optional string backendDocid = 2; 14 | optional int32 docType = 3; 15 | optional int32 backendId = 4; 16 | optional string title = 5; 17 | optional string creator = 6; 18 | optional string descriptionHtml = 7; 19 | repeated Offer offer = 8; 20 | optional filter_rules.Availability availability = 9; 21 | repeated Image image = 10; 22 | repeated DocV2 child = 11; 23 | optional containers.ContainerMetadata containerMetadata = 12; 24 | optional DocumentDetails details = 13; 25 | optional AggregateRating aggregateRating = 14; 26 | optional Annotations annotations = 15; 27 | optional string detailsUrl = 16; 28 | optional string shareUrl = 17; 29 | optional string reviewsUrl = 18; 30 | optional string backendUrl = 19; 31 | optional string purchaseDetailsUrl = 20; 32 | optional bool detailsReusable = 21; 33 | optional string subtitle = 22; 34 | optional string translatedDescriptionHtml = 23; 35 | optional bytes serverLogsCookie = 24; 36 | optional ProductDetails productDetails = 25; 37 | optional bool mature = 26; 38 | optional string promotionalDescription = 27; 39 | optional bool availableForPreregistration = 29; 40 | repeated ReviewTip tip = 30; 41 | optional string snippetsUrl = 31; 42 | optional bool forceShareability = 32; 43 | optional bool useWishlistAsPrimaryAction = 33; 44 | } 45 | 46 | message Annotations { 47 | optional SectionMetadata sectionRelated = 1; 48 | optional SectionMetadata sectionMoreBy = 2; 49 | optional PlusOneData plusOneData = 3; 50 | repeated Warning warning = 4; 51 | optional SectionMetadata sectionBodyOfWork = 5; 52 | optional SectionMetadata sectionCoreContent = 6; 53 | // TODO: optional Template template = 7; 54 | repeated Badge badgeForCreator = 8; 55 | repeated Badge badgeForDoc = 9; 56 | optional link.Link link = 10; 57 | optional SectionMetadata sectionCrossSell = 11; 58 | optional SectionMetadata sectionRelatedDocType = 12; 59 | repeated PromotedDoc promotedDoc = 13; 60 | optional string offerNote = 14; 61 | repeated DocV2 subscription = 16; 62 | optional OBSOLETE_Reason OBSOLETEReason = 17; 63 | optional string privacyPolicyUrl = 18; 64 | // TODO: optional SuggestionReasons suggestionReasons = 19; 65 | optional Warning optimalDeviceClassWarning = 20; 66 | repeated BadgeContainer docBadgeContainer = 21; 67 | optional SectionMetadata sectionSuggestForRating = 22; 68 | optional SectionMetadata sectionPurchaseCrossSell = 24; 69 | repeated link.OverflowLink overflowLink = 25; 70 | optional DocV2 creatorDoc = 26; 71 | optional string attributionHtml = 27; 72 | optional PurchaseHistoryDetails purchaseHistoryDetails = 28; 73 | optional Badge badgeForContentRating = 29; 74 | repeated VoucherInfo voucherInfo = 30; 75 | optional SectionMetadata sectionFeaturedApps = 32; 76 | optional string applicableVoucherDescription = 33; 77 | repeated SectionMetadata detailsPageCluster = 34; 78 | optional VideoAnnotations videoAnnotations = 35; 79 | optional SectionMetadata sectionPurchaseRelatedTopics = 36; 80 | optional MySubscriptionDetails mySubscriptionDetails = 37; 81 | optional MyRewardDetails myRewardDetails = 38; 82 | repeated Badge featureBadge = 39; 83 | optional SelectedChild selectedChild = 40; 84 | optional PurchaseDetails purchaseDetails = 41; 85 | optional Snippet snippet = 42; 86 | } 87 | message PlusOneData { 88 | optional bool setByUser = 1; 89 | optional int64 total = 2; 90 | optional int64 circlesTotal = 3; 91 | repeated OBSOLETE_PlusProfile OBSOLETECirclesProfiles = 4; 92 | repeated DocV2 circlePerson = 5; 93 | } 94 | message OBSOLETE_PlusProfile { 95 | optional string displayName = 2; 96 | optional string profileImageUrl = 4; 97 | optional Image profileImage = 5; 98 | } 99 | message SectionMetadata { 100 | optional string header = 1; 101 | optional string listUrl = 2; 102 | optional string browseUrl = 3; 103 | optional string descriptionHtml = 4; 104 | } 105 | message Warning { 106 | optional string localizedMessage = 1; 107 | optional bool showIcon = 2; 108 | } 109 | message Badge { 110 | optional string title = 1; 111 | repeated Image image = 2; 112 | optional string browseUrl = 3; 113 | optional string description = 4; 114 | optional string textInTitleSection = 5; 115 | optional Image expandedBadgeImage = 6; 116 | } 117 | message BadgeContainer { 118 | optional string title = 1; 119 | repeated Image image = 2; 120 | repeated Badge badge = 3; 121 | } 122 | message PurchaseDetails { 123 | optional int64 legalDocumentBrokerId = 1; 124 | optional bool showAgeConfirmationPrompt = 33; 125 | optional bool purchaseAuthenticationRequired = 34; 126 | } 127 | message PurchaseHistoryDetails { 128 | optional int64 purchaseTimestampMsec = 2; 129 | optional string purchaseDetailsHtml = 3; 130 | optional Offer offer = 5; 131 | optional string purchaseStatus = 6; 132 | optional string titleBylineHtml = 7; 133 | optional bytes clientRefundContext = 8; 134 | optional Image purchaseDetailsImage = 9; 135 | } 136 | message OBSOLETE_Reason { 137 | optional string briefReason = 1; 138 | optional string OBSOLETEDetailedReason = 2; 139 | optional string uniqueId = 3; 140 | } 141 | message VoucherInfo { 142 | optional DocV2 doc = 1; 143 | repeated Offer offer = 2; 144 | } 145 | message VideoAnnotations { 146 | optional bool bundle = 1; 147 | optional string bundleContentListUrl = 2; 148 | optional string extrasContentListUrl = 3; 149 | optional string alsoAvailableInListUrl = 4; 150 | repeated Docid bundleDocid = 5; 151 | } 152 | message MySubscriptionDetails { 153 | optional string subscriptionStatusHtml = 1; 154 | optional string title = 2; 155 | optional string titleBylineHtml = 3; 156 | optional string formattedPrice = 4; 157 | optional string priceBylineHtml = 5; 158 | optional bool cancelSubscription = 6; 159 | optional link.Link paymentDeclinedLearnMoreLink = 7; 160 | optional bool inTrialPeriod = 8; 161 | optional Image titleBylineIcon = 9; 162 | } 163 | message MyRewardDetails { 164 | optional int64 expirationTimeMillis = 1; 165 | optional string expirationDescription = 2; 166 | optional string buttonLabel = 3; 167 | optional link.Link linkAction = 4; 168 | } 169 | message SelectedChild { 170 | optional string docid = 1; 171 | optional SelectedChild selectedChild = 2; 172 | } 173 | message Snippet { 174 | optional string snippetHtml = 1; 175 | } 176 | 177 | message ProductDetails { 178 | optional string title = 1; 179 | repeated ProductDetailsSection section = 2; 180 | } 181 | message ProductDetailsSection { 182 | optional string title = 1; 183 | repeated ProductDetailsDescription description = 3; 184 | } 185 | message ProductDetailsDescription { 186 | optional Image image = 1; 187 | optional string description = 2; 188 | } 189 | message PromotedDoc { 190 | optional string title = 1; 191 | optional string subtitle = 2; 192 | repeated Image image = 3; 193 | optional string descriptionHtml = 4; 194 | optional string detailsUrl = 5; 195 | } 196 | 197 | message DocumentDetails { 198 | optional AppDetails appDetails = 1; 199 | optional AlbumDetails albumDetails = 2; 200 | optional ArtistDetails artistDetails = 3; 201 | optional SongDetails songDetails = 4; 202 | optional BookDetails bookDetails = 5; 203 | optional VideoDetails videoDetails = 6; 204 | optional SubscriptionDetails subscriptionDetails = 7; 205 | optional MagazineDetails magazineDetails = 8; 206 | optional TvShowDetails tvShowDetails = 9; 207 | optional TvSeasonDetails tvSeasonDetails = 10; 208 | optional TvEpisodeDetails tvEpisodeDetails = 11; 209 | optional PersonDetails personDetails = 12; 210 | optional TalentDetails talentDetails = 13; 211 | optional DeveloperDetails developerDetails = 14; 212 | optional BookSeriesDetails bookSeriesDetails = 15; 213 | } 214 | 215 | message AppDetails { 216 | optional string developerName = 1; 217 | optional int32 majorVersionNumber = 2; 218 | optional int32 versionCode = 3; 219 | optional string versionString = 4; 220 | optional string title = 5; 221 | repeated string appCategory = 7; 222 | optional int32 contentRating = 8; 223 | optional int64 installationSize = 9; 224 | repeated string permission = 10; 225 | optional string developerEmail = 11; 226 | optional string developerWebsite = 12; 227 | optional string numDownloads = 13; 228 | optional string packageName = 14; 229 | optional string recentChangesHtml = 15; 230 | optional string uploadDate = 16; 231 | repeated download.FileMetadata file = 17; 232 | optional string appType = 18; 233 | repeated string certificateHash = 19; 234 | optional bool variesByAccount = 21; 235 | repeated CertificateSet certificateSet = 22; 236 | repeated string autoAcquireFreeAppIfHigherVersionAvailableTag = 23; 237 | optional bool declaresIab = 24; 238 | repeated string splitId = 25; 239 | optional bool gamepadRequired = 26; 240 | optional bool externallyHosted = 27; 241 | optional bool everExternallyHosted = 28; 242 | optional string installNotes = 30; 243 | optional int32 installLocation = 31; 244 | optional int32 targetSdkVersion = 32; 245 | optional string preregistrationPromoCode = 33; 246 | optional download.InstallDetails installDetails = 34; 247 | optional TestingProgramInfo testingProgramInfo = 35; 248 | } 249 | message TestingProgramInfo { 250 | optional bool subscribed = 2; 251 | optional bool subscribed1 = 3; 252 | } 253 | message AlbumDetails { 254 | optional string name = 1; 255 | optional MusicDetails details = 2; 256 | optional ArtistDetails displayArtist = 3; 257 | } 258 | message MusicDetails { 259 | optional int32 censoring = 1; 260 | optional int32 durationSec = 2; 261 | optional string originalReleaseDate = 3; 262 | optional string label = 4; 263 | repeated ArtistDetails artist = 5; 264 | repeated string genre = 6; 265 | optional string releaseDate = 7; 266 | repeated int32 releaseType = 8; 267 | } 268 | message ArtistDetails { 269 | optional string detailsUrl = 1; 270 | optional string name = 2; 271 | optional ArtistExternalLinks externalLinks = 3; 272 | } 273 | message ArtistExternalLinks { 274 | repeated string websiteUrl = 1; 275 | optional string googlePlusProfileUrl = 2; 276 | optional string youtubeChannelUrl = 3; 277 | } 278 | message SongDetails { 279 | optional string name = 1; 280 | optional MusicDetails details = 2; 281 | optional string albumName = 3; 282 | optional int32 trackNumber = 4; 283 | optional string previewUrl = 5; 284 | optional ArtistDetails displayArtist = 6; 285 | optional Badge badge = 7; 286 | } 287 | message BookDetails { 288 | optional string publisher = 4; 289 | optional string publicationDate = 5; 290 | optional string isbn = 6; 291 | optional int32 numberOfPages = 7; 292 | optional string aboutTheAuthor = 17; 293 | optional int32 bookType = 27; 294 | optional string seriesLine = 28; 295 | optional string conciseTitle = 29; 296 | optional string shortTitle = 30; 297 | optional string seriesTitle = 31; 298 | } 299 | message VideoDetails { 300 | repeated VideoCredit credit = 1; 301 | optional string duration = 2; 302 | optional string releaseDate = 3; 303 | optional string contentRating = 4; 304 | optional int64 likes = 5; 305 | optional int64 dislikes = 6; 306 | repeated string genre = 7; 307 | repeated Trailer trailer = 8; 308 | repeated VideoRentalTerm rentalTerm = 9; 309 | repeated string audioLanguage = 10; 310 | repeated string captionLanguage = 11; 311 | } 312 | message VideoCredit { 313 | optional int32 creditType = 1; 314 | optional string credit = 2; 315 | repeated string name = 3; 316 | } 317 | message Trailer { 318 | optional string trailerId = 1; 319 | optional string title = 2; 320 | optional string thumbnailUrl = 3; 321 | optional string watchUrl = 4; 322 | optional string duration = 5; 323 | } 324 | message VideoRentalTerm { 325 | optional int32 offerType = 1; 326 | optional string offerAbbreviation = 2; 327 | optional string rentalHeader = 3; 328 | repeated group Term = 4 { 329 | optional string header = 5; 330 | optional string body = 6; 331 | } 332 | } 333 | message SubscriptionDetails { 334 | optional int32 subscriptionPeriod = 1; 335 | } 336 | message MagazineDetails { 337 | optional string parentDetailsUrl = 1; 338 | optional string deviceAvailabilityDescriptionHtml = 2; 339 | optional string psvDescription = 3; 340 | optional string deliveryFrequencyDescription = 4; 341 | } 342 | message TvShowDetails { 343 | optional int32 seasonCount = 1; 344 | optional int32 startYear = 2; 345 | optional int32 endYear = 3; 346 | optional string broadcaster = 4; 347 | } 348 | message TvSeasonDetails { 349 | optional string parentDetailsUrl = 1; 350 | optional int32 seasonIndex = 2; 351 | optional string releaseDate = 3; 352 | optional string broadcaster = 4; 353 | optional int32 episodeCount = 5; 354 | optional int32 expectedEpisodeCount = 6; 355 | optional bool inProgress = 7; 356 | } 357 | message TvEpisodeDetails { 358 | optional string parentDetailsUrl = 1; 359 | optional int32 episodeIndex = 2; 360 | optional string releaseDate = 3; 361 | } 362 | message PersonDetails { 363 | optional bool personIsRequester = 1; 364 | optional bool isGplusUser = 2; 365 | } 366 | message TalentDetails { 367 | optional TalentExternalLinks externalLinks = 1; 368 | optional int32 primaryRoleId = 2; 369 | } 370 | message TalentExternalLinks { 371 | repeated link.Link websiteUrl = 1; 372 | optional link.Link googlePlusProfileUrl = 2; 373 | optional link.Link youtubeChannelUrl = 3; 374 | } 375 | message DeveloperDetails { 376 | optional string websiteUrl = 1; 377 | } 378 | message BookSeriesDetails { 379 | optional string publisher = 1; 380 | optional string seriesComposition = 2; 381 | optional string seriesCardComposition = 3; 382 | } 383 | 384 | message AggregateRating { 385 | optional int32 type = 1; 386 | optional float starRating = 2; 387 | optional int64 ratingsCount = 3; 388 | optional int64 oneStarRatings = 4; 389 | optional int64 twoStarRatings = 5; 390 | optional int64 threeStarRatings = 6; 391 | optional int64 fourStarRatings = 7; 392 | optional int64 fiveStarRatings = 8; 393 | optional int64 thumbsUpCount = 9; 394 | optional int64 thumbsDownCount = 10; 395 | optional int64 commentCount = 11; 396 | optional double bayesianMeanRating = 12; 397 | repeated AggregateRatingTip tip = 13; 398 | } 399 | message AggregateRatingTip { 400 | optional string tipId = 1; 401 | optional string text = 2; 402 | optional int32 polarity = 3; 403 | optional int64 reviewCount = 4; 404 | optional string language = 5; 405 | repeated string snippetReviewId = 6; 406 | } 407 | 408 | message Review { 409 | optional string authorName = 1; 410 | optional string url = 2; 411 | optional string source = 3; 412 | optional string documentVersion = 4; 413 | optional int64 timestampMsec = 5; 414 | optional int32 starRating = 6; 415 | optional string title = 7; 416 | optional string comment = 8; 417 | optional string commentId = 9; 418 | optional string deviceName = 19; 419 | optional string replyText = 29; 420 | optional int64 replyTimestampMsec = 30; 421 | optional OBSOLETE_PlusProfile OBSOLETEPlusProfile = 31; 422 | optional DocV2 author = 33; 423 | optional Image sentiment = 34; 424 | optional int32 helpfulCount = 35; 425 | optional int64 thumbsUpCount = 38; 426 | } 427 | message ReviewTip { 428 | optional string tipUrl = 1; 429 | optional string text = 2; 430 | optional int32 polarity = 3; 431 | optional int64 reviewCount = 4; 432 | } -------------------------------------------------------------------------------- /proto/play_download.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky.download; 4 | 5 | import "play_common.proto"; 6 | 7 | message Install { 8 | optional fixed64 androidId = 1; 9 | optional int32 version = 2; 10 | optional bool bundled = 3; 11 | optional bool pending = 4; 12 | optional int64 lastUpdateTimestampMillis = 5; 13 | } 14 | 15 | message Dependency { 16 | optional string packageName = 1; 17 | optional int32 minVersionCode = 2; 18 | optional int32 versionCodeMask = 3; 19 | optional bool skipPermissions = 4; 20 | } 21 | message InstallDetails { 22 | optional int32 installLocation = 1; 23 | optional int64 size = 2; 24 | repeated Dependency dependency = 3; 25 | optional int32 targetSdkVersion = 4; 26 | } 27 | 28 | message PatchDetails { 29 | optional int32 baseVersionCode = 1; 30 | optional int64 size = 2; 31 | } 32 | message FileMetadata { 33 | optional int32 fileType = 1; 34 | optional int32 versionCode = 2; 35 | optional int64 size = 3; 36 | optional string splitId = 4; 37 | optional int64 compressedSize = 5; 38 | repeated PatchDetails patchDetails = 6; 39 | } 40 | 41 | message AndroidAppPatchData { 42 | optional int32 baseVersionCode = 1; 43 | optional string baseSignature = 2; 44 | optional string downloadUrl = 3; 45 | optional int32 patchFormat = 4; 46 | optional int64 maxPatchSize = 5; 47 | } 48 | message AppFileMetadata { 49 | optional int32 fileType = 1; 50 | optional int32 versionCode = 2; 51 | optional int64 size = 3; 52 | optional string downloadUrl = 4; 53 | optional AndroidAppPatchData patchData = 5; 54 | optional int64 compressedSize = 6; 55 | optional string compressedDownloadUrl = 7; 56 | optional string signature = 8; 57 | } 58 | message EncryptionParams { 59 | optional int32 version = 1; 60 | optional string encryptionKey = 2; 61 | optional string hmacKey = 3; 62 | } 63 | message SplitDeliveryData { 64 | optional string id = 1; 65 | optional int64 downloadSize = 2; 66 | optional int64 gzippedDownloadSize = 3; 67 | optional string signature = 4; 68 | optional string downloadUrl = 5; 69 | optional string gzippedDownloadUrl = 6; 70 | optional AndroidAppPatchData patchData = 7; 71 | } 72 | message AndroidAppDeliveryData { 73 | optional int64 downloadSize = 1; 74 | optional string signature = 2; 75 | optional string downloadUrl = 3; 76 | repeated AppFileMetadata additionalFile = 4; 77 | repeated HttpCookie downloadAuthCookie = 5; 78 | optional bool forwardLocked = 6; 79 | optional int64 refundTimeout = 7; 80 | optional bool serverInitiated = 8; 81 | optional int64 postInstallRefundWindowMillis = 9; 82 | optional bool immediateStartNeeded = 10; 83 | optional AndroidAppPatchData patchData = 11; 84 | optional EncryptionParams encryptionParams = 12; 85 | optional string gzippedDownloadUrl = 13; 86 | optional int64 gzippedDownloadSize = 14; 87 | repeated SplitDeliveryData splitDeliveryData = 15; 88 | optional int32 installLocation = 16; 89 | optional bool everExternallyHosted = 17; 90 | } 91 | message DeliveryResponse { 92 | optional int32 status = 1; 93 | optional AndroidAppDeliveryData appDeliveryData = 2; 94 | } -------------------------------------------------------------------------------- /proto/play_filter_rules.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky.filter_rules; 4 | 5 | import "play_common.proto"; 6 | import "play_download.proto"; 7 | 8 | message Availability { 9 | optional int32 restriction = 5; 10 | optional int32 offerType = 6; 11 | optional Rule rule = 7; 12 | repeated group PerDeviceAvailabilityRestriction = 9 { 13 | optional fixed64 androidId = 10; 14 | optional int32 deviceRestriction = 11; 15 | optional int64 channelId = 12; 16 | optional FilterEvaluationInfo filterInfo = 15; 17 | optional bool availableIfOwned = 22; 18 | } 19 | optional bool availableIfOwned = 13; 20 | repeated download.Install install = 14; 21 | optional FilterEvaluationInfo filterInfo = 16; 22 | optional OwnershipInfo ownershipInfo = 17; 23 | repeated AvailabilityProblem availabilityProblem = 18; 24 | optional bool hidden = 21; 25 | } 26 | message AvailabilityProblem { 27 | optional int32 problemType = 1; 28 | repeated string missingValue = 2; 29 | } 30 | message FilterEvaluationInfo { 31 | repeated RuleEvaluation ruleEvaluation = 1; 32 | } 33 | message Rule { 34 | optional bool negate = 1; 35 | optional int32 operator = 2; 36 | optional int32 key = 3; 37 | repeated string stringArg = 4; 38 | repeated int64 longArg = 5; 39 | repeated double doubleArg = 6; 40 | repeated Rule subrule = 7; 41 | optional int32 responseCode = 8; 42 | optional string comment = 9; 43 | repeated fixed64 stringArgHash = 10; 44 | optional int32 availabilityProblemType = 12; 45 | optional bool includeMissingValues = 13; 46 | } 47 | message RuleEvaluation { 48 | optional Rule rule = 1; 49 | repeated string actualStringValue = 2; 50 | repeated int64 actualLongValue = 3; 51 | repeated bool actualBoolValue = 4; 52 | repeated double actualDoubleValue = 5; 53 | } -------------------------------------------------------------------------------- /proto/play_link.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky.link; 4 | 5 | import "play_common.proto"; 6 | 7 | message Link { 8 | optional string uri = 1; 9 | optional ResolvedLink resolvedLink = 2; 10 | optional int32 uriBackend = 3; 11 | } 12 | message OverflowLink { 13 | optional string title = 1; 14 | optional Link link = 2; 15 | } 16 | message DirectPurchase { 17 | optional string detailsUrl = 1; 18 | optional string purchaseDocid = 2; 19 | optional string parentDocid = 3; 20 | optional int32 offerType = 4; 21 | } 22 | message HelpCenter { 23 | optional string contextId = 1; 24 | } 25 | message RedeemGiftCard { 26 | optional string prefillCode = 1; 27 | optional string partnerPayload = 2; 28 | } 29 | message ResolvedLink { 30 | optional string detailsUrl = 1; 31 | optional string browseUrl = 2; 32 | optional string searchUrl = 3; 33 | optional DirectPurchase directPurchase = 4; 34 | optional string homeUrl = 5; 35 | optional RedeemGiftCard redeemGiftCard = 6; 36 | optional bytes serverLogsCookie = 7; 37 | optional Docid docid = 8; 38 | optional string wishlistUrl = 9; 39 | optional int32 backend = 10; 40 | optional string query = 11; 41 | optional string myAccountUrl = 12; 42 | optional HelpCenter helpCenter = 13; 43 | optional string giftUrl = 14; 44 | } -------------------------------------------------------------------------------- /proto/play_ownership.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky.ownership; 4 | 5 | import "play_common.proto"; 6 | 7 | message OwnershipInfo { 8 | optional int64 initiationTimestampMsec = 1; 9 | optional int64 validUntilTimestampMsec = 2; 10 | optional bool autoRenewing = 3; 11 | optional int64 refundTimeoutTimestampMsec = 4; 12 | optional int64 postDeliveryRefundWindowMsec = 5; 13 | optional SignedData developerPurchaseInfo = 6; 14 | optional bool preordered = 7; 15 | optional bool hidden = 8; 16 | optional RentalTerms rentalTerms = 9; 17 | optional GroupLicenseInfo groupLicenseInfo = 10; 18 | optional LicensedDocumentInfo licensedDocumentInfo = 11; 19 | optional int32 quantity = 12; 20 | optional int64 libraryExpirationTimestampMsec = 14; 21 | optional LibraryVoucher libraryVoucher = 15; 22 | optional Docid bundleDocid = 16; 23 | optional bool bonus = 17; 24 | optional int64 storedValidUntilTimestampMsec = 18; 25 | } -------------------------------------------------------------------------------- /proto/play_respone.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky.response; 4 | 5 | import "play_common.proto"; 6 | import "play_download.proto"; 7 | import "play_settings.proto"; 8 | import "play_toc.proto"; 9 | import "play_device_config.proto"; 10 | import "play_search.proto"; 11 | import "play_browse.proto"; 12 | import "play_details.proto"; 13 | 14 | message ServerCommands { 15 | optional bool clearCache = 1; 16 | optional string displayErrorMessage = 2; 17 | optional string logErrorStacktrace = 3; 18 | repeated settings.UserSettingDirtyData userSettingDirtyData = 4; 19 | } 20 | 21 | message PreFetch { 22 | optional string url = 1; 23 | optional bytes response = 2; 24 | optional string etag = 3; 25 | optional int64 ttl = 4; 26 | optional int64 softTtl = 5; 27 | } 28 | 29 | message AndroidAppNotificationData { 30 | optional int32 versionCode = 1; 31 | optional string assetId = 2; 32 | optional string installReferrer = 3; 33 | optional download.InstallDetails installDetails = 4; 34 | } 35 | message PurchaseRemovalData { 36 | optional bool malicious = 1; 37 | } 38 | message UserNotificationData { 39 | optional string notificationTitle = 1; 40 | optional string notificationText = 2; 41 | optional string tickerText = 3; 42 | optional string dialogTitle = 4; 43 | optional string dialogText = 5; 44 | } 45 | message InAppNotificationData { 46 | optional string checkoutOrderId = 1; 47 | optional string inAppNotificationId = 2; 48 | } 49 | message PurchaseDeclinedData { 50 | optional int32 reason = 1; 51 | optional bool showNotification = 2; 52 | } 53 | message LibraryDirtyData { 54 | optional int32 backend = 1; 55 | optional string libraryId = 2; 56 | } 57 | message Notification { 58 | optional int32 notificationType = 1; 59 | optional int64 timestamp = 3; 60 | optional Docid docid = 4; 61 | optional string docTitle = 5; 62 | optional string userEmail = 6; 63 | optional AndroidAppNotificationData appData = 7; 64 | optional download.AndroidAppDeliveryData appDeliveryData = 8; 65 | optional PurchaseRemovalData purchaseRemovalData = 9; 66 | optional UserNotificationData userNotificationData = 10; 67 | optional InAppNotificationData inAppNotificationData = 11; 68 | optional PurchaseDeclinedData purchaseDeclinedData = 12; 69 | optional string notificationId = 13; 70 | // TODO: optional LibraryUpdateProto.LibraryUpdate libraryUpdate = 14; 71 | optional LibraryDirtyData libraryDirtyData = 15; 72 | optional settings.UserSettingDirtyData userSettingDirtyData = 16; 73 | optional bool notificationAckRequired = 17; 74 | } 75 | 76 | message ServerMetadata { 77 | optional int64 latencyMillis = 1; 78 | } 79 | 80 | message Targets { 81 | repeated int64 targetId = 1; 82 | optional bytes signature = 2; 83 | } 84 | 85 | message ServerCookie { 86 | optional int32 type = 1; 87 | optional bytes token = 2; 88 | } 89 | message ServerCookies { 90 | repeated ServerCookie serverCookie = 1; 91 | } 92 | 93 | message AcceptTosResponse { 94 | } 95 | 96 | message Payload { 97 | optional details.DetailsResponse detailsResponse = 2; 98 | optional search.SearchResponse searchResponse = 5; 99 | optional toc.TocResponse tocResponse = 6; 100 | optional browse.BrowseResponse browseResponse = 7; 101 | optional details.BulkDetailsResponse bulkDetailsResponse = 19; 102 | optional download.DeliveryResponse deliveryResponse = 21; 103 | optional AcceptTosResponse acceptTosResponse = 22; 104 | optional device_config.UploadDeviceConfigResponse uploadDeviceConfigResponse = 28; 105 | optional search.SearchSuggestResponse searchSuggestResponse = 40; 106 | optional settings.GetUserSettingsResponse getUserSettingsResponse = 54; 107 | } 108 | message ResponseWrapper { 109 | optional Payload payload = 1; 110 | optional ServerCommands commands = 2; 111 | repeated PreFetch preFetch = 3; 112 | repeated Notification notification = 4; 113 | optional ServerMetadata serverMetadata = 5; 114 | optional Targets targets = 6; 115 | optional ServerCookies serverCookies = 7; 116 | optional bytes serverLogsCookie = 9; 117 | } -------------------------------------------------------------------------------- /proto/play_search.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky.search; 4 | 5 | import "play_common.proto"; 6 | import "play_document.proto"; 7 | import "play_link.proto"; 8 | 9 | message SearchResponse { 10 | optional string originalQuery = 1; 11 | optional string suggestedQuery = 2; 12 | optional bool aggregateQuery = 3; 13 | // unused: repeated Bucket bucket = 4; 14 | repeated document.DocV2 doc = 5; 15 | repeated RelatedSearch relatedSearch = 6; 16 | optional bytes serverLogsCookie = 7; 17 | optional bool fullPageReplaced = 8; 18 | optional bool containsSnow = 9; 19 | } 20 | message RelatedSearch { 21 | optional string searchUrl = 1; 22 | optional string header = 2; 23 | optional int32 backendId = 3; 24 | optional int32 docType = 4; 25 | optional bool current = 5; 26 | } 27 | 28 | message SearchSuggestResponse { 29 | repeated Suggestion suggestion = 1; 30 | optional bytes serverLogsCookie = 2; 31 | } 32 | message Suggestion { 33 | optional int32 type = 1; 34 | optional string suggestedQuery = 2; 35 | optional NavSuggestion navSuggestion = 3; 36 | optional bytes serverLogsCookie = 4; 37 | optional Image image = 5; 38 | optional string displayText = 6; 39 | optional link.Link link = 7; 40 | optional document.DocV2 document = 8; 41 | optional int32 searchBackend = 9; 42 | } 43 | message NavSuggestion { 44 | optional string docId = 1; 45 | optional bytes imageBlob = 2; 46 | optional Image image = 3; 47 | optional string description = 4; 48 | } -------------------------------------------------------------------------------- /proto/play_settings.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky.settings; 4 | 5 | import "play_document.proto"; 6 | import "play_search.proto"; 7 | 8 | message UserSettingsConsistencyTokens { 9 | message ConsistencyTokenInfo { 10 | optional string requestHeader = 1; 11 | optional string consistencyToken = 2; 12 | } 13 | repeated ConsistencyTokenInfo consistencyTokenInfo = 1; 14 | } 15 | 16 | message MarketingSettings { 17 | optional bool marketingEmailsOptedIn = 1; 18 | } 19 | message PrivacySetting { 20 | optional int32 type = 1; 21 | optional int32 currentStatus = 2; 22 | optional bool enabledByDefault = 3; 23 | } 24 | message PrivacySettings { 25 | repeated PrivacySetting privacySetting = 1; 26 | } 27 | 28 | message FamilyInfo { 29 | optional int32 familyMembershipStatus = 1; 30 | optional Family family = 2; 31 | } 32 | message Family { 33 | repeated FamilyMember member = 1; 34 | } 35 | message FamilyMember { 36 | optional int32 role = 1; 37 | optional document.DocV2 personDocument = 2; 38 | } 39 | 40 | message Onboarding { 41 | optional int32 type = 1; 42 | } 43 | message Onboardings { 44 | repeated Onboarding onboarding = 1; 45 | } 46 | 47 | message UserSettings { 48 | optional MarketingSettings marketingSettings = 1; 49 | optional PrivacySettings privacySettings = 2; 50 | optional FamilyInfo familyInfo = 3; 51 | optional Onboardings dismissedOnboardings = 4; 52 | // TODO: optional PlayAccountProto.PlayAccountUserPurchaseCache playAccountUserPurchaseCache = 5; 53 | // TODO: optional PlayAccountProto.PlayAccountGlobalPurchaseCache playAccountGlobalPurchaseCache = 6; 54 | } 55 | message OBSOLETEUserSettings { 56 | optional bool tosCheckboxMarketingEmailsOptedIn = 1; 57 | repeated PrivacySetting privacySetting = 2; 58 | } 59 | message UserSettingDirtyData { 60 | optional int32 type = 1; 61 | optional UserSettingsConsistencyTokens consistencyTokens = 2; 62 | } 63 | message GetUserSettingsResponse { 64 | optional UserSettings userSettings = 1; 65 | optional UserSettingsConsistencyTokens consistencyTokens = 2; 66 | } -------------------------------------------------------------------------------- /proto/play_toc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package playapi.proto.finsky.toc; 4 | 5 | import "play_settings.proto"; 6 | 7 | message CarrierBillingConfig { 8 | optional string id = 1; 9 | optional string name = 2; 10 | optional int32 apiVersion = 3; 11 | optional string provisioningUrl = 4; 12 | optional string credentialsUrl = 5; 13 | optional bool tosRequired = 6; 14 | optional bool perTransactionCredentialsRequired = 7; 15 | optional bool sendSubscriberIdWithCarrierBillingRequests = 8; 16 | } 17 | message BillingConfig { 18 | optional CarrierBillingConfig carrierBillingConfig = 1; 19 | optional int32 maxIabApiVersion = 2; 20 | } 21 | message CorpusMetadata { 22 | optional int32 backend = 1; 23 | optional string name = 2; 24 | optional string landingUrl = 3; 25 | optional string libraryName = 4; 26 | optional string recsWidgetUrl = 6; 27 | optional string shopName = 7; 28 | } 29 | message Experiments { 30 | repeated string experimentId = 1; 31 | } 32 | message SelfUpdateConfig { 33 | optional int32 latestClientVersionCode = 1; 34 | } 35 | message TocResponse { 36 | repeated CorpusMetadata corpus = 1; 37 | optional int32 tosVersionDeprecated = 2; 38 | optional string tosContent = 3; 39 | optional string homeUrl = 4; 40 | optional Experiments experiments = 5; 41 | optional string tosCheckboxTextMarketingEmails = 6; 42 | optional string tosToken = 7; 43 | optional settings.OBSOLETEUserSettings userSettings = 8; 44 | optional string iconOverrideUrl = 9; 45 | optional SelfUpdateConfig selfUpdateConfig = 10; 46 | optional bool requiresUploadDeviceConfig = 11; 47 | optional BillingConfig billingConfig = 12; 48 | optional string recsWidgetUrl = 13; 49 | optional string socialHomeUrl = 15; 50 | optional bool ageVerificationRequired = 16; 51 | optional bool gplusSignupEnabled = 17; 52 | optional bool redeemEnabled = 18; 53 | optional string helpUrl = 19; 54 | optional int32 themeId = 20; 55 | optional string entertainmentHomeUrl = 21; 56 | optional string cookie = 22; 57 | } -------------------------------------------------------------------------------- /src/arg_list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct arg_list { 6 | 7 | private: 8 | int argc; 9 | const char** argv; 10 | 11 | public: 12 | arg_list(int argc, const char** argv) : argc(argc), argv(argv) { 13 | } 14 | 15 | const char* peek() { 16 | if (argc <= 0) 17 | return nullptr; 18 | return *(argv); 19 | } 20 | 21 | const char* next_or_null() { 22 | if (argc <= 0) 23 | return nullptr; 24 | argc--; 25 | return *(argv++); 26 | } 27 | 28 | const char* next() { 29 | const char* val = next_or_null(); 30 | if (val == nullptr) { 31 | std::cerr << "error: missing argument value" << std::endl; 32 | exit(1); 33 | } 34 | return val; 35 | } 36 | 37 | const char* next_value_or_null() { 38 | const char* val = peek(); 39 | if (val == nullptr || val[0] == '-') 40 | return nullptr; 41 | return next(); 42 | } 43 | 44 | const char* next_value() { 45 | const char* val = next_value_or_null(); 46 | if (val == nullptr) { 47 | std::cerr << "error: missing argument value" << std::endl; 48 | exit(1); 49 | } 50 | return val; 51 | } 52 | 53 | }; -------------------------------------------------------------------------------- /src/common.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../include/playapi/login.h" 5 | #include "../include/playapi/device_info.h" 6 | #include "../include/playapi/util/config.h" 7 | #include "../include/playapi/checkin.h" 8 | #include "../include/playapi/api.h" 9 | #include 10 | #include 11 | #include "../include/playapi/file_login_cache.h" 12 | #include "config.h" 13 | #include "common.h" 14 | 15 | using namespace playapi; 16 | 17 | playapi_cli_base::playapi_cli_base() : conf("playdl.conf"), login_cache("token_cache.conf"), 18 | login_api(device, login_cache), api(device) { 19 | 20 | } 21 | 22 | void playapi_cli_base::do_interactive_auth() { 23 | auth_select_method: 24 | std::cout << "We haven't authorized you yet. Please select your preferred login method:" << std::endl; 25 | std::cout << "1. via login and password" << std::endl; 26 | std::cout << "2. via access token" << std::endl; 27 | std::cout << "3. via master token" << std::endl; 28 | std::cout << "Please type the selected number and press enter: "; 29 | std::string method; 30 | std::getline(std::cin, method); 31 | std::cout << std::endl; 32 | if (method == "1") { 33 | // via login & password 34 | do_login_pass_auth: 35 | std::cout << "Please enter your email: "; 36 | std::string email; 37 | std::getline(std::cin, email); 38 | std::cout << "Please enter password for your account: "; 39 | std::string password; 40 | std::getline(std::cin, password); 41 | std::cout << "Authenticating..." << std::endl; 42 | try { 43 | login_api.perform(email, password)->call(); 44 | } catch (std::runtime_error err) { 45 | std::cout << "Failed to login using the specified credentials: " << err.what() << std::endl; 46 | goto do_login_pass_auth; 47 | } 48 | std::cout << "Logged you in successfully." << std::endl; 49 | std::cout << std::endl; 50 | } else if (method == "2") { 51 | // via master token 52 | do_token_auth: 53 | std::cout << "Please enter your access token: "; 54 | std::string token; 55 | std::getline(std::cin, token); 56 | try { 57 | login_api.perform_with_access_token(token)->call(); 58 | } catch (std::runtime_error err) { 59 | std::cout << "Failed to login using the specified token: " << err.what() << std::endl << std::endl; 60 | goto do_token_auth; 61 | } 62 | std::cout << std::endl; 63 | } else if (method == "3") { 64 | // via master token 65 | do_master_token_auth: 66 | std::cout << "Please enter your master token: "; 67 | std::string token; 68 | std::getline(std::cin, token); 69 | login_api.set_token("", token); 70 | try { 71 | login_api.verify()->call(); 72 | } catch (std::runtime_error err) { 73 | std::cout << "Failed to login using the specified token: " << err.what() << std::endl << std::endl; 74 | goto do_master_token_auth; 75 | } 76 | std::cout << std::endl; 77 | } else { 78 | std::cout << "You have entered an invalid number." << std::endl << std::endl; 79 | goto auth_select_method; 80 | } 81 | 82 | if (login_api.has_token()) { 83 | std::cout << "We can save a token on your hard drive that will allow you to use this application" 84 | << " without having to sign in again." << std::endl; 85 | std::cout << "Would you like to store the token? [Y/n]: "; 86 | std::string answ; 87 | std::getline(std::cin, answ); 88 | if (answ.length() == 0 || answ[0] == 'Y' || answ[0] == 'y') { 89 | conf.user_email = login_api.get_email(); 90 | conf.user_token = login_api.get_token(); 91 | conf.save(); 92 | std::cout << "Saved the token." << std::endl; 93 | } 94 | } else { 95 | std::cout << "Something went wrong. Let's try again." << std::endl; 96 | goto auth_select_method; 97 | } 98 | } 99 | 100 | void playapi_cli_base::do_auth_from_config() { 101 | do_auth: 102 | login_api.set_token(conf.user_email, conf.user_token); 103 | if (opt_login_no_verify) 104 | return; 105 | 106 | try { 107 | login_api.verify()->call(); 108 | } catch (std::runtime_error &err) { 109 | if (!opt_interactive) { 110 | std::cerr << "error: bad saved token" << std::endl; 111 | exit(1); 112 | } 113 | 114 | std::cout << "Failed to login using a saved token: " << err.what() << std::endl; 115 | std::cout << "Would you like to delete it and authenticate again? [y/N]"; 116 | std::string answ; 117 | std::getline(std::cin, answ); 118 | if (answ[0] == 'Y' || answ[0] == 'y') { 119 | conf.user_email = ""; 120 | conf.user_token = ""; 121 | do_interactive_auth(); 122 | return; 123 | } 124 | goto do_auth; 125 | } 126 | } 127 | 128 | void playapi_cli_base::print_global_help() { 129 | std::cout << "Global options" << std::endl; 130 | std::cout << "-i --interactive Enables interactive mode" << std::endl; 131 | std::cout << "-u --email Email to use for automatic login" << std::endl; 132 | std::cout << "-p --password Password to use for automatic login" << std::endl; 133 | std::cout << "-t --token Token to use for automatic login" << std::endl; 134 | std::cout << "-sa --save-auth Save authentication information to file" << std::endl; 135 | std::cout << "-tos --accept-tos Automatically accept ToS if needed" << std::endl; 136 | std::cout << "-d --device Use the specified device configuration file" << std::endl; 137 | std::cout << "-nv --login-no-verify Disable login credential verification" << std::endl << std::endl; 138 | } 139 | 140 | bool playapi_cli_base::parse_arg(arg_list &list, const char *key) { 141 | if (strcmp(key, "-i") == 0 || strcmp(key, "--interactive") == 0) 142 | opt_interactive = true; 143 | else if (strcmp(key, "-u") == 0 || strcmp(key, "--email") == 0) 144 | opt_email = list.next_value(); 145 | else if (strcmp(key, "-p") == 0 || strcmp(key, "--password") == 0) 146 | opt_password = list.next_value(); 147 | else if (strcmp(key, "-t") == 0 || strcmp(key, "--token") == 0) 148 | opt_token = list.next_value(); 149 | else if (strcmp(key, "-sa") == 0 || strcmp(key, "--save-auth") == 0) 150 | opt_save_auth = true; 151 | else if (strcmp(key, "-tos") == 0 || strcmp(key, "--accept-tos") == 0) 152 | opt_accept_tos = true; 153 | else if (strcmp(key, "-d") == 0 || strcmp(key, "--device") == 0) 154 | opt_device_path = list.next_value(); 155 | else if (strcmp(key, "-nv") == 0 || strcmp(key, "--login-no-verify") == 0) 156 | opt_login_no_verify = true; 157 | else 158 | return false; 159 | return true; 160 | } 161 | 162 | void playapi_cli_base::parse_args(int argc, const char **argv) { 163 | arg_list list (argc, argv); 164 | list.next(); 165 | if ((list.peek()) == nullptr) { 166 | opt_interactive = true; // with no args, default to interactive 167 | return; 168 | } 169 | const char *key; 170 | while ((key = list.next_or_null())) { 171 | if (strcmp(key, "-h") == 0 || strcmp(key, "--help") == 0) { 172 | print_help(); 173 | exit(1); 174 | } 175 | 176 | bool b = parse_arg(list, key); 177 | if (!b) 178 | std::cerr << "error: bad argument: " << key << std::endl; 179 | } 180 | } 181 | 182 | void playapi_cli_base::perform_auth() { 183 | { 184 | std::ifstream dev_info_file(opt_device_path); 185 | config dev_info_conf; 186 | dev_info_conf.load(dev_info_file); 187 | device.load(dev_info_conf); 188 | } 189 | 190 | device_config dev_state(opt_device_path + ".state"); 191 | dev_state.load(); 192 | dev_state.load_device_info_data(device); 193 | device.generate_fields(); 194 | dev_state.set_device_info_data(device); 195 | dev_state.save(); 196 | 197 | login_api.set_checkin_data(dev_state.checkin_data); 198 | if (opt_token.length() > 0) { 199 | login_api.set_token(opt_email, opt_token); 200 | if (!opt_login_no_verify) { 201 | try { 202 | login_api.verify()->call(); 203 | } catch (std::runtime_error &err) { 204 | std::cerr << "error: bad token" << std::endl; 205 | exit(1); 206 | } 207 | } 208 | } else if (conf.user_token.length() <= 0) { 209 | if (opt_email.length() > 0 && opt_password.length() > 0) { 210 | login_api.perform(opt_email, opt_password); 211 | if (opt_save_auth) { 212 | conf.user_email = login_api.get_email(); 213 | conf.user_token = login_api.get_token(); 214 | conf.save(); 215 | } 216 | } else if (opt_interactive) { 217 | do_interactive_auth(); 218 | } else { 219 | std::cerr << "error: no authentication set and no playdl.conf file exists" << std::endl; 220 | exit(1); 221 | } 222 | } else { 223 | do_auth_from_config(); 224 | } 225 | if (dev_state.checkin_data.android_id == 0) { 226 | checkin_api checkin(device); 227 | checkin.add_auth(login_api)->call(); 228 | dev_state.checkin_data = checkin.perform_checkin()->call(); 229 | dev_state.save(); 230 | } 231 | 232 | api.set_auth(login_api)->call(); 233 | api.set_checkin_data(dev_state.checkin_data); 234 | dev_state.load_api_data(login_api.get_email(), api); 235 | if (api.toc_cookie.length() == 0 || api.device_config_token.length() == 0) { 236 | api.fetch_user_settings()->call(); 237 | auto toc = api.fetch_toc()->call(); 238 | if (toc.payload().tocresponse().has_cookie()) 239 | api.toc_cookie = toc.payload().tocresponse().cookie(); 240 | 241 | if (api.fetch_toc()->call().payload().tocresponse().requiresuploaddeviceconfig()) { 242 | auto resp = api.upload_device_config()->call(); 243 | api.device_config_token = resp.payload().uploaddeviceconfigresponse().uploaddeviceconfigtoken(); 244 | 245 | toc = api.fetch_toc()->call(); 246 | assert(!toc.payload().tocresponse().requiresuploaddeviceconfig() && 247 | toc.payload().tocresponse().has_cookie()); 248 | api.toc_cookie = toc.payload().tocresponse().cookie(); 249 | if (toc.payload().tocresponse().has_toscontent() && toc.payload().tocresponse().has_tostoken()) { 250 | if (opt_interactive) 251 | std::cout << "Terms of Service:" << std::endl 252 | << toc.payload().tocresponse().toscontent() << " [y/N]: "; 253 | bool allow_marketing_emails = false; 254 | if (!opt_accept_tos) { 255 | std::string str; 256 | if (!opt_interactive) { 257 | std::cerr << "error: tos not accepted" << std::endl; 258 | exit(1); 259 | } 260 | std::getline(std::cin, str); 261 | if (str[0] != 'Y' && str[0] != 'y') { 262 | std::cout << "You have to accept the Terms of Service!" << std::endl; 263 | exit(1); 264 | } 265 | std::cout << "Optional: " << toc.payload().tocresponse().toscheckboxtextmarketingemails() 266 | << " [y/N]: "; 267 | std::getline(std::cin, str); 268 | allow_marketing_emails = (str[0] == 'Y' || str[0] == 'y'); 269 | } 270 | auto tos = api.accept_tos(toc.payload().tocresponse().tostoken(), allow_marketing_emails)->call(); 271 | assert(tos.payload().has_accepttosresponse()); 272 | dev_state.set_api_data(login_api.get_email(), api); 273 | dev_state.save(); 274 | } 275 | } 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "arg_list.h" 4 | #include 5 | #include 6 | #include 7 | 8 | class playapi_cli_base { 9 | 10 | public: 11 | bool opt_interactive = false; 12 | std::string opt_email, opt_password, opt_token; 13 | bool opt_save_auth = false, opt_login_no_verify = false; 14 | bool opt_accept_tos = false; 15 | std::string opt_device_path = "devices/default.conf"; 16 | 17 | app_config conf; 18 | playapi::device_info device; 19 | playapi::file_login_cache login_cache; 20 | playapi::login_api login_api; 21 | playapi::api api; 22 | 23 | playapi_cli_base(); 24 | 25 | 26 | void print_global_help(); 27 | 28 | virtual void print_help() = 0; 29 | 30 | virtual bool parse_arg(arg_list &list, const char *key); 31 | 32 | void parse_args(int argc, const char* argv[]); 33 | 34 | void perform_auth(); 35 | 36 | private: 37 | void do_interactive_auth(); 38 | 39 | void do_auth_from_config(); 40 | 41 | }; -------------------------------------------------------------------------------- /src/config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #include 4 | #include "../include/playapi/device_info.h" 5 | #include "../include/playapi/api.h" 6 | 7 | using namespace playapi; 8 | 9 | void app_config::load() { 10 | std::ifstream s(path); 11 | config.load(s); 12 | 13 | user_email = config.get("user_email"); 14 | user_token = config.get("user_token"); 15 | } 16 | 17 | void app_config::save() { 18 | config.set("user_email", user_email); 19 | config.set("user_token", user_token); 20 | 21 | std::ofstream s(path); 22 | config.save(s); 23 | } 24 | 25 | void device_config::load() { 26 | std::ifstream s(path); 27 | config.load(s); 28 | 29 | checkin_data.time = config.get_long("checkin.time", checkin_data.time); 30 | checkin_data.android_id = (unsigned long long) config.get_long("checkin.android_id", checkin_data.android_id); 31 | checkin_data.security_token = (unsigned long long) config.get_long("checkin.security_token", checkin_data.security_token); 32 | checkin_data.device_data_version_info = config.get("checkin.device_data_version_info", checkin_data.device_data_version_info); 33 | } 34 | 35 | void device_config::save() { 36 | config.set_long("checkin.time", checkin_data.time); 37 | config.set_long("checkin.android_id", checkin_data.android_id); 38 | config.set_long("checkin.security_token", checkin_data.security_token); 39 | config.set("checkin.device_data_version_info", checkin_data.device_data_version_info); 40 | 41 | std::ofstream s(path); 42 | config.save(s); 43 | } 44 | 45 | void device_config::load_device_info_data(playapi::device_info& dev) { 46 | dev.generated_mac_addr = config.get("generated_mac_addr", dev.generated_mac_addr); 47 | dev.generated_meid = config.get("generated_meid", dev.generated_meid); 48 | dev.generated_serial_number = config.get("generated_serial_number", dev.generated_serial_number); 49 | dev.random_logging_id = config.get_long("random_logging_id", dev.random_logging_id); 50 | } 51 | 52 | void device_config::set_device_info_data(const playapi::device_info& dev) { 53 | config.set("generated_mac_addr", dev.generated_mac_addr); 54 | config.set("generated_meid", dev.generated_meid); 55 | config.set("generated_serial_number", dev.generated_serial_number); 56 | config.set_long("random_logging_id", dev.random_logging_id); 57 | } 58 | 59 | void device_config::load_api_data(const std::string& email, playapi::api& api) { 60 | std::string p = "api." + email + "."; 61 | api.device_config_token = config.get(p + "device_config_token", api.device_config_token); 62 | api.toc_cookie = config.get(p + "toc_cookie", api.toc_cookie); 63 | api.experiments.set_targets(config.get(p + "experiments")); 64 | } 65 | 66 | void device_config::set_api_data(const std::string& email, const playapi::api& api) { 67 | std::string p = "api." + email + "."; 68 | config.set(p + "device_config_token", api.device_config_token); 69 | config.set(p + "toc_cookie", api.toc_cookie); 70 | config.set(p + "experiments", api.experiments.get_comma_separated_target_list()); 71 | } -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "../include/playapi/util/config.h" 6 | #include "../include/playapi/checkin.h" 7 | 8 | namespace playapi { struct device_info; class api; } 9 | 10 | struct app_config { 11 | 12 | playapi::config config; 13 | std::string path; 14 | 15 | std::string user_email, user_token; 16 | 17 | app_config(const std::string& path) : path(path) { 18 | load(); 19 | } 20 | 21 | void load(); 22 | 23 | void save(); 24 | 25 | }; 26 | 27 | struct device_config { 28 | 29 | playapi::config config; 30 | std::string path; 31 | 32 | playapi::checkin_result checkin_data; 33 | 34 | device_config(const std::string& path) : path(path) { 35 | load(); 36 | } 37 | 38 | 39 | void load(); 40 | 41 | void save(); 42 | 43 | void load_device_info_data(playapi::device_info& dev); 44 | 45 | void set_device_info_data(const playapi::device_info& dev); 46 | 47 | void load_api_data(const std::string& email, playapi::api& api); 48 | 49 | void set_api_data(const std::string& email, const playapi::api& api); 50 | 51 | }; -------------------------------------------------------------------------------- /src/gplaydl.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "../include/playapi/file_login_cache.h" 6 | #include "config.h" 7 | #include "common.h" 8 | 9 | using namespace playapi; 10 | 11 | struct playapi_cli_downloader : playapi_cli_base { 12 | 13 | std::string opt_app; 14 | int opt_app_version = -1; 15 | std::string opt_app_output; 16 | 17 | bool parse_arg(arg_list &list, const char *key) override; 18 | 19 | void print_help() override { 20 | std::cout << "Google Play Downloader tool" << std::endl << std::endl; 21 | print_global_help(); 22 | std::cout << "Download options:" << std::endl; 23 | std::cout << "-a --app Download this app (package name)" << std::endl; 24 | std::cout << "-v --app-version Specify the version to download" << std::endl; 25 | std::cout << "-o --output Specify the output file name" << std::endl;; 26 | } 27 | 28 | void download_file(http_request &req, std::string file_name, bool gzipped); 29 | 30 | void run(); 31 | 32 | }; 33 | 34 | static void do_zlib_inflate(z_stream& zs, FILE* file, char* data, size_t len, int flags) { 35 | char buf[4096]; 36 | int ret; 37 | zs.avail_in = (uInt) len; 38 | zs.next_in = (unsigned char*) data; 39 | zs.avail_out = 0; 40 | while (zs.avail_out == 0) { 41 | zs.avail_out = 4096; 42 | zs.next_out = (unsigned char*) buf; 43 | ret = inflate(&zs, flags); 44 | assert(ret != Z_STREAM_ERROR); 45 | fwrite(buf, 1, sizeof(buf) - zs.avail_out, file); 46 | } 47 | } 48 | 49 | bool playapi_cli_downloader::parse_arg(arg_list &list, const char *key) { 50 | if (strcmp(key, "-a") == 0 || strcmp(key, "--app") == 0) 51 | opt_app = list.next_value(); 52 | else if (strcmp(key, "-v") == 0 || strcmp(key, "--app-version") == 0) 53 | opt_app_version = atoi(list.next_value()); 54 | else if (strcmp(key, "-o") == 0 || strcmp(key, "--output") == 0) 55 | opt_app_output = list.next_value(); 56 | else 57 | return playapi_cli_base::parse_arg(list, key); 58 | return true; 59 | } 60 | 61 | void playapi_cli_downloader::run() { 62 | if (opt_app.length() == 0) { 63 | if (!opt_interactive) { 64 | std::cerr << "error: no app package name provided\n"; 65 | exit(1); 66 | } 67 | std::cout << "Please type the name of the app you want to download: "; 68 | std::cin >> opt_app; 69 | } 70 | if (opt_app_version == -1) { 71 | auto details = api.details(opt_app)->call().payload().detailsresponse().docv2(); 72 | if (!details.details().appdetails().has_versioncode()) { 73 | if (opt_interactive) 74 | std::cerr << "No version code found. Did you specify a valid package name?" << std::endl; 75 | else 76 | std::cerr << "error: no version code found\n"; 77 | exit(1); 78 | } 79 | opt_app_version = details.details().appdetails().versioncode(); 80 | } 81 | 82 | // TODO: Free app purchase flow 83 | 84 | { 85 | // TODO: we should pass a valid library token here - however that requires implementation of the 86 | // replicateLibrary API call 87 | auto resp = api.delivery(opt_app, opt_app_version, std::string())->call(); 88 | auto dd = resp.payload().deliveryresponse().appdeliverydata(); 89 | http_request req(dd.has_gzippeddownloadurl() ? dd.gzippeddownloadurl() : dd.downloadurl()); 90 | req.add_header("Accept-Encoding", "identity"); 91 | auto cookie = dd.downloadauthcookie(0); 92 | req.add_header("Cookie", cookie.name() + "=" + cookie.value()); 93 | req.set_user_agent("AndroidDownloadManager/" + device.build_version_string + " (Linux; U; Android " + 94 | device.build_version_string + "; " + device.build_model + " Build/" + device.build_id + ")"); 95 | req.set_follow_location(true); 96 | 97 | std::string file_name = opt_app_output; 98 | if (file_name.length() == 0) 99 | file_name = opt_app + " " + std::to_string(opt_app_version) + ".apk"; 100 | 101 | download_file(req, file_name, dd.has_gzippeddownloadurl()); 102 | } 103 | } 104 | 105 | void playapi_cli_downloader::download_file(http_request &req, std::string file_name, bool gzipped) { 106 | if (gzipped) 107 | req.set_encoding("gzip,deflate"); 108 | req.set_timeout(0L); 109 | 110 | FILE* file = fopen(file_name.c_str(), "w"); 111 | z_stream zs; 112 | zs.zalloc = Z_NULL; 113 | zs.zfree = Z_NULL; 114 | zs.opaque = Z_NULL; 115 | 116 | if (gzipped) { 117 | int ret = inflateInit2(&zs, 31); 118 | assert(ret == Z_OK); 119 | 120 | req.set_custom_output_func([file, &zs](char* data, size_t size) { 121 | do_zlib_inflate(zs, file, data, size, Z_NO_FLUSH); 122 | return size; 123 | }); 124 | } else { 125 | req.set_custom_output_func([file, &zs](char* data, size_t size) { 126 | fwrite(data, sizeof(char), size, file); 127 | return size; 128 | }); 129 | } 130 | 131 | req.set_progress_callback([&req](curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { 132 | if (dltotal > 0) { 133 | printf("\rDownloaded %i%% [%lli/%lli MiB]", (int) (dlnow * 100 / dltotal), dlnow / 1024 / 1024, 134 | dltotal / 1024 / 1024); 135 | std::cout.flush(); 136 | } 137 | }); 138 | std::cout << std::endl << "Starting download..."; 139 | req.perform(); 140 | 141 | if (gzipped) { 142 | do_zlib_inflate(zs, file, Z_NULL, 0, Z_FINISH); 143 | inflateEnd(&zs); 144 | } 145 | 146 | fclose(file); 147 | } 148 | 149 | int main(int argc, char* argv[]) { 150 | curl_global_init(CURL_GLOBAL_ALL); 151 | GOOGLE_PROTOBUF_VERIFY_VERSION; 152 | 153 | playapi_cli_downloader cli; 154 | cli.parse_args(argc, (const char **) argv); 155 | cli.perform_auth(); 156 | cli.run(); 157 | 158 | curl_global_cleanup(); 159 | google::protobuf::ShutdownProtobufLibrary(); 160 | return 0; 161 | } 162 | -------------------------------------------------------------------------------- /src/gplayver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "../include/playapi/file_login_cache.h" 6 | #include "config.h" 7 | #include "common.h" 8 | 9 | using namespace playapi; 10 | 11 | struct playapi_cli_ver : playapi_cli_base { 12 | 13 | std::string opt_app; 14 | bool opt_fast = false; 15 | 16 | bool parse_arg(arg_list &list, const char *key) override; 17 | 18 | void print_help() override { 19 | std::cout << "Google Play App Info tool" << std::endl << std::endl; 20 | print_global_help(); 21 | std::cout << "App Info options:" << std::endl; 22 | std::cout << "-a --app Download this app (package name)" << std::endl; 23 | std::cout << "-f --fast Uses a more lightweight query" << std::endl; 24 | } 25 | 26 | void run(); 27 | 28 | }; 29 | 30 | bool playapi_cli_ver::parse_arg(arg_list &list, const char *key) { 31 | if (strcmp(key, "-a") == 0 || strcmp(key, "--app") == 0) 32 | opt_app = list.next_value(); 33 | else if (strcmp(key, "-f") == 0 || strcmp(key, "--fast") == 0) 34 | opt_fast = true; 35 | else 36 | return playapi_cli_base::parse_arg(list, key); 37 | return true; 38 | } 39 | 40 | void playapi_cli_ver::run() { 41 | if (opt_fast) { 42 | auto details = api.bulk_details({opt_app})->call(); 43 | if (details.payload().bulkdetailsresponse().entry_size() != 1) 44 | throw std::runtime_error("Invalid response: entry_size() != 1"); 45 | if (!details.payload().bulkdetailsresponse().entry(0).has_doc() || 46 | !details.payload().bulkdetailsresponse().entry(0).doc().has_details() || 47 | !details.payload().bulkdetailsresponse().entry(0).doc().details().has_appdetails()) 48 | throw std::runtime_error("Invalid response: does not have details"); 49 | auto app_details = details.payload().bulkdetailsresponse().entry(0).doc().details().appdetails(); 50 | std::cout << "version code: " << app_details.versioncode() << std::endl; 51 | std::cout << "version string: " << app_details.versionstring() << std::endl; 52 | std::cout << "changelog: " << app_details.recentchangeshtml() << std::endl; 53 | return; 54 | } 55 | 56 | auto details = api.details(opt_app)->call().payload().detailsresponse().docv2(); 57 | std::cout << "version code: " << details.details().appdetails().versioncode() << std::endl; 58 | std::cout << "version string: " << details.details().appdetails().versionstring() << std::endl; 59 | std::cout << "changelog: " << details.details().appdetails().recentchangeshtml() << std::endl; 60 | } 61 | 62 | int main(int argc, char* argv[]) { 63 | curl_global_init(CURL_GLOBAL_ALL); 64 | GOOGLE_PROTOBUF_VERIFY_VERSION; 65 | 66 | playapi_cli_ver cli; 67 | cli.parse_args(argc, (const char **) argv); 68 | cli.perform_auth(); 69 | cli.run(); 70 | 71 | curl_global_cleanup(); 72 | google::protobuf::ShutdownProtobufLibrary(); 73 | return 0; 74 | } 75 | --------------------------------------------------------------------------------