diff --git a/cfg.js b/cfg.js index d448361..f2c8c94 100644 --- a/cfg.js +++ b/cfg.js @@ -4,10 +4,8 @@ export default { MAX_CONNECTIONS: 64, PORT: 3000, GAME_MODE: 0, - TICK_INTERVAL: 1, SAVE_INTERVAL: 60000, PLAYER_CONNECTION_TIMEOUT: 1800000, - BOOT_TIMEOUT: 10000, MINIMUM_CLIENT_VERSION: "0.33.0", DEFAULT_CONSOLE_COLOR: 32, TRANSFER_ACCOUNTS: false, @@ -40,6 +38,12 @@ export default { // Server debug options DEBUG_DUMP_PATH: "./logs/", DEBUG_DUMP_TRAFFIC: true, - DEBUG_LOG_REQUESTS: true + DEBUG_LOG_REQUESTS: true, + + // Better dont touch these + TICK_INTERVAL: 1, + BOOT_TIMEOUT: 10000, + // Currently supported pokemon + MAX_POKEMON_NATIONAL_ID: 151 } \ No newline at end of file diff --git a/package.json b/package.json index 2a4bd29..74d1e6a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "POGOServer", - "version": "0.3.2", + "version": "0.3.4", "description": "", "repository": { "type": "git", @@ -9,7 +9,7 @@ "scripts": { "test": "echo \"Error: no test specified\"", "babel-node": "babel-node --presets=es2015", - "start": "nodemon --ignore tmp/ --exec npm run babel-node -- ./src/index.js" + "start": "nodemon --ignore tmp/ --ignore data/ --ignore logs/ --exec npm run babel-node -- ./src/index.js" }, "engines": { "node": ">= 6.x", diff --git a/src/index.js b/src/index.js index 3ac6fdc..64f3925 100644 --- a/src/index.js +++ b/src/index.js @@ -43,6 +43,7 @@ class GameServer { collections: {} }; + this.asset = null; this.socket = null; this.cycleInstance = null; @@ -57,6 +58,9 @@ class GameServer { this.clients = []; this.greet(); + + this.print(`Booting Server v${require("../package.json").version}...`, 33); + this.setup(); } @@ -77,40 +81,6 @@ class GameServer { } - createAssetDownloadSession() { - - return new Promise((resolve) => { - pogodown.login({ - provider: String(CFG.DOWNLOAD_PROVIDER).toLowerCase(), - username: CFG.DOWNLOAD_USERNAME, - password: CFG.DOWNLOAD_PASSWORD, - downloadModels: false - }).then((asset) => { - if (asset && asset.digest && asset.digest.length) { - this.print("Created asset download session"); - resolve(asset); - } - else { - this.print("Failed to download asset digest!", 31); - return void 0; - } - }); - }); - - } - - /** - * @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} */ @@ -134,7 +104,7 @@ class GameServer { let buffer = Buffer.concat(chunks); req.body = buffer; player.updateResponse(res); - this.routeRequest(req); + this.routeRequest(req, res); }); }); server.listen(CFG.PORT); diff --git a/src/packets/Responses.FortSearch.js b/src/packets/Responses.FortSearch.js index acddeb5..f09472f 100644 --- a/src/packets/Responses.FortSearch.js +++ b/src/packets/Responses.FortSearch.js @@ -14,7 +14,7 @@ export default function FortSearch(obj) { items_awarded: [ new proto.Inventory.Item.ItemAward({ item_id: proto.Inventory.Item.ItemId.ITEM_MASTER_BALL, - item_count: 10 + item_count: 3 }), new proto.Inventory.Item.ItemAward({ item_id: proto.Inventory.Item.ItemId.ITEM_ULTRA_BALL, diff --git a/src/packets/Responses.GetAssetDigest.js b/src/packets/Responses.GetAssetDigest.js index 2d93009..2da7f6e 100644 --- a/src/packets/Responses.GetAssetDigest.js +++ b/src/packets/Responses.GetAssetDigest.js @@ -7,30 +7,8 @@ import CFG from "../../cfg"; * @param {Request} req * @return {Object} */ -export default function GetAssetDigest(asset, req) { +export default function GetAssetDigest(req) { - let data = proto.Networking.Requests.Messages.GetAssetDigestMessage.decode(req.request_message.toBuffer()); - - let digests = []; - - asset.digest.map((item) => { - digests.push( - new proto.Data.AssetDigestEntry({ - asset_id: item.getAssetId(), - bundle_name: item.getBundleName(), - version: item.getVersion(), - checksum: item.getChecksum(), - size: item.getSize(), - key: item.getKey() - }) - ); - }); - - return ( - new proto.Networking.Responses.GetAssetDigestResponse({ - timestamp_ms: new Date().getTime() * 1e3, - digest: digests - }).encode() - ); + return (fs.readFileSync("data/asset_digest")); } \ No newline at end of file diff --git a/src/packets/Responses.GetDownloadUrls.js b/src/packets/Responses.GetDownloadUrls.js index 0670f0b..b24914a 100644 --- a/src/packets/Responses.GetDownloadUrls.js +++ b/src/packets/Responses.GetDownloadUrls.js @@ -2,6 +2,8 @@ import proto from "../proto"; import DownloadUrlEntry from "./Data.DownloadUrlEntry"; +import CFG from "../../cfg"; + import { getDownloadUrlByAssetId } from "../utils.js"; @@ -9,7 +11,7 @@ import { /** * @return {Object} */ -export default function GetDownloadUrls(asset, req, download) { +export default function GetDownloadUrls(asset, ip, req) { let data = proto.Networking.Requests.Messages.GetDownloadUrlsMessage.decode(req.request_message.toBuffer()); @@ -26,25 +28,20 @@ export default function GetDownloadUrls(asset, req, download) { }; return new Promise((resolve) => { - - download(key).then((asset) => { - console.log(node); - download_urls.push( - new proto.Data.DownloadUrlEntry({ - url: asset[0].asset, - asset_id: key, - size: parseInt(node.size), - checksum: parseInt(node.checksum) - }) - ); - let output = ( - new proto.Networking.Responses.GetDownloadUrlsResponse({ - download_urls: download_urls - }).encode() - ); - resolve(output); - }); - + download_urls.push( + new proto.Data.DownloadUrlEntry({ + asset_id: key, + url: `http://${ip}:${CFG.PORT}/model/${node.bundle_name}`, + size: node.size, + checksum: node.checksum + }) + ); + let output = ( + new proto.Networking.Responses.GetDownloadUrlsResponse({ + download_urls: download_urls + }).encode() + ); + resolve(output); }); } \ No newline at end of file diff --git a/src/packets/Responses.GetMapObjects.js b/src/packets/Responses.GetMapObjects.js index c5f962a..8245212 100644 --- a/src/packets/Responses.GetMapObjects.js +++ b/src/packets/Responses.GetMapObjects.js @@ -11,9 +11,11 @@ import { */ export default function GetMapObjects(player, request) { - var cells = proto.Networking.Requests.Messages.GetMapObjectsMessage.decode(request.request_message.toBuffer()).cell_id; + let cells = proto.Networking.Requests.Messages.GetMapObjectsMessage.decode(request.request_message.toBuffer()).cell_id; - var cellsRes = []; + let cellsRes = []; + + let pokemon_id = 121; cells.forEach((cell) => { cellsRes.push(new proto.Map.MapCell({ @@ -59,7 +61,7 @@ export default function GetMapObjects(player, request) { longitude: longitude, spawn_point_id: "87bdd289c69", pokemon_data: new proto.Data.PokemonData({ - pokemon_id: 145, + pokemon_id: pokemon_id, cp: 277, stamina: 41, stamina_max: 41, @@ -80,7 +82,7 @@ export default function GetMapObjects(player, request) { new proto.Map.Pokemon.MapPokemon({ spawn_point_id: "87bdd289c69", encounter_id: 11810991820755313517, - pokemon_id: 145, + pokemon_id: pokemon_id, latitude: latitude, longitude: longitude, expiration_timestamp_ms: (new Date().getTime() + 1e6) * 1e3 @@ -90,7 +92,7 @@ export default function GetMapObjects(player, request) { cell.nearby_pokemons = [ new proto.Map.Pokemon.NearbyPokemon({ distance_in_meters: 200.0, - pokemon_id: 145 + pokemon_id: pokemon_id }) ]; diff --git a/src/player.js b/src/player.js index f6d72c9..d8581ef 100644 --- a/src/player.js +++ b/src/player.js @@ -53,7 +53,7 @@ class Player { this.gender = 0; this.backpack = 0; - this.tutorial_state = [32, 1, 3, 4, 7]; + this.tutorial_state = []; this.badges = null; this.pokedex = null; diff --git a/src/proto.js b/src/proto.js index 56c7f74..e92dc2b 100644 --- a/src/proto.js +++ b/src/proto.js @@ -1,4 +1,3 @@ import pogo from "node-pogo-protos"; -import path from "path"; export default pogo; \ No newline at end of file diff --git a/src/request.js b/src/request.js index a1bf5cf..58f935c 100644 --- a/src/request.js +++ b/src/request.js @@ -1,3 +1,5 @@ +import fs from "fs"; +import url from "url"; import proto from "./proto"; import CFG from "../cfg"; @@ -9,6 +11,45 @@ import { const REQUEST = proto.Networking.Requests.RequestType; +/** + * @param {Request} req + * @param {Response} res + */ +export function routeRequest(req, res) { + + let player = this.getPlayerByRequest(req); + + let parsed = url.parse(req.url).pathname; + let route = parsed.split("/"); + let host = req.headers.host; + + switch (route[1]) { + case "plfe": + case "custom": + if (route[2] === "rpc") this.onRequest(req); + break; + case "model": + // make sure no random dudes can access download + if (!player.authenticated || !player.email_verified) return void 0; + let name = route[2]; + if (name && name.length > 1) { + fs.readFile("data/" + name, (error, data) => { + if (error) { + this.print(`Error file resolving model ${name}:` + error, 31); + return void 0; + } + this.print(`Sent ${name} to ${player.email}`, 36); + res.end(data); + }); + } + break; + default: + console.log(`Unknown request url: https://${req.headers.host}${req.url}`); + break; + }; + +} + /** * @param {Request} req * @return {String} @@ -118,26 +159,6 @@ export function processRequests(player, requests) { } -/** - * @param {Request} req - */ -export function routeRequest(req) { - - let url = String(req.url); - let route = url.substring(url.lastIndexOf("/") + 1); - let host = req.headers.host; - - switch (route) { - case "rpc": - this.onRequest(req); - break; - default: - console.log(`Unknown request url: https://${req.headers.host}${req.url}`); - break; - }; - -} - /** * @param {Request} req * @return {Boolean} diff --git a/src/response.js b/src/response.js index ec8cfb7..e459e1c 100644 --- a/src/response.js +++ b/src/response.js @@ -67,7 +67,7 @@ export function processResponse(player, req) { buffer = DownloadRemoteConfigVersion(req); break; case REQUEST.GET_ASSET_DIGEST: - buffer = GetAssetDigest(this.asset, req); + buffer = GetAssetDigest(req); break; case REQUEST.GET_PLAYER_PROFILE: buffer = GetPlayerProfile(); @@ -81,7 +81,7 @@ export function processResponse(player, req) { return void 0; break; case REQUEST.GET_DOWNLOAD_URLS: - GetDownloadUrls(this.asset, req, this.generateDownloadUrlByAssetId).then((res) => { + GetDownloadUrls(this.asset, this.getLocalIPv4(), req).then((res) => { resolve(res); }); return void 0; diff --git a/src/setup.js b/src/setup.js index c2a3f1a..2dcd21e 100644 --- a/src/setup.js +++ b/src/setup.js @@ -1,36 +1,91 @@ +import fs from "fs"; +import fse from "fs-extra"; +import pogo from "pogo-asset-downloader"; +import proto from "./proto"; + import CFG from "../cfg"; export function setup() { - this.print(`Booting Server v${require("../package.json").version}...`, 33); + let isFirstRun = !!fse.ensureDirSync("data/"); - let assetSessionLoaded = false; + if (isFirstRun) { + this.print("Preparing to dump required assets..", 36); + setTimeout(() => { + this.onFirstRun(() => { + this.setup(); + }); + }, 1e3); + return void 0; + } - setTimeout(() => { - if (!assetSessionLoaded) { - this.print("Boot timeout, please check your login details!", 31); + this.asset = this.parseAssetDigest(); + + this.setupDatabaseConnection().then(() => { + if (CFG.PORT < 1) { + this.print("Invalid port!", 31); + return void 0; } - }, CFG.BOOT_TIMEOUT); - - this.createAssetDownloadSession().then((asset) => { - assetSessionLoaded = true; - this.asset = asset; - this.setupDatabaseConnection().then(() => { - - if (CFG.PORT < 1) { - this.print("Invalid port!", 31); - return void 0; - } - - this.socket = this.createHTTPServer(); - - setTimeout(this::this.cycle, 1); - - let localIPv4 = this.getLocalIPv4(); - - this.print(`Server running at ${localIPv4}:${CFG.PORT}`); - - }); + this.socket = this.createHTTPServer(); + setTimeout(this::this.cycle, 1); + let localIPv4 = this.getLocalIPv4(); + this.print(`Server running at ${localIPv4}:${CFG.PORT}`); }); +} + +export function parseAssetDigest() { + let asset = null; + try { + asset = proto.Networking.Responses.GetAssetDigestResponse.decode(fs.readFileSync("data/asset_digest")); + } catch (e) { + this.print(e, 31); + } + return (asset); +} + +export function onFirstRun(resolve) { + pogo.login({ + provider: CFG.DOWNLOAD_PROVIDER, // google or ptc + username: CFG.DOWNLOAD_USERNAME, + password: CFG.DOWNLOAD_PASSWORD + }).then((asset) => { + this.print(`Dumping asset digest..`, 35); + fs.writeFileSync("data/asset_digest", asset.toBuffer()); + this.dumpPkmnModels(() => { + resolve(); + }); + }); +} + +export function dumpPkmnModels(resolve) { + + let limit = CFG.MAX_POKEMON_NATIONAL_ID; + + const dump = (index) => { + let ids = []; + if (++index <= limit) ids.push(index); + if (++index <= limit) ids.push(index); + if (++index <= limit) ids.push(index); + pogo.getAssetByPokemonId(ids).then((downloads) => { + downloads.map((item) => { + this.print(`Dumping model ${item.name}..`, 35); + try { + fs.writeFileSync("data/" + 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!`); + resolve(); + return void 0; + } + setTimeout(() => dump(index), 2e3); + }); + }; + + dump(0); + } \ No newline at end of file