Big update

- Added asset session downloader
- Added on off option logging request types
- Updated proto files
- Updated readme.md
- Smoother setup process
- Added various console commands
- Added claim codename packet
- Added encounter packet
- Added *fixed* download url packet
- Various changes, cant remember at all
This commit is contained in:
Felix 2016-08-15 13:09:42 +02:00
parent 1db70b4d96
commit bf4e26e338
41 changed files with 1971 additions and 324 deletions

View File

@ -7,11 +7,12 @@ message CatchPokemonLogEntry {
.POGOProtos.Data.Logs.CatchPokemonLogEntry.Result result = 1;
.POGOProtos.Enums.PokemonId pokemon_id = 2;
int32 combat_points = 3;
uint64 pokemon_data_id = 4;
fixed64 pokemon_data_id = 4;
enum Result {
UNSET = 0;
POKEMON_CAPTURED = 1;
POKEMON_FLED = 2;
POKEMON_HATCHED = 3;
}
}

View File

@ -21,4 +21,5 @@ message PlayerData {
.POGOProtos.Data.Player.EquippedBadge equipped_badge = 12;
.POGOProtos.Data.Player.ContactSettings contact_settings = 13;
repeated .POGOProtos.Data.Player.Currency currencies = 14;
int32 remaining_codename_claims = 15;
}

View File

@ -3,6 +3,7 @@ package POGOProtos.Map.Fort;
import "POGOProtos/Enums/PokemonId.proto";
import "POGOProtos/Enums/TeamColor.proto";
import "POGOProtos/Inventory/Item/ItemId.proto";
import "POGOProtos/Map/Fort/FortType.proto";
import "POGOProtos/Map/Fort/FortSponsor.proto";
import "POGOProtos/Map/Fort/FortRenderingType.proto";
@ -32,14 +33,12 @@ message FortData {
bool is_in_battle = 11;
//// Fields related to pokestops only
repeated .POGOProtos.Inventory.Item.ItemId active_fort_modifier = 12;
.POGOProtos.Map.Fort.FortLureInfo lure_info = 13;
// Timestamp when the pokestop can be activated again to get items / xp
int64 cooldown_complete_timestamp_ms = 14;
.POGOProtos.Map.Fort.FortSponsor sponsor = 15;
.POGOProtos.Map.Fort.FortRenderingType rendering_type = 16;
// Might represent the type of item applied to the pokestop, right now only lures can be applied
bytes active_fort_modifier = 12;
.POGOProtos.Map.Fort.FortLureInfo lure_info = 13;
}

View File

@ -4,9 +4,9 @@ package POGOProtos.Map.Pokemon;
import "POGOProtos/Enums/PokemonId.proto";
message NearbyPokemon {
.POGOProtos.Enums.PokemonId pokemon_id = 1;
float distance_in_meters = 2;
fixed64 encounter_id = 3;
string fort_id = 4;
string fort_image_url = 5;
}
.POGOProtos.Enums.PokemonId pokemon_id = 1;
float distance_in_meters = 2;
fixed64 encounter_id = 3;
string fort_id = 4;
string fort_image_url = 5;
}

View File

@ -5,7 +5,7 @@ message Signature {
message LocationFix {
string provider = 1; // "network", "gps", "fused", possibly others
uint64 timestamp_since_start = 2; // in ms
uint64 timestamp_snapshot = 2; // in ms since start
float latitude = 13;
float longitude = 14;
@ -34,7 +34,7 @@ message Signature {
}
message SensorInfo {
uint64 timestamp_snapshot = 1; // in ms
uint64 timestamp_snapshot = 1; // in ms since start
double magnetometer_x = 3;
double magnetometer_y = 4;
double magnetometer_z = 5;
@ -69,6 +69,7 @@ message Signature {
string firmware_fingerprint = 14; // Android only, build.fingerprint
}
// Only used in iOS - Android just sends an empty version
message ActivityStatus {
// all of these had 1 as their value
uint64 start_time_ms = 1;
@ -88,14 +89,11 @@ message Signature {
SensorInfo sensor_info = 7;
DeviceInfo device_info = 8;
ActivityStatus activity_status = 9;
uint32 location_hash1 = 10; // Location1 hashed based on the auth_token - xxHash32
uint32 location_hash2 = 20; // Location2 hashed based on the auth_token - xxHash32
bytes unknown22 = 22; // possibly replay check. Generation unknown but pointed to by 0001B8614
uint64 location_hash1 = 10; // Location1 hashed signed based on the auth_token or auth_info - xxHash32
uint64 location_hash2 = 20; // Location2 hashed (unsigned) - xxHash32
bytes session_hash = 22; // 16 bytes, unique per session
uint64 timestamp = 23; // epoch timestamp in ms
repeated uint64 request_hash = 24; // hashes of each request message in a hashArray - xxhash64
// Addresses for the corresponding hash functions:
// xxHash32 00054D28
// xxhash64 000546C8 - Feeds into 00053D40
repeated int64 request_hash = 24; // hashes of each request message in a hashArray signed based on the auth_token or auth_info - xxhash64
int64 unknown25 = 25; // for 0.33 its static -8537042734809897855 or 0x898654dd2753a481, generated via xxHash64("\"b8fa9757195897aae92c53dbcf8a60fb3d86d745\"".ToByteArray(), 0x88533787)
}

View File

@ -7,6 +7,6 @@ message Unknown6 {
message Unknown2 {
bytes encrypted_signature = 1; // This are the bytes of POGOProtos/Networking/Envelopes/Signature.proto encrypted.
// For an example check https://github.com/keyphact/pgoapi/blob/75eba6b5b630841ee4f7c2ea983f15874fb0862d/pgoapi/rpc_api.py#L192-L212
// For an example check https://github.com/keyphact/pgoapi/blob/75eba6b5b630841ee4f7c2ea983f15874fb0862d/pgoapi/rpc_api.py#L192-L212
}
}

View File

@ -7,4 +7,5 @@ message GetGymDetailsMessage {
double player_longitude = 3;
double gym_latitude = 4;
double gym_longitude = 5;
string client_version = 6;
}

View File

@ -2,6 +2,4 @@ syntax = "proto3";
package POGOProtos.Networking.Requests.Messages;
message GetPlayerMessage {
// Human readable app version, e.g. "0.31.1"
string app_version = 1;
}

View File

@ -1,6 +1,17 @@
syntax = "proto3";
package POGOProtos.Networking.Responses;
import "POGOProtos/Networking/Responses/FortDetailsResponse.proto";
message AddFortModifierResponse {
// Initialized by assist.py
.POGOProtos.Networking.Responses.AddFortModifierResponse.Result result = 1;
.POGOProtos.Networking.Responses.FortDetailsResponse fort_details = 2;
enum Result {
NO_RESULT_SET = 0;
SUCCESS = 1;
FORT_ALREADY_HAS_MODIFIER = 2;
TOO_FAR_AWAY = 3;
NO_ITEM_IN_INVENTORY = 4;
}
}

View File

@ -1,12 +1,14 @@
syntax = "proto3";
package POGOProtos.Networking.Responses;
// TODO: Might be incorrect, please test.
import "POGOProtos/Data/PlayerData.proto";
message ClaimCodenameResponse {
string codename = 1;
string user_message = 2;
bool is_assignable = 3;
.POGOProtos.Networking.Responses.ClaimCodenameResponse.Status status = 4;
.POGOProtos.Data.PlayerData updated_player = 5;
enum Status {
UNSET = 0;

View File

@ -5,6 +5,7 @@ import "POGOProtos/Settings/FortSettings.proto";
import "POGOProtos/Settings/MapSettings.proto";
import "POGOProtos/Settings/LevelSettings.proto";
import "POGOProtos/Settings/InventorySettings.proto";
import "POGOProtos/Settings/GpsSettings.proto";
message GlobalSettings {
.POGOProtos.Settings.FortSettings fort_settings = 2;
@ -12,4 +13,5 @@ message GlobalSettings {
.POGOProtos.Settings.LevelSettings level_settings = 4;
.POGOProtos.Settings.InventorySettings inventory_settings = 5;
string minimum_client_version = 6;
.POGOProtos.Settings.GpsSettings gps_settings = 7;
}

9
POGOProtos/Settings/GpsSettings.proto vendored Normal file
View File

@ -0,0 +1,9 @@
syntax = "proto3";
package POGOProtos.Settings;
message GpsSettings {
float driving_warning_speed_meters_per_second = 1;
float driving_warning_cooldown_minutes = 2;
float driving_speed_sample_interval_seconds = 3;
int32 driving_speed_sample_count = 4;
}

View File

@ -16,4 +16,5 @@ message GymBattleSettings {
int32 dodge_duration_ms = 12;
int32 minimum_player_level = 13;
int32 swap_duration_ms = 14;
float dodge_damage_reduction_percent = 15;
}

View File

@ -44,4 +44,11 @@ You need a running mongodb service, enter your credentials into ``./cfg.js``.
If everything went fine, it should look like:
![Preview](http://image.prntscr.com/image/6ce92058147b4067b8027c42258a198c.png "")
![Preview](http://image.prntscr.com/image/6ce92058147b4067b8027c42258a198c.png "")
## Todo
- Viewing a pokemon shows invalid data (wrong weight, height, type etc.)
- Transfer button doesnt trigger (maybe related to above?)
- Items doesnt show
- Pokemon models get downloaded correctly, but somehow they dont load properly. They show on map, but not in pokemon info screen, pokedex or while encountering.
- Players exp bar is bugged, proto related?

9
cfg.js
View File

@ -6,9 +6,11 @@ export const SERVER_SAVE_INTERVAL = 1e3 * 60; // 1min
export const SERVER_MAX_CONNECTIONS = 64;
export const SERVER_PLAYER_CONNECTION_TIMEOUT = 1e3 * 60 * 30; // 30min
export const SERVER_LOG_REQUESTS = true;
export const SERVER_DEFAULT_CONSOLE_COLOR = 32;
export const ASSET_DIGEST_PATH = "asset_digest";
export const ASSET_DIGEST_PATH = "0005368950BD6068_ASSET_DIGEST";
export const MINIMUM_CLIENT_VERSION = "0.33.0";
@ -18,4 +20,7 @@ export const SERVER_MONGO_DB_NAME = "pokemongo";
export const SERVER_MONGO_COLLECTION_USERS = "users";
export const SERVER_MONGO_URL = `mongodb://${SERVER_MONGO_HOST_IP}:${SERVER_MONGO_PORT}/${SERVER_MONGO_DB_NAME}`;
export const SERVER_GMAPS_API_KEY = "AIzaSyDF9rkP8lhcddBtvH9gVFzjnNo13WtmJIM";
export const SERVER_GMAPS_API_KEY = "AIzaSyDF9rkP8lhcddBtvH9gVFzjnNo13WtmJIM";
export const SERVER_POGO_CLIENT_USERNAME = "USERNAME";
export const SERVER_POGO_CLIENT_PASSWORD = "PASSWORD";

View File

@ -49,7 +49,7 @@ export function resetTimers() {
this.saveTick++;
// Save interval
if (this.saveTick >= CFG.SERVER_SAVE_INTERVAL) {
this.savePlayers();
this.saveAllPlayers();
this.saveTick = 0;
}
return void 0;

View File

@ -2,20 +2,19 @@ import mongodb from "mongodb";
import * as CFG from "../cfg";
/**
* @param {Function} resolve
*/
export function setupMongo(resolve) {
export function setupMongo() {
mongodb.MongoClient.connect(CFG.SERVER_MONGO_URL, (error, db) => {
if (error) {
this.print("Unable to connect to database", 31);
} else {
this.db.instance = db;
this.loadCollection(CFG.SERVER_MONGO_COLLECTION_USERS).then(() => {
resolve();
});
}
return new Promise((resolve) => {
mongodb.MongoClient.connect(CFG.SERVER_MONGO_URL, (error, db) => {
if (error) {
this.print("Unable to connect to database", 31);
} else {
this.db.instance = db;
this.loadCollection(CFG.SERVER_MONGO_COLLECTION_USERS).then(() => {
resolve();
});
}
});
});
}
@ -70,7 +69,9 @@ export function createUser(obj) {
stardust: obj.stardust,
pokecoins: obj.pokecoins,
avatar: obj.avatar,
contact_settings: obj.contact_settings
team: obj.team,
contact_settings: obj.contact_settings,
tutorial_state: obj.tutorial_state
};
return new Promise((resolve) => {
@ -93,7 +94,9 @@ export function updateUser(obj) {
stardust: obj.stardust,
pokecoins: obj.pokecoins,
avatar: obj.avatar,
contact_settings: obj.contact_settings
team: obj.team,
contact_settings: obj.contact_settings,
tutorial_state: obj.tutorial_state
};
return new Promise((resolve) => {

View File

@ -3,12 +3,13 @@ import http from "http";
import proto from "./proto";
import {
inherit,
decodeLong
inherit
} from "./utils";
import * as CFG from "../cfg";
import pogodown from "pogo-asset-downloader";
import * as _setup from "./setup";
import * as _cycle from "./cycle";
import * as _player from "./player";
@ -62,7 +63,7 @@ class GameServer {
clientAlreadyConnected(client) {
let remoteAddress = client.connection.remoteAddress;
let remoteAddress = client.headers.host;
let ii = 0, length = this.clients.length;
@ -76,14 +77,45 @@ class GameServer {
}
createAssetDownloadSession() {
return new Promise((resolve) => {
pogodown.login({
username: CFG.SERVER_POGO_CLIENT_USERNAME,
password: CFG.SERVER_POGO_CLIENT_PASSWORD,
downloadModels: false
}).then(() => {
resolve();
});
});
}
/**
* @param {Array} assets
*/
generateDownloadUrlByAssetId(assets) {
return new Promise((resolve) => {
pogodown.getAssetByAssetId(assets).then((response) => {
// maybe cache and provide own local download link?
resolve(response);
});
});
}
/**
* @return {HTTP}
*/
createHTTPServer() {
let server = http.createServer((req, res) => {
if (this.clients.length >= CFG.SERVER_MAX_CONNECTIONS) {
this.print(`Server is full! Refused ${req.headers.host}`, 31);
return void 0;
}
this.response = res;
// client already connected
if (!this.clientAlreadyConnected(req)) {
this.addPlayer(req.connection);
this.addPlayer(req);
}
let chunks = [];
req.on("data", (chunk) => {
@ -93,7 +125,7 @@ class GameServer {
let buffer = Buffer.concat(chunks);
req.body = buffer;
this.request = req;
this.onRequest(req, res);
this.routeRequest(req, res);
});
});
server.listen(CFG.SERVER_PORT);
@ -106,7 +138,7 @@ class GameServer {
*/
print(msg, color) {
color = Number.isInteger(color) ? color : CFG.SERVER_DEFAULT_CONSOLE_COLOR;
console.log(`\x1b[${color};1m${msg}\x1b[0m`);
console.log(`[Console] \x1b[${color};1m${msg}\x1b[0m`);
}
greet() {

View File

@ -0,0 +1,9 @@
{
"exp": 0,
"level": 5,
"stardust": 1000,
"pokecoins": 0,
"team": 0,
"items": [],
"pokemons": []
}

View File

View File

@ -10,7 +10,9 @@ export default function CheckAwardedBadges(obj) {
return (
new proto.Networking.Responses.CheckAwardedBadgesResponse({
success: true
success: true,
awarded_badges: [],
awarded_badge_levels: []
}).encode()
);

View File

@ -0,0 +1,25 @@
import proto from "../proto";
import { GetPlayer } from "./";
/**
* @param {Request} req
* @param {Player} player
* @return {Object}
*/
export default function ClaimCodeName(req, player) {
let data = proto.Networking.Requests.Messages.ClaimCodenameMessage.decode(req.request_message.toBuffer());
player.username = data.codename;
return (
new proto.Networking.Responses.ClaimCodenameResponse({
codename: data.codename,
user_message: data.codename,
is_assignable: true,
status: 1
}).encode()
);
}

View File

@ -9,8 +9,8 @@ export default function DownloadRemoteConfigVersion(obj) {
return (
new proto.Networking.Responses.DownloadRemoteConfigVersionResponse({
result: 1,
item_templates_timestamp_ms: 1468540960537,
asset_digest_timestamp_ms: 1467338276561000
item_templates_timestamp_ms: new Date().getTime() * 1e3,
asset_digest_timestamp_ms: new Date().getTime() * 1e3
}).encode()
);

View File

@ -10,30 +10,37 @@ export default function DownloadSettings(obj) {
return (
new proto.Networking.Responses.DownloadSettingsResponse({
hash: "54b359c97e46900f87211ef6e6dd0b7f2a3ea1f5",
hash: "2788184af4004004d6ab0740f7632983332106f6",
settings: new proto.Settings.GlobalSettings({
fort_settings: new proto.Settings.FortSettings({
interaction_range_meters: 40,
max_total_deployed_pokemon: 10,
max_player_deployed_pokemon: 1,
deploy_stamina_multiplier: 2,
far_interaction_range_meters: 1000
"interaction_range_meters": 40.25098039215686,
"max_total_deployed_pokemon": 10,
"max_player_deployed_pokemon": 1,
"deploy_stamina_multiplier": 8.062745098039215,
"deploy_attack_multiplier": 0,
"far_interaction_range_meters": 1000.0156862745098
}),
map_settings: new proto.Settings.MapSettings({
pokemon_visible_range: 70,
poke_nav_range_meters: 751,
encounter_range_meters: 50,
get_map_objects_min_refresh_seconds: 10,
get_map_objects_max_refresh_seconds: 30,
get_map_objects_min_distance_meters: 10,
google_maps_api_key: CFG.SERVER_GMAPS_API_KEY
"pokemon_visible_range": 70.00196078431372,
"poke_nav_range_meters": 751.0156862745098,
"encounter_range_meters": 50.25098039215686,
"get_map_objects_min_refresh_seconds": 10.007843017578125,
"get_map_objects_max_refresh_seconds": 30.01568603515625,
"get_map_objects_min_distance_meters": 10.007843017578125,
"google_maps_api_key": CFG.SERVER_GMAPS_API_KEY
}),
inventory_settings: new proto.Settings.InventorySettings({
max_pokemon: 1000,
max_bag_items: 1000,
base_pokemon: 250,
base_bag_items: 350,
base_eggs: 9
"max_pokemon": 1000,
"max_bag_items": 1000,
"base_pokemon": 250,
"base_bag_items": 350,
"base_eggs": 9
}),
gps_settings: new proto.Settings.GpsSettings({
driving_warning_speed_meters_per_second: 10,
driving_warning_cooldown_minutes: 0,
driving_speed_sample_interval_seconds: 0,
driving_speed_sample_count: 0
}),
minimum_client_version: CFG.MINIMUM_CLIENT_VERSION
})

View File

@ -10,46 +10,68 @@ import { decodeLong } from "../utils";
*/
export default function Encounter(req) {
let data = proto.Networking.Requests.Messages.EncounterMessage.decode(req.request_message.toBuffer());
let encounter_id = decodeLong(
data.encounter_id.high, data.encounter_id.low, data.encounter_id.unsigned
);
let id = decodeLong({
"low": 0,
"high": 0,
"unsigned": true
});
return (
new proto.Networking.Responses.EncounterResponse({
wild_pokemon: new proto.Map.Pokemon.WildPokemon({
encounter_id: 11810991820755313517,
last_modified_timestamp_ms: 1470787552996,
latitude: 39.19047143172622,
longitude: -96.58502161502839,
spawn_point_id: "87bdd289c69",
pokemon_data: new proto.Data.PokemonData({
pokemon_id: 19,
cp: 277,
stamina: 41,
stamina_max: 41,
move_1: 221,
move_2: 26,
height_m: 0.22802678267819977,
weight_kg: 1.3452539511871338,
individual_attack: 9,
individual_defense: 13,
individual_stamina: 14,
cp_multiplier: 0.5663545199394226
wild_pokemon: new proto.Map.Pokemon.WildPokemon({
encounter_id: decodeLong({
"low": -1477718883,
"high": -1178282273,
"unsigned": true
}),
time_till_hidden_ms: 730176
last_modified_timestamp_ms: decodeLong({
"low": 2047340539,
"high": 342,
"unsigned": false
}),
latitude: 39.19071817474392,
longitude: -96.58505386390713,
spawn_point_id: "87bdcd8e959",
pokemon_data: new proto.Data.PokemonData({
id: id,
pokemon_id: 17,
cp: 48,
stamina: 22,
stamina_max: 22,
move_1: 210,
move_2: 45,
deployed_fort_id: "",
owner_name: "",
is_egg: false,
egg_km_walked_target: 0,
egg_km_walked_start: 0,
origin: 0,
height_m: 0.8371214866638184,
weight_kg: 19.039264678955078,
individual_attack: 6,
individual_defense: 12,
individual_stamina: 7,
cp_multiplier: 0.16639786958694458,
pokeball: 0,
captured_cell_id: id,
battles_attacked: 0,
battles_defended: 0,
egg_incubator_id: "",
creation_time_ms: id,
num_upgrades: 0,
additional_cp_multiplier: 0,
favorite: 0,
nickname: "",
from_fort: 0
}),
time_till_hidden_ms: 336009
}),
background: proto.Networking.Responses.EncounterResponse.Background.PARK,
status: proto.Networking.Responses.EncounterResponse.Status.ENCOUNTER_SUCCESS,
background: 0,
status: 1,
capture_probability: new proto.Data.Capture.CaptureProbability({
pokeball_type: [1, 2, 3],
capture_probability: [
0.352886438369751,
0.47944003343582153,
0.5812440514564514
],
reticle_difficulty_scale: 2
capture_probability: [0.6009692549705505, 0.7479367256164551, 0.8407744765281677],
reticle_difficulty_scale: 0
})
}).encode()
);

View File

@ -6,10 +6,10 @@ import * as CFG from "../../cfg";
let asset = fs.readFileSync(CFG.ASSET_DIGEST_PATH);
/**
* @param {Object} obj
* @param {Request} req
* @return {Object}
*/
export default function GetAssetDigest(obj) {
export default function GetAssetDigest(req) {
return (asset);

View File

@ -2,24 +2,42 @@ import proto from "../proto";
import DownloadUrlEntry from "./Data.DownloadUrlEntry";
import {
getDownloadUrlByAssetId
} from "../utils.js";
/**
* @return {Object}
*/
export default function GetDownloadUrls() {
export default function GetDownloadUrls(req, download) {
let download_urls = [
DownloadUrlEntry({
asset_id: "9649e04b-ccd8-4b1c-b066-cc75ed4c0976/1467338147687000",
url: "https://storage.googleapis.com/cloud_assets_pgorelease/bundles/android/pm0101?generation=1467338147687000&GoogleAccessId=pgorelease-service-account@pgorelease.iam.gserviceaccount.com&Expires=1469970173&Signature=CY4YbWipMVMUzyDgHGceoo2NO429spFyH39p%2FSMltBlQOBYLcRD8panRgTH8tuMc3uB65rtp613IkGwUMaMMXx40v0NBECEsRXcggYxYluO2k4waShMyOmWHOomMPKwGXg5ot93wLH6aXFwv0%2FD%2FUaqZDrJYJ9bMRcsF2KlPPy363XbrbcSCcT19otqU3D9yjWU1mDbzRy9yZRqmaCu%2FPtWHKKWnUKiN2f2UqL8Gbulex8BDpm4OKf4lXnOwAKYDpcAMph%2FC3LKVhvoRuryzPRJgXojb1CvfHaY0svUFoKq0N%2FuFl9hzR8RAVLsAPaD%2FpKuql4BUm%2BZtoc%2BWi0lQXA%3D%3D",
size: 169205,
checksum: 0xbe30d925
})
];
let data = proto.Networking.Requests.Messages.GetDownloadUrlsMessage.decode(req.request_message.toBuffer());
return (
new proto.Networking.Responses.GetDownloadUrlsResponse({
download_urls: download_urls
}).encode()
);
let key = data.asset_id[0];
let download_urls = [];
let obj = getDownloadUrlByAssetId(key);
return new Promise((resolve) => {
download(key).then((asset) => {
download_urls.push(
new proto.Data.DownloadUrlEntry({
url: asset[0].asset,
asset_id: key,
size: obj.size,
checksum: obj.checksum
})
);
let output = (
new proto.Networking.Responses.GetDownloadUrlsResponse({
download_urls: download_urls
}).encode()
);
resolve(output);
});
});
}

View File

@ -13,21 +13,39 @@ export default function GetInventoryData(obj) {
success: true,
inventory_delta: new proto.Inventory.InventoryDelta({
inventory_items: [
// player stats
new proto.Inventory.InventoryItem({
inventory_item_data: new proto.Inventory.InventoryItemData({
"modified_timestamp_ms": new Date().getTime() * 1e3,
"inventory_item_data": new proto.Inventory.InventoryItemData({
"candy": new proto.Inventory.Candy({
"family_id": 48,
"candy": 3
})
})
}),
new proto.Inventory.InventoryItem({
"modified_timestamp_ms": new Date().getTime() * 1e3,
"inventory_item_data": new proto.Inventory.InventoryItemData({
"candy": new proto.Inventory.Candy({
"family_id": 16,
"candy": 3
})
})
}),
new proto.Inventory.InventoryItem({
"modified_timestamp_ms": new Date().getTime() * 1e3,
"inventory_item_data": new proto.Inventory.InventoryItemData({
"player_stats": new proto.Data.Player.PlayerStats({
"level": 98,
"experience": 2000,
"prev_level_xp": 1000,
"next_level_xp": 6000,
"km_walked": 0.55459213256836,
"pokemons_encountered": 1,
"unique_pokedex_entries": 1,
"pokemons_captured": 1,
"level": 3,
"experience": 0,
"prev_level_xp": 0,
"next_level_xp": 0,
"km_walked": 1.017870306968689,
"pokemons_encountered": 6,
"unique_pokedex_entries": 3,
"pokemons_captured": 3,
"evolutions": 0,
"poke_stop_visits": 1,
"pokeballs_thrown": 1,
"poke_stop_visits": 25,
"pokeballs_thrown": 11,
"eggs_hatched": 0,
"big_magikarp_caught": 0,
"battle_attack_won": 0,
@ -42,66 +60,109 @@ export default function GetInventoryData(obj) {
})
})
}),
// player camera
new proto.Inventory.InventoryItem({
inventory_item_data: new proto.Inventory.InventoryItemData({
"player_camera": new proto.Data.Player.PlayerCamera({
is_default_camera: true
})
})
}),
// player currencies
new proto.Inventory.InventoryItem({
inventory_item_data: new proto.Inventory.InventoryItemData({
"player_currency": new proto.Data.Player.PlayerCurrency({
gems: 0
})
})
}),
// item
new proto.Inventory.InventoryItem({
inventory_item_data: new proto.Inventory.InventoryItemData({
"modified_timestamp_ms": new Date().getTime() * 1e3,
"inventory_item_data": new proto.Inventory.InventoryItemData({
"item": new proto.Inventory.Item.ItemData({
item_id: proto.Inventory.Item.ItemId.ITEM_MASTER_BALL,
count: 4,
unseen: false
"item_id": 901,
"count": 1,
"unseen": true
})
})
}),
// pokedex entry
new proto.Inventory.InventoryItem({
inventory_item_data: new proto.Inventory.InventoryItemData({
"modified_timestamp_ms": new Date().getTime() * 1e3,
"inventory_item_data": new proto.Inventory.InventoryItemData({
"pokemon_data": new proto.Data.PokemonData({
"id": 16,
"pokemon_id": proto.Enums.PokemonId.PIDGEY,
"cp": 10,
"stamina": 10,
"stamina_max": 10,
"move_1": 219,
"move_2": 121,
"deployed_fort_id": "",
"owner_name": "",
"is_egg": false,
"egg_km_walked_target": 0,
"egg_km_walked_start": 0,
"origin": 0,
"height_m": 0.29406625032424927,
"weight_kg": 2.070491313934326,
"individual_attack": 3,
"individual_defense": 5,
"individual_stamina": 10,
"cp_multiplier": 0.09399999678134918,
"pokeball": 1,
"captured_cell_id": 2277362944,
"battles_attacked": 0,
"battles_defended": 0,
"egg_incubator_id": "",
"creation_time_ms": 9029623884583797000,
"num_upgrades": 0,
"additional_cp_multiplier": 0,
"favorite": 0,
"nickname": "",
"from_fort": 0
})
})
}),
new proto.Inventory.InventoryItem({
"modified_timestamp_ms": new Date().getTime() * 1e3,
"inventory_item_data": new proto.Inventory.InventoryItemData({
"item": new proto.Inventory.Item.ItemData({
"item_id": 1,
"count": 145,
"unseen": false
})
})
}),
new proto.Inventory.InventoryItem({
"modified_timestamp_ms": new Date().getTime() * 1e3,
"inventory_item_data": {
"candy": new proto.Inventory.Candy({
"family_id": 7,
"candy": 3
})
}
}),
new proto.Inventory.InventoryItem({
"modified_timestamp_ms": new Date().getTime() * 1e3,
"inventory_item_data": new proto.Inventory.InventoryItemData({
"pokedex_entry": new proto.Data.PokedexEntry({
"pokemon_id": 6,
"times_encountered": 1,
"pokemon_id": 16,
"times_encountered": 3,
"times_captured": 1,
"evolution_stone_pieces": 0,
"evolution_stones": 0
})
})
}),
// pokemon
new proto.Inventory.InventoryItem({
inventory_item_data: new proto.Inventory.InventoryItemData({
"pokemon_data": new proto.Data.PokemonData({
pokemon_id: 19,
cp: 277,
stamina: 41,
stamina_max: 41,
move_1: 221,
move_2: 26,
height_m: 0.22802678267819977,
weight_kg: 1.3452539511871338,
individual_attack: 9,
individual_defense: 13,
individual_stamina: 14,
cp_multiplier: 0.5663545199394226,
additional_cp_multiplier: 0,
favorite: 0,
nickname: "BauerVomLande",
owner_name: "Administrator",
origin: 0,
is_egg: false
"modified_timestamp_ms": new Date().getTime() * 1e3,
"inventory_item_data": new proto.Inventory.InventoryItemData({
"egg_incubators": new proto.Inventory.EggIncubators({
"egg_incubator": [
new proto.Inventory.EggIncubator({
"id": "EggIncubatorProto-2589714687259241229",
"item_id": 901,
"incubator_type": 1,
"uses_remaining": 0,
"pokemon_id": 0,
"start_km_walked": 0,
"target_km_walked": 0
})
]
})
})
}),
new proto.Inventory.InventoryItem({
"modified_timestamp_ms": new Date().getTime() * 1e3,
"inventory_item_data": new proto.Inventory.InventoryItemData({
"item": new proto.Inventory.Item.ItemData({
"item_id": 401,
"count": 2,
"unseen": true
})
})
})

View File

@ -1,5 +1,9 @@
import proto from "../proto";
import {
decodeLong
} from "../utils";
/**
* @param {Request} request
* @return {Object}

View File

@ -1,5 +1,4 @@
import proto from "../proto";
/**
* @param {Object} obj
* @return {Object}
@ -16,10 +15,12 @@ function getPlayerDataPacket(obj) {
max_pokemon_storage: 250,
max_item_storage: 350,
daily_bonus: new proto.Data.Player.DailyBonus({
next_collected_timestamp_ms: 1470174535972,
next_defender_bonus_collect_timestamp_ms: 1470174535972
}),
contact_settings: new proto.Data.Player.ContactSettings(obj.contact_settings),
currencies: obj.currencies
currencies: obj.currencies,
remaining_codename_claims: 0
})
);

View File

@ -10,13 +10,8 @@ export default function LevelUpRewards(obj) {
return (
new proto.Networking.Responses.LevelUpRewardsResponse({
result: proto.Networking.Responses.LevelUpRewardsResponse.Result.SUCCESS,
items_awarded: [
new proto.Inventory.Item.ItemAward({
item_id: proto.Inventory.Item.ItemId.ITEM_GREAT_BALL,
item_count: 2
})
],
result: 2,
items_awarded: [],
items_unlocked: []
}).encode()
);

View File

@ -9,7 +9,7 @@ import { GetPlayer } from "./";
export default function MarkTutorialComplete(player) {
return (
new proto.Networking.Respones.MarkTutorialCompleteResponse({
new proto.Networking.Responses.MarkTutorialCompleteResponse({
success: true,
player_data: GetPlayer(player).player_data
}).encode()

View File

@ -14,14 +14,21 @@ export default function SfidaActionLog(obj) {
log_entries: [
new proto.Data.Logs.ActionLogEntry({
timestamp_ms: (new Date().getTime() - 1e4) * 1e3,
sfida: false,
catch_pokemon: null,
fort_search: new proto.Data.Logs.FortSearchLogEntry({
result: proto.Data.Logs.FortSearchLogEntry.Result.SUCCESS,
fort_id: "wurf",
fort_id: "",
items: [
new proto.Inventory.Item.ItemData({
item_id: proto.Inventory.Item.ItemId.ITEM_MASTER_BALL,
count: 2,
unseen: false
}),
new proto.Inventory.Item.ItemData({
item_id: proto.Inventory.Item.ItemId.ITEM_ULTRA_BALL,
count: 2,
unseen: false
})
]
})

View File

@ -18,6 +18,7 @@ export CheckAwardedBadges from "./Responses.CheckAwardedBadges";
export GetDownloadUrls from "./Responses.GetDownloadUrls";
export MarkTutorialComplete from "./Responses.MarkTutorialComplete";
export ClaimCodeName from "./Responses.ClaimCodeName";
export LevelUpRewards from "./Responses.LevelUpRewards";

View File

@ -20,7 +20,7 @@ class Player {
this.uid = -1;
this.email = null;
this._username = "Administrator";
this._username = "undefined";
this.position = {
latitude: 0,
@ -35,21 +35,25 @@ class Player {
this.exp = 0;
this.stardust = 1337;
this.pokecoins = 1338;
this.stardust = 0;
this.pokecoins = 0;
this.team = proto.Enums.TeamColor.BLUE;
this.avatar = {
skin: 0,
hair: 2,
shirt: 1,
pants: 2,
hair: 0,
shirt: 0,
pants: 0,
hat: 0,
shoes: 2,
eyes: 3,
gender: proto.Enums.Gender.MALE,
backpack: 1
shoes: 0,
eyes: 0,
gender: 0,
backpack: 0
};
this.tutorial_state = [32, 1, 3, 4, 7];
this.badges = null;
this.pokedex = null;
this.inventory = null;
@ -181,7 +185,7 @@ export function getPlayerIndex(player) {
*/
export function getPlayerByRequest(req) {
return (
this.getPlayerByIP(req.connection.remoteAddress)
this.getPlayerByIP(req.headers.host)
);
}
@ -203,16 +207,35 @@ export function getPlayerByIP(ip) {
}
/**
* @param {Request} connection
* @param {String} name
*/
export function addPlayer(connection) {
export function getPlayerByName(name) {
let ii = 0, length = this.clients.length;
for (; ii < length; ++ii) {
if (this.clients[ii].username === name) {
return (this.clients[ii]);
}
};
return (null);
}
/**
* @param {Request} req
*/
export function addPlayer(req) {
let connection = req.connection;
this.clients.push(new Player({
timeout: this.time,
connection: connection,
response: this.response,
remotePort: connection.remotePort,
remoteAddress: connection.remoteAddress
remoteAddress: req.headers.host
}));
}
@ -234,17 +257,45 @@ export function removePlayer(player) {
}
export function updatePlayers() {
//this.print("Updating players");
return void 0;
/**
* @param {String} name
*/
export function kickPlayer(name) {
if (
name === null ||
name === void 0 ||
typeof name === "string" &&
name.length <= 1
) {
this.print(`Invalid player name!`, 31);
return void 0;
}
let player = this.getPlayerByName(name);
if (player !== null) {
this.removePlayer(player);
this.print(`Kicked ${name} from the server!`);
}
else this.print(`Failed to kick ${name} from the server!`, 31);
}
export function savePlayers() {
export function updatePlayers() {
//this.print("Updating players");
}
export function saveAllPlayers() {
for (let client of this.clients) {
this.savePlayer(client);
};
this.print("Saved players into database");
return void 0;
}
export function removeAllPlayers() {
for (let client of this.clients) {
this.removePlayer(client);
};
}
/**
@ -281,6 +332,9 @@ export function forwardPlayer() {
return new Promise((resolve) => {
this.getUserByEmail(player.email).then((doc) => {
if (player.email.length) {
this.print(`${player.email.replace("@gmail.com", "")} authenticated!`, 36);
}
if (doc === void 0) {
this.registerPlayer(doc).then((res) => {
resolve(res);
@ -306,6 +360,7 @@ export function registerPlayer(doc) {
return new Promise((resolve) => {
this.createUser(player).then(() => {
this.print(`${this.player.email.replace("@gmail.com", "")} registered!`, 36);
player.tutorial_state = [];
this.loginPlayer(doc).then((res) => {
resolve(res);
});

View File

@ -4,24 +4,60 @@ export function processCommand(cmd, data) {
switch (cmd) {
// How many active connections there are
case "/clients":
let length = this.clients.length;
this.print(`${length} connected player${length === 1 ? "": "s"}!`, 33);
var length = this.clients.length;
this.print(`${length}:${CFG.SERVER_MAX_CONNECTIONS} connected players!`, 33);
break;
// Kill the server
// Exit the server
case "/exit":
this.print("Killed the server!", 31);
process.exit();
this.socket.close(() => {
this.print("Closed http server!", 33);
this.db.instance.close(() => {
this.print("Closed database connection!", 33);
this.print("Killed the server!", 31);
setTimeout(() => process.exit(1), 2e3);
});
});
break;
case "/kick":
this.kickPlayer(data[1]);
break;
case "/killall":
var length = this.clients.length;
this.removeAllPlayers();
var result = length - this.clients.length;
this.print(`Removed ${result} player${result === 1 ? "": "s"}!`);
break;
case "/clear":
process.stdout.write("\x1Bc");
this.greet();
break;
case "/help":
this.printHelp();
break;
case "/save":
this.savePlayers();
this.saveAllPlayers();
var length = this.clients.length;
this.print(`Saved ${length} player${length === 1 ? "": "s"} into database!`);
break;
default:
this.print(`${cmd} is not a valid command!`, 31);
break;
};
};
export function printHelp() {
console.log("\x1b[36;1m===================================== HELP =====================================\x1b[0m");
this.print("clients : how many players are connected");
this.print("exit : exit the server");
this.print("kick [PlayerUsername] : kick player by username");
this.print("kickall : kick all players");
this.print("clear : clear the server console");
this.print("save : save all palyers into database");
console.log("\n\x1b[36;1m================================================================================\x1b[0m");
};
export function stdinInput(data) {
data = data.toString().substring(0, data.length - 2);
if (data.length < 1) return void 0;

View File

@ -11,7 +11,10 @@ import {
GetInventory
} from "./packets";
import { decodeRequestEnvelope } from "./utils";
import {
decodeLong,
decodeRequestEnvelope
} from "./utils";
import jwtDecode from "jwt-decode";
@ -81,16 +84,20 @@ export function onRequest(req, res) {
// Validate email verification
if (player.authenticated) {
if (!player.email_verified) {
this.print(`${player.email.replace("@gmail.com", "")}'s email isnt verified, kicking..`, 31);
this.removePlayer(player);
return void 0;
}
}
let request = proto.Networking.Envelopes.RequestEnvelope.decode(req.body);
console.log("#####");
request.requests.map((request) => {
console.log("Got request:", this.getRequestType(request));
}).join(",");
if (CFG.SERVER_LOG_REQUESTS) {
console.log("#####");
request.requests.map((request) => {
console.log("Got request:", this.getRequestType(request));
}).join(",");
}
if (!player.authenticated) {
this.send(this.authenticatePlayer());
@ -98,7 +105,7 @@ export function onRequest(req, res) {
}
this.processRequests(request.requests).then((answer) => {
let msg = this.envelopResponse(1, request.request_id, answer, request.hasOwnProperty("auth_ticket"), request.unknown6);
let msg = this.envelopResponse(1, request.request_id, answer, request.hasOwnProperty("auth_ticket"));
this.send(msg);
});
@ -109,10 +116,9 @@ export function onRequest(req, res) {
* @param {Long} id
* @param {Array} response
* @param {Boolean} auth
* @param {Buffer} unknown6
* @return {Buffer}
*/
export function envelopResponse(status, id, response, auth, unknown6) {
export function envelopResponse(status, id, response, auth) {
let answer = ResponseEnvelope({
id: id,
@ -133,19 +139,56 @@ export function envelopResponse(status, id, response, auth, unknown6) {
export function processRequests(requests) {
return new Promise((resolve) => {
let ii = 0;
let index = 0;
let length = requests.length;
let body = [];
for (; ii < length; ++ii) {
this.processResponse(requests[ii]).then((request) => {
let loop = (index) => {
this.processResponse(requests[index]).then((request) => {
body.push(request);
if (ii + 1 >= length) resolve(body);
if (++index >= length) resolve(body);
else return loop(index);
});
};
loop(0);
});
}
/**
* @param {Request} req
* @param {Response} res
*/
export function routeRequest(req, res) {
let url = String(req.url);
let route = url.substring(url.lastIndexOf("/") + 1);
let host = req.headers.host;
switch (route) {
case "rpc":
this.onRequest(req, res);
break;
case "oauth":
let out = null;
let buffer = req.body.toString();
let signature = "321187995bc7cdc2b5fc91b11a96e2baa8602c62";
if (/Email.*com.nianticlabs.pokemongo/.test(buffer)) {
out = new Buffer(buffer.replace(/&client_sig=[^&]*&/, "&client_sig=" + signature + "&"));
}
console.log("OAUTH", out);
if (out instanceof Buffer) this.send(out);
break;
default:
console.log(`Unknown request url: https://${req.headers.host}${req.url}`);
break;
};
}
/**
* @param {Request} req
* @return {Boolean}

1210
src/res/index.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,8 @@ import {
NicknamePokemon,
UpgradePokemon,
EvolvePokemon,
SetFavoritePokemon
SetFavoritePokemon,
ClaimCodeName
} from "./packets";
/**
@ -40,87 +41,102 @@ export function processResponse(request) {
return new Promise((resolve) => {
switch (request.request_type) {
case REQUEST.GET_PLAYER:
this.forwardPlayer().then((res) => resolve(res));
return void 0;
break;
case REQUEST.GET_HATCHED_EGGS:
buffer = GetHatchedEggs();
break;
case REQUEST.GET_INVENTORY:
buffer = GetInventory();
break;
case REQUEST.CHECK_AWARDED_BADGES:
buffer = CheckAwardedBadges();
break;
case REQUEST.DOWNLOAD_SETTINGS:
buffer = DownloadSettings();
break;
case REQUEST.DOWNLOAD_ITEM_TEMPLATES:
buffer = ItemTemplates();
break;
case REQUEST.DOWNLOAD_REMOTE_CONFIG_VERSION:
buffer = DownloadRemoteConfigVersion();
try {
switch (request.request_type) {
case REQUEST.GET_PLAYER:
this.forwardPlayer().then((res) => resolve(res));
return void 0;
break;
case REQUEST.GET_ASSET_DIGEST:
buffer = GetAssetDigest();
break;
case REQUEST.GET_PLAYER_PROFILE:
buffer = GetPlayerProfile();
break;
case REQUEST.GET_MAP_OBJECTS:
this.player.updatePosition(request);
buffer = GetMapObjects(request);
this.savePlayer(player);
break;
case REQUEST.GET_DOWNLOAD_URLS:
buffer = GetDownloadUrls();
break;
case REQUEST.SET_AVATAR:
player.updateAvatar(request);
buffer = SetAvatar(player);
this.savePlayer(player);
break;
case REQUEST.SFIDA_ACTION_LOG:
buffer = SfidaActionLog();
break;
case REQUEST.MARK_TUTORIAL_COMPLETE:
buffer = MarkTutorialComplete(player);
this.savePlayer(player);
break;
case REQUEST.LEVEL_UP_REWARDS:
buffer = LevelUpRewards();
break;
case REQUEST.FORT_DETAILS:
buffer = FortDetails(request);
break;
case REQUEST.FORT_SEARCH:
buffer = FortSearch();
break;
case REQUEST.SET_CONTACT_SETTINGS:
player.updateContactSettings(request);
buffer = SetContactSettings(player);
this.savePlayer(player);
break;
case REQUEST.ENCOUNTER:
buffer = Encounter(request);
break;
case REQUEST.NICKNAME_POKEMON:
buffer = NicknamePokemon(request);
break;
case REQUEST.UPGRADE_POKEMON:
buffer = UpgradePokemon(request);
break;
case REQUEST.EVOLVE_POKEMON:
buffer = EvolvePokemon(request);
break;
case REQUEST.SET_FAVORITE_POKEMON:
buffer = SetFavoritePokemon(request);
break;
default:
this.print(`Unknown request: ${this.getRequestType(request)}`, 31);
break;
case REQUEST.GET_HATCHED_EGGS:
buffer = GetHatchedEggs();
break;
case REQUEST.GET_INVENTORY:
buffer = GetInventory();
break;
case REQUEST.CHECK_AWARDED_BADGES:
buffer = CheckAwardedBadges();
break;
case REQUEST.DOWNLOAD_SETTINGS:
buffer = DownloadSettings();
break;
case REQUEST.DOWNLOAD_ITEM_TEMPLATES:
buffer = ItemTemplates();
break;
case REQUEST.DOWNLOAD_REMOTE_CONFIG_VERSION:
buffer = DownloadRemoteConfigVersion();
break;
case REQUEST.GET_ASSET_DIGEST:
buffer = GetAssetDigest(request);
break;
case REQUEST.GET_PLAYER_PROFILE:
buffer = GetPlayerProfile();
break;
case REQUEST.GET_MAP_OBJECTS:
this.player.updatePosition(request);
buffer = GetMapObjects(request);
this.savePlayer(player);
break;
case REQUEST.GET_DOWNLOAD_URLS:
GetDownloadUrls(request, this.generateDownloadUrlByAssetId).then((res) => {
resolve(res);
});
return void 0;
break;
case REQUEST.SET_AVATAR:
player.updateAvatar(request);
buffer = SetAvatar(player);
this.savePlayer(player);
break;
case REQUEST.SFIDA_ACTION_LOG:
buffer = SfidaActionLog();
break;
case REQUEST.MARK_TUTORIAL_COMPLETE:
buffer = MarkTutorialComplete(player);
this.savePlayer(player);
break;
case REQUEST.CLAIM_CODENAME:
buffer = ClaimCodeName(request, player);
this.savePlayer(player);
break;
case REQUEST.LEVEL_UP_REWARDS:
buffer = LevelUpRewards();
break;
case REQUEST.FORT_DETAILS:
buffer = FortDetails(request);
break;
case REQUEST.FORT_SEARCH:
buffer = FortSearch();
break;
case REQUEST.SET_CONTACT_SETTINGS:
player.updateContactSettings(request);
buffer = SetContactSettings(player);
this.savePlayer(player);
break;
case REQUEST.ENCOUNTER:
buffer = Encounter(request);
break;
case REQUEST.NICKNAME_POKEMON:
buffer = NicknamePokemon(request);
break;
case REQUEST.UPGRADE_POKEMON:
buffer = UpgradePokemon(request);
break;
case REQUEST.EVOLVE_POKEMON:
buffer = EvolvePokemon(request);
break;
case REQUEST.SET_FAVORITE_POKEMON:
buffer = SetFavoritePokemon(request);
break;
case REQUEST.CATCH_POKEMON:
let data = proto.Networking.Requests.Messages.CatchPokemonMessage.decode(request.request_message.toBuffer());
console.log(data);
break;
default:
this.print(`Unknown request: ${this.getRequestType(request)}`, 31);
break;
};
} catch (e) {
console.log(e);
};
resolve(buffer);

View File

@ -4,23 +4,26 @@ import * as CFG from "../cfg";
export function setup() {
this.print("Booting server...", 33);
this.print("Booting server..", 33);
this.setupMongo(() => {
this.createAssetDownloadSession().then(() => {
this.print("Created asset download session");
this.setupMongo().then(() => {
this.print("Database connection established");
this.print("Database connection established");
if (CFG.SERVER_PORT < 1) {
this.print("Invalid port!", 31);
return void 0;
}
if (CFG.SERVER_PORT < 1) {
this.print("Invalid port!", 31);
return void 0;
}
this.socket = this.createHTTPServer();
this.socket = this.createHTTPServer();
setTimeout(this::this.cycle, 1);
setTimeout(this::this.cycle, 1);
this.print(`Server running at ${CFG.SERVER_HOST_IP}:${CFG.SERVER_PORT}`);
this.print(`Server running at ${CFG.SERVER_HOST_IP}:${CFG.SERVER_PORT}`);
});
});
}

View File

@ -1,5 +1,8 @@
import Long from "long";
import proto from "./proto";
import { DownloadUrls } from "./res";
import * as CFG from "../cfg";
/**
* @param {Object} cls
@ -44,15 +47,74 @@ export function getHashCodeFrom(str) {
};
/**
* @param {Number} hi
* @param {Number} lo
* @param {Boolean} unsigned
* @param {Long} long
* @return {Number}
*/
export function decodeLong(hi, lo, unsigned) {
export function decodeLong(long) {
let value = Long.fromBits(hi, lo, !!unsigned);
let value = Long.fromBits(long.high, long.low, !!long.unsigned);
return (parseInt(value.toString()));
}
/**
* @param {String} pm
* @return {Object}
*/
export function getDownloadUrl(pm) {
let ii = 0;
let length = DownloadUrls.length;
let node = null;
for (; ii < length; ++ii) {
node = DownloadUrls[ii];
if (node.bundle_name === pm) {
return (node);
}
};
return (null);
}
/**
* @param {String} pm
* @return {Object}
*/
export function getDownloadUrlByAssetId(id) {
let ii = 0;
let length = DownloadUrls.length;
let node = null;
let index = 0;
for (; ii < length; ++ii) {
node = DownloadUrls[ii];
if (node.asset_id.substring(index) === id) {
return (node);
}
};
return (null);
}
export function getGenerationIdByAssetId(asset_id) {
let index = asset_id.lastIndexOf("/") + 1;
return (
parseInt(asset_id.substring(index))
);
}
export function randomRequestId() {
return (
1e18 - Math.floor(Math.random() * 1e18)
);
}