Mega update, 0.4.7

- Support pkmn models for ios devices
- Dump android and ios assets in seperate phase as well as into seperate
folders
- Add local ip to fix GetDownloadUrls bug
- SetFavoritePokemon and ReleasePokemon working
- Update players pkmn party into database
- Extract players device signature on first auth to figure out, what
models and asset_digest we need to response with
- Heavily extended setup dump process
- Added method to capitalize strings
- Added db query to delete rows
This commit is contained in:
Felix 2016-08-24 19:23:17 +02:00
parent 63f65a95df
commit 3dc5e303ad
14 changed files with 471 additions and 110 deletions

View File

@ -15,6 +15,20 @@ export function getQueryByColumnFromTable(column, value, table) {
});
}
/**
* @param {String} column
* @param {String} value
* @param {String} table
*/
export function deleteQueryByColumnFromTable(column, value, table) {
return new Promise((resolve) => {
this.db.instance.query(`DELETE FROM ${table} WHERE ${column}=?`, [value], (e, rows) => {
if (e) console.log(e);
else resolve(void 0);
});
});
}
/**
* @param {String} column
* @param {String} value

View File

@ -76,10 +76,23 @@ export function createOwnedPokemon(obj) {
/**
* @param {Object} obj
*/
export function updateUser(obj) {
export function deleteOwnedPokemon(id) {
return new Promise((resolve) => {
this.deleteQueryByColumnFromTable("id", id, CFG.MYSQL_OWNED_PKMN_TABLE).then(() => {
resolve();
});
});
}
/**
* @param {Player} player
*/
export function updateUser(player) {
let query = this.getUserQuery("UPDATE", "WHERE email=? LIMIT 1");
let data = this.getUserQueryData(obj);
let data = this.getUserQueryData(player);
return new Promise((resolve) => {
this.db.instance.query(query, data, resolve);
@ -88,15 +101,40 @@ export function updateUser(obj) {
}
/**
* @param {Object} obj
* @param {Player} player
*/
export function updateUserItems(obj) {
export function updateUserItems(player) {
let query = this.getUserItemQuery("UPDATE", "WHERE email=? LIMIT 1");
let data = this.getUserItemQueryData(obj);
let data = this.getUserItemQueryData(player);
return new Promise((resolve) => {
this.db.instance.query(query, data, resolve);
});
}
/**
* @param {Player} player
*/
export function updateUserParty(player) {
let pkmn = null;
let data = null;
let query = this.getOwnedPkmnQuery("UPDATE", "WHERE id=? AND owner_id=? LIMIT 1");
return new Promise((resolve) => {
let ii = 0;
let index = 0;
let length = player.party.length;
for (; ii < length; ++ii) {
pkmn = player.party[ii];
data = this.getOwnedPkmnQueryData(pkmn);
data.push(pkmn.id, player.owner_id);
this.db.instance.query(query, data, () => {
if (++index >= length) resolve();
});
};
});
}

View File

@ -3,10 +3,10 @@ import os from "os";
import fse from "fs-extra";
import http from "http";
import proto from "./proto";
import decode from "pogo-decode";
import {
inherit
inherit,
_toCC
} from "./utils";
import CFG from "../cfg";
@ -45,7 +45,7 @@ class GameServer {
collections: {}
};
this.asset = null;
this.assets = {};
this.master = null;
this.socket = null;
this.cycleInstance = null;
@ -173,16 +173,66 @@ class GameServer {
}
}
/**
* @param {Request} req
* @param {Array} res
* @return {Object}
*/
decode(req, res) {
// clone
req = JSON.parse(JSON.stringify(req));
res = JSON.parse(JSON.stringify(res));
// dont decode unknown6, since it bloats the file size
delete req.unknown6;
// decode requests
for (let request of req.requests) {
let key = _toCC(request.request_type);
let msg = request.request_message;
if (msg) {
let proto = `POGOProtos.Networking.Requests.Messages.${key}Message`;
request.request_message = this.parseProtobuf(new Buffer(msg.data), proto);
}
};
// decode responses
let index = 0;
for (let resp of res) {
let key = _toCC(req.requests[index].request_type);
let msg = new Buffer(resp);
let proto = `POGOProtos.Networking.Responses.${key}Response`;
res[index] = this.parseProtobuf(msg, proto);
index++;
};
// clone again to build response out of it
let req2 = JSON.parse(JSON.stringify(req));
// build res base out of req
delete req2.requests;
req2.returns = res;
req2.status_code = 1;
return ({
req: req,
res: res
});
}
dumpTraffic(req, res) {
// decode opts
let opts = {
removeNulls: true,
encodeBuffers: true
let decoded = this.decode(req, res);
let out = {
Request: decoded.req,
Response: decoded.res
};
try {
let decoded = JSON.stringify(decode(req, res, opts), null, 2);
let decoded = JSON.stringify(out, null, 2);
fse.outputFileSync(CFG.DEBUG_DUMP_PATH + Date.now(), decoded);
} catch (e) {
this.print("Dump traffic: " + e, 31);

105
src/models/GetInventory.js Normal file
View File

@ -0,0 +1,105 @@
import POGOProtos from "pokemongo-protobuf";
export function GetInventory() {
let stats = this.GetInventoryPlayer();
let items = this.GetInventoryItems();
let party = this.GetInventoryParty();
let buffer = ({
success: true,
inventory_delta: {
new_timestamp_ms: new Date().getTime(),
inventory_items: stats.concat(items).concat(party)
}
});
return (POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.GetInventoryResponse"));
}
export function GetInventoryPlayer() {
let player = this.player;
return ({
modified_timestamp_ms: new Date().getTime(),
inventory_item_data: {
player_stats: {
level: player.level,
experience: player.exp,
prev_level_xp: "21000",
next_level_xp: "36000",
km_walked: 3.921541213989258,
pokemons_encountered: 75,
unique_pokedex_entries: 25,
pokemons_captured: 71,
poke_stop_visits: 123,
pokeballs_thrown: 74,
eggs_hatched: 1,
big_magikarp_caught: 1,
pokemon_deployed: 1
}
}
});
}
export function GetInventoryItems() {
let player = this.player;
let items = [];
let match = "item_";
for (let key in player) {
if (key.substring(0, 5) === match) {
items.push({
modified_timestamp_ms: new Date().getTime(),
inventory_item_data: {
item: {
item_id: key.toUpperCase(),
count: parseFloat(player[key])
}
}
});
}
};
return (items);
}
export function GetInventoryParty() {
let player = this.player;
let pkmns = [];
for (let pkmn of player.party) {
pkmns.push({
inventory_item_data: {
pokemon_data: {
id: pkmn.id,
pokemon_id: pkmn.pokemon_id,
cp: pkmn.cp,
stamina: pkmn.stamina,
stamina_max: pkmn.stamina_max,
move_1: pkmn.move_1,
move_2: pkmn.move_2,
height_m: pkmn.height_m,
weight_kg: pkmn.weight_kg,
individual_attack: pkmn.individual_attack,
individual_defense: pkmn.individual_defense,
individual_stamina: pkmn.individual_stamina,
cp_multiplier: pkmn.cp_multiplier,
pokeball: pkmn.pokeball,
captured_cell_id: pkmn.captured_cell_id,
creation_time_ms: pkmn.creation_time_ms
}
}
});
};
return (pkmns);
}

View File

@ -0,0 +1,18 @@
import POGOProtos from "pokemongo-protobuf";
/**
* @param {Request} request
*/
export function GetMapObject(request) {
let player = this.player;
let latitude = player.latitude;
let longitude = player.longitude;
return new Promise(() => {
// db call
//
});
}

14
src/models/index.js Normal file
View File

@ -0,0 +1,14 @@
/**
* @class GameServer
*/
class PacketModel {
/**
* @param {Player} player
* @constructor
*/
constructor(player) {
this.player = player;
}
}

View File

@ -4,9 +4,9 @@ import proto from "../proto";
import CFG from "../../cfg";
/**
* @param {Request} req
* @param {Player} player
* @return {Object}
*/
export default function GetAssetDigest(req) {
return (fs.readFileSync("data/asset_digest"));
export default function GetAssetDigest(player) {
return (player.asset_digest.buffer);
}

View File

@ -92,7 +92,8 @@ export default function GetInventoryData(player) {
"cp_multiplier": pkmn.cp_multiplier,
"pokeball": pkmn.pokeball,
"captured_cell_id": pkmn.captured_cell_id,
"creation_time_ms": pkmn.creation_time_ms
"creation_time_ms": pkmn.creation_time_ms,
"favorite": pkmn.favorite
}
}
});

View File

@ -7,11 +7,13 @@ import POGOProtos from "pokemongo-protobuf";
* @param {Request} req
* @return {Object}
*/
export default function SetFavoritePokemon(req) {
export default function SetFavoritePokemon(player, req) {
buffer = ({
result: proto.Networking.Responses.SetFavoritePokemonResponse.Result.ERROR_POKEMON_NOT_FOUND
});
let buffer = {
"result": "SUCCESS",
};
player.setFavoritePkmn(req.pokemon_id << 0, req.is_favorite);
return (
POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.SetFavoritePokemonResponse")

View File

@ -27,7 +27,7 @@ class Player {
this.uid = -1;
this.owner_id = -1;
this.email = null;
this._email = null;
this.username = null;
this.latitude = 0;
@ -91,11 +91,17 @@ class Player {
this.pokedex = null;
this.inventory = null;
this.asset_digest = null;
this.hasSignature = false;
this.isIOS = false;
this.isAndroid = false;
this.isPTCAccount = false;
this.isGoogleAccount = false;
this.request = obj.request;
// gets updated after each chunk end event
this.response = null;
this.connection = obj.connection;
@ -113,6 +119,13 @@ class Player {
}
get email() {
return (this._email);
}
set email(value) {
this._email = value.replace("@gmail.com", "");
}
/**
* @param {String} email
*/
@ -191,6 +204,29 @@ class Player {
}
getPkmnIndexFromPartyById(id) {
for (let ii = 0; ii < this.party.length; ++ii) {
if (this.party[ii].id === id) return (ii);
};
return (-1);
}
getPkmnFromPartyById(id) {
let index = this.getPkmnIndexFromPartyById(id);
return (this.party[index]);
}
deletePkmnFromParty(id) {
let index = this.getPkmnIndexFromPartyById(id);
let pkmn = this.party[index];
if (pkmn) this.party.splice(index, 1);
}
setFavoritePkmn(id, favorite) {
let pkmn = this.getPkmnFromPartyById(id);
if (pkmn) pkmn.favorite = favorite;
}
}
/**
@ -325,7 +361,7 @@ export function spawnPkmnAtPlayer(name, pkmn, amount) {
if (player !== null) {
let index = 0;
while (++index < amount) {
while (++index <= amount) {
spawn = {
pokemon_id: pkmn.toUpperCase(),
cp: Math.random() * 1e3 << 0,
@ -384,7 +420,12 @@ export function savePlayer(player) {
return new Promise((resolve) => {
if (player.authenticated) {
this.updateUser(player).then(() => {
this.updateUserItems(player).then(resolve);
this.updateUserItems(player).then(() => {
this.updateUserParty(player).then(() => {
// refresh player data by database
this.loginPlayer(player).then(resolve);
});
});
});
}
});
@ -399,7 +440,7 @@ export function forwardPlayer(player) {
this.getUserByEmail(player.email).then((doc) => {
if (player.email.length) {
let provider = player.isGoogleAccount ? "Google" : "PTC";
this.print(`${player.email.replace("@gmail.com", "")} authenticated via ${provider}!`, 36);
this.print(`${player.email} authenticated via ${provider}!`, 36);
}
if (doc) {
this.loginPlayer(player).then((res) => {
@ -442,7 +483,7 @@ export function registerPlayer(player) {
return new Promise((resolve) => {
this.createUser(player).then(() => {
this.print(`${player.email.replace("@gmail.com", "")} registered!`, 36);
this.print(`${player.email} registered!`, 36);
player.tutorial_state = [];
this.loginPlayer(player).then((res) => {
resolve(res);
@ -481,7 +522,7 @@ export function authenticatePlayer(player) {
player.email = decoded.email;
player.email_verified = decoded.email_verified;
player.isGoogleAccount = true;
this.print(`${player.email.replace("@gmail.com", "")} connected!`, 36);
this.print(`${player.email} connected!`, 36);
}
else {
this.print("Invalid authentication token! Kicking..", 31);

View File

@ -34,7 +34,8 @@ export function routeRequest(req, res) {
if (!player.authenticated || !player.email_verified) return void 0;
let name = route[2];
if (name && name.length > 1) {
fs.readFile("data/" + name, (error, data) => {
let folder = player.isAndroid ? "android/" : "ios/";
fs.readFile("data/" + folder + name, (error, data) => {
if (error) {
this.print(`Error file resolving model ${name}:` + error, 31);
return void 0;
@ -44,6 +45,14 @@ export function routeRequest(req, res) {
});
}
break;
case "om":
if (
route[2] === "glm" &&
route[3] === "mmap"
) {
this.print(`Received gmaps request!`, 33);
}
break;
default:
console.log(`Unknown request url: https://${req.headers.host}${req.url}`);
break;
@ -79,7 +88,7 @@ export function onRequest(req) {
// Validate email verification
if (player.authenticated) {
if (!player.email_verified) {
this.print(`${player.email.replace("@gmail.com", "")}'s email isnt verified, kicking..`, 31);
this.print(`${player.email}'s email isnt verified, kicking..`, 31);
this.removePlayer(player);
return void 0;
}
@ -115,10 +124,10 @@ export function onRequest(req) {
}
this.processRequests(player, request.requests).then((returns) => {
let msg = this.envelopResponse(1, returns, request, !!request.auth_ticket, false);
if (CFG.DEBUG_DUMP_TRAFFIC) {
this.dumpTraffic(req.body, msg);
this.dumpTraffic(request, returns);
}
let msg = this.envelopResponse(1, returns, request, player, !!request.auth_ticket, false);
player.sendResponse(msg);
});
@ -128,11 +137,12 @@ export function onRequest(req) {
* @param {Number} status
* @param {Array} returns
* @param {Request} req
* @param {Player} player
* @param {Boolean} auth
* @param {Boolean} shop
* @return {Buffer}
*/
export function envelopResponse(status, returns, req, auth, shop) {
export function envelopResponse(status, returns, req, player, auth, shop) {
let buffer = req;
@ -140,6 +150,18 @@ export function envelopResponse(status, returns, req, auth, shop) {
buffer.returns = returns;
// get players device platform one time
if (player.hasSignature === false && buffer.unknown6 && buffer.unknown6.unknown2) {
let sig = this.parseSignature(buffer);
if (sig.device_info !== void 0) {
player.isIOS = sig.device_info.device_brand === "Apple";
player.isAndroid = !player.isIOS;
player.hasSignature = true;
player.asset_digest = this.assets[player.isAndroid ? "android" : "ios"];
this.print(`${player.email} is playing with an ${player.isIOS ? "Apple" : "Android"} device!`, 36);
}
}
if (auth) buffer.auth_ticket = AuthTicket();
if (shop) buffer.unknown6 = [ShopData()];
@ -154,7 +176,7 @@ export function envelopResponse(status, returns, req, auth, shop) {
}];
}
buffer.status_code = 1;
buffer.status_code = status;
return (
POGOProtos.serialize(buffer, "POGOProtos.Networking.Envelopes.ResponseEnvelope")

View File

@ -93,7 +93,7 @@ export function processResponse(player, req) {
buffer = DownloadRemoteConfigVersion(msg);
break;
case "GET_ASSET_DIGEST":
buffer = GetAssetDigest(msg);
buffer = GetAssetDigest(player);
break;
case "GET_PLAYER_PROFILE":
buffer = GetPlayerProfile();
@ -107,7 +107,7 @@ export function processResponse(player, req) {
return void 0;
break;
case "GET_DOWNLOAD_URLS":
GetDownloadUrls(this.asset, this.getLocalIPv4(), msg).then((res) => {
GetDownloadUrls(player.asset_digest.decode, CFG.LOCAL_IP || this.getLocalIPv4(), msg).then((res) => {
resolve(res);
});
return void 0;
@ -195,7 +195,20 @@ export function processResponse(player, req) {
buffer = EvolvePokemon(msg);
break;
case "SET_FAVORITE_POKEMON":
buffer = SetFavoritePokemon(msg);
buffer = SetFavoritePokemon(player, msg);
break;
case "RELEASE_POKEMON":
buffer = {
result: "SUCCESS",
candy_awarded: 5
};
let id = msg.pokemon_id << 0;
this.deleteOwnedPokemon(id).then(() => {
player.deletePkmnFromParty(id);
buffer = POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.ReleasePokemonResponse");
resolve(buffer);
});
return void 0;
break;
default:
this.print(`Unknown request: ${req.request_type}`, 31);

View File

@ -6,14 +6,17 @@ import POGOProtos from "pokemongo-protobuf";
import CFG from "../cfg";
import { idToPkmnBundleName } from "./utils";
import {
capitalize,
idToPkmnBundleName
} from "./utils";
export function setup() {
let isFirstRun = !this.directoryExists(CFG.DUMP_ASSET_PATH);
if (isFirstRun) {
this.print("Preparing to dump required assets..", 36);
this.print("Required assets are missing! Preparing dump session..", 31);
setTimeout(() => {
this.onFirstRun(() => {
this.setup();
@ -27,7 +30,6 @@ export function setup() {
this.print(`Downloaded assets are valid! Proceeding..`);
this.asset = this.parseAssetDigest();
this.master = POGOProtos.serialize(this.parseGameMaster(), "POGOProtos.Networking.Responses.DownloadItemTemplatesResponse");
this.setupDatabaseConnection().then(() => {
@ -54,43 +56,65 @@ export function setup() {
*/
export function validateAssets() {
let index = 0;
let max = CFG.MAX_POKEMON_NATIONAL_ID;
return new Promise((resolve, reject) => {
if (!this.fileExists(CFG.DUMP_ASSET_PATH + "asset_digest")) {
return reject("File asset_digest");
}
// validate game master
if (!this.fileExists(CFG.DUMP_ASSET_PATH + "game_master")) {
return reject("File game_master");
}
// validate models
while (++index <= max) {
let id = idToPkmnBundleName(index);
if (!this.fileExists(CFG.DUMP_ASSET_PATH + id)) {
return reject("Model " + id);
}
};
resolve();
this.validateModels().then(() => {
resolve();
}).catch((e) => {
reject(e);
});
});
}
export function parseAssetDigest() {
let asset = null;
try {
let data = fs.readFileSync(CFG.DUMP_ASSET_PATH + "asset_digest");
asset = this.parseProtobuf(data, "POGOProtos.Networking.Responses.GetAssetDigestResponse");
} catch (e) {
this.print(e, 31);
}
return (asset);
export function validateModels() {
let max = CFG.MAX_POKEMON_NATIONAL_ID;
let limit = pogo.platforms.length;
return new Promise((resolve, reject) => {
const validate = (index) => {
let platform = pogo.platforms[index];
let name = platform.name;
let path = CFG.DUMP_ASSET_PATH + name + "/";
// ups, validate asset_digest's too
if (!this.fileExists(path + "asset_digest")) {
return reject(`${path}asset_digest`);
}
else {
let buffer = fs.readFileSync(path + "asset_digest");
this.assets[name] = {
buffer: buffer,
decode: this.parseProtobuf(buffer, "POGOProtos.Networking.Responses.GetAssetDigestResponse")
}
}
// validate models inside folder
let ii = 0;
while (++ii <= max) {
let id = idToPkmnBundleName(ii);
if (!this.fileExists(path + id)) {
return reject("Model " + id);
}
};
if (++index >= limit) {
resolve();
return void 0;
}
validate(index);
};
validate(0);
});
}
export function parseGameMaster() {
@ -105,66 +129,75 @@ export function parseGameMaster() {
}
export function onFirstRun(resolve) {
// make sure to login first!
pogo.login({
provider: CFG.DOWNLOAD_PROVIDER, // google or ptc
username: CFG.DOWNLOAD_USERNAME,
password: CFG.DOWNLOAD_PASSWORD
}).then((res) => {
// create data dir, if login successed
fse.ensureDirSync(CFG.DUMP_ASSET_PATH);
// write game master
fs.writeFileSync(CFG.DUMP_ASSET_PATH + "game_master", res.master.toBuffer());
// get and write asset digests
this.dumpAssetDigests(res.client).then(() => {
// dump pkmn models
this.dumpPkmnModels(() => {
resolve();
});
}).then(() => {
this.downloadAssetDigests().then(() => {
this.downloadAssets().then(resolve);
});
}).catch((e) => {
this.print(e, 31);
});
}
export function dumpAssetDigests(client) {
this.print(`Dumping asset digests..`, 35);
let platforms = [
{
name: "android",
platform: 2,
manufacturer: "LGE",
model: "Nexus 5",
locale: "",
version: 3300
}
];
export function downloadAssetDigests(assets) {
return new Promise((resolve, reject) => {
let ii = 0;
// create data folder for each support platform
// and download each asset digest and related models
let index = 0;
for (; ii < platforms.length; ++ii) {
let key = platforms[ii];
client.getAssetDigest(
key.platform,
key.manufacturer,
key.model,
key.locale,
key.version
).then((asset) => {
this.print(`Dumping ${key.name} asset digest..`, 35);
fs.writeFileSync(CFG.DUMP_ASSET_PATH + "asset_digest", asset.toBuffer());
if (++index >= platforms.length) {
resolve();
}
let length = pogo.platforms.length;
for (let platform of pogo.platforms) {
fse.ensureDirSync(CFG.DUMP_ASSET_PATH + platform.name);
pogo.getAssetDigest(platform).then((asset) => {
fs.writeFileSync(CFG.DUMP_ASSET_PATH + platform.name + "/asset_digest", asset.toBuffer());
if (++index >= length) resolve();
});
};
});
}
export function downloadAssets() {
return new Promise((resolve, reject) => {
pogo.getGameMaster().then((master) => {
fs.writeFileSync(CFG.DUMP_ASSET_PATH + "game_master", master.toBuffer());
this.downloadModels().then(() => {
resolve();
});
});
});
}
export function downloadModels() {
let limit = pogo.platforms.length;
return new Promise((resolve, reject) => {
const dump = (index) => {
let platform = pogo.platforms[index];
let name = platform.name;
let caps = capitalize(name);
caps = name === "ios" ? "iOS" : caps;
pogo.setPlatform(name);
this.print(`Preparing to dump ${caps} assets..`, 36);
this.dumpPkmnModels(CFG.DUMP_ASSET_PATH + name + "/", () => {
this.print(`Dumped ${CFG.MAX_POKEMON_NATIONAL_ID} ${caps} assets successfully!`);
if (++index >= limit) {
this.print("Dumped all assets successfully!");
resolve();
return void 0;
}
dump(index);
});
};
dump(0);
});
}
export function dumpPkmnModels(resolve) {
export function dumpPkmnModels(path, resolve) {
let limit = CFG.MAX_POKEMON_NATIONAL_ID;
@ -177,18 +210,18 @@ export function dumpPkmnModels(resolve) {
downloads.map((item) => {
this.print(`Dumping model ${item.name}..`, 35);
try {
fs.writeFileSync(CFG.DUMP_ASSET_PATH + item.name, item.body);
fs.writeFileSync(path + item.name, item.body);
}
catch (e) {
this.print(`Error while dumping model ${item.name}:` + e, 31);
}
});
if (index >= limit) {
this.print(`Dumped ${limit} assets successfully!`);
//this.print(`Dumped ${limit} assets successfully!`);
resolve();
return void 0;
}
setTimeout(() => dump(index), 2e3);
setTimeout(() => dump(index), 1e3);
});
};

View File

@ -86,4 +86,14 @@ export function idToPkmnBundleName(index) {
return (
"pm" + (index >= 10 ? index >= 100 ? "0" : "00" : "000") + index
);
}
/**
* @param {String} str
* @return {String}
*/
export function capitalize(str) {
return (
str[0].toUpperCase() + str.slice(1)
);
}