Форум программистов, компьютерный форум, киберфорум
C# для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 5.00/9: Рейтинг темы: голосов - 9, средняя оценка - 5.00
97 / 4 / 0
Регистрация: 09.05.2015
Сообщений: 70

Приложение для скачивания apk-файлов из магазина Google Play

19.11.2016, 11:37. Показов 1951. Ответов 1
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
У меня возникла необходимость создания приложения на С# для программного скачивания apk-файлов из магазина Google Play по HTTP-протоколу.

Для облегчения решения этой задачи я нашла уже работающий прототип этого приложения, реализованный на JavaScript.

Этим прототипом является плагин-приложение к браузеру "APK Downloader".

Вот ссылка на этот плагин:

[Official] APK Downloader v2 – Download APK files from Google Play Store to PC
http://codekiem.com/2014/08/07... /#more-374

Вот видео с последовательностью всех необходимых шагов для осуществления этой процедуры:

Download apk-files directly from Google Play
https://www.youtube.com/watch?v=7i98JlCENcc

Механизм "общения" c магазином Google Play хорошо описан в статье:

When Angry Birds Attack: Android Edition
https://jon.oberheide.org/blog... d-edition/

Вот ссылка на русскоязычный вариант описания механизма общения с магазином Google Play:

ANDROID-МАРИОНЕТКИ: CИСТЕМА УПРАВЛЕНИЯ ВСЕМИ УСТРОЙСТВАМИ НА ANDROID
http://xakep-archive.ru/xa/150/56319/default.htm

Теперь мне бы хотелось разобраться, каким образом приведенный выше плагин самостоятельно осуществляет загрузку apk-файлов из магазина Google Play.

По большому счету, необходимо всего лишь получить authToken от Android AccountManager для прохождения авторизации и правильно сконструировать HTTP-запрос на сервер, чтобы скачать apk-файл из магазина Google Play.

Сам плагин "APK Downloader" написан на JavaScript.

Поэтому исходный код алгоритма загрузки apk-файлов из магазина Google Play - фактически открыт.

Я заглянула в папку с плагином и обнаружила там файл login.html, который в свою очередь использует подключаемые JavaScript-файлы для общения с сервером Google Play и для отправки запросов на сервер при скачивании apk-файлов из магазина Google Play.

Например, авторизация осуществляется при помощи подключаемого JavaScript-файла login.js.

В принципе, как я уже писала выше, авторизацию можно осуществлять не так как сделал автор плагина (логин/пароль), а при помощи получения программным образом authToken от Android AccountManager как это описано в статьях (см. выше).

Вот JavaScript-файл из этого плагина, в котором происходит основная работа с загрузкой самого apk-файла из магазина Google Play.

Файл protocol.js
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
'use strict';
 
/**
 * Protocol-related functions. For a PHP implementation, see
 * [url]http://thomascannon.net/blog/2011/06/downloading-apks-from-android-market/[/url]
 *
 * Authors:
 *    redphx <http://codekiem.com/>
 *    Stephan Schmitz <eyecatchup@gmail.com>
 *    Peter Wu <lekensteyn@gmail.com>
 */
 
/**
 * Serialize Javascript types in a special format used by MarketSession.
 */
var Utils = {
  parseParams: function(url) {
    var params = {};
    url.split('?')[1].split('&').forEach(function(el) {
      el = el.split('=');
      params[el[0]] = el[1];
    });
    return params;
  },
 
  stringToByteArray: function(str) {
    var b = [];
    for (var pos = 0, size = str.length; pos < size; ++pos) {
      b.push(str.charCodeAt(pos));
    }
    return b;
  },
 
  serializeInt32: function(num) {
    var data = [];
    for (var times = 0; times < 5; times++) {
      var elm = num % 128;
      if ((num >>>= 7)) { // jshint ignore:line
        elm += 128;
      }
      data.push(elm);
      if (num === 0) {
        break;
      }
    }
    return data;
  },
 
  serializeData: function(arr, value) {
    var newData = [];
    var dataType = typeof value;
    switch (dataType) {
      case 'string':
        newData = newData.concat(this.serializeInt32(value.length))
                         .concat(this.stringToByteArray(value));
        break;
      case 'number':
        newData = newData.concat(this.serializeInt32(value));
        break;
      case 'boolean':
        newData.push(value ? 1 : 0);
        break;
    }
    return arr.concat(newData);
  },
 
  formatData: function(def) {
    var data = [];
    for (var i = 0, size = def.length; i < size; i++) {
      var val = def[i];
      if (val instanceof Array) {
        data = data.concat(val);
        continue;
      }
 
      data = this.serializeData(data, val);
    }
 
    var arr = [];
    arr = arr.concat(this.serializeInt32(data.length));
    arr = arr.concat(data);
    return arr;
  },
 
  unpackGzip: function(content) {
    var chars = new Uint8Array(content);
    /* gzipped content, try to unpack */
    return BrowserGzip.uncompress(chars);
  }
};
 
/* Starts an APK download attempt */
var MarketSession = {
  /**
   * Called when pressing the APK Downloader icon in the location bar.
   */
  download: function(packageName, versionCode, tabId) {
    BrowserStorage.get(['account', 'sim'], function(items) {
      if (!items.account || !items.sim) {
        BrowserTabs.create({
          url: 'options.html'
        });
        return;
      }
 
      if (!items.account.deviceCodename) {
        items.account.deviceCodename = 'hammerhead';
      }
 
      if (!items.account.deviceSdk) {
        items.account.deviceSdk = 19;
      }
 
      var options = {
        authToken: items.account.authToken,
        isSecure: true,
        sdkVersion: 2009011,
        deviceId: items.account.deviceId,
        deviceAndSdkVersion: items.account.deviceCodename + ':' + items.account.deviceSdk,
        locale: 'en',
        country: 'US',
        operatorAlpha: items.sim.operator,
        simOperatorAlpha: items.sim.operator,
        operatorNumeric: '' + items.sim.operatorCode,
        simOperatorNumeric: '' + items.sim.operatorCode,
        packageName: packageName,
        versionCode: versionCode,
        nonce: 123456
      };
 
      if (!versionCode) {
        MarketSession.getVersionCode(options, function() {
          if (options.versionCode > 0) {
            MarketSession.download(options.packageName, options.versionCode, tabId);
          } else {
            BrowserTabs.sendMessage(tabId, {
              cmd: 'downloadResponse',
              error: -2
            });
          }
        });
        return;
      }
 
      var assetQueryBase64 = MarketSession.generateAssetRequest('apk', options);
 
      var API_URL = 'https://android.clients.google.com/market/licensing/LicenseRequest';
      BrowserCookie.set({
          url: API_URL,
          name: 'ANDROIDSECURE',
          value: items.account.authToken,
        }, function() {
          var postData = {
            version: 2,
            request: assetQueryBase64
          };
 
          BrowserApi.post(postData, function(xhr) {
            if (xhr.status !== 200) {
              BrowserTabs.sendMessage(tabId, {
                cmd: 'downloadResponse',
                error: -1
              });
              return;
            }
 
            var data = Utils.unpackGzip(xhr.response);
            var appUrl, marketDA;
 
            var urls = data.match(/https?:\/\/.*?downloadId=[0-9\-\_]+/ig);
            if (urls && urls.length > 0) {
              /* not sure if decoding is even necessary */
              appUrl = decodeURIComponent(urls[0]);
              console.log(appUrl);
 
              /* format: 'MarketDA', 0x72 ('r'), length of data, data */
              if ((marketDA = /MarketDA..(\d+)/.exec(data))) {
                marketDA = marketDA[1];
                var name = packageName + (versionCode ? '-' + versionCode : '');
                var filename = name + '.apk';
 
                BrowserCookie.set({
                  url: appUrl,
                  name: 'MarketDA',
                  value: marketDA
                }, function() {
                  MarketSession.getRedirectUrl(appUrl, function(redirectUrl) {
                    BrowserCookie.set({
                      url: redirectUrl,
                      name: 'MarketDA',
                      value: marketDA
                    }, function() {
                      BrowserDownloads.download(redirectUrl, filename, 'apk-downloader');
                      MarketSession.downloadObbs(options);
                      BrowserTabs.sendMessage(tabId, {
                        cmd: 'downloadResponse',
                        error: 1
                      });
                    });
                  });
                });
                return;
              }
            } else {
              BrowserTabs.sendMessage(tabId, {
                cmd: 'downloadResponse',
                error: -2
              });
            }
          });
      });
    });
  },
 
  getRedirectUrl: function(url, callback) {
    var client = new XMLHttpRequest();
    client.open('GET', url);
    client.onreadystatechange = function() {
      if (this.readyState === this.HEADERS_RECEIVED) {
        callback(this.responseURL);
      }
    };
    client.send();
  },
 
  downloadObbs: function(options) {
    var assetQueryBase64 = MarketSession.generateAssetRequest('obb', options);
 
    var postData = {
      version: 2,
      request: assetQueryBase64
    };
 
    BrowserApi.post(postData, function(xhr) {
      var data = Utils.unpackGzip(xhr.response);
      var fileUrls, fileNames;
      var regexFileUrls = /FILE\_URL[0-9]+=([^&]+)/g;
      var regexFileNames = /FILE\_NAME[0-9]+=([^&]+)/g;
      while ((fileUrls = regexFileUrls.exec(data)) !== null) {
        fileNames = regexFileNames.exec(data);
        var saveLocation = 'apk-downloader/' + options.packageName + (options.versionCode ? '-' + options.versionCode : '') + '-obbs';
        BrowserDownloads.download(decodeURIComponent(fileUrls[1]), fileNames[1], saveLocation);
      }
    });
  },
 
  getVersionCode: function(options, callback) {
    var assetQueryBase64 = MarketSession.generateAssetRequest('info', options);
 
    var postData = {
      version: 2,
      request: assetQueryBase64
    };
 
    BrowserApi.post(postData, function(xhr) {
      var data = Utils.unpackGzip(xhr.response);
      var match = data.match(/v2:[\w\.]+:1:([0-9]+)/);
 
      options.versionCode = match ? parseInt(match[1]) : -1;
      callback && callback();
    });
  },
 
  /**
   * @returns base64 encoded binary data that can be passed to Google Play API.
   */
  generateAssetRequest: function(type, options) {
    /* describes format of request, numbers will be filled in, arrays of
     * numbers will be appended as-is */
 
    var requestContextDef = [
      [0x0A], options.authToken,
      [0x10], options.isSecure,
      [0x18], options.sdkVersion,
      [0x22], options.deviceId,
      [0x2A], options.deviceAndSdkVersion,
      [0x32], options.locale,
      [0x3A], options.country,
      [0x42], options.operatorAlpha,
      [0x4A], options.simOperatorAlpha,
      [0x52], options.operatorNumeric,
      [0x5A], options.simOperatorNumeric
    ];
 
    var protoData = [];
    protoData.push(0x0A);
    protoData = protoData.concat(Utils.formatData(requestContextDef));
    protoData.push(0x13);
 
    var assetContextDef = [];
    if (type === 'apk') {
      protoData.push(0x52);
      var pkg = options.versionCode ? 'v2:' + options.packageName + ':1:' + options.versionCode : options.packageName;
      assetContextDef = [
        [0x0A], pkg
      ];
    } else if (type === 'obb') {
      protoData.push(0x92, 0x01);
      assetContextDef = [
        [0x0A], options.packageName,
        [0x10], options.versionCode,
        [0x18], options.nonce
      ];
    } else if (type === 'info') {
      protoData.push(0x22);
      assetContextDef = [
        [0x22], options.packageName,
        [0x30, 0x01]
      ];
    }
    protoData = protoData.concat(Utils.formatData(assetContextDef));
 
    protoData.push(0x14);
    var binary = protoData.map(function (c) {
      return String.fromCharCode(c);
    }).join('');
 
    var base64 = btoa(binary);
    // makes it url safe
    base64 = base64.replace(/\//g, '_').replace(/\+/g, '-').replace(/\=+$/, '');
    return base64;
  }
};
Насколько я поняла, в файле protocol.js осуществляется загрузка apk-файла, а в файле login.js - авторизация.

Мне же необходимо осуществить авторизацию через authToken, полученный программным образом от Android AccountManager.

А затем сконструировать HTTP-запрос на сервер Google Play для скачивания apk-файла.

Для этого необходимо сформировать корректную структуру request-запроса в соответствии с форматом protobuf, используемом Google для работы с серверами магазина Google Play.

Алгоритм заполнения структуры протокола protobuf приведен в файле protocol.js.

Таким образом, мне необходимо "перевести" код с JavaScript на язык C# для дальнейшего использования в своей игре для реализации механизма ее автоматического обновления путем программного скачивания apk-файла на устройство из магазина Google Play и программной его переустановки с локального диска.

Про то, что при этом будет выведено дополнительное диалоговое окно для подтверждения действий по переустановке игры - я в курсе.

Предполагаю решить эту задачу в несколько этапов, в связи с чем у меня возникло несколько вопросов.

Скажите, каким образом на языке С# будет выглядеть программная реализация для следующих методов:

1. Авторизация через authToken, получаемый программным образом от Android AccountManager?

2. Формирование корректной структуры request-запроса в соответствии с форматом protobuf и получением всех необходимых полей программным образом из настроек мобильного устройства?

3. Осуществление, собственно, самого HTTP-запроса на сервер Google Play для скачивания apk-файла?


Заранее благодарна, за конкретику в ответах на эти вопросы.
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
19.11.2016, 11:37
Ответы с готовыми решениями:

Не выкладывается apk в Google play
Сбой при загрузке Не удается проанализировать APK с использованием команды aapt. Текст ошибки: Failed to run aapt dump badging: ...

Unity 3d проблемы с загрузкой APK файла в Google Play
Делал игру на Unity для андроид . Создал аккаунт разработчика . Загружаю APK файл ,который успешно построила в Unity. Когда загрузка...

Загрузка APK файла программы с google play программно
Делаю автоматическое обновление программы если в google play имеется более свежая версия её. Сделал проеврку версии. Как реализовать...

1
Си-решеточник
 Аватар для Rameron
141 / 135 / 60
Регистрация: 07.02.2011
Сообщений: 669
29.11.2016, 10:22
Цитата Сообщение от InessaSuper Посмотреть сообщение
1. Авторизация через authToken, получаемый программным образом от Android AccountManager?
Выглядеть будет как, в принципе и везде - передаваться в соответствующем заголовке. Могу скинуть пример, где я с сайта железной дороги запрашиваю информацию. Там также с помощью запроса получаются ключ сессии и токен для будущего запроса, формируется на их основе запрос определенного формата и выполняется с последующим получением данных.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
29.11.2016, 10:22
Помогаю со студенческими работами здесь

Не получается добавить apk с новой версией приложения в Google Play
Я хотел выложить новую версию приложения в google play, но при загрузке возникла ошибка: у старого и нового apk разные опечатки. Все дело в...

Ошибка при добавлении .apk файла в google play: запретите отладку
когда загружаю в guugle play .apk-файл, то гугл пишет ошибку: &quot;Прежде чем опубликовать APK-файл в Google Play, запретите его...

Приложение и Google Play
Такой вопрос. Я скачал к одному приложению XMODGames и установил патч. Поиграл, всё ок, но через день у меня при нормально работающем...

Приложение на Google Play
Подскажите пожалуйста, нужно ли как-то особенно генерировать ключи (подписывать) перед выпуском приложения и заливкой его в Google Play,...

Размещение приложение в google play
Для публикации приложения в Google play достаточно проделать данные действия ? http://developer.alexanderklimov.ru/android/publish.php...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
2
Ответ Создать тему
Новые блоги и статьи
SDL3 для Web (WebAssembly): Реализация движения на Box2D v3 - трение и коллизии с повёрнутыми стенами
8Observer8 20.02.2026
Содержание блога Box2D позволяет легко создать главного героя, который не проходит сквозь стены и перемещается с заданным трением о препятствия, которые можно располагать под углом, как верхнее. . .
Конвертировать закладки radiotray-ng в m3u-плейлист
damix 19.02.2026
Это можно сделать скриптом для PowerShell. Использование . \СonvertRadiotrayToM3U. ps1 <path_to_bookmarks. json> Рядом с файлом bookmarks. json появится файл bookmarks. m3u с результатом. # Check if. . .
Семь CDC на одном интерфейсе: 5 U[S]ARTов, 1 CAN и 1 SSI
Eddy_Em 18.02.2026
Постепенно допиливаю свою "многоинтерфейсную плату". Выглядит вот так: https:/ / www. cyberforum. ru/ blog_attachment. php?attachmentid=11617&stc=1&d=1771445347 Основана на STM32F303RBT6. На борту пять. . .
Камера Toupcam IUA500KMA
Eddy_Em 12.02.2026
Т. к. у всяких "хикроботов" слишком уж мелкий пиксель, для подсмотра в ESPriF они вообще плохо годятся: уже 14 величину можно рассмотреть еле-еле лишь на экспозициях под 3 секунды (а то и больше),. . .
И ясному Солнцу
zbw 12.02.2026
И ясному Солнцу, и светлой Луне. В мире покоя нет и люди не могут жить в тишине. А жить им немного лет.
«Знание-Сила»
zbw 12.02.2026
«Знание-Сила» «Время-Деньги» «Деньги -Пуля»
SDL3 для Web (WebAssembly): Подключение Box2D v3, физика и отрисовка коллайдеров
8Observer8 12.02.2026
Содержание блога Box2D - это библиотека для 2D физики для анимаций и игр. С её помощью можно определять были ли коллизии между конкретными объектами и вызывать обработчики событий столкновения. . . .
SDL3 для Web (WebAssembly): Загрузка PNG с прозрачным фоном с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 11.02.2026
Содержание блога Библиотека SDL3 содержит встроенные инструменты для базовой работы с изображениями - без использования библиотеки SDL3_image. Пошагово создадим проект для загрузки изображения. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru