From ddb155333d8c0e5cb3931b757a9c93625ec13f0f Mon Sep 17 00:00:00 2001 From: OTCv8 Date: Wed, 27 Nov 2019 23:06:03 +0100 Subject: [PATCH] Fixes for bot and websocket based entergame --- README.md | 2 + api/websockets/app.ts | 96 +++++++++++++++++++ api/websockets/config.json | 36 +++++++ api/websockets/login.ts | 84 ++++++++++++++++ api/websockets/package.json | 22 +++++ api/websockets/tsconfig.json | 11 +++ api/websockets/typings.json | 5 + modules/client_entergame/entergame.lua | 4 +- modules/game_bot/functions/player.lua | 7 +- .../game_bot/functions/player_inventory.lua | 11 ++- modules/game_bot/panels/healing.lua | 4 +- 11 files changed, 277 insertions(+), 5 deletions(-) create mode 100644 api/websockets/app.ts create mode 100644 api/websockets/config.json create mode 100644 api/websockets/login.ts create mode 100644 api/websockets/package.json create mode 100644 api/websockets/tsconfig.json create mode 100644 api/websockets/typings.json diff --git a/README.md b/README.md index eea78c0..37f57a8 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ It's based on https://github.com/edubart/otclient and it's not backward compatib - Rewritten path finding and auto walking - Rewritten walking system with animations - HTTP/HTTPS lua API with JSON support +- WebSocket lua API - Auto updater with failsafe (recovery) mode - New filesystem - File encryption and compression @@ -28,6 +29,7 @@ It's based on https://github.com/edubart/otclient and it's not backward compatib - Much more client options - Removed a lot of useless and outdated things - Advanced bot (https://github.com/OTCv8/otclientv8_bot) +- Linux version - Support for proxies to lower latency and protect against DDoS (extra paid option) - Bot protection (extra paid option) - [Soon] Mobile application for quick authorization diff --git a/api/websockets/app.ts b/api/websockets/app.ts new file mode 100644 index 0000000..272e2a4 --- /dev/null +++ b/api/websockets/app.ts @@ -0,0 +1,96 @@ +import { App } from 'uWebSockets.js'; +import * as Login from './login'; +const config = require("./config.json"); + +let Sessions = new Set(); +let Clients = {}; +let QuickLogin = {}; + +App({ + // options for ssl + key_file_name: 'key.pem', + cert_file_name: 'cert.pem' +}).ws('/*', { + compression: 0, + maxPayloadLength: 64 * 1024, + idleTimeout: 10, + open: (ws, req) => { + ws.uid = null; + Sessions.add(ws); + }, + close: (ws, code, message) => { + if (ws.uid && Clients[ws.uid] == ws) { + delete Clients[ws.uid]; + delete QuickLogin[ws.short_code]; + delete QuickLogin[ws.full_code]; + } + Sessions.delete(ws); + }, + message: (ws, message, isBinary) => { + try { + let data = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(message))); + if (data["type"] == "init") { + if (ws.uid || typeof (data["uid"]) != "string" || data["uid"].length < 10) { + return ws.end(1, "Invalid init message"); // already has an uid or uid is invalid + } + ws.uid = data["uid"]; + ws.version = data["version"] + if (Clients[ws.uid]) { + Clients[ws.uid].close(); + } + ws.short_code = "XXXX"; + ws.full_code = "Login on server otclient.ovh. XXXX"; + Clients[ws.uid] = ws; + QuickLogin[ws.short_code] = ws; + QuickLogin[ws.full_code] = ws; + return ws.send(JSON.stringify({ + "type": "quick_login", + "code": ws.short_code, + "qrcode": ws.full_code, + "message": "" + })); + } + if (!ws.uid) { + return ws.end(2, "Missing uid"); + } + if (data["type"] == "login") { + return Login.login(ws, data["account"], data["password"]); + } + } catch (e) { + try { + return ws.end(3, "Exception"); + } catch (e) {} + } + } +}).any('/login', (res, req) => { + let buffer: string = ""; + res.onData((chunk, last) => { + try { + buffer += String.fromCharCode.apply(null, new Uint8Array(chunk)); + if (!last) { + return; + } + const data = JSON.parse(buffer); + const code = data["code"]; + const client = QuickLogin[code]; + if (!client) { + return res.end("Invalid code"); + } + Login.quickLogin(res, client, data); + } catch (e) { + res.end("Exception"); + } + }); + + res.onAborted(() => { + return res.end("Aborted"); + }); +}).any('/*', (res, req) => { + res.end('404'); +}).listen(config.port, (listenSocket) => { + if (listenSocket) { + console.log(`Listening to port ${config.port}`); + } else { + console.log(`Error, can't listen on port ${config.port}`) + } +}); \ No newline at end of file diff --git a/api/websockets/config.json b/api/websockets/config.json new file mode 100644 index 0000000..37b5ef6 --- /dev/null +++ b/api/websockets/config.json @@ -0,0 +1,36 @@ +{ + "port": 88, + "sql": { + "host": "otclient.ovh", + "user": "otclient", + "password": "otclient", + "database": "otclient" + }, + "maxLogins": 10, + "blockTime": 60, + "hash": "sha1", + "serverName": "OTClientV8", + "serverIP": "otclient.ovh", + "serverPort": 7172, + "version": 1099, + "things": { + "sprites": [ "1099/Tibia.spr", "63d38646597649a55a8be463d6c0fb49" ], + "data": [ "1099/Tibia.dat", "ae7157cfff42f14583d6363e77044df7" ] + }, + "customProtocol": null, + "options": { + "allowFullView": true + }, + "features": { + "22": true, + "25": true, + "30": true, + "80": true, + "90": true, + "95": true + }, + "proxies": { + + }, + "rsa": "109120132967399429278860960508995541528237502902798129123468757937266291492576446330739696001110603907230888610072655818825358503429057592827629436413108566029093628212635953836686562675849720620786279431090218017681061521755056710823876476444260558147179707119674283982419152118103759076030616683978566631413" +} \ No newline at end of file diff --git a/api/websockets/login.ts b/api/websockets/login.ts new file mode 100644 index 0000000..1c64011 --- /dev/null +++ b/api/websockets/login.ts @@ -0,0 +1,84 @@ +import { HttpResponse, WebSocket } from 'uWebSockets.js'; +import * as mysql from 'mysql2/promise'; +import * as crypto from 'crypto'; +const config = require("./config.json"); + +function hash(algorithm: string, data: string): string { + return crypto.createHash(algorithm).update(data).digest("hex"); +} + +function time(): number { + return new Date().getTime(); +} + +export async function login(ws: WebSocket, login: string, password: string) { + let sql : mysql.Connection = null; + try { + sql = await mysql.createConnection({ + host: config.sql.host, + user: config.sql.user, + password: config.sql.password, + database: config.sql.database + }); + + let hash_password = password + if (config.hash == "md5") { + hash_password = hash("md5", password); + } else if (config.hash == "sha1") { + hash_password = hash("sha1", password); + } + + const [accounts, accountFields] = await sql.execute('SELECT * FROM `accounts` where `name` = ? and `password` = ?', [login, hash_password]); + if (accounts.length != 1) { + await sql.end(); + return ws.send(JSON.stringify({"type": "login", "error": "Invalid account/password"}), false); + } + const account = accounts[0]; + const [players, playersFields] = await sql.execute('SELECT * FROM `players` where `account_id` = ?', [account.id]); + await sql.end(); + + let response = { + "type": "login", + "error": "", + "rsa": config.rsa, + "version": config.version, + "things": config.things, + "customProtocol": config.customProtocol, + "session": "", + "characters": [], + "account": {}, + "options": config.options, + "features": config.features, + "proxies": config.proxies + } + + response["session"] = `${login}\n${password}\n\n${time()}`; + + response["account"]["status"] = 0; // 0=ok, 1=frozen, 2=supsended + response["account"]["subStatus"] = 1; // 0=free, 1=premium + response["account"]["premDays"] = 65535; + + for (let i = 0; i < players.length; ++i) { + response.characters.push({ + "name": players[i].name, + "worldName": config.serverName, + "worldIp": config.serverIP, + "worldPort": config.serverPort + }) + } + + console.log(response); + ws.send(JSON.stringify(response), false); + } catch (e) { + try { + await sql.end() + } catch (e) { }; + try { + ws.end(5, "Login exception"); + } catch (e) { }; + } +} + +export async function quickLogin(res : HttpResponse, ws: WebSocket, data: any) { + +} \ No newline at end of file diff --git a/api/websockets/package.json b/api/websockets/package.json new file mode 100644 index 0000000..cb6e0a3 --- /dev/null +++ b/api/websockets/package.json @@ -0,0 +1,22 @@ +{ + "name": "otcv8socks", + "version": "1.0.0", + "description": "Websockets server for otclientv8", + "main": "app.js", + "author": { + "name": "" + }, + "scripts": { + "build": "tsc --build", + "clean": "tsc --build --clean" + }, + "devDependencies": { + "@types/mysql2": "github:types/mysql2", + "@types/node": "^8.0.14", + "typescript": "^3.2.2" + }, + "dependencies": { + "mysql2": "^2.0.1", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v16.4.0" + } +} diff --git a/api/websockets/tsconfig.json b/api/websockets/tsconfig.json new file mode 100644 index 0000000..c4d5e95 --- /dev/null +++ b/api/websockets/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "lib": ["es6"], + "sourceMap": true + }, + "exclude": [ + "node_modules" + ] +} diff --git a/api/websockets/typings.json b/api/websockets/typings.json new file mode 100644 index 0000000..0fe4f0b --- /dev/null +++ b/api/websockets/typings.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "mysql2": "registry:npm/mysql2#1.1.1+20170314181009" + } +} diff --git a/modules/client_entergame/entergame.lua b/modules/client_entergame/entergame.lua index 3cb6844..bc41acd 100644 --- a/modules/client_entergame/entergame.lua +++ b/modules/client_entergame/entergame.lua @@ -310,6 +310,7 @@ function EnterGame.checkWebsocket() if webSocket then webSocket:close() webSocket = nil + newLogin.code:setText("") end return end @@ -328,13 +329,14 @@ function EnterGame.checkWebsocket() webSocket = HTTP.WebSocketJSON(url, { onOpen = function(message, webSocketId) if webSocket and webSocket.id == webSocketId then - webSocket.send({type="init", uid=G.uuid, version=APP_VERSION}) + webSocket.send({type="init", uid=G.UUID, version=APP_VERSION}) end end, onMessage = function(message, webSocketId) if webSocket and webSocket.id == webSocketId then if message.type == "login" then webSocketLoginPacket = nil + EnterGame.hide() onHTTPResult(message, nil) elseif message.type == "quick_login" and message.code and message.qrcode then EnterGame.showNewLogin(message.code, message.qrcode) diff --git a/modules/game_bot/functions/player.lua b/modules/game_bot/functions/player.lua index 1bfd116..e8f7615 100644 --- a/modules/game_bot/functions/player.lua +++ b/modules/game_bot/functions/player.lua @@ -130,7 +130,12 @@ context.useRune = function(itemid, target, lastSpellTimeout) end context.userune = context.useRune -context.findItem = g_game.findItemInContainers +context.findItem = function(itemId, subType) + if subType == nil then + subType = -1 + end + return g_game.findItemInContainers(itemId, subType) +end context.attack = g_game.attack context.cancelAttack = g_game.cancelAttack diff --git a/modules/game_bot/functions/player_inventory.lua b/modules/game_bot/functions/player_inventory.lua index c6a47be..72dea91 100644 --- a/modules/game_bot/functions/player_inventory.lua +++ b/modules/game_bot/functions/player_inventory.lua @@ -28,6 +28,15 @@ context.getFinger = function() return context.getInventoryItem(context.SlotFinge context.getAmmo = function() return context.getInventoryItem(context.SlotAmmo) end context.getPurse = function() return context.getInventoryItem(context.SlotPurse) end - context.getContainers = function() return g_game.getContainers() end context.getContainer = function(index) return g_game.getContainer(index) end + +context.moveToSlot = function(item, slot, count) + if not item then + return + end + if count == nil then + count = item:getCount() + end + return g_game.move(item, {x=65535, y=slot, z=0}, count) +end \ No newline at end of file diff --git a/modules/game_bot/panels/healing.lua b/modules/game_bot/panels/healing.lua index b9f43e6..fd83993 100644 --- a/modules/game_bot/panels/healing.lua +++ b/modules/game_bot/panels/healing.lua @@ -276,9 +276,9 @@ Panels.Equip = function(parent) if slotItem and (slotItem:getId() == item1 or slotItem:getId() == item2) then return end - local newItem = context.findItem(context.storage[panelName].item1, 0) + local newItem = context.findItem(context.storage[panelName].item1) if not newItem then - newItem = context.findItem(context.storage[panelName].item2, 0) + newItem = context.findItem(context.storage[panelName].item2) if not newItem then return end